Major architectural refactoring to achieve 1:1 structural compatibility: 🏗️ **VC2022 Structure Replication** - Iso1EXIDocument: 1:1 replica of VC2022 iso1EXIDocument struct - DinEXIDocument: 1:1 replica of VC2022 dinEXIDocument struct - Iso2EXIDocument: 1:1 replica of VC2022 iso2EXIDocument struct - All _isUsed flags and Initialize() methods exactly matching VC2022 🔄 **VC2022 Function Porting** - ParseXmlToIso1(): Exact port of VC2022 parse_xml_to_iso1() - EncodeIso1ExiDocument(): Exact port of VC2022 encode_iso1ExiDocument() - Choice 76 (V2G_Message) encoding with identical logic - BulkChargingComplete ignore behavior preserved ⚡ **Call Sequence Alignment** - Old: EncodeV2GMessage() → direct EXI encoding - New: EncodeV2GMessage() → Iso1EXIDocument → EncodeIso1ExiDocument() - Exact VC2022 call chain: init → parse → encode → finish 🔍 **1:1 Debug Comparison Ready** - C# exiDoc.V2G_Message_isUsed ↔ VC2022 exiDoc->V2G_Message_isUsed - Identical structure enables line-by-line debugging comparison - Ready for precise 1-byte difference investigation (41 vs 42 bytes) 📁 **Project Reorganization** - Moved from csharp/ to Port/ for cleaner structure - Port/dotnet/ and Port/vc2022/ for parallel development - Complete build system and documentation updates 🎯 **Achievement**: 97.6% binary compatibility (41/42 bytes) Next: 1:1 debug session to identify exact byte difference location 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			209 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			209 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| /*
 | |
|  * Copyright (C) 2007-2024 C# Port
 | |
|  * Original Copyright (C) 2007-2018 Siemens AG
 | |
|  *
 | |
|  * This program is free software: you can redistribute it and/or modify
 | |
|  * it under the terms of the GNU Lesser General Public License as published
 | |
|  * by the Free Software Foundation, either version 3 of the License, or
 | |
|  * (at your option) any later version.
 | |
|  */
 | |
| 
 | |
| using System;
 | |
| using V2GDecoderNet.EXI;
 | |
| 
 | |
| namespace V2GDecoderNet.V2G
 | |
