当前位置: 首页 > news >正文

第二章 EXI协议原理与实现--9 设计完整的EXI编解码库

9 设计完整的EXI编解码库

在第8节我们已经验证cbExiGen编解码库是完全可以信赖的,但是缺少输入输出层代码,无法对json格式的命令进行解析填充成结构体,解码后的数据结构也无法输出成json格式字符串。本节作者开始搭建一个完整的EXI编解码库框架,目的是完美替换EXICodec.jar。 新的的编解码库使用C语言编码,具备相同的encode、decode函数接口。

9.1 编解码库设计框架

作者对框架进行了深入思考,决定采用cjson+cbExiGen+转换层的结构,按照分层设计思想设计的框架如下图所示。

  • 接口层:提供3个接口函数,version、encode、decode,函数原型与EXICodec.jar的调用接口一致。
  • 分配层:编码时,根据域名参数区分-2/-20命令,根据命令参数区分片段编码、签名编码、命令编码。
  • 表示层:编码时把命令cstring转换成cjson对象。解码时把cjson对象转换成cstring。 
  • 转换层:编码是根据cjson对象解析填充数据结构。解码时把数据结构填充到cjson对象。
  • 编解码层:根据数据结构进行编码, 根据exi数据进行解码。

接口定义:

extern EncodeResult encode(char * message, char * namespace);
extern char * decode(uint8_t * exidata, int len, char * namespace);
extern const char * getVersion();

9.2 编码流程

调用encode()接口,编码步骤如下:

1 检查namespace,确定是哪种协议或签名编码。 这些命名空间定义如下:

char * AppProtocolNS = "urn:iso:15118:2:2010:AppProtocol";
char * MsgDefNS = "urn:iso:15118:2:2013:MsgDef";
char * CommonMessagesNS = "urn:iso:std:iso:15118:-20:CommonMessages";
char * ACNS = "urn:iso:std:iso:15118:-20:AC";
char * DCNS = "urn:iso:std:iso:15118:-20:DC";
char * WPTNS = "urn:iso:std:iso:15118:-20:WPT";
char * ACDPNS = "urn:iso:std:iso:15118:-20:ACDP";
char * XMLSIG = "http://www.w3.org/2000/09/xmldsig#";

2 对于-2/-20命令,检查是完整的V2G命令还是片段编码。

命令中包含“V2G_Message”一般是完整的V2G命令,否则就是片段编码。

识别片段编码的方法是查询预定义的命令表,-2命令中支持的片段编码命令全部罗列如下:

//解码函数映射
ISO2_MsgDefNS_FragmentFunction frag_MsgDefNS[] = {

        {"AuthorizationReq",                     ISO2_Frag_AuthorizationReq_J2S,                    ISO2_Frag_AuthorizationReq_S2J},
        {"CertificateInstallationReq",           ISO2_Frag_CertificateInstallationReq_J2S,          ISO2_Frag_CertificateInstallationReq_S2J},
        {"CertificateInstallationRes",           ISO2_Frag_CertificateInstallationRes_J2S,          ISO2_Frag_CertificateInstallationRes_S2J},
        {"CertificateUpdateReq",                 ISO2_Frag_CertificateUpdateReq_J2S,                ISO2_Frag_CertificateUpdateReq_S2J},
        {"CertificateUpdateRes",                 ISO2_Frag_CertificateUpdateRes_J2S,                ISO2_Frag_CertificateUpdateRes_S2J},
        {"ChargeParameterDiscoveryRes",          ISO2_Frag_ChargeParameterDiscoveryRes_J2S,         ISO2_Frag_ChargeParameterDiscoveryRes_S2J},
        {"ContractSignatureCertChain",           ISO2_Frag_ContractSignatureCertChain_J2S,          ISO2_Frag_ContractSignatureCertChain_S2J},
        {"ContractSignatureEncryptedPrivateKey", ISO2_Frag_ContractSignatureEncryptedPrivateKey_J2S,ISO2_Frag_ContractSignatureEncryptedPrivateKey_S2J},
        {"DHpublickey",                          ISO2_Frag_DHpublickey_J2S,                         ISO2_Frag_DHpublickey_S2J},
        {"MeteringReceiptReq",                   ISO2_Frag_MeteringReceiptReq_J2S,                  ISO2_Frag_MeteringReceiptReq_S2J},
        {"SalesTariff",                          ISO2_Frag_SalesTariff_J2S,                         ISO2_Frag_SalesTariff_S2J},
        {"eMAID",                                ISO2_Frag_eMAID_J2S,                               ISO2_Frag_eMAID_S2J},
};

