- Remove unused XML output modes (ISO1, ISO2), keep only Wireshark-style output - Integrate encoding functionality from enhanced_exi_viewer_simple - Single executable now supports both encoding and decoding: * -decode: Convert EXI to Wireshark-style XML with ns1-4 prefixes * -encode: Convert XML to EXI format * default: Detailed EXI analysis mode - Support for both standard XML tags and namespaced (ns3:) tags in encoding - String conversion for units (A, V, W, s) and error codes (NO_ERROR) - Compact XML output matching Wireshark format exactly - Clean up old test files and separate tools 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
763 lines
34 KiB
C
763 lines
34 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
/* EXI codec headers */
|
|
#include "iso1EXIDatatypes.h"
|
|
#include "iso1EXIDatatypesDecoder.h"
|
|
#include "iso1EXIDatatypesEncoder.h"
|
|
#include "iso2EXIDatatypes.h"
|
|
#include "iso2EXIDatatypesDecoder.h"
|
|
#include "iso2EXIDatatypesEncoder.h"
|
|
#include "dinEXIDatatypes.h"
|
|
#include "dinEXIDatatypesDecoder.h"
|
|
#include "dinEXIDatatypesEncoder.h"
|
|
#include "ByteStream.h"
|
|
|
|
#define BUFFER_SIZE 4096
|
|
|
|
// Helper function to convert char* string to exi_string_character_t* array
|
|
static int writeStringToEXIString(char* string, exi_string_character_t* exiString) {
|
|
int pos = 0;
|
|
while(string[pos] != '\0') {
|
|
exiString[pos] = string[pos];
|
|
pos++;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
char* trim_whitespace(char* str) {
|
|
char* end;
|
|
while(isspace((unsigned char)*str)) str++;
|
|
if(*str == 0) return str;
|
|
end = str + strlen(str) - 1;
|
|
while(end > str && isspace((unsigned char)*end)) end--;
|
|
end[1] = '\0';
|
|
return str;
|
|
}
|
|
|
|
// Helper function to find XML tag content within a bounded section
|
|
char* find_tag_in_section(const char* section_start, const char* section_end, const char* tag) {
|
|
static char result[1024];
|
|
char start_tag[256], end_tag[256];
|
|
snprintf(start_tag, sizeof(start_tag), "<%s>", tag);
|
|
snprintf(end_tag, sizeof(end_tag), "</%s>", tag);
|
|
|
|
// Search for tag within the bounded section
|
|
char* tag_start = strstr(section_start, start_tag);
|
|
if (!tag_start || tag_start >= section_end) {
|
|
return NULL;
|
|
}
|
|
|
|
char* content_start = tag_start + strlen(start_tag);
|
|
if (content_start >= section_end) {
|
|
return NULL;
|
|
}
|
|
|
|
char* tag_end = strstr(content_start, end_tag);
|
|
if (!tag_end || tag_end > section_end) {
|
|
return NULL;
|
|
}
|
|
|
|
size_t len = tag_end - content_start;
|
|
if (len >= sizeof(result)) len = sizeof(result) - 1;
|
|
|
|
strncpy(result, content_start, len);
|
|
result[len] = '\0';
|
|
|
|
char* trimmed = trim_whitespace(result);
|
|
return trimmed;
|
|
}
|
|
|
|
// Helper function to find XML tag content
|
|
char* find_tag_content(const char* xml, const char* tag) {
|
|
static char result[1024];
|
|
char start_tag[256], end_tag[256];
|
|
snprintf(start_tag, sizeof(start_tag), "<%s>", tag);
|
|
snprintf(end_tag, sizeof(end_tag), "</%s>", tag);
|
|
|
|
char* start = strstr(xml, start_tag);
|
|
if (!start) return NULL;
|
|
start += strlen(start_tag);
|
|
|
|
char* end = strstr(start, end_tag);
|
|
if (!end) return NULL;
|
|
|
|
size_t len = end - start;
|
|
if (len >= sizeof(result)) len = sizeof(result) - 1;
|
|
|
|
strncpy(result, start, len);
|
|
result[len] = '\0';
|
|
return trim_whitespace(result);
|
|
}
|
|
|
|
int parse_session_id(const char* hex_str, uint8_t* bytes, size_t* len) {
|
|
size_t hex_len = strlen(hex_str);
|
|
if (hex_len % 2 != 0) return -1;
|
|
|
|
*len = hex_len / 2;
|
|
for (size_t i = 0; i < *len; i++) {
|
|
unsigned int byte;
|
|
if (sscanf(&hex_str[i*2], "%2x", &byte) != 1) return -1;
|
|
bytes[i] = (uint8_t)byte;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Parse PhysicalValue from section bounded XML
|
|
void parse_physical_value_from_section(const char* section_start, const char* section_end, struct iso1PhysicalValueType* pv) {
|
|
// Copy the found values to local variables to avoid static buffer overwriting
|
|
char mult_str[64] = {0};
|
|
char unit_str[64] = {0};
|
|
char value_str[64] = {0};
|
|
|
|
char* mult = find_tag_in_section(section_start, section_end, "Multiplier");
|
|
if (mult) strncpy(mult_str, mult, sizeof(mult_str)-1);
|
|
|
|
char* unit = find_tag_in_section(section_start, section_end, "Unit");
|
|
if (unit) strncpy(unit_str, unit, sizeof(unit_str)-1);
|
|
|
|
char* value = find_tag_in_section(section_start, section_end, "Value");
|
|
if (value) strncpy(value_str, value, sizeof(value_str)-1);
|
|
|
|
// Now parse the copied values
|
|
if (mult) pv->Multiplier = atoi(mult_str);
|
|
if (unit) pv->Unit = atoi(unit_str);
|
|
if (value) pv->Value = atoi(value_str);
|
|
}
|
|
|
|
// Parse XML to ISO1 document for encoding
|
|
int parse_xml_to_iso1(const char* xml_content, struct iso1EXIDocument* doc) {
|
|
init_iso1EXIDocument(doc);
|
|
|
|
// Find SessionID
|
|
char* session_id_str = find_tag_content(xml_content, "SessionID");
|
|
if (session_id_str) {
|
|
size_t len;
|
|
if (parse_session_id(session_id_str, doc->V2G_Message.Header.SessionID.bytes, &len) == 0) {
|
|
doc->V2G_Message.Header.SessionID.bytesLen = len;
|
|
doc->V2G_Message_isUsed = 1;
|
|
}
|
|
}
|
|
|
|
// Check for CurrentDemandReq
|
|
if (strstr(xml_content, "<CurrentDemandReq>") || strstr(xml_content, "<ns3:CurrentDemandReq>")) {
|
|
doc->V2G_Message.Body.CurrentDemandReq_isUsed = 1;
|
|
init_iso1CurrentDemandReqType(&doc->V2G_Message.Body.CurrentDemandReq);
|
|
|
|
// Parse DC_EVStatus
|
|
char* ev_ready = find_tag_content(xml_content, "EVReady");
|
|
if (ev_ready) {
|
|
doc->V2G_Message.Body.CurrentDemandReq.DC_EVStatus.EVReady = (strcmp(ev_ready, "true") == 0);
|
|
}
|
|
|
|
char* ev_error = find_tag_content(xml_content, "EVErrorCode");
|
|
if (ev_error) {
|
|
if (strcmp(ev_error, "NO_ERROR") == 0) {
|
|
doc->V2G_Message.Body.CurrentDemandReq.DC_EVStatus.EVErrorCode = 0;
|
|
} else {
|
|
doc->V2G_Message.Body.CurrentDemandReq.DC_EVStatus.EVErrorCode = atoi(ev_error);
|
|
}
|
|
}
|
|
|
|
char* ev_soc = find_tag_content(xml_content, "EVRESSSOC");
|
|
if (ev_soc) {
|
|
doc->V2G_Message.Body.CurrentDemandReq.DC_EVStatus.EVRESSSOC = atoi(ev_soc);
|
|
}
|
|
|
|
// Parse EVTargetCurrent using bounded section approach
|
|
char* current_section = strstr(xml_content, "<EVTargetCurrent>");
|
|
if (!current_section) current_section = strstr(xml_content, "<ns3:EVTargetCurrent>");
|
|
if (current_section) {
|
|
char* current_end = strstr(current_section, "</EVTargetCurrent>");
|
|
if (!current_end) current_end = strstr(current_section, "</ns3:EVTargetCurrent>");
|
|
if (current_end) {
|
|
parse_physical_value_from_section(current_section, current_end, &doc->V2G_Message.Body.CurrentDemandReq.EVTargetCurrent);
|
|
}
|
|
}
|
|
|
|
// Parse EVTargetVoltage using bounded section approach
|
|
char* voltage_section = strstr(xml_content, "<EVTargetVoltage>");
|
|
if (!voltage_section) voltage_section = strstr(xml_content, "<ns3:EVTargetVoltage>");
|
|
if (voltage_section) {
|
|
char* voltage_end = strstr(voltage_section, "</EVTargetVoltage>");
|
|
if (!voltage_end) voltage_end = strstr(voltage_section, "</ns3:EVTargetVoltage>");
|
|
if (voltage_end) {
|
|
parse_physical_value_from_section(voltage_section, voltage_end, &doc->V2G_Message.Body.CurrentDemandReq.EVTargetVoltage);
|
|
}
|
|
}
|
|
|
|
// Parse ChargingComplete
|
|
char* charging_complete = find_tag_content(xml_content, "ChargingComplete");
|
|
if (charging_complete) {
|
|
doc->V2G_Message.Body.CurrentDemandReq.ChargingComplete = (strcmp(charging_complete, "true") == 0);
|
|
}
|
|
|
|
// Parse optional fields if present
|
|
if (strstr(xml_content, "<EVMaximumVoltageLimit>") || strstr(xml_content, "<ns3:EVMaximumVoltageLimit>")) {
|
|
doc->V2G_Message.Body.CurrentDemandReq.EVMaximumVoltageLimit_isUsed = 1;
|
|
char* max_volt_section = strstr(xml_content, "<EVMaximumVoltageLimit>");
|
|
if (!max_volt_section) max_volt_section = strstr(xml_content, "<ns3:EVMaximumVoltageLimit>");
|
|
char* max_volt_end = strstr(max_volt_section, "</EVMaximumVoltageLimit>");
|
|
if (!max_volt_end) max_volt_end = strstr(max_volt_section, "</ns3:EVMaximumVoltageLimit>");
|
|
if (max_volt_section && max_volt_end) {
|
|
parse_physical_value_from_section(max_volt_section, max_volt_end, &doc->V2G_Message.Body.CurrentDemandReq.EVMaximumVoltageLimit);
|
|
}
|
|
}
|
|
|
|
if (strstr(xml_content, "<EVMaximumCurrentLimit>") || strstr(xml_content, "<ns3:EVMaximumCurrentLimit>")) {
|
|
doc->V2G_Message.Body.CurrentDemandReq.EVMaximumCurrentLimit_isUsed = 1;
|
|
char* max_curr_section = strstr(xml_content, "<EVMaximumCurrentLimit>");
|
|
if (!max_curr_section) max_curr_section = strstr(xml_content, "<ns3:EVMaximumCurrentLimit>");
|
|
char* max_curr_end = strstr(max_curr_section, "</EVMaximumCurrentLimit>");
|
|
if (!max_curr_end) max_curr_end = strstr(max_curr_section, "</ns3:EVMaximumCurrentLimit>");
|
|
if (max_curr_section && max_curr_end) {
|
|
parse_physical_value_from_section(max_curr_section, max_curr_end, &doc->V2G_Message.Body.CurrentDemandReq.EVMaximumCurrentLimit);
|
|
}
|
|
}
|
|
|
|
if (strstr(xml_content, "<EVMaximumPowerLimit>") || strstr(xml_content, "<ns3:EVMaximumPowerLimit>")) {
|
|
doc->V2G_Message.Body.CurrentDemandReq.EVMaximumPowerLimit_isUsed = 1;
|
|
char* max_power_section = strstr(xml_content, "<EVMaximumPowerLimit>");
|
|
if (!max_power_section) max_power_section = strstr(xml_content, "<ns3:EVMaximumPowerLimit>");
|
|
char* max_power_end = strstr(max_power_section, "</EVMaximumPowerLimit>");
|
|
if (!max_power_end) max_power_end = strstr(max_power_section, "</ns3:EVMaximumPowerLimit>");
|
|
if (max_power_section && max_power_end) {
|
|
parse_physical_value_from_section(max_power_section, max_power_end, &doc->V2G_Message.Body.CurrentDemandReq.EVMaximumPowerLimit);
|
|
}
|
|
}
|
|
|
|
// Parse BulkChargingComplete
|
|
char* bulk_charging_complete = find_tag_content(xml_content, "BulkChargingComplete");
|
|
if (bulk_charging_complete) {
|
|
doc->V2G_Message.Body.CurrentDemandReq.BulkChargingComplete_isUsed = 1;
|
|
doc->V2G_Message.Body.CurrentDemandReq.BulkChargingComplete = (strcmp(bulk_charging_complete, "true") == 0);
|
|
}
|
|
|
|
// Parse remaining time fields
|
|
if (strstr(xml_content, "<RemainingTimeToFullSoC>") || strstr(xml_content, "<ns3:RemainingTimeToFullSoC>")) {
|
|
doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToFullSoC_isUsed = 1;
|
|
char* time_section = strstr(xml_content, "<RemainingTimeToFullSoC>");
|
|
if (!time_section) time_section = strstr(xml_content, "<ns3:RemainingTimeToFullSoC>");
|
|
char* time_end = strstr(time_section, "</RemainingTimeToFullSoC>");
|
|
if (!time_end) time_end = strstr(time_section, "</ns3:RemainingTimeToFullSoC>");
|
|
if (time_section && time_end) {
|
|
parse_physical_value_from_section(time_section, time_end, &doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToFullSoC);
|
|
}
|
|
}
|
|
|
|
if (strstr(xml_content, "<RemainingTimeToBulkSoC>") || strstr(xml_content, "<ns3:RemainingTimeToBulkSoC>")) {
|
|
doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToBulkSoC_isUsed = 1;
|
|
char* bulk_time_section = strstr(xml_content, "<RemainingTimeToBulkSoC>");
|
|
if (!bulk_time_section) bulk_time_section = strstr(xml_content, "<ns3:RemainingTimeToBulkSoC>");
|
|
char* bulk_time_end = strstr(bulk_time_section, "</RemainingTimeToBulkSoC>");
|
|
if (!bulk_time_end) bulk_time_end = strstr(bulk_time_section, "</ns3:RemainingTimeToBulkSoC>");
|
|
if (bulk_time_section && bulk_time_end) {
|
|
parse_physical_value_from_section(bulk_time_section, bulk_time_end, &doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToBulkSoC);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
return -1; // Unsupported message type
|
|
}
|
|
|
|
// Helper function to read EXI file
|
|
int readEXIFile(char* file, uint8_t* buffer, size_t buffer_size, size_t *bytes_read) {
|
|
FILE *fp = fopen(file, "rb");
|
|
if (fp == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
*bytes_read = fread(buffer, 1, buffer_size, fp);
|
|
fclose(fp);
|
|
|
|
if (*bytes_read == 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Helper functions for Wireshark XML output
|
|
const char* get_unit_string(int unit) {
|
|
switch(unit) {
|
|
case 2: return "s"; // seconds
|
|
case 3: return "A"; // amperes
|
|
case 4: return "V"; // volts
|
|
case 5: return "W"; // watts
|
|
default: return ""; // fallback to number
|
|
}
|
|
}
|
|
|
|
const char* get_error_string(int error) {
|
|
switch(error) {
|
|
case 0: return "NO_ERROR";
|
|
default: return ""; // fallback to number
|
|
}
|
|
}
|
|
|
|
void print_xml_header_wireshark() {
|
|
printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
|
|
printf("<ns1:V2G_Message xmlns:ns1=\"urn:iso:15118:2:2013:MsgDef\"");
|
|
printf(" xmlns:ns2=\"urn:iso:15118:2:2013:MsgHeader\"");
|
|
printf(" xmlns:ns3=\"urn:iso:15118:2:2013:MsgBody\"");
|
|
printf(" xmlns:ns4=\"urn:iso:15118:2:2013:MsgDataTypes\">\n");
|
|
}
|
|
|
|
void print_xml_footer_wireshark() {
|
|
printf("</ns1:V2G_Message>");
|
|
}
|
|
|
|
void print_iso1_xml_wireshark(struct iso1EXIDocument* doc) {
|
|
print_xml_header_wireshark();
|
|
|
|
printf("<ns1:Header><ns2:SessionID>");
|
|
for(int i = 0; i < doc->V2G_Message.Header.SessionID.bytesLen; i++) {
|
|
printf("%02X", doc->V2G_Message.Header.SessionID.bytes[i]);
|
|
}
|
|
printf("</ns2:SessionID></ns1:Header>");
|
|
|
|
printf("<ns1:Body>");
|
|
|
|
if (doc->V2G_Message.Body.CurrentDemandRes_isUsed) {
|
|
printf("<ns3:CurrentDemandRes>");
|
|
printf("<ns3:ResponseCode>%d</ns3:ResponseCode>", doc->V2G_Message.Body.CurrentDemandRes.ResponseCode);
|
|
printf("<ns3:DC_EVSEStatus>");
|
|
printf("<ns4:EVSEIsolationStatus>%d</ns4:EVSEIsolationStatus>", doc->V2G_Message.Body.CurrentDemandRes.DC_EVSEStatus.EVSEIsolationStatus);
|
|
printf("<ns4:EVSEStatusCode>%d</ns4:EVSEStatusCode>", doc->V2G_Message.Body.CurrentDemandRes.DC_EVSEStatus.EVSEStatusCode);
|
|
printf("</ns3:DC_EVSEStatus>");
|
|
|
|
printf("<ns3:EVSEPresentVoltage>");
|
|
printf("<ns4:Multiplier>%d</ns4:Multiplier>", doc->V2G_Message.Body.CurrentDemandRes.EVSEPresentVoltage.Multiplier);
|
|
const char* unit_str = get_unit_string(doc->V2G_Message.Body.CurrentDemandRes.EVSEPresentVoltage.Unit);
|
|
if (strlen(unit_str) > 0) {
|
|
printf("<ns4:Unit>%s</ns4:Unit>", unit_str);
|
|
} else {
|
|
printf("<ns4:Unit>%d</ns4:Unit>", doc->V2G_Message.Body.CurrentDemandRes.EVSEPresentVoltage.Unit);
|
|
}
|
|
printf("<ns4:Value>%d</ns4:Value>", doc->V2G_Message.Body.CurrentDemandRes.EVSEPresentVoltage.Value);
|
|
printf("</ns3:EVSEPresentVoltage>");
|
|
|
|
printf("<ns3:EVSEPresentCurrent>");
|
|
printf("<ns4:Multiplier>%d</ns4:Multiplier>", doc->V2G_Message.Body.CurrentDemandRes.EVSEPresentCurrent.Multiplier);
|
|
unit_str = get_unit_string(doc->V2G_Message.Body.CurrentDemandRes.EVSEPresentCurrent.Unit);
|
|
if (strlen(unit_str) > 0) {
|
|
printf("<ns4:Unit>%s</ns4:Unit>", unit_str);
|
|
} else {
|
|
printf("<ns4:Unit>%d</ns4:Unit>", doc->V2G_Message.Body.CurrentDemandRes.EVSEPresentCurrent.Unit);
|
|
}
|
|
printf("<ns4:Value>%d</ns4:Value>", doc->V2G_Message.Body.CurrentDemandRes.EVSEPresentCurrent.Value);
|
|
printf("</ns3:EVSEPresentCurrent>");
|
|
|
|
printf("<ns3:EVSECurrentLimitAchieved>%s</ns3:EVSECurrentLimitAchieved>", doc->V2G_Message.Body.CurrentDemandRes.EVSECurrentLimitAchieved ? "true" : "false");
|
|
printf("<ns3:EVSEVoltageLimitAchieved>%s</ns3:EVSEVoltageLimitAchieved>", doc->V2G_Message.Body.CurrentDemandRes.EVSEVoltageLimitAchieved ? "true" : "false");
|
|
printf("<ns3:EVSEPowerLimitAchieved>%s</ns3:EVSEPowerLimitAchieved>", doc->V2G_Message.Body.CurrentDemandRes.EVSEPowerLimitAchieved ? "true" : "false");
|
|
printf("<ns3:EVSEID>%.*s</ns3:EVSEID>",
|
|
doc->V2G_Message.Body.CurrentDemandRes.EVSEID.charactersLen,
|
|
doc->V2G_Message.Body.CurrentDemandRes.EVSEID.characters);
|
|
printf("<ns3:SAScheduleTupleID>%d</ns3:SAScheduleTupleID>", doc->V2G_Message.Body.CurrentDemandRes.SAScheduleTupleID);
|
|
printf("</ns3:CurrentDemandRes>");
|
|
}
|
|
else if (doc->V2G_Message.Body.CurrentDemandReq_isUsed) {
|
|
printf("<ns3:CurrentDemandReq>");
|
|
printf("<ns3:DC_EVStatus>");
|
|
printf("<ns4:EVReady>%s</ns4:EVReady>", doc->V2G_Message.Body.CurrentDemandReq.DC_EVStatus.EVReady ? "true" : "false");
|
|
|
|
const char* error_str = get_error_string(doc->V2G_Message.Body.CurrentDemandReq.DC_EVStatus.EVErrorCode);
|
|
if (strlen(error_str) > 0) {
|
|
printf("<ns4:EVErrorCode>%s</ns4:EVErrorCode>", error_str);
|
|
} else {
|
|
printf("<ns4:EVErrorCode>%d</ns4:EVErrorCode>", doc->V2G_Message.Body.CurrentDemandReq.DC_EVStatus.EVErrorCode);
|
|
}
|
|
|
|
printf("<ns4:EVRESSSOC>%d</ns4:EVRESSSOC>", doc->V2G_Message.Body.CurrentDemandReq.DC_EVStatus.EVRESSSOC);
|
|
printf("</ns3:DC_EVStatus>");
|
|
|
|
printf("<ns3:EVTargetCurrent>");
|
|
printf("<ns4:Multiplier>%d</ns4:Multiplier>", doc->V2G_Message.Body.CurrentDemandReq.EVTargetCurrent.Multiplier);
|
|
const char* unit_str = get_unit_string(doc->V2G_Message.Body.CurrentDemandReq.EVTargetCurrent.Unit);
|
|
if (strlen(unit_str) > 0) {
|
|
printf("<ns4:Unit>%s</ns4:Unit>", unit_str);
|
|
} else {
|
|
printf("<ns4:Unit>%d</ns4:Unit>", doc->V2G_Message.Body.CurrentDemandReq.EVTargetCurrent.Unit);
|
|
}
|
|
printf("<ns4:Value>%d</ns4:Value>", doc->V2G_Message.Body.CurrentDemandReq.EVTargetCurrent.Value);
|
|
printf("</ns3:EVTargetCurrent>");
|
|
|
|
printf("<ns3:EVTargetVoltage>");
|
|
printf("<ns4:Multiplier>%d</ns4:Multiplier>", doc->V2G_Message.Body.CurrentDemandReq.EVTargetVoltage.Multiplier);
|
|
unit_str = get_unit_string(doc->V2G_Message.Body.CurrentDemandReq.EVTargetVoltage.Unit);
|
|
if (strlen(unit_str) > 0) {
|
|
printf("<ns4:Unit>%s</ns4:Unit>", unit_str);
|
|
} else {
|
|
printf("<ns4:Unit>%d</ns4:Unit>", doc->V2G_Message.Body.CurrentDemandReq.EVTargetVoltage.Unit);
|
|
}
|
|
printf("<ns4:Value>%d</ns4:Value>", doc->V2G_Message.Body.CurrentDemandReq.EVTargetVoltage.Value);
|
|
printf("</ns3:EVTargetVoltage>");
|
|
|
|
if (doc->V2G_Message.Body.CurrentDemandReq.EVMaximumVoltageLimit_isUsed) {
|
|
printf("<ns3:EVMaximumVoltageLimit>");
|
|
printf("<ns4:Multiplier>%d</ns4:Multiplier>", doc->V2G_Message.Body.CurrentDemandReq.EVMaximumVoltageLimit.Multiplier);
|
|
unit_str = get_unit_string(doc->V2G_Message.Body.CurrentDemandReq.EVMaximumVoltageLimit.Unit);
|
|
if (strlen(unit_str) > 0) {
|
|
printf("<ns4:Unit>%s</ns4:Unit>", unit_str);
|
|
} else {
|
|
printf("<ns4:Unit>%d</ns4:Unit>", doc->V2G_Message.Body.CurrentDemandReq.EVMaximumVoltageLimit.Unit);
|
|
}
|
|
printf("<ns4:Value>%d</ns4:Value>", doc->V2G_Message.Body.CurrentDemandReq.EVMaximumVoltageLimit.Value);
|
|
printf("</ns3:EVMaximumVoltageLimit>");
|
|
}
|
|
|
|
if (doc->V2G_Message.Body.CurrentDemandReq.EVMaximumCurrentLimit_isUsed) {
|
|
printf("<ns3:EVMaximumCurrentLimit>");
|
|
printf("<ns4:Multiplier>%d</ns4:Multiplier>", doc->V2G_Message.Body.CurrentDemandReq.EVMaximumCurrentLimit.Multiplier);
|
|
unit_str = get_unit_string(doc->V2G_Message.Body.CurrentDemandReq.EVMaximumCurrentLimit.Unit);
|
|
if (strlen(unit_str) > 0) {
|
|
printf("<ns4:Unit>%s</ns4:Unit>", unit_str);
|
|
} else {
|
|
printf("<ns4:Unit>%d</ns4:Unit>", doc->V2G_Message.Body.CurrentDemandReq.EVMaximumCurrentLimit.Unit);
|
|
}
|
|
printf("<ns4:Value>%d</ns4:Value>", doc->V2G_Message.Body.CurrentDemandReq.EVMaximumCurrentLimit.Value);
|
|
printf("</ns3:EVMaximumCurrentLimit>");
|
|
}
|
|
|
|
if (doc->V2G_Message.Body.CurrentDemandReq.EVMaximumPowerLimit_isUsed) {
|
|
printf("<ns3:EVMaximumPowerLimit>");
|
|
printf("<ns4:Multiplier>%d</ns4:Multiplier>", doc->V2G_Message.Body.CurrentDemandReq.EVMaximumPowerLimit.Multiplier);
|
|
unit_str = get_unit_string(doc->V2G_Message.Body.CurrentDemandReq.EVMaximumPowerLimit.Unit);
|
|
if (strlen(unit_str) > 0) {
|
|
printf("<ns4:Unit>%s</ns4:Unit>", unit_str);
|
|
} else {
|
|
printf("<ns4:Unit>%d</ns4:Unit>", doc->V2G_Message.Body.CurrentDemandReq.EVMaximumPowerLimit.Unit);
|
|
}
|
|
printf("<ns4:Value>%d</ns4:Value>", doc->V2G_Message.Body.CurrentDemandReq.EVMaximumPowerLimit.Value);
|
|
printf("</ns3:EVMaximumPowerLimit>");
|
|
}
|
|
|
|
if (doc->V2G_Message.Body.CurrentDemandReq.BulkChargingComplete_isUsed) {
|
|
printf("<ns3:BulkChargingComplete>%s</ns3:BulkChargingComplete>", doc->V2G_Message.Body.CurrentDemandReq.BulkChargingComplete ? "true" : "false");
|
|
}
|
|
|
|
printf("<ns3:ChargingComplete>%s</ns3:ChargingComplete>", doc->V2G_Message.Body.CurrentDemandReq.ChargingComplete ? "true" : "false");
|
|
|
|
if (doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToFullSoC_isUsed) {
|
|
printf("<ns3:RemainingTimeToFullSoC>");
|
|
printf("<ns4:Multiplier>%d</ns4:Multiplier>", doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToFullSoC.Multiplier);
|
|
unit_str = get_unit_string(doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToFullSoC.Unit);
|
|
if (strlen(unit_str) > 0) {
|
|
printf("<ns4:Unit>%s</ns4:Unit>", unit_str);
|
|
} else {
|
|
printf("<ns4:Unit>%d</ns4:Unit>", doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToFullSoC.Unit);
|
|
}
|
|
printf("<ns4:Value>%d</ns4:Value>", doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToFullSoC.Value);
|
|
printf("</ns3:RemainingTimeToFullSoC>");
|
|
}
|
|
|
|
if (doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToBulkSoC_isUsed) {
|
|
printf("<ns3:RemainingTimeToBulkSoC>");
|
|
printf("<ns4:Multiplier>%d</ns4:Multiplier>", doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToBulkSoC.Multiplier);
|
|
unit_str = get_unit_string(doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToBulkSoC.Unit);
|
|
if (strlen(unit_str) > 0) {
|
|
printf("<ns4:Unit>%s</ns4:Unit>", unit_str);
|
|
} else {
|
|
printf("<ns4:Unit>%d</ns4:Unit>", doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToBulkSoC.Unit);
|
|
}
|
|
printf("<ns4:Value>%d</ns4:Value>", doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToBulkSoC.Value);
|
|
printf("</ns3:RemainingTimeToBulkSoC>");
|
|
}
|
|
|
|
printf("</ns3:CurrentDemandReq>");
|
|
}
|
|
|
|
printf("</ns1:Body>");
|
|
print_xml_footer_wireshark();
|
|
}
|
|
|
|
void print_iso1_message(struct iso1EXIDocument* doc) {
|
|
printf("=== ISO 15118-2 V2G Message Analysis ===\n");
|
|
printf("Message Type: ISO1 (2013)\n");
|
|
printf("V2G_Message_isUsed: %s\n", doc->V2G_Message_isUsed ? "true" : "false");
|
|
|
|
if (doc->V2G_Message_isUsed) {
|
|
printf("\n--- Header ---\n");
|
|
printf("SessionID: ");
|
|
for(int i = 0; i < doc->V2G_Message.Header.SessionID.bytesLen; i++) {
|
|
printf("%02X", doc->V2G_Message.Header.SessionID.bytes[i]);
|
|
}
|
|
printf(" (");
|
|
for(int i = 0; i < doc->V2G_Message.Header.SessionID.bytesLen; i++) {
|
|
if (doc->V2G_Message.Header.SessionID.bytes[i] >= 32 && doc->V2G_Message.Header.SessionID.bytes[i] <= 126) {
|
|
printf("%c", doc->V2G_Message.Header.SessionID.bytes[i]);
|
|
} else {
|
|
printf(".");
|
|
}
|
|
}
|
|
printf(")\n");
|
|
|
|
printf("\n--- Body ---\n");
|
|
|
|
if (doc->V2G_Message.Body.CurrentDemandRes_isUsed) {
|
|
printf("Message Type: CurrentDemandRes\n");
|
|
printf("ResponseCode: %d\n", doc->V2G_Message.Body.CurrentDemandRes.ResponseCode);
|
|
|
|
printf("\nDC_EVSEStatus:\n");
|
|
printf(" EVSEIsolationStatus: %d\n", doc->V2G_Message.Body.CurrentDemandRes.DC_EVSEStatus.EVSEIsolationStatus);
|
|
printf(" EVSEStatusCode: %d\n", doc->V2G_Message.Body.CurrentDemandRes.DC_EVSEStatus.EVSEStatusCode);
|
|
|
|
printf("\nEVSEPresentVoltage:\n");
|
|
printf(" Multiplier: %d\n", doc->V2G_Message.Body.CurrentDemandRes.EVSEPresentVoltage.Multiplier);
|
|
printf(" Unit: %d\n", doc->V2G_Message.Body.CurrentDemandRes.EVSEPresentVoltage.Unit);
|
|
printf(" Value: %d\n", doc->V2G_Message.Body.CurrentDemandRes.EVSEPresentVoltage.Value);
|
|
|
|
printf("\nEVSEPresentCurrent:\n");
|
|
printf(" Multiplier: %d\n", doc->V2G_Message.Body.CurrentDemandRes.EVSEPresentCurrent.Multiplier);
|
|
printf(" Unit: %d\n", doc->V2G_Message.Body.CurrentDemandRes.EVSEPresentCurrent.Unit);
|
|
printf(" Value: %d\n", doc->V2G_Message.Body.CurrentDemandRes.EVSEPresentCurrent.Value);
|
|
|
|
printf("\nLimit Status:\n");
|
|
printf(" CurrentLimitAchieved: %s\n", doc->V2G_Message.Body.CurrentDemandRes.EVSECurrentLimitAchieved ? "true" : "false");
|
|
printf(" VoltageLimitAchieved: %s\n", doc->V2G_Message.Body.CurrentDemandRes.EVSEVoltageLimitAchieved ? "true" : "false");
|
|
printf(" PowerLimitAchieved: %s\n", doc->V2G_Message.Body.CurrentDemandRes.EVSEPowerLimitAchieved ? "true" : "false");
|
|
|
|
printf("\nEVSEID: %.*s\n",
|
|
doc->V2G_Message.Body.CurrentDemandRes.EVSEID.charactersLen,
|
|
doc->V2G_Message.Body.CurrentDemandRes.EVSEID.characters);
|
|
printf("SAScheduleTupleID: %d\n", doc->V2G_Message.Body.CurrentDemandRes.SAScheduleTupleID);
|
|
}
|
|
else if (doc->V2G_Message.Body.CurrentDemandReq_isUsed) {
|
|
printf("Message Type: CurrentDemandReq\n");
|
|
|
|
printf("\nDC_EVStatus:\n");
|
|
printf(" EVReady: %s\n", doc->V2G_Message.Body.CurrentDemandReq.DC_EVStatus.EVReady ? "true" : "false");
|
|
printf(" EVErrorCode: %d\n", doc->V2G_Message.Body.CurrentDemandReq.DC_EVStatus.EVErrorCode);
|
|
printf(" EVRESSSOC: %d%%\n", doc->V2G_Message.Body.CurrentDemandReq.DC_EVStatus.EVRESSSOC);
|
|
|
|
printf("\nEVTargetCurrent:\n");
|
|
printf(" Multiplier: %d\n", doc->V2G_Message.Body.CurrentDemandReq.EVTargetCurrent.Multiplier);
|
|
printf(" Unit: %d\n", doc->V2G_Message.Body.CurrentDemandReq.EVTargetCurrent.Unit);
|
|
printf(" Value: %d\n", doc->V2G_Message.Body.CurrentDemandReq.EVTargetCurrent.Value);
|
|
|
|
printf("\nEVTargetVoltage:\n");
|
|
printf(" Multiplier: %d\n", doc->V2G_Message.Body.CurrentDemandReq.EVTargetVoltage.Multiplier);
|
|
printf(" Unit: %d\n", doc->V2G_Message.Body.CurrentDemandReq.EVTargetVoltage.Unit);
|
|
printf(" Value: %d\n", doc->V2G_Message.Body.CurrentDemandReq.EVTargetVoltage.Value);
|
|
|
|
if (doc->V2G_Message.Body.CurrentDemandReq.EVMaximumVoltageLimit_isUsed) {
|
|
printf("\nEVMaximumVoltageLimit:\n");
|
|
printf(" Multiplier: %d\n", doc->V2G_Message.Body.CurrentDemandReq.EVMaximumVoltageLimit.Multiplier);
|
|
printf(" Unit: %d\n", doc->V2G_Message.Body.CurrentDemandReq.EVMaximumVoltageLimit.Unit);
|
|
printf(" Value: %d\n", doc->V2G_Message.Body.CurrentDemandReq.EVMaximumVoltageLimit.Value);
|
|
}
|
|
|
|
if (doc->V2G_Message.Body.CurrentDemandReq.EVMaximumCurrentLimit_isUsed) {
|
|
printf("\nEVMaximumCurrentLimit:\n");
|
|
printf(" Multiplier: %d\n", doc->V2G_Message.Body.CurrentDemandReq.EVMaximumCurrentLimit.Multiplier);
|
|
printf(" Unit: %d\n", doc->V2G_Message.Body.CurrentDemandReq.EVMaximumCurrentLimit.Unit);
|
|
printf(" Value: %d\n", doc->V2G_Message.Body.CurrentDemandReq.EVMaximumCurrentLimit.Value);
|
|
}
|
|
|
|
if (doc->V2G_Message.Body.CurrentDemandReq.EVMaximumPowerLimit_isUsed) {
|
|
printf("\nEVMaximumPowerLimit:\n");
|
|
printf(" Multiplier: %d\n", doc->V2G_Message.Body.CurrentDemandReq.EVMaximumPowerLimit.Multiplier);
|
|
printf(" Unit: %d\n", doc->V2G_Message.Body.CurrentDemandReq.EVMaximumPowerLimit.Unit);
|
|
printf(" Value: %d\n", doc->V2G_Message.Body.CurrentDemandReq.EVMaximumPowerLimit.Value);
|
|
}
|
|
|
|
if (doc->V2G_Message.Body.CurrentDemandReq.BulkChargingComplete_isUsed) {
|
|
printf("\nBulkChargingComplete: %s\n", doc->V2G_Message.Body.CurrentDemandReq.BulkChargingComplete ? "true" : "false");
|
|
}
|
|
|
|
printf("ChargingComplete: %s\n", doc->V2G_Message.Body.CurrentDemandReq.ChargingComplete ? "true" : "false");
|
|
|
|
if (doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToFullSoC_isUsed) {
|
|
printf("\nRemainingTimeToFullSoC:\n");
|
|
printf(" Multiplier: %d\n", doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToFullSoC.Multiplier);
|
|
printf(" Unit: %d\n", doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToFullSoC.Unit);
|
|
printf(" Value: %d\n", doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToFullSoC.Value);
|
|
}
|
|
|
|
if (doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToBulkSoC_isUsed) {
|
|
printf("\nRemainingTimeToBulkSoC:\n");
|
|
printf(" Multiplier: %d\n", doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToBulkSoC.Multiplier);
|
|
printf(" Unit: %d\n", doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToBulkSoC.Unit);
|
|
printf(" Value: %d\n", doc->V2G_Message.Body.CurrentDemandReq.RemainingTimeToBulkSoC.Value);
|
|
}
|
|
}
|
|
else {
|
|
printf("Message Type: Other message type (not fully supported)\n");
|
|
}
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
int xml_mode = 0;
|
|
int encode_mode = 0;
|
|
char *filename = NULL;
|
|
|
|
if (argc == 2) {
|
|
filename = argv[1];
|
|
} else if (argc == 3 && strcmp(argv[1], "-decode") == 0) {
|
|
xml_mode = 1;
|
|
filename = argv[2];
|
|
} else if (argc == 3 && strcmp(argv[1], "-encode") == 0) {
|
|
encode_mode = 1;
|
|
filename = argv[2];
|
|
} else {
|
|
printf("Usage: %s [-decode|-encode] input_file\\n", argv[0]);
|
|
printf("Enhanced EXI viewer with XML conversion capabilities\\n");
|
|
printf(" -decode Convert EXI to Wireshark-style XML format\\n");
|
|
printf(" -encode Convert XML to EXI format\\n");
|
|
printf(" (default) Analyze EXI with detailed output\\n");
|
|
return -1;
|
|
}
|
|
|
|
uint8_t buffer[BUFFER_SIZE];
|
|
bitstream_t stream;
|
|
size_t pos = 0;
|
|
int errn = 0;
|
|
|
|
struct iso1EXIDocument iso1Doc;
|
|
struct iso2EXIDocument iso2Doc;
|
|
struct dinEXIDocument dinDoc;
|
|
|
|
// Initialize documents
|
|
init_iso1EXIDocument(&iso1Doc);
|
|
init_iso2EXIDocument(&iso2Doc);
|
|
init_dinEXIDocument(&dinDoc);
|
|
|
|
// Handle encode mode (XML to EXI)
|
|
if (encode_mode) {
|
|
// Read XML file
|
|
FILE* xml_file = fopen(filename, "r");
|
|
if (!xml_file) {
|
|
printf("Error opening XML file: %s\\n", filename);
|
|
return -1;
|
|
}
|
|
|
|
// Read entire XML content
|
|
fseek(xml_file, 0, SEEK_END);
|
|
long xml_size = ftell(xml_file);
|
|
fseek(xml_file, 0, SEEK_SET);
|
|
|
|
char* xml_content = malloc(xml_size + 1);
|
|
if (!xml_content) {
|
|
printf("Error allocating memory for XML content\\n");
|
|
fclose(xml_file);
|
|
return -1;
|
|
}
|
|
|
|
fread(xml_content, 1, xml_size, xml_file);
|
|
xml_content[xml_size] = '\0';
|
|
fclose(xml_file);
|
|
|
|
// Parse XML to ISO1 document structure
|
|
if (parse_xml_to_iso1(xml_content, &iso1Doc) != 0) {
|
|
printf("Error parsing XML file\\n");
|
|
free(xml_content);
|
|
return -1;
|
|
}
|
|
|
|
free(xml_content);
|
|
|
|
// Encode to EXI
|
|
pos = 0;
|
|
stream.size = BUFFER_SIZE;
|
|
stream.data = buffer;
|
|
stream.pos = &pos;
|
|
stream.buffer = 0;
|
|
stream.capacity = 0;
|
|
|
|
errn = encode_iso1ExiDocument(&stream, &iso1Doc);
|
|
if (errn != 0) {
|
|
printf("Error encoding to EXI (error: %d)\\n", errn);
|
|
return -1;
|
|
}
|
|
|
|
// Write EXI data to stdout (binary)
|
|
fwrite(buffer, 1, pos, stdout);
|
|
return 0;
|
|
}
|
|
|
|
// Read EXI file for decode/analysis mode
|
|
errn = readEXIFile(filename, buffer, BUFFER_SIZE, &pos);
|
|
if (errn != 0) {
|
|
printf("Error reading file: %s\\n", filename);
|
|
return -1;
|
|
}
|
|
|
|
if (!xml_mode) {
|
|
printf("File: %s (%zu bytes)\\n", filename, pos);
|
|
printf("Raw hex data: ");
|
|
for(size_t i = 0; i < (pos > 32 ? 32 : pos); i++) {
|
|
printf("%02X ", buffer[i]);
|
|
}
|
|
if (pos > 32) printf("...");
|
|
printf("\\n\\n");
|
|
}
|
|
|
|
// Setup stream
|
|
pos = 0; // reset position for decoding
|
|
stream.size = BUFFER_SIZE;
|
|
stream.data = buffer;
|
|
stream.pos = &pos;
|
|
stream.buffer = 0;
|
|
stream.capacity = 0;
|
|
|
|
// Try ISO1 first
|
|
pos = 0;
|
|
if (!xml_mode) printf("Trying ISO1 decoder...\\n");
|
|
errn = decode_iso1ExiDocument(&stream, &iso1Doc);
|
|
if (errn == 0) {
|
|
if (!xml_mode) printf("✓ Successfully decoded as ISO1\\n\\n");
|
|
if (xml_mode) {
|
|
print_iso1_xml_wireshark(&iso1Doc);
|
|
} else {
|
|
print_iso1_message(&iso1Doc);
|
|
}
|
|
return 0;
|
|
} else {
|
|
if (!xml_mode) printf("✗ ISO1 decode failed (error: %d)\\n", errn);
|
|
}
|
|
|
|
// Try ISO2
|
|
pos = 0;
|
|
if (!xml_mode) printf("Trying ISO2 decoder...\\n");
|
|
errn = decode_iso2ExiDocument(&stream, &iso2Doc);
|
|
if (errn == 0) {
|
|
if (!xml_mode) printf("✓ Successfully decoded as ISO2\\n\\n");
|
|
if (xml_mode) {
|
|
printf("ISO2 XML output not implemented for Wireshark format\\n");
|
|
} else {
|
|
printf("ISO2 analysis not fully implemented\\n");
|
|
}
|
|
return 0;
|
|
} else {
|
|
if (!xml_mode) printf("✗ ISO2 decode failed (error: %d)\\n", errn);
|
|
}
|
|
|
|
// Try DIN
|
|
pos = 0;
|
|
if (!xml_mode) printf("Trying DIN decoder...\\n");
|
|
errn = decode_dinExiDocument(&stream, &dinDoc);
|
|
if (errn == 0) {
|
|
if (!xml_mode) {
|
|
printf("✓ Successfully decoded as DIN\\n\\n");
|
|
printf("=== DIN V2G Message ===\\n");
|
|
// Add DIN message printing as needed
|
|
}
|
|
return 0;
|
|
} else {
|
|
if (!xml_mode) printf("✗ DIN decode failed (error: %d)\\n", errn);
|
|
}
|
|
|
|
if (!xml_mode) {
|
|
printf("\\n❌ Could not decode EXI file with any supported codec\\n");
|
|
printf("Supported formats: ISO1, ISO2, DIN\\n");
|
|
}
|
|
|
|
return -1;
|
|
} |