# VC2022 EXI Encoding Method - Complete Analysis 이 문서는 VC2022 C 구현의 정확한 EXI 인코딩 방법을 완전히 분석하여 C# 포팅에 사용합니다. ## 🎉 **최종 성과 (2024-09-11)** ### **100% 바이너리 호환성 달성** - **목표**: VC2022와 dotnet 인코딩 결과 100% 동일 - **결과**: ✅ **완전 달성** - 42바이트 바이너리 완전 일치 - **검증**: `cmp` 명령어로 바이트 단위 완전 동일 확인 ### **핵심 해결 요소** 1. **writeBits 함수**: VC2022 BitOutputStream.c의 정확한 복제 구현 2. **버퍼 관리**: Position, Capacity, Buffer 상태 완전 매칭 3. **플러시 알고리즘**: `encodeFinish()` 동작의 정확한 구현 4. **비트 정렬**: 바이트 경계 처리 로직 완전 일치 ### **최종 출력 결과** ``` 파일 크기: 42 바이트 시작 16바이트: 80 98 02 10 50 90 8C 0C 0C 0E 0C 50 D1 00 32 01 종료 코드: 0 (성공) 바이너리 검증: 완전 동일 ✅ ``` --- ## 1. VC2022 구조체 정의 ### 1.1 CurrentDemandReqType Structure (iso1EXIDatatypes.h) ```c struct iso1CurrentDemandReqType { struct iso1DC_EVStatusType DC_EVStatus; struct iso1PhysicalValueType EVTargetCurrent; struct iso1PhysicalValueType EVMaximumVoltageLimit; int EVMaximumVoltageLimit_isUsed; struct iso1PhysicalValueType EVMaximumCurrentLimit; int EVMaximumCurrentLimit_isUsed; struct iso1PhysicalValueType EVMaximumPowerLimit; int EVMaximumPowerLimit_isUsed; int BulkChargingComplete; int BulkChargingComplete_isUsed; int ChargingComplete; // ❌ NO _isUsed flag! struct iso1PhysicalValueType RemainingTimeToFullSoC; int RemainingTimeToFullSoC_isUsed; struct iso1PhysicalValueType RemainingTimeToBulkSoC; int RemainingTimeToBulkSoC_isUsed; struct iso1PhysicalValueType EVTargetVoltage; // ❌ NO _isUsed flag! }; ``` **중요한 차이점**: - `ChargingComplete`: _isUsed 플래그 없음 (mandatory) - `EVTargetVoltage`: _isUsed 플래그 없음 (mandatory) ### 1.2 CurrentDemandResType Structure (iso1EXIDatatypes.h) ```c struct iso1CurrentDemandResType { int ResponseCode; struct iso1DC_EVSEStatusType DC_EVSEStatus; struct iso1PhysicalValueType EVSEPresentVoltage; struct iso1PhysicalValueType EVSEPresentCurrent; int EVSECurrentLimitAchieved; int EVSEVoltageLimitAchieved; int EVSEPowerLimitAchieved; struct iso1PhysicalValueType EVSEMaximumVoltageLimit; int EVSEMaximumVoltageLimit_isUsed; struct iso1PhysicalValueType EVSEMaximumCurrentLimit; int EVSEMaximumCurrentLimit_isUsed; struct iso1PhysicalValueType EVSEMaximumPowerLimit; int EVSEMaximumPowerLimit_isUsed; iso1EXIString EVSEID; // String with array int SAScheduleTupleID; struct iso1MeterInfoType MeterInfo; int MeterInfo_isUsed; int ReceiptRequired; int ReceiptRequired_isUsed; }; ``` ## 2. VC2022 CurrentDemandReq Grammar States 완전 분석 ### 2.1 Grammar State 275 - 5개 선택지 (3-bit choice) ```c // EVMaximumVoltageLimit_isUsed: choice 0 // EVMaximumCurrentLimit_isUsed: choice 1 // EVMaximumPowerLimit_isUsed: choice 2 // BulkChargingComplete_isUsed: choice 3 // ChargingComplete (mandatory): choice 4 ``` ### 2.2 Grammar State 276 - 4개 선택지 (3-bit choice) ```c // EVMaximumCurrentLimit_isUsed: choice 0 → Grammar 277 // EVMaximumPowerLimit_isUsed: choice 1 → Grammar 278 // BulkChargingComplete_isUsed: choice 2 → Grammar 279 // ChargingComplete (mandatory): choice 3 → Grammar 280 ``` ### 2.3 Grammar State 277 - 3개 선택지 (2-bit choice) ```c // EVMaximumPowerLimit_isUsed: choice 0 → Grammar 278 // BulkChargingComplete_isUsed: choice 1 → Grammar 279 // ChargingComplete (mandatory): choice 2 → Grammar 280 ``` ### 2.4 Grammar State 278 - 2개 선택지 (2-bit choice) ```c // BulkChargingComplete_isUsed: choice 0 → Grammar 279 // ChargingComplete (mandatory): choice 1 → Grammar 280 ``` ### 2.5 Grammar State 279 - ChargingComplete만 (choice 0) ```c // ChargingComplete (mandatory): choice 0 → Grammar 280 ``` **중요 사실**: ChargingComplete는 모든 grammar state에서 `if ( 1 == 1 )`으로 체크 → 항상 mandatory! ## 3. Overall Encoding Flow ### 1.1 encode_iso1ExiDocument() - Entry Point ```c int encode_iso1ExiDocument(bitstream_t* stream, struct iso1EXIDocument* exiDoc) { errn = writeEXIHeader(stream); if(errn == 0) { // V2G_Message uses choice 76 in 7-bit encoding if ( exiDoc->V2G_Message_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 7, 76); if(errn == 0) { errn = encode_iso1AnonType_V2G_Message(stream, &exiDoc->V2G_Message ); } } } if(errn == 0) { errn = encodeFinish(stream); // Flush remaining bits } } ``` ### 1.2 writeEXIHeader() - Header Only ```c int writeEXIHeader(bitstream_t* stream) { stream->buffer = 0; stream->capacity = 8; return writeBits(stream, 8, 128); // Write 0x80 (10000000) } ``` **중요**: writeEXIHeader는 단순히 0x80만 작성합니다. 0x98은 다음 단계에서 생성됩니다. ## 2. V2G_Message Structure Encoding ### 2.1 encode_iso1AnonType_V2G_Message() - Grammar States ```c static int encode_iso1AnonType_V2G_Message(bitstream_t* stream, struct iso1AnonType_V2G_Message* iso1AnonType_V2G_Message) { int grammarID = 256; while(!done) { switch(grammarID) { case 256: // Grammar 256: Header is mandatory errn = encodeNBitUnsignedInteger(stream, 1, 0); // START_ELEMENT(Header) errn = encode_iso1MessageHeaderType(stream, &iso1AnonType_V2G_Message->Header ); grammarID = 257; break; case 257: // Grammar 257: Body is mandatory errn = encodeNBitUnsignedInteger(stream, 1, 0); // START_ELEMENT(Body) errn = encode_iso1BodyType(stream, &iso1AnonType_V2G_Message->Body ); grammarID = 3; break; case 3: // Grammar 3: END_ELEMENT errn = encodeNBitUnsignedInteger(stream, 1, 0); // END_ELEMENT done = 1; break; } } } ``` ## 3. MessageHeader Encoding ### 3.1 encode_iso1MessageHeaderType() ```c static int encode_iso1MessageHeaderType(bitstream_t* stream, struct iso1MessageHeaderType* iso1MessageHeaderType) { int grammarID = 0; switch(grammarID) { case 0: // SessionID is mandatory errn = encodeNBitUnsignedInteger(stream, 1, 0); // START_ELEMENT(SessionID) // BINARY_HEX encoding errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[BINARY_HEX] errn = encodeUnsignedInteger16(stream, (uint16_t)(iso1MessageHeaderType->SessionID.bytesLen)); errn = encodeBytes(stream, iso1MessageHeaderType->SessionID.bytes, iso1MessageHeaderType->SessionID.bytesLen); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE grammarID = 1; break; case 1: // Skip optional Notification, Signature errn = encodeNBitUnsignedInteger(stream, 2, 2); // END_ELEMENT choice done = 1; break; } } ``` **중요**: SessionID는 BINARY_HEX로 인코딩되며, 길이(16비트) + 바이트 데이터 형식입니다. ## 4. Body Encoding ### 4.1 encode_iso1BodyType() - 6-bit Choice ```c static int encode_iso1BodyType(bitstream_t* stream, struct iso1BodyType* iso1BodyType) { int grammarID = 220; switch(grammarID) { case 220: // CurrentDemandReq = choice 13 in 6-bit encoding if ( iso1BodyType->CurrentDemandReq_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 6, 13); errn = encode_iso1CurrentDemandReqType(stream, &iso1BodyType->CurrentDemandReq ); grammarID = 3; } // CurrentDemandRes = choice 14 in 6-bit encoding else if ( iso1BodyType->CurrentDemandRes_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 6, 14); errn = encode_iso1CurrentDemandResType(stream, &iso1BodyType->CurrentDemandRes ); grammarID = 3; } break; case 3: // END_ELEMENT errn = encodeNBitUnsignedInteger(stream, 1, 0); done = 1; break; } } ``` ## 5. CurrentDemandReq Detailed Grammar States ### 5.1 encode_iso1CurrentDemandReqType() - Complete Flow ```c static int encode_iso1CurrentDemandReqType(bitstream_t* stream, struct iso1CurrentDemandReqType* iso1CurrentDemandReqType) { int grammarID = 273; while(!done) { switch(grammarID) { case 273: // DC_EVStatus is mandatory errn = encodeNBitUnsignedInteger(stream, 1, 0); errn = encode_iso1DC_EVStatusType(stream, &iso1CurrentDemandReqType->DC_EVStatus ); grammarID = 274; break; case 274: // EVTargetCurrent is mandatory errn = encodeNBitUnsignedInteger(stream, 1, 0); errn = encode_iso1PhysicalValueType(stream, &iso1CurrentDemandReqType->EVTargetCurrent ); grammarID = 275; break; case 275: // 3-bit choice for optional elements if ( iso1CurrentDemandReqType->EVMaximumVoltageLimit_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 3, 0); errn = encode_iso1PhysicalValueType(stream, &iso1CurrentDemandReqType->EVMaximumVoltageLimit ); grammarID = 276; } else if ( iso1CurrentDemandReqType->EVMaximumCurrentLimit_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 3, 1); errn = encode_iso1PhysicalValueType(stream, &iso1CurrentDemandReqType->EVMaximumCurrentLimit ); grammarID = 277; } else if ( iso1CurrentDemandReqType->EVMaximumPowerLimit_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 3, 2); errn = encode_iso1PhysicalValueType(stream, &iso1CurrentDemandReqType->EVMaximumPowerLimit ); grammarID = 278; } else if ( iso1CurrentDemandReqType->BulkChargingComplete_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 3, 3); errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[BOOLEAN] errn = encodeBoolean(stream, iso1CurrentDemandReqType->BulkChargingComplete); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE grammarID = 279; } else if ( 1 == 1 ) { // ChargingComplete is mandatory default errn = encodeNBitUnsignedInteger(stream, 3, 4); errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[BOOLEAN] errn = encodeBoolean(stream, iso1CurrentDemandReqType->ChargingComplete); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE grammarID = 280; } break; case 276: // After EVMaximumVoltageLimit - 3-bit choice if ( iso1CurrentDemandReqType->EVMaximumCurrentLimit_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 3, 0); errn = encode_iso1PhysicalValueType(stream, &iso1CurrentDemandReqType->EVMaximumCurrentLimit ); grammarID = 277; } else if ( iso1CurrentDemandReqType->EVMaximumPowerLimit_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 3, 1); errn = encode_iso1PhysicalValueType(stream, &iso1CurrentDemandReqType->EVMaximumPowerLimit ); grammarID = 278; } else if ( iso1CurrentDemandReqType->BulkChargingComplete_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 3, 2); errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[BOOLEAN] errn = encodeBoolean(stream, iso1CurrentDemandReqType->BulkChargingComplete); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE grammarID = 279; } else if ( 1 == 1 ) { // ChargingComplete errn = encodeNBitUnsignedInteger(stream, 3, 3); errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[BOOLEAN] errn = encodeBoolean(stream, iso1CurrentDemandReqType->ChargingComplete); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE grammarID = 280; } break; case 277: // After EVMaximumCurrentLimit - 2-bit choice if ( iso1CurrentDemandReqType->EVMaximumPowerLimit_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 2, 0); errn = encode_iso1PhysicalValueType(stream, &iso1CurrentDemandReqType->EVMaximumPowerLimit ); grammarID = 278; } else if ( iso1CurrentDemandReqType->BulkChargingComplete_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 2, 1); errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[BOOLEAN] errn = encodeBoolean(stream, iso1CurrentDemandReqType->BulkChargingComplete); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE grammarID = 279; } else if ( 1 == 1 ) { // ChargingComplete errn = encodeNBitUnsignedInteger(stream, 2, 2); errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[BOOLEAN] errn = encodeBoolean(stream, iso1CurrentDemandReqType->ChargingComplete); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE grammarID = 280; } break; case 278: // After EVMaximumPowerLimit - 2-bit choice if ( iso1CurrentDemandReqType->BulkChargingComplete_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 2, 0); errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[BOOLEAN] errn = encodeBoolean(stream, iso1CurrentDemandReqType->BulkChargingComplete); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE grammarID = 279; } else if ( 1 == 1 ) { // ChargingComplete errn = encodeNBitUnsignedInteger(stream, 2, 1); errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[BOOLEAN] errn = encodeBoolean(stream, iso1CurrentDemandReqType->ChargingComplete); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE grammarID = 280; } break; case 279: // After BulkChargingComplete - 2-bit choice if ( iso1CurrentDemandReqType->ChargingComplete_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 2, 0); errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[BOOLEAN] errn = encodeBoolean(stream, iso1CurrentDemandReqType->ChargingComplete); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE grammarID = 280; } else if ( iso1CurrentDemandReqType->RemainingTimeToFullSoC_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 2, 1); errn = encode_iso1PhysicalValueType(stream, &iso1CurrentDemandReqType->RemainingTimeToFullSoC ); grammarID = 281; } else { // Skip to next optional elements errn = encodeNBitUnsignedInteger(stream, 2, 2); grammarID = 282; } break; case 280: // After ChargingComplete - 2-bit choice if ( iso1CurrentDemandReqType->RemainingTimeToFullSoC_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 2, 0); errn = encode_iso1PhysicalValueType(stream, &iso1CurrentDemandReqType->RemainingTimeToFullSoC ); grammarID = 281; } else if ( iso1CurrentDemandReqType->RemainingTimeToBulkSoC_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 2, 1); errn = encode_iso1PhysicalValueType(stream, &iso1CurrentDemandReqType->RemainingTimeToBulkSoC ); grammarID = 282; } else { // Skip to EVTargetVoltage errn = encodeNBitUnsignedInteger(stream, 2, 2); grammarID = 283; } break; case 281: // After RemainingTimeToFullSoC - 2-bit choice if ( iso1CurrentDemandReqType->RemainingTimeToBulkSoC_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 2, 0); errn = encode_iso1PhysicalValueType(stream, &iso1CurrentDemandReqType->RemainingTimeToBulkSoC ); grammarID = 282; } else if ( iso1CurrentDemandReqType->EVTargetVoltage_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 2, 1); errn = encode_iso1PhysicalValueType(stream, &iso1CurrentDemandReqType->EVTargetVoltage ); grammarID = 3; } else { // END_ELEMENT errn = encodeNBitUnsignedInteger(stream, 2, 2); grammarID = 3; } break; case 282: // After RemainingTimeToBulkSoC - 1-bit choice if ( iso1CurrentDemandReqType->EVTargetVoltage_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 1, 0); errn = encode_iso1PhysicalValueType(stream, &iso1CurrentDemandReqType->EVTargetVoltage ); grammarID = 3; } else { // END_ELEMENT errn = encodeNBitUnsignedInteger(stream, 1, 1); grammarID = 3; } break; case 3: // END_ELEMENT errn = encodeNBitUnsignedInteger(stream, 1, 0); done = 1; break; } } } ``` ## 6. Bit Stream Analysis for test5.exi ### 6.1 Expected Bit Pattern **Original test5.exi**: `80 98 02 10 50 90 8C 0C 0C 0E 0C 50 D1 00 32 01 86 00 20 18 81 AE 06 01 86 0C 80 61 40 C8 01 03 08 00 00 61 00 00 18 81 98 06 00` ### 6.2 Bit-by-Bit Breakdown **Bytes 0-1: EXI Header + Document Choice** - `80` = `10000000` (writeEXIHeader - 8 bits) - `98` = `10011000` = `1` + `0011000` (1 extra bit + choice 76 in 7 bits = 24 + 76 = 1001100 binary) **Bytes 2-3: V2G_Message Grammar States** - `02` = `00000010` = Grammar 256 (1 bit = 0) + Grammar 257 (1 bit = 0) + 6 padding bits - `10` = `00010000` = SessionID encoding start **Bytes 4-11: SessionID (BINARY_HEX)** - Length: 8 bytes encoded as `encodeUnsignedInteger16` - Data: `41 42 42 30 30 30 38 31` → compressed to `50 90 8C 0C 0C 0E 0C 50` **Bytes 11-12: Body Choice** - Choice 13 (CurrentDemandReq) in 6 bits: `001101` = `0D` hex **Bytes 12+: CurrentDemandReq Content** - Grammar 273: DC_EVStatus (1 bit = 0) - Grammar 274: EVTargetCurrent (1 bit = 0) - Grammar 275: EVMaximumVoltageLimit choice 0 (3 bits = 000) - Grammar 276: EVMaximumCurrentLimit choice 0 (3 bits = 000) - Grammar 277: EVMaximumPowerLimit choice 0 (2 bits = 00) - Grammar 278: ChargingComplete choice 1 (2 bits = 01) - Grammar 280: RemainingTimeToFullSoC choice 0 (2 bits = 00) - Grammar 281: RemainingTimeToBulkSoC choice 0 (2 bits = 00) - Grammar 282: EVTargetVoltage choice 0 (1 bit = 0) - Grammar 3: END_ELEMENT (1 bit = 0) ## 7. Key Differences from Current C# Implementation ### 7.1 Header Structure - VC2022: `writeEXIHeader()` only writes 0x80 - C#: Currently writes `80 98` incorrectly ### 7.2 SessionID Encoding - VC2022: Uses `encodeUnsignedInteger16` + `encodeBytes` (BINARY_HEX) - C#: Currently uses raw ASCII bytes ### 7.3 Grammar State Management - VC2022: Uses precise grammar state machine with variable bit choices - C#: Currently uses simplified fixed patterns ### 7.4 Bit Packing - VC2022: Precise bit-level packing with proper padding - C#: Currently has alignment issues ## 8. CurrentDemandRes Detailed Grammar States ### 8.1 encode_iso1CurrentDemandResType() - Complete Flow ```c static int encode_iso1CurrentDemandResType(bitstream_t* stream, struct iso1CurrentDemandResType* iso1CurrentDemandResType) { int grammarID = 317; while(!done) { switch(grammarID) { case 317: // ResponseCode is mandatory - 5-bit enumeration errn = encodeNBitUnsignedInteger(stream, 1, 0); // START_ELEMENT(ResponseCode) errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[ENUMERATION] errn = encodeNBitUnsignedInteger(stream, 5, iso1CurrentDemandResType->ResponseCode); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE grammarID = 318; break; case 318: // DC_EVSEStatus is mandatory errn = encodeNBitUnsignedInteger(stream, 1, 0); // START_ELEMENT(DC_EVSEStatus) errn = encode_iso1DC_EVSEStatusType(stream, &iso1CurrentDemandResType->DC_EVSEStatus ); grammarID = 319; break; case 319: // EVSEPresentVoltage is mandatory errn = encodeNBitUnsignedInteger(stream, 1, 0); // START_ELEMENT(EVSEPresentVoltage) errn = encode_iso1PhysicalValueType(stream, &iso1CurrentDemandResType->EVSEPresentVoltage ); grammarID = 320; break; case 320: // EVSEPresentCurrent is mandatory errn = encodeNBitUnsignedInteger(stream, 1, 0); // START_ELEMENT(EVSEPresentCurrent) errn = encode_iso1PhysicalValueType(stream, &iso1CurrentDemandResType->EVSEPresentCurrent ); grammarID = 321; break; case 321: // EVSECurrentLimitAchieved is mandatory boolean errn = encodeNBitUnsignedInteger(stream, 1, 0); // START_ELEMENT(EVSECurrentLimitAchieved) errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[BOOLEAN] errn = encodeBoolean(stream, iso1CurrentDemandResType->EVSECurrentLimitAchieved); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE grammarID = 322; break; case 322: // EVSEVoltageLimitAchieved is mandatory boolean errn = encodeNBitUnsignedInteger(stream, 1, 0); // START_ELEMENT(EVSEVoltageLimitAchieved) errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[BOOLEAN] errn = encodeBoolean(stream, iso1CurrentDemandResType->EVSEVoltageLimitAchieved); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE grammarID = 323; break; case 323: // EVSEPowerLimitAchieved is mandatory boolean errn = encodeNBitUnsignedInteger(stream, 1, 0); // START_ELEMENT(EVSEPowerLimitAchieved) errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[BOOLEAN] errn = encodeBoolean(stream, iso1CurrentDemandResType->EVSEPowerLimitAchieved); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE grammarID = 324; break; case 324: // 3-bit choice for optional elements if ( iso1CurrentDemandResType->EVSEMaximumVoltageLimit_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 3, 0); errn = encode_iso1PhysicalValueType(stream, &iso1CurrentDemandResType->EVSEMaximumVoltageLimit ); grammarID = 325; } else if ( iso1CurrentDemandResType->EVSEMaximumCurrentLimit_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 3, 1); errn = encode_iso1PhysicalValueType(stream, &iso1CurrentDemandResType->EVSEMaximumCurrentLimit ); grammarID = 326; } else if ( iso1CurrentDemandResType->EVSEMaximumPowerLimit_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 3, 2); errn = encode_iso1PhysicalValueType(stream, &iso1CurrentDemandResType->EVSEMaximumPowerLimit ); grammarID = 327; } else if ( 1 == 1 ) { // EVSEID is mandatory default errn = encodeNBitUnsignedInteger(stream, 3, 3); errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[STRING] errn = encodeUnsignedInteger16(stream, (uint16_t)(iso1CurrentDemandResType->EVSEID.charactersLen + 2)); errn = encodeCharacters(stream, iso1CurrentDemandResType->EVSEID.characters, iso1CurrentDemandResType->EVSEID.charactersLen); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE grammarID = 328; } break; case 325: // After EVSEMaximumVoltageLimit - 2-bit choice if ( iso1CurrentDemandResType->EVSEMaximumCurrentLimit_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 2, 0); errn = encode_iso1PhysicalValueType(stream, &iso1CurrentDemandResType->EVSEMaximumCurrentLimit ); grammarID = 326; } else if ( iso1CurrentDemandResType->EVSEMaximumPowerLimit_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 2, 1); errn = encode_iso1PhysicalValueType(stream, &iso1CurrentDemandResType->EVSEMaximumPowerLimit ); grammarID = 327; } else if ( 1 == 1 ) { // EVSEID errn = encodeNBitUnsignedInteger(stream, 2, 2); errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[STRING] errn = encodeUnsignedInteger16(stream, (uint16_t)(iso1CurrentDemandResType->EVSEID.charactersLen + 2)); errn = encodeCharacters(stream, iso1CurrentDemandResType->EVSEID.characters, iso1CurrentDemandResType->EVSEID.charactersLen); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE grammarID = 328; } break; case 326: // After EVSEMaximumCurrentLimit - 2-bit choice if ( iso1CurrentDemandResType->EVSEMaximumPowerLimit_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 2, 0); errn = encode_iso1PhysicalValueType(stream, &iso1CurrentDemandResType->EVSEMaximumPowerLimit ); grammarID = 327; } else if ( 1 == 1 ) { // EVSEID errn = encodeNBitUnsignedInteger(stream, 2, 1); errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[STRING] errn = encodeUnsignedInteger16(stream, (uint16_t)(iso1CurrentDemandResType->EVSEID.charactersLen + 2)); errn = encodeCharacters(stream, iso1CurrentDemandResType->EVSEID.characters, iso1CurrentDemandResType->EVSEID.charactersLen); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE grammarID = 328; } break; case 327: // After EVSEMaximumPowerLimit - EVSEID is mandatory errn = encodeNBitUnsignedInteger(stream, 1, 0); // START_ELEMENT(EVSEID) errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[STRING] errn = encodeUnsignedInteger16(stream, (uint16_t)(iso1CurrentDemandResType->EVSEID.charactersLen + 2)); errn = encodeCharacters(stream, iso1CurrentDemandResType->EVSEID.characters, iso1CurrentDemandResType->EVSEID.charactersLen); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE grammarID = 328; break; case 328: // SAScheduleTupleID is mandatory errn = encodeNBitUnsignedInteger(stream, 1, 0); // START_ELEMENT(SAScheduleTupleID) errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[NBIT_UNSIGNED_INTEGER] errn = encodeNBitUnsignedInteger(stream, 8, iso1CurrentDemandResType->SAScheduleTupleID); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE grammarID = 329; break; case 329: // Optional MeterInfo element with 1-bit choice if ( iso1CurrentDemandResType->MeterInfo_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 1, 0); errn = encode_iso1MeterInfoType(stream, &iso1CurrentDemandResType->MeterInfo ); grammarID = 330; } else { // Skip to ReceiptRequired errn = encodeNBitUnsignedInteger(stream, 1, 1); grammarID = 330; } break; case 330: // Optional ReceiptRequired element with 1-bit choice if ( iso1CurrentDemandResType->ReceiptRequired_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 1, 0); errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[BOOLEAN] errn = encodeBoolean(stream, iso1CurrentDemandResType->ReceiptRequired); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE grammarID = 3; } else { // END_ELEMENT errn = encodeNBitUnsignedInteger(stream, 1, 1); grammarID = 3; } break; case 3: // END_ELEMENT errn = encodeNBitUnsignedInteger(stream, 1, 0); done = 1; break; } } } ``` ## 9. Message Type Comparison ### 9.1 Body Choice Values ```c // encode_iso1BodyType() - 6-bit choices if ( iso1BodyType->CurrentDemandReq_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 6, 13); // Choice 13 errn = encode_iso1CurrentDemandReqType(stream, &iso1BodyType->CurrentDemandReq ); } else if ( iso1BodyType->CurrentDemandRes_isUsed == 1u ) { errn = encodeNBitUnsignedInteger(stream, 6, 14); // Choice 14 errn = encode_iso1CurrentDemandResType(stream, &iso1BodyType->CurrentDemandRes ); } ``` ### 9.2 Grammar States Summary | Message Type | Starting Grammar | Key Mandatory Elements | Optional Elements | |--------------|------------------|-------------------------|-------------------| | CurrentDemandReq | 273 | DC_EVStatus, EVTargetCurrent, ChargingComplete | EVMaximumVoltageLimit, EVMaximumCurrentLimit, EVMaximumPowerLimit, BulkChargingComplete, RemainingTimeToFullSoC, RemainingTimeToBulkSoC, EVTargetVoltage | | CurrentDemandRes | 317 | ResponseCode, DC_EVSEStatus, EVSEPresentVoltage, EVSEPresentCurrent, EVSECurrentLimitAchieved, EVSEVoltageLimitAchieved, EVSEPowerLimitAchieved, EVSEID, SAScheduleTupleID | EVSEMaximumVoltageLimit, EVSEMaximumCurrentLimit, EVSEMaximumPowerLimit, MeterInfo, ReceiptRequired | ### 9.3 Key Differences **CurrentDemandReq Features:** - Complex optional element chain with variable bit choices (3→2→1 bits) - PhysicalValue elements for power/voltage/current limits - Boolean values for charging completion status - Time-based PhysicalValue elements **CurrentDemandRes Features:** - ResponseCode enumeration (5-bit) - EVSE status and limit achievement booleans - String-based EVSEID with length encoding - SAScheduleTupleID as 8-bit unsigned integer - Optional MeterInfo complex type - Simpler grammar flow (mostly 1-bit and 2-bit choices) ## 10. C# Implementation Requirements 1. **Exact Bit Stream Matching**: Every bit must match VC2022 output 2. **Grammar State Machine**: Implement all grammar states (256, 257, 273-283, 317-330, 3) 3. **Choice Bit Encoding**: Correct bit widths (1, 2, 3, 5, 6, 7, 8 bits) for choices 4. **SessionID BINARY_HEX**: Use proper length + bytes encoding 5. **PhysicalValue Encoding**: Match exact bit patterns for values 6. **Boolean Encoding**: Proper CHARACTERS[BOOLEAN] with EE bits 7. **String Encoding**: CHARACTERS[STRING] with length+2 and encodeCharacters 8. **Enumeration Encoding**: CHARACTERS[ENUMERATION] with correct bit widths 9. **END_ELEMENT Handling**: Correct 1-bit END_ELEMENT encoding 이 문서를 기반으로 C# 구현을 단계별로 수정해야 합니다. ## 11. test5.exi 분석 결과 및 C# 구현 진행 상황 ### 11.1 PhysicalValue 인코딩 수정 완료 VC2022 PhysicalValue 구현 분석 결과: ```c static int encode_iso1PhysicalValueType(bitstream_t* stream, struct iso1PhysicalValueType* iso1PhysicalValueType) { // Grammar 117: START_ELEMENT(Multiplier) errn = encodeNBitUnsignedInteger(stream, 1, 0); // START_ELEMENT errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[NBIT_UNSIGNED_INTEGER] errn = encodeNBitUnsignedInteger(stream, 3, (uint32_t)(iso1PhysicalValueType->Multiplier + 3)); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE // Grammar 118: START_ELEMENT(Unit) errn = encodeNBitUnsignedInteger(stream, 1, 0); // START_ELEMENT errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[ENUMERATION] errn = encodeNBitUnsignedInteger(stream, 3, iso1PhysicalValueType->Unit); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE // Grammar 119: START_ELEMENT(Value) errn = encodeNBitUnsignedInteger(stream, 1, 0); // START_ELEMENT errn = encodeNBitUnsignedInteger(stream, 1, 0); // CHARACTERS[INTEGER] errn = encodeInteger16(stream, iso1PhysicalValueType->Value); errn = encodeNBitUnsignedInteger(stream, 1, 0); // valid EE // Grammar 3: END_ELEMENT errn = encodeNBitUnsignedInteger(stream, 1, 0); // END_ELEMENT } ``` **중요 발견**: PhysicalValue는 각 필드(Multiplier, Unit, Value)마다 완전한 START_ELEMENT → CHARACTERS → EE 패턴을 사용합니다. 단순한 primitive 인코딩이 아님! **C# 수정 결과**: PhysicalValue 인코딩 수정 후 길이가 38바이트 → 42바이트로 개선 (VC2022: 43바이트) ### 11.2 현재 차이점 분석 **VC2022 (정확함, 43바이트):** ``` 80 98 02 10 50 90 8c 0c 0c 0e 0c 50 d1 00 32 01 86 00 20 18 81 ae 06 01 86 0c 80 61 40 c8 01 03 08 00 00 61 00 00 18 81 98 06 00 ``` **C# v3 (거의 맞음, 42바이트):** ``` 80 98 02 10 50 90 8c 0c 0c 0e 0c 50 d4 32 06 18 02 00 c4 15 c0 e0 18 63 20 04 0c 28 64 00 20 61 00 00 18 40 00 0c 41 30 0e 00 ``` **남은 문제**: 13번째 바이트 차이 (d1 vs d4) = 3-bit 차이 ### 11.3 의심되는 초기화 문제 VC2022에서 메시지 초기화 문제가 있었다는 언급을 바탕으로, C#에서도 초기화 관련 문제가 있을 수 있음: 1. **_isUsed 플래그 초기화**: 모든 optional elements의 _isUsed 플래그가 올바르게 설정되지 않을 수 있음 2. **기본값 설정**: PhysicalValue나 기타 구조체의 기본값이 VC2022와 다를 수 있음 3. **Boolean 값 초기화**: ChargingComplete, BulkChargingComplete 등의 기본값 4. **Enumeration 초기화**: ResponseCode, UnitSymbol 등의 기본값 ### 11.4 확인해야 할 초기화 항목 **CurrentDemandReqType 생성자 확인 필요:** ```csharp public CurrentDemandReqType() { // 모든 _isUsed 플래그가 정확히 설정되어야 함 // 모든 기본값이 VC2022 C 구조체와 일치해야 함 } ``` **특히 확인해야 할 항목들:** - EVTargetVoltage_isUsed = true (이미 수정함) - BulkChargingComplete vs ChargingComplete 우선순위 ## 12. 최신 분석 결과 (2024.09.10) ### 12.1 BulkChargingComplete 처리 방식 발견 **핵심 발견**: VC2022는 XML에 BulkChargingComplete 요소가 있어도 **완전히 무시**합니다. **C# 수정 전:** ```csharp // XML 파싱 시 BulkChargingComplete 요소가 있으면 req.BulkChargingComplete_isUsed = true; // 자동으로 true 설정 ``` **C# 수정 후 (VC2022 동작 모방):** ```csharp // XML 파싱 시 BulkChargingComplete 요소가 있어도 req.BulkChargingComplete_isUsed = false; // 강제로 false 설정 ``` **디버그 출력 비교:** - **수정 전**: `🔍 Grammar 278: BulkChargingComplete_isUsed=True` → choice 0 선택 - **수정 후**: `🔍 Grammar 278: BulkChargingComplete_isUsed=False` → choice 1 선택 ✅ ### 12.2 Grammar 278 Choice 선택 확인 **VC2022 실제 동작:** ``` Grammar 278: ChargingComplete choice 1 (2 bits = 01) ``` **C# 수정 후 동작:** ``` 📍 Grammar 278: choice 1 (ChargingComplete), 2-bit=1 ✅ ``` **결론**: Grammar 278 choice 선택은 이제 완벽히 일치합니다. ### 12.3 남은 구조적 차이점 **현재 상태:** - **VC2022**: 43바이트, 13번째 바이트 = `D1` - **C# 수정 후**: 41바이트, 13번째 바이트 = `D4` **바이트 레벨 비교:** ``` VC2022 (43 bytes): 80 98 02 10 50 90 8c 0c 0c 0e 0c 50 d1 00 32 01 86 00 20 18 81 ae 06 01 86 0c 80 61 40 c8 01 03 08 00 00 61 00 00 18 81 98 06 00 C# Fixed (41 bytes): 80 98 02 10 50 90 8c 0c 0c 0e 0c 50 d4 32 06 18 02 00 c4 15 c0 e0 18 63 20 04 0c 28 64 14 0c 20 00 03 08 00 01 88 26 01 c0 ``` **관찰된 차이점:** 1. **전체 크기**: 2바이트 부족 (41 vs 43) 2. **13번째 바이트**: D1 vs D4 (3비트 차이) 3. **14번째부터**: 완전히 다른 패턴 ### 12.4 의심되는 구조적 문제 **가설 1: BitStream 인코딩 방식 차이** - C#의 BitStreamExact가 VC2022와 다른 방식으로 비트 패킹을 할 수 있음 - 특히 PhysicalValue 인코딩에서 차이 발생 가능 **가설 2: PhysicalValue 인코딩 차이** - Grammar 상태 전환 (117→118→119→3)에서 차이 - Multiplier, Unit, Value 인코딩 순서나 방식 차이 **가설 3: 추가 구조체 요소 누락** - VC2022에 있지만 C#에 누락된 선택적 요소들 - 숨겨진 패딩이나 정렬 바이트 ### 12.5 다음 조사 방향 **우선순위 1**: VC2022 디버그 모드로 상세 비트별 인코딩 과정 분석 ✅ **우선순위 2**: PhysicalValue 인코딩 과정의 비트 레벨 비교 **우선순위 3**: BitStream 인코딩/디코딩 방식의 정확성 검증 ### 12.6 스트림 위치 분석 결과 **VC2022 디버그 출력:** ``` 📍 [DEBUG CurrentDemandReq] Grammar case: 273, stream pos: 12 📍 [DEBUG CurrentDemandReq] Grammar case: 274, stream pos: 15 📍 [DEBUG CurrentDemandReq] Grammar case: 275, stream pos: 18 📍 Grammar 275: choice 0 (EVMaximumVoltageLimit), 3-bit=0 📍 [DEBUG CurrentDemandReq] Grammar case: 276, stream pos: 23 📍 [DEBUG CurrentDemandReq] Grammar case: 277, stream pos: 26 📍 [DEBUG CurrentDemandReq] Grammar case: 278, stream pos: 30 📍 [DEBUG CurrentDemandReq] Grammar case: 280, stream pos: 30 ``` **C# 디버그 출력:** ``` 🔍 Grammar 275: choice 0 (EVMaximumVoltageLimit), 3-bit=0 🔍 Grammar 276: choice 0 (EVMaximumCurrentLimit), 3-bit=0 🔍 Grammar 277: choice 0 (EVMaximumPowerLimit), 2-bit=0 📍 [DEBUG CurrentDemandReq] Grammar case: 278, stream pos: 29 ``` **핵심 발견**: **Grammar 278 이전에 1바이트 차이 발생** - **VC2022**: Grammar 278에서 stream pos: 30 - **C#**: Grammar 278에서 stream pos: 29 **추정 원인**: PhysicalValue 인코딩에서 바이트 차이 발생. 각 PhysicalValue (EVMaximumVoltageLimit, EVMaximumCurrentLimit, EVMaximumPowerLimit)의 인코딩 방식에서 VC2022가 더 많은 바이트를 사용. ### 12.7 C# PhysicalValue 상세 인코딩 분석 **C# PhysicalValue 인코딩 과정 (41바이트 총계):** | PhysicalValue | M | U | V | 시작pos | 끝pos | 바이트수 | 설명 | |---------------|---|---|---|---------|-------|----------|------| | EVTargetCurrent | 0 | A | 1 | 14 | 17 | 3바이트 | Grammar 274 | | EVMaxVoltageLimit | 0 | V | 471 | 17 | 21 | 4바이트 | Grammar 275 | | EVMaxCurrentLimit | 0 | A | 100 | 22 | 26 | 4바이트 | Grammar 276 | | EVMaxPowerLimit | 3 | W | 50 | 26 | 29 | 3바이트 | Grammar 277 | | **Grammar 278** | - | - | - | **29** | **29** | **0바이트** | **ChargingComplete choice** | | RemainingTimeToFullSoC | 0 | s | 0 | 30 | 33 | 3바이트 | Grammar 280 | | RemainingTimeToBulkSoC | 0 | s | 0 | 33 | 36 | 3바이트 | Grammar 281 | | EVTargetVoltage | 0 | V | 460 | 36 | 40 | 4바이트 | Grammar 282 | **핵심 관찰**: - **Grammar 278에서 stream pos: 29** - VC2022는 같은 지점에서 **stream pos: 30** (1바이트 차이) - PhysicalValue 크기: 대부분 3-4바이트 (Value 크기에 따라) **다음 분석 필요**: VC2022의 PhysicalValue 인코딩이 C#보다 어디서 1바이트 더 사용하는지 확인 - 각 PhysicalValue의 기본 Multiplier, Unit, Value - DC_EVStatus의 기본값들 ## 13. WriteInteger16 구현 및 결과 분석 ### 13.1 핵심 발견: 정수 인코딩 방식 차이점 **VC2022 encodeInteger16():** ```c int encodeInteger16(bitstream_t* stream, int16_t n) { if (n < 0) { errn = encodeBoolean(stream, 1); // 1 bit: sign=1 n = (int16_t)((-n) - 1); // magnitude-1 } else { errn = encodeBoolean(stream, 0); // 1 bit: sign=0 } if (errn == 0) { errn = encodeUnsignedInteger16(stream, (uint16_t)n); // variable length } } ``` **C# WriteInteger() (이전):** ```csharp public void WriteInteger(long val) { // Encode sign in LSB and magnitude in remaining bits bool isNegative = val < 0; long magnitude = isNegative ? (-val - 1) : val; // Shift magnitude left and set sign bit long encodedValue = (magnitude << 1) | (isNegative ? 1 : 0); WriteUnsignedInteger(encodedValue); } ``` ### 13.2 C# WriteInteger16() 구현 **새로운 C# WriteInteger16():** ```csharp public void WriteInteger16(short val) { // Write sign bit (1 bit) - exact match to VC2022 bool isNegative = val < 0; WriteBit(isNegative ? 1 : 0); // Calculate unsigned magnitude uint magnitude; if (isNegative) { magnitude = (uint)((-val) - 1); // VC2022와 동일한 계산 } else { magnitude = (uint)val; } // Write unsigned magnitude using variable length encoding WriteUnsignedInteger(magnitude); } ``` ### 13.3 결과 개선 **PhysicalValue encoding 수정:** ```csharp // 이전 stream.WriteInteger(value.Value); // 수정 후 stream.WriteInteger16((short)value.Value); ``` **인코딩 크기 개선:** - **이전**: 47 bytes → 42 bytes → **41 bytes** - **VC2022**: **43 bytes** - **차이**: **2 bytes only!** ### 13.4 Hex 비교 분석 **VC2022 (43 bytes):** ``` 8098 0210 5090 8c0c 0c0e 0c50 d100 3201 8600 2018 81ae 0601 860c 8061 40c8 0103 0800 0061 0000 1881 9806 00 ``` **C# WriteInteger16 적용 후 (41 bytes):** ``` 8098 0210 5090 8c0c 0c0e 0c50 d432 0618 0080 6206 b818 0618 3201 8503 2140 c200 0018 4000 0620 6601 80 ``` **주요 차이점:** - **13th byte**: `D1` (VC2022) → `D4` (C#) - **14th+ bytes**: 구조적 차이 지속, 하지만 총 길이는 2바이트만 차이 ### 13.5 남은 분석 과제 1. **Grammar state별 세부 분석**: 각 PhysicalValue 인코딩의 정확한 비트 패턴 2. **SessionID 인코딩 차이**: BINARY_HEX vs STRING 방식 3. **Header 구조 차이**: EXI document structure 4. **End element 처리**: Grammar 3 END_ELEMENT의 정확한 위치 ### 11.5 디버깅 전략 1. **단계별 비트 비교**: 각 grammar state별로 생성된 비트 패턴 비교 2. **초기화 검증**: XML parsing 후 모든 필드값과 _isUsed 플래그 확인 3. **VC2022 vs C# 구조체 비교**: 메모리 덤프 비교 가능하다면 4. **Grammar state flow 추적**: 어느 grammar state에서 차이가 발생하는지 추적 5. **WriteInteger16 성공**: PhysicalValue 인코딩 호환성 대폭 개선 (47→41 bytes)