3 调用具体命令的转换函数

这一步设计成函数指针,调用函数指针dispatchMessage派发到对应的处理函数进行处理。

4 字符串转成cjson对象

在处理函数内首先把命令字符串转换成cjson对象。 如果出错,表示字符串格式非法。

5 根据该命令的数据结构,解析cjson对象正确填充数据结构

读取cjson每一个字段,填充到对应的数据结构体中。尤其要注意可选项,如果存在就必须正确填充。

6 对数据结构编码,返回exi数据

调用cbExigen编码函数,完成编码。

 9.3 解码流程

调用decode()接口,解码步骤如下:

1 检查namespace,确定是哪种协议。实际上,在解码过程中只有三种namaspace:

char * AppProtocolNS = "urn:iso:15118:2:2010:AppProtocol";
char * MsgDefNS = "urn:iso:15118:2:2013:MsgDef";
char * CommonMessagesNS = "urn:iso:std:iso:15118:-20:CommonMessages";

2 调用cbExigen解码函数

3 根据解码后的数据结构,识别出是哪一个命令。

4 调用对应命令的转换函数,把数据结构转成cjson

5 把cjson转成字符串,返回。    

9.4 转换层设计

转换层函数需要实现数据结构和cjson之间的双向转换,针对每一个命令都需要实现一步步一层层解析到底。 这些转换层代码行数总共5W+,作者付出最多精力的也正是这一层代码。因为V2G命令定义特别复杂,最多时候结构嵌套13层以上,写程序时直接让人坠入十八层地狱,烧的脑子都冒烟了。

针对-2协议编写的转换文件如下:

\src\Json2Schema\iso15118-2 的目录

2024/03/14  11:09             7,249 J2S_AuthorisationReq.c
2023/11/14  13:47             6,635 J2S_CableCheckReq.c
2025/01/16  17:56            21,999 J2S_CertificateInstallationReq.c
2025/01/17  11:08            49,141 J2S_ChargeParameterDiscoveryReq.c
2023/11/08  17:16             8,130 J2S_ChargingStatusReq.c
2023/11/14  09:50            12,546 J2S_common.c
2025/02/07  14:42            40,545 J2S_createHeaderWithSignedInfo.c
2023/11/14  14:56            16,961 J2S_CurrentDemandReq.c
2024/03/14  11:10             2,859 J2S_Frag_AuthorizationReq.c
2024/03/13  15:38             3,799 J2S_Frag_CertificateInstallationReq.c
2024/03/13  18:22             8,988 J2S_Frag_CertificateUpdateReq.c
2024/03/13  13:27             1,671 J2S_Frag_ChargeParameterDiscoveryRes.c
2024/03/13  11:22             1,542 J2S_Frag_ContractSignatureCertChain.c
2024/03/13  11:22             1,762 J2S_Frag_ContractSignatureEncryptedPrivateKey.c
2024/03/13  11:20             1,471 J2S_Frag_DHpublickey.c
2024/03/13  11:20             1,301 J2S_Frag_eMAID.c
2024/03/13  16:46             1,507 J2S_Frag_MeteringReceiptReq.c
2024/03/14  09:47             1,434 J2S_Frag_SalesTariff.c
2025/01/15  16:57            12,197 J2S_MeteringReceiptReq.c
2023/11/14  15:26             7,880 J2S_PaymentDetailsReq.c
2023/11/02  10:08             8,988 J2S_PaymentServiceSelectionReq.c
2025/01/22  17:22            24,762 J2S_PowerDeliveryReq.c
2023/11/14  15:10             7,709 J2S_PreChargeReq.c
2023/11/02  10:09            12,901 J2S_ServiceDetailReq.c
2024/05/20  10:33            18,174 J2S_ServiceDiscoveryReq.c
2025/02/07  17:13             6,104 J2S_SessionSetupReq.c
2023/11/08  17:59             5,808 J2S_SessionStopReq.c
2023/11/30  16:13             1,429 J2S_SignedInfo.c
2023/11/08  09:27             5,232 J2S_supportedAppProtocolReq.c
2023/11/14  13:52             7,075 J2S_WeldingDetectionReq.c
              30 个文件        307,799 字节

