diff --git a/src/MongoDB.Bson/IO/BsonBinaryReader.cs b/src/MongoDB.Bson/IO/BsonBinaryReader.cs
index 1cc9f8f255d..5b739c3904a 100644
--- a/src/MongoDB.Bson/IO/BsonBinaryReader.cs
+++ b/src/MongoDB.Bson/IO/BsonBinaryReader.cs
@@ -15,7 +15,6 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.IO;
namespace MongoDB.Bson.IO
@@ -90,7 +89,7 @@ public BsonStream BsonStream
}
///
- /// Gets the settings of the writer.
+ /// Gets the settings of the reader.
///
public new BsonBinaryReaderSettings Settings
{
@@ -214,7 +213,7 @@ public override BsonType ReadBsonType()
{
// insert the element name into the error message
var periodIndex = ex.Message.IndexOf('.');
- var dottedElementName = GenerateDottedElementName();
+ var dottedElementName = BsonBinaryReaderUtils.GenerateDottedElementName(_context, _contextStack.ToArray(), () => _bsonStream.ReadCString(Utf8Encodings.Lenient));
var message = ex.Message.Substring(0, periodIndex) + $" for fieldname \"{dottedElementName}\"" + ex.Message.Substring(periodIndex);
throw new FormatException(message);
}
@@ -758,59 +757,6 @@ protected override void Dispose(bool disposing)
}
// private methods
- private string GenerateDottedElementName()
- {
- string elementName;
- if (_context.ContextType == ContextType.Document)
- {
- try
- {
- elementName = _bsonStream.ReadCString(Utf8Encodings.Lenient);
- }
- catch
- {
- elementName = "?"; // ignore exception
- }
- }
- else if (_context.ContextType == ContextType.Array)
- {
- elementName = _context.ArrayIndex.ToString(NumberFormatInfo.InvariantInfo);
- }
- else
- {
- elementName = "?";
- }
-
- return GenerateDottedElementName(_contextStack.ToArray(), 0, elementName);
- }
-
- private string GenerateDottedElementName(BsonBinaryReaderContext[] contexts, int currentContextIndex, string elementName)
- {
- if (currentContextIndex >= contexts.Length)
- return elementName;
-
- var context = contexts[currentContextIndex];
- var nextIndex = currentContextIndex + 1;
-
- if (context.ContextType == ContextType.Document)
- {
- return GenerateDottedElementName(contexts, nextIndex, (context.ElementName ?? "?") + "." + elementName);
- }
-
- if (context.ContextType == ContextType.Array)
- {
- var indexElementName = context.ArrayIndex.ToString(NumberFormatInfo.InvariantInfo);
- return GenerateDottedElementName(contexts, nextIndex, indexElementName + "." + elementName);
- }
-
- if (nextIndex < contexts.Length)
- {
- return GenerateDottedElementName(contexts, nextIndex, "?." + elementName);
- }
-
- return elementName;
- }
-
private BsonReaderState GetNextState()
{
switch (_context.ContextType)
diff --git a/src/MongoDB.Bson/IO/BsonBinaryReaderUtils.cs b/src/MongoDB.Bson/IO/BsonBinaryReaderUtils.cs
new file mode 100644
index 00000000000..96e32dfee1c
--- /dev/null
+++ b/src/MongoDB.Bson/IO/BsonBinaryReaderUtils.cs
@@ -0,0 +1,103 @@
+/* Copyright 2010-present MongoDB Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Globalization;
+
+namespace MongoDB.Bson.IO;
+
+///
+/// Provides utility methods for working with instances in binary BSON format.
+///
+public static class BsonBinaryReaderUtils
+{
+ ///
+ /// Creates an instance of for the given byte buffer and reader settings.
+ /// The result instance does not own the byte buffer, and will not Dispose it.
+ /// For continuous single chunk buffers an optimized implementation of is created.
+ ///
+ /// The byte buffer containing BSON data.
+ /// The settings to configure the BSON reader.
+ /// An Bson reader.
+ public static IBsonReader CreateBinaryReader(IByteBuffer byteBuffer, BsonBinaryReaderSettings settings)
+ {
+ if (byteBuffer is ReadOnlyMemoryBuffer readOnlyMemoryBuffer)
+ {
+ return new ReadOnlyMemoryBsonReader(readOnlyMemoryBuffer.Memory, new ReadOnlyMemoryReaderSettings(settings));
+ }
+
+ var backingBytes = byteBuffer.AccessBackingBytes(0);
+ if (backingBytes.Count == byteBuffer.Length)
+ {
+ return new ReadOnlyMemoryBsonReader(backingBytes, new ByteBufferSlicer(byteBuffer), new ReadOnlyMemoryReaderSettings(settings));
+ }
+
+ var stream = new ByteBufferStream(byteBuffer, ownsBuffer: false);
+ return new BsonBinaryReader(stream, settings);
+ }
+
+ internal static string GenerateDottedElementName(BsonBinaryReaderContext context, BsonBinaryReaderContext[] parentContexts, Func elementNameReader)
+ {
+ string elementName;
+ if (context.ContextType == ContextType.Document)
+ {
+ try
+ {
+ elementName = elementNameReader();
+ }
+ catch
+ {
+ elementName = "?"; // ignore exception
+ }
+ }
+ else if (context.ContextType == ContextType.Array)
+ {
+ elementName = context.ArrayIndex.ToString(NumberFormatInfo.InvariantInfo);
+ }
+ else
+ {
+ elementName = "?";
+ }
+
+ return GenerateDottedElementName(parentContexts, 0, elementName);
+ }
+
+ private static string GenerateDottedElementName(BsonBinaryReaderContext[] contexts, int currentContextIndex, string elementName)
+ {
+ if (currentContextIndex >= contexts.Length)
+ return elementName;
+
+ var context = contexts[currentContextIndex];
+ var nextIndex = currentContextIndex + 1;
+
+ if (context.ContextType == ContextType.Document)
+ {
+ return GenerateDottedElementName(contexts, nextIndex, (context.ElementName ?? "?") + "." + elementName);
+ }
+
+ if (context.ContextType == ContextType.Array)
+ {
+ var indexElementName = context.ArrayIndex.ToString(NumberFormatInfo.InvariantInfo);
+ return GenerateDottedElementName(contexts, nextIndex, indexElementName + "." + elementName);
+ }
+
+ if (nextIndex < contexts.Length)
+ {
+ return GenerateDottedElementName(contexts, nextIndex, "?." + elementName);
+ }
+
+ return elementName;
+ }
+}
diff --git a/src/MongoDB.Bson/IO/BsonReader.cs b/src/MongoDB.Bson/IO/BsonReader.cs
index 25724b855e7..928f0423560 100644
--- a/src/MongoDB.Bson/IO/BsonReader.cs
+++ b/src/MongoDB.Bson/IO/BsonReader.cs
@@ -466,9 +466,17 @@ protected void ThrowObjectDisposedException()
///
/// The name of the method calling this one.
/// The required BSON type.
- protected void VerifyBsonType(string methodName, BsonType requiredBsonType)
+ protected void VerifyBsonType(string methodName, BsonType requiredBsonType) =>
+ VerifyBsonType(requiredBsonType, methodName);
+
+ ///
+ /// Verifies the current state and BsonType of the reader.
+ ///
+ /// /// The required BSON type.
+ /// The name of the method calling this one.
+ protected void VerifyBsonType(BsonType requiredBsonType, [System.Runtime.CompilerServices.CallerMemberName]string methodName = null)
{
- if (_state == BsonReaderState.Initial || _state == BsonReaderState.ScopeDocument || _state == BsonReaderState.Type)
+ if (_state is BsonReaderState.Initial or BsonReaderState.ScopeDocument or BsonReaderState.Type)
{
ReadBsonType();
}
@@ -483,10 +491,7 @@ protected void VerifyBsonType(string methodName, BsonType requiredBsonType)
}
if (_currentBsonType != requiredBsonType)
{
- var message = string.Format(
- "{0} can only be called when CurrentBsonType is {1}, not when CurrentBsonType is {2}.",
- methodName, requiredBsonType, _currentBsonType);
- throw new InvalidOperationException(message);
+ throw new InvalidOperationException($"{methodName} can only be called when CurrentBsonType is {requiredBsonType}, not when CurrentBsonType is {_currentBsonType}.");
}
}
}
diff --git a/src/MongoDB.Bson/IO/BsonStream.cs b/src/MongoDB.Bson/IO/BsonStream.cs
index 20268155c38..12dc985d3f3 100644
--- a/src/MongoDB.Bson/IO/BsonStream.cs
+++ b/src/MongoDB.Bson/IO/BsonStream.cs
@@ -14,16 +14,13 @@
*/
using System;
-using System.Collections.Generic;
using System.IO;
-using System.Linq;
using System.Text;
-using System.Threading.Tasks;
namespace MongoDB.Bson.IO
{
///
- /// Represents a Stream has additional methods to suport reading and writing BSON values.
+ /// Represents a Stream has additional methods to support reading and writing BSON values.
///
public abstract class BsonStream : Stream
{
diff --git a/src/MongoDB.Bson/IO/BsonStreamExtensions.cs b/src/MongoDB.Bson/IO/BsonStreamExtensions.cs
index 2f3fcc1a557..dbd870fc06d 100644
--- a/src/MongoDB.Bson/IO/BsonStreamExtensions.cs
+++ b/src/MongoDB.Bson/IO/BsonStreamExtensions.cs
@@ -65,6 +65,13 @@ public static void BackpatchSize(this BsonStream stream, long startPosition)
stream.Position = endPosition;
}
+ ///
+ /// Determines whether the specified BSON type is valid.
+ ///
+ /// The BSON type to validate.
+ /// True if the BSON type is valid; otherwise, false.
+ public static bool IsValidBsonType(BsonType bsonType) => __validBsonTypes[(byte)bsonType];
+
///
/// Reads the binary sub type.
///
diff --git a/src/MongoDB.Bson/IO/BsonTrie.cs b/src/MongoDB.Bson/IO/BsonTrie.cs
index 6c276113b44..ecaad36a0ff 100644
--- a/src/MongoDB.Bson/IO/BsonTrie.cs
+++ b/src/MongoDB.Bson/IO/BsonTrie.cs
@@ -93,6 +93,27 @@ public bool TryGetNode(ArraySegment utf8, out BsonTrieNode node)
return node != null;
}
+ ///
+ /// Gets the node associated with the specified element name.
+ ///
+ /// The element name.
+ ///
+ /// When this method returns, contains the node associated with the specified element name, if the key is found;
+ /// otherwise, null. This parameter is passed uninitialized.
+ ///
+ /// True if the node was found; otherwise, false.
+ public bool TryGetNode(ReadOnlySpan utf8, out BsonTrieNode node)
+ {
+ node = _root;
+ for (var i = 0; node != null && i < utf8.Length; i++)
+ {
+ var keyByte = utf8[i];
+ node = node.GetChild(keyByte);
+ }
+
+ return node != null;
+ }
+
///
/// Tries to get the node associated with a name read from a stream.
///
diff --git a/src/MongoDB.Bson/IO/BsonWriter.cs b/src/MongoDB.Bson/IO/BsonWriter.cs
index 3fd705fa31b..a264ff40d9d 100644
--- a/src/MongoDB.Bson/IO/BsonWriter.cs
+++ b/src/MongoDB.Bson/IO/BsonWriter.cs
@@ -347,15 +347,13 @@ public virtual void WriteRawBsonDocument(IByteBuffer slice)
// overridden in BsonBinaryWriter to write the raw bytes to the stream
// for all other streams, deserialize the raw bytes and serialize the resulting document instead
- using (var stream = new ByteBufferStream(slice, ownsBuffer: false))
- using (var bsonReader = new BsonBinaryReader(stream, BsonBinaryReaderSettings.Defaults))
- {
- var deserializationContext = BsonDeserializationContext.CreateRoot(bsonReader);
- var document = BsonDocumentSerializer.Instance.Deserialize(deserializationContext);
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(slice, BsonBinaryReaderSettings.Defaults);
- var serializationContext = BsonSerializationContext.CreateRoot(this);
- BsonDocumentSerializer.Instance.Serialize(serializationContext, document);
- }
+ var deserializationContext = BsonDeserializationContext.CreateRoot(bsonReader);
+ var document = BsonDocumentSerializer.Instance.Deserialize(deserializationContext);
+
+ var serializationContext = BsonSerializationContext.CreateRoot(this);
+ BsonDocumentSerializer.Instance.Serialize(serializationContext, document);
}
///
diff --git a/src/MongoDB.Bson/IO/IBufferSlicer.cs b/src/MongoDB.Bson/IO/IBufferSlicer.cs
new file mode 100644
index 00000000000..93fd06d8ee4
--- /dev/null
+++ b/src/MongoDB.Bson/IO/IBufferSlicer.cs
@@ -0,0 +1,37 @@
+/* Copyright 2010-present MongoDB Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+
+namespace MongoDB.Bson.IO;
+
+internal interface IByteBufferSlicer
+{
+ public IByteBuffer GetSlice(int position, int length);
+}
+
+internal sealed class ReadOnlyMemorySlicer(ReadOnlyMemory ReadOnlyMemory) : IByteBufferSlicer
+{
+ public IByteBuffer GetSlice(int position, int length)
+ {
+ var slice = ReadOnlyMemory.Slice(position, length);
+ return new ReadOnlyMemoryBuffer(slice, new ReadOnlyMemorySlicer(slice));
+ }
+}
+
+internal sealed class ByteBufferSlicer(IByteBuffer ByteBuffer) : IByteBufferSlicer
+{
+ public IByteBuffer GetSlice(int position, int length) => ByteBuffer.GetSlice(position, length);
+}
diff --git a/src/MongoDB.Bson/IO/INameDecoder.cs b/src/MongoDB.Bson/IO/INameDecoder.cs
index 06cd0cf11d0..e11cb168f7f 100644
--- a/src/MongoDB.Bson/IO/INameDecoder.cs
+++ b/src/MongoDB.Bson/IO/INameDecoder.cs
@@ -14,7 +14,6 @@
*/
using System;
-using System.IO;
using System.Text;
namespace MongoDB.Bson.IO
@@ -40,4 +39,9 @@ public interface INameDecoder
/// The name.
void Inform(string name);
}
+
+ internal interface INameDecoderInternal
+ {
+ string Decode(ReadOnlySpan span, UTF8Encoding encoding);
+ }
}
diff --git a/src/MongoDB.Bson/IO/ReadOnlyMemoryBsonReader.cs b/src/MongoDB.Bson/IO/ReadOnlyMemoryBsonReader.cs
new file mode 100644
index 00000000000..ac4c0617d14
--- /dev/null
+++ b/src/MongoDB.Bson/IO/ReadOnlyMemoryBsonReader.cs
@@ -0,0 +1,748 @@
+/* Copyright 2010-present MongoDB Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Buffers.Binary;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MongoDB.Bson.IO;
+
+internal sealed class ReadOnlyMemoryBsonReader : BsonReader
+{
+ private static readonly BsonReaderState[] __stateMap;
+
+ private readonly IByteBufferSlicer _byteBufferSlicer;
+ private readonly ReadOnlyMemory _memory;
+ private int _position;
+
+ private BsonBinaryReaderContext _context;
+ private readonly Stack _contextStack = new(4);
+
+ static ReadOnlyMemoryBsonReader()
+ {
+ var count = Enum.GetValues(typeof(ContextType)).Length;
+ __stateMap = Enumerable.Repeat(BsonReaderState.Closed, count).ToArray();
+ __stateMap[(int)ContextType.Array] = BsonReaderState.Type;
+ __stateMap[(int)ContextType.Document] = BsonReaderState.Type;
+ __stateMap[(int)ContextType.ScopeDocument] = BsonReaderState.Type;
+ __stateMap[(int)ContextType.TopLevel] = BsonReaderState.Initial;
+ }
+
+ public ReadOnlyMemoryBsonReader(ReadOnlyMemory memory)
+ : this(memory, ReadOnlyMemoryReaderSettings.Defaults)
+ {
+ }
+
+ public ReadOnlyMemoryBsonReader(ReadOnlyMemory memory, ReadOnlyMemoryReaderSettings settings)
+ : this(memory, new ReadOnlyMemorySlicer(memory), settings)
+ {
+ }
+
+ public ReadOnlyMemoryBsonReader(ReadOnlyMemory memory, IByteBufferSlicer byteBufferSlicer, ReadOnlyMemoryReaderSettings settings)
+ : base(settings)
+ {
+ if (byteBufferSlicer == null)
+ {
+ throw new ArgumentNullException(nameof(byteBufferSlicer));
+ }
+
+ _memory = memory;
+ _byteBufferSlicer = byteBufferSlicer;
+ _position = 0;
+
+ _context = new BsonBinaryReaderContext(ContextType.TopLevel, 0, 0);
+ }
+
+ ///
+ /// Gets or sets the current position within the BSON data.
+ ///
+ ///
+ /// Thrown when the assigned value is less than 0 or greater than the length of the BSON data.
+ ///
+ public int Position
+ {
+ get => _position;
+ set
+ {
+ if (value < 0 || value > _memory.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), $"Valid range is [{0}..{_memory.Length}]");
+ }
+
+ _position = value;
+ }
+ }
+
+ ///
+ /// Gets the settings of the reader.
+ ///
+ public new ReadOnlyMemoryReaderSettings Settings => (ReadOnlyMemoryReaderSettings)base.Settings;
+
+ ///
+ public override void Close()
+ {
+ // Close can be called on Disposed objects
+ State = BsonReaderState.Closed;
+ }
+
+ ///
+ public override BsonReaderBookmark GetBookmark() =>
+ new BsonBinaryReaderBookmark(State, CurrentBsonType, CurrentName, _context, _contextStack, _position);
+
+ ///
+ public override bool IsAtEndOfFile() => _position >= _memory.Length;
+
+ ///
+#pragma warning disable 618 // about obsolete BsonBinarySubType.OldBinary
+ public override BsonBinaryData ReadBinaryData()
+ {
+ VerifyBsonTypeAndSetNextState(BsonType.Binary);
+
+ var dataSize = ReadSize();
+ var totalSize = dataSize + 1; // data + subtype
+ var span = _memory.Span.Slice(_position, totalSize);
+ _position += totalSize;
+
+ var subType = (BsonBinarySubType)span[0];
+ if (subType == BsonBinarySubType.OldBinary)
+ {
+ // sub type OldBinary has two sizes (for historical reasons)
+ int dataSize2 = ReadSize();
+ if (dataSize2 != dataSize - 4)
+ {
+ throw new FormatException("Binary sub type OldBinary has inconsistent sizes");
+ }
+ dataSize = dataSize2;
+
+ if (Settings.FixOldBinarySubTypeOnInput)
+ {
+ subType = BsonBinarySubType.Binary; // replace obsolete OldBinary with new Binary sub type
+ }
+ }
+
+ var bytes = span.Slice(1, dataSize).ToArray();
+
+ if ((subType == BsonBinarySubType.UuidStandard || subType == BsonBinarySubType.UuidLegacy) &&
+ bytes.Length != 16)
+ {
+ throw new FormatException($"Length must be 16, not {bytes.Length}, when subType is {subType}.");
+ }
+
+ return new BsonBinaryData(bytes, subType);
+ }
+#pragma warning restore 618
+
+ ///
+ public override bool ReadBoolean()
+ {
+ VerifyBsonTypeAndSetNextState(BsonType.Boolean);
+
+ var b = _memory.Span[_position++];
+
+ return b switch
+ {
+ 0 => false,
+ 1 => true,
+ _ => throw new FormatException($"Invalid BsonBoolean value: {b}.")
+ };
+ }
+
+ ///
+ public override BsonType ReadBsonType()
+ {
+ var state = State;
+
+ if (state is BsonReaderState.Initial or BsonReaderState.ScopeDocument)
+ {
+ // there is an implied type of Document for the top level and for scope documents
+ CurrentBsonType = BsonType.Document;
+ State = BsonReaderState.Value;
+ return CurrentBsonType;
+ }
+ if (state != BsonReaderState.Type)
+ {
+ ThrowInvalidState(nameof(ReadBsonType), BsonReaderState.Type);
+ }
+
+ if (_context.ContextType == ContextType.Array)
+ {
+ _context.ArrayIndex++;
+ }
+
+ CurrentBsonType = (BsonType)_memory.Span[_position++];
+
+ if (!BsonStreamExtensions.IsValidBsonType(CurrentBsonType))
+ {
+ var dottedElementName = BsonBinaryReaderUtils.GenerateDottedElementName(_context, _contextStack.ToArray(), () => ReadCStringFromMemory(Utf8Encodings.Lenient));
+ throw new FormatException($"Detected unknown BSON type \"\\x{(int)CurrentBsonType:x2}\" for fieldname \"{dottedElementName}\". Are you using the latest driver version?");
+ }
+
+ if (CurrentBsonType == BsonType.EndOfDocument)
+ {
+ switch (_context.ContextType)
+ {
+ case ContextType.Array:
+ State = BsonReaderState.EndOfArray;
+ return BsonType.EndOfDocument;
+ case ContextType.Document:
+ case ContextType.ScopeDocument:
+ State = BsonReaderState.EndOfDocument;
+ return BsonType.EndOfDocument;
+ default:
+ throw new FormatException($"BsonType EndOfDocument is not valid when ContextType is {_context.ContextType}.");
+ }
+ }
+
+ switch (_context.ContextType)
+ {
+ case ContextType.Array:
+ SkipCString();
+ State = BsonReaderState.Value;
+ break;
+ case ContextType.Document:
+ case ContextType.ScopeDocument:
+ State = BsonReaderState.Name;
+ break;
+ default:
+ throw new BsonInternalException("Unexpected ContextType.");
+ }
+
+ return CurrentBsonType;
+ }
+
+ private void SkipCString()
+ {
+ var offset = _memory.Span.Slice(_position).IndexOf((byte)0);
+ _position += offset + 1;
+ }
+
+ ///
+ public override byte[] ReadBytes()
+ {
+ VerifyBsonTypeAndSetNextState(BsonType.Binary);
+
+ var size = ReadSize();
+ var subType = (BsonBinarySubType)_memory.Span[_position++];
+
+#pragma warning disable 618
+ if (subType != BsonBinarySubType.Binary && subType != BsonBinarySubType.OldBinary)
+ {
+ throw new FormatException($"{nameof(ReadBytes)} requires the binary sub type to be Binary, not {subType}.");
+ }
+#pragma warning restore 618
+
+ var result = _memory.Span.Slice(_position, size).ToArray();
+ _position += size;
+
+ return result;
+ }
+
+ ///
+ public override long ReadDateTime()
+ {
+ VerifyBsonTypeAndSetNextState(BsonType.DateTime);
+
+ var value = ReadInt64FromMemory();
+
+ if (value == BsonConstants.DateTimeMaxValueMillisecondsSinceEpoch + 1)
+ {
+ if (Settings.FixOldDateTimeMaxValueOnInput)
+ {
+ value = BsonConstants.DateTimeMaxValueMillisecondsSinceEpoch;
+ }
+ }
+ return value;
+ }
+
+ ///
+ public override Decimal128 ReadDecimal128()
+ {
+ VerifyBsonTypeAndSetNextState(BsonType.Decimal128);
+
+ var lowBits = (ulong)ReadInt64FromMemory();
+ var highBits = (ulong)ReadInt64FromMemory();
+ return Decimal128.FromIEEEBits(highBits, lowBits);
+ }
+
+ ///
+ public override double ReadDouble()
+ {
+ VerifyBsonTypeAndSetNextState(BsonType.Double);
+
+ var result = BinaryPrimitivesCompat.ReadDoubleLittleEndian(_memory.Span.Slice(_position, 8));
+ _position += 8;
+
+ return result;
+ }
+
+ ///
+ public override void ReadEndArray()
+ {
+ if (_context.ContextType != ContextType.Array)
+ {
+ ThrowInvalidContextType(nameof(ReadEndArray), _context.ContextType, ContextType.Array);
+ }
+ if (State == BsonReaderState.Type)
+ {
+ ReadBsonType(); // will set state to EndOfArray if at end of array
+ }
+ if (State != BsonReaderState.EndOfArray)
+ {
+ ThrowInvalidState(nameof(ReadEndArray), BsonReaderState.EndOfArray);
+ }
+
+ PopContext();
+
+ switch (_context.ContextType)
+ {
+ case ContextType.Array: State = BsonReaderState.Type; break;
+ case ContextType.Document: State = BsonReaderState.Type; break;
+ case ContextType.TopLevel: State = BsonReaderState.Initial; break;
+ default: throw new BsonInternalException("Unexpected ContextType.");
+ }
+ }
+
+ ///
+ public override void ReadEndDocument()
+ {
+ if (_context.ContextType != ContextType.Document && _context.ContextType != ContextType.ScopeDocument)
+ {
+ ThrowInvalidContextType(nameof(ReadEndDocument), _context.ContextType, ContextType.Document, ContextType.ScopeDocument);
+ }
+ if (State == BsonReaderState.Type)
+ {
+ ReadBsonType(); // will set state to EndOfDocument if at end of document
+ }
+ if (State != BsonReaderState.EndOfDocument)
+ {
+ ThrowInvalidState(nameof(ReadEndDocument), BsonReaderState.EndOfDocument);
+ }
+
+ PopContext();
+ if (_context.ContextType == ContextType.JavaScriptWithScope)
+ {
+ PopContext(); // JavaScriptWithScope
+ }
+ switch (_context.ContextType)
+ {
+ case ContextType.Array: State = BsonReaderState.Type; break;
+ case ContextType.Document: State = BsonReaderState.Type; break;
+ case ContextType.TopLevel: State = BsonReaderState.Initial; break;
+ default: throw new BsonInternalException("Unexpected ContextType.");
+ }
+ }
+
+ ///
+ public override int ReadInt32()
+ {
+ VerifyBsonTypeAndSetNextState(BsonType.Int32);
+
+ return ReadInt32FromMemory();
+ }
+
+ ///
+ public override long ReadInt64()
+ {
+ VerifyBsonTypeAndSetNextState(BsonType.Int64);
+
+ return ReadInt64FromMemory();
+ }
+
+ ///
+ public override string ReadJavaScript()
+ {
+ VerifyBsonTypeAndSetNextState(BsonType.JavaScript);
+
+ return ReadStringFromMemory();
+ }
+
+ ///
+ public override string ReadJavaScriptWithScope()
+ {
+ VerifyBsonType(BsonType.JavaScriptWithScope);
+
+ var startPosition = _position;
+ var size = ReadSize();
+
+ PushContext(new(ContextType.JavaScriptWithScope, startPosition, size));
+
+ var code = ReadStringFromMemory();
+
+ State = BsonReaderState.ScopeDocument;
+ return code;
+ }
+
+ ///
+ public override void ReadMaxKey() =>
+ VerifyBsonTypeAndSetNextState(BsonType.MaxKey);
+
+ ///
+ public override void ReadMinKey() =>
+ VerifyBsonTypeAndSetNextState(BsonType.MinKey);
+
+ ///
+ public override string ReadName(INameDecoder nameDecoder)
+ {
+ if (State == BsonReaderState.Type)
+ {
+ ReadBsonType();
+ }
+ if (State != BsonReaderState.Name)
+ {
+ ThrowInvalidState(nameof(ReadName), BsonReaderState.Name);
+ }
+
+ var span = _memory.Span.Slice(_position);
+ var nameEndIndex = span.IndexOf((byte)0);
+ var nameSpan = span.Slice(0, nameEndIndex);
+
+ if (nameDecoder is INameDecoderInternal nameDecoderInternal)
+ {
+ CurrentName = nameDecoderInternal.Decode(nameSpan, Settings.Encoding);
+ }
+ else
+ {
+ throw new Exception($"Name decoder {nameDecoder.GetType()} is not supported");
+ }
+
+ _position += nameEndIndex + 1;
+
+ State = BsonReaderState.Value;
+
+ if (_context.ContextType == ContextType.Document)
+ {
+ _context.ElementName = CurrentName;
+ }
+
+ return CurrentName;
+ }
+
+ ///
+ public override void ReadNull() =>
+ VerifyBsonTypeAndSetNextState(BsonType.Null);
+
+ ///
+ public override ObjectId ReadObjectId()
+ {
+ VerifyBsonTypeAndSetNextState(BsonType.ObjectId);
+
+ var result = new ObjectId(_memory.Span.Slice(_position, 12));
+ _position += 12;
+
+ return result;
+ }
+
+ ///
+ /// Reads a raw BSON array.
+ ///
+ ///
+ /// The raw BSON array.
+ ///
+ public override IByteBuffer ReadRawBsonArray()
+ {
+ VerifyBsonType(BsonType.Array);
+
+ var slice = ReadSliceFromMemory();
+
+ switch (_context.ContextType)
+ {
+ case ContextType.Array: State = BsonReaderState.Type; break;
+ case ContextType.Document: State = BsonReaderState.Type; break;
+ case ContextType.TopLevel: State = BsonReaderState.Initial; break;
+ default: throw new BsonInternalException("Unexpected ContextType.");
+ }
+
+ return slice;
+ }
+
+ ///
+ /// Reads a raw BSON document.
+ ///
+ ///
+ /// The raw BSON document.
+ ///
+ public override IByteBuffer ReadRawBsonDocument()
+ {
+ VerifyBsonType(BsonType.Document);
+
+ var slice = ReadSliceFromMemory();
+
+ if (_context.ContextType == ContextType.JavaScriptWithScope)
+ {
+ PopContext(); // JavaScriptWithScope
+ }
+ switch (_context.ContextType)
+ {
+ case ContextType.Array: State = BsonReaderState.Type; break;
+ case ContextType.Document: State = BsonReaderState.Type; break;
+ case ContextType.TopLevel: State = BsonReaderState.Initial; break;
+ default: throw new BsonInternalException("Unexpected ContextType.");
+ }
+
+ return slice;
+ }
+
+ ///
+ public override BsonRegularExpression ReadRegularExpression()
+ {
+ VerifyBsonTypeAndSetNextState(BsonType.RegularExpression);
+
+ var pattern = ReadCStringFromMemory();
+ var options = ReadCStringFromMemory();
+ return new(pattern, options);
+ }
+
+ ///
+ public override void ReadStartArray()
+ {
+ VerifyBsonType(BsonType.Array);
+
+ var startPosition = _position;
+ var size = ReadSize();
+
+ PushContext(new(ContextType.Array, startPosition, size));
+
+ State = BsonReaderState.Type;
+ }
+
+ ///
+ public override void ReadStartDocument()
+ {
+ VerifyBsonType(BsonType.Document);
+
+ var contextType = (State == BsonReaderState.ScopeDocument) ? ContextType.ScopeDocument : ContextType.Document;
+ var startPosition = _position;
+ var size = ReadSize();
+
+ PushContext(new(contextType, startPosition, size));
+
+ State = BsonReaderState.Type;
+ }
+
+ ///
+ public override string ReadString()
+ {
+ VerifyBsonTypeAndSetNextState(BsonType.String);
+
+ return ReadStringFromMemory();
+ }
+
+ ///
+ public override string ReadSymbol()
+ {
+ VerifyBsonTypeAndSetNextState(BsonType.Symbol);
+
+ return ReadStringFromMemory();
+ }
+
+ ///
+ public override long ReadTimestamp()
+ {
+ VerifyBsonTypeAndSetNextState(BsonType.Timestamp);
+
+ return ReadInt64FromMemory();
+ }
+
+ ///
+ public override void ReadUndefined()
+ {
+ VerifyBsonTypeAndSetNextState(BsonType.Undefined);
+ }
+
+ ///
+ public override void ReturnToBookmark(BsonReaderBookmark bookmark)
+ {
+ var binaryReaderBookmark = (BsonBinaryReaderBookmark)bookmark;
+ State = binaryReaderBookmark.State;
+ CurrentBsonType = binaryReaderBookmark.CurrentBsonType;
+ CurrentName = binaryReaderBookmark.CurrentName;
+ _context = binaryReaderBookmark.RestoreContext(_contextStack);
+ _position = checked((int)binaryReaderBookmark.Position);
+ }
+
+ ///
+ public override void SkipName()
+ {
+ if (State != BsonReaderState.Name)
+ {
+ ThrowInvalidState(nameof(SkipName), BsonReaderState.Name);
+ }
+
+ SkipCString();
+
+ CurrentName = null;
+ State = BsonReaderState.Value;
+
+ if (_context.ContextType == ContextType.Document)
+ {
+ _context.ElementName = CurrentName;
+ }
+ }
+
+ ///
+ public override void SkipValue()
+ {
+ if (State != BsonReaderState.Value)
+ {
+ ThrowInvalidState(nameof(SkipValue), BsonReaderState.Value);
+ }
+
+ int skip;
+ switch (CurrentBsonType)
+ {
+ case BsonType.Array: skip = ReadSize() - 4; break;
+ case BsonType.Binary: skip = ReadSize() + 1; break;
+ case BsonType.Boolean: skip = 1; break;
+ case BsonType.DateTime: skip = 8; break;
+ case BsonType.Document: skip = ReadSize() - 4; break;
+ case BsonType.Decimal128: skip = 16; break;
+ case BsonType.Double: skip = 8; break;
+ case BsonType.Int32: skip = 4; break;
+ case BsonType.Int64: skip = 8; break;
+ case BsonType.JavaScript: skip = ReadSize(); break;
+ case BsonType.JavaScriptWithScope: skip = ReadSize() - 4; break;
+ case BsonType.MaxKey: skip = 0; break;
+ case BsonType.MinKey: skip = 0; break;
+ case BsonType.Null: skip = 0; break;
+ case BsonType.ObjectId: skip = 12; break;
+ case BsonType.RegularExpression: SkipCString(); SkipCString(); skip = 0; break;
+ case BsonType.String: skip = ReadSize(); break;
+ case BsonType.Symbol: skip = ReadSize(); break;
+ case BsonType.Timestamp: skip = 8; break;
+ case BsonType.Undefined: skip = 0; break;
+ default: throw new BsonInternalException("Unexpected BsonType.");
+ }
+
+ _position += skip;
+ State = BsonReaderState.Type;
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ try
+ {
+ Close();
+ }
+ catch
+ {
+ // ignore exceptions
+ }
+ }
+ base.Dispose(disposing);
+ }
+
+ private void PopContext()
+ {
+ var actualSize = _position - _context.StartPosition;
+ if (actualSize != _context.Size)
+ {
+ throw new FormatException($"Expected size to be {_context.Size}, not {actualSize}.");
+ }
+
+ _context = _contextStack.Pop();
+ }
+
+ private void PushContext(BsonBinaryReaderContext newContext)
+ {
+ _contextStack.Push(_context);
+ _context = newContext;
+ }
+
+ private int ReadSize()
+ {
+ var size = ReadInt32FromMemory();
+
+ if (size < 0)
+ {
+ throw new FormatException($"Size {size} is not valid because it is negative.");
+ }
+ if (size > Settings.MaxDocumentSize)
+ {
+ throw new FormatException($"Size {size} is not valid because it is larger than MaxDocumentSize {Settings.MaxDocumentSize}.");
+ }
+
+ return size;
+ }
+
+ private int ReadInt32FromMemory()
+ {
+ var result = BinaryPrimitives.ReadInt32LittleEndian(_memory.Span.Slice(_position));
+ _position += 4;
+ return result;
+ }
+
+ private long ReadInt64FromMemory()
+ {
+ var result = BinaryPrimitives.ReadInt64LittleEndian(_memory.Span.Slice(_position));
+ _position += 8;
+ return result;
+ }
+
+ private IByteBuffer ReadSliceFromMemory()
+ {
+ var memoryAtPosition = _memory.Slice(_position);
+ var length = BinaryPrimitives.ReadInt32LittleEndian(memoryAtPosition.Span);
+
+ var result = _byteBufferSlicer.GetSlice(_position, length);
+ _position += length;
+
+ return result;
+ }
+
+ private string ReadStringFromMemory()
+ {
+ var span = _memory.Span.Slice(_position);
+ var length = BinaryPrimitives.ReadInt32LittleEndian(span);
+
+ if (span[4 + length - 1] != 0)
+ {
+ throw new FormatException("String is missing terminating null byte.");
+ }
+
+ var result = Utf8Helper.DecodeUtf8String(span.Slice(4, length - 1), Settings.Encoding);
+ _position += length + 4;
+
+ return result;
+ }
+
+ private string ReadCStringFromMemory(UTF8Encoding encoding = null)
+ {
+ var span = _memory.Span.Slice(_position);
+ var index = span.IndexOf((byte)0);
+
+ var result = Utf8Helper.DecodeUtf8String(span.Slice(0, index), encoding ?? Settings.Encoding);
+ _position += index + 1;
+
+ return result;
+ }
+
+ private void VerifyBsonTypeAndSetNextState(BsonType requiredBsonType, [System.Runtime.CompilerServices.CallerMemberName]string methodName = null)
+ {
+ VerifyBsonType(requiredBsonType, methodName);
+ var nextState = __stateMap[(int)_context.ContextType];
+
+ if (nextState == BsonReaderState.Closed)
+ {
+ throw new BsonInternalException($"Unexpected ContextType {_context.ContextType}.");
+ }
+
+ State = nextState;
+ }
+}
diff --git a/src/MongoDB.Bson/IO/ReadOnlyMemoryBuffer.cs b/src/MongoDB.Bson/IO/ReadOnlyMemoryBuffer.cs
new file mode 100644
index 00000000000..fab16d07f05
--- /dev/null
+++ b/src/MongoDB.Bson/IO/ReadOnlyMemoryBuffer.cs
@@ -0,0 +1,104 @@
+/* Copyright 2010-present MongoDB Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace MongoDB.Bson.IO;
+
+internal sealed class ReadOnlyMemoryBuffer : IByteBuffer
+{
+ private readonly ReadOnlyMemory _memory;
+ private readonly IByteBufferSlicer _bufferSlicer;
+
+ public ReadOnlyMemoryBuffer(ReadOnlyMemory memory, IByteBufferSlicer bufferSlicer)
+ {
+ if (bufferSlicer == null)
+ {
+ throw new ArgumentNullException(nameof(bufferSlicer));
+ }
+
+ _memory = memory;
+ _bufferSlicer = bufferSlicer;
+ }
+
+ ///
+ public int Capacity => _memory.Length;
+
+ ///
+ public bool IsReadOnly => true;
+
+ ///
+ public int Length
+ {
+ get => _memory.Length;
+ set => ThrowNotWritableException();
+ }
+
+ public ReadOnlyMemory Memory => _memory;
+
+ ///
+ public ArraySegment AccessBackingBytes(int position)
+ {
+ var slice = _memory.Slice(position);
+ if (!MemoryMarshal.TryGetArray(slice, out var segment))
+ {
+ segment = new(slice.ToArray());
+ }
+
+ return segment;
+ }
+
+ ///
+ public void Clear(int position, int count) =>
+ ThrowNotWritableException();
+
+ ///
+ public void Dispose()
+ {
+ }
+
+ ///
+ public void EnsureCapacity(int minimumCapacity) =>
+ ThrowNotWritableException();
+
+ ///
+ public byte GetByte(int position) =>
+ _memory.Span[position];
+
+ ///
+ public void GetBytes(int position, byte[] destination, int offset, int count) =>
+ _memory.Span.Slice(position, count).CopyTo(new Span(destination, offset, count));
+
+ ///
+ public IByteBuffer GetSlice(int position, int length) =>
+ _bufferSlicer.GetSlice(position, length);
+
+ ///
+ public void MakeReadOnly()
+ {
+ }
+
+ ///
+ public void SetByte(int position, byte value) =>
+ ThrowNotWritableException();
+
+ ///
+ public void SetBytes(int position, byte[] source, int offset, int count) =>
+ ThrowNotWritableException();
+
+ private static void ThrowNotWritableException() =>
+ throw new InvalidOperationException($"{nameof(ReadOnlyMemoryBuffer)} is not writable.");
+}
diff --git a/src/MongoDB.Bson/IO/ReadOnlyMemoryReaderSettings.cs b/src/MongoDB.Bson/IO/ReadOnlyMemoryReaderSettings.cs
new file mode 100644
index 00000000000..1d93af8b25d
--- /dev/null
+++ b/src/MongoDB.Bson/IO/ReadOnlyMemoryReaderSettings.cs
@@ -0,0 +1,65 @@
+/* Copyright 2010-present MongoDB Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB.Bson.IO;
+
+///
+/// Represents settings for a .
+///
+public sealed class ReadOnlyMemoryReaderSettings : BsonBinaryReaderSettings
+{
+ // constructors
+ ///
+ /// Initializes a new instance of the ReadOnlyMemoryReaderSettings class.
+ ///
+ public ReadOnlyMemoryReaderSettings()
+ {
+ }
+
+ internal ReadOnlyMemoryReaderSettings(BsonBinaryReaderSettings readerSettings)
+ {
+ Encoding = readerSettings.Encoding;
+ FixOldBinarySubTypeOnInput = readerSettings.FixOldBinarySubTypeOnInput;
+ FixOldDateTimeMaxValueOnInput = readerSettings.FixOldDateTimeMaxValueOnInput;
+ MaxDocumentSize = readerSettings.MaxDocumentSize;
+ }
+
+ // public static properties
+ ///
+ /// Gets the default settings for a
+ ///
+ public static new ReadOnlyMemoryReaderSettings Defaults { get; } = new();
+
+ // public methods
+ ///
+ /// Creates a clone of the settings.
+ ///
+ /// A clone of the settings.
+ public new ReadOnlyMemoryReaderSettings Clone() => (ReadOnlyMemoryReaderSettings)CloneImplementation();
+
+ // protected methods
+ ///
+ /// Creates a clone of the settings.
+ ///
+ /// A clone of the settings.
+ protected override BsonReaderSettings CloneImplementation() =>
+ new ReadOnlyMemoryReaderSettings
+ {
+ Encoding = Encoding,
+ FixOldBinarySubTypeOnInput = FixOldBinarySubTypeOnInput,
+ FixOldDateTimeMaxValueOnInput = FixOldDateTimeMaxValueOnInput,
+ MaxDocumentSize = MaxDocumentSize
+ };
+}
diff --git a/src/MongoDB.Bson/IO/SingleChunkBuffer.cs b/src/MongoDB.Bson/IO/SingleChunkBuffer.cs
index c0435df598a..e9504e66502 100644
--- a/src/MongoDB.Bson/IO/SingleChunkBuffer.cs
+++ b/src/MongoDB.Bson/IO/SingleChunkBuffer.cs
@@ -260,7 +260,7 @@ private void EnsureIsReadOnly()
{
if (!_isReadOnly)
{
- throw new InvalidOperationException("MultiChunkBuffer is not read only.");
+ throw new InvalidOperationException($"{nameof(SingleChunkBuffer)} is not read only.");
}
}
@@ -268,7 +268,7 @@ private void EnsureIsWritable()
{
if (_isReadOnly)
{
- throw new InvalidOperationException("MultiChunkBuffer is not writable.");
+ throw new InvalidOperationException($"{nameof(SingleChunkBuffer)} is not writable.");
}
}
diff --git a/src/MongoDB.Bson/IO/TrieNameDecoder.cs b/src/MongoDB.Bson/IO/TrieNameDecoder.cs
index 88b93701ef6..05fb3987331 100644
--- a/src/MongoDB.Bson/IO/TrieNameDecoder.cs
+++ b/src/MongoDB.Bson/IO/TrieNameDecoder.cs
@@ -13,6 +13,7 @@
* limitations under the License.
*/
+using System;
using System.Text;
namespace MongoDB.Bson.IO
@@ -21,7 +22,7 @@ namespace MongoDB.Bson.IO
/// Represents a Trie-based name decoder that also provides a value.
///
/// The type of the value.
- public class TrieNameDecoder : INameDecoder
+ public class TrieNameDecoder : INameDecoder, INameDecoderInternal
{
// private fields
private bool _found;
@@ -92,6 +93,30 @@ public string Decode(BsonStream stream, UTF8Encoding encoding)
return stream.ReadCString(encoding);
}
+ ///
+ /// Reads the name.
+ ///
+ /// The span.
+ /// The encoding.
+ ///
+ /// The name.
+ ///
+ public string Decode(ReadOnlySpan span, UTF8Encoding encoding)
+ {
+ BsonTrieNode node;
+ if (_trie.TryGetNode(span, out node) && node.HasValue)
+ {
+ _found = true;
+ _value = node.Value;
+ return node.ElementName;
+ }
+
+ _found = false;
+ _value = default;
+
+ return encoding.GetString(span);
+ }
+
///
/// Informs the decoder of an already decoded name (so the decoder can change state if necessary).
///
diff --git a/src/MongoDB.Bson/IO/Utf8EncodingExtensions.cs b/src/MongoDB.Bson/IO/Utf8EncodingExtensions.cs
new file mode 100644
index 00000000000..f89cf474ccc
--- /dev/null
+++ b/src/MongoDB.Bson/IO/Utf8EncodingExtensions.cs
@@ -0,0 +1,33 @@
+/* Copyright 2010-present MongoDB Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Text;
+
+namespace MongoDB.Bson.IO
+{
+ internal static class Utf8EncodingExtensions
+ {
+#if NET472
+ public static unsafe string GetString(this UTF8Encoding encoding, ReadOnlySpan span)
+ {
+ fixed (byte* ptr = span)
+ {
+ return encoding.GetString(ptr, span.Length);
+ }
+ }
+#endif
+ }
+}
diff --git a/src/MongoDB.Bson/IO/Utf8Helper.cs b/src/MongoDB.Bson/IO/Utf8Helper.cs
index e165f470806..d531b458542 100644
--- a/src/MongoDB.Bson/IO/Utf8Helper.cs
+++ b/src/MongoDB.Bson/IO/Utf8Helper.cs
@@ -13,6 +13,7 @@
* limitations under the License.
*/
+using System;
using System.Text;
namespace MongoDB.Bson.IO
@@ -63,5 +64,32 @@ public static string DecodeUtf8String(byte[] bytes, int index, int count, UTF8En
return encoding.GetString(bytes, index, count);
}
+
+ ///
+ /// Decodes a UTF8 string.
+ ///
+ /// The span.
+ /// The encoding.
+ /// The decoded string.
+ public static string DecodeUtf8String(ReadOnlySpan span, UTF8Encoding encoding)
+ {
+ switch (span.Length)
+ {
+ // special case empty strings
+ case 0:
+ return "";
+
+ // special case single character strings
+ case 1:
+ var byte1 = (int)span[0];
+ if (byte1 < __asciiStringTable.Length)
+ {
+ return __asciiStringTable[byte1];
+ }
+ break;
+ }
+
+ return encoding.GetString(span);
+ }
}
}
diff --git a/src/MongoDB.Bson/IO/Utf8NameDecoder.cs b/src/MongoDB.Bson/IO/Utf8NameDecoder.cs
index e6204a4ac66..3e1596e9b6e 100644
--- a/src/MongoDB.Bson/IO/Utf8NameDecoder.cs
+++ b/src/MongoDB.Bson/IO/Utf8NameDecoder.cs
@@ -14,17 +14,17 @@
*/
using System;
-using System.IO;
using System.Text;
+
namespace MongoDB.Bson.IO
{
///
/// Represents a UTF8 name decoder.
///
- public class Utf8NameDecoder : INameDecoder
+ public class Utf8NameDecoder : INameDecoder, INameDecoderInternal
{
// private static fields
- private static readonly Utf8NameDecoder __instance = new Utf8NameDecoder();
+ private static readonly Utf8NameDecoder __instance = new();
// public static properties
///
@@ -53,6 +53,17 @@ public string Decode(BsonStream stream, UTF8Encoding encoding)
return Utf8Helper.DecodeUtf8String(utf8.Array, utf8.Offset, utf8.Count, encoding);
}
+ ///
+ /// Decodes the name.
+ ///
+ /// The span.
+ /// The encoding.
+ ///
+ /// The name.
+ ///
+ public string Decode(ReadOnlySpan span, UTF8Encoding encoding) =>
+ Utf8Helper.DecodeUtf8String(span, encoding);
+
///
/// Informs the decoder of an already decoded name (so the decoder can change state if necessary).
///
diff --git a/src/MongoDB.Bson/MongoDB.Bson.csproj b/src/MongoDB.Bson/MongoDB.Bson.csproj
index 71ffb458fa9..780170537fc 100644
--- a/src/MongoDB.Bson/MongoDB.Bson.csproj
+++ b/src/MongoDB.Bson/MongoDB.Bson.csproj
@@ -2,6 +2,7 @@
..\..\MongoDBLegacy.ruleset
+ true
diff --git a/src/MongoDB.Bson/ObjectModel/BsonType.cs b/src/MongoDB.Bson/ObjectModel/BsonType.cs
index ff3ed6d7402..db9cf899fd0 100644
--- a/src/MongoDB.Bson/ObjectModel/BsonType.cs
+++ b/src/MongoDB.Bson/ObjectModel/BsonType.cs
@@ -13,8 +13,6 @@
* limitations under the License.
*/
-using System;
-
namespace MongoDB.Bson
{
///
diff --git a/src/MongoDB.Bson/ObjectModel/LazyBsonArray.cs b/src/MongoDB.Bson/ObjectModel/LazyBsonArray.cs
index f9232489f4f..4b066dc9aaa 100644
--- a/src/MongoDB.Bson/ObjectModel/LazyBsonArray.cs
+++ b/src/MongoDB.Bson/ObjectModel/LazyBsonArray.cs
@@ -32,8 +32,8 @@ public class LazyBsonArray : MaterializedOnDemandBsonArray
{
// private fields
private IByteBuffer _slice;
- private List _disposableItems = new List();
- private BsonBinaryReaderSettings _readerSettings = BsonBinaryReaderSettings.Defaults;
+ private List _disposableItems = new();
+ private readonly BsonBinaryReaderSettings _readerSettings = BsonBinaryReaderSettings.Defaults;
// constructors
///
@@ -150,7 +150,7 @@ private IByteBuffer CloneSlice()
return _slice.GetSlice(0, _slice.Length);
}
- private LazyBsonArray DeserializeLazyBsonArray(BsonBinaryReader bsonReader)
+ private LazyBsonArray DeserializeLazyBsonArray(IBsonReader bsonReader)
{
var slice = bsonReader.ReadRawBsonArray();
var nestedArray = new LazyBsonArray(slice);
@@ -158,7 +158,7 @@ private LazyBsonArray DeserializeLazyBsonArray(BsonBinaryReader bsonReader)
return nestedArray;
}
- private LazyBsonDocument DeserializeLazyBsonDocument(BsonBinaryReader bsonReader)
+ private LazyBsonDocument DeserializeLazyBsonDocument(IBsonReader bsonReader)
{
var slice = bsonReader.ReadRawBsonDocument();
var nestedDocument = new LazyBsonDocument(slice);
@@ -170,27 +170,24 @@ private IEnumerable MaterializeThisLevel()
{
var values = new List();
- using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
- using (var bsonReader = new BsonBinaryReader(stream, _readerSettings))
- {
- var context = BsonDeserializationContext.CreateRoot(bsonReader);
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings);
+ var context = BsonDeserializationContext.CreateRoot(bsonReader);
- bsonReader.ReadStartDocument();
- BsonType bsonType;
- while ((bsonType = bsonReader.ReadBsonType()) != BsonType.EndOfDocument)
+ bsonReader.ReadStartDocument();
+ BsonType bsonType;
+ while ((bsonType = bsonReader.ReadBsonType()) != BsonType.EndOfDocument)
+ {
+ bsonReader.SkipName();
+ BsonValue value;
+ switch (bsonType)
{
- bsonReader.SkipName();
- BsonValue value;
- switch (bsonType)
- {
- case BsonType.Array: value = DeserializeLazyBsonArray(bsonReader); break;
- case BsonType.Document: value = DeserializeLazyBsonDocument(bsonReader); break;
- default: value = BsonValueSerializer.Instance.Deserialize(context); break;
- }
- values.Add(value);
+ case BsonType.Array: value = DeserializeLazyBsonArray(bsonReader); break;
+ case BsonType.Document: value = DeserializeLazyBsonDocument(bsonReader); break;
+ default: value = BsonValueSerializer.Instance.Deserialize(context); break;
}
- bsonReader.ReadEndDocument();
+ values.Add(value);
}
+ bsonReader.ReadEndDocument();
return values;
}
diff --git a/src/MongoDB.Bson/ObjectModel/LazyBsonDocument.cs b/src/MongoDB.Bson/ObjectModel/LazyBsonDocument.cs
index a95e766a3b8..977ad883bb8 100644
--- a/src/MongoDB.Bson/ObjectModel/LazyBsonDocument.cs
+++ b/src/MongoDB.Bson/ObjectModel/LazyBsonDocument.cs
@@ -14,10 +14,7 @@
*/
using System;
-using System.Collections;
using System.Collections.Generic;
-using System.IO;
-using System.Linq;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
@@ -33,8 +30,8 @@ public class LazyBsonDocument : MaterializedOnDemandBsonDocument
{
// private fields
private IByteBuffer _slice;
- private List _disposableItems = new List();
- private BsonBinaryReaderSettings _readerSettings = BsonBinaryReaderSettings.Defaults;
+ private List _disposableItems = new();
+ private readonly BsonBinaryReaderSettings _readerSettings = BsonBinaryReaderSettings.Defaults;
// constructors
///
@@ -161,7 +158,7 @@ private IByteBuffer CloneSlice()
return _slice.GetSlice(0, _slice.Length);
}
- private LazyBsonArray DeserializeLazyBsonArray(BsonBinaryReader bsonReader)
+ private LazyBsonArray DeserializeLazyBsonArray(IBsonReader bsonReader)
{
var slice = bsonReader.ReadRawBsonArray();
var nestedArray = new LazyBsonArray(slice);
@@ -169,7 +166,7 @@ private LazyBsonArray DeserializeLazyBsonArray(BsonBinaryReader bsonReader)
return nestedArray;
}
- private LazyBsonDocument DeserializeLazyBsonDocument(BsonBinaryReader bsonReader)
+ private LazyBsonDocument DeserializeLazyBsonDocument(IBsonReader bsonReader)
{
var slice = bsonReader.ReadRawBsonDocument();
var nestedDocument = new LazyBsonDocument(slice);
@@ -181,27 +178,24 @@ private IEnumerable MaterializeThisLevel()
{
var elements = new List();
- using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
- using (var bsonReader = new BsonBinaryReader(stream, _readerSettings))
- {
- var context = BsonDeserializationContext.CreateRoot(bsonReader);
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings);
+ var context = BsonDeserializationContext.CreateRoot(bsonReader);
- bsonReader.ReadStartDocument();
- BsonType bsonType;
- while ((bsonType = bsonReader.ReadBsonType()) != BsonType.EndOfDocument)
+ bsonReader.ReadStartDocument();
+ BsonType bsonType;
+ while ((bsonType = bsonReader.ReadBsonType()) != BsonType.EndOfDocument)
+ {
+ var name = bsonReader.ReadName();
+ BsonValue value;
+ switch (bsonType)
{
- var name = bsonReader.ReadName();
- BsonValue value;
- switch (bsonType)
- {
- case BsonType.Array: value = DeserializeLazyBsonArray(bsonReader); break;
- case BsonType.Document: value = DeserializeLazyBsonDocument(bsonReader); break;
- default: value = BsonValueSerializer.Instance.Deserialize(context); break;
- }
- elements.Add(new BsonElement(name, value));
+ case BsonType.Array: value = DeserializeLazyBsonArray(bsonReader); break;
+ case BsonType.Document: value = DeserializeLazyBsonDocument(bsonReader); break;
+ default: value = BsonValueSerializer.Instance.Deserialize(context); break;
}
- bsonReader.ReadEndDocument();
+ elements.Add(new BsonElement(name, value));
}
+ bsonReader.ReadEndDocument();
return elements;
}
diff --git a/src/MongoDB.Bson/ObjectModel/ObjectId.cs b/src/MongoDB.Bson/ObjectModel/ObjectId.cs
index 6cebdff4f7e..2efbc92f9cb 100644
--- a/src/MongoDB.Bson/ObjectModel/ObjectId.cs
+++ b/src/MongoDB.Bson/ObjectModel/ObjectId.cs
@@ -44,14 +44,14 @@ public ObjectId(byte[] bytes)
{
if (bytes == null)
{
- throw new ArgumentNullException("bytes");
+ throw new ArgumentNullException(nameof(bytes));
}
if (bytes.Length != 12)
{
- throw new ArgumentException("Byte array must be 12 bytes long", "bytes");
+ throw new ArgumentException("Byte array must be 12 bytes long", nameof(bytes));
}
- FromByteArray(bytes, 0, out _a, out _b, out _c);
+ FromByteArray(bytes, out _a, out _b, out _c);
}
///
@@ -61,7 +61,12 @@ public ObjectId(byte[] bytes)
/// The index into the byte array where the ObjectId starts.
internal ObjectId(byte[] bytes, int index)
{
- FromByteArray(bytes, index, out _a, out _b, out _c);
+ FromByteArray(new ReadOnlySpan(bytes, index, 12), out _a, out _b, out _c);
+ }
+
+ internal ObjectId(ReadOnlySpan span)
+ {
+ FromByteArray(span, out _a, out _b, out _c);
}
///
@@ -72,11 +77,11 @@ public ObjectId(string value)
{
if (value == null)
{
- throw new ArgumentNullException("value");
+ throw new ArgumentNullException(nameof(value));
}
var bytes = BsonUtils.ParseHexString(value);
- FromByteArray(bytes, 0, out _a, out _b, out _c);
+ FromByteArray(bytes, out _a, out _b, out _c);
}
private ObjectId(int a, int b, int c)
@@ -338,16 +343,16 @@ private static int GetTimestampFromDateTime(DateTime timestamp)
var secondsSinceEpoch = (long)Math.Floor((BsonUtils.ToUniversalTime(timestamp) - BsonConstants.UnixEpoch).TotalSeconds);
if (secondsSinceEpoch < uint.MinValue || secondsSinceEpoch > uint.MaxValue)
{
- throw new ArgumentOutOfRangeException("timestamp");
+ throw new ArgumentOutOfRangeException(nameof(timestamp));
}
return (int)(uint)secondsSinceEpoch;
}
- private static void FromByteArray(byte[] bytes, int offset, out int a, out int b, out int c)
+ private static void FromByteArray(ReadOnlySpan span, out int a, out int b, out int c)
{
- a = (bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3];
- b = (bytes[offset + 4] << 24) | (bytes[offset + 5] << 16) | (bytes[offset + 6] << 8) | bytes[offset + 7];
- c = (bytes[offset + 8] << 24) | (bytes[offset + 9] << 16) | (bytes[offset + 10] << 8) | bytes[offset + 11];
+ a = (span[0] << 24) | (span[1] << 16) | (span[2] << 8) | span[3];
+ b = (span[4] << 24) | (span[5] << 16) | (span[6] << 8) | span[7];
+ c = (span[8] << 24) | (span[9] << 16) | (span[10] << 8) | span[11];
}
// public methods
diff --git a/src/MongoDB.Bson/ObjectModel/RawBsonArray.cs b/src/MongoDB.Bson/ObjectModel/RawBsonArray.cs
index 9ddbd6fcb57..3bfb2573057 100644
--- a/src/MongoDB.Bson/ObjectModel/RawBsonArray.cs
+++ b/src/MongoDB.Bson/ObjectModel/RawBsonArray.cs
@@ -16,7 +16,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
@@ -34,8 +33,8 @@ public class RawBsonArray : BsonArray, IDisposable
// private fields
private bool _disposed;
private IByteBuffer _slice;
- private List _disposableItems = new List();
- private BsonBinaryReaderSettings _readerSettings = BsonBinaryReaderSettings.Defaults;
+ private List _disposableItems = new();
+ private readonly BsonBinaryReaderSettings _readerSettings = BsonBinaryReaderSettings.Defaults;
// constructors
///
@@ -79,22 +78,20 @@ public override int Count
get
{
ThrowIfDisposed();
- using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
- using (var bsonReader = new BsonBinaryReader(stream, _readerSettings))
- {
- var count = 0;
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings);
- bsonReader.ReadStartDocument();
- while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
- {
- bsonReader.SkipName();
- bsonReader.SkipValue();
- count++;
- }
- bsonReader.ReadEndDocument();
+ var count = 0;
- return count;
+ bsonReader.ReadStartDocument();
+ while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ {
+ bsonReader.SkipName();
+ bsonReader.SkipValue();
+ count++;
}
+ bsonReader.ReadEndDocument();
+
+ return count;
}
}
@@ -125,19 +122,16 @@ public override IEnumerable Values
get
{
ThrowIfDisposed();
- using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
- using (var bsonReader = new BsonBinaryReader(stream, _readerSettings))
- {
- var context = BsonDeserializationContext.CreateRoot(bsonReader);
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings);
+ var context = BsonDeserializationContext.CreateRoot(bsonReader);
- bsonReader.ReadStartDocument();
- while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
- {
- bsonReader.SkipName();
- yield return DeserializeBsonValue(context);
- }
- bsonReader.ReadEndDocument();
+ bsonReader.ReadStartDocument();
+ while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ {
+ bsonReader.SkipName();
+ yield return DeserializeBsonValue(context);
}
+ bsonReader.ReadEndDocument();
}
}
@@ -153,31 +147,29 @@ public override BsonValue this[int index]
{
if (index < 0)
{
- throw new ArgumentOutOfRangeException("index");
+ throw new ArgumentOutOfRangeException(nameof(index));
}
ThrowIfDisposed();
- using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
- using (var bsonReader = new BsonBinaryReader(stream, _readerSettings))
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings);
+
+ bsonReader.ReadStartDocument();
+ var i = 0;
+ while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
{
- bsonReader.ReadStartDocument();
- var i = 0;
- while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ bsonReader.SkipName();
+ if (i == index)
{
- bsonReader.SkipName();
- if (i == index)
- {
- var context = BsonDeserializationContext.CreateRoot(bsonReader);
- return DeserializeBsonValue(context);
- }
-
- bsonReader.SkipValue();
- i++;
+ var context = BsonDeserializationContext.CreateRoot(bsonReader);
+ return DeserializeBsonValue(context);
}
- bsonReader.ReadEndDocument();
- throw new ArgumentOutOfRangeException("index");
+ bsonReader.SkipValue();
+ i++;
}
+ bsonReader.ReadEndDocument();
+
+ throw new ArgumentOutOfRangeException(nameof(index));
}
set
{
@@ -311,24 +303,22 @@ public override void Clear()
public override bool Contains(BsonValue value)
{
ThrowIfDisposed();
- using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
- using (var bsonReader = new BsonBinaryReader(stream, _readerSettings))
- {
- var context = BsonDeserializationContext.CreateRoot(bsonReader);
- bsonReader.ReadStartDocument();
- while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings);
+ var context = BsonDeserializationContext.CreateRoot(bsonReader);
+
+ bsonReader.ReadStartDocument();
+ while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ {
+ bsonReader.SkipName();
+ if (DeserializeBsonValue(context).Equals(value))
{
- bsonReader.SkipName();
- if (DeserializeBsonValue(context).Equals(value))
- {
- return true;
- }
+ return true;
}
- bsonReader.ReadEndDocument();
-
- return false;
}
+ bsonReader.ReadEndDocument();
+
+ return false;
}
///
@@ -339,19 +329,17 @@ public override bool Contains(BsonValue value)
public override void CopyTo(BsonValue[] array, int arrayIndex)
{
ThrowIfDisposed();
- using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
- using (var bsonReader = new BsonBinaryReader(stream, _readerSettings))
- {
- var context = BsonDeserializationContext.CreateRoot(bsonReader);
- bsonReader.ReadStartDocument();
- while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
- {
- bsonReader.SkipName();
- array[arrayIndex++] = DeserializeBsonValue(context);
- }
- bsonReader.ReadEndDocument();
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings);
+ var context = BsonDeserializationContext.CreateRoot(bsonReader);
+
+ bsonReader.ReadStartDocument();
+ while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ {
+ bsonReader.SkipName();
+ array[arrayIndex++] = DeserializeBsonValue(context);
}
+ bsonReader.ReadEndDocument();
}
///
@@ -380,19 +368,17 @@ public void Dispose()
public override IEnumerator GetEnumerator()
{
ThrowIfDisposed();
- using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
- using (var bsonReader = new BsonBinaryReader(stream, _readerSettings))
- {
- var context = BsonDeserializationContext.CreateRoot(bsonReader);
- bsonReader.ReadStartDocument();
- while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
- {
- bsonReader.SkipName();
- yield return DeserializeBsonValue(context);
- }
- bsonReader.ReadEndDocument();
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings);
+ var context = BsonDeserializationContext.CreateRoot(bsonReader);
+
+ bsonReader.ReadStartDocument();
+ while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ {
+ bsonReader.SkipName();
+ yield return DeserializeBsonValue(context);
}
+ bsonReader.ReadEndDocument();
}
///
@@ -426,41 +412,39 @@ public override int IndexOf(BsonValue value, int index)
public override int IndexOf(BsonValue value, int index, int count)
{
ThrowIfDisposed();
- using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
- using (var bsonReader = new BsonBinaryReader(stream, _readerSettings))
- {
- var context = BsonDeserializationContext.CreateRoot(bsonReader);
- bsonReader.ReadStartDocument();
- var i = 0;
- while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings);
+ var context = BsonDeserializationContext.CreateRoot(bsonReader);
+
+ bsonReader.ReadStartDocument();
+ var i = 0;
+ while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ {
+ bsonReader.SkipName();
+ if (i >= index)
{
- bsonReader.SkipName();
- if (i >= index)
+ if (count == 0)
{
- if (count == 0)
- {
- return -1;
- }
-
- if (DeserializeBsonValue(context).Equals(value))
- {
- return i;
- }
-
- count--;
+ return -1;
}
- else
+
+ if (DeserializeBsonValue(context).Equals(value))
{
- bsonReader.SkipValue();
+ return i;
}
- i++;
+ count--;
+ }
+ else
+ {
+ bsonReader.SkipValue();
}
- bsonReader.ReadEndDocument();
- return -1;
+ i++;
}
+ bsonReader.ReadEndDocument();
+
+ return -1;
}
///
@@ -486,13 +470,10 @@ public BsonArray Materialize(BsonBinaryReaderSettings binaryReaderSettings)
var document = new BsonDocument("array", this);
var bytes = document.ToBson();
- using (var stream = new MemoryStream(bytes))
- using (var reader = new BsonBinaryReader(stream, binaryReaderSettings))
- {
- var context = BsonDeserializationContext.CreateRoot(reader);
- var materializedDocument = BsonDocumentSerializer.Instance.Deserialize(context);
- return materializedDocument["array"].AsBsonArray;
- }
+ using var reader = new ReadOnlyMemoryBsonReader(bytes, new(binaryReaderSettings));
+ var context = BsonDeserializationContext.CreateRoot(reader);
+ var materializedDocument = BsonDocumentSerializer.Instance.Deserialize(context);
+ return materializedDocument["array"].AsBsonArray;
}
///
diff --git a/src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs b/src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs
index 6fde27bf5c3..b7ac7699b7a 100644
--- a/src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs
+++ b/src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs
@@ -16,8 +16,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
-using System.IO;
-using System.Linq;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
@@ -64,77 +62,64 @@ public RawBsonDocument(byte[] bytes)
}
// public properties
- ///
- /// Gets the number of elements.
- ///
+ ///
public override int ElementCount
{
get
{
ThrowIfDisposed();
- using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
- using (var bsonReader = new BsonBinaryReader(stream, _readerSettings))
- {
- var elementCount = 0;
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings);
- bsonReader.ReadStartDocument();
- while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
- {
- bsonReader.SkipName();
- bsonReader.SkipValue();
- elementCount++;
- }
- bsonReader.ReadEndDocument();
+ var elementCount = 0;
- return elementCount;
+ bsonReader.ReadStartDocument();
+ while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ {
+ bsonReader.SkipName();
+ bsonReader.SkipValue();
+ elementCount++;
}
+ bsonReader.ReadEndDocument();
+
+ return elementCount;
}
}
- ///
- /// Gets the elements.
- ///
+ ///
public override IEnumerable Elements
{
get
{
ThrowIfDisposed();
- using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
- using (var bsonReader = new BsonBinaryReader(stream, _readerSettings))
- {
- var context = BsonDeserializationContext.CreateRoot(bsonReader);
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings);
+ var context = BsonDeserializationContext.CreateRoot(bsonReader);
- bsonReader.ReadStartDocument();
- while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
- {
- var name = bsonReader.ReadName();
- var value = DeserializeBsonValue(context);
- yield return new BsonElement(name, value);
- }
- bsonReader.ReadEndDocument();
+ bsonReader.ReadStartDocument();
+ while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ {
+ var name = bsonReader.ReadName();
+ var value = DeserializeBsonValue(context);
+ yield return new BsonElement(name, value);
}
+ bsonReader.ReadEndDocument();
}
}
- ///
- /// Gets the element names.
- ///
+ ///
public override IEnumerable Names
{
get
{
ThrowIfDisposed();
- using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
- using (var bsonReader = new BsonBinaryReader(stream, _readerSettings))
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings);
+
+ bsonReader.ReadStartDocument();
+ while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
{
- bsonReader.ReadStartDocument();
- while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
- {
- yield return bsonReader.ReadName();
- bsonReader.SkipValue();
- }
- bsonReader.ReadEndDocument();
+ yield return bsonReader.ReadName();
+ bsonReader.SkipValue();
}
+ bsonReader.ReadEndDocument();
}
}
@@ -153,47 +138,34 @@ public IByteBuffer Slice
}
}
- ///
- /// Gets the values.
- ///
+ ///
public override IEnumerable Values
{
get
{
ThrowIfDisposed();
- using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
- using (var bsonReader = new BsonBinaryReader(stream, _readerSettings))
- {
- var context = BsonDeserializationContext.CreateRoot(bsonReader);
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings);
+ var context = BsonDeserializationContext.CreateRoot(bsonReader);
- bsonReader.ReadStartDocument();
- while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
- {
- bsonReader.SkipName();
- yield return DeserializeBsonValue(context);
- }
- bsonReader.ReadEndDocument();
+ bsonReader.ReadStartDocument();
+ while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ {
+ bsonReader.SkipName();
+ yield return DeserializeBsonValue(context);
}
+ bsonReader.ReadEndDocument();
}
}
// public indexers
- ///
- /// Gets or sets a value by position.
- ///
- /// The position.
- /// The value.
+ ///
public override BsonValue this[int index]
{
get { return GetValue(index); }
set { Set(index, value); }
}
- ///
- /// Gets or sets a value by name.
- ///
- /// The name.
- /// The value.
+ ///
public override BsonValue this[string name]
{
get { return GetValue(name); }
@@ -201,243 +173,154 @@ public override BsonValue this[string name]
}
// public methods
- ///
- /// Adds an element to the document.
- ///
- /// The element to add.
- ///
- /// The document (so method calls can be chained).
- ///
+ ///
public override BsonDocument Add(BsonElement element)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
- ///
- /// Creates and adds an element to the document.
- ///
- /// The name of the element.
- /// The value of the element.
- ///
- /// The document (so method calls can be chained).
- ///
+ ///
public override BsonDocument Add(string name, BsonValue value)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
- ///
- /// Creates and adds an element to the document, but only if the condition is true.
- ///
- /// The name of the element.
- /// The value of the element.
- /// Whether to add the element to the document.
- /// The document (so method calls can be chained).
+ ///
public override BsonDocument Add(string name, BsonValue value, bool condition)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
- ///
- /// Adds elements to the document from a dictionary of key/value pairs.
- ///
- /// The dictionary.
- ///
- /// The document (so method calls can be chained).
- ///
+ ///
public override BsonDocument AddRange(Dictionary dictionary)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
- ///
- /// Adds elements to the document from a dictionary of key/value pairs.
- ///
- /// The dictionary.
- ///
- /// The document (so method calls can be chained).
- ///
+ ///
public override BsonDocument AddRange(IDictionary dictionary)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
- ///
- /// Adds a list of elements to the document.
- ///
- /// The list of elements.
- ///
- /// The document (so method calls can be chained).
- ///
+ ///
public override BsonDocument AddRange(IEnumerable elements)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
- ///
- /// Adds elements to the document from a dictionary of key/value pairs.
- ///
- /// The dictionary.
- ///
- /// The document (so method calls can be chained).
- ///
+ ///
public override BsonDocument AddRange(IEnumerable> dictionary)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
- ///
- /// Clears the document (removes all elements).
- ///
+ ///
public override void Clear()
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
- ///
- /// Creates a shallow clone of the document (see also DeepClone).
- ///
- ///
- /// A shallow clone of the document.
- ///
+ ///
public override BsonValue Clone()
{
ThrowIfDisposed();
return new RawBsonDocument(CloneSlice());
}
- ///
- /// Tests whether the document contains an element with the specified name.
- ///
- /// The name of the element to look for.
- ///
- /// True if the document contains an element with the specified name.
- ///
+ ///
public override bool Contains(string name)
{
if (name == null)
{
- throw new ArgumentNullException("name");
+ throw new ArgumentNullException(nameof(name));
}
ThrowIfDisposed();
- using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
- using (var bsonReader = new BsonBinaryReader(stream, _readerSettings))
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings);
+
+ bsonReader.ReadStartDocument();
+ while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
{
- bsonReader.ReadStartDocument();
- while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ if (bsonReader.ReadName() == name)
{
- if (bsonReader.ReadName() == name)
- {
- return true;
- }
- bsonReader.SkipValue();
+ return true;
}
- bsonReader.ReadEndDocument();
-
- return false;
+ bsonReader.SkipValue();
}
+ bsonReader.ReadEndDocument();
+
+ return false;
}
- ///
- /// Tests whether the document contains an element with the specified value.
- ///
- /// The value of the element to look for.
- ///
- /// True if the document contains an element with the specified value.
- ///
+ ///
public override bool ContainsValue(BsonValue value)
{
ThrowIfDisposed();
- using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
- using (var bsonReader = new BsonBinaryReader(stream, _readerSettings))
- {
- var context = BsonDeserializationContext.CreateRoot(bsonReader);
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings);
+ var context = BsonDeserializationContext.CreateRoot(bsonReader);
- bsonReader.ReadStartDocument();
- while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ bsonReader.ReadStartDocument();
+ while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ {
+ bsonReader.SkipName();
+ if (DeserializeBsonValue(context).Equals(value))
{
- bsonReader.SkipName();
- if (DeserializeBsonValue(context).Equals(value))
- {
- return true;
- }
+ return true;
}
- bsonReader.ReadEndDocument();
-
- return false;
}
+ bsonReader.ReadEndDocument();
+
+ return false;
}
- ///
- /// Creates a deep clone of the document (see also Clone).
- ///
- ///
- /// A deep clone of the document.
- ///
+ ///
public override BsonValue DeepClone()
{
ThrowIfDisposed();
return new RawBsonDocument(CloneSlice());
}
- ///
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- ///
+ ///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
- ///
- /// Gets an element of this document.
- ///
- /// The zero based index of the element.
- ///
- /// The element.
- ///
+ ///
public override BsonElement GetElement(int index)
{
if (index < 0)
{
- throw new ArgumentOutOfRangeException("index");
+ throw new ArgumentOutOfRangeException(nameof(index));
}
ThrowIfDisposed();
- using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
- using (var bsonReader = new BsonBinaryReader(stream, _readerSettings))
- {
- var context = BsonDeserializationContext.CreateRoot(bsonReader);
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings);
+ var context = BsonDeserializationContext.CreateRoot(bsonReader);
- bsonReader.ReadStartDocument();
- var i = 0;
- while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ bsonReader.ReadStartDocument();
+ var i = 0;
+ while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ {
+ if (i == index)
{
- if (i == index)
- {
- var name = bsonReader.ReadName();
- var value = DeserializeBsonValue(context);
- return new BsonElement(name, value);
- }
-
- bsonReader.SkipName();
- bsonReader.SkipValue();
- i++;
+ var name = bsonReader.ReadName();
+ var value = DeserializeBsonValue(context);
+ return new BsonElement(name, value);
}
- bsonReader.ReadEndDocument();
- throw new ArgumentOutOfRangeException("index");
+ bsonReader.SkipName();
+ bsonReader.SkipValue();
+ i++;
}
+ bsonReader.ReadEndDocument();
+
+ throw new ArgumentOutOfRangeException(nameof(index));
}
- ///
- /// Gets an element of this document.
- ///
- /// The name of the element.
- ///
- /// A BsonElement.
- ///
+ ///
public override BsonElement GetElement(string name)
{
ThrowIfDisposed();
@@ -447,81 +330,56 @@ public override BsonElement GetElement(string name)
return element;
}
- string message = string.Format("Element '{0}' not found.", name);
- throw new KeyNotFoundException(message);
+ throw new KeyNotFoundException($"Element '{name}' not found.");
}
- ///
- /// Gets an enumerator that can be used to enumerate the elements of this document.
- ///
- ///
- /// An enumerator.
- ///
+ ///
public override IEnumerator GetEnumerator()
{
ThrowIfDisposed();
- using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
- using (var bsonReader = new BsonBinaryReader(stream, _readerSettings))
- {
- var context = BsonDeserializationContext.CreateRoot(bsonReader);
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings);
+ var context = BsonDeserializationContext.CreateRoot(bsonReader);
- bsonReader.ReadStartDocument();
- while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
- {
- var name = bsonReader.ReadName();
- var value = DeserializeBsonValue(context);
- yield return new BsonElement(name, value);
- }
- bsonReader.ReadEndDocument();
+ bsonReader.ReadStartDocument();
+ while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ {
+ var name = bsonReader.ReadName();
+ var value = DeserializeBsonValue(context);
+ yield return new BsonElement(name, value);
}
}
- ///
- /// Gets the value of an element.
- ///
- /// The zero based index of the element.
- ///
- /// The value of the element.
- ///
+ ///
public override BsonValue GetValue(int index)
{
if (index < 0)
{
- throw new ArgumentOutOfRangeException("index");
+ throw new ArgumentOutOfRangeException(nameof(index));
}
ThrowIfDisposed();
- using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
- using (var bsonReader = new BsonBinaryReader(stream, _readerSettings))
- {
- var context = BsonDeserializationContext.CreateRoot(bsonReader);
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings);
+ var context = BsonDeserializationContext.CreateRoot(bsonReader);
- bsonReader.ReadStartDocument();
- var i = 0;
- while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ bsonReader.ReadStartDocument();
+ var i = 0;
+ while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ {
+ bsonReader.SkipName();
+ if (i == index)
{
- bsonReader.SkipName();
- if (i == index)
- {
- return DeserializeBsonValue(context);
- }
-
- bsonReader.SkipValue();
- i++;
+ return DeserializeBsonValue(context);
}
- bsonReader.ReadEndDocument();
- throw new ArgumentOutOfRangeException("index");
+ bsonReader.SkipValue();
+ i++;
}
+ bsonReader.ReadEndDocument();
+
+ throw new ArgumentOutOfRangeException(nameof(index));
}
- ///
- /// Gets the value of an element.
- ///
- /// The name of the element.
- ///
- /// The value of the element.
- ///
+ ///
public override BsonValue GetValue(string name)
{
ThrowIfDisposed();
@@ -535,14 +393,7 @@ public override BsonValue GetValue(string name)
throw new KeyNotFoundException(message);
}
- ///
- /// Gets the value of an element or a default value if the element is not found.
- ///
- /// The name of the element.
- /// The default value returned if the element is not found.
- ///
- /// The value of the element or the default value if the element is not found.
- ///
+ ///
public override BsonValue GetValue(string name, BsonValue defaultValue)
{
ThrowIfDisposed();
@@ -555,11 +406,7 @@ public override BsonValue GetValue(string name, BsonValue defaultValue)
return defaultValue;
}
- ///
- /// Inserts a new element at a specified position.
- ///
- /// The position of the new element.
- /// The element.
+ ///
public override void InsertAt(int index, BsonElement element)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
@@ -573,185 +420,115 @@ public override void InsertAt(int index, BsonElement element)
public BsonDocument Materialize(BsonBinaryReaderSettings binaryReaderSettings)
{
ThrowIfDisposed();
- using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
- using (var reader = new BsonBinaryReader(stream, binaryReaderSettings))
- {
- var context = BsonDeserializationContext.CreateRoot(reader);
- return BsonDocumentSerializer.Instance.Deserialize(context);
- }
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, binaryReaderSettings);
+ var context = BsonDeserializationContext.CreateRoot(bsonReader);
+
+ return BsonDocumentSerializer.Instance.Deserialize(context);
}
- ///
- /// Merges another document into this one. Existing elements are not overwritten.
- ///
- /// The other document.
- ///
- /// The document (so method calls can be chained).
- ///
+ ///
public override BsonDocument Merge(BsonDocument document)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
- ///
- /// Merges another document into this one, specifying whether existing elements are overwritten.
- ///
- /// The other document.
- /// Whether to overwrite existing elements.
- ///
- /// The document (so method calls can be chained).
- ///
+ ///
public override BsonDocument Merge(BsonDocument document, bool overwriteExistingElements)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
- ///
- /// Removes an element from this document (if duplicate element names are allowed
- /// then all elements with this name will be removed).
- ///
- /// The name of the element to remove.
+ ///
public override void Remove(string name)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
- ///
- /// Removes an element from this document.
- ///
- /// The zero based index of the element to remove.
+ ///
public override void RemoveAt(int index)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
- ///
- /// Removes an element from this document.
- ///
- /// The element to remove.
+ ///
public override void RemoveElement(BsonElement element)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
- ///
- /// Sets the value of an element.
- ///
- /// The zero based index of the element whose value is to be set.
- /// The new value.
- ///
- /// The document (so method calls can be chained).
- ///
+ ///
public override BsonDocument Set(int index, BsonValue value)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
- ///
- /// Sets the value of an element (an element will be added if no element with this name is found).
- ///
- /// The name of the element whose value is to be set.
- /// The new value.
- ///
- /// The document (so method calls can be chained).
- ///
+ ///
public override BsonDocument Set(string name, BsonValue value)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
- ///
- /// Sets an element of the document (replaces any existing element with the same name or adds a new element if an element with the same name is not found).
- ///
- /// The new element.
- ///
- /// The document.
- ///
+ ///
public override BsonDocument SetElement(BsonElement element)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
- ///
- /// Sets an element of the document (replacing the existing element at that position).
- ///
- /// The zero based index of the element to replace.
- /// The new element.
- ///
- /// The document.
- ///
+ ///
public override BsonDocument SetElement(int index, BsonElement element)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
- ///
- /// Tries to get an element of this document.
- ///
- /// The name of the element.
- /// The element.
- ///
- /// True if an element with that name was found.
- ///
+ ///
public override bool TryGetElement(string name, out BsonElement element)
{
ThrowIfDisposed();
- using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
- using (var bsonReader = new BsonBinaryReader(stream, _readerSettings))
- {
- var context = BsonDeserializationContext.CreateRoot(bsonReader);
- bsonReader.ReadStartDocument();
- while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
- {
- if (bsonReader.ReadName() == name)
- {
- var value = DeserializeBsonValue(context);
- element = new BsonElement(name, value);
- return true;
- }
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings);
+ var context = BsonDeserializationContext.CreateRoot(bsonReader);
- bsonReader.SkipValue();
+ bsonReader.ReadStartDocument();
+ while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ {
+ if (bsonReader.ReadName() == name)
+ {
+ var value = DeserializeBsonValue(context);
+ element = new BsonElement(name, value);
+ return true;
}
- bsonReader.ReadEndDocument();
- element = default(BsonElement);
- return false;
+ bsonReader.SkipValue();
}
+ bsonReader.ReadEndDocument();
+
+ element = default;
+ return false;
}
- ///
- /// Tries to get the value of an element of this document.
- ///
- /// The name of the element.
- /// The value of the element.
- ///
- /// True if an element with that name was found.
- ///
+ ///
public override bool TryGetValue(string name, out BsonValue value)
{
ThrowIfDisposed();
- using (var stream = new ByteBufferStream(_slice, ownsBuffer: false))
- using (var bsonReader = new BsonBinaryReader(stream, _readerSettings))
- {
- var context = BsonDeserializationContext.CreateRoot(bsonReader);
- bsonReader.ReadStartDocument();
- while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
- {
- if (bsonReader.ReadName() == name)
- {
- value = DeserializeBsonValue(context);
- return true;
- }
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings);
+ var context = BsonDeserializationContext.CreateRoot(bsonReader);
- bsonReader.SkipValue();
+ bsonReader.ReadStartDocument();
+ while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
+ {
+ if (bsonReader.ReadName() == name)
+ {
+ value = DeserializeBsonValue(context);
+ return true;
}
- bsonReader.ReadEndDocument();
- value = null;
- return false;
+ bsonReader.SkipValue();
}
+ bsonReader.ReadEndDocument();
+
+ value = null;
+ return false;
}
// protected methods
diff --git a/src/MongoDB.Bson/Properties/AssemblyInfo.cs b/src/MongoDB.Bson/Properties/AssemblyInfo.cs
index ee574295e6c..856f7c167bd 100644
--- a/src/MongoDB.Bson/Properties/AssemblyInfo.cs
+++ b/src/MongoDB.Bson/Properties/AssemblyInfo.cs
@@ -26,5 +26,6 @@
// as Xamarin.iOS/Xamarin.Mac.
[assembly: Preserve(AllMembers = true)]
+[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
[assembly: InternalsVisibleTo("MongoDB.Bson.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010035287f0d3883c0a075c88e0cda3ce93b621003ecbd5e920d4a8c7238564f4d2f4f68116aca28c9b21341dc3a877679c14556192b2b2f5fe2c11d624e0894d308ff7b94bf6fd72aef1b41017ffe2572e99019d1c61963e68cd0ed67734a42cb333b808e3867cbe631937214e32e409fb1fa62fdb69d494c2530e64a40e417d6ee")]
[assembly: InternalsVisibleTo("MongoDB.Analyzer.MQLGenerator, PublicKey=002400000480000094000000060200000024000052534131000400000100010035287f0d3883c0a075c88e0cda3ce93b621003ecbd5e920d4a8c7238564f4d2f4f68116aca28c9b21341dc3a877679c14556192b2b2f5fe2c11d624e0894d308ff7b94bf6fd72aef1b41017ffe2572e99019d1c61963e68cd0ed67734a42cb333b808e3867cbe631937214e32e409fb1fa62fdb69d494c2530e64a40e417d6ee")]
diff --git a/src/MongoDB.Driver/Core/Misc/Ensure.cs b/src/MongoDB.Driver/Core/Misc/Ensure.cs
index aff914d07fe..7ff08b3283b 100644
--- a/src/MongoDB.Driver/Core/Misc/Ensure.cs
+++ b/src/MongoDB.Driver/Core/Misc/Ensure.cs
@@ -83,7 +83,7 @@ public static T IsEqualTo(T value, T comparand, string paramName)
///
/// Ensures that the value of a parameter is greater than a comparand.
///
- /// Type type of the value.
+ /// Type of the value.
/// The value of the parameter.
/// The comparand.
/// The name of the parameter.
diff --git a/src/MongoDB.Driver/Core/Operations/ChangeStreamCursor.cs b/src/MongoDB.Driver/Core/Operations/ChangeStreamCursor.cs
index f43cb30a168..7f3399aa20c 100644
--- a/src/MongoDB.Driver/Core/Operations/ChangeStreamCursor.cs
+++ b/src/MongoDB.Driver/Core/Operations/ChangeStreamCursor.cs
@@ -159,15 +159,12 @@ public BsonDocument GetResumeToken()
}
// private methods
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
private TDocument DeserializeDocument(RawBsonDocument rawDocument)
{
- using (var stream = new ByteBufferStream(rawDocument.Slice, ownsBuffer: false))
- using (var reader = new BsonBinaryReader(stream))
- {
- var context = BsonDeserializationContext.CreateRoot(reader);
- return _documentSerializer.Deserialize(context);
- }
+ using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(rawDocument.Slice, BsonBinaryReaderSettings.Defaults);
+ var context = BsonDeserializationContext.CreateRoot(bsonReader);
+
+ return _documentSerializer.Deserialize(context);
}
private IEnumerable DeserializeDocuments(IEnumerable rawDocuments)
diff --git a/src/MongoDB.Driver/Core/Operations/CursorBatchDeserializationHelper.cs b/src/MongoDB.Driver/Core/Operations/CursorBatchDeserializationHelper.cs
index 27ff4252dc0..8a2883ad5ca 100644
--- a/src/MongoDB.Driver/Core/Operations/CursorBatchDeserializationHelper.cs
+++ b/src/MongoDB.Driver/Core/Operations/CursorBatchDeserializationHelper.cs
@@ -1,4 +1,4 @@
-/* Copyright 2015-present MongoDB Inc.
+/* Copyright 2010-present MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,6 @@ internal static class CursorBatchDeserializationHelper
/// The document serializer.
/// The message encoder settings.
/// The documents.
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
public static List DeserializeBatch(RawBsonArray batch, IBsonSerializer documentSerializer, MessageEncoderSettings messageEncoderSettings)
{
var documents = new List();
@@ -44,23 +43,20 @@ public static List DeserializeBatch(RawBsonArray batch, IB
if (messageEncoderSettings != null)
{
readerSettings.Encoding = messageEncoderSettings.GetOrDefault(MessageEncoderSettingsName.ReadEncoding, Utf8Encodings.Strict);
- };
+ }
- using (var stream = new ByteBufferStream(batch.Slice, ownsBuffer: false))
- using (var reader = new BsonBinaryReader(stream, readerSettings))
- {
- var context = BsonDeserializationContext.CreateRoot(reader);
+ using var reader = BsonBinaryReaderUtils.CreateBinaryReader(batch.Slice, readerSettings);
+ var context = BsonDeserializationContext.CreateRoot(reader);
- // BSON requires that the top level object be a document, but an array looks close enough to a document that we can pretend it is one
- reader.ReadStartDocument();
- while (reader.ReadBsonType() != 0)
- {
- reader.SkipName(); // skip over the index pseudo names
- var document = documentSerializer.Deserialize(context);
- documents.Add(document);
- }
- reader.ReadEndDocument();
+ // BSON requires that the top level object be a document, but an array looks close enough to a document that we can pretend it is one
+ reader.ReadStartDocument();
+ while (reader.ReadBsonType() != 0)
+ {
+ reader.SkipName(); // skip over the index pseudo names
+ var document = documentSerializer.Deserialize(context);
+ documents.Add(document);
}
+ reader.ReadEndDocument();
return documents;
}
diff --git a/src/MongoDB.Driver/Core/Operations/FindAndModifyOperationBase.cs b/src/MongoDB.Driver/Core/Operations/FindAndModifyOperationBase.cs
index 1b346fe013f..5b8ac3eaba0 100644
--- a/src/MongoDB.Driver/Core/Operations/FindAndModifyOperationBase.cs
+++ b/src/MongoDB.Driver/Core/Operations/FindAndModifyOperationBase.cs
@@ -14,7 +14,6 @@
*/
using System;
-using System.Text;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Bson.IO;
@@ -125,10 +124,9 @@ public TResult ExecuteAttempt(OperationContext operationContext, RetryableWriteC
using (var channelBinding = new ChannelReadWriteBinding(channelSource.Server, channel, binding.Session.Fork()))
{
var operation = CreateOperation(operationContext, channelBinding.Session, channel.ConnectionDescription, transactionNumber);
- using (var rawBsonDocument = operation.Execute(operationContext, channelBinding))
- {
- return ProcessCommandResult(channel.ConnectionDescription.ConnectionId, rawBsonDocument);
- }
+ using var rawBsonDocument = operation.Execute(operationContext, channelBinding);
+
+ return ProcessCommandResult(rawBsonDocument);
}
}
@@ -141,10 +139,9 @@ public async Task ExecuteAttemptAsync(OperationContext operationContext
using (var channelBinding = new ChannelReadWriteBinding(channelSource.Server, channel, binding.Session.Fork()))
{
var operation = CreateOperation(operationContext, channelBinding.Session, channel.ConnectionDescription, transactionNumber);
- using (var rawBsonDocument = await operation.ExecuteAsync(operationContext, channelBinding).ConfigureAwait(false))
- {
- return ProcessCommandResult(channel.ConnectionDescription.ConnectionId, rawBsonDocument);
- }
+ using var rawBsonDocument = await operation.ExecuteAsync(operationContext, channelBinding).ConfigureAwait(false);
+
+ return ProcessCommandResult(rawBsonDocument);
}
}
@@ -163,20 +160,16 @@ private WriteCommandOperation CreateOperation(OperationContext
};
}
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
- private TResult ProcessCommandResult(ConnectionId connectionId, RawBsonDocument rawBsonDocument)
+ private TResult ProcessCommandResult(RawBsonDocument rawBsonDocument)
{
var binaryReaderSettings = new BsonBinaryReaderSettings
{
- Encoding = _messageEncoderSettings.GetOrDefault(MessageEncoderSettingsName.ReadEncoding, Utf8Encodings.Strict)
+ Encoding = _messageEncoderSettings.GetOrDefault(MessageEncoderSettingsName.ReadEncoding, Utf8Encodings.Strict)
};
- using (var stream = new ByteBufferStream(rawBsonDocument.Slice, ownsBuffer: false))
- using (var reader = new BsonBinaryReader(stream, binaryReaderSettings))
- {
- var context = BsonDeserializationContext.CreateRoot(reader);
- return _resultSerializer.Deserialize(context);
- }
+ using var reader = BsonBinaryReaderUtils.CreateBinaryReader(rawBsonDocument.Slice, binaryReaderSettings);
+ var context = BsonDeserializationContext.CreateRoot(reader);
+ return _resultSerializer.Deserialize(context);
}
}
}
diff --git a/src/MongoDB.Driver/Core/WireProtocol/CommandUsingCommandMessageWireProtocol.cs b/src/MongoDB.Driver/Core/WireProtocol/CommandUsingCommandMessageWireProtocol.cs
index 4b7af6b7209..288f0007076 100644
--- a/src/MongoDB.Driver/Core/WireProtocol/CommandUsingCommandMessageWireProtocol.cs
+++ b/src/MongoDB.Driver/Core/WireProtocol/CommandUsingCommandMessageWireProtocol.cs
@@ -440,7 +440,6 @@ private void MessageWasProbablySent(CommandRequestMessage message)
}
}
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
private TCommandResult ProcessResponse(ConnectionId connectionId, CommandMessage responseMessage)
{
using (new CommandMessageDisposer(responseMessage))
@@ -532,14 +531,9 @@ private TCommandResult ProcessResponse(ConnectionId connectionId, CommandMessage
throw new MongoWriteConcernException(connectionId, message, writeConcernResult);
}
- using (var stream = new ByteBufferStream(rawDocument.Slice, ownsBuffer: false))
- {
- using (var reader = new BsonBinaryReader(stream, binaryReaderSettings))
- {
- var context = BsonDeserializationContext.CreateRoot(reader);
- return _resultSerializer.Deserialize(context);
- }
- }
+ using var reader = BsonBinaryReaderUtils.CreateBinaryReader(rawDocument.Slice, binaryReaderSettings);
+ var context = BsonDeserializationContext.CreateRoot(reader);
+ return _resultSerializer.Deserialize(context);
}
}
diff --git a/src/MongoDB.Driver/Core/WireProtocol/CommandUsingQueryMessageWireProtocol.cs b/src/MongoDB.Driver/Core/WireProtocol/CommandUsingQueryMessageWireProtocol.cs
index ecfb53f0f70..f625704e6dc 100644
--- a/src/MongoDB.Driver/Core/WireProtocol/CommandUsingQueryMessageWireProtocol.cs
+++ b/src/MongoDB.Driver/Core/WireProtocol/CommandUsingQueryMessageWireProtocol.cs
@@ -235,7 +235,6 @@ private void IgnoreResponse(OperationContext operationContext, IConnection conne
connection.ReceiveMessageAsync(operationContext, message.RequestId, encoderSelector, _messageEncoderSettings).IgnoreExceptions();
}
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
private TCommandResult ProcessReply(ConnectionId connectionId, ReplyMessage reply)
{
if (reply.NumberReturned == 0)
diff --git a/tests/MongoDB.Bson.Tests/IO/BsonBinaryReaderUtilsTests.cs b/tests/MongoDB.Bson.Tests/IO/BsonBinaryReaderUtilsTests.cs
new file mode 100644
index 00000000000..29cef3d0478
--- /dev/null
+++ b/tests/MongoDB.Bson.Tests/IO/BsonBinaryReaderUtilsTests.cs
@@ -0,0 +1,85 @@
+/* Copyright 2010-present MongoDB Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Linq;
+using FluentAssertions;
+using MongoDB.Bson.IO;
+using Xunit;
+
+namespace MongoDB.Bson.Tests.IO;
+
+public class BsonBinaryReaderUtilsTests
+{
+ [Fact]
+ public void CreateBinaryReader_should_create_ReadOnlyMemoryBsonReader_for_ReadOnlyMemoryBuffer()
+ {
+ var bytes = new byte[] { 29, 0, 0, 0, 5, 120, 0, 16, 0, 0, 0, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0 };
+ using var buffer = new ReadOnlyMemoryBuffer(bytes, new ReadOnlyMemorySlicer(bytes));
+ using var reader = BsonBinaryReaderUtils.CreateBinaryReader(buffer, new());
+
+ reader.Should().BeOfType();
+
+ ValidateSliceType(reader, bytes.Length);
+ }
+
+ [Fact]
+ public void CreateBinaryReader_should_create_ReadOnlyMemoryBsonReader_for_SingleChunkBuffer()
+ {
+ var bytes = new byte[] { 29, 0, 0, 0, 5, 120, 0, 16, 0, 0, 0, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0 };
+
+ var buffer = new SingleChunkBuffer(new ByteArrayChunk(bytes), bytes.Length, true);
+ var reader = BsonBinaryReaderUtils.CreateBinaryReader(buffer, new());
+
+ reader.Should().BeOfType();
+
+ ValidateSliceType(reader, bytes.Length);
+ }
+
+ [Fact]
+ public void CreateBinaryReader_should_create_ReadOnlyMemoryBsonReader_for_MultiChunkBuffer_with_single_chunk()
+ {
+ var bytes = new byte[] { 29, 0, 0, 0, 5, 120, 0, 16, 0, 0, 0, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0 };
+
+ var buffer = new MultiChunkBuffer([new ByteArrayChunk(bytes)], bytes.Length, true);
+ var reader = BsonBinaryReaderUtils.CreateBinaryReader(buffer, new());
+
+ reader.Should().BeOfType();
+
+ ValidateSliceType(reader, bytes.Length);
+ }
+
+ [Fact]
+ public void CreateBinaryReader_should_create_BsonBinaryReader_for_MultiChunkBuffer_with_multiple_chunks()
+ {
+ var bytes = new byte[] { 29, 0, 0, 0, 5, 120, 0, 16, 0, 0, 0, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0 };
+
+ var buffer = new MultiChunkBuffer([new ByteArrayChunk(bytes.Take(10).ToArray()), new ByteArrayChunk(bytes.Skip(10).ToArray())], bytes.Length, true);
+ var reader = BsonBinaryReaderUtils.CreateBinaryReader(buffer, new());
+
+ reader.Should().BeOfType();
+
+ ValidateSliceType(reader, bytes.Length);
+ }
+
+ private static void ValidateSliceType(IBsonReader reader, int expectedSliceSize)
+ where T : IByteBuffer
+ {
+ // Check the usage of the correct slicer by reading a slice
+ var slice = reader.ReadRawBsonDocument();
+
+ slice.Length.Should().Be(expectedSliceSize);
+ slice.Should().BeOfType();
+ }
+}
diff --git a/tests/MongoDB.Bson.Tests/IO/IByteBufferSlicerTests.cs b/tests/MongoDB.Bson.Tests/IO/IByteBufferSlicerTests.cs
new file mode 100644
index 00000000000..848c7c1f5b8
--- /dev/null
+++ b/tests/MongoDB.Bson.Tests/IO/IByteBufferSlicerTests.cs
@@ -0,0 +1,54 @@
+/* Copyright 2010-present MongoDB Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using FluentAssertions;
+using MongoDB.Bson.IO;
+using Moq;
+using Xunit;
+
+namespace MongoDB.Bson.Tests.IO;
+
+public class ByteBufferSlicerTests
+{
+ [Fact]
+ public void ByteBufferSlicer_should_use_underlying_buffer_slice()
+ {
+ using var byteBufferExpected = new ByteArrayBuffer([1, 2, 3]);
+
+ var byteBufferMock = new Mock();
+ byteBufferMock.Setup(b => b.GetSlice(0, 3)).Returns(byteBufferExpected);
+
+ var slicer = new ByteBufferSlicer(byteBufferMock.Object);
+
+ using var slice = slicer.GetSlice(0, 3);
+
+ slice.Should().Be(byteBufferExpected);
+ byteBufferMock.Verify(b => b.GetSlice(0, 3), Times.Once);
+ }
+
+ [Fact]
+ public void ReadOnlyMemorySlicer_should_return_ReadOnlyMemoryBuffer()
+ {
+ byte[] bytes = [1, 2, 3];
+
+ var slicer = new ReadOnlyMemorySlicer(bytes);
+
+ using var slice = slicer.GetSlice(0, 1);
+
+ var buffer = slice.Should().BeOfType().Subject;
+ buffer.Memory.Length.Should().Be(1);
+ buffer.Memory.Span[0].Should().Be(1);
+ }
+}
diff --git a/tests/MongoDB.Bson.Tests/IO/ReadOnlyMemoryBufferTests.cs b/tests/MongoDB.Bson.Tests/IO/ReadOnlyMemoryBufferTests.cs
new file mode 100644
index 00000000000..af95b252b49
--- /dev/null
+++ b/tests/MongoDB.Bson.Tests/IO/ReadOnlyMemoryBufferTests.cs
@@ -0,0 +1,374 @@
+/* Copyright 2010-present MongoDB Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Linq;
+using FluentAssertions;
+using MongoDB.Bson.IO;
+using MongoDB.TestHelpers.XunitExtensions;
+using Moq;
+using Xunit;
+
+namespace MongoDB.Bson.Tests.IO;
+
+public class ReadOnlyMemoryBufferTests
+{
+ [Theory]
+ [InlineData(0, 0)]
+ [InlineData(1, 1)]
+ [InlineData(2, 2)]
+ public void AccessBackingBytes_should_return_expected_result_for_length(int length, int expectedCount)
+ {
+ var bytes = Enumerable.Range(0, length).Select(i => (byte)i).ToArray();
+ var subject = CreateSubject(bytes);
+
+ var result = subject.AccessBackingBytes(0);
+
+ result.Array.Should().BeEquivalentTo(bytes);
+ result.Offset.Should().Be(0);
+ result.Count.Should().Be(expectedCount);
+ }
+
+ [Theory]
+ [InlineData(0, 2)]
+ [InlineData(1, 1)]
+ [InlineData(2, 0)]
+ public void AccessBackingBytes_should_return_expected_result_for_position(int position, int expectedCount)
+ {
+ var bytes = new byte[2];
+ var subject = CreateSubject(bytes);
+
+ var result = subject.AccessBackingBytes(position);
+
+ result.ToArray().ShouldBeEquivalentTo(bytes.Skip(position).ToArray());
+ result.Offset.Should().Be(position);
+ result.Count.Should().Be(expectedCount);
+ }
+
+ [Theory]
+ [ParameterAttributeData]
+ public void AccessBackingBytes_should_throw_when_position_is_invalid([Values(-1, 3)]int position)
+ {
+ var subject = CreateSubject(2);
+
+ var exception = Record.Exception(() => subject.AccessBackingBytes(position));
+ exception.Should().BeOfType();
+ }
+
+ [Theory]
+ [InlineData(2)]
+ [InlineData(4)]
+ public void Capacity_get_should_return_expected_result(int length)
+ {
+ var subject = CreateSubject(length);
+
+ var result = subject.Capacity;
+
+ result.Should().Be(length);
+ }
+
+ [Fact]
+ public void Clear_should_throw()
+ {
+ var bytes = new byte[] { 1, 2 };
+ var subject = CreateSubject(bytes);
+
+ ValidateWritableException(() => subject.Clear(0, 0));
+ }
+
+ [Theory]
+ [ParameterAttributeData]
+ public void constructor_should_initialize_subject([Values(1, 2)]int length)
+ {
+ var bytes = Enumerable.Range(0, length).Select(i => (byte)i).ToArray();
+ var subject = CreateSubject(bytes);
+
+ subject.IsReadOnly.Should().Be(true);
+ subject.Length.Should().Be(length);
+ subject.Memory.ToArray().ShouldBeEquivalentTo(bytes);
+ }
+
+ [Fact]
+ public void Dispose_can_be_called_multiple_times()
+ {
+ var subject = CreateSubject(2);
+
+ subject.Dispose();
+ subject.Dispose();
+ }
+
+ [Fact]
+ public void EnsureCapacity_should_throw()
+ {
+ var subject = CreateSubject(2);
+
+ ValidateWritableException(() => subject.EnsureCapacity(1));
+ }
+
+ [Theory]
+ [InlineData(1, 2)]
+ [InlineData(2, 3)]
+ public void GetByte_should_return_expected_result(int position, byte expectedResult)
+ {
+ var bytes = new byte[] { 1, 2, 3 };
+ var subject = CreateSubject(bytes);
+
+ var result = subject.GetByte(position);
+
+ result.Should().Be(expectedResult);
+ }
+
+ [Theory]
+ [ParameterAttributeData]
+ public void GetByte_should_throw_when_position_is_invalid(
+ [Values(-1, 3)]
+ int position)
+ {
+ var subject = CreateSubject(2);
+
+ var exception = Record.Exception(() => subject.GetByte(position));
+ exception.Should().BeOfType();
+ }
+
+ [Theory]
+ [InlineData(0, new byte[] { 0, 0 })]
+ [InlineData(1, new byte[] { 1, 0 })]
+ [InlineData(2, new byte[] { 1, 2 })]
+ public void GetBytes_should_have_expected_effect_for_count(int count, byte[] expectedBytes)
+ {
+ var bytes = new byte[] { 1, 2 };
+ var subject = CreateSubject(bytes);
+ var destination = new byte[2];
+
+ subject.GetBytes(0, destination, 0, count);
+
+ destination.Should().Equal(expectedBytes);
+ }
+
+ [Theory]
+ [InlineData(1, new byte[] { 0, 1, 2, 0 })]
+ [InlineData(2, new byte[] { 0, 0, 1, 2 })]
+ public void GetBytes_should_have_expected_effect_for_offset(int offset, byte[] expectedBytes)
+ {
+ var bytes = new byte[] { 1, 2 };
+ var subject = CreateSubject(bytes);
+ var destination = new byte[4];
+
+ subject.GetBytes(0, destination, offset, 2);
+
+ destination.Should().Equal(expectedBytes);
+ }
+
+ [Theory]
+ [InlineData(1, new byte[] { 2, 3 })]
+ [InlineData(2, new byte[] { 3, 4 })]
+ public void GetBytes_should_have_expected_effect_for_position(int position, byte[] expectedBytes)
+ {
+ var bytes = new byte[] { 1, 2, 3, 4 };
+ var subject = CreateSubject(bytes);
+ var destination = new byte[2];
+
+ subject.GetBytes(position, destination, 0, 2);
+ destination.Should().Equal(expectedBytes);
+ }
+
+ [Theory]
+ [InlineData(0, -1)]
+ [InlineData(0, 3)]
+ [InlineData(1, 2)]
+ [InlineData(2, 1)]
+ public void GetBytes_should_throw_when_count_is_invalid_for_buffer(int position, int count)
+ {
+ var subject = CreateSubject(2);
+ var destination = new byte[3];
+
+ var ex = Record.Exception(() => subject.GetBytes(position, destination, 0, count));
+ ex.Should().BeOfType();
+ }
+
+ [Theory]
+ [InlineData(0, -1)]
+ [InlineData(0, 3)]
+ [InlineData(1, 2)]
+ [InlineData(2, 1)]
+ public void GetBytes_should_throw_when_count_is_invalid_for_destination(int offset, int count)
+ {
+ var subject = CreateSubject([1, 2, 3]);
+ var destination = new byte[2];
+
+ var ex = Record.Exception(() => subject.GetBytes(0, destination, offset, count));
+ ex.Should().BeOfType();
+ }
+
+ [Fact]
+ public void GetBytes_should_throw_when_destination_is_null()
+ {
+ var subject = CreateSubject([1, 2]);
+
+ var ex = Record.Exception(() => subject.GetBytes(0, null, 0, 2));
+ ex.Should().BeOfType();
+ }
+
+ [Theory]
+ [ParameterAttributeData]
+ public void GetBytes_should_throw_when_offset_is_invalid([Values(-1, 3)]int offset)
+ {
+ var subject = CreateSubject([1, 2, 3, 4]);
+ var destination = new byte[2];
+
+ var ex = Record.Exception(() => subject.GetBytes(0, destination, offset, 0));
+ ex.Should().BeOfType();
+ }
+
+ [Theory]
+ [ParameterAttributeData]
+ public void GetBytes_should_throw_when_position_is_invalid(
+ [Values(-1, 3)]
+ int position)
+ {
+ var subject = CreateSubject([1, 2]);
+ var destination = Array.Empty();
+
+ var ex = Record.Exception(() => subject.GetBytes(position, destination, 0, 0));
+ ex.Should().BeOfType();
+ }
+
+ [Theory]
+ [InlineData(0, 2)]
+ [InlineData(1, 1)]
+ [InlineData(2, 0)]
+ public void GetSlice_should_return_expected_result(int position, int length)
+ {
+ var bytes = new byte[2];
+ var subject = CreateSubject(bytes);
+
+ var result = subject.GetSlice(position, length);
+
+ result.AccessBackingBytes(0).Offset.Should().Be(position);
+ result.AccessBackingBytes(0).Count.Should().Be(length);
+ }
+
+ [Fact]
+ public void GetSlice_should_return_slice_that_does_not_dispose_subject_when_slice_is_disposed()
+ {
+ var chunk = new ByteArrayChunk([0, 1, 2, 3, 4]);
+ var buffer = new SingleChunkBuffer(chunk, chunk.Bytes.Count, true);
+
+ using var subject = new ReadOnlyMemoryBuffer(buffer.AccessBackingBytes(0).Array, new ByteBufferSlicer(buffer));
+ var slice = subject.GetSlice(0, 4);
+
+ slice.Should().BeOfType();
+ slice.Dispose();
+
+ using var sliceSecond = subject.GetSlice(1, 1);
+ }
+
+ [Fact]
+ public void GetSlice_should_return_slice_that_is_not_disposed_when_subject_is_disposed()
+ {
+ var chunk = new ByteArrayChunk([0, 1, 2, 3, 4]);
+ var buffer = new SingleChunkBuffer(chunk, chunk.Bytes.Count, true);
+
+ var subject = new ReadOnlyMemoryBuffer(buffer.AccessBackingBytes(0).Array, new ByteBufferSlicer(buffer));
+ using var slice = subject.GetSlice(0, 4);
+
+ subject.Dispose();
+
+ slice.GetByte(2).Should().Be(2);
+ slice.Should().BeOfType();
+ }
+
+ [Theory]
+ [InlineData(0, -1)]
+ [InlineData(0, 3)]
+ [InlineData(1, 2)]
+ [InlineData(2, 1)]
+ public void GetSlice_should_throw_when_length_is_invalid(int position, int length)
+ {
+ var subject = CreateSubject(2);
+
+ var ex = Record.Exception(() => subject.GetSlice(position, length));
+ ex.Should().BeOfType();
+ }
+
+ [Theory]
+ [ParameterAttributeData]
+ public void GetSlice_should_throw_when_position_is_invalid([Values(-1, 3)]int position)
+ {
+ var subject = CreateSubject(2);
+
+ var ex = Record.Exception(() => subject.GetSlice(position, 0));
+ ex.Should().BeOfType();
+ }
+
+ [Fact]
+ public void GetSlice_should_use_bytebuffer_slicer()
+ {
+ var buffer = new byte[] { 1, 2, 3 };
+ var bufferSliceExpected = new ByteArrayBuffer([3, 4, 5]);
+ var slicer = new Mock();
+ slicer.Setup(s => s.GetSlice(It.IsAny(), It.IsAny())).Returns(bufferSliceExpected);
+
+ var subject = new ReadOnlyMemoryBuffer(buffer, slicer.Object);
+
+ var slice = subject.GetSlice(1, 1);
+
+ slice.Should().BeSameAs(bufferSliceExpected);
+ slicer.Verify(s => s.GetSlice(1, 1), Times.Once);
+ }
+
+ [Fact]
+ public void Length_set_should_throw()
+ {
+ var subject = CreateDisposedSubject();
+
+ ValidateWritableException(() => subject.Length = 0);
+ }
+
+ [Fact]
+ public void MakeReadOnly_should_do_nothing()
+ {
+ var subject = CreateSubject(2);
+ subject.MakeReadOnly();
+ }
+
+ [Fact]
+ public void SetBytes_should_throw()
+ {
+ var subject = CreateSubject(2);
+ var source = new byte[0];
+
+ ValidateWritableException(() => subject.SetBytes(0, source, 0, 0));
+ }
+
+ // helper methods
+ private ReadOnlyMemoryBuffer CreateDisposedSubject()
+ {
+ var subject = CreateSubject(2);
+ subject.Dispose();
+ return subject;
+ }
+
+ private ReadOnlyMemoryBuffer CreateSubject(int length) => CreateSubject(Enumerable.Range(0, length).Select(i => (byte)i).ToArray());
+
+ private ReadOnlyMemoryBuffer CreateSubject(byte[] bytes) => new(bytes, new ReadOnlyMemorySlicer(bytes));
+
+ private void ValidateWritableException(Action action)
+ {
+ var e = Record.Exception(action);
+ e.Should().BeOfType();
+ e.Message.Should().Contain("is not writable.");
+ }
+}
diff --git a/tests/MongoDB.Bson.Tests/IO/ReadOnlyMemoryReaderTests.cs b/tests/MongoDB.Bson.Tests/IO/ReadOnlyMemoryReaderTests.cs
new file mode 100644
index 00000000000..57a989760c4
--- /dev/null
+++ b/tests/MongoDB.Bson.Tests/IO/ReadOnlyMemoryReaderTests.cs
@@ -0,0 +1,425 @@
+/* Copyright 2010-present MongoDB Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using FluentAssertions;
+using MongoDB.Bson.IO;
+using MongoDB.Bson.Serialization;
+using MongoDB.Bson.Serialization.Serializers;
+using MongoDB.TestHelpers.XunitExtensions;
+using Moq;
+using Xunit;
+
+namespace MongoDB.Bson.Tests.IO;
+
+public class ReadOnlyMemoryReaderTests
+{
+ [Fact]
+ public void Bookmarks_should_work()
+ {
+ var document = new BsonDocument { { "x", 1 }, { "y", 2 }, { "z", new BsonDocument { { "a", "a"}, { "b", "b" } } } };
+ var bytes = document.ToBson();
+
+ using var bsonReader = new ReadOnlyMemoryBsonReader(bytes);
+
+ AssertRead(() => bsonReader.ReadBsonType(), BsonType.Document);
+ DoReaderAction(() => bsonReader.ReadStartDocument());
+
+ var bookmarkX = bsonReader.GetBookmark();
+ AssertRead(() => bsonReader.ReadBsonType(), BsonType.Int32);
+ AssertRead(() => bsonReader.ReadName(), "x");
+ AssertRead(() => bsonReader.ReadInt32(), 1);
+
+ AssertRead(() => bsonReader.ReadBsonType(), BsonType.Int32);
+ AssertRead(() => bsonReader.ReadName(), "y");
+ AssertRead(() => bsonReader.ReadInt32(), 2);
+
+ AssertRead(() => bsonReader.ReadBsonType(), BsonType.Document);
+ AssertRead(() => bsonReader.ReadName(), "z");
+ DoReaderAction(() => bsonReader.ReadStartDocument());
+
+ AssertRead(() => bsonReader.ReadBsonType(), BsonType.String);
+ AssertRead(() => bsonReader.ReadName(), "a");
+ AssertRead(() => bsonReader.ReadString(), "a");
+
+ var bookmarkB = bsonReader.GetBookmark();
+ AssertRead(() => bsonReader.ReadBsonType(), BsonType.String);
+ AssertRead(() => bsonReader.ReadName(), "b");
+ AssertRead(() => bsonReader.ReadString(), "b");
+ DoReaderAction(() => bsonReader.ReadEndDocument());
+
+ DoReaderAction(() => bsonReader.ReadEndDocument());
+ bsonReader.State.Should().Be(BsonReaderState.Initial);
+ bsonReader.IsAtEndOfFile().Should().BeTrue();
+
+ bsonReader.ReturnToBookmark(bookmarkX);
+ AssertRead(() => bsonReader.ReadBsonType(), BsonType.Int32);
+ AssertRead(() => bsonReader.ReadName(), "x");
+ AssertRead(() => bsonReader.ReadInt32(), 1);
+
+ bsonReader.ReturnToBookmark(bookmarkB);
+ AssertRead(() => bsonReader.ReadBsonType(), BsonType.String);
+ AssertRead(() => bsonReader.ReadName(), "b");
+ AssertRead(() => bsonReader.ReadString(), "b");
+ DoReaderAction(() => bsonReader.ReadEndDocument());
+
+ // do everything twice returning to bookmark in between
+ void DoReaderAction(Action readerAction)
+ {
+ var bookmark = bsonReader.GetBookmark();
+ readerAction();
+ bsonReader.ReturnToBookmark(bookmark);
+ readerAction();
+ }
+
+ void AssertRead(Func reader, T expected)
+ {
+ var bookmark = bsonReader.GetBookmark();
+ reader().Should().Be(expected);
+ bsonReader.ReturnToBookmark(bookmark);
+ reader().Should().Be(expected);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(BsonValues))]
+ public void BsonValue_should_be_deserialized(BsonValue value)
+ {
+ var document = new BsonDocument("x", value);
+
+ RehydrateAndValidate(document);
+ }
+
+ public static readonly IEnumerable