Compare commits
3 Commits
fdfab0c666
...
7ba42fe215
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ba42fe215 | |||
| e09c63080e | |||
| 4f2f6a99d7 |
38
Helper.cs
Normal file
38
Helper.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
|
||||
namespace V2GProtocol
|
||||
{
|
||||
/// <summary>
|
||||
/// 헥스 문자열 변환 헬퍼 클래스
|
||||
/// </summary>
|
||||
public static class Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// 헥스 문자열을 바이트 배열로 변환
|
||||
/// </summary>
|
||||
/// <param name="hexString">헥스 문자열</param>
|
||||
/// <returns>바이트 배열</returns>
|
||||
public static byte[] FromHexString(string hexString)
|
||||
{
|
||||
if (hexString.Length % 2 != 0)
|
||||
throw new ArgumentException("Invalid hex string length");
|
||||
|
||||
byte[] bytes = new byte[hexString.Length / 2];
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
bytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 바이트 배열을 헥스 문자열로 변환
|
||||
/// </summary>
|
||||
/// <param name="bytes">바이트 배열</param>
|
||||
/// <returns>헥스 문자열</returns>
|
||||
public static string ToHexString(byte[] bytes)
|
||||
{
|
||||
return BitConverter.ToString(bytes).Replace("-", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
140
Program.cs
140
Program.cs
@@ -9,7 +9,7 @@ namespace V2GProtocol
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// Only show header for help or analysis mode (not for encode/decode operations)
|
||||
// 헤더는 도움말 또는 분석 모드에서만 표시 (인코드/디코드 작업에서는 표시하지 않음)
|
||||
bool showHeader = false;
|
||||
|
||||
if (args.Length == 0)
|
||||
@@ -20,18 +20,18 @@ namespace V2GProtocol
|
||||
{
|
||||
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)
|
||||
// 분석 모드용 파일인지 확인 (옵션 없이 .dump/.txt만)
|
||||
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)
|
||||
// 분석 모드에서만 헤더 표시 (.dump/.txt)
|
||||
// 자동 변환에서는 표시하지 않음 (.hex, .xml)
|
||||
if (extension == ".dump" || extension == ".txt")
|
||||
{
|
||||
showHeader = true;
|
||||
@@ -42,8 +42,9 @@ namespace V2GProtocol
|
||||
|
||||
if (showHeader)
|
||||
{
|
||||
Console.WriteLine("=== V2G Transfer Protocol Decoder/Encoder ===");
|
||||
Console.WriteLine("ISO 15118-2 / DIN SPEC 70121 / SAP Protocol");
|
||||
Console.WriteLine("=== V2G Transfer Protocol Decoder/Encoder");
|
||||
Console.WriteLine("=== ISO 15118-2 / DIN SPEC 70121 / SAP Protocol");
|
||||
Console.WriteLine("=== made by [tindevil82@gmail.com]");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
@@ -62,13 +63,16 @@ namespace V2GProtocol
|
||||
{
|
||||
switch (command)
|
||||
{
|
||||
case "--decode" or "-d":
|
||||
case "--decode":
|
||||
case "-d":
|
||||
HandleDecodeCommand(args);
|
||||
break;
|
||||
case "--encode" or "-e":
|
||||
case "--encode":
|
||||
case "-e":
|
||||
HandleEncodeCommand(args);
|
||||
break;
|
||||
case "--help" or "-h":
|
||||
case "--help":
|
||||
case "-h":
|
||||
ShowUsage();
|
||||
break;
|
||||
default:
|
||||
@@ -97,35 +101,35 @@ namespace V2GProtocol
|
||||
// 파일명만 전달된 경우 (분석 모드)
|
||||
else if (File.Exists(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
|
||||
// .hex 파일 - 자동으로 XML로 디코드
|
||||
var autoArgs = new List<string> { "--decode", args[0] };
|
||||
// Add remaining args (like -out)
|
||||
// 나머지 인자 추가 (-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
|
||||
// .xml 파일 - 자동으로 EXI로 인코드
|
||||
var autoArgs = new List<string> { "--encode", args[0] };
|
||||
// Add remaining args (like -out)
|
||||
// 나머지 인자 추가 (-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");
|
||||
@@ -136,7 +140,7 @@ namespace V2GProtocol
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Error: Unknown option or file not found: {args[0]}");
|
||||
ShowUsage();
|
||||
//ShowUsage();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -166,19 +170,12 @@ namespace V2GProtocol
|
||||
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("Example:");
|
||||
Console.WriteLine("V2GDecoder.exe 01FE80010000001E809802104142423030303831908C0C0C0E0C51E020256968C0C0C0C0C080 --decode");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Examples:");
|
||||
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 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)
|
||||
static string GetOutputFilename(string[] args)
|
||||
{
|
||||
for (int i = 0; i < args.Length - 1; i++)
|
||||
{
|
||||
@@ -194,24 +191,24 @@ namespace V2GProtocol
|
||||
{
|
||||
string input;
|
||||
byte[] exiBytes;
|
||||
string? outputFile = GetOutputFilename(args);
|
||||
string outputFile = GetOutputFilename(args);
|
||||
|
||||
// Check if we have sufficient arguments for file/string input
|
||||
// 파일/문자열 입력을 위한 충분한 인자가 있는지 확인
|
||||
if (args.Length >= 2)
|
||||
{
|
||||
// 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)
|
||||
// 충분한 인자가 없으므로 stdin(파이프)에서 입력이 오는지 확인
|
||||
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
|
||||
outputFile = null; // stdin의 경우 콘솔 출력 강제
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -220,18 +217,18 @@ namespace V2GProtocol
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if input is a file
|
||||
// 입력이 파일인지 확인
|
||||
if (File.Exists(input))
|
||||
{
|
||||
// Handle different file extensions
|
||||
// 다양한 파일 확장자 처리
|
||||
string extension = Path.GetExtension(input).ToLower();
|
||||
|
||||
if (extension == ".dump")
|
||||
{
|
||||
// Hex dump file format with arrows (→)
|
||||
// 화살표(→)가 있는 헥스 덤프 파일 형식
|
||||
byte[] rawData = V2GDecoder.ParseHexFile(input);
|
||||
|
||||
// Find V2G message in the hex dump
|
||||
// 헥스 덤프에서 V2G 메시지 찾기
|
||||
var v2gMessage = ExtractV2GMessageFromHexDump(rawData);
|
||||
if (v2gMessage != null)
|
||||
{
|
||||
@@ -239,24 +236,22 @@ namespace V2GProtocol
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try to decode the raw data directly (might be pure EXI without V2G header)
|
||||
// 원시 데이터를 직접 디코드 시도 (V2G 헤더 없는 순수 EXI일 수 있음)
|
||||
exiBytes = rawData;
|
||||
}
|
||||
}
|
||||
else if (extension == ".hex")
|
||||
{
|
||||
// Plain hex file (pure hex string)
|
||||
string fileContent = File.ReadAllText(input).Trim();
|
||||
fileContent = fileContent.Replace(" ", "").Replace("-", "").Replace("0x", "").Replace("\n", "").Replace("\r", "");
|
||||
exiBytes = Convert.FromHexString(fileContent);
|
||||
// 바이너리 hex 파일
|
||||
exiBytes = File.ReadAllBytes(input);
|
||||
}
|
||||
else if (extension == ".txt")
|
||||
{
|
||||
// Legacy support for .txt files - check content format
|
||||
// .txt 파일에 대한 레거시 지원 - 컨텐츠 형식 확인
|
||||
string fileContent = File.ReadAllText(input).Trim();
|
||||
if (fileContent.Contains("→"))
|
||||
{
|
||||
// Hex dump format
|
||||
// 헥스 덤프 형식
|
||||
byte[] rawData = V2GDecoder.ParseHexFile(input);
|
||||
var v2gMessage = ExtractV2GMessageFromHexDump(rawData);
|
||||
if (v2gMessage != null)
|
||||
@@ -270,22 +265,22 @@ namespace V2GProtocol
|
||||
}
|
||||
else
|
||||
{
|
||||
// Plain hex
|
||||
// 순수 헥스
|
||||
fileContent = fileContent.Replace(" ", "").Replace("-", "").Replace("0x", "").Replace("\n", "").Replace("\r", "");
|
||||
exiBytes = Convert.FromHexString(fileContent);
|
||||
exiBytes = Helper.FromHexString(fileContent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unknown extension, try as plain hex
|
||||
// 알 수 없는 확장자, 순수 헥스로 시도
|
||||
string fileContent = File.ReadAllText(input).Trim();
|
||||
fileContent = fileContent.Replace(" ", "").Replace("-", "").Replace("0x", "").Replace("\n", "").Replace("\r", "");
|
||||
exiBytes = Convert.FromHexString(fileContent);
|
||||
exiBytes = Helper.FromHexString(fileContent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Direct hex string input
|
||||
// 직접 헥스 문자열 입력
|
||||
string exiHexString = input.Replace(" ", "").Replace("-", "").Replace("0x", "");
|
||||
|
||||
if (exiHexString.Length % 2 != 0)
|
||||
@@ -294,23 +289,23 @@ namespace V2GProtocol
|
||||
return;
|
||||
}
|
||||
|
||||
exiBytes = Convert.FromHexString(exiHexString);
|
||||
exiBytes = Helper.FromHexString(exiHexString);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Decode EXI to XML - output only the XML
|
||||
// EXI를 XML로 디코드 - XML만 출력
|
||||
var decodedXml = V2GDecoder.DecodeEXIToXML(exiBytes);
|
||||
|
||||
if (outputFile != null)
|
||||
{
|
||||
// Save to file
|
||||
// 파일로 저장
|
||||
File.WriteAllText(outputFile, decodedXml);
|
||||
Console.WriteLine($"Decoded XML saved to: {outputFile}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Output to console
|
||||
// 콘솔로 출력
|
||||
Console.WriteLine(decodedXml);
|
||||
}
|
||||
}
|
||||
@@ -320,17 +315,17 @@ namespace V2GProtocol
|
||||
}
|
||||
}
|
||||
|
||||
static byte[]? ExtractV2GMessageFromHexDump(byte[] data)
|
||||
static byte[] ExtractV2GMessageFromHexDump(byte[] data)
|
||||
{
|
||||
// Find V2G Transfer Protocol signature (0x01FE)
|
||||
// V2G Transfer Protocol 시그니처 찾기 (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
|
||||
// EXI 페이로드 추출
|
||||
if (i + 8 + payloadLength <= data.Length)
|
||||
{
|
||||
byte[] exiPayload = new byte[payloadLength];
|
||||
@@ -346,15 +341,15 @@ namespace V2GProtocol
|
||||
{
|
||||
string xmlInput;
|
||||
string xmlContent;
|
||||
string? outputFile = GetOutputFilename(args);
|
||||
string outputFile = GetOutputFilename(args);
|
||||
|
||||
// Check if we have sufficient arguments for file/string input
|
||||
// 파일/문자열 입력을 위한 충분한 인자가 있는지 확인
|
||||
if (args.Length >= 2)
|
||||
{
|
||||
// We have command line arguments, use them
|
||||
// 명령줄 인자가 있으므로 사용
|
||||
xmlInput = args[1];
|
||||
|
||||
// Check if input is a file
|
||||
// 입력이 파일인지 확인
|
||||
if (File.Exists(xmlInput))
|
||||
{
|
||||
xmlContent = File.ReadAllText(xmlInput);
|
||||
@@ -366,14 +361,14 @@ namespace V2GProtocol
|
||||
}
|
||||
else if (Console.IsInputRedirected)
|
||||
{
|
||||
// No sufficient arguments, check if input is coming from stdin (pipe)
|
||||
// 충분한 인자가 없으므로 stdin(파이프)에서 입력이 오는지 확인
|
||||
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
|
||||
outputFile = null; // stdin의 경우 콘솔 출력 강제
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -384,7 +379,7 @@ namespace V2GProtocol
|
||||
|
||||
try
|
||||
{
|
||||
// Encode XML to EXI
|
||||
// XML을 EXI로 인코드
|
||||
var exiBytes = V2GDecoder.EncodeXMLToEXI(xmlContent);
|
||||
|
||||
if (outputFile != null)
|
||||
@@ -394,13 +389,13 @@ namespace V2GProtocol
|
||||
Console.WriteLine($"Encoded binary saved to: {outputFile} ({exiBytes.Length} bytes)");
|
||||
|
||||
// Also show hex string on console for reference
|
||||
var exiHexString = Convert.ToHexString(exiBytes);
|
||||
var exiHexString = Helper.ToHexString(exiBytes);
|
||||
Console.WriteLine($"Hex: {exiHexString}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Output hex string to console
|
||||
var exiHexString = Convert.ToHexString(exiBytes);
|
||||
var exiHexString = Helper.ToHexString(exiBytes);
|
||||
Console.WriteLine(exiHexString);
|
||||
}
|
||||
}
|
||||
@@ -493,8 +488,8 @@ namespace V2GProtocol
|
||||
{
|
||||
// 이더넷 헤더 분석 (14 bytes)
|
||||
Console.WriteLine("Ethernet Header:");
|
||||
Console.WriteLine($" Destination MAC: {string.Join(":", data[0..6].Select(b => b.ToString("X2")))}");
|
||||
Console.WriteLine($" Source MAC: {string.Join(":", data[6..12].Select(b => b.ToString("X2")))}");
|
||||
Console.WriteLine($" Destination MAC: {string.Join(":", data.Take(6).Select(b => b.ToString("X2")))}");
|
||||
Console.WriteLine($" Source MAC: {string.Join(":", data.Skip(6).Take(6).Select(b => b.ToString("X2")))}");
|
||||
Console.WriteLine($" EtherType: 0x{(data[12] << 8 | data[13]):X4}");
|
||||
|
||||
ushort etherType = (ushort)(data[12] << 8 | data[13]);
|
||||
@@ -516,8 +511,10 @@ namespace V2GProtocol
|
||||
Console.WriteLine($" Hop Limit: {hopLimit}");
|
||||
|
||||
// IPv6 주소는 16바이트씩
|
||||
var srcAddr = data[(ipv6Offset + 8)..(ipv6Offset + 24)];
|
||||
var dstAddr = data[(ipv6Offset + 24)..(ipv6Offset + 40)];
|
||||
byte[] srcAddr = new byte[16];
|
||||
byte[] dstAddr = new byte[16];
|
||||
Array.Copy(data, ipv6Offset + 8, srcAddr, 0, 16);
|
||||
Array.Copy(data, ipv6Offset + 24, dstAddr, 0, 16);
|
||||
|
||||
Console.WriteLine($" Source Address: {string.Join(":", Enumerable.Range(0, 8).Select(i => $"{srcAddr[i*2]:X2}{srcAddr[i*2+1]:X2}"))}");
|
||||
Console.WriteLine($" Destination Address: {string.Join(":", Enumerable.Range(0, 8).Select(i => $"{dstAddr[i*2]:X2}{dstAddr[i*2+1]:X2}"))}");
|
||||
@@ -547,7 +544,8 @@ namespace V2GProtocol
|
||||
if (tcpDataOffset < data.Length)
|
||||
{
|
||||
Console.WriteLine($"\nTCP Payload (starting at offset 0x{tcpDataOffset:X4}):");
|
||||
byte[] tcpPayload = data[tcpDataOffset..];
|
||||
byte[] tcpPayload = new byte[data.Length - tcpDataOffset];
|
||||
Array.Copy(data, tcpDataOffset, tcpPayload, 0, tcpPayload.Length);
|
||||
|
||||
// TCP 페이로드에서 V2G 메시지 찾기
|
||||
if (tcpPayload.Length > 8 && tcpPayload[0] == 0x01 && tcpPayload[1] == 0xFE)
|
||||
|
||||
@@ -420,7 +420,7 @@ namespace V2GProtocol
|
||||
if (byteCounts[i] > 0)
|
||||
{
|
||||
double p = (double)byteCounts[i] / totalBytes;
|
||||
entropy -= p * Math.Log2(p);
|
||||
entropy -= p * (Math.Log(p) / Math.Log(2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,7 +447,7 @@ namespace V2GProtocol
|
||||
|
||||
foreach (var keyword in v2gKeywords)
|
||||
{
|
||||
if (text.Contains(keyword, StringComparison.OrdinalIgnoreCase))
|
||||
if (text.ToLower().Contains(keyword.ToLower()))
|
||||
xmlIndicators += 2;
|
||||
}
|
||||
|
||||
@@ -488,7 +488,7 @@ namespace V2GProtocol
|
||||
|
||||
foreach (var keyword in keywords)
|
||||
{
|
||||
if (dataAsString.Contains(keyword, StringComparison.OrdinalIgnoreCase))
|
||||
if (dataAsString.ToLower().Contains(keyword.ToLower()))
|
||||
{
|
||||
found.Add(keyword);
|
||||
}
|
||||
@@ -1057,7 +1057,7 @@ namespace V2GProtocol
|
||||
var sessionId = ExtractValueFromXML(xmlContent, "SessionID");
|
||||
if (!string.IsNullOrEmpty(sessionId) && sessionId.Length == 16) // 8 bytes as hex
|
||||
{
|
||||
var sessionBytes = Convert.FromHexString(sessionId);
|
||||
var sessionBytes = Helper.FromHexString(sessionId);
|
||||
foreach (byte b in sessionBytes)
|
||||
{
|
||||
if (b >= 0x30 && b <= 0x5A) // ASCII range
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<LangVersion>9.0</LangVersion>
|
||||
<AssemblyName>V2GDecoder</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user