From a5247e0d3214b21ece68e2d9dece1d165543d5a3 Mon Sep 17 00:00:00 2001 From: ChiKyun Kim Date: Fri, 12 Sep 2025 17:32:53 +0900 Subject: [PATCH] feat: Add automatic file type detection and enhanced analysis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Auto-detect XML files for encoding, other files for decoding/analysis - Enhanced structure analysis for both .NET and VC versions - Added V2GTP header analysis and EXI structure breakdown - Added message type prediction based on body choice patterns - Improved SessionID analysis with ASCII decoding - Updated usage messages to reflect auto-detection capabilities πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- DotNet/Program.cs | 177 ++++++++++++++++++++++++++++++++++++++++++---- VC/V2GDecoder.c | 150 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 305 insertions(+), 22 deletions(-) diff --git a/DotNet/Program.cs b/DotNet/Program.cs index fe62071..628e3df 100644 --- a/DotNet/Program.cs +++ b/DotNet/Program.cs @@ -31,6 +31,17 @@ namespace V2GDecoderNet if (args.Length == 1) { filename = args[0]; + // μžλ™μœΌλ‘œ ν™•μž₯자λ₯Ό κ°μ§€ν•˜μ—¬ λͺ¨λ“œ κ²°μ • + string extension = Path.GetExtension(filename).ToLower(); + if (extension == ".xml") + { + encodeMode = true; + Console.WriteLine($"Auto-detected XML file: encoding to EXI"); + } + else + { + Console.WriteLine($"Auto-detected binary file: analyzing structure"); + } } else if (args.Length == 2 && args[0] == "-decode") { @@ -55,9 +66,11 @@ namespace V2GDecoderNet else { Console.Error.WriteLine("Usage: V2GDecoderNet [-decode|-encode] input_file"); + Console.Error.WriteLine(" V2GDecoderNet filename (auto-detect by extension)"); Console.Error.WriteLine(" V2GDecoderNet -encode (read XML from stdin)"); Console.Error.WriteLine(" V2GDecoderNet -decode (read hex string from stdin)"); Console.Error.WriteLine("Enhanced EXI viewer with XML conversion capabilities"); + Console.Error.WriteLine(" filename Auto-detect: .xml files are encoded, others are decoded/analyzed"); Console.Error.WriteLine(" -decode Convert EXI to Wireshark-style XML format"); Console.Error.WriteLine(" -decode Read hex string from stdin (echo hex | V2GDecoderNet -decode)"); Console.Error.WriteLine(" -encode Convert XML to EXI format"); @@ -234,35 +247,171 @@ namespace V2GDecoderNet Console.WriteLine($"First 4 bytes: 0x{firstFourBytes:X8}"); } - // Check for EXI start pattern - for (int i = 0; i <= buffer.Length - 2; i++) - { - ushort pattern = (ushort)((buffer[i] << 8) | buffer[i + 1]); - if (pattern == EXI_START_PATTERN) - { - Console.WriteLine($"EXI start pattern (0x{EXI_START_PATTERN:X4}) found at offset: {i}"); - Console.WriteLine($"EXI payload size: {buffer.Length - i} bytes"); - break; - } - } - - // Determine protocol type + // Determine protocol type and analyze if (buffer.Length >= 8 && buffer[0] == V2G_PROTOCOL_VERSION && buffer[1] == V2G_INV_PROTOCOL_VERSION) { Console.WriteLine("Protocol: V2G Transfer Protocol detected"); + AnalyzeV2GTPHeader(buffer); } else if (buffer.Length >= 2 && ((buffer[0] << 8) | buffer[1]) == EXI_START_PATTERN) { Console.WriteLine("Protocol: Direct EXI format"); + AnalyzeEXIStructure(buffer, 0); } else { - Console.WriteLine("Protocol: Unknown or Direct EXI"); + Console.WriteLine("Protocol: Unknown format - attempting EXI detection"); + // Check for EXI start pattern anywhere in the buffer + for (int i = 0; i <= buffer.Length - 2; i++) + { + ushort pattern = (ushort)((buffer[i] << 8) | buffer[i + 1]); + if (pattern == EXI_START_PATTERN) + { + Console.WriteLine($"EXI start pattern (0x{EXI_START_PATTERN:X4}) found at offset: {i}"); + AnalyzeEXIStructure(buffer, i); + break; + } + } } Console.WriteLine(); } + private static void AnalyzeV2GTPHeader(byte[] buffer) + { + if (buffer.Length < 8) return; + + Console.WriteLine("\n--- V2G Transfer Protocol Header ---"); + Console.WriteLine($"Version: 0x{buffer[0]:X2}"); + Console.WriteLine($"Inverse Version: 0x{buffer[1]:X2}"); + + ushort payloadType = (ushort)((buffer[2] << 8) | buffer[3]); + Console.WriteLine($"Payload Type: 0x{payloadType:X4} ({GetPayloadTypeDescription(payloadType)})"); + + uint payloadLength = (uint)((buffer[4] << 24) | (buffer[5] << 16) | (buffer[6] << 8) | buffer[7]); + Console.WriteLine($"Payload Length: {payloadLength} bytes"); + + if (8 + payloadLength == buffer.Length) + { + Console.WriteLine("βœ“ V2GTP header is valid (payload length matches)"); + } + else + { + Console.WriteLine($"⚠ V2GTP header mismatch (expected {8 + payloadLength}, got {buffer.Length})"); + } + + // Analyze EXI payload if present + if (buffer.Length > 8) + { + AnalyzeEXIStructure(buffer, 8); + } + } + + private static void AnalyzeEXIStructure(byte[] buffer, int offset) + { + if (offset + 4 > buffer.Length) return; + + Console.WriteLine("\n--- EXI Structure Analysis ---"); + Console.WriteLine($"EXI data starts at offset: {offset}"); + Console.WriteLine($"EXI payload size: {buffer.Length - offset} bytes"); + + // EXI Header analysis + if (offset + 4 <= buffer.Length) + { + Console.WriteLine($"EXI Magic: 0x{buffer[offset]:X2} (expected 0x80)"); + Console.WriteLine($"Document Choice: 0x{buffer[offset + 1]:X2}"); + Console.WriteLine($"Grammar State: 0x{buffer[offset + 2]:X2} 0x{buffer[offset + 3]:X2}"); + } + + // SessionID analysis (if present after EXI header) + if (offset + 12 <= buffer.Length) + { + Console.WriteLine("\n--- SessionID Analysis ---"); + Console.Write("SessionID bytes: "); + for (int i = offset + 4; i < offset + 12 && i < buffer.Length; i++) + { + Console.Write($"{buffer[i]:X2} "); + } + Console.WriteLine(); + + // Try to decode SessionID as ASCII if reasonable + if (IsReadableAscii(buffer, offset + 4, 8)) + { + string sessionId = System.Text.Encoding.ASCII.GetString(buffer, offset + 4, 8); + Console.WriteLine($"SessionID (ASCII): \"{sessionId}\""); + } + } + + // Predict message type based on patterns + PredictMessageType(buffer, offset); + } + + private static void PredictMessageType(byte[] buffer, int offset) + { + Console.WriteLine("\n--- Message Type Prediction ---"); + + // Look for body choice pattern (around offset 12-16 typically) + for (int i = offset + 8; i < Math.Min(offset + 20, buffer.Length); i++) + { + byte b = buffer[i]; + // Body choice is typically encoded in specific patterns + if ((b & 0xF0) == 0xD0) // Common pattern for body choices + { + int bodyChoice = (b >> 1) & 0x1F; // Extract 5-bit choice + Console.WriteLine($"Possible Body Choice at offset {i}: {bodyChoice} ({GetMessageTypeDescription(bodyChoice)})"); + } + } + } + + private static bool IsReadableAscii(byte[] buffer, int offset, int length) + { + for (int i = 0; i < length && offset + i < buffer.Length; i++) + { + byte b = buffer[offset + i]; + if (b < 32 || b > 126) // Not printable ASCII + return false; + } + return true; + } + + private static string GetPayloadTypeDescription(ushort payloadType) + { + return payloadType switch + { + V2G_PAYLOAD_ISO_DIN_SAP => "ISO 15118-2/DIN/SAP", + V2G_PAYLOAD_ISO2 => "ISO 15118-20", + _ => "Unknown" + }; + } + + private static string GetMessageTypeDescription(int bodyChoice) + { + return bodyChoice switch + { + 0 => "SessionSetupReq", + 1 => "SessionSetupRes", + 2 => "ServiceDiscoveryReq", + 3 => "ServiceDiscoveryRes", + 4 => "ServiceDetailReq", + 5 => "ServiceDetailRes", + 6 => "PaymentServiceSelectionReq", + 7 => "PaymentServiceSelectionRes", + 8 => "AuthorizationReq", + 9 => "AuthorizationRes", + 10 => "ChargeParameterDiscoveryReq", + 11 => "ChargeParameterDiscoveryRes", + 12 => "PowerDeliveryReq", + 13 => "CurrentDemandReq", + 14 => "CurrentDemandRes", + 15 => "PowerDeliveryRes", + 16 => "ChargingStatusReq", + 17 => "ChargingStatusRes", + 18 => "SessionStopReq", + 19 => "SessionStopRes", + _ => $"Unknown ({bodyChoice})" + }; + } + private static byte[] ExtractExiBody(byte[] inputData) { if (inputData.Length < 8) diff --git a/VC/V2GDecoder.c b/VC/V2GDecoder.c index 7368aa7..d6dc14c 100644 --- a/VC/V2GDecoder.c +++ b/VC/V2GDecoder.c @@ -122,6 +122,122 @@ const char* get_payload_type_name(uint16_t payload_type) { } } +void analyze_v2gtp_header(uint8_t* data, size_t size) { + if (size < 8) return; + + printf("\n--- V2G Transfer Protocol Header ---\n"); + printf("Version: 0x%02X\n", data[0]); + printf("Inverse Version: 0x%02X\n", data[1]); + + uint16_t payload_type = (data[2] << 8) | data[3]; + printf("Payload Type: 0x%04X (%s)\n", payload_type, get_payload_type_name(payload_type)); + + uint32_t payload_length = (data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7]; + printf("Payload Length: %u bytes\n", payload_length); + + if (8 + payload_length == size) { + printf("βœ“ V2GTP header is valid (payload length matches)\n"); + } else { + printf("⚠ V2GTP header mismatch (expected %u, got %zu)\n", (unsigned)(8 + payload_length), size); + } + + // Analyze EXI payload if present + if (size > 8) { + analyze_exi_structure(data, size, 8); + } +} + +void analyze_exi_structure(uint8_t* data, size_t size, size_t offset) { + if (offset + 4 > size) return; + + printf("\n--- EXI Structure Analysis ---\n"); + printf("EXI data starts at offset: %zu\n", offset); + printf("EXI payload size: %zu bytes\n", size - offset); + + // EXI Header analysis + if (offset + 4 <= size) { + printf("EXI Magic: 0x%02X (expected 0x80)\n", data[offset]); + printf("Document Choice: 0x%02X\n", data[offset + 1]); + printf("Grammar State: 0x%02X 0x%02X\n", data[offset + 2], data[offset + 3]); + } + + // SessionID analysis (if present after EXI header) + if (offset + 12 <= size) { + printf("\n--- SessionID Analysis ---\n"); + printf("SessionID bytes: "); + for (size_t i = offset + 4; i < offset + 12 && i < size; i++) { + printf("%02X ", data[i]); + } + printf("\n"); + + // Try to decode SessionID as ASCII if reasonable + if (is_readable_ascii(data, offset + 4, 8)) { + printf("SessionID (ASCII): \""); + for (size_t i = offset + 4; i < offset + 12 && i < size; i++) { + printf("%c", data[i]); + } + printf("\"\n"); + } + } + + // Predict message type based on patterns + predict_message_type(data, size, offset); +} + +int is_readable_ascii(uint8_t* data, size_t offset, size_t length) { + for (size_t i = 0; i < length && offset + i < length; i++) { + uint8_t b = data[offset + i]; + if (b < 32 || b > 126) // Not printable ASCII + return 0; + } + return 1; +} + +void predict_message_type(uint8_t* data, size_t size, size_t offset) { + printf("\n--- Message Type Prediction ---\n"); + + // Look for body choice pattern (around offset 12-16 typically) + for (size_t i = offset + 8; i < offset + 20 && i < size; i++) { + uint8_t b = data[i]; + // Body choice is typically encoded in specific patterns + if ((b & 0xF0) == 0xD0) { // Common pattern for body choices + int body_choice = (b >> 1) & 0x1F; // Extract 5-bit choice + printf("Possible Body Choice at offset %zu: %d (%s)\n", + i, body_choice, get_message_type_description(body_choice)); + } + } +} + +const char* get_message_type_description(int body_choice) { + switch(body_choice) { + case 0: return "SessionSetupReq"; + case 1: return "SessionSetupRes"; + case 2: return "ServiceDiscoveryReq"; + case 3: return "ServiceDiscoveryRes"; + case 4: return "ServiceDetailReq"; + case 5: return "ServiceDetailRes"; + case 6: return "PaymentServiceSelectionReq"; + case 7: return "PaymentServiceSelectionRes"; + case 8: return "AuthorizationReq"; + case 9: return "AuthorizationRes"; + case 10: return "ChargeParameterDiscoveryReq"; + case 11: return "ChargeParameterDiscoveryRes"; + case 12: return "PowerDeliveryReq"; + case 13: return "CurrentDemandReq"; + case 14: return "CurrentDemandRes"; + case 15: return "PowerDeliveryRes"; + case 16: return "ChargingStatusReq"; + case 17: return "ChargingStatusRes"; + case 18: return "SessionStopReq"; + case 19: return "SessionStopRes"; + default: { + static char unknown_msg[32]; + snprintf(unknown_msg, sizeof(unknown_msg), "Unknown (%d)", body_choice); + return unknown_msg; + } + } +} + // Function to analyze complete packet structure void analyze_data_structure(uint8_t* data, size_t size) { printf("=== Data Structure Analysis ===\n"); @@ -237,15 +353,23 @@ void analyze_data_structure(uint8_t* data, size_t size) { } } - // Look for EXI pattern - for (size_t i = 0; i <= size - 2; i++) { - uint16_t pattern = (data[i] << 8) | data[i + 1]; - if (pattern == EXI_START_PATTERN) { - printf("EXI start pattern (0x8098) found at offset: %zu\n", i); - if (i >= offset) { - printf("EXI payload size: %zu bytes\n", size - i); + // Check protocol type and analyze + if (size >= 8 && data[0] == V2G_PROTOCOL_VERSION && data[1] == V2G_INV_PROTOCOL_VERSION) { + printf("Protocol: V2G Transfer Protocol detected\n"); + analyze_v2gtp_header(data, size); + } else if (size >= 2 && ((data[0] << 8) | data[1]) == EXI_START_PATTERN) { + printf("Protocol: Direct EXI format\n"); + analyze_exi_structure(data, size, 0); + } else { + printf("Protocol: Unknown format - attempting EXI detection\n"); + // Look for EXI pattern + for (size_t i = 0; i <= size - 2; i++) { + uint16_t pattern = (data[i] << 8) | data[i + 1]; + if (pattern == EXI_START_PATTERN) { + printf("EXI start pattern (0x8098) found at offset: %zu\n", i); + analyze_exi_structure(data, size, i); + break; } - break; } } printf("\n"); @@ -1453,6 +1577,14 @@ int main(int argc, char *argv[]) { filename = "-"; // Use "-" to indicate stdin } else { filename = argv[arg_index]; + // μžλ™μœΌλ‘œ ν™•μž₯자λ₯Ό κ°μ§€ν•˜μ—¬ λͺ¨λ“œ κ²°μ • + char* ext = strrchr(filename, '.'); + if (ext && _stricmp(ext, ".xml") == 0) { + encode_mode = 1; + printf("Auto-detected XML file: encoding to EXI\n"); + } else { + printf("Auto-detected binary file: analyzing structure\n"); + } } } else if (argc == 3 && strcmp(argv[arg_index], "-decode") == 0) { xml_mode = 1; @@ -1462,9 +1594,11 @@ int main(int argc, char *argv[]) { filename = argv[arg_index + 1]; } else { printf("Usage: V2GDecoder [-debug] [-decode|-encode] input_file\\n"); + printf(" V2GDecoder [-debug] filename (auto-detect by extension)\\n"); printf(" V2GDecoder [-debug] -encode (read XML from stdin)\\n"); printf(" V2GDecoder [-debug] -decode (read hex string from stdin)\\n"); printf("Enhanced EXI viewer with XML conversion capabilities\\n"); + printf(" filename Auto-detect: .xml files are encoded, others are decoded/analyzed\\n"); printf(" -debug Enable detailed bit-level encoding/decoding output\\n"); printf(" -decode Convert EXI to Wireshark-style XML format\\n"); printf(" -decode Read hex string from stdin (echo hex | V2GDecoder -decode)\\n");