/* 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;
using System.Text;
namespace MongoDB.Bson.IO
{
///
/// Represents a buffer for BSON encoded bytes.
///
public class BsonBuffer : IDisposable
{
// private static fields
private static readonly string[] __asciiStringTable = BuildAsciiStringTable();
private static readonly bool[] __validBsonTypes = new bool[256];
// private fields
private bool _disposed = false;
private IByteBuffer _byteBuffer;
private bool _disposeByteBuffer;
// static constructor
static BsonBuffer()
{
foreach (BsonType bsonType in Enum.GetValues(typeof(BsonType)))
{
__validBsonTypes[(byte)bsonType] = true;
}
}
// constructors
///
/// Initializes a new instance of the BsonBuffer class.
///
public BsonBuffer()
: this(new MultiChunkBuffer(BsonChunkPool.Default), true)
{
}
///
/// Initializes a new instance of the class.
///
/// The buffer.
/// if set to true this BsonBuffer will own the byte buffer and when Dispose is called the byte buffer will be Disposed also.
public BsonBuffer(IByteBuffer byteBuffer, bool disposeByteBuffer)
{
_byteBuffer = byteBuffer;
_disposeByteBuffer = disposeByteBuffer;
}
// public properties
///
/// Gets the byte buffer.
///
///
/// The byte buffer.
///
public IByteBuffer ByteBuffer
{
get { return _byteBuffer; }
}
///
/// Gets or sets the length of the data in the buffer.
///
public int Length
{
get
{
ThrowIfDisposed();
return _byteBuffer.Length;
}
set
{
ThrowIfDisposed();
_byteBuffer.Length = value;
}
}
///
/// Gets or sets the current position in the buffer.
///
public int Position
{
get
{
ThrowIfDisposed();
return _byteBuffer.Position;
}
set
{
ThrowIfDisposed();
_byteBuffer.Position = value;
}
}
// private static methods
private static string[] BuildAsciiStringTable()
{
var asciiStringTable = new string[128];
for (int i = 0; i < 128; ++i)
{
asciiStringTable[i] = new string((char)i, 1);
}
return asciiStringTable;
}
// public methods
///
/// Backpatches the length of an object.
///
/// The start position of the object.
/// The length of the object.
public void Backpatch(int position, int length)
{
ThrowIfDisposed();
var savedPosition = _byteBuffer.Position;
_byteBuffer.Position = position;
WriteInt32(length);
_byteBuffer.Position = savedPosition;
}
///
/// Clears the data in the buffer.
///
public void Clear()
{
ThrowIfDisposed();
_byteBuffer.Clear();
}
///
/// Copies data from the buffer to a byte array.
///
/// The source offset in the buffer.
/// The destination byte array.
/// The destination offset in the byte array.
/// The number of bytes to copy.
[Obsolete("Use ReadBytes instead.")]
public void CopyTo(int sourceOffset, byte[] destination, int destinationOffset, int count)
{
ThrowIfDisposed();
var savedPosition = _byteBuffer.Position;
_byteBuffer.Position = sourceOffset;
_byteBuffer.ReadBytes(destination, destinationOffset, count);
_byteBuffer.Position = savedPosition;
}
///
/// Disposes of any resources held by the buffer.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Loads the buffer from a Stream (the Stream must be positioned at a 4 byte length field).
///
/// The Stream.
public void LoadFrom(Stream stream)
{
LoadFrom(stream, 4); // does not advance position
int length = ReadInt32(); // advances position 4 bytes
LoadFrom(stream, length - 4); // does not advance position
Position -= 4; // move back to just before the length field
}
///
/// Loads the buffer from a Stream (leaving the position in the buffer unchanged).
///
/// The stream.
/// The number of bytes to load.
public void LoadFrom(Stream stream, int count)
{
ThrowIfDisposed();
_byteBuffer.LoadFrom(stream, count); // does not advance position
}
///
/// Peeks at the next byte in the buffer and returns it as a BsonType.
///
/// A BsonType.
[Obsolete("Use ReadBsonType instead.")]
public BsonType PeekBsonType()
{
ThrowIfDisposed();
var value = ReadBsonType();
Position -= 1;
return value;
}
///
/// Peeks at the next byte in the buffer.
///
/// A Byte.
[Obsolete("Use ReadByte instead.")]
public byte PeekByte()
{
ThrowIfDisposed();
var value = ReadByte();
Position -= 1;
return value;
}
///
/// Reads a BSON Boolean from the buffer.
///
/// A Boolean.
public bool ReadBoolean()
{
ThrowIfDisposed();
return _byteBuffer.ReadByte() != 0;
}
///
/// Reads a BSON type from the buffer.
///
/// A BsonType.
public BsonType ReadBsonType()
{
ThrowIfDisposed();
var bsonType = (int)_byteBuffer.ReadByte();
if (!__validBsonTypes[bsonType])
{
string message = string.Format("Invalid BsonType {0}.", bsonType);
throw new Exception(message);
}
return (BsonType)bsonType;
}
///
/// Reads a byte from the buffer.
///
/// A Byte.
public byte ReadByte()
{
ThrowIfDisposed();
return _byteBuffer.ReadByte();
}
///
/// Reads bytes from the buffer.
///
/// The number of bytes to read.
/// A byte array.
public byte[] ReadBytes(int count)
{
ThrowIfDisposed();
return _byteBuffer.ReadBytes(count);
}
///
/// Reads a BSON Double from the buffer.
///
/// A Double.
public double ReadDouble()
{
ThrowIfDisposed();
var segment = _byteBuffer.ReadBackingBytes(8);
if (segment.Count >= 8)
{
return BitConverter.ToDouble(segment.Array, segment.Offset);
}
else
{
var bytes = _byteBuffer.ReadBytes(8);
return BitConverter.ToDouble(bytes, 0);
}
}
///
/// Reads a BSON Int32 from the reader.
///
/// An Int32.
public int ReadInt32()
{
ThrowIfDisposed();
var segment = _byteBuffer.ReadBackingBytes(4);
if (segment.Count >= 4)
{
// for int only we come out ahead with this code vs using BitConverter
return
((int)segment.Array[segment.Offset + 0]) +
((int)segment.Array[segment.Offset + 1] << 8) +
((int)segment.Array[segment.Offset + 2] << 16) +
((int)segment.Array[segment.Offset + 3] << 24);
}
else
{
var bytes = _byteBuffer.ReadBytes(4);
return BitConverter.ToInt32(bytes, 0);
}
}
///
/// Reads a BSON Int64 from the reader.
///
/// An Int64.
public long ReadInt64()
{
ThrowIfDisposed();
var segment = _byteBuffer.ReadBackingBytes(8);
if (segment.Count >= 8)
{
return BitConverter.ToInt64(segment.Array, segment.Offset);
}
else
{
var bytes = _byteBuffer.ReadBytes(8);
return BitConverter.ToInt64(bytes, 0);
}
}
///
/// Reads a BSON ObjectId from the reader.
///
/// An ObjectId.
public ObjectId ReadObjectId()
{
ThrowIfDisposed();
var segment = _byteBuffer.ReadBackingBytes(12);
if (segment.Count >= 12)
{
var bytes = segment.Array;
var offset = segment.Offset;
var timestamp = (bytes[offset + 0] << 24) + (bytes[offset + 1] << 16) + (bytes[offset + 2] << 8) + bytes[offset + 3];
var machine = (bytes[offset + 4] << 16) + (bytes[offset + 5] << 8) + bytes[offset + 6];
var pid = (short)((bytes[offset + 7] << 8) + bytes[offset + 8]);
var increment = (bytes[offset + 9] << 16) + (bytes[offset + 10] << 8) + bytes[offset + 11];
return new ObjectId(timestamp, machine, pid, increment);
}
else
{
var bytes = _byteBuffer.ReadBytes(12);
return new ObjectId(bytes);
}
}
///
/// Reads a BSON ObjectId from the reader.
///
/// The timestamp.
/// The machine hash.
/// The PID.
/// The increment.
[Obsolete("Use ReadObjectId() instead.")]
public void ReadObjectId(out int timestamp, out int machine, out short pid, out int increment)
{
var objectId = ReadObjectId();
timestamp = objectId.Timestamp;
machine = objectId.Machine;
pid = objectId.Pid;
increment = objectId.Increment;
}
///
/// Reads a BSON string from the reader.
///
/// A String.
public string ReadString(UTF8Encoding encoding)
{
ThrowIfDisposed();
var length = ReadInt32(); // length including the null terminator
if (length <= 0)
{
var message = string.Format("Invalid string length: {0} (the length includes the null terminator so it must be greater than or equal to 1).", length);
throw new Exception(message);
}
string value;
byte finalByte;
var segment = _byteBuffer.ReadBackingBytes(length);
if (segment.Count >= length)
{
value = DecodeUtf8String(encoding, segment.Array, segment.Offset, length - 1);
finalByte = segment.Array[segment.Offset + length - 1];
}
else
{
var bytes = _byteBuffer.ReadBytes(length);
value = DecodeUtf8String(encoding, bytes, 0, length - 1);
finalByte = bytes[length - 1];
}
if (finalByte != 0)
{
throw new Exception("String is missing null terminator.");
}
return value;
}
///
/// Reads a BSON CString from the reader (a null terminated string).
///
/// A string.
public string ReadCString(UTF8Encoding encoding)
{
ThrowIfDisposed();
var nullPosition = _byteBuffer.FindNullByte();
if (nullPosition == -1)
{
throw new BsonSerializationException("Missing null terminator.");
}
return ReadCString(encoding, nullPosition);
}
///
/// Reads an element name.
///
/// The type of the BsonTrie values.
/// An optional BsonTrie to use during decoding.
/// Set to true if the string was found in the trie.
/// Set to the value found in the trie; otherwise, null.
/// A string.
public string ReadName(BsonTrie bsonTrie, out bool found, out TValue value)
{
ThrowIfDisposed();
found = false;
value = default(TValue);
if (bsonTrie == null)
{
return ReadCString(new UTF8Encoding(false, true)); // always use strict encoding for names
}
var savedPosition = _byteBuffer.Position;
var bsonTrieNode = bsonTrie.Root;
while (true)
{
var keyByte = _byteBuffer.ReadByte();
if (keyByte == 0)
{
if (bsonTrieNode.HasValue)
{
found = true;
value = bsonTrieNode.Value;
return bsonTrieNode.ElementName;
}
else
{
var nullPosition = _byteBuffer.Position - 1;
_byteBuffer.Position = savedPosition;
return ReadCString(new UTF8Encoding(false, true), nullPosition); // always use strict encoding for names
}
}
bsonTrieNode = bsonTrieNode.GetChild(keyByte);
if (bsonTrieNode == null)
{
var nullPosition = _byteBuffer.FindNullByte(); // starting from where we got so far
_byteBuffer.Position = savedPosition;
return ReadCString(new UTF8Encoding(false, true), nullPosition); // always use strict encoding for names
}
}
}
///
/// Skips over bytes in the buffer (advances the position).
///
/// The number of bytes to skip.
public void Skip(int count)
{
_byteBuffer.Position += count;
}
///
/// Skips over a CString in the buffer (advances the position).
///
public void SkipCString()
{
ThrowIfDisposed();
var nullPosition = _byteBuffer.FindNullByte();
if (nullPosition == -1)
{
throw new Exception("String is missing null terminator");
}
_byteBuffer.Position = nullPosition + 1;
}
///
/// Converts the buffer to a byte array.
///
/// A byte array.
public byte[] ToByteArray()
{
ThrowIfDisposed();
var savedPosition = _byteBuffer.Position;
_byteBuffer.Position = 0;
var byteArray = _byteBuffer.ReadBytes(_byteBuffer.Length);
_byteBuffer.Position = savedPosition;
return byteArray;
}
///
/// Writes a BSON Boolean to the buffer.
///
/// The Boolean value.
public void WriteBoolean(bool value)
{
ThrowIfDisposed();
_byteBuffer.WriteByte(value ? (byte)1 : (byte)0);
}
///
/// Writes a byte to the buffer.
///
/// A byte.
public void WriteByte(byte value)
{
ThrowIfDisposed();
_byteBuffer.WriteByte(value);
}
///
/// Writes bytes to the buffer.
///
/// A byte array.
public void WriteBytes(byte[] value)
{
ThrowIfDisposed();
_byteBuffer.WriteBytes(value);
}
///
/// Writes a CString to the buffer.
///
/// A UTF8 encoding.
/// A string.
public void WriteCString(UTF8Encoding encoding, string value)
{
if (value == null)
{
throw new ArgumentNullException("value");
}
if (value.IndexOf('\0') != -1)
{
throw new ArgumentException("CStrings cannot contain nulls.", "value");
}
ThrowIfDisposed();
var maxLength = encoding.GetMaxByteCount(value.Length) + 1;
var segment = _byteBuffer.WriteBackingBytes(maxLength);
if (segment.Count >= maxLength)
{
var length = encoding.GetBytes(value, 0, value.Length, segment.Array, segment.Offset);
segment.Array[segment.Offset + length] = 0;
_byteBuffer.Position += length + 1;
}
else
{
_byteBuffer.WriteBytes(encoding.GetBytes(value));
_byteBuffer.WriteByte(0);
}
}
///
/// Writes a BSON Double to the buffer.
///
/// The Double value.
public void WriteDouble(double value)
{
ThrowIfDisposed();
_byteBuffer.WriteBytes(BitConverter.GetBytes(value));
}
///
/// Writes a BSON Int32 to the buffer.
///
/// The Int32 value.
public void WriteInt32(int value)
{
ThrowIfDisposed();
var segment = _byteBuffer.WriteBackingBytes(4);
if (segment.Count >= 4)
{
segment.Array[segment.Offset + 0] = (byte)(value);
segment.Array[segment.Offset + 1] = (byte)(value >> 8);
segment.Array[segment.Offset + 2] = (byte)(value >> 16);
segment.Array[segment.Offset + 3] = (byte)(value >> 24);
_byteBuffer.Position += 4;
}
else
{
_byteBuffer.WriteBytes(BitConverter.GetBytes(value));
}
}
///
/// Writes a BSON Int64 to the buffer.
///
/// The Int64 value.
public void WriteInt64(long value)
{
ThrowIfDisposed();
_byteBuffer.WriteBytes(BitConverter.GetBytes(value));
}
///
/// Writes a BSON ObjectId to the buffer.
///
/// The timestamp.
/// The machine hash.
/// The PID.
/// The increment.
[Obsolete("Use WriteObjectId(ObjectId objectId) instead.")]
public void WriteObjectId(int timestamp, int machine, short pid, int increment)
{
var objectId = new ObjectId(timestamp, machine, pid, increment);
WriteObjectId(objectId);
}
///
/// Writes a BSON ObjectId to the buffer.
///
/// The ObjectId.
public void WriteObjectId(ObjectId objectId)
{
ThrowIfDisposed();
var segment = _byteBuffer.WriteBackingBytes(12);
if (segment.Count >= 12)
{
var timestamp = objectId.Timestamp;
var machine = objectId.Machine;
var pid = objectId.Pid;
var increment = objectId.Increment;
segment.Array[segment.Offset + 0] = (byte)(timestamp >> 24);
segment.Array[segment.Offset + 1] = (byte)(timestamp >> 16);
segment.Array[segment.Offset + 2] = (byte)(timestamp >> 8);
segment.Array[segment.Offset + 3] = (byte)(timestamp);
segment.Array[segment.Offset + 4] = (byte)(machine >> 16);
segment.Array[segment.Offset + 5] = (byte)(machine >> 8);
segment.Array[segment.Offset + 6] = (byte)(machine);
segment.Array[segment.Offset + 7] = (byte)(pid >> 8);
segment.Array[segment.Offset + 8] = (byte)(pid);
segment.Array[segment.Offset + 9] = (byte)(increment >> 16);
segment.Array[segment.Offset + 10] = (byte)(increment >> 8);
segment.Array[segment.Offset + 11] = (byte)(increment);
_byteBuffer.Position += 12;
}
else
{
_byteBuffer.WriteBytes(objectId.ToByteArray());
}
}
///
/// Writes a BSON String to the buffer.
///
/// A UTF8 encoding.
/// The String value.
public void WriteString(UTF8Encoding encoding, string value)
{
ThrowIfDisposed();
var maxLength = encoding.GetMaxByteCount(value.Length) + 5;
var segment = _byteBuffer.WriteBackingBytes(maxLength);
if (segment.Count >= maxLength)
{
var length = encoding.GetBytes(value, 0, value.Length, segment.Array, segment.Offset + 4);
var lengthPlusOne = length + 1;
segment.Array[segment.Offset + 0] = (byte)(lengthPlusOne); // now we know the length
segment.Array[segment.Offset + 1] = (byte)(lengthPlusOne >> 8);
segment.Array[segment.Offset + 2] = (byte)(lengthPlusOne >> 16);
segment.Array[segment.Offset + 3] = (byte)(lengthPlusOne >> 24);
segment.Array[segment.Offset + 4 + length] = 0;
_byteBuffer.Position += length + 5;
}
else
{
var bytes = encoding.GetBytes(value);
WriteInt32(bytes.Length + 1);
_byteBuffer.WriteBytes(bytes);
_byteBuffer.WriteByte(0);
}
}
///
/// Writes all the data in the buffer to a Stream.
///
/// The Stream.
public void WriteTo(Stream stream)
{
ThrowIfDisposed();
_byteBuffer.WriteTo(stream);
}
///
/// Writes a 32-bit zero the the buffer.
///
[Obsolete("Use WriteByte or WriteInt32 instead.")]
public void WriteZero()
{
ThrowIfDisposed();
WriteInt32(0);
}
// private static methods
private static string DecodeUtf8String(UTF8Encoding encoding, byte[] buffer, int index, int count)
{
switch (count)
{
// special case empty strings
case 0:
return string.Empty;
// special case single character strings
case 1:
var byte1 = (int)buffer[index];
if (byte1 < __asciiStringTable.Length)
{
return __asciiStringTable[byte1];
}
break;
}
return encoding.GetString(buffer, index, count);
}
// protected methods
///
/// Releases unmanaged and - optionally - managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
if (_byteBuffer != null)
{
if (_disposeByteBuffer)
{
_byteBuffer.Dispose();
}
_byteBuffer = null;
}
}
_disposed = true;
}
}
///
/// Throws if disposed.
///
///
protected void ThrowIfDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(GetType().Name);
}
}
// private methods
private string ReadCString(UTF8Encoding encoding, int nullPosition)
{
if (nullPosition == -1)
{
throw new BsonSerializationException("Missing null terminator.");
}
var length = nullPosition - _byteBuffer.Position + 1;
var segment = _byteBuffer.ReadBackingBytes(length);
if (segment.Count >= length)
{
return DecodeUtf8String(encoding, segment.Array, segment.Offset, length - 1);
}
else
{
var bytes = _byteBuffer.ReadBytes(length);
return DecodeUtf8String(encoding, bytes, 0, length - 1);
}
}
}
}