using System;
using System.IO;
using SabreTools.IO;
// TODO: Add more complete implementation to SabreTools.IO
namespace SabreTools.Compression
{
///
/// Wrapper to allow reading bits from a source stream
///
public class BitStream
{
///
public long Position => _source.Position;
///
public long Length => _source.Length;
///
/// Original stream source
///
private readonly Stream _source;
///
/// Last read byte value from the stream
///
private byte? _bitBuffer;
///
/// Index in the byte of the current bit
///
private int _bitIndex;
///
/// Create a new BitStream from a source Stream
///
public BitStream(Stream? source)
{
if (source == null || !source.CanRead || !source.CanSeek)
throw new ArgumentException(nameof(source));
_source = source;
_bitBuffer = null;
_bitIndex = 0;
}
///
/// Discard the current cached byte
///
public void Discard()
{
_bitBuffer = null;
_bitIndex = 0;
}
///
/// Read a single bit, if possible
///
/// The next bit encoded in a byte, null on error or end of stream
public byte? ReadBit()
{
// If we reached the end of the stream
if (_source.Position >= _source.Length)
return null;
// If we don't have a value cached
if (_bitBuffer == null)
{
// Read the next byte, if possible
_bitBuffer = ReadSourceByte();
if (_bitBuffer == null)
return null;
// Reset the bit index
_bitIndex = 0;
}
// Get the value by bit-shifting
int value = _bitBuffer.Value & 0x01;
_bitBuffer = (byte?)(_bitBuffer >> 1);
_bitIndex++;
// Reset the byte if we're at the end
if (_bitIndex >= 8)
Discard();
return (byte)value;
}
///
/// Read a multiple bits in LSB, if possible
///
/// The next bits encoded in a UInt32, null on error or end of stream
public uint? ReadBitsLSB(int bits)
{
uint value = 0;
for (int i = 0; i < bits; i++)
{
// Read the next bit
byte? bitValue = ReadBit();
if (bitValue == null)
return null;
// Add the bit shifted by the current index
value += (uint)(bitValue.Value << i);
}
return value;
}
///
/// Read a multiple bits in MSB, if possible
///
/// The next bits encoded in a UInt32, null on error or end of stream
public uint? ReadBitsMSB(int bits)
{
uint value = 0;
for (int i = 0; i < bits; i++)
{
// Read the next bit
byte? bitValue = ReadBit();
if (bitValue == null)
return null;
// Add the bit shifted by the current index
value += (value << 1) + bitValue.Value;
}
return value;
}
///
/// Read a byte, if possible
///
/// The next byte, null on error or end of stream
/// Assumes the stream is byte-aligned
public byte? ReadByte()
{
try
{
Discard();
return _source.ReadByteValue();
}
catch
{
return null;
}
}
///
/// Read a UInt16, if possible
///
/// The next UInt16, null on error or end of stream
/// Assumes the stream is byte-aligned
public ushort? ReadUInt16()
{
try
{
Discard();
return _source.ReadUInt16();
}
catch
{
return null;
}
}
///
/// Read a UInt32, if possible
///
/// The next UInt32, null on error or end of stream
/// Assumes the stream is byte-aligned
public uint? ReadUInt32()
{
try
{
Discard();
return _source.ReadUInt32();
}
catch
{
return null;
}
}
///
/// Read a UInt64, if possible
///
/// The next UInt64, null on error or end of stream
/// Assumes the stream is byte-aligned
public ulong? ReadUInt64()
{
try
{
Discard();
return _source.ReadUInt64();
}
catch
{
return null;
}
}
///
/// Read bytes, if possible
///
/// Number of bytes to read
/// The next bytes, null on error or end of stream
/// Assumes the stream is byte-aligned
public byte[]? ReadBytes(int bytes)
{
try
{
Discard();
return _source.ReadBytes(bytes);
}
catch
{
return null;
}
}
///
/// Read a single byte from the underlying stream, if possible
///
/// The next full byte from the stream, null on error or end of stream
private byte? ReadSourceByte()
{
try
{
return _source.ReadByteValue();
}
catch
{
return null;
}
}
}
}