针对-20编写的文件如下:

\src\Json2Schema\iso15118-20 的目录

2023/11/29  17:24             1,539 J2S_AbsolutePriceSchedule_20.c
2023/12/03  12:55            99,497 J2S_AC_ChargeLoopReq_20.c
2023/12/08  11:30            35,753 J2S_AC_ChargeParameterDiscoveryReq_20.c
2023/10/26  11:27             8,176 J2S_ac_common_20.c
2025/01/15  17:00            30,967 J2S_AC_createHeaderWithSignedInfo_20.c
2024/03/11  23:49            10,848 J2S_AuthorizationReq_20.c
2024/03/12  00:07             9,814 J2S_AuthorizationSetupReq_20.c
2025/01/17  09:20            26,533 J2S_CertificateInstallationReq_20.c
2024/03/13  18:25            13,356 J2S_common_20.c
2025/01/15  17:34            41,991 J2S_createHeaderWithSignedInfo_20.c
2023/12/04  16:56             4,113 J2S_DC_CableCheckReq_20.c
2024/02/28  16:22            77,415 J2S_DC_ChargeLoopReq_20.c
2023/12/06  13:26            26,368 J2S_DC_ChargeParameterDiscoveryReq_20.c
2023/12/04  16:25             4,785 J2S_dc_common_20.c
2025/01/15  17:26            35,233 J2S_DC_createHeaderWithSignedInfo_20.c
2023/12/04  19:07             5,280 J2S_DC_PreChargeReq_20.c
2023/12/04  19:08             4,701 J2S_DC_WeldingDetectionReq_20.c
2025/01/15  17:26            18,721 J2S_MeteringConfirmationReq_20.c
2024/04/19  10:30             1,549 J2S_OEMProvisioningCertificateChain_20.c
2023/11/29  15:19             1,451 J2S_PnC_AReqAuthorizationMode_20.c
2024/03/14  17:41            13,564 J2S_PowerDeliveryReq_20.c
2023/11/01  16:03             1,149 J2S_PreChargeReq_20.c
2023/12/06  13:44            68,086 J2S_ScheduleExchangeReq_20.c
2023/11/02  10:16            10,893 J2S_ServiceDetailReq_20.c
2023/11/02  10:16             8,582 J2S_ServiceDiscoveryReq_20.c
2023/11/02  10:16             7,293 J2S_ServiceSelectionReq_20.c
2025/02/05  13:23             4,801 J2S_SessionSetupReq_20.c
2023/11/06  15:57             5,840 J2S_SessionStopReq_20.c
2023/11/29  15:55             1,297 J2S_SignedInfo_20.c
2023/11/29  15:48             1,275 J2S_SignedInstallationData_20.c
              30 个文件        580,870 字节

举个一般的命令转换文件iso15118-2\J2S_AuthorisationReq.c,说明下关键的代码

/*
* J2S_AuthorisationReq.c
*
*  Created on: 2023-10-10
*      Author: Tom
*/

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

#include "cJSON.h"

#include "iso2_msgDefDatatypes.h"
#include "iso2_msgDefEncoder.h"
#include "iso2_msgDefDecoder.h"

