Add support for pipe input and file extension handling

- Added stdin pipe support for both encode and decode operations
- Implemented file extension-based processing:
  * .dump files: hex dump format with arrow separators
  * .hex files: pure hex string data
  * .xml files: XML content for encoding
- Added 'filename --encode' and 'filename --decode' syntax support
- Enhanced command line parsing for flexible usage
- Updated help text with pipe usage examples

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Arin(asus)
2025-09-07 15:07:31 +09:00
parent 8a718f5d4f
commit f9b17dd7e7
10 changed files with 1386 additions and 64 deletions

View File

@@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Linq;
namespace V2GProtocol
{
@@ -13,53 +14,355 @@ namespace V2GProtocol
try
{
// 기본 데이터 파일 경로
string dataFilePath = @"data\632 raw data.txt";
if (args.Length > 0)
if (args.Length == 0)
{
dataFilePath = args[0];
}
if (!File.Exists(dataFilePath))
{
Console.WriteLine($"Error: Data file not found: {dataFilePath}");
Console.WriteLine("Usage: V2GDecoder.exe [data_file_path]");
ShowUsage();
return;
}
Console.WriteLine($"Loading data from: {dataFilePath}");
var command = args[0].ToLower();
// 헥스 파일에서 바이너리 데이터 파싱
byte[] rawData = V2GDecoder.ParseHexFile(dataFilePath);
Console.WriteLine($"Parsed {rawData.Length} bytes from hex file");
Console.WriteLine();
// 전체 데이터 헥스 덤프
Console.WriteLine("=== Raw Data Hex Dump ===");
Console.WriteLine(V2GDecoder.BytesToHex(rawData));
Console.WriteLine();
// V2G 메시지 디코딩 시도
Console.WriteLine("=== V2G Message Analysis ===");
// 전체 데이터에서 V2G 메시지 찾기
AnalyzeV2GMessages(rawData);
// 네트워크 패킷 분석 (이더넷/IPv6/TCP 헤더 포함인 경우)
AnalyzeNetworkPacket(rawData);
Console.WriteLine("\nPress any key to exit...");
Console.ReadKey();
// 첫 번째 인자가 옵션인 경우 (--decode, --encode, --help)
if (command.StartsWith("--") || command.StartsWith("-"))
{
switch (command)
{
case "--decode" or "-d":
HandleDecodeCommand(args);
break;
case "--encode" or "-e":
HandleEncodeCommand(args);
break;
case "--help" or "-h":
ShowUsage();
break;
default:
Console.WriteLine($"Error: Unknown option: {args[0]}");
ShowUsage();
break;
}
}
// 두 번째 인자가 옵션인 경우 (filename --decode, filename --encode)
else if (args.Length >= 2 && (args[1].ToLower() == "--decode" || args[1].ToLower() == "-d"))
{
// filename --decode 형태
string[] newArgs = { args[1], args[0] }; // 순서를 바꿔서 --decode filename 형태로 만듦
HandleDecodeCommand(newArgs);
}
else if (args.Length >= 2 && (args[1].ToLower() == "--encode" || args[1].ToLower() == "-e"))
{
// filename --encode 형태
string[] newArgs = { args[1], args[0] }; // 순서를 바꿔서 --encode filename 형태로 만듦
HandleEncodeCommand(newArgs);
}
// 파일명만 전달된 경우 (분석 모드)
else if (File.Exists(args[0]))
{
AnalyzeFile(args[0]);
}
else
{
Console.WriteLine($"Error: Unknown option or file not found: {args[0]}");
ShowUsage();
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
Console.WriteLine(ex.StackTrace);
}
}
static void ShowUsage()
{
Console.WriteLine("Usage:");
Console.WriteLine(" V2GDecoder.exe [file.txt] # Analyze hex dump file");
Console.WriteLine(" V2GDecoder.exe --decode <exi_hex_or_file> # Decode EXI to XML");
Console.WriteLine(" V2GDecoder.exe --encode <xml_file_or_string> # Encode XML to EXI");
Console.WriteLine(" V2GDecoder.exe <file> --decode # Decode file content to XML");
Console.WriteLine(" V2GDecoder.exe <file> --encode # Encode file content to EXI");
Console.WriteLine(" V2GDecoder.exe --help # Show this help");
Console.WriteLine();
Console.WriteLine("Pipe Usage:");
Console.WriteLine(" type file.xml | V2GDecoder.exe --encode # Pipe XML file to encode");
Console.WriteLine(" echo \"hex_string\" | V2GDecoder.exe --decode # Pipe hex string to decode");
Console.WriteLine();
Console.WriteLine("Examples:");
Console.WriteLine(" V2GDecoder.exe data/dump2.txt # Full analysis");
Console.WriteLine(" V2GDecoder.exe -d \"8098021050908C0C0C0E0C5211003200\" # Decode hex string");
Console.WriteLine(" V2GDecoder.exe -d data/dump2.txt # Extract & decode from file");
Console.WriteLine(" V2GDecoder.exe -e \"<V2G_Message>...</V2G_Message>\" # Encode XML string");
Console.WriteLine(" V2GDecoder.exe -e message.xml # Encode XML file");
Console.WriteLine(" V2GDecoder.exe data/encode0.xml --encode # Encode XML file to EXI");
Console.WriteLine(" V2GDecoder.exe data/dump2.txt --decode # Decode file to XML only");
Console.WriteLine(" type data\\encode0.xml | V2GDecoder.exe --encode # Pipe file content to encode");
Console.WriteLine(" echo \"8098021050908C0C0C0E0C5211003200\" | V2GDecoder.exe -d # Pipe hex to decode");
}
static void HandleDecodeCommand(string[] args)
{
string input;
byte[] exiBytes;
// Check if input is coming from stdin (pipe)
if (args.Length < 2)
{
if (!Console.IsInputRedirected)
{
Console.WriteLine("Error: EXI hex string or file required for decode operation");
Console.WriteLine("Usage: V2GDecoder.exe --decode <exi_hex_string_or_file>");
Console.WriteLine(" or: echo \"hex_string\" | V2GDecoder.exe --decode");
return;
}
// Read from stdin
Console.WriteLine("Reading EXI data from stdin (pipe input)...");
input = Console.In.ReadToEnd().Trim();
if (string.IsNullOrEmpty(input))
{
Console.WriteLine("Error: No input data received from stdin");
return;
}
}
else
{
input = args[1];
}
// Check if input is a file
if (File.Exists(input))
{
Console.WriteLine($"Reading EXI data from file: {input}");
// Handle different file extensions
string extension = Path.GetExtension(input).ToLower();
if (extension == ".dump")
{
// Hex dump file format with arrows (→)
Console.WriteLine("Processing hex dump format file...");
byte[] rawData = V2GDecoder.ParseHexFile(input);
// Find V2G message in the hex dump
var v2gMessage = ExtractV2GMessageFromHexDump(rawData);
if (v2gMessage != null)
{
exiBytes = v2gMessage;
Console.WriteLine($"Extracted V2G EXI message from hex dump ({exiBytes.Length} bytes).");
}
else
{
Console.WriteLine("Warning: No V2G message header (01 FE) found in hex dump. Attempting to decode raw data.");
// Try to decode the raw data directly (might be pure EXI without V2G header)
exiBytes = rawData;
}
}
else if (extension == ".hex")
{
// Plain hex file (pure hex string)
Console.WriteLine("Processing hex string file...");
string fileContent = File.ReadAllText(input).Trim();
fileContent = fileContent.Replace(" ", "").Replace("-", "").Replace("0x", "").Replace("\n", "").Replace("\r", "");
exiBytes = Convert.FromHexString(fileContent);
Console.WriteLine($"Parsed hex string from file ({exiBytes.Length} bytes).");
}
else if (extension == ".txt")
{
// Legacy support for .txt files - check content format
string fileContent = File.ReadAllText(input).Trim();
if (fileContent.Contains("→"))
{
// Hex dump format
Console.WriteLine("Processing legacy hex dump format file...");
byte[] rawData = V2GDecoder.ParseHexFile(input);
var v2gMessage = ExtractV2GMessageFromHexDump(rawData);
if (v2gMessage != null)
{
exiBytes = v2gMessage;
Console.WriteLine($"Extracted V2G EXI message from hex dump ({exiBytes.Length} bytes).");
}
else
{
exiBytes = rawData;
}
}
else
{
// Plain hex
fileContent = fileContent.Replace(" ", "").Replace("-", "").Replace("0x", "").Replace("\n", "").Replace("\r", "");
exiBytes = Convert.FromHexString(fileContent);
Console.WriteLine($"Parsed hex string from file ({exiBytes.Length} bytes).");
}
}
else
{
// Unknown extension, try as plain hex
Console.WriteLine($"Unknown file extension '{extension}', attempting to parse as hex string...");
string fileContent = File.ReadAllText(input).Trim();
fileContent = fileContent.Replace(" ", "").Replace("-", "").Replace("0x", "").Replace("\n", "").Replace("\r", "");
exiBytes = Convert.FromHexString(fileContent);
}
}
else
{
// Direct hex string input
string exiHexString = input.Replace(" ", "").Replace("-", "").Replace("0x", "");
if (exiHexString.Length % 2 != 0)
{
Console.WriteLine("Error: Invalid hex string (odd number of characters)");
return;
}
exiBytes = Convert.FromHexString(exiHexString);
Console.WriteLine("Processing direct hex string input.");
}
try
{
Console.WriteLine($"Decoding EXI data ({exiBytes.Length} bytes):");
Console.WriteLine($"Input: {Convert.ToHexString(exiBytes)}");
Console.WriteLine();
// Decode EXI to XML
var decodedXml = V2GDecoder.DecodeEXIToXML(exiBytes);
Console.WriteLine(decodedXml);
}
catch (Exception ex)
{
Console.WriteLine($"Error decoding EXI: {ex.Message}");
}
}
static byte[]? ExtractV2GMessageFromHexDump(byte[] data)
{
// Find V2G Transfer Protocol signature (0x01FE)
for (int i = 0; i < data.Length - 8; i++)
{
if (data[i] == 0x01 && data[i + 1] == 0xFE)
{
// Get payload length
uint payloadLength = (uint)((data[i + 4] << 24) | (data[i + 5] << 16) | (data[i + 6] << 8) | data[i + 7]);
// Extract EXI payload
if (i + 8 + payloadLength <= data.Length)
{
byte[] exiPayload = new byte[payloadLength];
Array.Copy(data, i + 8, exiPayload, 0, (int)payloadLength);
return exiPayload;
}
}
}
return null;
}
static void HandleEncodeCommand(string[] args)
{
string xmlInput;
string xmlContent;
// Check if input is coming from stdin (pipe)
if (args.Length < 2)
{
if (!Console.IsInputRedirected)
{
Console.WriteLine("Error: XML string or file required for encode operation");
Console.WriteLine("Usage: V2GDecoder.exe --encode <xml_file_or_string>");
Console.WriteLine(" or: type file.xml | V2GDecoder.exe --encode");
return;
}
// Read from stdin
Console.WriteLine("Reading XML data from stdin (pipe input)...");
xmlContent = Console.In.ReadToEnd().Trim();
if (string.IsNullOrEmpty(xmlContent))
{
Console.WriteLine("Error: No input data received from stdin");
return;
}
}
else
{
xmlInput = args[1];
// Check if input is a file
if (File.Exists(xmlInput))
{
string extension = Path.GetExtension(xmlInput).ToLower();
if (extension == ".xml")
{
Console.WriteLine($"Reading XML from file: {xmlInput}");
xmlContent = File.ReadAllText(xmlInput);
}
else
{
// For non-XML extensions, still try to read as text file
Console.WriteLine($"Reading content from file: {xmlInput} (extension: {extension})");
xmlContent = File.ReadAllText(xmlInput);
}
}
else
{
Console.WriteLine("Processing XML string:");
xmlContent = xmlInput;
}
}
try
{
Console.WriteLine("Input XML:");
Console.WriteLine(xmlContent);
Console.WriteLine();
// Encode XML to EXI
var exiBytes = V2GDecoder.EncodeXMLToEXI(xmlContent);
var exiHexString = Convert.ToHexString(exiBytes);
Console.WriteLine($"Encoded EXI data ({exiBytes.Length} bytes):");
Console.WriteLine(exiHexString);
// Also show formatted hex
Console.WriteLine();
Console.WriteLine("Formatted hex:");
Console.WriteLine(V2GDecoder.BytesToHex(exiBytes));
}
catch (Exception ex)
{
Console.WriteLine($"Error encoding XML: {ex.Message}");
}
}
static void AnalyzeFile(string dataFilePath)
{
if (!File.Exists(dataFilePath))
{
Console.WriteLine($"Error: Data file not found: {dataFilePath}");
return;
}
Console.WriteLine($"Analyzing file: {dataFilePath}");
// 헥스 파일에서 바이너리 데이터 파싱
byte[] rawData = V2GDecoder.ParseHexFile(dataFilePath);
Console.WriteLine($"Parsed {rawData.Length} bytes from hex file");
Console.WriteLine();
// 전체 데이터 헥스 덤프
Console.WriteLine("=== Raw Data Hex Dump ===");
Console.WriteLine(V2GDecoder.BytesToHex(rawData));
Console.WriteLine();
// V2G 메시지 디코딩 시도
Console.WriteLine("=== V2G Message Analysis ===");
// 전체 데이터에서 V2G 메시지 찾기
AnalyzeV2GMessages(rawData);
// 네트워크 패킷 분석 (이더넷/IPv6/TCP 헤더 포함인 경우)
AnalyzeNetworkPacket(rawData);
}
static void AnalyzeV2GMessages(byte[] data)
{
// V2G Transfer Protocol 시그니처 찾기