本文剖析asn-octs.h/c,从源代码来学习eSNACC对OCTET STRING的编码和解码。
eSNACC对字节串OCTET STRING的处理与上一篇描述的比特串的方法类似,而且字节串的处理更加简单。所以在上一篇的基础上,我们专门分析上一篇中对连接型串解码时没有展开讲的函数,也作为上一篇的补充。上一篇可以参见eSNACC对BIT STRING的编码和解码 。
先看看eSNACC对字节串的表示方法:
typedef struct AsnOcts
{
unsigned long octetLen;
char *octs;
} AsnOcts;
可以看到与比特串很类似,唯一不同的是octetLen的类型,对字节串的是unsigned long;而比特串的是int。为什么要这样设计呢?因为长度肯定不可能是负数,所以设计成unsigned的是我们认为合理的。而对比特串的想法,我不太清楚,这其实也可以说是对那个模块的另一个设计问题。
octetLen代表该字节串的长度,但不包含串末尾的null字节。
octs是指向字节串的指针,分配的字节串长度为octetLen+1,在解码时会主动给末尾设为null。千万要注意:这个char*与比特串的char*有很大的不同:字节串的char*是一个字符指针,其指向的是一个由null终结的字符串。而比特串的就是一串比特位,最末尾也没有null。
好了,看字节串的编码和解码,我们发现他的操作很简单,只是一个内存拷贝的过程,编码的时候也不需要专门来存len信息,也更不需要像比特串那样做若干操作来处理字节填充、未使用位数计算等等。所以解码的时候也不需要判断这些。仅仅一个要记住的是:len是不包括字节串末尾的null字符的,只是在解码时多分配了一个字节,主动使其为null结束。
上面这些都很简单,我想本文就主要分析对连接型字节串的解码过程。这是两个静态函数,不够在分析这些代码之前,我觉得应该下说明一下他的设计方法:
由于连接型串就是有多个字节串嵌套构造而成的。也就是
连接串 => 连接串 + 原生串
这样的话,在解码时,势必产生很多夹杂其中的原生串碎片。如果让用户来管理这些碎片是很麻烦的事,我们更喜欢把一个连接串也存为一串整体的内存中,就如同操作一个原生串一样。正是基于这个原因,eSNACC就为我们做好封装,他自己判断串类型,然后做不同的解码,最后都是返回给用户一块连续的内存。
我们先看看用于管理串碎片的方案:结构体加宏
typedef struct StrStkElmt
{
char *str;
unsigned long len;
} StrStkElmt;
typedef struct StrStk
{
StrStkElmt *stk; /**//* ptr to array of SSElmts with 'size' elmts */
unsigned long initialNumElmts;
unsigned long numElmts; /**//* total # of elements in str stk */
unsigned long growElmts; /**//* # elmts to increase size by when nec */
unsigned long nextFreeElmt; /**//* index of next free element */
unsigned long totalByteLen; /**//* octet len of string stored in stk */
} StrStk;
extern StrStk strStkG;
/**//*
* initializes stk (Allocates if nec.)
* once stk is enlarged, it doesn't shrink
*/
#define RESET_STR_STK()\
{\
strStkG.nextFreeElmt = 0;\
strStkG.totalByteLen = 0;\
if (strStkG.stk == NULL){\
strStkG.stk = (StrStkElmt*) malloc ((strStkG.initialNumElmts) *sizeof (StrStkElmt));\
strStkG.numElmts = strStkG.initialNumElmts;}\
}
/**//*
* add a char*,len pair to top of stack.
* grows stack if necessary using realloc (!)
*/
#define PUSH_STR(strPtr, strsLen, env)\
{\
if (strStkG.nextFreeElmt >= strStkG.numElmts)\
{\
strStkG.stk = (StrStkElmt*) realloc (strStkG.stk, (strStkG.numElmts + strStkG.growElmts) *sizeof (StrStkElmt));\
strStkG.numElmts += strStkG.growElmts;\
}\
strStkG.totalByteLen += strsLen;\
strStkG.stk[strStkG.nextFreeElmt].str = strPtr;\
strStkG.stk[strStkG.nextFreeElmt].len = strsLen;\
strStkG.nextFreeElmt++;\
}
/**//*
* Set up size values for the stack that is used for merging constructed
* octet or bit string into single strings.
* **** Call this before decoding anything. *****
* Note: you don't have to call this if the default values
* for initialStkSizeG and stkGrowSizeG are acceptable
*/
#define SetupConsBitsOctsStringStk (initialNumberOfElmts, numberOfElmtsToGrowBy)\
{\
strStkG.initialNumElmts = initialNumberOfElmts; \
strStkG.growElmts = numberOfElmtsToGrowBy;\
}
可以看到用于管理每一块比特串或字节串碎片的结构StrStkElmt与我们定义的AsnBits/AsnOcts非常类似:基本就是定义字段的顺序不同。
而结构体StrStk就是用来管理若干碎片的,使得我们在解码时不需要处理这些烦人的片段,他内部将这些都解码好并且最后拷贝到一个整体的内存块中返回,真是功德无限呀!我们就来认识一下这个活佛吧:
StrStkElmt *stk:指向碎片串的指针。
unsigned long initialNumElmts:首次分配StrStkElmt的数目。
unsigned long numElmts:stk中拥有的总的StrStkElmt数。
unsigned long growElmts:当首次分配的initialNumElmts不够用,而需要再次分配时,默认的增长数。
unsigned long nextFreeElmt:stk中可用存放StrStkElmt的序号。
unsigned long totalByteLen:stk存放的串的总字节数。
然后声明了一个变量strStkG,具体定义在实现文件中。后面就是定义了几个宏来操作这个变量:
RESET_STR_STK:用于给stk分配内存,并且初始化numElmts值。
PUSH_STR:将char*,len对加到stk串中。如果空间不够会自动增长。
SetupConsBitsOctsStringStk:如果你对他提供的initialNumElmts和growElmts不满意,请在做任何解码操作之前调用这个宏来定义自己的需求。
对于这种方案,有几点说明是:
1、一旦stk扩容了,那么就无法缩水。
2、如果由于数量不够而需要增长,这可能会导致内存的重分配和拷贝,一定程度影响性能。
在该模块的实现文件中只有结构体变量strStkG的定义:
/**//* global for use by AsnBits and AsnOcts */
StrStk strStkG = { NULL, 128, 0, 64, 0, 0 };
也就是strStkG的初始值为:StrStkElmt指针为空,默认会分配的128个指针空间。因为当前还没分配内存,所以总数为0.当不够用时一次增长64个。然后后面两个量都初始化为0.
好了,有了对这个辅助体的全面的认识,那么最上面的两个解码函数也就迎刃而解了。
先看解码连续字节串的入口:
/**//*
* Decodes a seq of universally tagged octets strings until either EOC is
* encountered or the given len is decoded. Merges them into a single
* string. puts a NULL terminator on the string but does not include
* this in the length.
*/
static void
BDecConsAsnOcts PARAMS ((b, len, result, bytesDecoded, env),
GenBuf *b _AND_
AsnLen len _AND_
AsnOcts *result _AND_
AsnLen *bytesDecoded _AND_
jmp_buf env)
{
char *bufCurr;
unsigned long curr;
RESET_STR_STK();
/**//*
* decode each piece of the octet string, puting
* an entry in the octet string stack for each
*/
FillOctetStringStk (b, len, bytesDecoded, env);
result->octetLen = strStkG.totalByteLen;
/**//* alloc str for all octs pieces with extra byte for null terminator */
bufCurr = result->octs = Asn1Alloc (strStkG.totalByteLen +1);
CheckAsn1Alloc (result->octs, env);
/**//* copy octet str pieces into single blk */
for (curr = 0; curr < strStkG.nextFreeElmt; curr++)
{
memcpy (bufCurr, strStkG.stk[curr].str, strStkG.stk[curr].len);
bufCurr += strStkG.stk[curr].len;
}
/**//* add null terminator - this is not included in the str's len */
*bufCurr = '\0';
} /**//* BDecConsAsnOcts */
首先函数注释说明了解码一串连接字节串,直到指定长度或者遇到EOC。最后把这些分散的碎片整合到一个字符串中。与比特串不同的是,会在串最后添加null字符。
逻辑上,首先调用RESET_STR_STK()给完成strStkG的指针内存分配。然后调用FillOctetStringStk来完成真正的碎片解码,该函数完成操作后,各个碎片都可以通过strStkG的成员变量来访问了。正如我们看到的,他分配了一个strStkG.totalByteLen +1的空间,多1是为了存放null。然后遍历每一个有效的指针,将值拷贝到分配这一整块内存中。最后在末尾附上null。
FillOctetStringStk具体定义如下:
/**//*
* Used for decoding constructed OCTET STRING values into
* a contiguous local rep.
* fills string stack with references to the pieces of a
* construced octet string
*/
static void
FillOctetStringStk PARAMS ((b, elmtLen0, bytesDecoded, env),
GenBuf *b _AND_
AsnLen elmtLen0 _AND_
AsnLen *bytesDecoded _AND_
jmp_buf env)
{
unsigned long refdLen;
unsigned long totalRefdLen;
char *strPtr;
unsigned long totalElmtsLen1 = 0;
unsigned long tagId1;
unsigned long elmtLen1;
for (; (totalElmtsLen1 < elmtLen0) || (elmtLen0 == INDEFINITE_LEN); )
{
tagId1 = BDecTag (b, &totalElmtsLen1, env);
if ((tagId1 == EOC_TAG_ID) && (elmtLen0 == INDEFINITE_LEN))
{
BDEC_2ND_EOC_OCTET (b, &totalElmtsLen1, env);
break;
}
elmtLen1 = BDecLen (b, &totalElmtsLen1, env);
if (tagId1 == MAKE_TAG_ID (UNIV, PRIM, OCTETSTRING_TAG_CODE))
{
/**//*
* primitive part of string, put references to piece (s) in
* str stack
*/
totalRefdLen = 0;
refdLen = elmtLen1;
while (1)
{
strPtr = (char *)BufGetSeg (b, &refdLen);
PUSH_STR (strPtr, refdLen, env);
totalRefdLen += refdLen;
if (totalRefdLen == elmtLen1)
break; /**//* exit this while loop */
if (refdLen == 0) /**//* end of data */
{
Asn1Error ("BDecConsOctetString: ERROR - attempt to decode past end of data\n");
longjmp (env, -18);
}
refdLen = elmtLen1 - totalRefdLen;
}
totalElmtsLen1 += elmtLen1;
}
else if (tagId1 == MAKE_TAG_ID (UNIV, CONS, OCTETSTRING_TAG_CODE))
{
/**//*
* constructed octets string embedding in this constructed
* octet string. decode it.
*/
FillOctetStringStk (b, elmtLen1, &totalElmtsLen1, env);
}
else /**//* wrong tag */
{
Asn1Error ("BDecConsOctetString: ERROR - decoded non-OCTET STRING tag inside a constructed OCTET STRING\n");
longjmp (env, -19);
}
} /**//* end of for */
(*bytesDecoded) += totalElmtsLen1;
} /**//* FillOctetStringStk */
在FillOctetStringStk中,判断当前串碎片是什么类型,如果是原生类型,就直接分配内存存放缓冲区的内容;如果还是连接类型,那就递归调用本函数,否则报错。
好了,字节串编码解码的分析就到此了。