#include "J2S_header.h"

#include "base64Codec.h"
#include "printHex.h"

/*--------------------------------------------------------------------------------*/

static cJSON * ISO2_create_cJSON_Body_AuthorizationRes(struct iso2_AuthorizationResType cmd);

/*--------------------------------------------------------------------------------*/

// transform json to Schema Struct
struct iso2_exiDocument ISO2_AuthorizationReq_J2S(cJSON *cjson) {

    //读取V2G_Message
    cJSON *json_V2G_Message = cJSON_GetObjectItem(cjson, "V2G_Message");

    //读取Header
    cJSON *json_Header = cJSON_GetObjectItem(json_V2G_Message, "Header");

    //读取Body对象
    cJSON *json_Body = cJSON_GetObjectItem(json_V2G_Message, "Body");
    cJSON *json_AuthorizationReq = cJSON_GetObjectItem(json_Body, "AuthorizationReq");

    //填充Schema数据结构
    // init for structs
    struct iso2_exiDocument exiDoc;
    struct iso2_V2G_Message V2G_Message;
    struct iso2_MessageHeaderType Header;
    struct iso2_BodyType Body;
    struct iso2_AuthorizationReqType AuthorizationReq;

    // fill Header
    init_iso2_MessageHeaderType(&Header);
    Header = create_Header_WithSignedInfo(json_Header);

    // fill Body
    init_iso2_AuthorizationReqType(&AuthorizationReq);

    // Attribute: Id, ID (base: NCName)
    cJSON * json_Id = cJSON_GetObjectItem(json_AuthorizationReq,"Id");
    if(json_Id){
        strcpy(AuthorizationReq.Id.characters, json_Id->valuestring);
        AuthorizationReq.Id.charactersLen = strlen(json_Id->valuestring);
        AuthorizationReq.Id_isUsed = 1u;
    }

    // GenChallenge, genChallengeType (base: base64Binary)
    cJSON * json_GenChallenge = cJSON_GetObjectItem(json_AuthorizationReq,"GenChallenge");
    if(json_GenChallenge){
        AuthorizationReq.GenChallenge_isUsed = 1u;
        base64_decode((uint8_t *)json_GenChallenge->valuestring, strlen(json_GenChallenge->valuestring),
                AuthorizationReq.GenChallenge.bytes, &AuthorizationReq.GenChallenge.bytesLen);
    }

    init_iso2_BodyType(&Body);
    Body.AuthorizationReq = AuthorizationReq;
    Body.AuthorizationReq_isUsed = 1u;

    init_iso2_V2G_Message(&V2G_Message);
    V2G_Message.Header = Header;
    V2G_Message.Body = Body;

    init_iso2_exiDocument( &exiDoc );
    exiDoc.V2G_Message = V2G_Message;

    return exiDoc;
}

// transform Schema Struct to json
cJSON * ISO2_AuthorizationReq_S2J(struct iso2_exiDocument *exiDoc){

    cJSON * root   =  cJSON_CreateObject();
    cJSON * json_V2G_Message   =  cJSON_CreateObject();
    cJSON_AddItemToObject(root, "V2G_Message", json_V2G_Message);

    //----------Header--------
    cJSON * json_Header = create_cJSON_Header_WithSignedInfo(exiDoc->V2G_Message.Header);
    cJSON_AddItemToObject(json_V2G_Message, "Header", json_Header);

    //----------Body-----------
    cJSON * json_Body   =  cJSON_CreateObject();
    cJSON_AddItemToObject(json_V2G_Message, "Body", json_Body);
    if(exiDoc->V2G_Message.Body.AuthorizationReq_isUsed){
        cJSON * json_AuthorizationReq   =  cJSON_CreateObject();
        if(json_AuthorizationReq){
            cJSON_AddItemToObject(json_Body, "AuthorizationReq", json_AuthorizationReq);

            struct iso2_AuthorizationReqType cmd = exiDoc->V2G_Message.Body.AuthorizationReq;
            // Attribute: Id, ID (base: NCName)
            if(cmd.Id_isUsed){
                char *Id = (char *)malloc(cmd.Id.charactersLen+1);
                memset(Id,0, cmd.Id.charactersLen+1);
                memcpy(Id, cmd.Id.characters, cmd.Id.charactersLen);
                cJSON_AddItemToObject(json_AuthorizationReq, "Id", cJSON_CreateString(Id));
                free(Id);
            }
            // GenChallenge, genChallengeType (base: base64Binary)
            if(cmd.GenChallenge_isUsed){
                char * encode = (char *)base64_encode2Str(cmd.GenChallenge.bytes, (int)cmd.GenChallenge.bytesLen);
                if(encode){
                    cJSON_AddItemToObject(json_AuthorizationReq, "GenChallenge", cJSON_CreateString(encode));
                    free(encode);
                }
            }

        }
    }

    return root;
}

