本文剖析asn-oid.h/c,從源代碼來學習eSNACC對OBJECT IDENTIFIER的編碼和解碼。
在研究代碼之前,我們先來說明什么是OBJECT IDENTIFIER。
————————————以下一段來自于http://www.eccsdk.com/bbs/read.php?tid=1772————————————————
ASN.1 對象標識符類型
對象標識符(OBJECT IDENTIFIER, OID)類型用層次的形式來表示標準規范.標識符樹通過一個點分的十進制符號來定義,這個符號以組織,子部分然后是標準的類型和各自的子標識符開始.
例如:MD5的OID 是 1.2.840.113549.2.5 表示為"iso(1) member-body (2) US (840) rsadsi(113549) digestAlgorithm (2) md5 (5)", 所以當解碼程序看到這個OID時,就知道是MD5散列.
OID在公鑰算法標準中很流行,它指出證書綁定了哪種散列算法. 同樣,也有公鑰算法,分組算法,和操作模式的OID. 它們是一種高效且可移植的表示數據包中所選算法的形式.
對OID的編碼規則:
1、前兩部分如果定義為x.y, 那么它們將合成一個字40*x + y, 其余部分單獨作為一個字節進行編碼.
2、每個字首先被分割為最少數量的沒有頭零數字的7位數字.這些數字以big-endian格式進行組織,并且一個接一個地組合成字節. 除了編碼的最后一個字節外,其他所有字節的最高位(位8)都為1.
舉例: 30331 = 1 * 128^2 + 108 * 128 + 123 分割成7位數字(0x80)后為{1,108,123}設置最高位后變成{129,236,123}.如果該字只有一個7位數字,那么最高為0.
MD5 OID的編碼:
1. 將1.2.840.113549.2.5轉換成字數組 {42, 840, 113549, 2, 5}.
2. 然后將每個字分割為帶有最高位的7位數字,{{0x2A},{0x86,0x48},{0x86,0xF7,0x0D},{0x02},{0x05}}.
3. 最后完整的編碼為 0x06 08 2A 86 48 86 F7 0D 02 05.
————————————————————————————————————————————————————————————
有了上面的直觀的理解,我們再研究代碼就不會困惑了。
在eSNACC中,OBJECT IDENTIFIER實現分為oid和RELATIVE OID。oid要求至少必須由兩部分數字組成,因為要編碼的一個值為40*x + y,所以如果不滿足就會報錯。就如同上面例子中的MD5 OID,就有6個部分。而RELATIVE OID就沒有這個要求,對她的編碼沒有做40*x + y的操作,解碼也不需要逆處理。但是RELATIVE OID必須和一個oid根相關聯。因為RELATIVE OID的定義方式和處理函數都與oid類似,僅僅是少了上面所說的第一個編碼規則操作,所以我們只討論oid,而RELATIVE OID就不展開了。
eSNACC對oid有兩種實現:用字節串存放和用鏈表形式存放,對字節串存放,就是每一個字節存放一個數值;而鏈表,就是每一個節點元素存放一個值。不過文檔說:如果追求更好的性能,應當采用字節串的形式。這兩種方式定義如下:
字節串形式,定義為AsnOid,直接從AsnOcts定義過來:

typedef AsnOcts AsnOid; /**//* standard oid type */
鏈表形式,定義為OID:

/**//* linked oid type that may be easier to use in some circumstances */
#define NULL_OID_ARCNUM -1
typedef struct OID


{
struct OID *next;
long arcNum;
#if COMPILER || TTBL
struct Value *valueRef;
#endif
} OID;
頭文件中還定義了若干函數實現這兩者的互相轉換。
嚴重說明:
AsnOid存放的是已經對原始的OBJECT IDENTIFIER編碼之后的值!比如上面例子的MD5,字節串是2A 86 48 86 F7 0D 02 05。
而OID存的是OBJECT IDENTIFIER的原始值鏈。比如上面例子的MD5,鏈表為1->2->840->113549->2->5。
/**************************************休息一下********************************************
上文我們說過:AsnOid存放的是已經對原始的OBJECT IDENTIFIER編碼之后的值。所以我們當我們要對一個AsnOid進行打印輸出時,就需要進行前面所說編碼算法的逆運算,這個我們可以通過打印例程來驗證一下:

/**//*
* Prints the given OID to the given FILE * in ASN.1 Value Notation.
* Since the internal rep of an OID is 'encoded', this routine
* decodes each individual arc number to print it.
*/
void
PrintAsnOid PARAMS ((f,v, indent),
FILE *f _AND_
AsnOid *v _AND_
unsigned int indent)


