diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..717bb4e --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,17 @@ +{ + "permissions": { + "allow": [ + "Bash(dotnet build)", + "Bash(dotnet run:*)", + "Bash(git add:*)", + "Bash(git commit:*)", + "Bash(git push:*)", + "Read(//c/Program Files/Wireshark/plugins/**)", + "WebSearch", + "WebFetch(domain:github.com)", + "Bash(dir data)" + ], + "deny": [], + "ask": [] + } +} \ No newline at end of file diff --git a/Data/Dump/01_V2GMSG-SDP_Request.txt b/Data/Dump/01_V2GMSG-SDP_Request.txt new file mode 100644 index 0000000..9690300 --- /dev/null +++ b/Data/Dump/01_V2GMSG-SDP_Request.txt @@ -0,0 +1,5 @@ +0000 33 33 00 00 00 01 10 22 33 44 55 66 86 dd 60 00 33....."3DUf..`. +0010 00 00 00 12 11 ff fe 80 00 00 00 00 00 00 12 22 ..............." +0020 33 ff fe 44 55 66 ff 02 00 00 00 00 00 00 00 00 3..DUf.......... +0030 00 00 00 00 00 01 c3 51 3b 0e 00 12 c8 18 01 fe .......Q;....... +0040 90 00 00 00 00 02 10 00 ........ \ No newline at end of file diff --git a/Data/Dump/02_V2GMSG-SDP_Response.txt b/Data/Dump/02_V2GMSG-SDP_Response.txt new file mode 100644 index 0000000..2526654 --- /dev/null +++ b/Data/Dump/02_V2GMSG-SDP_Response.txt @@ -0,0 +1,6 @@ +0000 10 22 33 44 55 66 80 34 28 2e 23 dd 86 dd 60 00 ."3DUf.4(.#...`. +0010 00 00 00 24 11 ff fe 80 00 00 00 00 00 00 82 34 ...$...........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 3b 0e c3 51 00 24 5e 42 01 fe 3..DUf;..Q.$^B.. +0040 90 01 00 00 00 14 fe 80 00 00 00 00 00 00 82 34 ...............4 +0050 28 ff fe 2e 23 dd d1 21 10 00 (...#..!.. diff --git a/Data/Dump/03-V2GMSG-SAP_SupportedAppProtocolReq.txt b/Data/Dump/03-V2GMSG-SAP_SupportedAppProtocolReq.txt new file mode 100644 index 0000000..da4d020 --- /dev/null +++ b/Data/Dump/03-V2GMSG-SAP_SupportedAppProtocolReq.txt @@ -0,0 +1,8 @@ +0000 80 34 28 2e 23 dd 10 22 33 44 55 66 86 dd 60 00 .4(.#.."3DUf..`. +0010 00 00 00 40 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 63 9b 07 2c c5 (...#..e.!.c..,. +0040 4a 30 50 18 11 1c d4 f8 00 00 01 fe 80 01 00 00 J0P............. +0050 00 24 80 00 eb ab 93 71 d3 4b 9b 79 d1 89 a9 89 .$.....q.K.y.... +0060 89 c1 d1 91 d1 91 81 89 99 d2 6b 9b 3a 23 2b 30 ..........k.:#+0 +0070 02 00 00 0c 38 40 ....8@ diff --git a/Data/Dump/04-V2GMSG-SAP_SupportedAppProtocolRes.txt b/Data/Dump/04-V2GMSG-SAP_SupportedAppProtocolRes.txt new file mode 100644 index 0000000..b78bc9b --- /dev/null +++ b/Data/Dump/04-V2GMSG-SAP_SupportedAppProtocolRes.txt @@ -0,0 +1,6 @@ +0000 10 22 33 44 55 66 80 34 28 2e 23 dd 86 dd 60 00 ."3DUf.4(.#...`. +0010 00 00 00 20 06 ff fe 80 00 00 00 00 00 00 82 34 ... ...........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 30 00 63 3..DUf.!.e,.J0.c +0040 9b 33 50 18 0b 3c 96 5f 00 00 01 fe 80 01 00 00 .3P..<._........ +0050 00 04 80 40 00 c0 ...@.. diff --git a/Data/632 Raw Data.txt b/Data/data0.dump similarity index 100% rename from Data/632 Raw Data.txt rename to Data/data0.dump diff --git a/Data/encode0.xml b/Data/data0.xml similarity index 100% rename from Data/encode0.xml rename to Data/data0.xml diff --git a/Data/dump1.dump b/Data/data1.dump similarity index 100% rename from Data/dump1.dump rename to Data/data1.dump diff --git a/Data/encode1.xml b/Data/data1.xml similarity index 100% rename from Data/encode1.xml rename to Data/data1.xml diff --git a/Data/dump2.dump b/Data/data2.dump similarity index 100% rename from Data/dump2.dump rename to Data/data2.dump diff --git a/Data/encode2.xml b/Data/data2.xml similarity index 100% rename from Data/encode2.xml rename to Data/data2.xml diff --git a/Data/Xml.txt b/Data/data3.dump similarity index 100% rename from Data/Xml.txt rename to Data/data3.dump diff --git a/Data/dump0.dump b/Data/dump0.dump deleted file mode 100644 index 947f3b1..0000000 --- a/Data/dump0.dump +++ /dev/null @@ -1,7 +0,0 @@ -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. diff --git a/Data/test_decode.xml b/Data/test_decode.xml new file mode 100644 index 0000000..972628a --- /dev/null +++ b/Data/test_decode.xml @@ -0,0 +1,16 @@ + + + + 4142423030303831 + + + + + True + NO_ERROR + 100 + + + + + diff --git a/Data/test_exi.hex b/Data/test_exi.hex deleted file mode 100644 index d964569..0000000 --- a/Data/test_exi.hex +++ /dev/null @@ -1 +0,0 @@ -8098021050908C0C0C0E0C5211003200 \ No newline at end of file diff --git a/Data/test_output.hex b/Data/test_output.hex new file mode 100644 index 0000000..3ef70ca Binary files /dev/null and b/Data/test_output.hex differ diff --git a/Program.cs b/Program.cs index 7b1c296..6cb7331 100644 --- a/Program.cs +++ b/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; @@ -8,9 +9,43 @@ namespace V2GProtocol { static void Main(string[] args) { - Console.WriteLine("=== V2G Transfer Protocol Decoder/Encoder ==="); - Console.WriteLine("ISO 15118-2 / DIN SPEC 70121 / SAP Protocol"); - Console.WriteLine(); + // Only show header for help or analysis mode (not for encode/decode operations) + bool showHeader = false; + + if (args.Length == 0) + { + showHeader = true; + } + else if (args.Length > 0) + { + string firstArg = args[0].ToLower(); + + // Check if it's help command + if (firstArg == "--help" || firstArg == "-h") + { + showHeader = true; + } + // Check if it's a file for analysis mode (only .dump/.txt without options) + else if (File.Exists(args[0]) && args.Length == 1) + { + string extension = Path.GetExtension(args[0]).ToLower(); + + // Only show header for analysis mode (.dump/.txt) + // NOT for auto-conversion (.hex, .xml) + if (extension == ".dump" || extension == ".txt") + { + showHeader = true; + } + // .hex and .xml are auto-conversion, no header + } + } + + if (showHeader) + { + Console.WriteLine("=== V2G Transfer Protocol Decoder/Encoder ==="); + Console.WriteLine("ISO 15118-2 / DIN SPEC 70121 / SAP Protocol"); + Console.WriteLine(); + } try { @@ -45,20 +80,58 @@ namespace V2GProtocol // 두 번째 인자가 옵션인 경우 (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); + // filename --decode 형태 - 나머지 인자들도 전달 + var newArgs = new List { args[1], args[0] }; + for (int i = 2; i < args.Length; i++) + newArgs.Add(args[i]); + HandleDecodeCommand(newArgs.ToArray()); } 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); + // filename --encode 형태 - 나머지 인자들도 전달 + var newArgs = new List { args[1], args[0] }; + for (int i = 2; i < args.Length; i++) + newArgs.Add(args[i]); + HandleEncodeCommand(newArgs.ToArray()); } // 파일명만 전달된 경우 (분석 모드) else if (File.Exists(args[0])) { - AnalyzeFile(args[0]); + // Check file extension to determine default action + string extension = Path.GetExtension(args[0]).ToLower(); + + if (extension == ".dump" || extension == ".txt") + { + // Hex dump files - use full analysis mode + AnalyzeFile(args[0]); + } + else if (extension == ".hex") + { + // .hex files - automatically decode to XML + var autoArgs = new List { "--decode", args[0] }; + // Add remaining args (like -out) + for (int i = 1; i < args.Length; i++) + autoArgs.Add(args[i]); + HandleDecodeCommand(autoArgs.ToArray()); + } + else if (extension == ".xml") + { + // .xml files - automatically encode to EXI + var autoArgs = new List { "--encode", args[0] }; + // Add remaining args (like -out) + for (int i = 1; i < args.Length; i++) + autoArgs.Add(args[i]); + HandleEncodeCommand(autoArgs.ToArray()); + } + else + { + // Unknown extension - require explicit option + Console.WriteLine($"Error: Unknown file extension '{extension}'. Please specify operation:"); + Console.WriteLine($" {Path.GetFileName(args[0])} --decode # To decode as EXI to XML"); + Console.WriteLine($" {Path.GetFileName(args[0])} --encode # To encode as XML to EXI"); + Console.WriteLine($" OR use: --decode {args[0]} / --encode {args[0]}"); + return; + } } else { @@ -75,71 +148,87 @@ namespace V2GProtocol static void ShowUsage() { Console.WriteLine("Usage:"); - Console.WriteLine(" V2GDecoder.exe [file.txt] # Analyze hex dump file"); + Console.WriteLine(" V2GDecoder.exe [file.dump/.txt] # Analyze hex dump file"); + Console.WriteLine(" V2GDecoder.exe [file.hex] # Auto-decode hex to XML"); + Console.WriteLine(" V2GDecoder.exe [file.xml] # Auto-encode XML to EXI"); Console.WriteLine(" V2GDecoder.exe --decode # Decode EXI to XML"); Console.WriteLine(" V2GDecoder.exe --encode # Encode XML to EXI"); - Console.WriteLine(" V2GDecoder.exe --decode # Decode file content to XML"); - Console.WriteLine(" V2GDecoder.exe --encode # Encode file content to EXI"); + Console.WriteLine(" V2GDecoder.exe --decode [-out file] # Force decode operation"); + Console.WriteLine(" V2GDecoder.exe --encode [-out file] # Force encode operation"); Console.WriteLine(" V2GDecoder.exe --help # Show this help"); Console.WriteLine(); + Console.WriteLine("File Extension Auto-Processing:"); + Console.WriteLine(" .dump, .txt → Full analysis mode (network + V2G message)"); + Console.WriteLine(" .hex → Auto-decode to XML"); + Console.WriteLine(" .xml → Auto-encode to EXI hex"); + Console.WriteLine(" Other → Requires --decode or --encode option"); + 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 data/dump2.dump # Full analysis of hex dump"); + Console.WriteLine(" V2GDecoder.exe data/test.hex # Auto-decode hex to XML"); + Console.WriteLine(" V2GDecoder.exe data/encode0.xml # Auto-encode XML to EXI"); 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 \"...\" # 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"); + Console.WriteLine(" V2GDecoder.exe data/test.dat --decode # Decode unknown file type"); + Console.WriteLine(" V2GDecoder.exe data/data0.xml -out data0.hex # Encode to binary file"); + Console.WriteLine(" V2GDecoder.exe --decode data.hex -out out.xml # Decode to XML file"); + Console.WriteLine(" type data\\encode0.xml | V2GDecoder.exe -e # Pipe XML to encode"); + } + + static string? GetOutputFilename(string[] args) + { + for (int i = 0; i < args.Length - 1; i++) + { + if (args[i].ToLower() == "-out" || args[i].ToLower() == "-o") + { + return args[i + 1]; + } + } + return null; } static void HandleDecodeCommand(string[] args) { string input; byte[] exiBytes; + string? outputFile = GetOutputFilename(args); - // Check if input is coming from stdin (pipe) - if (args.Length < 2) + // Check if we have sufficient arguments for file/string input + 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 "); - Console.WriteLine(" or: echo \"hex_string\" | V2GDecoder.exe --decode"); - return; - } - - // Read from stdin - Console.WriteLine("Reading EXI data from stdin (pipe input)..."); + // We have command line arguments, use them + input = args[1]; + } + else if (Console.IsInputRedirected) + { + // No sufficient arguments, check if input is coming from stdin (pipe) input = Console.In.ReadToEnd().Trim(); if (string.IsNullOrEmpty(input)) { Console.WriteLine("Error: No input data received from stdin"); return; } + outputFile = null; // Force console output for stdin } else { - input = args[1]; + Console.WriteLine("Error: EXI hex string or file required for decode operation"); + Console.WriteLine("Usage: V2GDecoder.exe --decode [-out output_file]"); + return; } // 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 @@ -147,11 +236,9 @@ namespace V2GProtocol 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; } @@ -159,11 +246,9 @@ namespace V2GProtocol 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") { @@ -172,13 +257,11 @@ namespace V2GProtocol 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 { @@ -190,13 +273,11 @@ namespace V2GProtocol // 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); @@ -214,18 +295,24 @@ namespace V2GProtocol } 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 + // Decode EXI to XML - output only the XML var decodedXml = V2GDecoder.DecodeEXIToXML(exiBytes); - Console.WriteLine(decodedXml); + + if (outputFile != null) + { + // Save to file + File.WriteAllText(outputFile, decodedXml); + Console.WriteLine($"Decoded XML saved to: {outputFile}"); + } + else + { + // Output to console + Console.WriteLine(decodedXml); + } } catch (Exception ex) { @@ -259,72 +346,63 @@ namespace V2GProtocol { string xmlInput; string xmlContent; + string? outputFile = GetOutputFilename(args); - // Check if input is coming from stdin (pipe) - if (args.Length < 2) + // Check if we have sufficient arguments for file/string input + if (args.Length >= 2) { - if (!Console.IsInputRedirected) - { - Console.WriteLine("Error: XML string or file required for encode operation"); - Console.WriteLine("Usage: V2GDecoder.exe --encode "); - Console.WriteLine(" or: type file.xml | V2GDecoder.exe --encode"); - return; - } + // We have command line arguments, use them + xmlInput = args[1]; - // Read from stdin - Console.WriteLine("Reading XML data from stdin (pipe input)..."); + // Check if input is a file + if (File.Exists(xmlInput)) + { + xmlContent = File.ReadAllText(xmlInput); + } + else + { + xmlContent = xmlInput; + } + } + else if (Console.IsInputRedirected) + { + // No sufficient arguments, check if input is coming from stdin (pipe) xmlContent = Console.In.ReadToEnd().Trim(); if (string.IsNullOrEmpty(xmlContent)) { Console.WriteLine("Error: No input data received from stdin"); return; } + outputFile = null; // Force console output for stdin } 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; - } + Console.WriteLine("Error: XML string or file required for encode operation"); + Console.WriteLine("Usage: V2GDecoder.exe --encode [-out output_file]"); + return; } 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)); + if (outputFile != null) + { + // Save as binary file + File.WriteAllBytes(outputFile, exiBytes); + Console.WriteLine($"Encoded binary saved to: {outputFile} ({exiBytes.Length} bytes)"); + + // Also show hex string on console for reference + var exiHexString = Convert.ToHexString(exiBytes); + Console.WriteLine($"Hex: {exiHexString}"); + } + else + { + // Output hex string to console + var exiHexString = Convert.ToHexString(exiBytes); + Console.WriteLine(exiHexString); + } } catch (Exception ex) { diff --git a/V2GDecoder.cs b/V2GDecoder.cs index ffa6b6f..5b60b1e 100644 --- a/V2GDecoder.cs +++ b/V2GDecoder.cs @@ -619,13 +619,7 @@ namespace V2GProtocol { // 메시지 타입 식별 var messageType = IdentifyV2GMessageType(exiPayload); - sb.AppendLine($"Identified Message Type: {messageType.Type}"); - sb.AppendLine($"Message Category: {messageType.Category}"); - sb.AppendLine($"Message Description: {messageType.Description}"); - sb.AppendLine(); - sb.AppendLine("Reconstructed XML from EXI binary:"); - sb.AppendLine(); sb.AppendLine(""); sb.AppendLine(""); @@ -653,17 +647,11 @@ namespace V2GProtocol sb.AppendLine(" "); sb.AppendLine(""); - - sb.AppendLine(); - sb.AppendLine("=== Message Analysis ==="); - sb.AppendLine($"Message Type: {messageType.Type}"); - sb.AppendLine($"Session ID: {sessionID}"); - sb.AppendLine($"Category: {messageType.Category}"); - sb.AppendLine($"Purpose: {messageType.Description}"); } catch (Exception ex) { - sb.AppendLine($"Error decoding EXI: {ex.Message}"); + // In case of error, return error message + return $"Error decoding EXI: {ex.Message}"; } return sb.ToString();