/*
* Copyright (C) 2007-2024 C# Port
* Original Copyright (C) 2007-2018 Siemens AG
*
* Exact BitStream implementation - byte-compatible with OpenV2G C implementation
* Matches BitInputStream.c and BitOutputStream.c exactly
*/
using System;
namespace V2GDecoderNet.EXI
{
///
/// Exact bit input stream implementation matching OpenV2G BitInputStream.c
///
public class BitInputStreamExact
{
private readonly BitstreamExact _stream;
public BitInputStreamExact(byte[] buffer)
{
_stream = new BitstreamExact(buffer);
}
public BitInputStreamExact(BitstreamExact stream)
{
_stream = stream ?? throw new ArgumentNullException(nameof(stream));
}
///
/// Read specified number of bits - exact implementation of readBits()
///
public int ReadBits(int numBits)
{
if (numBits < 1 || numBits > 32)
throw new ArgumentException("Number of bits must be between 1 and 32", nameof(numBits));
int val = 0;
while (numBits > 0)
{
// If buffer is empty, read next byte
if (_stream.Capacity == 0)
{
if (_stream.Position >= _stream.Size)
return -1; // End of stream
_stream.Buffer = _stream.Data[_stream.Position++];
_stream.Capacity = EXIConstantsExact.BITS_IN_BYTE;
}
// Calculate how many bits to read from current buffer
int bitsToRead = Math.Min(numBits, _stream.Capacity);
// Extract bits from buffer (from MSB side)
int mask = (0xFF >> (EXIConstantsExact.BITS_IN_BYTE - bitsToRead));
int bits = (_stream.Buffer >> (_stream.Capacity - bitsToRead)) & mask;
// Add to result value
val = (val << bitsToRead) | bits;
// Update state
_stream.Capacity -= (byte)bitsToRead;
numBits -= bitsToRead;
}
return val;
}
///
/// Read single bit - exact implementation
///
public int ReadBit()
{
return ReadBits(1);
}
///
/// Read N-bit unsigned integer - exact implementation of decodeNBitUnsignedInteger()
///
public int ReadNBitUnsignedInteger(int numBits)
{
if (numBits == 0) return 0;
return ReadBits(numBits);
}
///
/// Read variable length unsigned integer - exact implementation of decodeUnsignedInteger()
/// Uses 7-bit continuation encoding exactly like C implementation
///
public long ReadUnsignedInteger()
{
const int MASK_7_BITS = 0x7F;
const int CONTINUATION_BIT = 0x80;
byte[] maskedOctets = new byte[8]; // Max 8 bytes for 64-bit value
int i = 0;
byte b;
// Read continuation bytes exactly like C implementation
do
{
int byteVal = ReadBits(8);
if (byteVal < 0) throw new InvalidOperationException("Unexpected end of stream");
b = (byte)byteVal;
maskedOctets[i++] = (byte)(b & MASK_7_BITS);
if (i >= maskedOctets.Length)
throw new InvalidOperationException("Variable length integer too long");
} while ((b & CONTINUATION_BIT) != 0);
// Assemble value from bytes (reverse order) - exact C algorithm
long value = 0;
for (int j = i - 1; j >= 0; j--)
{
value = (value << 7) | maskedOctets[j];
}
return value;
}
///
/// Read variable length signed integer - exact implementation
///
public long ReadInteger()
{
long magnitude = ReadUnsignedInteger();
// Check sign bit (LSB of magnitude)
bool isNegative = (magnitude & 1) != 0;
// Remove sign bit and adjust value
long value = magnitude >> 1;
return isNegative ? -(value + 1) : value;
}
public bool IsEndOfStream => _stream.Position >= _stream.Size && _stream.Capacity == 0;
public int Position => _stream.Position;
public int BitPosition => EXIConstantsExact.BITS_IN_BYTE - _stream.Capacity;
}
///
/// Exact bit output stream implementation matching OpenV2G BitOutputStream.c
///
public class BitOutputStreamExact
{
private readonly BitstreamExact _stream;
public BitOutputStreamExact(int capacity = EXIConstantsExact.BUFFER_SIZE)
{
_stream = new BitstreamExact(capacity);
}
public BitOutputStreamExact(BitstreamExact stream)
{
_stream = stream ?? throw new ArgumentNullException(nameof(stream));
}
///
/// Write specified number of bits - exact implementation of writeBits()
///
public void WriteBits(int numBits, int val)
{
if (numBits < 1 || numBits > 32)
throw new ArgumentException("Number of bits must be between 1 and 32", nameof(numBits));
// Process bits in chunks that fit in current buffer
while (numBits > 0)
{
// Calculate how many bits can fit in current buffer
int bitsToWrite = Math.Min(numBits, _stream.Capacity);
// Extract bits to write (from MSB side of value)
int mask = (0xFF >> (EXIConstantsExact.BITS_IN_BYTE - bitsToWrite));
int bitsValue = (val >> (numBits - bitsToWrite)) & mask;
// Pack bits into buffer (shift left and OR)
_stream.Buffer = (byte)((_stream.Buffer << bitsToWrite) | bitsValue);
_stream.Capacity -= (byte)bitsToWrite;
// If buffer is full, write it to stream
if (_stream.Capacity == 0)
{
if (_stream.Position >= _stream.Size)
throw new InvalidOperationException("Output buffer overflow");
_stream.Data[_stream.Position++] = _stream.Buffer;
_stream.Buffer = 0;
_stream.Capacity = EXIConstantsExact.BITS_IN_BYTE;
}
numBits -= bitsToWrite;
}
}
///
/// Write single bit - exact implementation
///
public void WriteBit(int bit)
{
WriteBits(1, bit);
}
///
/// Write N-bit unsigned integer - exact implementation
///
public void WriteNBitUnsignedInteger(int numBits, int val)
{
if (numBits > 0)
WriteBits(numBits, val);
}
///
/// Write variable length unsigned integer - exact implementation of encodeUnsignedInteger()
/// Uses 7-bit continuation encoding exactly like C implementation
///
public void WriteUnsignedInteger(long val)
{
const int MASK_7_BITS = 0x7F;
const int CONTINUATION_BIT = 0x80;
if (val < 0)
throw new ArgumentException("Value must be non-negative", nameof(val));
// Handle zero as special case
if (val == 0)
{
WriteBits(8, 0);
return;
}
// Split into 7-bit chunks with continuation bits - exact C algorithm
byte[] bytes = new byte[10]; // Max bytes needed for 64-bit value
int numBytes = 0;
while (val > 0)
{
byte chunk = (byte)(val & MASK_7_BITS);
val >>= 7;
// Set continuation bit if more bytes follow
if (val > 0)
chunk |= CONTINUATION_BIT;
bytes[numBytes++] = chunk;
}
// Write bytes in forward order
for (int i = 0; i < numBytes; i++)
{
WriteBits(8, bytes[i]);
}
}
///
/// Write variable length signed integer - exact implementation
///
public void WriteInteger(long val)
{
// Encode sign in LSB and magnitude in remaining bits
bool isNegative = val < 0;
long magnitude = isNegative ? (-val - 1) : val;
// Shift magnitude left and set sign bit
long encodedValue = (magnitude << 1) | (isNegative ? 1 : 0);
WriteUnsignedInteger(encodedValue);
}
///
/// Flush remaining bits - exact implementation of flush()
///
public void Flush()
{
// If there are remaining bits in buffer, flush with zero padding
if (_stream.Capacity < EXIConstantsExact.BITS_IN_BYTE)
{
// Shift remaining bits to MSB and write
byte paddedBuffer = (byte)(_stream.Buffer << _stream.Capacity);
if (_stream.Position >= _stream.Size)
throw new InvalidOperationException("Output buffer overflow");
_stream.Data[_stream.Position++] = paddedBuffer;
_stream.Buffer = 0;
_stream.Capacity = EXIConstantsExact.BITS_IN_BYTE;
}
}
public byte[] ToArray()
{
return _stream.ToArray();
}
public int Position => _stream.Position;
public int BitPosition => EXIConstantsExact.BITS_IN_BYTE - _stream.Capacity;
}
}