【问题标题】:Representing BER TLV data structure in ANSI C?在 ANSI C 中表示 BER TLV 数据结构?
【发布时间】:2012-08-11 08:46:42
【问题描述】:

昨天我了解到使用TLV 格式表示信息。

如果您要使用 ANSI C 编写便携式 BER TLV 编码器/解码器,您会使用什么数据结构 (*)?

像下面这样的东西会做吗?

struct TlvElement
{
    int nTag;
    int nLength;
    unsigned char *pValue; // Byte array
    TlvElement *pNext;
}; 

(*) 很遗憾,我不能为此使用 C++ 和 STL。

【问题讨论】:

    标签: c data-structures asn.1


    【解决方案1】:

    来自维基文章:

    类型和长度大小固定(一般为1-4字节)

    所以,我会将nTagnLength 更改为一些固定长度的类型。 int 的大小是特定于平台的,这可能会给您带来一些麻烦。为您的协议修复它们的大小并使用int8_tint16_tint32_t 等。对于nLength,您甚至可以使用无符号。


    由于值可以是任何类型,我会使用void* 代替pValue,而不是unsigned char*


    你将如何使用这个数据结构?您希望如何访问不同的 TLV?
    我的意思是 - 你需要链表吗?或者,对于您的案例/应用程序/目的/等,链表会是最佳选项吗?

    我想说的是,您可以删除 pNext 元素并将 TLV 视为(动态增长的)数组的元素。这个真的取决于你的需要。

    很可能,当您实现 TLV 时,您需要通过某种连接发送它们,对吗?如果是这样,您需要考虑一些协议。我会做这样的事情 - 一开始就发送 TLV 的总数,我不会使用链表,而是使用动态数组。
    您应该小心通过网络发送此类数据结构 - pNext 指针无效,必须在连接的另一端重置它们。
    您还需要仔细发送数据,但我想您知道这些事情。我只是想提一下。


    编辑我发现您在理解 nested TLV 的含义时遇到了一些麻烦。

    嵌套的 TLV 只是一个 TLV 元素,它具有 TLV 类型的值。而这与 TLV 的“容器”——动态数组或链表无关。

    这是一个未经测试的示例,只是为了了解这个想法。我会这样做:

    struct TLV
    {
        uint32_t nTag;
        uint32_t nLength;
        void* pValue;
    };
    
    // created dynamic array with 3 TLVs:
    TLV* pMyTLVs = malloc( 3 * sizeof( struct TLV ) );
    
    // set the first 2 TLVs, some primitives, for example
    // ..
    
    // now, set the third TLV to be nested:
    pMyTLVs[ 2 ].nTag = ...; // set some tag, that will indicate nested TLV
    pMyTLVs[ 2 ].nLength = ...; // set length of the TLV element
    pMyTLVs[ 2 ].pValue = malloc( sizeof( struct TLV ) );
    
    // get pointer to the new, _nested_ TLV:
    TLV* pNested = (TLV*)pMyTLVs[ 2 ].pValue; 
    
    // now use the pNested TLV as an usual TLV:
    pNested->nTag = ...;
    pNested->nLength = ...;
    pNested->pValue = ...;
    
    // of course, pNested is not absolutely necessary, you may use directly
    // pMyTLVs[ 2 ].pValue->...;
    // but using pNested, makes the code more clear
    

    注意:再一次,这不是经过测试的代码,但我想你明白了。希望对您有所帮助。

    【讨论】:

    • 我需要能够表示嵌套结构(一个 TLV 元素具有另一个 TLV 元素等),所以我认为链表非常适合它。
    • @jpen - 您可以使用void* 指针创建嵌套结构,您不需要使用a-la-链表结构。但这取决于您,取决于您的需求。
    • @KirilKirov-我不确定如何使用动态数组创建嵌套结构。你能更详细地解释一下吗?
    • @KirilKirov-非常感谢您的代码。回复:“pMyTLVs[2].pValue = malloc(sizeof(struct TLV));”。那么 pMyTLVs[ 2 ].pValue 应该只指向一个 TLV 元素吗?
    • 我的理解是,一个 TLV 元素可以有一个 TLV 元素的集合,每个 TLV 元素可以有更多 TLV 元素的集合,依此类推。在 C++ 中,它可能表示为:class Tlv {uint32_t m_nTag;uint32_t m_nLength;void *m_pValue;std::vector m_vecChildElements;};
    【解决方案2】:

    如果我要使用 ANSI C 编写 TLV 编码器/解码器,我会选择经过验证的、标准化的、灵活的数据序列化(即在线)格式:ASN.1 BERThriftetc

    这是一个经典领域,每天都会对车轮进行改造。聪明人已经想到了高效、可维护和灵活的解决方案;再次经历同样的过程没有什么意义。

    例如,如果您示例中的结构用于序列化,您仍然需要考虑:

    • 字节序问题
    • 语言类型的大小(int 的大小取决于编译器平台和操作系统)
    • 负载中的数据类型(您可能希望携带原始数据、字符串、数字、位字段、枚举等)
    • 标签号集中分配
    • 可选元素和选项
    • 复合结构(例如 TLV 列表)

    一些现有的格式提供了语义和语法之间的分离;其他允许您为数据方案自动生成编码器/解码器。

    一旦您选择了 序列化 格式,您就可以开始考虑 in-memory 格式,这在很大程度上取决于您的应用程序将如何处理数据,例如:

    • 应用程序如何在解码后提取数据(例如,给定一个整数项,应用程序是访问编码表示还是可以轻松使用的本机表示?)
    • 应用程序如何在编码前准备数据
    • 应用程序是否是多线程的
    • 您是否想尽量减少复制开销(例如,如果您有大量原始数据,是否需要复制它以对其进行编码?如果原始数据是碎片化的,您是否需要在连续内存中的某处将其重组为编码?)
    • 解码和解码是否可以增量完成
    • 分配的内存属于应用程序还是库?
    • 如何处理内存不足和未知标签等错误

    我建议看一下asn1c处理ASN.1 BER的API,或者libtasn1的API。

    【讨论】:

    • @SquareRootOfTwentyThree-实际上我使用的是 ASN.1 BER TLV 格式。我想用这种格式写一个解码器/编码器。
    • @jpen 那么真正的问题不是“你会使用什么数据格式”而是“应用程序应该使用什么 API 来处理 ASN.1 数据结构”(数据访问、数据更新、内存分配) )。例如,应用程序将如何创建和访问 SEQUENCE、INTEGER、CHOICE 等。内存中的数据格式将随之而来。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-30
    • 1970-01-01
    • 1970-01-01
    • 2016-03-24
    相关资源
    最近更新 更多