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

7
Data/dump0.dump Normal file
View File

@@ -0,0 +1,7 @@
0000 10 22 33 44 55 66 80 34 28 2e 23 dd 86 dd 60 00 ."3DUf.4(.#...`.
0010 00 00 00 33 06 ff fe 80 00 00 00 00 00 00 82 34 ...3...........4
0020 28 ff fe 2e 23 dd fe 80 00 00 00 00 00 00 12 22 (...#.........."
0030 33 ff fe 44 55 66 d1 21 c3 65 2c c5 61 f8 00 63 3..DUf.!.e,.a..c
0040 ae c9 50 18 08 a8 72 51 00 00 01 fe 80 01 00 00 ..P...rQ........
0050 00 17 80 98 02 10 50 90 8c 0c 0c 0e 0c 51 80 00 ......P......Q..
0060 00 00 20 40 c4 08 a0 30 00 .. @...0.

7
Data/dump1.dump Normal file
View File

@@ -0,0 +1,7 @@
0000 10 22 33 44 55 66 80 34 28 2e 23 dd 86 dd 60 00 ."3DUf.4(.#...`.
0010 00 00 00 33 06 ff fe 80 00 00 00 00 00 00 82 34 ...3...........4
0020 28 ff fe 2e 23 dd fe 80 00 00 00 00 00 00 12 22 (...#.........."
0030 33 ff fe 44 55 66 d1 21 c3 65 2c c5 4a 3c 00 63 3..DUf.!.e,.J<.c
0040 9b 49 50 18 0b 26 2f bd 00 00 01 fe 80 01 00 00 .IP..&/.........
0050 00 17 80 98 02 10 50 90 8c 0c 0c 0e 0c 51 e0 20 ......P......Q.
0060 25 69 68 c0 c0 c0 c0 c0 80 %ih......

7
Data/dump2.dump Normal file
View File

@@ -0,0 +1,7 @@
0000 80 34 28 2e 23 dd 10 22 33 44 55 66 86 dd 60 00 .4(.#.."3DUf..`.
0010 00 00 00 2c 06 ff fe 80 00 00 00 00 00 00 12 22 ...,..........."
0020 33 ff fe 44 55 66 fe 80 00 00 00 00 00 00 82 34 3..DUf.........4
0030 28 ff fe 2e 23 dd c3 65 d1 21 00 7d 20 74 2c e1 (...#..e.!.} t,.
0040 d2 0a 50 18 11 1c 49 71 00 00 01 fe 80 01 00 00 ..P...Iq........
0050 00 10 80 98 02 10 50 90 8c 0c 0c 0e 0c 52 11 00 ......P......R..
0060 32 00 2.

15
Data/encode0.xml Normal file
View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<ns1:V2G_Message xmlns:ns1="urn:iso:15118:2:2013:MsgDef" xmlns:ns2="urn:iso:15118:2:2013:MsgHeader" xmlns:ns3="urn:iso:15118:2:2013:MsgBody" xmlns:ns4="urn:iso:15118:2:2013:MsgDataTypes">
<ns1:Header>
<ns2:SessionID>4142423030303831</ns2:SessionID>
</ns1:Header>
<ns1:Body>
<ns3:WeldingDetectionReq>
<ns3:DC_EVStatus>
<ns4:EVReady>true</ns4:EVReady>
<ns4:EVErrorCode>NO_ERROR</ns4:EVErrorCode>
<ns4:EVRESSSOC>100</ns4:EVRESSSOC>
</ns3:DC_EVStatus>
</ns3:WeldingDetectionReq>
</ns1:Body>
</ns1:V2G_Message>

22
Data/encode1.xml Normal file
View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<ns1:V2G_Message xmlns:ns1="urn:iso:15118:2:2013:MsgDef" xmlns:ns2="urn:iso:15118:2:2013:MsgHeader" xmlns:ns3="urn:iso:15118:2:2013:MsgBody" xmlns:ns4="urn:iso:15118:2:2013:MsgDataTypes">
<ns1:Header>
<ns2:SessionID>4142423030303831</ns2:SessionID>
</ns1:Header>
<ns1:Body>
<ns3:WeldingDetectionRes>
<ns3:ResponseCode>OK</ns3:ResponseCode>
<ns3:DC_EVSEStatus>
<ns4:NotificationMaxDelay>0</ns4:NotificationMaxDelay>
<ns4:EVSENotification>StopCharging</ns4:EVSENotification>
<ns4:EVSEIsolationStatus>Invalid</ns4:EVSEIsolationStatus>
<ns4:EVSEStatusCode>EVSE_Shutdown</ns4:EVSEStatusCode>
</ns3:DC_EVSEStatus>
<ns3:EVSEPresentVoltage>
<ns4:Multiplier>0</ns4:Multiplier>
<ns4:Unit>V</ns4:Unit>
<ns4:Value>449</ns4:Value>
</ns3:EVSEPresentVoltage>
</ns3:WeldingDetectionRes>
</ns1:Body>
</ns1:V2G_Message>

22
Data/encode2.xml Normal file
View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<ns1:V2G_Message xmlns:ns1="urn:iso:15118:2:2013:MsgDef" xmlns:ns2="urn:iso:15118:2:2013:MsgHeader" xmlns:ns3="urn:iso:15118:2:2013:MsgBody" xmlns:ns4="urn:iso:15118:2:2013:MsgDataTypes">
<ns1:Header>
<ns2:SessionID>41424230303038313</ns2:SessionID>
</ns1:Header>
<ns1:Body>
<ns3:PreChargeRes>
<ns3:ResponseCode>OK</ns3:ResponseCode>
<ns3:DC_EVSEStatus>
<ns4:NotificationMaxDelay>0</ns4:NotificationMaxDelay>
<ns4:EVSENotification>None</ns4:EVSENotification>
<ns4:EVSEIsolationStatus>Valid</ns4:EVSEIsolationStatus>
<ns4:EVSEStatusCode>EVSE_Ready</ns4:EVSEStatusCode>
</ns3:DC_EVSEStatus>
<ns3:EVSEPresentVoltage>
<ns4:Multiplier>0</ns4:Multiplier>
<ns4:Unit>V</ns4:Unit>
<ns4:Value>394</ns4:Value>
</ns3:EVSEPresentVoltage>
</ns3:PreChargeRes>
</ns1:Body>
</ns1:V2G_Message>

1
Data/test_exi.hex Normal file
View File

@@ -0,0 +1 @@
8098021050908C0C0C0E0C5211003200

View File

@@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Linq;
namespace V2GProtocol
{
@@ -13,22 +14,333 @@ 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();
// 첫 번째 인자가 옵션인 경우 (--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}");
}
}
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);
@@ -49,15 +361,6 @@ namespace V2GProtocol
// 네트워크 패킷 분석 (이더넷/IPv6/TCP 헤더 포함인 경우)
AnalyzeNetworkPacket(rawData);
Console.WriteLine("\nPress any key to exit...");
Console.ReadKey();
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
Console.WriteLine(ex.StackTrace);
}
}
static void AnalyzeV2GMessages(byte[] data)

File diff suppressed because it is too large Load Diff

View File

@@ -8,4 +8,31 @@
<AssemblyName>V2GDecoder</AssemblyName>
</PropertyGroup>
<ItemGroup>
<None Update="Data\2025-05-01_15-23-10_L460_AVM_AA8_3_DC_Charging_LGIT_32KW_T2_20to100_SOC.pcapng">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Data\dump0.dump">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Data\dump2.dump">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Data\dump1.dump">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Data\encode0.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Data\encode1.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Data\encode2.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Data\Xml.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>