本文剖析asn-bits.h/c,從源代碼來學習eSNACC對BIT STRING的編碼和解碼。
比特字符串的編碼和解碼比較復雜,我們來仔細分析一下代碼吧。
eSNACC用一個結構體來表示BIT STRING,定義如下:
typedef struct AsnBits


{
int bitLen;//bit位總長度
char *bits;
} AsnBits;
這兩個參數分別是:
bitlen代表這個比特串的bit位的總長度,注意是bit位,不是字節數!
bits用來存放比特串;要注意這是一個bit串,不是字符串,也就是說中間可以有0.這不是一個要求以null結尾的字符串!
在頭文件中其他的就是BER、DER的編碼解碼的聲明,還有一些幫助函數的聲明和宏定義。
來研究一下主要實現:
先看最外層編碼和解碼函數:

/**//*
* encodes universal TAG LENGTH and Contents of and ASN.1 BIT STRING
*/
AsnLen
BEncAsnBits PARAMS ((b, data),
GenBuf *b _AND_
AsnBits *data)


{
AsnLen len;

len = BEncAsnBitsContent (b, data);
len += BEncDefLen (b, len);
len += BEncTag1 (b, UNIV, PRIM, BITSTRING_TAG_CODE);
return len;

} /**//* BEncAsnInt */



/**//*
* decodes universal TAG LENGTH and Contents of and ASN.1 BIT STRING
*/
void
BDecAsnBits PARAMS ((b, result, bytesDecoded, env),
GenBuf *b _AND_
AsnBits *result _AND_
AsnLen *bytesDecoded _AND_
jmp_buf env)


{
AsnTag tag;
AsnLen elmtLen;

if (((tag =BDecTag (b, bytesDecoded, env)) !=
MAKE_TAG_ID (UNIV, PRIM, BITSTRING_TAG_CODE)) &&
(tag != MAKE_TAG_ID (UNIV, CONS, BITSTRING_TAG_CODE)))

{
Asn1Error ("BDecAsnBits: ERROR - wrong tag on BIT STRING.\n");
longjmp (env, -40);
}

elmtLen = BDecLen (b, bytesDecoded, env);
BDecAsnBitsContent (b, tag, elmtLen, result, bytesDecoded, env);


} /**//* BDecAsnBits */
我們發現在BEncAsnBits中編碼時對比特串的標簽只可能是UNIV-PRIM-BITSTRING_TAG_CODE,但是解碼時卻支持兩種標簽:UNIV-PRIM-BITSTRING_TAG_CODE和UNIV-CONS-BITSTRING_TAG_CODE。
第一個UNIV-PRIM-BITSTRING_TAG_CODE就是原生的比特串,而第二個UNIV-CONS-BITSTRING_TAG_CODE是對應多個原生或者連接型比特串構造而成的比特串(嵌套)。這種數據是在什么時候編碼形成的就留到以后的文章來研究了。反正在當前這對文件的編碼中肯定不會產生。
我們看一下真正編碼比特串內容的函數:

/**//*
* Encodes the BIT STRING value (including the unused bits
* byte) to the given buffer.
*/
AsnLen
BEncAsnBitsContent PARAMS ((b, bits),
GenBuf *b _AND_
AsnBits *bits)


{
unsigned long unusedBits;
unsigned long byteLen;
int i = 0;

/**//* Check for a dumb special case */
for (i=0; i <bits->bitLen/8 + 1; i++)

{
if (bits->bits[i] != 0)
break;
}
if (i == bits->bitLen/8 + 1)

{
bits->bitLen = 1;
unusedBits = 7;
}


/**//* Work out number of unused bits */
unusedBits = (bits->bitLen % 8);
if (unusedBits != 0)
unusedBits = 8 - unusedBits;


/**//* Work out number of bytes */

if (bits->bitLen == 0)
{
byteLen = 0;
}

else
{
byteLen = ((bits->bitLen-1) / 8) + 1;

/**//* Ensure last byte is zero padded */

if (unusedBits)
{
//此處為什么只在字節長度為1時才做這個處理呢?
if ((byteLen == 1) && (bits->bits[0] != 0))

{
bits->bits[byteLen-1] = (char)(bits->bits[byteLen-1] &
(0xff << unusedBits));
}
}
}

BufPutSegRvs (b, bits->bits, byteLen);

/**//* check for special DER encoding rules to return 03 01 00 not
03 02 07 00 RWC */
if ( ((bits->bits[0] != 0) || (byteLen > 1))
&& (unusedBits != 7) )

{
BufPutByteRvs (b, (unsigned char)unusedBits);
return byteLen + 1;
}
else
return byteLen;//如果未用的位的數目為7,并且長度大于1,或者第一字節不為0,就不在填充了,這是為什么呢?這不就和解碼時相沖突了嗎?


} /**//* BEncAsnBitsContent */
我對這個函數還存在幾個問題,就如同在上面注釋中寫的。
首先我們看到他判斷要編碼的串是不是就是一個空串,如果是空串,就把長度設為1,未使用字節數設為7。然后計算了一下將位長度轉為字節(8位)時會產生的未使用的字節數。
接著就是實戰了,根據bit長度來取得要保存這些bit需要的字節數:byteLen = ((bits->bitLen-1) / 8) + 1;然后如果根據前面計算的如果有未使用的字節數,就要用0填充。但是這里不知道他為什么要把這步操作放到if中:只在字節長度為1時才做這個處理!?除非就是外部傳進來的bits->bits本來就是用0填充好了的,所以不需要修正。但是如果是填充好了的,那對1個字節長度的也不需要做這個操作了。
另外就是,在填充無效位數時,為什么是這樣一個條件?因為在解碼函數(下面分析)中,都始終會減去代表這個未用位字節。
我們來看看解碼函數

