本文剖析asn-len.h/c,從源代碼來學習eSNACC對長度的編碼和解碼。
在正式引出源代碼之前,我覺得非常有必要強調幾點非常重要的知識:
1、eSNACC編譯器對數(shù)據(jù)的編碼設計是反序的,也就是先編碼數(shù)據(jù)并寫進緩沖區(qū),以此而知道了編碼好的數(shù)據(jù)長度,然后再將本長度值編碼插到緩沖區(qū)前面。這樣設計的目的是減少性能的損失。而很多其他編譯器是開一個臨時緩沖區(qū)來完成這個工作,這就帶來了性能的損失。詳細的說明,請參加eSNACC文檔。我們要記住的是eSNACC編碼時反序的。
2、eSNACC既支持確定長度編碼也支持不確定長度編碼。原理是:確定長度編碼,那么在數(shù)據(jù)之前的若干字節(jié)來表面后面數(shù)據(jù)的長度;若為不確定長度編碼,那么數(shù)據(jù)前面的長度字節(jié)為0x80,此代表長度不確定,然后在數(shù)據(jù)最后用EOC(End-Of-Contents)來表示數(shù)據(jù)的結束。
3、eSNACC支持BER和DER編碼。不過BER允許不確定長度,但是DER只支持確定長度,所以在他們的編碼解碼函數(shù)上有所不同。
4、如果感覺代碼中的PROTO和PARAMS很陌生,請讀本系列中的《關于老式函數(shù)聲明》一文。
以下具體結合源碼分析。
typedef unsigned long AsnLen;

/**//*
* BER Encoding/Decoding routines
*/

/**//* max unsigned value - used for internal rep of indef len */
#define INDEFINITE_LEN ~0L
以上說明,eSNACC長度用AsnLen類型來定義,而該類型也就是unsigned long。而不確定長度的標記就是INDEFINITE_LEN。
#ifdef USE_INDEF_LEN

#define BEncEocIfNec( b) BEncEoc (b)


/**//*
* include len for EOC (2 must be first due to BEncIndefLen
* - ack! ugly macros!)
*/
#define BEncConsLen( b, len) 2 + BEncIndefLen(b)


#else /* use definite length - faster?/smaller encodings */


/**//* do nothing since only using definite lens */
#define BEncEocIfNec( b)

#define BEncConsLen( b, len) BEncDefLen(b, len)

#endif
以上用ifdef來定義了BER編碼確定長度和不確定長度的2個宏:
BEncEocIfNec代表BER中編碼EOC標記,從代碼可見,若為不確定長度,那么就用BEncEoc來完成;若為確定長度,因為根本就不需要EOC,所以什么都不要做。
BEncConsLen代表BER中編碼內容的長度,代碼說明了,若為不確定長度,出了調用BEncIndefLen來編碼內容長度,因為內容之后還需要2個字節(jié)表示EOC,所以再前面加了2;若為確定長度,因為不需要EOC,所以就直接調用BEncDefLen來完成。

/**//*
* writes indefinite length byte to buffer. 'returns' encoded len (1)
*/
#define BEncIndefLen( b)\
1;\
BufPutByteRvs (b, 0x80);

#ifndef _DEBUG
#define BEncEoc( b)\
2;\
BufPutByteRvs (b, 0);\
BufPutByteRvs (b, 0);
#endif
這一段說明了編碼不確定長度和編碼EOC的兩個宏,我們會發(fā)現(xiàn)這兩個宏很奇怪,因為與我們以前常見的不一樣,我當時看就感覺好像語法錯了一樣。是的,這些宏就是這樣設計的,我們先看看源文件開頭作者寫的說明:
* Warning: many of these routines are MACROs for performance reasons
* - be carful where you use them. Don't use more than one per
* assignment statement -
* (eg itemLen += BEncEoc (b) + BEncFoo (b) ..; this
* will break the code)

