feat: Add automatic file type detection and enhanced analysis

- 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 <noreply@anthropic.com>
This commit is contained in:
ChiKyun Kim
2025-09-12 17:32:53 +09:00
parent a3eb5cbf27
commit a5247e0d32
2 changed files with 305 additions and 22 deletions

View File

@@ -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)