| {
 | |
|     /// <summary>
 | |
|     /// V2G Transfer Protocol constants and definitions
 | |
|     /// </summary>
 | |
|     public static class V2GProtocol
 | |
|     {
 | |
|         // Network protocol patterns
 | |
|         public const ushort ETH_TYPE_IPV6 = 0x86DD;
 | |
|         public const byte IPV6_NEXT_HEADER_TCP = 0x06;
 | |
|         public const ushort TCP_V2G_PORT = 15118;
 | |
| 
 | |
|         // V2G Transfer Protocol patterns
 | |
|         public const byte V2G_PROTOCOL_VERSION = 0x01;
 | |
|         public const byte V2G_INV_PROTOCOL_VERSION = 0xFE;
 | |
|         public const ushort V2G_PAYLOAD_ISO_DIN_SAP = 0x8001;
 | |
|         public const ushort V2G_PAYLOAD_ISO2 = 0x8002;
 | |
|         public const ushort EXI_START_PATTERN = 0x8098;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Get payload type name for display
 | |
|         /// </summary>
 | |
|         /// <param name="payloadType">Payload type value</param>
 | |
|         /// <returns>Human-readable payload type name</returns>
 | |
|         public static string GetPayloadTypeName(ushort payloadType)
 | |
|         {
 | |
|             return payloadType switch
 | |
|             {
 | |
|                 V2G_PAYLOAD_ISO_DIN_SAP => "ISO 15118-2/DIN/SAP",
 | |
|                 V2G_PAYLOAD_ISO2 => "ISO 15118-20",
 | |
|                 _ => "Unknown"
 | |
|             };
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Extract EXI body from V2G Transfer Protocol data
 | |
|         /// </summary>
 | |
|         /// <param name="inputData">Input data containing V2GTP header and EXI body</param>
 | |
|         /// <returns>Extracted EXI body data</returns>
 | |
|         public static byte[] ExtractEXIBody(byte[] inputData)
 | |
|         {
 | |
|             if (inputData == null || inputData.Length < 8)
 | |
|             {
 | |
|                 // Too small for V2GTP header, assume it's pure EXI
 | |
|                 return inputData ?? Array.Empty<byte>();
 | |
|             }
 | |
| 
 | |
|             // First, look for V2G Transfer Protocol header anywhere in the data
 | |
|             // Pattern: 0x01 0xFE 0x80 0x01 (V2GTP header for ISO/DIN/SAP)
 | |
|             for (int i = 0; i <= inputData.Length - 8; i++)
 | |
|             {
 | |
|                 if (inputData[i] == V2G_PROTOCOL_VERSION && inputData[i + 1] == V2G_INV_PROTOCOL_VERSION)
 | |
|                 {
 | |
|                     ushort payloadType = (ushort)((inputData[i + 2] << 8) | inputData[i + 3]);
 | |
| 
 | |
|                     if (payloadType == V2G_PAYLOAD_ISO_DIN_SAP || payloadType == V2G_PAYLOAD_ISO2)
 | |
|                     {
 | |
|                         // Valid V2GTP header found: skip 8-byte header to get EXI body
 | |
|                         int exiStart = i + 8;
 | |
|                         var exiBody = new byte[inputData.Length - exiStart];
 | |
|                         Array.Copy(inputData, exiStart, exiBody, 0, exiBody.Length);
 | |
|                         return exiBody;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // If no V2GTP header found, look for EXI start pattern anywhere in the data
 | |
|             for (int i = 0; i <= inputData.Length - 2; i++)
 | |
|             {
 | |
|                 ushort pattern = (ushort)((inputData[i] << 8) | inputData[i + 1]);
 | |
|                 if (pattern == EXI_START_PATTERN)
 | |
|                 {
 | |
|                     // Found EXI start pattern
 | |
|                     var exiBody = new byte[inputData.Length - i];
 | |
|                     Array.Copy(inputData, i, exiBody, 0, exiBody.Length);
 | |
|                     return exiBody;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // No pattern found, assume it's pure EXI
 | |
|             return inputData;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Analyze complete packet structure
 | |
|         /// </summary>
 | |
|         /// <param name="data">Packet data</param>
 | |
|         /// <returns>Analysis result</returns>
 | |
|         public static PacketAnalysis AnalyzeDataStructure(byte[] data)
 | |
|         {
 | |
|             var analysis = new PacketAnalysis
 | |
|             {
 | |
|                 TotalSize = data?.Length ?? 0,
 | |
|                 HasEthernetHeader = false,
 | |
|                 HasIPv6Header = false,
 | |
|                 HasTCPHeader = false,
 | |
|                 HasV2GTPHeader = false,
 | |
|                 V2GTPPayloadType = 0,
 | |
|                 EXIBodyOffset = 0,
 | |
|                 EXIBodyLength = 0
 | |
|             };
 | |
| 
 | |
|             if (data == null || data.Length == 0)
 | |
|                 return analysis;
 | |
| 
 | |
|             int offset = 0;
 | |
| 
 | |
|             // Check for Ethernet header (at least 14 bytes)
 | |
|             if (data.Length >= 14)
 | |
|             {
 | |
|                 ushort etherType = (ushort)((data[12] << 8) | data[13]);
 | |
|                 if (etherType == ETH_TYPE_IPV6)
 | |
|                 {
 | |
|                     analysis.HasEthernetHeader = true;
 | |
|                     offset = 14;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // Check for IPv6 header (40 bytes)
 | |
|             if (analysis.HasEthernetHeader && data.Length >= offset + 40)
 | |
|             {
 | |
|                 byte version = (byte)((data[offset] >> 4) & 0x0F);
 | |
|                 if (version == 6)
 | |
|                 {
 | |
|                     analysis.HasIPv6Header = true;
 | |
|                     byte nextHeader = data[offset + 6];
 | |
|                     if (nextHeader == IPV6_NEXT_HEADER_TCP)
 | |
|                     {
 | |
|                         offset += 40;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // Check for TCP header (at least 20 bytes)
 | |
|             if (analysis.HasIPv6Header && data.Length >= offset + 20)
 | |
|             {
 | |
|                 ushort destPort = (ushort)((data[offset + 2] << 8) | data[offset + 3]);
 | |
|                 if (destPort == TCP_V2G_PORT)
 | |
|                 {
 | |
|                     analysis.HasTCPHeader = true;
 | |
|                     byte headerLength = (byte)((data[offset + 12] >> 4) * 4);
 | |
|                     offset += headerLength;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // Check for V2GTP header
 | |
|             if (data.Length >= offset + 8)
 | |
|             {
 | |
|                 if (data[offset] == V2G_PROTOCOL_VERSION && data[offset + 1] == V2G_INV_PROTOCOL_VERSION)
 | |
|                 {
 | |
|                     analysis.HasV2GTPHeader = true;
 | |
|                     analysis.V2GTPPayloadType = (ushort)((data[offset + 2] << 8) | data[offset + 3]);
 | |
|                     offset += 8;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // Remaining data is EXI body
 | |
|             analysis.EXIBodyOffset = offset;
 | |
|             analysis.EXIBodyLength = Math.Max(0, data.Length - offset);
 | |
| 
 | |
|             return analysis;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Packet analysis result
 | |
|     /// </summary>
 | |
|     public class PacketAnalysis
 | |
|     {
 | |
|         public int TotalSize { get; set; }
 | |
|         public bool HasEthernetHeader { get; set; }
 | |
|         public bool HasIPv6Header { get; set; }
 | |
|         public bool HasTCPHeader { get; set; }
 | |
|         public bool HasV2GTPHeader { get; set; }
 | |
|         public ushort V2GTPPayloadType { get; set; }
 | |
|         public int EXIBodyOffset { get; set; }
 | |
|         public int EXIBodyLength { get; set; }
 | |
| 
 | |
|         public string GetPayloadTypeName()
 | |
|         {
 | |
|             return V2GProtocol.GetPayloadTypeName(V2GTPPayloadType);
 | |
|         }
 | |
| 
 | |
|         public override string ToString()
 | |
|         {
 | |
|             var parts = new List<string>();
 | |
|             if (HasEthernetHeader) parts.Add("Ethernet");
 | |
|             if (HasIPv6Header) parts.Add("IPv6");
 | |
|             if (HasTCPHeader) parts.Add("TCP");
 | |
|             if (HasV2GTPHeader) parts.Add($"V2GTP ({GetPayloadTypeName()})");
 | |
|             
 | |
|             var structure = parts.Count > 0 ? string.Join(" → ", parts) : "Raw data";
 | |
|             return $"{structure} | EXI: {EXIBodyLength} bytes @ offset {EXIBodyOffset}";
 | |
|         }
 | |
|     }
 | |
| } |