/**//*
* include len for EOC (2 must be first due to BEncIndefLen
* - ack! ugly macros!)
*/
看到了,作者深知這些宏的丑陋!也深知這些宏的弊端!他們不能像調用函數(shù)或變量那樣連接使用,否則會破壞代碼!這一切,嗨,都是為了性能,無所不用其極呀!
好吧,引入上面這些就是提醒大家在用這些宏時要高度警惕。下面分析這些宏:
首先,這些宏要編碼長度。其次,他還要返回編碼好的長度的值。就是因為要實現(xiàn)這兩個功能,才被迫寫成這樣:
這兩個宏的第一個分號前的數(shù)值就是返回值。
而后面的就是把編碼的值壓到緩沖區(qū)里面去,實現(xiàn)真正的編碼。
就是這么簡單了。
///***************************************休息一下*************************
為了更好的說明.h文件的后面一點點,下面我們先跳到.c中分析具體的函數(shù)實現(xiàn):

/**//*
* BER encode/decode routines
*/
AsnLen
BEncDefLen PARAMS ((b, len),
GenBuf *b _AND_
AsnLen len)


{

/**//*
* unrolled for efficiency
* check each possibitlity of the 4 byte integer
*/
if (len < 128)

{
BufPutByteRvs (b, (unsigned char)len);
return 1;
}
else if (len < 256)

{
BufPutByteRvs (b, (unsigned char)len);
BufPutByteRvs (b, 0x81);
return 2;
}
else if (len < 65536)

{
BufPutByteRvs (b, (unsigned char)len);
BufPutByteRvs (b, (unsigned char)(len >> 8));
BufPutByteRvs (b, 0x82);
return 3;
}
else if (len < 16777126)

{
BufPutByteRvs (b, (unsigned char)len);
BufPutByteRvs (b, (unsigned char)(len >> 8));
BufPutByteRvs (b, (unsigned char)(len >> 16));
BufPutByteRvs (b, 0x83);
return 4;
}
else

{
BufPutByteRvs (b, (unsigned char)len);
BufPutByteRvs (b, (unsigned char)(len >> 8));
BufPutByteRvs (b, (unsigned char)(len >> 16));
BufPutByteRvs (b, (unsigned char)(len >> 24));
BufPutByteRvs (b, 0x84);
return 5;
}

} /**//* BEncDefLen */
仔細理解,我們發(fā)現(xiàn)本函數(shù)就是做了這樣一件事情:把長度值的有效字節(jié)壓到緩沖區(qū),然后把有效字節(jié)的值壓到緩沖區(qū),最后返回編碼的長度的字節(jié)數(shù)。
而這一切,都是通過BufPutByteRvs完成。之所以這樣,是因為長度值本身是用一個AsnLen(也就是unsigned long)來表示的,這用了4個字節(jié)。如果長度值小,比如小于128,僅僅一個字節(jié)表示就夠了,所以壓縮一下而已。
下面我們看解碼:

/**//*
* decodes and returns an ASN.1 length
*/
AsnLen
BDecLen PARAMS ((b, bytesDecoded, env),
GenBuf *b _AND_
unsigned long *bytesDecoded _AND_
jmp_buf env)


{
AsnLen len;
AsnLen byte;
int lenBytes;

byte = (unsigned long) BufGetByte (b);

if (BufReadError (b))

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

(*bytesDecoded)++;

if (byte < 128) /**//* short length */
return byte;


else if (byte == (AsnLen) 0x080) /**//* indef len indicator */
return (unsigned long)INDEFINITE_LEN;


else /**//* long len form */

{

/**//*
* strip high bit to get # bytes left in len
*/
lenBytes = byte & (AsnLen) 0x7f;

if (lenBytes > sizeof (AsnLen))

{
Asn1Error ("BDecLen: ERROR - length overflow\n");
longjmp (env, -14);
}

(*bytesDecoded) += lenBytes;

for (len = 0; lenBytes > 0; lenBytes--)
len = (len << 8) | (AsnLen) BufGetByte (b);


if (BufReadError (b))

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

return len;
}

/**//* not reached */

} /**//* BDecLen */
首先用BufGetByte從緩沖區(qū)讀取第一個字節(jié),這讀出來的是什么呢?是關于長度嗎?當然是的了!但是細心的你就會說:在前面的編碼函數(shù)BEncDefLen中長度不是最后才被壓進緩沖區(qū)的嗎?哈哈,這個就得想起本文開始提到的第一條:反序編碼。當然實現(xiàn)是在BufPutByteRvs里面,這個以后再講,不過我想我這樣一說,大家也已經(jīng)明白了。是嗎?
然后判斷讀出來的值,如果小于128,那很好,長度就是他了!而如果為0x80,那就是不確定長度了(最上面第2條)。其他的情況,那么這個值就是長度的有效字節(jié)數(shù),這樣就很簡單了,依次讀取解析就可以了。
當然,本函數(shù)內部有兩種情況會進行出錯處理,此處就不展開了。
#ifdef _DEBUG
AsnLen
BEncEoc PARAMS ((b),
GenBuf *b)


