Add BitStream type and add BitArray extensions

This commit is contained in:
Matt Nadareski
2022-12-16 21:59:46 -08:00
parent 01a365033e
commit 4d26535d07
4 changed files with 307 additions and 412 deletions

View File

@@ -3,7 +3,7 @@ namespace BurnOutSharp.Models.MicrosoftCabinet.MSZIP
/// <summary>
/// Each MSZIP block MUST consist of a 2-byte MSZIP signature and one or more RFC 1951 blocks. The
/// 2-byte MSZIP signature MUST consist of the bytes 0x43 and 0x4B. The MSZIP signature MUST be
/// the first 2 bytes in the MSZIP block.The MSZIP signature is shown in the following packet diagram.
/// the first 2 bytes in the MSZIP block. The MSZIP signature is shown in the following packet diagram.
/// </summary>
/// <see href="https://interoperability.blob.core.windows.net/files/MS-MCI/%5bMS-MCI%5d.pdf"/>
public class Block

View File

@@ -0,0 +1,147 @@
using System.Collections;
using System.IO;
namespace BurnOutSharp.Utilities
{
/// <summary>
/// Stream that allows reading bits or groups of bits at a time
/// </summary>
public class BitStream
{
/// <summary>
/// Underlying stream to read from
/// </summary>
private readonly Stream _stream;
/// <summary>
/// Bit array representing the current byte in the stream
/// </summary>
private BitArray _bitBuffer;
/// <summary>
/// Next bit position to read from in the buffer
/// </summary>
private int _bitPosition;
public BitStream(byte[] data)
{
_stream = new MemoryStream(data);
_bitBuffer = null;
_bitPosition = -1;
}
public BitStream(Stream data)
{
_stream = data;
_bitBuffer = null;
_bitPosition = -1;
}
/// <summary>
/// Read an array of bits from the input
/// </summary>
/// <param name="bitCount">Number of bits to read</param>
/// <returns>Array representing the read bits, null on error</returns>
public BitArray ReadBits(int bitCount)
{
// If we have an invalid bit count
if (bitCount <= 0)
return null;
// If we have an invalid bit buffer
if (_bitBuffer == null || _bitPosition < 0 || _bitPosition >= 8)
RefreshBuffer();
// Create an array to hold the bits
BitArray bits = new BitArray(bitCount);
// Loop through and populate the bits
for (int i = 0; i < bitCount; i++)
{
// Read the next bit from the buffer
bits[i] = _bitBuffer[_bitPosition++];
// Attempt to refresh the buffer if we need to
if (_bitPosition < 0 || _bitPosition > 7)
{
// If the refresh failed
if (!RefreshBuffer())
break;
}
}
// Return the bits
return bits;
}
/// <summary>
/// Read an array of bytes from the input
/// </summary>
/// <param name="byteCount">Number of bytes to read</param>
/// <returns>Array representing the read bytes, null on error</returns>
public byte[] ReadBytes(int byteCount)
{
// If we have an invalid byte count
if (byteCount <= 0)
return null;
// Get the corresponding bit array
BitArray bits = ReadBits(byteCount * 8);
if (bits == null)
return null;
// Create the byte array
byte[] bytes = new byte[byteCount];
// Initialize the loop variables
byte byt = 0; int bitNumber = 0, byteNumber = 0;
// Loop and build the byte array
for (int i = 0; i < bits.Length; i++)
{
// Add the new bit to the byte
byt <<= 1;
byt |= (byte)(bits[i] ? 1 : 0);
bitNumber++;
// If we are at the end of a byte
if (bitNumber == 8)
{
bytes[byteNumber++] = byt;
bitNumber = 0;
}
}
// Add the last byte
bytes[byteNumber] = byt;
return bytes;
}
/// <summary>
/// Discard the remaining bits in the buffer
/// </summary>
public void DiscardBuffer() => RefreshBuffer();
/// <summary>
/// Refresh the bit buffer from the data source
/// </summary>
/// <returns>True if the buffer refreshed, false otherwise</returns>
private bool RefreshBuffer()
{
// If we ran out of bytes
if (_stream.Position >= _stream.Length)
{
_bitBuffer = null;
_bitPosition = -1;
return false;
}
// Read the next byte and reset
byte next = _stream.ReadByteValue();
_bitBuffer = new BitArray(new byte[] { next });
_bitPosition = 0;
return true;
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
@@ -29,6 +30,146 @@ namespace BurnOutSharp.Utilities
#endregion
#region BitArray
/// <summary>
/// Convert a bit array into a byte
/// </summary>
public static byte AsByte(this BitArray array)
{
byte value = 0;
int maxValue = Math.Min(8, array.Length);
for (int i = 0; i < maxValue; i++)
{
value <<= 1;
value |= (byte)(array[i] ? 1 : 0);
}
return value;
}
/// <summary>
/// Convert a bit array into an sbyte
/// </summary>
public static sbyte AsSByte(this BitArray array)
{
sbyte value = 0;
int maxValue = Math.Min(8, array.Length);
for (int i = 0; i < maxValue; i++)
{
value <<= 1;
value |= (sbyte)(array[i] ? 1 : 0);
}
return value;
}
/// <summary>
/// Convert a bit array into a short
/// </summary>
public static short AsInt16(this BitArray array)
{
short value = 0;
int maxValue = Math.Min(16, array.Length);
for (int i = 0; i < maxValue; i++)
{
value <<= 1;
value |= (short)(array[i] ? 1 : 0);
}
return value;
}
/// <summary>
/// Convert a bit array into a ushort
/// </summary>
public static ushort AsUInt16(this BitArray array)
{
ushort value = 0;
int maxValue = Math.Min(16, array.Length);
for (int i = 0; i < maxValue; i++)
{
value <<= 1;
value |= (ushort)(array[i] ? 1 : 0);
}
return value;
}
/// <summary>
/// Convert a bit array into an int
/// </summary>
public static int AsInt32(this BitArray array)
{
int value = 0;
int maxValue = Math.Min(32, array.Length);
for (int i = 0; i < maxValue; i++)
{
value <<= 1;
value |= (int)(array[i] ? 1 : 0);
}
return value;
}
/// <summary>
/// Convert a bit array into a uint
/// </summary>
public static uint AsUInt32(this BitArray array)
{
uint value = 0;
int maxValue = Math.Min(32, array.Length);
for (int i = 0; i < maxValue; i++)
{
value <<= 1;
value |= (uint)(array[i] ? 1 : 0);
}
return value;
}
/// <summary>
/// Convert a bit array into a long
/// </summary>
public static long AsInt64(this BitArray array)
{
long value = 0;
int maxValue = Math.Min(64, array.Length);
for (int i = 0; i < maxValue; i++)
{
value <<= 1;
value |= (long)(array[i] ? 1 : 0);
}
return value;
}
/// <summary>
/// Convert a bit array into a ulong
/// </summary>
public static ulong AsUInt64(this BitArray array)
{
ulong value = 0;
int maxValue = Math.Min(64, array.Length);
for (int i = 0; i < maxValue; i++)
{
value <<= 1;
value |= (ulong)(array[i] ? 1 : 0);
}
return value;
}
#endregion
#region Byte Array Reading
/// <summary>

View File

@@ -44,18 +44,18 @@ namespace BurnOutSharp.FileType
public static class MSZIPDynamicHuffmanCompressedBlockBuilder
{
public static DynamicHuffmanCompressedBlock Create(MSZIPDeflateStream stream)
public static DynamicHuffmanCompressedBlock Create(BitStream stream)
{
DynamicHuffmanCompressedBlock dynamicHuffmanCompressedBlock = new DynamicHuffmanCompressedBlock();
// # of Literal/Length codes - 257
ulong HLIT = stream.ReadBitsLSB(5) + 257;
ulong HLIT = stream.ReadBits(5).AsUInt64() + 257;
// # of Distance codes - 1
ulong HDIST = stream.ReadBitsLSB(5) + 1;
ulong HDIST = stream.ReadBits(5).AsUInt64() + 1;
// HCLEN, # of Code Length codes - 4
ulong HCLEN = stream.ReadBitsLSB(5) + 4;
ulong HCLEN = stream.ReadBits(5).AsUInt64() + 4;
// (HCLEN + 4) x 3 bits: code lengths for the code length
// alphabet given just above
@@ -66,7 +66,7 @@ namespace BurnOutSharp.FileType
// length) is not used.
int[] codeLengthAlphabet = new int[19];
for (ulong i = 0; i < HCLEN; i++)
codeLengthAlphabet[MSZIPDeflate.BitLengthOrder[i]] = (int)stream.ReadBitsLSB(3);
codeLengthAlphabet[MSZIPDeflate.BitLengthOrder[i]] = (int)stream.ReadBits(3).AsUInt64();
for (ulong i = HCLEN; i < 19; i++)
codeLengthAlphabet[MSZIPDeflate.BitLengthOrder[i]] = 0;
@@ -88,7 +88,7 @@ namespace BurnOutSharp.FileType
/// <summary>
/// The alphabet for code lengths is as follows
/// </summary>
private static int[] BuildHuffmanTree(MSZIPDeflateStream stream, ulong codeCount, int[] codeLengths)
private static int[] BuildHuffmanTree(BitStream stream, ulong codeCount, int[] codeLengths)
{
// Setup the huffman tree
int[] tree = new int[codeCount];
@@ -97,7 +97,7 @@ namespace BurnOutSharp.FileType
int lastCode = 0, repeatLength = 0;
for (ulong i = 0; i < codeCount; i++)
{
int code = codeLengths[(int)stream.ReadBitsLSB(7)];
int code = codeLengths[(int)stream.ReadBits(7).AsUInt64()];
// Represent code lengths of 0 - 15
if (code > 0 && code <= 15)
@@ -111,7 +111,7 @@ namespace BurnOutSharp.FileType
// Example: Codes 8, 16 (+2 bits 11), 16 (+2 bits 10) will expand to 12 code lengths of 8 (1 + 6 + 5)
else if (code == 16)
{
repeatLength = (int)stream.ReadBitsLSB(2);
repeatLength = (int)stream.ReadBits(2).AsUInt64();
repeatLength += 2;
code = lastCode;
}
@@ -120,7 +120,7 @@ namespace BurnOutSharp.FileType
// (3 bits of length)
else if (code == 17)
{
repeatLength = (int)stream.ReadBitsLSB(3);
repeatLength = (int)stream.ReadBits(3).AsUInt64();
repeatLength += 3;
code = 0;
}
@@ -129,7 +129,7 @@ namespace BurnOutSharp.FileType
// (7 bits of length)
else if (code == 18)
{
repeatLength = (int)stream.ReadBitsLSB(7);
repeatLength = (int)stream.ReadBits(7).AsUInt64();
repeatLength += 11;
code = 0;
}
@@ -329,7 +329,7 @@ namespace BurnOutSharp.FileType
/// <summary>
/// The decoding algorithm for the actual data
/// </summary>
public static void Decode(MSZIPDeflateStream data)
public static void Decode(BitStream data)
{
// Create the output byte array
List<byte> decodedBytes = new List<byte>();
@@ -339,7 +339,7 @@ namespace BurnOutSharp.FileType
do
{
ulong header = data.ReadBitsLSB(3);
ulong header = data.ReadBits(3).AsUInt64();
block = MSZIPDeflateBlockBuilder.Create(header);
// We should never get a reserved block
@@ -350,15 +350,15 @@ namespace BurnOutSharp.FileType
if (block.BTYPE == DeflateCompressionType.NoCompression)
{
// Skip any remaining bits in current partially processed byte
data.DiscardToByteBoundary();
data.DiscardBuffer();
// Read LEN and NLEN
byte[] nonCompressedHeader = data.ReadBytesLSB(4);
byte[] nonCompressedHeader = data.ReadBytes(4);
block.BlockData = MSZIPNonCompressedBlockBuilder.Create(nonCompressedHeader);
// Copy LEN bytes of data to output
ushort length = ((NonCompressedBlock)block.BlockData).LEN;
((NonCompressedBlock)block.BlockData).Data = data.ReadBytesLSB(length);
((NonCompressedBlock)block.BlockData).Data = data.ReadBytes(length);
decodedBytes.AddRange(((NonCompressedBlock)block.BlockData).Data);
}
@@ -383,7 +383,7 @@ namespace BurnOutSharp.FileType
while (true)
{
// Decode literal/length value from input stream
int symbol = literalDecodeTable[data.ReadBitsLSB(9)];
int symbol = literalDecodeTable[data.ReadBits(9).AsUInt64()];
// Copy value (literal byte) to output stream
if (symbol < 256)
@@ -398,12 +398,12 @@ namespace BurnOutSharp.FileType
else
{
// Decode distance from input stream
ulong length = data.ReadBitsLSB(LiteralExtraBits[symbol]);
ulong length = data.ReadBits(LiteralExtraBits[symbol]).AsUInt64();
length += (ulong)LiteralLengths[symbol];
int code = distanceDecodeTable[length];
ulong distance = data.ReadBitsLSB(DistanceExtraBits[code]);
ulong distance = data.ReadBits(DistanceExtraBits[code]).AsUInt64();
distance += (ulong)DistanceOffsets[code];
@@ -535,398 +535,5 @@ namespace BurnOutSharp.FileType
}
}
/// <see href="https://www.rfc-editor.org/rfc/rfc1951"/>
public class MSZIPDeflateStream
{
#region Instance Variables
/// <summary>
/// Original data source to read from
/// </summary>
private System.IO.Stream _dataStream = null;
/// <summary>
/// Current rolling buffer
/// </summary>
private byte[] _buffer = null;
/// <summary>
/// Current position in the buffer
/// </summary>
private int _bufferPointer = -1;
/// <summary>
/// Bit buffer to read bits from when necessary
/// </summary>
private BitArray _bitBuffer = null;
/// <summary>
/// Number of bits left in the buffer
/// </summary>
private int _bitsLeft = 0;
#endregion
/// <summary>
/// Constructor
/// </summary>
public MSZIPDeflateStream(System.IO.Stream dataStream)
{
_dataStream = dataStream;
}
/// <summary>
/// Read between 0 and 64 bits of data from the stream assuming LSB
/// </summary>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public ulong ReadBitsLSB(int numBits)
{
// If we are reading an invalid number of bits
if (numBits < 0 || numBits > 64)
throw new ArgumentOutOfRangeException();
// Allocate the bit buffer
ulong bitBuffer = 0;
// If the bit buffer has the right number remaining
if (_bitsLeft >= numBits)
{
for (int i = 0; i < numBits; i++)
{
bitBuffer |= _bitBuffer[i + _bitBuffer.Length - _bitsLeft--] ? 1u : 0;
bitBuffer <<= 1;
}
return bitBuffer;
}
// Otherwise, we need to read what we can
int bitsRemaining = _bitsLeft;
for (int i = 0; i < bitsRemaining; i++)
{
bitBuffer |= _bitBuffer[i + _bitBuffer.Length - _bitsLeft--] ? 1u : 0;
bitBuffer <<= 1;
}
// Fill the bit buffer, if possible
FillBitBuffer();
// If we couldn't read anything, throw an exception
if (_buffer == null)
throw new IndexOutOfRangeException();
// Otherwise, read in the remaining bits needed
for (int i = 0; i < bitsRemaining; i++)
{
bitBuffer |= _bitBuffer[i + _bitBuffer.Length - _bitsLeft--] ? 1u : 0;
bitBuffer <<= 1;
}
return bitBuffer;
}
/// <summary>
/// Read between 0 and 64 bits of data from the stream assuming MSB
/// </summary>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public ulong ReadBitsMSB(int numBits)
{
// If we are reading an invalid number of bits
if (numBits < 0 || numBits > 64)
throw new ArgumentOutOfRangeException();
// Allocate the bit buffer
ulong bitBuffer = 0;
// If the bit buffer has the right number remaining
if (_bitsLeft >= numBits)
{
for (int i = 0; i < numBits; i++)
{
bitBuffer |= _bitBuffer[i + _bitsLeft--] ? 1u : 0;
bitBuffer <<= 1;
}
return bitBuffer;
}
// Otherwise, we need to read what we can
int bitsRemaining = _bitsLeft;
for (int i = 0; i < bitsRemaining; i++)
{
bitBuffer |= _bitBuffer[i + _bitsLeft--] ? 1u : 0;
bitBuffer <<= 1;
}
// Fill the bit buffer, if possible
FillBitBuffer();
// If we couldn't read anything, throw an exception
if (_buffer == null)
throw new IndexOutOfRangeException();
// Otherwise, read in the remaining bits needed
for (int i = 0; i < bitsRemaining; i++)
{
bitBuffer |= _bitBuffer[i + _bitsLeft--] ? 1u : 0;
bitBuffer <<= 1;
}
return bitBuffer;
}
/// <summary>
/// Read more than 0 bytes of data from the stream assuming LSB
/// </summary>
public byte[] ReadBytesLSB(int numBytes)
{
// If we are reading an invalid number of bytes
if (numBytes < 0)
throw new ArgumentOutOfRangeException();
// Allocate the byte buffer
byte[] byteBuffer = new byte[numBytes];
int byteBufferPtr = 0;
// If the bit buffer has the right number remaining
if (_bitsLeft >= numBytes * 8)
{
byte fullBitBuffer = 0;
for (int i = 0; i < numBytes * 8; i++)
{
fullBitBuffer |= (byte)(_bitBuffer[i + _bitBuffer.Length - _bitsLeft--] ? 1 : 0);
if (i % 8 == 7)
{
byteBuffer[byteBufferPtr++] = fullBitBuffer;
fullBitBuffer = 0;
}
else
{
fullBitBuffer <<= 1;
}
}
byteBuffer[byteBufferPtr++] = fullBitBuffer;
return byteBuffer;
}
// Otherwise, we need to read what we can
int bitsRemaining = _bitsLeft;
byte bitBuffer = 0;
for (int i = 0; i < numBytes * 8; i++)
{
bitBuffer |= (byte)(_bitBuffer[i + _bitBuffer.Length - _bitsLeft--] ? 1 : 0);
if (i % 8 == 7)
{
byteBuffer[byteBufferPtr++] = bitBuffer;
bitBuffer = 0;
}
else
{
bitBuffer <<= 1;
}
}
// Fill the bit buffer, if possible
FillBitBuffer();
// If we couldn't read anything, throw an exception
if (_buffer == null)
throw new IndexOutOfRangeException();
// Otherwise, read in the remaining bits needed
for (int i = 0; i < bitsRemaining; i++)
{
bitBuffer |= (byte)(_bitBuffer[i + _bitBuffer.Length - _bitsLeft--] ? 1 : 0);
if (i % 8 == 7)
{
byteBuffer[byteBufferPtr++] = bitBuffer;
bitBuffer = 0;
}
else
{
bitBuffer <<= 1;
}
}
byteBuffer[byteBufferPtr++] = bitBuffer;
return byteBuffer;
}
/// <summary>
/// Read more than 0 bytes of data from the stream assuming MSB
/// </summary>
public byte[] ReadBytesMSB(int numBytes)
{
// If we are reading an invalid number of bytes
if (numBytes < 0)
throw new ArgumentOutOfRangeException();
// Allocate the byte buffer
byte[] byteBuffer = new byte[numBytes];
int byteBufferPtr = 0;
// If the bit buffer has the right number remaining
if (_bitsLeft >= numBytes * 8)
{
byte fullBitBuffer = 0;
for (int i = 0; i < numBytes * 8; i++)
{
fullBitBuffer |= (byte)(_bitBuffer[i + _bitsLeft--] ? 1 : 0);
if (i % 8 == 7)
{
byteBuffer[byteBufferPtr++] = fullBitBuffer;
fullBitBuffer = 0;
}
else
{
fullBitBuffer <<= 1;
}
}
byteBuffer[byteBufferPtr++] = fullBitBuffer;
return byteBuffer;
}
// Otherwise, we need to read what we can
int bitsRemaining = _bitsLeft;
byte bitBuffer = 0;
for (int i = 0; i < numBytes * 8; i++)
{
bitBuffer |= (byte)(_bitBuffer[i + _bitsLeft--] ? 1 : 0);
if (i % 8 == 7)
{
byteBuffer[byteBufferPtr++] = bitBuffer;
bitBuffer = 0;
}
else
{
bitBuffer <<= 1;
}
}
// Fill the bit buffer, if possible
FillBitBuffer();
// If we couldn't read anything, throw an exception
if (_buffer == null)
throw new IndexOutOfRangeException();
// Otherwise, read in the remaining bits needed
for (int i = 0; i < bitsRemaining; i++)
{
bitBuffer |= (byte)(_bitBuffer[i + _bitsLeft--] ? 1 : 0);
if (i % 8 == 7)
{
byteBuffer[byteBufferPtr++] = bitBuffer;
bitBuffer = 0;
}
else
{
bitBuffer <<= 1;
}
}
byteBuffer[byteBufferPtr++] = bitBuffer;
return byteBuffer;
}
/// <summary>
/// Discard bits in the array up to the next byte boundary
/// </summary>
public void DiscardToByteBoundary()
{
int bitsToDiscard = _bitsLeft & 7;
_bitsLeft -= bitsToDiscard;
}
/// <summary>
/// Fill the internal bit buffer from the internal buffer
/// </summary>
/// <remarks>Fills up to 4 bytes worth of data at a time</remarks>
private void FillBitBuffer()
{
// If we have 4 bytes left, just create the bit buffer directly
if (_bufferPointer < _buffer.Length - 4)
{
// Read all 4 bytes directly
byte[] readAllBytes = new ReadOnlySpan<byte>(_buffer, _bufferPointer, 4).ToArray();
_bufferPointer += 4;
// Create the new bit buffer
_bitBuffer = new BitArray(readAllBytes);
_bitsLeft = 32;
return;
}
// If we have less than 4 bytes left, we need to get creative
// Create the byte array to hold the data
byte[] bytes = new byte[4];
// Read what we can first
int bytesRemaining = _buffer.Length - _bufferPointer;
if (bytesRemaining > 0)
{
byte[] readBytesRemaining = new ReadOnlySpan<byte>(_buffer, _bufferPointer, bytesRemaining).ToArray();
Array.Copy(readBytesRemaining, 0, bytes, 0, bytesRemaining);
_bufferPointer += bytesRemaining;
}
// Fill the buffer, if we can
FillBuffer();
// If we couldn't read anything, reset the buffer
if (_buffer == null && bytesRemaining == 4)
{
_bitBuffer = null;
_bitsLeft = 0;
return;
}
// If we don't have anything left, just create a bit array
if (_buffer == null)
{
byte[] readBytesRemaining = new ReadOnlySpan<byte>(bytes, 0, bytesRemaining).ToArray();
_bitBuffer = new BitArray(readBytesRemaining);
_bitsLeft = 8 * bytesRemaining;
return;
}
// Otherwise, we want to read in the remaining necessary bytes
int bytesToRead = 4 - bytesRemaining;
byte[] bytesRead = new ReadOnlySpan<byte>(_buffer, _bufferPointer, bytesToRead).ToArray();
_bufferPointer += bytesToRead;
Array.Copy(bytesRead, 0, bytes, bytesRemaining, bytesToRead);
_bitBuffer = new BitArray(bytes);
_bitsLeft = 32;
}
/// <summary>
/// Fill the internal buffer from the original data source
/// </summary>
/// <remarks>Reads up to 4096 bytes at a time</remarks>
private void FillBuffer()
{
// Get the amount of bytes to read
int bytesRemaining = (int)(_dataStream.Length - _dataStream.Position);
int bytesToRead = Math.Min(bytesRemaining, 4096);
// If we can't ready any bytes, reset the buffer
if (bytesToRead == 0)
{
_buffer = null;
_bufferPointer = -1;
return;
}
// Otherwise, read and reset the position
_buffer = _dataStream.ReadBytes(bytesToRead);
_bufferPointer = 0;
}
}
#endregion
}