/* Copyright 2010-2014 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.IO; namespace MongoDB.Bson.IO { /// /// Represents a BSON reader for a binary BSON byte array. /// public class BsonBinaryReader : BsonReader { // private fields private BsonBuffer _buffer; // if reading from a stream Create will have loaded the buffer private bool _disposeBuffer; private BsonBinaryReaderSettings _binaryReaderSettings; // same value as in base class just declared as derived class private BsonBinaryReaderContext _context; // constructors /// /// Initializes a new instance of the BsonBinaryReader class. /// /// A BsonBuffer. /// A BsonBinaryReaderSettings. public BsonBinaryReader(BsonBuffer buffer, BsonBinaryReaderSettings settings) : this(buffer ?? new BsonBuffer(), buffer == null, settings) { } /// /// Initializes a new instance of the BsonBinaryReader class. /// /// A BsonBuffer. /// if set to true this BsonBinaryReader will own the buffer and when Dispose is called the buffer will be Disposed also. /// A BsonBinaryReaderSettings. /// /// buffer /// or /// settings /// public BsonBinaryReader(BsonBuffer buffer, bool disposeBuffer, BsonBinaryReaderSettings settings) : base(settings) { if (buffer == null) { throw new ArgumentNullException("buffer"); } _buffer = buffer; _disposeBuffer = disposeBuffer; _binaryReaderSettings = settings; // already frozen by base class _context = new BsonBinaryReaderContext(null, ContextType.TopLevel, 0, 0); } // public properties /// /// Gets the reader's buffer. /// public BsonBuffer Buffer { get { return _buffer; } } // public methods /// /// Closes the reader. /// public override void Close() { // Close can be called on Disposed objects if (State != BsonReaderState.Closed) { State = BsonReaderState.Closed; } } /// /// Gets a bookmark to the reader's current position and state. /// /// A bookmark. public override BsonReaderBookmark GetBookmark() { return new BsonBinaryReaderBookmark(State, CurrentBsonType, CurrentName, _context, _buffer.Position); } /// /// Reads BSON binary data from the reader. /// /// A BsonBinaryData. #pragma warning disable 618 // about obsolete BsonBinarySubType.OldBinary public override BsonBinaryData ReadBinaryData() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadBinaryData", BsonType.Binary); int size = ReadSize(); var subType = (BsonBinarySubType)_buffer.ReadByte(); if (subType == BsonBinarySubType.OldBinary) { // sub type OldBinary has two sizes (for historical reasons) int size2 = ReadSize(); if (size2 != size - 4) { throw new Exception("Binary sub type OldBinary has inconsistent sizes"); } size = size2; if (_binaryReaderSettings.FixOldBinarySubTypeOnInput) { subType = BsonBinarySubType.Binary; // replace obsolete OldBinary with new Binary sub type } } var bytes = _buffer.ReadBytes(size); var guidRepresentation = GuidRepresentation.Unspecified; if (subType == BsonBinarySubType.UuidLegacy || subType == BsonBinarySubType.UuidStandard) { if (_binaryReaderSettings.GuidRepresentation != GuidRepresentation.Unspecified) { var expectedSubType = (_binaryReaderSettings.GuidRepresentation == GuidRepresentation.Standard) ? BsonBinarySubType.UuidStandard : BsonBinarySubType.UuidLegacy; if (subType != expectedSubType) { var message = string.Format( "The GuidRepresentation for the reader is {0}, which requires the binary sub type to be {1}, not {2}.", _binaryReaderSettings.GuidRepresentation, expectedSubType, subType); throw new Exception(message); } } guidRepresentation = (subType == BsonBinarySubType.UuidStandard) ? GuidRepresentation.Standard : _binaryReaderSettings.GuidRepresentation; } State = GetNextState(); return new BsonBinaryData(bytes, subType, guidRepresentation); } #pragma warning restore 618 /// /// Reads a BSON boolean from the reader. /// /// A Boolean. public override bool ReadBoolean() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadBoolean", BsonType.Boolean); State = GetNextState(); return _buffer.ReadBoolean(); } /// /// Reads a BsonType from the reader. /// /// The type of the BsonTrie values. /// An optional trie to search for a value that matches the next element name. /// Set to true if a matching value was found in the trie. /// Set to the matching value found in the trie or null if no matching value was found. /// A BsonType. public override BsonType ReadBsonType(BsonTrie bsonTrie, out bool found, out TValue value) { if (Disposed) { ThrowObjectDisposedException(); } found = false; value = default(TValue); if (State == BsonReaderState.Initial || State == BsonReaderState.Done || State == 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("ReadBsonType", BsonReaderState.Type); } CurrentBsonType = _buffer.ReadBsonType(); 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: var message = string.Format("BsonType EndOfDocument is not valid when ContextType is {0}.", _context.ContextType); throw new Exception(message); } } else { switch (_context.ContextType) { case ContextType.Array: _buffer.SkipCString(); // ignore array element names State = BsonReaderState.Value; break; case ContextType.Document: case ContextType.ScopeDocument: CurrentName = _buffer.ReadName(bsonTrie, out found, out value); State = BsonReaderState.Name; break; default: throw new BsonInternalException("Unexpected ContextType."); } return CurrentBsonType; } } /// /// Reads BSON binary data from the reader. /// /// A byte array. #pragma warning disable 618 // about obsolete BsonBinarySubType.OldBinary public override byte[] ReadBytes() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadBytes", BsonType.Binary); int size = ReadSize(); var subType = (BsonBinarySubType)_buffer.ReadByte(); if (subType != BsonBinarySubType.Binary && subType != BsonBinarySubType.OldBinary) { var message = string.Format("ReadBytes requires the binary sub type to be Binary, not {2}.", subType); throw new Exception(message); } State = GetNextState(); return _buffer.ReadBytes(size); } #pragma warning restore 618 /// /// Reads a BSON DateTime from the reader. /// /// The number of milliseconds since the Unix epoch. public override long ReadDateTime() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadDateTime", BsonType.DateTime); State = GetNextState(); var value = _buffer.ReadInt64(); if (value == BsonConstants.DateTimeMaxValueMillisecondsSinceEpoch + 1) { if (_binaryReaderSettings.FixOldDateTimeMaxValueOnInput) { value = BsonConstants.DateTimeMaxValueMillisecondsSinceEpoch; } } return value; } /// /// Reads a BSON Double from the reader. /// /// A Double. public override double ReadDouble() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadDouble", BsonType.Double); State = GetNextState(); return _buffer.ReadDouble(); } /// /// Reads the end of a BSON array from the reader. /// public override void ReadEndArray() { if (Disposed) { ThrowObjectDisposedException(); } if (_context.ContextType != ContextType.Array) { ThrowInvalidContextType("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("ReadEndArray", BsonReaderState.EndOfArray); } _context = _context.PopContext(_buffer.Position); switch (_context.ContextType) { case ContextType.Array: State = BsonReaderState.Type; break; case ContextType.Document: State = BsonReaderState.Type; break; case ContextType.TopLevel: State = BsonReaderState.Done; break; default: throw new BsonInternalException("Unexpected ContextType."); } } /// /// Reads the end of a BSON document from the reader. /// public override void ReadEndDocument() { if (Disposed) { ThrowObjectDisposedException(); } if (_context.ContextType != ContextType.Document && _context.ContextType != ContextType.ScopeDocument) { ThrowInvalidContextType("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("ReadEndDocument", BsonReaderState.EndOfDocument); } _context = _context.PopContext(_buffer.Position); if (_context.ContextType == ContextType.JavaScriptWithScope) { _context = _context.PopContext(_buffer.Position); // JavaScriptWithScope } switch (_context.ContextType) { case ContextType.Array: State = BsonReaderState.Type; break; case ContextType.Document: State = BsonReaderState.Type; break; case ContextType.TopLevel: State = BsonReaderState.Done; break; default: throw new BsonInternalException("Unexpected ContextType."); } } /// /// Reads a BSON Int32 from the reader. /// /// An Int32. public override int ReadInt32() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadInt32", BsonType.Int32); State = GetNextState(); return _buffer.ReadInt32(); } /// /// Reads a BSON Int64 from the reader. /// /// An Int64. public override long ReadInt64() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadInt64", BsonType.Int64); State = GetNextState(); return _buffer.ReadInt64(); } /// /// Reads a BSON JavaScript from the reader. /// /// A string. public override string ReadJavaScript() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadJavaScript", BsonType.JavaScript); State = GetNextState(); return _buffer.ReadString(_binaryReaderSettings.Encoding); } /// /// Reads a BSON JavaScript with scope from the reader (call ReadStartDocument next to read the scope). /// /// A string. public override string ReadJavaScriptWithScope() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadJavaScriptWithScope", BsonType.JavaScriptWithScope); var startPosition = _buffer.Position; // position of size field var size = ReadSize(); _context = new BsonBinaryReaderContext(_context, ContextType.JavaScriptWithScope, startPosition, size); var code = _buffer.ReadString(_binaryReaderSettings.Encoding); State = BsonReaderState.ScopeDocument; return code; } /// /// Reads a BSON MaxKey from the reader. /// public override void ReadMaxKey() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadMaxKey", BsonType.MaxKey); State = GetNextState(); } /// /// Reads a BSON MinKey from the reader. /// public override void ReadMinKey() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadMinKey", BsonType.MinKey); State = GetNextState(); } /// /// Reads a BSON null from the reader. /// public override void ReadNull() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadNull", BsonType.Null); State = GetNextState(); } /// /// Reads a BSON ObjectId from the reader. /// /// An ObjectId. public override ObjectId ReadObjectId() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadObjectId", BsonType.ObjectId); State = GetNextState(); return _buffer.ReadObjectId(); } /// /// Reads a raw BSON array. /// /// /// The raw BSON array. /// public override IByteBuffer ReadRawBsonArray() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadRawBsonArray", BsonType.Array); var position = _buffer.Position; var length = _buffer.ReadInt32(); var slice = _buffer.ByteBuffer.GetSlice(position, length); _buffer.Position = position + length; switch (_context.ContextType) { case ContextType.Array: State = BsonReaderState.Type; break; case ContextType.Document: State = BsonReaderState.Type; break; case ContextType.TopLevel: State = BsonReaderState.Done; break; default: throw new BsonInternalException("Unexpected ContextType."); } return slice; } /// /// Reads a raw BSON document. /// /// /// The raw BSON document. /// public override IByteBuffer ReadRawBsonDocument() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadRawBsonDocument", BsonType.Document); var position = _buffer.Position; var length = _buffer.ReadInt32(); var slice = _buffer.ByteBuffer.GetSlice(position, length); _buffer.Position = position + length; if (_context.ContextType == ContextType.JavaScriptWithScope) { _context = _context.PopContext(_buffer.Position); // JavaScriptWithScope } switch (_context.ContextType) { case ContextType.Array: State = BsonReaderState.Type; break; case ContextType.Document: State = BsonReaderState.Type; break; case ContextType.TopLevel: State = BsonReaderState.Done; break; default: throw new BsonInternalException("Unexpected ContextType."); } return slice; } /// /// Reads a BSON regular expression from the reader. /// /// A BsonRegularExpression. public override BsonRegularExpression ReadRegularExpression() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadRegularExpression", BsonType.RegularExpression); State = GetNextState(); var pattern = _buffer.ReadCString(_binaryReaderSettings.Encoding); var options = _buffer.ReadCString(_binaryReaderSettings.Encoding); return new BsonRegularExpression(pattern, options); } /// /// Reads the start of a BSON array. /// public override void ReadStartArray() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadStartArray", BsonType.Array); var startPosition = _buffer.Position; // position of size field var size = ReadSize(); _context = new BsonBinaryReaderContext(_context, ContextType.Array, startPosition, size); State = BsonReaderState.Type; } /// /// Reads the start of a BSON document. /// public override void ReadStartDocument() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadStartDocument", BsonType.Document); var contextType = (State == BsonReaderState.ScopeDocument) ? ContextType.ScopeDocument : ContextType.Document; var startPosition = _buffer.Position; // position of size field var size = ReadSize(); _context = new BsonBinaryReaderContext(_context, contextType, startPosition, size); State = BsonReaderState.Type; } /// /// Reads a BSON string from the reader. /// /// A String. public override string ReadString() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadString", BsonType.String); State = GetNextState(); return _buffer.ReadString(_binaryReaderSettings.Encoding); } /// /// Reads a BSON symbol from the reader. /// /// A string. public override string ReadSymbol() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadSymbol", BsonType.Symbol); State = GetNextState(); return _buffer.ReadString(_binaryReaderSettings.Encoding); } /// /// Reads a BSON timestamp from the reader. /// /// The combined timestamp/increment. public override long ReadTimestamp() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadTimestamp", BsonType.Timestamp); State = GetNextState(); return _buffer.ReadInt64(); } /// /// Reads a BSON undefined from the reader. /// public override void ReadUndefined() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadUndefined", BsonType.Undefined); State = GetNextState(); } /// /// Returns the reader to previously bookmarked position and state. /// /// The bookmark. public override void ReturnToBookmark(BsonReaderBookmark bookmark) { var binaryReaderBookmark = (BsonBinaryReaderBookmark)bookmark; State = binaryReaderBookmark.State; CurrentBsonType = binaryReaderBookmark.CurrentBsonType; CurrentName = binaryReaderBookmark.CurrentName; _context = binaryReaderBookmark.CloneContext(); _buffer.Position = binaryReaderBookmark.Position; } /// /// Skips the name (reader must be positioned on a name). /// public override void SkipName() { if (Disposed) { ThrowObjectDisposedException(); } if (State != BsonReaderState.Name) { ThrowInvalidState("SkipName", BsonReaderState.Name); } State = BsonReaderState.Value; } /// /// Skips the value (reader must be positioned on a value). /// public override void SkipValue() { if (Disposed) { ThrowObjectDisposedException(); } if (State != BsonReaderState.Value) { ThrowInvalidState("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.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: _buffer.SkipCString(); _buffer.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."); } _buffer.Skip(skip); State = BsonReaderState.Type; } // protected methods /// /// Disposes of any resources used by the reader. /// /// True if called from Dispose. protected override void Dispose(bool disposing) { if (disposing) { try { Close(); if (_buffer != null) { if (_disposeBuffer) { _buffer.Dispose(); } _buffer = null; } } catch { } // ignore exceptions } base.Dispose(disposing); } // private methods private BsonReaderState GetNextState() { switch (_context.ContextType) { case ContextType.Array: case ContextType.Document: case ContextType.ScopeDocument: return BsonReaderState.Type; case ContextType.TopLevel: return BsonReaderState.Done; default: throw new BsonInternalException("Unexpected ContextType."); } } private int ReadSize() { int size = _buffer.ReadInt32(); if (size < 0) { var message = string.Format("Size {0} is not valid because it is negative.", size); throw new Exception(message); } if (size > _binaryReaderSettings.MaxDocumentSize) { var message = string.Format("Size {0} is not valid because it is larger than MaxDocumentSize {1}.", size, _binaryReaderSettings.MaxDocumentSize); throw new Exception(message); } return size; } } }