本文剖析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中,判斷當前串碎片是什么類型,如果是原生類型,就直接分配內存存放緩沖區的內容;如果還是連接類型,那就遞歸調用本函數,否則報錯。
好了,字節串編碼解碼的分析就到此了。