/* * 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 [output.xml] - Decode EXI to XML"); Console.WriteLine(" V2GDecoderNet encode [output.exi] - Encode XML to EXI"); Console.WriteLine(" V2GDecoderNet test [input.exi] - Run roundtrip test"); Console.WriteLine(" V2GDecoderNet analyze - 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(); 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"); } } }