{
unsigned int firstArcNum;
unsigned int arcNum;
int i;

fprintf (f,"{");


/**//* un-munge first two arc numbers */
for (arcNum = 0, i=0; (i < (int)(v->octetLen)) && (v->octs[i] & 0x80);i++)
arcNum = (arcNum << 7) + (v->octs[i] & 0x7f);

arcNum = (arcNum << 7) + (v->octs[i] & 0x7f);
i++;
firstArcNum = (unsigned short)(arcNum/40);
if (firstArcNum > 2)
firstArcNum = 2;

fprintf (f,"%u %u", (unsigned int)firstArcNum, arcNum - (firstArcNum * 40));

for (; i < (int)(v->octetLen); )

{
for (arcNum = 0; (i < (int)(v->octetLen)) && (v->octs[i] & 0x80);i++)
arcNum = (arcNum << 7) + (v->octs[i] & 0x7f);

arcNum = (arcNum << 7) + (v->octs[i] & 0x7f);
i++;
fprintf (f," %u", arcNum);
}
fprintf (f,"}");

indent=indent; /**//* referenced to avoid compiler warning. */


} /**//* PrintAsnOid */
從代碼可以看到,這個算法就是這樣的:
首先得到第一個數,因為如果一個編碼后的數用了多個字節表示,那么除了最后一個以外,前面的字節的最高位肯定為1.所以就用一個循環來獲取一個完整的數。后面嵌套的for也是這個原理。然后對取得的第一個數分拆:num=first*40+second。這樣就完成第一步的逆算法并打印出來。
然后遍歷這個字節串,每獲取一個完整的數就打印出來。由于for循環只是把最高位為1的字節遍歷了,所以都需要加上最后一個字節。其實我們發現用多個字節存的數對應前面的字節的128的n次方和最末尾一個字節值的和。
而OID存的是原始值,就讓我們通過這個OID -> AsnOid的函數來進一步理解兩者的不同:

/**//*
* given an oid list and a pre-allocated ENC_OID
* (use EncodedOidLen to figure out byte length needed)
* fills the ENC_OID with a BER encoded version
* of the oid.
*/
void
BuildEncodedOid PARAMS ((oid, result),
OID *oid _AND_
AsnOid *result)


{
unsigned long len;
unsigned long headArcNum;
unsigned long tmpArcNum;
char *buf;
int i;
OID *tmpOid;

buf = result->octs;

/**//*
* oid must have at least 2 elmts
*/
if (oid->next == NULL)
return;

/**//*
* munge together first two arcNum
* note first arcnum must be <= 2
* and second must be < 39 if first = 0 or 1
* see (X.209) for ref to this stupidity
*/
//head = first * 40 + second
headArcNum = (oid->arcNum * 40) + oid->next->arcNum;
tmpArcNum = headArcNum;


/**//*
* 計算存放第一個數需要幾個字節。每7位要一個字節
*/
for (len = 0; (tmpArcNum >>= 7) != 0; len++)
;


/**//*
* 從高位到低位,把每7位寫到緩沖區,因為不是最后一個字節,所以都把最高位設為1
*/
for (i=0; i < (int)len; i++)
*(buf++) = (char)(0x80 | (headArcNum >> ((len-i)*7)));


/**//*
* 將寫第一個數的最后7位寫到最后一個字節
*/
*(buf++) = (char)(0x7f & headArcNum);



/**//*
* 如果有,就把后面的數寫到緩沖區,原理和第一個數相同,里面不再注釋
*/
for (tmpOid = oid->next->next; tmpOid != NULL; tmpOid = tmpOid->next)

{
tmpArcNum = tmpOid->arcNum;
for (len = 0; (tmpArcNum >>= 7) != 0; len++)
;

for (i=0; i < (int)len; i++)
*(buf++) = (char)(0x80 | (tmpOid->arcNum >> ((len-i)*7)));

*(buf++) = (char)(0x7f & tmpOid->arcNum);
}
result->octetLen = (buf - result->octs);//根據被寫的緩沖區設定字節長度

} /**//* BuildEncodedOid */
在上面的函數中,我已經對相應語句做了注釋了,所以這里就不復述了。
文件中其他函數都是這個原理,就很簡單了。本篇就到此吧。