- Port core EXI encoding/decoding functionality to C# - Implement V2G protocol parsing and analysis - Add simplified decoder/encoder for roundtrip testing - Create comprehensive error handling with EXI exceptions - Support both byte array and file stream operations - Include packet structure analysis for V2GTP data - Successfully builds and runs basic functionality tests 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
333 lines
13 KiB
C#
333 lines
13 KiB
C#
/*
|
|
* Copyright (C) 2024 C# Port
|
|
*
|
|
* V2GDecoderNet - C# port of OpenV2G EXI codec
|
|
*
|
|
* 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 V2GDecoderNet.EXI;
|
|
using V2GDecoderNet.V2G;
|
|
|
|
namespace V2GDecoderNet
|
|
{
|
|
class Program
|
|
{
|
|
static void Main(string[] args)
|
|
{
|
|
Console.WriteLine("=== V2GDecoderNet - C# EXI Codec ===");
|
|
Console.WriteLine("OpenV2G C# Port v1.0.0");
|
|
Console.WriteLine();
|
|
|
|
if (args.Length < 1)
|
|
{
|
|
ShowUsage();
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
string command = args[0].ToLower();
|
|
|
|
switch (command)
|
|
{
|
|
case "decode":
|
|
if (args.Length < 2)
|
|
{
|
|
Console.WriteLine("Error: Input file required for decode command");
|
|
ShowUsage();
|
|
return;
|
|
}
|
|
DecodeFile(args[1], args.Length > 2 ? args[2] : null);
|
|
break;
|
|
|
|
case "encode":
|
|
if (args.Length < 2)
|
|
{
|
|
Console.WriteLine("Error: Input file required for encode command");
|
|
ShowUsage();
|
|
return;
|
|
}
|
|
EncodeFile(args[1], args.Length > 2 ? args[2] : null);
|
|
break;
|
|
|
|
case "test":
|
|
RunRoundtripTest(args.Length > 1 ? args[1] : "../../test1.exi");
|
|
break;
|
|
|
|
case "analyze":
|
|
if (args.Length < 2)
|
|
{
|
|
Console.WriteLine("Error: Input file required for analyze command");
|
|
ShowUsage();
|
|
return;
|
|
}
|
|
AnalyzeFile(args[1]);
|
|
break;
|
|
|
|
default:
|
|
Console.WriteLine($"Error: Unknown command '{command}'");
|
|
ShowUsage();
|
|
break;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Error: {ex.Message}");
|
|
if (ex is EXIException exiEx)
|
|
{
|
|
Console.WriteLine($"EXI Error Code: {exiEx.ErrorCode}");
|
|
}
|
|
#if DEBUG
|
|
Console.WriteLine($"Stack Trace: {ex.StackTrace}");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static void ShowUsage()
|
|
{
|
|
Console.WriteLine("Usage:");
|
|
Console.WriteLine(" V2GDecoderNet decode <input.exi> [output.xml] - Decode EXI to XML");
|
|
Console.WriteLine(" V2GDecoderNet encode <input.xml> [output.exi] - Encode XML to EXI");
|
|
Console.WriteLine(" V2GDecoderNet test [input.exi] - Run roundtrip test");
|
|
Console.WriteLine(" V2GDecoderNet analyze <input.exi> - Analyze EXI structure");
|
|
Console.WriteLine();
|
|
Console.WriteLine("Examples:");
|
|
Console.WriteLine(" V2GDecoderNet decode test1.exi test1.xml");
|
|
Console.WriteLine(" V2GDecoderNet encode test1.xml test1_new.exi");
|
|
Console.WriteLine(" V2GDecoderNet test test1.exi");
|
|
}
|
|
|
|
static void DecodeFile(string inputFile, string? outputFile = null)
|
|
{
|
|
Console.WriteLine($"Decoding: {inputFile}");
|
|
|
|
if (!File.Exists(inputFile))
|
|
{
|
|
throw new FileNotFoundException($"Input file not found: {inputFile}");
|
|
}
|
|
|
|
// Read EXI data
|
|
var result = ByteStream.ReadBytesFromFile(inputFile, out byte[] exiData, out int bytesRead);
|
|
if (result != 0)
|
|
{
|
|
throw new EXIException(result, $"Failed to read input file: {inputFile}");
|
|
}
|
|
|
|
Console.WriteLine($"Read {bytesRead} bytes from {inputFile}");
|
|
|
|
// Extract EXI body from V2GTP data if present
|
|
byte[] exiBody = V2GProtocol.ExtractEXIBody(exiData);
|
|
|
|
if (exiBody.Length != exiData.Length)
|
|
{
|
|
Console.WriteLine($"Extracted EXI body: {exiBody.Length} bytes (V2GTP header removed)");
|
|
}
|
|
|
|
// Analyze packet structure
|
|
var analysis = V2GProtocol.AnalyzeDataStructure(exiData);
|
|
Console.WriteLine($"Packet structure: {analysis}");
|
|
|
|
// Decode EXI to XML - use simplified decoder for now
|
|
var simpleDecoder = new SimpleV2GDecoder();
|
|
string xmlOutput = simpleDecoder.DecodeToSimpleXml(exiBody);
|
|
|
|
// Determine output file name
|
|
outputFile ??= Path.ChangeExtension(inputFile, ".xml");
|
|
|
|
// Write XML output
|
|
File.WriteAllText(outputFile, xmlOutput);
|
|
Console.WriteLine($"XML written to: {outputFile}");
|
|
Console.WriteLine($"XML size: {xmlOutput.Length} characters");
|
|
}
|
|
|
|
static void EncodeFile(string inputFile, string? outputFile = null)
|
|
{
|
|
Console.WriteLine($"Encoding: {inputFile}");
|
|
|
|
if (!File.Exists(inputFile))
|
|
{
|
|
throw new FileNotFoundException($"Input file not found: {inputFile}");
|
|
}
|
|
|
|
// Read XML data
|
|
string xmlContent = File.ReadAllText(inputFile);
|
|
Console.WriteLine($"Read {xmlContent.Length} characters from {inputFile}");
|
|
|
|
// Encode XML to EXI - use simplified encoder for now
|
|
var simpleEncoder = new SimpleV2GEncoder();
|
|
byte[] exiData = simpleEncoder.EncodeToSimpleEXI(xmlContent);
|
|
|
|
// Determine output file name
|
|
outputFile ??= Path.ChangeExtension(inputFile, ".exi");
|
|
|
|
// Write EXI output
|
|
int writeResult = ByteStream.WriteBytesToFile(exiData, outputFile);
|
|
if (writeResult != 0)
|
|
{
|
|
throw new EXIException(writeResult, $"Failed to write output file: {outputFile}");
|
|
}
|
|
|
|
Console.WriteLine($"EXI written to: {outputFile}");
|
|
Console.WriteLine($"EXI size: {exiData.Length} bytes");
|
|
}
|
|
|
|
static void AnalyzeFile(string inputFile)
|
|
{
|
|
Console.WriteLine($"Analyzing: {inputFile}");
|
|
|
|
if (!File.Exists(inputFile))
|
|
{
|
|
throw new FileNotFoundException($"Input file not found: {inputFile}");
|
|
}
|
|
|
|
// Read file data
|
|
var result = ByteStream.ReadBytesFromFile(inputFile, out byte[] data, out int bytesRead);
|
|
if (result != 0)
|
|
{
|
|
throw new EXIException(result, $"Failed to read input file: {inputFile}");
|
|
}
|
|
|
|
Console.WriteLine($"File size: {bytesRead} bytes");
|
|
|
|
// Analyze packet structure
|
|
var analysis = V2GProtocol.AnalyzeDataStructure(data);
|
|
Console.WriteLine();
|
|
Console.WriteLine("=== Data Structure Analysis ===");
|
|
Console.WriteLine(analysis);
|
|
Console.WriteLine();
|
|
|
|
// Show hex dump of first 64 bytes
|
|
int dumpSize = Math.Min(64, data.Length);
|
|
Console.WriteLine($"Hex dump (first {dumpSize} bytes):");
|
|
string hexDump = ByteStream.ByteArrayToHexString(data.Take(dumpSize).ToArray());
|
|
|
|
for (int i = 0; i < hexDump.Length; i += 32)
|
|
{
|
|
int length = Math.Min(32, hexDump.Length - i);
|
|
string line = hexDump.Substring(i, length);
|
|
|
|
// Format as pairs
|
|
var pairs = new List<string>();
|
|
for (int j = 0; j < line.Length; j += 2)
|
|
{
|
|
pairs.Add(line.Substring(j, Math.Min(2, line.Length - j)));
|
|
}
|
|
|
|
Console.WriteLine($"{i/2:X4}: {string.Join(" ", pairs)}");
|
|
}
|
|
|
|
// If it has EXI content, try to decode header
|
|
byte[] exiBody = V2GProtocol.ExtractEXIBody(data);
|
|
if (exiBody.Length > 0)
|
|
{
|
|
Console.WriteLine();
|
|
Console.WriteLine("=== EXI Header Analysis ===");
|
|
|
|
try
|
|
{
|
|
var decoder = new EXIDecoder();
|
|
var inputStream = new BitInputStream(exiBody);
|
|
var header = decoder.DecodeHeader(inputStream);
|
|
|
|
Console.WriteLine($"Has Cookie: {header.HasCookie}");
|
|
Console.WriteLine($"Format Version: {header.FormatVersion}");
|
|
Console.WriteLine($"Preserve Comments: {header.PreserveComments}");
|
|
Console.WriteLine($"Preserve PIs: {header.PreservePIs}");
|
|
Console.WriteLine($"Preserve DTD: {header.PreserveDTD}");
|
|
Console.WriteLine($"Preserve Prefixes: {header.PreservePrefixes}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Header analysis failed: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void RunRoundtripTest(string inputFile)
|
|
{
|
|
Console.WriteLine($"Running roundtrip test on: {inputFile}");
|
|
|
|
if (!File.Exists(inputFile))
|
|
{
|
|
throw new FileNotFoundException($"Input file not found: {inputFile}");
|
|
}
|
|
|
|
// Step 1: Read original EXI file
|
|
var result = ByteStream.ReadBytesFromFile(inputFile, out byte[] originalExi, out int originalSize);
|
|
if (result != 0)
|
|
{
|
|
throw new EXIException(result, $"Failed to read input file: {inputFile}");
|
|
}
|
|
|
|
Console.WriteLine($"Original EXI size: {originalSize} bytes");
|
|
|
|
// Step 2: Decode EXI to XML - use simplified decoder for now
|
|
byte[] exiBody = V2GProtocol.ExtractEXIBody(originalExi);
|
|
var simpleDecoder = new SimpleV2GDecoder();
|
|
string xmlContent = simpleDecoder.DecodeToSimpleXml(exiBody);
|
|
|
|
string xmlFile = Path.ChangeExtension(inputFile, ".xml");
|
|
File.WriteAllText(xmlFile, xmlContent);
|
|
Console.WriteLine($"Decoded to XML: {xmlFile} ({xmlContent.Length} characters)");
|
|
|
|
// Step 3: Encode XML back to EXI - use simplified encoder for now
|
|
var simpleEncoder = new SimpleV2GEncoder();
|
|
byte[] newExi = simpleEncoder.EncodeToSimpleEXI(xmlContent);
|
|
|
|
string newExiFile = Path.ChangeExtension(inputFile, "_new.exi");
|
|
int writeResult = ByteStream.WriteBytesToFile(newExi, newExiFile);
|
|
if (writeResult != 0)
|
|
{
|
|
throw new EXIException(writeResult, $"Failed to write output file: {newExiFile}");
|
|
}
|
|
|
|
Console.WriteLine($"Encoded to EXI: {newExiFile} ({newExi.Length} bytes)");
|
|
|
|
// Step 4: Compare original vs new EXI
|
|
bool identical = exiBody.SequenceEqual(newExi);
|
|
|
|
Console.WriteLine();
|
|
Console.WriteLine("=== Roundtrip Test Results ===");
|
|
Console.WriteLine($"Original EXI body: {exiBody.Length} bytes");
|
|
Console.WriteLine($"New EXI: {newExi.Length} bytes");
|
|
Console.WriteLine($"Files identical: {(identical ? "YES ✓" : "NO ✗")}");
|
|
|
|
if (!identical)
|
|
{
|
|
Console.WriteLine();
|
|
Console.WriteLine("Differences found:");
|
|
int maxCompare = Math.Min(exiBody.Length, newExi.Length);
|
|
int differences = 0;
|
|
|
|
for (int i = 0; i < maxCompare; i++)
|
|
{
|
|
if (exiBody[i] != newExi[i])
|
|
{
|
|
differences++;
|
|
if (differences <= 10) // Show first 10 differences
|
|
{
|
|
Console.WriteLine($" Offset {i:X4}: {exiBody[i]:X2} -> {newExi[i]:X2}");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (differences > 10)
|
|
{
|
|
Console.WriteLine($" ... and {differences - 10} more differences");
|
|
}
|
|
|
|
if (exiBody.Length != newExi.Length)
|
|
{
|
|
Console.WriteLine($" Size difference: {newExi.Length - exiBody.Length} bytes");
|
|
}
|
|
}
|
|
|
|
Console.WriteLine();
|
|
Console.WriteLine(identical ? "✓ Roundtrip test PASSED" : "✗ Roundtrip test FAILED");
|
|
}
|
|
}
|
|
} |