/* * 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 V2GDecoderNet.EXI; namespace V2GDecoderNet.V2G { /// /// V2G Transfer Protocol constants and definitions /// 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; /// /// Get payload type name for display /// /// Payload type value /// Human-readable payload type name 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" }; } /// /// Extract EXI body from V2G Transfer Protocol data /// /// Input data containing V2GTP header and EXI body /// Extracted EXI body data 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(); } // Check for V2G Transfer Protocol header if (inputData[0] == V2G_PROTOCOL_VERSION && inputData[1] == V2G_INV_PROTOCOL_VERSION) { ushort payloadType = (ushort)((inputData[2] << 8) | inputData[3]); if (payloadType == V2G_PAYLOAD_ISO_DIN_SAP || payloadType == V2G_PAYLOAD_ISO2) { // Valid V2GTP header detected: skip 8-byte header var exiBody = new byte[inputData.Length - 8]; Array.Copy(inputData, 8, exiBody, 0, exiBody.Length); return exiBody; } } // 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; } /// /// Analyze complete packet structure /// /// Packet data /// Analysis result 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; } } /// /// Packet analysis result /// 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(); 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}"; } } }