<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> 在BACnet协议栈中,发送方使用WHO-IS服务确定在同一个互联网上其它的BACnet设备的设备对象标识符和网络地址,Who-Is服务是一个无证实服务,这个服务有两种使用情况(1)、确定在网络上的所有设备的对象标识符和网络地址;(2)、确定某个设备标识符但是不知其网络地址的设备的网络地址。</span>
在bacnet的协议栈中,whois.c是 用于编码(encode)/ 解码(decode) WHO-IS 服务请求的文件,这其中有两个主要函数:
1)、int whois_encode_apdu(uint8_t * apdu, int32_t low_limit, int32_t high_limit)
2)、int whois_decode_service_request(uint8_t * apdu, unsigned apdu_len, int32_t * pLow_limit, int32_t * pHigh_limit)
参数说明:apdu:服务请求;low_limit:设备实例低阈值范围;high_limit:设备实例高阈值范围
int whois_encode_apdu(uint8_t * apdu, int32_t low_limit, int32_t high_limit)
{
int len = 0; /* length of each encoding */
int apdu_len = 0; /* total length of the apdu, return value */
if (apdu) {
apdu[0] = PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST; //定义非证实服务请求
apdu[1] = SERVICE_UNCONFIRMED_WHO_IS; /* service choice */ //定义WHO-IS服务
apdu_len = 2;
/* optional limits - must be used as a pair */
if ((low_limit >= 0) && (low_limit <= BACNET_MAX_INSTANCE) &&
(high_limit >= 0) && (high_limit <= BACNET_MAX_INSTANCE)) {
len = encode_context_unsigned(&apdu[apdu_len], 0, low_limit);
apdu_len += len;
len = encode_context_unsigned(&apdu[apdu_len], 1, high_limit);
apdu_len += len;
}
}
return apdu_len;
}
在whois_encode_apdu(...)这个函数中,在apdu[0]中定义了该请求为非证实服务请求,其中PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST的值为16(0x10),这个值并非真正的值,仅仅为了方便编码而设定的。SERVICE_UNCONFIRMED_WHO_IS的值为8,在bacnet协议中的BACnetUnconfimedServiceChoice中,它的枚举值为8。
在函数encode_context_unsigned(...)中,它的具体定义为:
int encode_context_unsigned(uint8_t * apdu, uint8_t tag_number,uint32_t value)
{
int len = 0;
/* length of unsigned is variable, as per 20.2.4 */
if (value < 0x100) { //0x100 = 2^8 = 256
len = 1;
} else if (value < 0x10000) { // 0x10000 = 2^16 = 65536
len = 2;
} else if (value < 0x1000000) { // 0x1000000 = 2^24
len = 3;
} else {
len = 4;
}
len = encode_tag(&apdu[0], tag_number, true, (uint32_t) len); // 标记编码主字节的长度
len += encode_bacnet_unsigned(&apdu[len], value);
return len;
}
在20.2.4中提到:无符号整型数值在内容字节中被编码成一个取值范围为 0 到 ( 2^(8*L) - 1)的二进制数,其中L是用于对这个值编码的字节数(byte),L至少为1。在 endcode_context_unfigned(...) 这个程序中,根据value的取值来确定len(即为L)的取值,例如当value<0x100(256)时,len取值为1。
在确定完 len 的取值之后, endcode_context_unfigned(...)还调用了两个函数,分别是:
encode_tag(&apdu[0], tag_number, true, (uint32_t) len) 和 encode_bacnet_unsigned(&apdu[len], value);
先来看看第一个函数在协议栈中的定义:
1、int encode_tag(uint8_t * apdu, uint8_t tag_number, bool context_specific, uint32_t len_value_type)
2、{
3、 int len = 1; /* return value */
4、
5、 apdu[0] = 0;
6、 if (context_specific)
7、 apdu[0] = BIT3;
8、
9、 /* additional tag byte after this byte */
10、 /* for extended tag byte */
11、 if (tag_number <= 14) {
12、 apdu[0] |= (tag_number << 4);
13、 } else {
14、 apdu[0] |= 0xF0;
15、 apdu[1] = tag_number;
16、 len++;
17、 }
18、
19、 /* NOTE: additional len byte(s) after extended tag byte */
20、 /* if larger than 4 */
21、 if (len_value_type <= 4) {
22、 apdu[0] |= len_value_type;
23、 } else {
24、 apdu[0] |= 5;
25、 if (len_value_type <= 253) {
26、 apdu[len++] = (uint8_t) len_value_type;
27、 }
28、 else if (len_value_type <= 65535) {
29、 apdu[len++] = 254;
30、 len += encode_unsigned16(&apdu[len], (uint16_t) len_value_type);
31、 } else {
32、 apdu[len++] = 255;
33、 len += encode_unsigned32(&apdu[len], len_value_type);
34、 }
35、 }
36、
37、 return len;
38、}
结合20.2.1小节的说明对代码进行理解,根据BACnet编码的一般规则:
可以看到高4位存放Tag Numbers,而class 和 length/value/type存放在低4位,分别占用1个比特位和3个比特位。
在line5的代码中,将BACnet标记编码的主字节初始化为序列00000000,然后line6的代码判断该标记是上下文特定标记还是应用标记,如果是应用标记,则class位的值为0;如果是上下文特定标记,则class的值为1。在BACnet协议栈中BIT3的值为0x08(00001000)。line11到line17是判断标记编号的范围,对范围在[0,14]的标记编号进行获取。由于TagNumber在[0,14]的区间上,对应二进制数范围为0000 0000 ~ 0000 1110,因此,通过移位操作,tag_number << 4 ,左移操作之后,将低4位的值移至高4位,然后与apdu[0](0000 0000 或者 0000 1000 )进行或操作,这样,得到的tag_number就处于主字节的高4位上。
在13~17行的代码段中,对于取值在[15,254]的标记编号,需要将主字节的Tag_Number区域标记为B'1111',再在主字节后紧跟一个扩展字节,用8个比特位用来表示标记编号。也就是说,将apdu[0]设置成0xFF00,apdu[1](扩展字节)表示tag_number,apdu长度增加1(扩展了一个字节)。
在21~35的代码段中,首先判断数据长度。
1)、对于在[0,4]个字节长度的数据,则通过apdu[0]和len_value_type的或运算来设置长度/值/类型域。
2)、而对于长度在[5,253]的数据,将apdu[0]和5(B'00000101')进行或运算,将长度/值/类型域设置成B'0101',而tag_number域保持B'1111'不变。
3)、对于长度在[254,65535]的数据,将长度/值/类型域设置成B'0101'之后,随后应紧跟一个字节并设置成D'254',也就是apdu[len++] = 254;后面再通过调用encode_unsigned16(...)来分配两个字节(16位),并且用这两个字节的16位来表示数据的长度。同理,对于[65535,2^32-1]根据定义也很好理解。
值得注意的是,在encode_unsigned16(...)和encode_unsigned32(...)这两个函数中,用到了&和>>操作,在编码过程中,由于网络通讯协议的字节序一般是大端序,而操作系统一般为小端序,因此需要通过小端转大端的操作编码这个序列。同理,在decode_unsigned??(...)则是大端转小端。
最后,返回的len参数就是该标记编码的长度(byte为单位)。这就是第一个函数encode_tag(...)的作用,返回的len继续在encode_bacnet_unsigned(...)调用。
_______________________________________________________________________________________________________
然后,encode_bacnet_unsigned( uint8_t * apdu, uint32_t value)函数中是根据20.2.4和20.2.1实现的。在20.2.4中定义了无符号整型数值的编码规则,针对无符号整型数值的内容字节(value),被编码成一个取值范围为[0 , 2^(8*L)-1]的二进制数,其中,L是用于对这个值编码的字节数,其值至少为1。encode_bacnet_unsigned返回的就是value的字节数。这样,将这个字节数加上tag的长度,就是该无符号上下文标记编码的长度。返回到whois_encode_apdu(...)函数中。在计算了实例低阈值 和 设备实例高阈值 的apdu长度后,两者相加就是这个apdu的总长度。