/* * Copyright (C) 2007-2024 C# Port * Original Copyright (C) 2007-2018 Siemens AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. */ using System; namespace V2GDecoderNetFx.EXI { /// /// Bit output stream for writing EXI encoded data /// public class BitOutputStream { private byte[] _buffer; private int _position; private int _bitPosition; private int _capacity; public BitOutputStream(int capacity = EXIConstants.BUFFER_SIZE) { _capacity = capacity; _buffer = new byte[capacity]; _position = 0; _bitPosition = 0; } public int Position => _position; public int BitPosition => _bitPosition; public int Capacity => _capacity; /// /// Write a single bit /// /// Bit value (0 or 1) public void WriteBit(int bit) { if (bit != 0 && bit != 1) throw new ArgumentException("Bit value must be 0 or 1", nameof(bit)); EnsureCapacity(_position + 1); if (bit == 1) { _buffer[_position] |= (byte)(1 << (7 - _bitPosition)); } _bitPosition++; if (_bitPosition == 8) { _bitPosition = 0; _position++; } } /// /// Write multiple bits from unsigned integer /// /// Value to write /// Number of bits to write (1-32) public void WriteBits(uint value, int numBits) { if (numBits < 1 || numBits > 32) throw new ArgumentException("Number of bits must be between 1 and 32", nameof(numBits)); for (int i = numBits - 1; i >= 0; i--) { int bit = (int)((value >> i) & 1); WriteBit(bit); } } /// /// Write unsigned integer using EXI encoding /// /// Unsigned integer value public void WriteUnsignedInteger(uint value) { if (value == 0) { WriteByte(0); return; } // Calculate number of bytes needed var bytes = new List(); while (value > 0) { byte currentByte = (byte)(value & 0x7F); value >>= 7; if (value > 0) currentByte |= 0x80; // Set continuation bit bytes.Add(currentByte); } // Write bytes in reverse order (big-endian) for (int i = bytes.Count - 1; i >= 0; i--) { WriteByte(bytes[i]); } } /// /// Write signed integer using EXI encoding /// /// Signed integer value public void WriteInteger(int value) { // Encode sign in LSB, shift value uint unsignedValue; if (value < 0) { unsignedValue = ((uint)(-value) << 1) | 1; } else { unsignedValue = (uint)value << 1; } WriteUnsignedInteger(unsignedValue); } /// /// Write a byte aligned to byte boundary /// /// Byte value public void WriteByte(byte value) { // Align to byte boundary if (_bitPosition != 0) { _bitPosition = 0; _position++; } EnsureCapacity(_position + 1); _buffer[_position++] = value; } /// /// Write multiple bytes /// /// Byte array to write public void WriteBytes(byte[] data) { if (data == null || data.Length == 0) return; // Align to byte boundary if (_bitPosition != 0) { _bitPosition = 0; _position++; } EnsureCapacity(_position + data.Length); Array.Copy(data, 0, _buffer, _position, data.Length); _position += data.Length; } /// /// Align to next byte boundary /// public void AlignToByteBank() { if (_bitPosition != 0) { _bitPosition = 0; _position++; } } /// /// Get the written data as byte array /// /// Byte array containing written data public byte[] ToArray() { int length = _position + (_bitPosition > 0 ? 1 : 0); var result = new byte[length]; Array.Copy(_buffer, result, length); return result; } /// /// Get the current buffer length in bytes /// /// Length in bytes public int GetLength() { return _position + (_bitPosition > 0 ? 1 : 0); } /// /// Reset the stream position to beginning /// public void Reset() { _position = 0; _bitPosition = 0; Array.Clear(_buffer, 0, _buffer.Length); } /// /// Ensure buffer has enough capacity /// /// Required size in bytes private void EnsureCapacity(int requiredSize) { if (requiredSize > _capacity) { int newCapacity = Math.Max(_capacity * 2, requiredSize); var newBuffer = new byte[newCapacity]; Array.Copy(_buffer, newBuffer, _position); _buffer = newBuffer; _capacity = newCapacity; } } /// /// Get current buffer usage statistics /// /// Usage information public (int UsedBytes, int TotalCapacity, double UsagePercentage) GetUsageStats() { int usedBytes = GetLength(); double usage = (double)usedBytes / _capacity * 100.0; return (usedBytes, _capacity, usage); } } }