SNMP++ 04-SNMP中OBJECT IDENTIFIER的BER編碼與解碼及一些思考
閱讀完本文你可以學到:
(1)SNMP 中 OBJECT IDENTIFIER 的 BER 編碼與解碼程式碼實現。
(2)在學習 OBJECT IDENTIFIER 編解碼過程中的一些思考(思考過後,懂得當涉及對無符號陣列進行傳輸編碼時,可以給出一個較佳的方案)。
(3)snmp++-3.3.7 版本中函式 asn_parse_objid 存在的 bug。
一、理論知識
1、Tag
OBJECT IDENTIFIER 對應的 Tag 為 0x06,佔用一個八位組。
2、Length
Length有三種形式:定長短格式、定長長格式和變長格式。(SNMP 的所有內建型別的 Length 段都採用定長格式)
定長短格式:採用定長方式,當長度不大於127個八位組時,Length 只在一個八位組中編碼。此時,bit0~bit6 表示實際長度,最高位 bit7 為0(用於區分其他兩種格式)。
定長長格式:採用定長方式,當長度大於127個八位組時,Length 在多個八位組中編碼,此時第一個八位組低七位(bit0~bit6)表示的是 Length 所佔的長度,第一個八位組的最高位 bit7 為1,第一個八位組的低七位不能全為 0(用於區分其他兩種格式)。
變長格式:採用變長方式,Length 所在八位組固定編碼為 0x80,但在 Value 編碼結束後以兩個 0x00 結尾。這種方式使得可以在編碼沒有完全結束的情況下,可以先發送部分訊息給對方。
3、Value
所有的子識別符號(OID 點分十進位制中的一位數值)連續編碼。(筆者注:記憶體大小對應的 OID 為 .1.3.6.1.2.1.25.2.2.0,其子識別符號自頂到下依次為 1,3,6,1,2,1,25,2,2,0。)
每個子識別符號由一個或多個位元組組成。子識別符號是由一個位元組還是多個位元組表示的區分規則是:每個位元組最高有效位 bit7 表徵了該子識別符號是否為編碼後的最後一個位元組。bit7 為 0 表示這個位元組是 OID 中最後一個編碼位元組;bit7 為 1 時表示該位元組不是該子識別符號最後一個編碼(OID沒有負值)。正因如此,對於數值大於 127 的子識別符號必然要使用多於一個位元組的編碼。如對於數值為 128(0x80 = 1000 0000B)的子識別符號,就應該使用兩個位元組編碼:1000 0001 0000 0000,值為 81 00。
子識別符號編碼應儘可能地使用位元組(也就少一個位元組),規定 OID 的第一個子識別符號和第二個子識別符號經過運算組合後編碼為一個位元組。該運演算法則為:(X * 40)+ Y = Z。
其中 X 表示第一個子識別符號,Y 為第二個子識別符號。X 的取值為(0,1,2)。另外之所以使用一個“magic number” 為 40,是因為首個子識別符號為 0 或 1 的 OID 的第二個子識別符號最大的是 39(這種不見得有多優雅的解決方案也確實減少了一個位元組的編碼等)。這就使得具有 N 個子識別符號的 OID,第 i 個(2 <= i <= N - 1)編碼值對應 OID 中的第(i + 1)個子識別符號。正是由於上述方程的約束條件,使得方程的結果也是唯一的,即當 Z 為表5-3總第一列所列出的值時,就可以推匯出前兩個子 OID 的值。
表5-3 OID TLV示例(16 進位制)
Z | X | Y |
0 <= Z <= 39 | 0 | Z |
40 <= Z <= 79 | 1 | Z - 40 |
Z >= 80 | 2 | Z - 80 |
二、思考——設計一個包格式,滿足“傳送端將一個無符號 int 陣列中的內容傳送到接收端,接收端接收並列印”?
最簡單地方案可能是: 其他資訊 + 整數1 + 整數2 + . . . + 整數 i + . . . + 整數 n。(其中,“其他資訊”在這裡的討論中並不涉及。在這種方案中每個整數 i 佔 4 個位元組)。
現在增加需求:儘可能地使用位元組。
思路之一:對每個無符號整數進行壓縮,即對每個無符號整數儘可能地只傳輸其有效位。比如,無符號整數 1 (0x00000001)在原來的傳輸中要佔 4 個位元組,現在通過一種規則使得只傳輸一個位元組 0x01。
這種思路是可以減少傳輸位元組的,但同時會帶來“不同無符號整數的有效位可能佔用不同的位元組數”的問題。此時,我們不得不提供額外的資訊對無符號整型陣列進行劃分。
額外的資訊是指什麼?
方案1(提供額外資訊的一種嘗試方式,暫不考慮是否可行)為:在每個無符號整數(只含有效位)的後面插入一個特殊標識(至少佔一個位元組 ),以區分不同的整數。同時,我們需要對與該特殊標識相同的的無符號整數進行“字元填充”類似的工作,以保證透明傳輸(我們先不考慮該工作的繁瑣度)。
假如方案1可行,那麼額外資訊至少要使用一個位元組,這個位元組指的就是特殊標識。那麼,是否存在更佳方案呢?
是的,看看 SNMP 中是如何對 OID 進行編解碼的(見一、理論知識)。不要驚訝,你完全可以把 OID 看作是一個一維無符號整型陣列:每個子識別符號都不會是負數,相鄰子識別符號就像陣列中的相鄰元素。
SNMP 中對 OID 的編碼規則可使得:額外資訊最多使用一個位元組。為什麼呢?見表 2-1 。
表 2-1
無符號整數 x | 採用 OID 編碼規則後佔用的八位組個數 | 額外資訊佔用的八位組個數 |
0 <= x < 27 | 1 | 0 |
x = 27 | 2 | 1 |
27 < x < 214 | 2 | 0 |
x = 214 | 3 | 1 |
. . . | . . . | . . . |
unsigned char *asn_parse_objid(unsigned char *data,
int *datalength,
unsigned char *type,
oid *objid,
int *objidlength);
bug 描述:函式 asn_parse_objid 實現中未對 *objidlength 表徵的 objid 緩衝大小是否足夠進行判斷。當 *objidlength 為0 時,直接取用 objid[0],會產生陣列訪問越界;當 *objidlength 為1 時,直接取用 objid[1],也會產生陣列訪問越界。 四、程式碼實現
/************************************************************************/
/* asn1.h */
/************************************************************************/
#pragma once
typedef unsigned char u_char;
typedef unsigned long u_long;
typedef u_long oid;
#define MAX_OID_LEN 128
#define MAX_SUBID 0xFFFFFFFF
static u_char* _asn_build_header(u_char *data, size_t *datalength, u_char type, size_t length);
static const u_char* _asn_parse_header(const u_char *data, size_t *datalength, u_char *type, size_t *length);
static u_char* _asn_build_length(u_char *data, size_t *datalength, size_t length);
static const u_char* _asn_parse_length(const u_char *data, size_t *datalength, size_t *length);
u_char* _asn_build_objid(u_char *data, size_t *datalength, u_char type, oid *objid, const size_t objidlength);
const u_char* _asn_parse_objid(const u_char *data, size_t *datalength, u_char *type, oid *objid, size_t *objidlength);
/************************************************************************/
/* asn1.cpp */
/************************************************************************/
#include "stdafx.h"
#include "asn1.h"
u_char* _asn_build_header(u_char *data, size_t *datalength, u_char type, size_t length)
{
if (nullptr == data || nullptr == datalength)
return nullptr;
if (*datalength < 1)
return nullptr;
*data++ = type;
--(*datalength);
return _asn_build_length(data, datalength, length);
}
const u_char* _asn_parse_header(const u_char *data, size_t *datalength, u_char *type, size_t *length)
{
if (nullptr == data || nullptr == datalength || nullptr == type || nullptr == length)
return nullptr;
if (*datalength < 1)
return nullptr;
*type = *data++;
--(*datalength);
return _asn_parse_length(data, datalength, length);
}
/* 支援的最大長度為65535位元組 */
u_char* _asn_build_length(u_char *data, size_t *datalength, size_t length)
{
if (nullptr == data || nullptr == datalength)
return nullptr;
const u_char *initdatap = data;
if (length < 0x80)
{ /* 定長短格式 */
if (*datalength < 1)
return nullptr;
*data++ = (u_char)length;
}
else if (length <= 0xFF)
{ /* 定長長格式,長度佔用一個八位組 */
if (*datalength < 2)
return nullptr;
*data++ = (u_char)0x81; /* Length 段的第一個八位組為 10000001,對應的十六進位制即為 0x81 */
*data++ = (u_char)length; /* 將 length 的低 bit0~bit7 賦值給 *data,並使 data 指向 data 的下一個八位組 */
}
else if (length <= 0xFFFF)
{ /* 定長長格式,長度佔用兩個八位組 */
if (*datalength < 3)
return nullptr;
*data++ = (u_char)0x82; /* Length 段的第一個八位組為 10000010,對應的十六進位制即為 0x82 */
*data++ = (u_char)(length >> 8); /* 將 length 的低 bit8~bit15 賦值給 *data,並使 data 指向 data 的下一個八位組 */
*data++ = (u_char)length; /* 將 length 的低 bit0~bit7 賦值給 *data,並使 data 指向 data 的下一個八位組 */
}
else
{ /* 長度太長,不支援 */
return nullptr;
}
*datalength -= (data - initdatap);
return data;
}
const u_char* _asn_parse_length(const u_char *data, size_t *datalength, size_t *length)
{
if (nullptr == data || nullptr == datalength || nullptr == length)
return nullptr;
const u_char *initdatap = data;
u_char lengthbyte = *data++;
if (lengthbyte < 0x80)
{ /* 定長短格式 */
*length = lengthbyte;
}
else if (lengthbyte == 0x80)
{ /* 0x80 為變長格式,不支援 */
return nullptr;
}
else
{ /* 定長長格式 */
size_t bytes = (size_t)(lengthbyte - 0x80); /* 計算 Length 段佔用的八位組個數 */
if (bytes > sizeof(*length))
return nullptr; /* Length 段太長 */
*length = 0; /* 消除 *length 的初值可能對 *length 最終結果帶來的影響 */
while (bytes--)
{
*length <<= 8;
*length |= (*data++);
}
}
*datalength -= (data - initdatap);
return data;
}
u_char* _asn_build_objid(u_char *data, size_t *datalength, u_char type, oid *objid, const size_t objidlength)
{
if (nullptr == data || nullptr == datalength || nullptr == objid)
return nullptr;
u_long first_objid_val;
if (objidlength == 0)
first_objid_val = 0;
else if (objid[0] > 2)
return nullptr; /* bad first sub_identifier */
else if (objidlength == 1)
first_objid_val = objid[0] * 40;
else
{
/* second sub_identifier <= 39 when first sub_identifier is 0 or 1 */
if (objid[1] >= 40 && objid[0] < 2)
return nullptr; /* bad second sub_identifier */
first_objid_val = objid[0] * 40 + objid[1];
}
u_char objid_size[MAX_OID_LEN];
u_long subid = first_objid_val;
size_t asn_length = 0;
if (objidlength > sizeof(objid_size) / sizeof(*objid_size))
return nullptr;
for (size_t i = 1;;)
{
if (subid < (u_long)0x80)
objid_size[i] = 1, asn_length += 1;
else if (subid < (u_long)0x4000)
objid_size[i] = 2, asn_length += 2;
else if (subid < (u_long)0x200000)
objid_size[i] = 3, asn_length += 3;
else if (subid < (u_long)0x10000000)
objid_size[i] = 4, asn_length += 4;
else if (subid <= (u_long)0xffffffff)
objid_size[i] = 5, asn_length += 5;
else
return nullptr; /* sub_identifier too long */
if (++i >= objidlength)
break;
subid = objid[i];
}
if ((data = _asn_build_header(data, datalength, type, asn_length)) == nullptr)
return nullptr;
if (*datalength < asn_length)
return nullptr;
oid *op = objid + 2;
for (size_t i = 1, subid = first_objid_val; i < objidlength; ++i)
{
if (i != 1)
subid = *op++;
switch (objid_size[i])
{
case 1:
*data++ = (u_char)subid;
break;
case 2:
*data++ = (u_char)(subid >> 7 | 0x80);
*data++ = (u_char)(subid & 0x07f);
break;
case 3:
*data++ = (u_char)(subid >> 14 | 0x80);
*data++ = (u_char)((subid >> 7 & 0x07f) | 0x80);
*data++ = (u_char)(subid & 0x07f);
break;
case 4:
*data++ = (u_char)(subid >> 21 | 0x80);
*data++ = (u_char)((subid >> 14 & 0x07f) | 0x80);
*data++ = (u_char)((subid >> 7 & 0x07f) | 0x80);
*data++ = (u_char)(subid & 0x07f);
break;
case 5:
*data++ = (u_char)(subid >> 28 | 0x80);
*data++ = (u_char)((subid >> 21 & 0x07f) | 0x80);
*data++ = (u_char)((subid >> 14 & 0x07f) | 0x80);
*data++ = (u_char)((subid >> 7 & 0x07f) | 0x80);
*data++ = (u_char)(subid & 0x07f);
break;
default:
return nullptr;
}
}
*datalength -= asn_length;
return data;
}
const u_char* _asn_parse_objid(const u_char *data, size_t *datalength, u_char *type, oid *objid, size_t *objidlength)
{
if (nullptr == data || nullptr == datalength || nullptr == type || nullptr == objid || nullptr == objidlength)
return nullptr;
const u_char *initdatap = data;
oid *oidp = objid + 1;
size_t asn_length;
if ((data = _asn_parse_header(data, datalength, type, &asn_length)) == nullptr)
return nullptr;
if (*type != 0x06)
return nullptr; /* Wrong Type. Not an oid */
if (asn_length > *datalength)
return nullptr; /* data overflow */
/* Handle invalid object identifier encodings of the form 06 00 robustly */
if (asn_length == 0)
{
if (*objidlength < 2)
return nullptr;
objid[0] = objid[1] = 0;
}
u_long subid;
for (size_t i = 1; asn_length > 0; ++i)
{
subid = 0;
do
{
subid <<= 7;
subid |= (u_long)(*data & 0x07f);
--asn_length;
} while ((*data++ & 0x80) && asn_length > 0);
if (subid > MAX_SUBID)
return nullptr; /* sub_identifier too long */
if (i >= *objidlength)
return nullptr; /* objid overflow */
*oidp++ = (oid)subid;
}
if (*objidlength < 2)
return nullptr; /* objid overflow */
if (objid[1] < 40)
objid[0] = 0;
else if (objid[1] < 80)
objid[0] = 1, objid[1] -= 40;
else
objid[0] = 2, objid[1] -= 80;
*objidlength = (size_t)(oidp - objid);
*datalength -= (data - initdatap);
return data;
}
// snmp_get.cpp : 定義控制檯應用程式的入口點。
//
#include "stdafx.h"
#include <stdio.h>
#include <iostream>
using namespace std;
#include <string.h>
#include "asn1.h"
void print(const u_char *data, size_t datalength)
{
if (nullptr == data || datalength < 1)
return;
for (size_t i = 0; i < datalength; ++i)
{
printf("0x%.2X ", data[i]);
}
printf("\n\n");
}
int _tmain(int argc, _TCHAR* argv[])
{
u_char buf[100];
size_t validlen = sizeof(buf);
u_long oid[] = {1,3,6,1,4,1,1048576,110,9,10012340}/*{1, 3, 6, 1, 2, 1, 25, 2, 2, 0}*/;
if (_asn_build_objid(buf, &validlen, 0x06, oid, sizeof(oid) / sizeof(*oid)) == nullptr)
return -1;
print(buf, sizeof(buf)-validlen);
u_long oid2[20];
size_t oidlength = sizeof(oid2) / sizeof(*oid2);
u_char type;
validlen = sizeof(buf)-validlen;
if (_asn_parse_objid(buf, &validlen, &type, oid2, &oidlength) == nullptr)
{
cout << "_asn_parse_objid error" << endl;
return -1;
}
for (size_t i = 0; i < oidlength; ++i)
{
cout << oid2[i] << " ";
}
cout << endl;
cout << "oidlength:" << oidlength << endl;
return 0;
}
注:“一、理論知識 3. Value”中的內容引自《深入理解Net-Snmp》(張春強著)。