// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // namespace ImageProcessorCore.IO { using System; using System.IO; using System.Text; /// /// Equivalent of , but with either endianness, depending on /// the EndianBitConverter it is constructed with. No data is buffered in the /// reader; the client may seek within the stream at will. /// internal class EndianBinaryReader : IDisposable { /// /// Decoder to use for string conversions. /// private readonly Decoder decoder; /// /// Buffer used for temporary storage before conversion into primitives /// private readonly byte[] buffer = new byte[16]; /// /// Buffer used for temporary storage when reading a single character /// private readonly char[] charBuffer = new char[1]; /// /// Minimum number of bytes used to encode a character /// private readonly int minBytesPerChar; /// /// Whether or not this reader has been disposed yet. /// private bool disposed; /// /// Equivalent of System.IO.BinaryWriter, but with either endianness, depending on /// the EndianBitConverter it is constructed with. /// /// Converter to use when reading data /// Stream to read data from public EndianBinaryReader(EndianBitConverter bitConverter, Stream stream) : this(bitConverter, stream, Encoding.UTF8) { } /// /// Constructs a new binary reader with the given bit converter, reading /// to the given stream, using the given encoding. /// /// Converter to use when reading data /// Stream to read data from /// Encoding to use when reading character data public EndianBinaryReader(EndianBitConverter bitConverter, Stream stream, Encoding encoding) { // TODO: Use Guard if (bitConverter == null) { throw new ArgumentNullException("bitConverter"); } if (stream == null) { throw new ArgumentNullException("stream"); } if (encoding == null) { throw new ArgumentNullException("encoding"); } if (!stream.CanRead) { throw new ArgumentException("Stream isn't writable", "stream"); } this.BaseStream = stream; this.BitConverter = bitConverter; this.Encoding = encoding; this.decoder = encoding.GetDecoder(); this.minBytesPerChar = 1; if (encoding is UnicodeEncoding) { this.minBytesPerChar = 2; } } /// /// Gets the bit converter used to read values from the stream. /// public EndianBitConverter BitConverter { get; } /// /// Gets the encoding used to read strings /// public Encoding Encoding { get; } /// /// Gets the underlying stream of the EndianBinaryReader. /// public Stream BaseStream { get; } /// /// Closes the reader, including the underlying stream. /// public void Close() { this.Dispose(); } /// /// Seeks within the stream. /// /// Offset to seek to. /// Origin of seek operation. public void Seek(int offset, SeekOrigin origin) { this.CheckDisposed(); this.BaseStream.Seek(offset, origin); } /// /// Reads a single byte from the stream. /// /// The byte read public byte ReadByte() { this.ReadInternal(this.buffer, 1); return this.buffer[0]; } /// /// Reads a single signed byte from the stream. /// /// The byte read public sbyte ReadSByte() { this.ReadInternal(this.buffer, 1); return unchecked((sbyte)this.buffer[0]); } /// /// Reads a boolean from the stream. 1 byte is read. /// /// The boolean read public bool ReadBoolean() { this.ReadInternal(this.buffer, 1); return this.BitConverter.ToBoolean(this.buffer, 0); } /// /// Reads a 16-bit signed integer from the stream, using the bit converter /// for this reader. 2 bytes are read. /// /// The 16-bit integer read public short ReadInt16() { this.ReadInternal(this.buffer, 2); return this.BitConverter.ToInt16(this.buffer, 0); } /// /// Reads a 32-bit signed integer from the stream, using the bit converter /// for this reader. 4 bytes are read. /// /// The 32-bit integer read public int ReadInt32() { this.ReadInternal(this.buffer, 4); return this.BitConverter.ToInt32(this.buffer, 0); } /// /// Reads a 64-bit signed integer from the stream, using the bit converter /// for this reader. 8 bytes are read. /// /// The 64-bit integer read public long ReadInt64() { this.ReadInternal(this.buffer, 8); return this.BitConverter.ToInt64(this.buffer, 0); } /// /// Reads a 16-bit unsigned integer from the stream, using the bit converter /// for this reader. 2 bytes are read. /// /// The 16-bit unsigned integer read public ushort ReadUInt16() { this.ReadInternal(this.buffer, 2); return this.BitConverter.ToUInt16(this.buffer, 0); } /// /// Reads a 32-bit unsigned integer from the stream, using the bit converter /// for this reader. 4 bytes are read. /// /// The 32-bit unsigned integer read public uint ReadUInt32() { this.ReadInternal(this.buffer, 4); return this.BitConverter.ToUInt32(this.buffer, 0); } /// /// Reads a 64-bit unsigned integer from the stream, using the bit converter /// for this reader. 8 bytes are read. /// /// The 64-bit unsigned integer read public ulong ReadUInt64() { this.ReadInternal(this.buffer, 8); return this.BitConverter.ToUInt64(this.buffer, 0); } /// /// Reads a single-precision floating-point value from the stream, using the bit converter /// for this reader. 4 bytes are read. /// /// The floating point value read public float ReadSingle() { this.ReadInternal(this.buffer, 4); return this.BitConverter.ToSingle(this.buffer, 0); } /// /// Reads a double-precision floating-point value from the stream, using the bit converter /// for this reader. 8 bytes are read. /// /// The floating point value read public double ReadDouble() { this.ReadInternal(this.buffer, 8); return this.BitConverter.ToDouble(this.buffer, 0); } /// /// Reads a decimal value from the stream, using the bit converter /// for this reader. 16 bytes are read. /// /// The decimal value read public decimal ReadDecimal() { this.ReadInternal(this.buffer, 16); return this.BitConverter.ToDecimal(this.buffer, 0); } /// /// Reads a single character from the stream, using the character encoding for /// this reader. If no characters have been fully read by the time the stream ends, /// -1 is returned. /// /// The character read, or -1 for end of stream. public int Read() { int charsRead = this.Read(this.charBuffer, 0, 1); if (charsRead == 0) { return -1; } else { return this.charBuffer[0]; } } /// /// Reads the specified number of characters into the given buffer, starting at /// the given index. /// /// The buffer to copy data into /// The first index to copy data into /// The number of characters to read /// The number of characters actually read. This will only be less than /// the requested number of characters if the end of the stream is reached. /// public int Read(char[] data, int index, int count) { this.CheckDisposed(); // TODO: Use Guard if (this.buffer == null) { throw new ArgumentNullException("buffer"); } if (index < 0) { throw new ArgumentOutOfRangeException("index"); } if (count < 0) { throw new ArgumentOutOfRangeException("index"); } if (count + index > data.Length) { throw new ArgumentException("Not enough space in buffer for specified number of characters starting at specified index"); } int read = 0; bool firstTime = true; // Use the normal buffer if we're only reading a small amount, otherwise // use at most 4K at a time. byte[] byteBuffer = this.buffer; if (byteBuffer.Length < count * this.minBytesPerChar) { byteBuffer = new byte[4096]; } while (read < count) { int amountToRead; // First time through we know we haven't previously read any data if (firstTime) { amountToRead = count * this.minBytesPerChar; firstTime = false; } // After that we can only assume we need to fully read 'chars left -1' characters // and a single byte of the character we may be in the middle of else { amountToRead = ((count - read - 1) * this.minBytesPerChar) + 1; } if (amountToRead > byteBuffer.Length) { amountToRead = byteBuffer.Length; } int bytesRead = this.TryReadInternal(byteBuffer, amountToRead); if (bytesRead == 0) { return read; } int decoded = this.decoder.GetChars(byteBuffer, 0, bytesRead, data, index); read += decoded; index += decoded; } return read; } /// /// Reads the specified number of bytes into the given buffer, starting at /// the given index. /// /// The buffer to copy data into /// The first index to copy data into /// The number of bytes to read /// The number of bytes actually read. This will only be less than /// the requested number of bytes if the end of the stream is reached. /// public int Read(byte[] buffer, int index, int count) { this.CheckDisposed(); if (buffer == null) { throw new ArgumentNullException("buffer"); } if (index < 0) { throw new ArgumentOutOfRangeException("index"); } if (count < 0) { throw new ArgumentOutOfRangeException("index"); } if (count + index > buffer.Length) { throw new ArgumentException("Not enough space in buffer for specified number of bytes starting at specified index"); } int read = 0; while (count > 0) { int block = this.BaseStream.Read(buffer, index, count); if (block == 0) { return read; } index += block; read += block; count -= block; } return read; } /// /// Reads the specified number of bytes, returning them in a new byte array. /// If not enough bytes are available before the end of the stream, this /// method will return what is available. /// /// The number of bytes to read /// The bytes read public byte[] ReadBytes(int count) { this.CheckDisposed(); if (count < 0) { throw new ArgumentOutOfRangeException("count"); } byte[] ret = new byte[count]; int index = 0; while (index < count) { int read = this.BaseStream.Read(ret, index, count - index); // Stream has finished half way through. That's fine, return what we've got. if (read == 0) { byte[] copy = new byte[index]; Buffer.BlockCopy(ret, 0, copy, 0, index); return copy; } index += read; } return ret; } /// /// Reads the specified number of bytes, returning them in a new byte array. /// If not enough bytes are available before the end of the stream, this /// method will throw an IOException. /// /// The number of bytes to read /// The bytes read public byte[] ReadBytesOrThrow(int count) { byte[] ret = new byte[count]; this.ReadInternal(ret, count); return ret; } /// /// Reads a 7-bit encoded integer from the stream. This is stored with the least significant /// information first, with 7 bits of information per byte of value, and the top /// bit as a continuation flag. This method is not affected by the endianness /// of the bit converter. /// /// The 7-bit encoded integer read from the stream. public int Read7BitEncodedInt() { this.CheckDisposed(); int ret = 0; for (int shift = 0; shift < 35; shift += 7) { int b = this.BaseStream.ReadByte(); if (b == -1) { throw new EndOfStreamException(); } ret = ret | ((b & 0x7f) << shift); if ((b & 0x80) == 0) { return ret; } } // Still haven't seen a byte with the high bit unset? Dodgy data. throw new IOException("Invalid 7-bit encoded integer in stream."); } /// /// Reads a 7-bit encoded integer from the stream. This is stored with the most significant /// information first, with 7 bits of information per byte of value, and the top /// bit as a continuation flag. This method is not affected by the endianness /// of the bit converter. /// /// The 7-bit encoded integer read from the stream. public int ReadBigEndian7BitEncodedInt() { this.CheckDisposed(); int ret = 0; for (int i = 0; i < 5; i++) { int b = this.BaseStream.ReadByte(); if (b == -1) { throw new EndOfStreamException(); } ret = (ret << 7) | (b & 0x7f); if ((b & 0x80) == 0) { return ret; } } // Still haven't seen a byte with the high bit unset? Dodgy data. throw new IOException("Invalid 7-bit encoded integer in stream."); } /// /// Reads a length-prefixed string from the stream, using the encoding for this reader. /// A 7-bit encoded integer is first read, which specifies the number of bytes /// to read from the stream. These bytes are then converted into a string with /// the encoding for this reader. /// /// The string read from the stream. public string ReadString() { int bytesToRead = this.Read7BitEncodedInt(); byte[] data = new byte[bytesToRead]; this.ReadInternal(data, bytesToRead); return this.Encoding.GetString(data, 0, data.Length); } /// /// Disposes of the underlying stream. /// public void Dispose() { if (!this.disposed) { this.disposed = true; ((IDisposable)this.BaseStream).Dispose(); } } /// /// Checks whether or not the reader has been disposed, throwing an exception if so. /// private void CheckDisposed() { if (this.disposed) { throw new ObjectDisposedException("EndianBinaryReader"); } } /// /// Reads the given number of bytes from the stream, throwing an exception /// if they can't all be read. /// /// Buffer to read into /// Number of bytes to read private void ReadInternal(byte[] data, int size) { this.CheckDisposed(); int index = 0; while (index < size) { int read = this.BaseStream.Read(data, index, size - index); if (read == 0) { throw new EndOfStreamException ( string.Format( "End of stream reached with {0} byte{1} left to read.", size - index, size - index == 1 ? "s" : string.Empty)); } index += read; } } /// /// Reads the given number of bytes from the stream if possible, returning /// the number of bytes actually read, which may be less than requested if /// (and only if) the end of the stream is reached. /// /// Buffer to read into /// Number of bytes to read /// Number of bytes actually read private int TryReadInternal(byte[] data, int size) { this.CheckDisposed(); int index = 0; while (index < size) { int read = this.BaseStream.Read(data, index, size - index); if (read == 0) { return index; } index += read; } return index; } } }