BACnet WHO-IS服务协议栈代码分析(1)------whois_encode_apdu(....)函数

<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的总长度。

    

      

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值