Major architectural refactoring to achieve 1:1 structural compatibility: 🏗️ **VC2022 Structure Replication** - Iso1EXIDocument: 1:1 replica of VC2022 iso1EXIDocument struct - DinEXIDocument: 1:1 replica of VC2022 dinEXIDocument struct - Iso2EXIDocument: 1:1 replica of VC2022 iso2EXIDocument struct - All _isUsed flags and Initialize() methods exactly matching VC2022 🔄 **VC2022 Function Porting** - ParseXmlToIso1(): Exact port of VC2022 parse_xml_to_iso1() - EncodeIso1ExiDocument(): Exact port of VC2022 encode_iso1ExiDocument() - Choice 76 (V2G_Message) encoding with identical logic - BulkChargingComplete ignore behavior preserved ⚡ **Call Sequence Alignment** - Old: EncodeV2GMessage() → direct EXI encoding - New: EncodeV2GMessage() → Iso1EXIDocument → EncodeIso1ExiDocument() - Exact VC2022 call chain: init → parse → encode → finish 🔍 **1:1 Debug Comparison Ready** - C# exiDoc.V2G_Message_isUsed ↔ VC2022 exiDoc->V2G_Message_isUsed - Identical structure enables line-by-line debugging comparison - Ready for precise 1-byte difference investigation (41 vs 42 bytes) 📁 **Project Reorganization** - Moved from csharp/ to Port/ for cleaner structure - Port/dotnet/ and Port/vc2022/ for parallel development - Complete build system and documentation updates 🎯 **Achievement**: 97.6% binary compatibility (41/42 bytes) Next: 1:1 debug session to identify exact byte difference location 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
237 lines
6.8 KiB
C#
237 lines
6.8 KiB
C#
/*
|
|
* 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.
|
|
*/
|
|
|
|
namespace V2GDecoderNet.EXI
|
|
{
|
|
/// <summary>
|
|
/// Bit output stream for writing EXI encoded data
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// Write a single bit
|
|
/// </summary>
|
|
/// <param name="bit">Bit value (0 or 1)</param>
|
|
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++;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write multiple bits from unsigned integer
|
|
/// </summary>
|
|
/// <param name="value">Value to write</param>
|
|
/// <param name="numBits">Number of bits to write (1-32)</param>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write unsigned integer using EXI encoding
|
|
/// </summary>
|
|
/// <param name="value">Unsigned integer value</param>
|
|
public void WriteUnsignedInteger(uint value)
|
|
{
|
|
if (value == 0)
|
|
{
|
|
WriteByte(0);
|
|
return;
|
|
}
|
|
|
|
// Calculate number of bytes needed
|
|
var bytes = new List<byte>();
|
|
|
|
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]);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write signed integer using EXI encoding
|
|
/// </summary>
|
|
/// <param name="value">Signed integer value</param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a byte aligned to byte boundary
|
|
/// </summary>
|
|
/// <param name="value">Byte value</param>
|
|
public void WriteByte(byte value)
|
|
{
|
|
// Align to byte boundary
|
|
if (_bitPosition != 0)
|
|
{
|
|
_bitPosition = 0;
|
|
_position++;
|
|
}
|
|
|
|
EnsureCapacity(_position + 1);
|
|
_buffer[_position++] = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write multiple bytes
|
|
/// </summary>
|
|
/// <param name="data">Byte array to write</param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Align to next byte boundary
|
|
/// </summary>
|
|
public void AlignToByteBank()
|
|
{
|
|
if (_bitPosition != 0)
|
|
{
|
|
_bitPosition = 0;
|
|
_position++;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the written data as byte array
|
|
/// </summary>
|
|
/// <returns>Byte array containing written data</returns>
|
|
public byte[] ToArray()
|
|
{
|
|
int length = _position + (_bitPosition > 0 ? 1 : 0);
|
|
var result = new byte[length];
|
|
Array.Copy(_buffer, result, length);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the current buffer length in bytes
|
|
/// </summary>
|
|
/// <returns>Length in bytes</returns>
|
|
public int GetLength()
|
|
{
|
|
return _position + (_bitPosition > 0 ? 1 : 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reset the stream position to beginning
|
|
/// </summary>
|
|
public void Reset()
|
|
{
|
|
_position = 0;
|
|
_bitPosition = 0;
|
|
Array.Clear(_buffer, 0, _buffer.Length);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensure buffer has enough capacity
|
|
/// </summary>
|
|
/// <param name="requiredSize">Required size in bytes</param>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get current buffer usage statistics
|
|
/// </summary>
|
|
/// <returns>Usage information</returns>
|
|
public (int UsedBytes, int TotalCapacity, double UsagePercentage) GetUsageStats()
|
|
{
|
|
int usedBytes = GetLength();
|
|
double usage = (double)usedBytes / _capacity * 100.0;
|
|
return (usedBytes, _capacity, usage);
|
|
}
|
|
}
|
|
} |