/**//*
* Decodes the content of a BIT STRING (including the unused bits octet)
* Always returns a single contiguous bit string
*/
void
BDecAsnBitsContent PARAMS ((b, tagId, len, result, bytesDecoded, env),
GenBuf *b _AND_
AsnTag tagId _AND_
AsnLen len _AND_
AsnBits *result _AND_
AsnLen *bytesDecoded _AND_
jmp_buf env)


{

/**//*
* tagId is encoded tag shifted into long int.
* if CONS bit is set then constructed bit string
*/
if (TAG_IS_CONS (tagId))
BDecConsAsnBits (b, len, result, bytesDecoded, env);

else /**//* primitive octet string */

{
if (len == INDEFINITE_LEN)

{
Asn1Error ("BDecAsnBitsContent: ERROR - indefinite length on primitive\n");
longjmp (env, -65);
}
(*bytesDecoded) += len;
len--;//減去代表未用位的那個字節
result->bitLen = (len * 8) - (unsigned int)BufGetByte (b);//得到有效位。
result->bits = Asn1Alloc (len);
CheckAsn1Alloc (result->bits, env);
BufCopy (result->bits, b, len);
if (BufReadError (b))

{
Asn1Error ("BDecAsnBitsContent: ERROR - decoded past end of data\n");
longjmp (env, -4);
}
}

} /**//* BDecAsnBitsContent */
他通過標簽走了兩條分支,對應原生比特串,解碼過程如下:
必須定義確定的長度,否則報錯。然后數據指針先加上指定長度。
注意此時先將len--!為什么呢?這是對應這個len還包含了存放那個說明未使用位的值的1個字節,所以len先減去這個。而下一句就是用BufGetByte獲取那個字節,從而得到未使用的bit位的數目。所以用字節數len*8減去這個值就是全部有用的bit位的數了。而后面的邏輯就很清楚了:分配一個len個字節的空間并且把bit串拷貝進去。
eSNACC對應ConsAsnBits解碼的兩個函數就不深入分析了,其原理構造了一個結構體,里面包含一些長度信息等,和一張指針表,表中有128個指針,用于指向比特串碎片,所以先對分散的比特串分別解析,最后分配一整塊大內存,再把指針指向的碎片的中內容拷貝過來。詳細代碼剖析可以參見eSNACC對OCTET STRING 的編碼和解碼。
文件中其他的一些幫助函數理解起來都比較簡單,但是似乎發現AsnBitsEquiv中存在一個bug:

/**//*
* Returns TRUE if the given BIT STRINGs are identical.
* Otherwise returns FALSE.
*/
int
AsnBitsEquiv PARAMS ((b1, b2),
AsnBits *b1 _AND_
AsnBits *b2)


{
int octetsLessOne;
int unusedBits;

if ((b1->bitLen == 0) && (b2->bitLen == 0))
return TRUE;

octetsLessOne = (b1->bitLen-1)/8;//字節長度減一
unusedBits = b1->bitLen % 8;
if (unusedBits != 0)
unusedBits = 8 - unusedBits;


/**//* trailing bits may not be significant */
//此處應該是一個bug。。

/**//*return b1->bitLen == b2->bitLen && !memcmpeq (b1->bits, b2->bits, octetsLessOne) &&
((b1->bits[octetsLessOne] & (0xFF << unusedBits)) == (b1->bits[octetsLessOne] & (0xFF << unusedBits)));*/
return b1->bitLen == b2->bitLen && !memcmpeq (b1->bits, b2->bits, octetsLessOne) &&
((b1->bits[octetsLessOne] & (0xFF << unusedBits)) == (b2->bits[octetsLessOne] & (0xFF << unusedBits)));


} /**//* AsnBitsEquiv */
bug就是函數最后這句話,原來那句我就是被注釋的那句,他目的是比較:1、兩個比特串長度要相等;2、有效bit位長度字節數減1的串要相同(也就是不考慮后面可能需要補齊的字節);3、檢查可能需要被補齊的那個字節的對應有效位是否相等。很明顯,應該是b1和b2的,所以修正在注釋下面。
分析到此,發現比特串的編碼解碼模塊存在bug,似乎還有上面說的設計問題,比較困惑:是不是作者在寫這個模塊時處在XXX時期?呵呵~