{
BufPutByteRvs (b, 0);
BufPutByteRvs (b, 0);
return 2;

} /**//* BEncEoc */
#endif

/**//*
* Decodes an End of Contents (EOC) marker from the given buffer.
* Flags and error if the octets are non-zero or if a read error
* occurs. Increments bytesDecoded by the length of the EOC marker.
*/

void
BDecEoc PARAMS ((b, bytesDecoded, env),
GenBuf *b _AND_
AsnLen *bytesDecoded _AND_
jmp_buf env)


{
if ((BufGetByte (b) != 0) || (BufGetByte (b) != 0) || BufReadError (b))

{
Asn1Error ("BDecEoc: ERROR - non zero byte in EOC or end of data reached\n");
longjmp (env, -16);
}
(*bytesDecoded) += 2;


} /**//* BDecEoc */
上面的代碼說明了對EOC的編碼和解碼實現(xiàn),可以發(fā)現(xiàn)EOC就是以連續(xù)的兩個全0字節(jié)表示的。
實現(xiàn)文件的最后還有這樣一個函數(shù):

/**//*
* decodes and returns a DER encoded ASN.1 length
*/
AsnLen
DDecLen PARAMS ((b, bytesDecoded, env),
GenBuf *b _AND_
unsigned long *bytesDecoded _AND_
jmp_buf env)


{
AsnLen len;
AsnLen byte;
int lenBytes;

byte = (AsnLen) BufGetByte (b);

if (BufReadError (b))

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

(*bytesDecoded)++;

if (byte < 128) /**//* short length */
return byte;


else if (byte == (AsnLen) 0x080)
{/**//* indef len indicator */
Asn1Error("DDecLen: ERROR - Indefinite length decoded");
longjmp(env, -666);
}


else /**//* long len form */

{

/**//*
* strip high bit to get # bytes left in len
*/
lenBytes = byte & (AsnLen) 0x7f;

if (lenBytes > sizeof (AsnLen))

{
Asn1Error ("DDecLen: ERROR - length overflow\n");
longjmp (env, -14);
}

(*bytesDecoded) += lenBytes;

for (len = 0; lenBytes > 0; lenBytes--)
len = (len << 8) | (AsnLen) BufGetByte (b);


if (BufReadError (b))

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

return len;
}

/**//* not reached */

} /**//* DDecLen */
這是對應DER編碼的解碼函數(shù),其實現(xiàn)與BER編碼基本一樣,唯一不同的是因為DER編碼不允許不確定長度,所以如果長度字節(jié)為0x80,那么人家就直接罷工了。
到此,編碼解碼的具體實現(xiàn)都明白了,那.h文件中還剩下什么了呢?除了上面函數(shù)的一些聲明,我們還發(fā)現(xiàn)一樣感興趣的東西:

/**//*
* use if you know the encoded length will be 0 >= len <= 127
* Eg for booleans, nulls, any resonable integers and reals
*
* NOTE: this particular Encode Routine does NOT return the length
* encoded (1).
*/
#define BEncDefLenTo127( b, len)\
BufPutByteRvs (b, (unsigned char) len)
是的,這里有這樣一個宏,在eSNACC文檔中也專門提了。這其實也是為了效率考慮而加的:當你確定長度小于127時,就應當直接調用這個宏,而不要調用前面講的編碼函數(shù)。(其實我們可以看到編碼函數(shù)做的就是同樣的事情,除了多了一些判斷,當然關鍵是省卻函數(shù)調用的過程,又見無所不用其極。)
注意:這個宏只做了編碼,而沒有返回長度(也就是1個字節(jié))!
好了,eSNACC支持BER和DER編碼解碼,他們的一些聲明也都類似,就不在敖述了。記住本文開始提到的第三點,其他代碼也就一目了然了。
到此,asn-len.h/c的剖析勝利完成!