//---------------------------------------------------------------------------------------------------------------
// transform json to Schema Struct
struct iso2_exiDocument ISO2_AuthorizationRes_J2S(cJSON *cjson) {

    //读取V2G_Message
    cJSON *json_V2G_Message = cJSON_GetObjectItem(cjson, "V2G_Message");

    //读取Header
    cJSON *json_Header = cJSON_GetObjectItem(json_V2G_Message, "Header");

    //读取Body对象
    cJSON *json_Body = cJSON_GetObjectItem(json_V2G_Message, "Body");
    cJSON *json_AuthorizationRes = cJSON_GetObjectItem(json_Body, "AuthorizationRes");

    //填充Schema数据结构
    // init for structs
    struct iso2_exiDocument exiDoc;
    struct iso2_V2G_Message V2G_Message;
    struct iso2_MessageHeaderType Header;
    struct iso2_BodyType Body;
    struct iso2_AuthorizationResType AuthorizationRes;

    // fill Header
    init_iso2_MessageHeaderType(&Header);
    Header = create_Header_WithSignedInfo(json_Header);

    // fill Body
    init_iso2_AuthorizationResType(&AuthorizationRes);

    //ResponseCode
    cJSON *json_ResponseCode = cJSON_GetObjectItem(json_AuthorizationRes, "ResponseCode");
    if(json_ResponseCode){
        AuthorizationRes.ResponseCode = ResponseCode_Str2Int(json_ResponseCode->valuestring);
    }

    // EVSEProcessing, EVSEProcessingType (base: string)
    cJSON * json_EVSEProcessing = cJSON_GetObjectItem(json_AuthorizationRes,"EVSEProcessing");
    if(json_EVSEProcessing){
        AuthorizationRes.EVSEProcessing  = EVSEProcessing_Str2Int(json_EVSEProcessing->valuestring);
    }

    init_iso2_BodyType(&Body);
    Body.AuthorizationRes = AuthorizationRes;
    Body.AuthorizationRes_isUsed = 1u;

    init_iso2_V2G_Message(&V2G_Message);
    V2G_Message.Header = Header;
    V2G_Message.Body = Body;

    init_iso2_exiDocument( &exiDoc );
    exiDoc.V2G_Message = V2G_Message;

    return exiDoc;
}


// transform Schema Struct to json
cJSON * ISO2_AuthorizationRes_S2J(struct iso2_exiDocument *exiDoc){

    cJSON * root   =  cJSON_CreateObject();
    cJSON * json_V2G_Message   =  cJSON_CreateObject();
    cJSON_AddItemToObject(root, "V2G_Message", json_V2G_Message);

    //----------Header--------
    cJSON * json_Header = create_cJSON_Header_WithSignedInfo(exiDoc->V2G_Message.Header);
    cJSON_AddItemToObject(json_V2G_Message, "Header", json_Header);

    //----------Body-----------
    cJSON * json_Body   =  cJSON_CreateObject();
    cJSON_AddItemToObject(json_V2G_Message, "Body", json_Body);
    if(exiDoc->V2G_Message.Body.AuthorizationRes_isUsed){
        cJSON * json_AuthorizationRes = ISO2_create_cJSON_Body_AuthorizationRes(exiDoc->V2G_Message.Body.AuthorizationRes);
        cJSON_AddItemToObject(json_Body, "AuthorizationRes", json_AuthorizationRes);
    }

    return root;
}

