在ANSI C中表示BER TLV数据结构?

时间:2012-08-11 08:46:42

标签: c data-structures asn.1

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

如果您要在ANSI C中编写便携式BER TLV编码器/解码器,您将使用哪种数据结构(*)?

会不会像以后那样呢?

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

(*)不幸的是我不能使用C ++和STL。

2 个答案:

答案 0 :(得分:3)

来自维基文章:

  

类型和长度的大小是固定的(通常为1-4个字节)

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


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


您将如何使用此数据结构?您希望如何访问不同的TLV? 我的观点是 - 你需要链表吗?或者,链接列表是否为您的案例/应用程序/目的/等的最佳选项?

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

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


编辑我发现您在理解嵌套 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

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

答案 1 :(得分:1)

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

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

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

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

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

选择序列化格式后,您可以开始考虑内存中格式,这在很大程度上取决于应用程序操作数据的方式,例如:

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

我建议您查看asn1c生成的用于处理ASN.1 BER或libtasn1 API的API。