//****************************************************************************************************** // StreamExtensions.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), 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.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 09/19/2008 - J. Ritchie Carroll // Generated original version of source code. // 10/24/2008 - Pinal C. Patel // Edited code comments. // 09/14/2009 - Stephen C. Wills // Added new header and license agreement. // 11/23/2011 - J. Ritchie Carroll // Modified copy stream to use buffer pool. // 12/14/2012 - Starlynn Danyelle Gilliam // Modified Header. // 08/15/2014 - Steven E. Chisholm // Added stream encoding functions. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using System.Text; namespace GSF.IO { /// /// Defines extension functions related to manipulation. /// public static unsafe class StreamExtensions { private const int BufferSize = 32768; /// /// Copies input onto output . /// /// The input . /// The output . [Obsolete("Since .NET 4.0, any class inheriting from Stream has a native \"CopyTo\" function; also, .NET 4.5 has a \"CopyToAsync\" method.")] public static void CopyStream(this Stream source, Stream destination) { byte[] buffer = BufferPool.TakeBuffer(BufferSize); try { int bytesRead = source.Read(buffer, 0, BufferSize); while (bytesRead > 0) { destination.Write(buffer, 0, bytesRead); bytesRead = source.Read(buffer, 0, BufferSize); } } finally { if ((object)buffer != null) BufferPool.ReturnBuffer(buffer); } } /// /// Reads entire contents, and returns array of data. /// /// The to be converted to array. /// An array of . public static byte[] ReadStream(this Stream source) { using (BlockAllocatedMemoryStream outStream = new BlockAllocatedMemoryStream()) { source.CopyTo(outStream); return outStream.ToArray(); } } #region [ Object Read/Write ] private enum ObjectType : byte { Null = 0, DBNull = 1, BooleanTrue = 2, BooleanFalse = 3, SByte = 5, Byte = 6, Char = 4, Int16 = 7, UInt16 = 8, Int32 = 9, UInt32 = 10, Int64 = 11, UInt64 = 12, Single = 13, Double = 14, Decimal = 15, DateTime = 16, String = 17, ByteArray = 18, CharArray = 19, Guid = 20 } /// /// Encodes an object on a stream. /// /// Destination stream. /// Object to encode. public static void WriteObject(this Stream stream, object value) { if (value == null) { stream.Write((byte)ObjectType.Null); return; } switch (Type.GetTypeCode(value.GetType())) { case TypeCode.Empty: stream.Write((byte)ObjectType.Null); break; case TypeCode.DBNull: stream.Write((byte)ObjectType.DBNull); break; case TypeCode.Boolean: if ((bool)value) { stream.Write((byte)ObjectType.BooleanFalse); } else { stream.Write((byte)ObjectType.BooleanTrue); } break; case TypeCode.Char: stream.Write((byte)ObjectType.Char); stream.Write((char)value); break; case TypeCode.SByte: stream.Write((byte)ObjectType.SByte); stream.Write((sbyte)value); break; case TypeCode.Byte: stream.Write((byte)ObjectType.Byte); stream.Write((byte)value); break; case TypeCode.Int16: stream.Write((byte)ObjectType.Int16); stream.Write((short)value); break; case TypeCode.UInt16: stream.Write((byte)ObjectType.UInt16); stream.Write((ushort)value); break; case TypeCode.Int32: stream.Write((byte)ObjectType.Int32); stream.Write((int)value); break; case TypeCode.UInt32: stream.Write((byte)ObjectType.UInt32); stream.Write((uint)value); break; case TypeCode.Int64: stream.Write((byte)ObjectType.Int64); stream.Write((long)value); break; case TypeCode.UInt64: stream.Write((byte)ObjectType.UInt64); stream.Write((ulong)value); break; case TypeCode.Single: stream.Write((byte)ObjectType.Single); stream.Write((float)value); break; case TypeCode.Double: stream.Write((byte)ObjectType.Double); stream.Write((double)value); break; case TypeCode.Decimal: stream.Write((byte)ObjectType.Decimal); stream.Write((Decimal)value); break; case TypeCode.DateTime: stream.Write((byte)ObjectType.DateTime); stream.Write((DateTime)value); break; case TypeCode.String: stream.Write((byte)ObjectType.String); stream.Write((String)value); break; case TypeCode.Object: if (value is byte[]) { stream.Write((byte)ObjectType.ByteArray); stream.WriteWithLength((byte[])value); } else if (value is char[]) { stream.Write((byte)ObjectType.CharArray); stream.Write(new string((char[])value)); } else if (value is Guid) { stream.Write((byte)ObjectType.Guid); stream.Write((Guid)value); } else { throw new NotSupportedException("This type cannot be serialized"); } break; default: throw new ArgumentOutOfRangeException(); } } /// /// Reads an object from a stream. /// /// Source stream. /// Decoded object. public static object ReadObject(this Stream stream) { ObjectType type = (ObjectType)stream.ReadNextByte(); switch (type) { case ObjectType.Null: return null; case ObjectType.DBNull: return DBNull.Value; case ObjectType.BooleanTrue: return true; case ObjectType.BooleanFalse: return false; case ObjectType.Char: return stream.ReadChar(); case ObjectType.SByte: return stream.ReadSByte(); case ObjectType.Byte: return stream.ReadNextByte(); case ObjectType.Int16: return stream.ReadInt16(); case ObjectType.UInt16: return stream.ReadUInt16(); case ObjectType.Int32: return stream.ReadInt32(); case ObjectType.UInt32: return stream.ReadUInt32(); case ObjectType.Int64: return stream.ReadInt64(); case ObjectType.UInt64: return stream.ReadUInt64(); case ObjectType.Single: return stream.ReadSingle(); case ObjectType.Double: return stream.ReadDouble(); case ObjectType.Decimal: return stream.ReadDecimal(); case ObjectType.DateTime: return stream.ReadDateTime(); case ObjectType.String: return stream.ReadString(); case ObjectType.ByteArray: return stream.ReadBytes(); case ObjectType.CharArray: return stream.ReadString().ToCharArray(); case ObjectType.Guid: return stream.ReadGuid(); default: throw new ArgumentOutOfRangeException(); } } #endregion #region [ 1 byte values ] /// /// Writes the supplied to /// in little endian format. /// /// the stream to write to /// the value to write public static void Write(this Stream stream, byte value) { stream.WriteByte((byte)value); } /// /// Read a byte from the stream. /// Will throw an exception if the end of the stream has been reached. /// /// the stream to read from. /// the value read [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte ReadNextByte(this Stream stream) { int value = stream.ReadByte(); if (value < 0) ThrowEOS(); return (byte)value; } /// /// Reads the value from the stream in little endian format. /// /// the stream to read from. /// The value read public static bool ReadBoolean(this Stream stream) { return stream.ReadNextByte() != 0; } /// /// Writes the supplied to /// in little endian format. /// /// the stream to write to /// the value to write public static void Write(this Stream stream, bool value) { if (value) stream.Write((byte)1); else stream.Write((byte)0); } /// /// Writes the supplied to /// in little endian format. /// /// the stream to write to /// the value to write public static void Write(this Stream stream, sbyte value) { stream.Write((byte)value); } /// /// Reads the value from the stream in little endian format. /// /// the stream to read from. /// The value read public static sbyte ReadSByte(this Stream stream) { return (sbyte)stream.ReadNextByte(); } #endregion #region [ 2-byte values ] /// /// Writes the supplied to /// in little endian format. /// /// the stream to write to /// the value to write public static void Write(this Stream stream, short value) { Write(stream, LittleEndian.GetBytes(value)); } /// /// Writes the supplied to /// in little endian format. /// /// the stream to write to /// the value to write public static void Write(this Stream stream, ushort value) { Write(stream, (short)value); } /// /// Writes the supplied to /// in little endian format. /// /// the stream to write to /// the value to write public static void Write(this Stream stream, char value) { Write(stream, (short)value); } /// /// Reads the value from the stream in little endian format. /// /// the stream to read from. /// The value read public static short ReadInt16(this Stream stream) { byte[] data = stream.ReadBytes(2); return LittleEndian.ToInt16(data, 0); } /// /// Reads the value from the stream in little endian format. /// /// the stream to read from. /// The value read public static ushort ReadUInt16(this Stream stream) { return (ushort)stream.ReadInt16(); } /// /// Reads the value from the stream in little endian format. /// /// the stream to read from. /// The value read public static char ReadChar(this Stream stream) { return (char)stream.ReadInt16(); } #endregion #region [ 4-byte values ] /// /// Writes the supplied to /// in little endian format. /// /// the stream to write to /// the value to write public static void Write(this Stream stream, int value) { Write(stream, LittleEndian.GetBytes(value)); } /// /// Writes the supplied to /// in little endian format. /// /// the stream to write to /// the value to write public static void Write(this Stream stream, uint value) { stream.Write((int)value); } /// /// Writes the supplied to /// in little endian format. /// /// the stream to write to /// the value to write public static void Write(this Stream stream, float value) { Write(stream, *(int*)&value); } /// /// Reads the value from the stream in little endian format. /// /// the stream to read from. /// The value read public static int ReadInt32(this Stream stream) { byte[] data = stream.ReadBytes(4); return LittleEndian.ToInt32(data, 0); } /// /// Reads the value from the stream in little endian format. /// /// the stream to read from. /// The value read public static uint ReadUInt32(this Stream stream) { return (uint)stream.ReadInt32(); } /// /// Reads the value from the stream in little endian format. /// /// the stream to read from. /// The value read public static float ReadSingle(this Stream stream) { var value = stream.ReadInt32(); return *(float*)&value; } #endregion #region [ 8-byte values ] /// /// Writes the supplied to /// in little endian format. /// /// the stream to write to /// the value to write public static void Write(this Stream stream, long value) { Write(stream, LittleEndian.GetBytes(value)); } /// /// Writes the supplied to /// in little endian format. /// /// the stream to write to /// the value to write public static void Write(this Stream stream, ulong value) { stream.Write((long)value); } /// /// Writes the supplied to /// in little endian format. /// /// the stream to write to /// the value to write public static void Write(this Stream stream, double value) { stream.Write(*(long*)&value); } /// /// Writes the supplied to /// in little endian format. /// /// the stream to write to /// the value to write public static void Write(this Stream stream, DateTime value) { stream.Write(value.Ticks); } /// /// Reads the value from the stream in little endian format. /// /// the stream to read from. /// The value read public static long ReadInt64(this Stream stream) { byte[] data = stream.ReadBytes(8); return LittleEndian.ToInt64(data, 0); } /// /// Reads the value from the stream in little endian format. /// /// the stream to read from. /// The value read public static double ReadDouble(this Stream stream) { var value = stream.ReadInt64(); return *(double*)&value; } /// /// Reads the value from the stream in little endian format. /// /// the stream to read from. /// The value read public static ulong ReadUInt64(this Stream stream) { return (ulong)stream.ReadInt64(); } /// /// Reads the value from the stream in little endian format. /// /// the stream to read from. /// The value read public static DateTime ReadDateTime(this Stream stream) { return new DateTime(stream.ReadInt64()); } #endregion #region [ 16-byte values ] /// /// Writes the supplied string to a in UTF8 encoding. /// /// /// public static void Write(this Stream stream, decimal value) { stream.Write(LittleEndian.GetBytes(value)); } /// /// Writes a guid in little endian bytes to the stream /// /// /// public static void Write(this Stream stream, Guid value) { Write(stream, GuidExtensions.ToLittleEndianBytes(value)); } /// /// Reads a decimal from the stream in Little Endian bytes. /// /// the stream to read the decimal from. /// the decimal value public static decimal ReadDecimal(this Stream stream) { return LittleEndian.ToDecimal(stream.ReadBytes(16), 0); } /// /// Reads a Guid from the stream in Little Endian bytes. /// /// the stream to read the guid from. /// the guid value public static Guid ReadGuid(this Stream stream) { return GuidExtensions.ToLittleEndianGuid(stream.ReadBytes(16)); } #endregion #region [ byte array ] /// /// Writes the entire buffer to the /// /// the stream to write to /// the value to write public static void Write(this Stream stream, byte[] value) { if (value.Length > 0) stream.Write(value, 0, value.Length); } /// /// Writes the supplied to /// along with prefixing the length /// so it can be properly read as a unit. /// /// the stream to write to /// the value to write public static void WriteWithLength(this Stream stream, byte[] value) { Encoding7Bit.Write(stream.WriteByte, (uint)value.Length); if (value.Length > 0) stream.Write(value, 0, value.Length); } /// /// Reads a byte array from a . /// The number of bytes should be prefixed in the stream. /// /// the stream to read from /// A new array containing the bytes. public static byte[] ReadBytes(this Stream stream) { int length = (int)stream.Read7BitUInt32(); if (length < 0) throw new Exception("Invalid length"); byte[] data = new byte[length]; if (length > 0) stream.ReadAll(data, 0, data.Length); return data; } /// /// Reads a byte array from a . /// The number of bytes should be prefixed in the stream. /// /// the stream to read from /// gets the number of bytes to read. /// A new array containing the bytes. public static byte[] ReadBytes(this Stream stream, int length) { if (length < 0) throw new Exception("Invalid length"); byte[] data = new byte[length]; if (length > 0) stream.ReadAll(data, 0, data.Length); return data; } /// /// Reads all of the provided bytes. Will not return prematurely, /// but continue to execute a command until the entire /// has been read. /// /// The stream to read from /// The buffer to write to /// the start position in the /// the number of bytes to read /// occurs if the end of the stream has been reached. public static void ReadAll(this Stream stream, byte[] buffer, int position, int length) { buffer.ValidateParameters(position, length); while (length > 0) { int bytesRead = stream.Read(buffer, position, length); if (bytesRead == 0) throw new EndOfStreamException(); length -= bytesRead; position += bytesRead; } } #endregion #region [ Write ] /// /// Writes the supplied string to a in UTF8 encoding. /// /// /// public static void Write(this Stream stream, string value) { if (value.Length == 0) { stream.WriteByte(0); return; } WriteWithLength(stream, Encoding.UTF8.GetBytes(value)); } /// /// Writes the supplied Collection to a . /// /// /// public static void WriteCollection(this Stream stream, ICollection collection) { if (collection == null) { stream.Write(false); return; } stream.Write(true); stream.Write(collection.Count); foreach (var item in collection) { stream.WriteNullable(item); } } /// /// Writes the supplied Collection to a . /// /// /// public static void WriteCollection(this Stream stream, ICollection collection) { if (collection == null) { stream.Write(false); return; } stream.Write(true); stream.Write(collection.Count); foreach (var item in collection) { stream.Write(item); } } /// /// Writes the supplied string to a /// in UTF8 encoding with a prefix if the value is null /// /// /// public static void WriteNullable(this Stream stream, string value) { if (value == null) { Write(stream, false); } else { Write(stream, true); Write(stream, value); } } #endregion #region [ Read ] /// /// Reads the 7-bit encoded value from the stream. /// /// /// public static uint Read7BitUInt32(this Stream stream) { return Encoding7Bit.ReadUInt32(stream); } /// /// Reads a string from a that was encoded in UTF8. /// /// /// public static string ReadString(this Stream stream) { byte[] data = stream.ReadBytes(); if (data.Length > 0) return Encoding.UTF8.GetString(data); return string.Empty; } /// /// Reads a string from a that was encoded in UTF8. /// Value can be null and is prefixed with a boolean. /// /// /// public static string ReadNullableString(this Stream stream) { if (stream.ReadBoolean()) return stream.ReadString(); return null; } /// /// Writes the supplied string to a in UTF8 encoding. /// /// public static string[] ReadStringCollection(this Stream stream) { if (!stream.ReadBoolean()) return null; int value = stream.ReadInt32(); var rv = new string[value]; for (int x = 0; x < rv.Length; x++) { rv[x] = stream.ReadNullableString(); } return rv; } /// /// Writes the supplied string to a in UTF8 encoding. /// /// public static int[] ReadInt32Collection(this Stream stream) { if (!stream.ReadBoolean()) return null; int value = stream.ReadInt32(); var rv = new int[value]; for (int x = 0; x < rv.Length; x++) { rv[x] = stream.ReadInt32(); } return rv; } #endregion [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowEOS() { throw new EndOfStreamException("End of stream"); } } }