static cJSON * ISO2_create_cJSON_Body_AuthorizationRes(struct iso2_AuthorizationResType cmd){
    cJSON *json_Res = cJSON_CreateObject();

    //ResponseCode
    char * ResponseCode = ResponseCode_Int2Str(cmd.ResponseCode);
    if(ResponseCode){
        cJSON_AddItemToObject(json_Res, "ResponseCode", cJSON_CreateString(ResponseCode));
    }

    // EVSEProcessing, EVSEProcessingType (base: string)
    char *EVSEProcessing = EVSEProcessing_Int2Str(cmd.EVSEProcessing);
    if(EVSEProcessing){
        cJSON_AddItemToObject(json_Res, "EVSEProcessing", cJSON_CreateString(EVSEProcessing));
    }

    return json_Res;
}

转换函数入口:

struct iso2_exiDocument ISO2_AuthorizationReq_J2S(cJSON *cjson)       // AuthorizationReq:   编码时把cjson解析成数据结构iso2_exiDocument
cJSON * ISO2_AuthorizationReq_S2J(struct iso2_exiDocument *exiDoc)    // AuthorizationReq:  解码时把iso2_exiDocument转换成cjson

struct iso2_exiDocument ISO2_AuthorizationRes_J2S(cJSON *cjson)       // AuthorizationRes:   编码时把cjson解析成数据结构iso2_exiDocument
cJSON * ISO2_AuthorizationRes_S2J(struct iso2_exiDocument *exiDoc)    // AuthorizationRes:   解码时把iso2_exiDocument转换成cjson

    

下节预告:

9.5 cjson库缺陷改进

9.6 cbExiGen库bug及改进

9.7 编解码库稳定性测试

9.8 编解码库兼容性测试

9.9 编解码库性能测试

相关文章:

  • NEW!睿本云接入抖音「会员通」!
  • OpenCV旋转估计(3)图像拼接类cv::detail::MultiBandBlender
  • Android RemoteViews:跨进程 UI 更新的奥秘与实践
  • C++类与对象的第二个简单的实战练习-3.24笔记
  • 2025年渗透测试面试题总结-某美团-安全工程师实习(题目+回答)
  • MVVM、MVC、MVP 的区别
  • Python前缀和(例题:异或和,求和)
  • python中的变量 - 第一章
  • Linux第一节:Linux系统编程入门指南
  • 【参考资料 II】C 运算符大全:算术、关系、赋值、逻辑、条件、指针、符号、成员、按位、混合运算符
  • ctfshow WEB web签到题
  • 五种IO模型
  • 【JavaEE】Mybatis XML配置文件实现增删改查
  • 编程从键盘输入一个大写英文字符,将其转换为小写字符显示并显示出它的十进制,十六的 ASCI码。
  • Kubernetes集群中部署SonarQube服务
  • Gitee上库常用git命令
  • Babel 从入门到精通(四):@babel/template的应用实例与最佳实践
  • 【JavaEE】springMVC返回Http响应
  • 【负载均衡系列】Nginx
  • 【例6.5】活动选择(信息学奥赛一本通-1323)
  • 两部门部署中小学幼儿园教师招聘工作:吸纳更多高校毕业生从教
  • 定位真核生物起源于约27.2亿年前,华东师大团队在《自然》发文
  • 金融监管总局将出八大增量政策,李云泽详解稳楼市稳股市“组合拳”
  • 巴基斯坦军方:印度向巴本土及巴控克什米尔发射导弹
  • 郑州一街道被指摊贩混乱经营,12345热线:已整治并加强巡查
  • 李翔宁:城市的每个人都参与了上海的建造,这一过程还在持续