LV02-01-天猫精灵IOT-07-SmartConfig-05-多播配网代码分析

本文主要是天猫精灵IOT Smart config—— 多播配网代码分析相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows版本 windows11
Ubuntu版本 Ubuntu22.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
点击查看本文参考资料
分类 网址 说明
官方网站 阿里云 阿里云官网主页
阿里生活物联平台 生活物联网平台(飞燕平台)主页
AliGenie 天猫精灵开放平台AliGenie主页
阿里物联网平台 阿里物联网平台主页
Bluetooth 技术网站 蓝牙协议规范什么的可以来这里找
Telink Telink | Chips for a Smarter IoT (telink-semi.com)
Telink中文官网
开发手册 AliOS Things开发指南 AliOS Things开发指南,这里是最新版本,可以直接从官网找到
AliOS Things开发指南 AliOS Things应用开发指南,这里应该是3.3版本的完整开发文档
AliOS Things开发指南(3.0) AliOS Things应用开发指南,这里应该是3.0版本的完整开发文档
生活物联网平台开发文档 生活物联网平台(飞燕平台)开发文档
《设备端开发指南》
Wi-Fi IoT品类定义与功能开发 天猫精灵IoT开放平台——Wi-Fi IoT品类定义与功能开发
硬件平台 mk3080 WiFi开发板 WiFi开发板使用指南-阿里云开发者社区
esp8266开发板 一个教程:ESP8266-NodeMCU开发板详解-太极创客 (taichi-maker.com)
TLSR8258 Datasheet Datasheet for Telink BLE + IEEE802.15.4 MultiStandard Wireless SoC TLSR8258
参考资料 AliOS Things 3.0 应用开发指南 这个只是一篇参考文章,里面是一些环境搭建相关的,可以参考
IP知识百科 - 华为 (huawei.com) IP的一些相关知识点
点击查看相关文件下载
分类 网址 说明
蓝牙规范相关文档 Core Specification 5.2 核心规格 5.2,该规范定义了创建可互操作的Bluetooth 设备所需的技术。
《Core_v5.2.pdf》
Mesh Model(v1.1) 本Bluetooth 规范定义了模型(以及它们所需的状态和消息),这些模型用于在mesh 网络中的节点上执行基本功能,超出了Bluetooth Mesh 配置文件 规范中定义的基础模型。
本规范包括定义跨设备类型标准功能的通用模型,以及支持关键mesh 场景的模型,如照明控制、传感器、时间和场景。
《MshMDL_v1.1.pdf》
Mesh Profile(v1.0.1) 该Bluetooth 规范定义了基本要求,以实现可互操作的mesh 网络解决方案,用于Bluetooth 低能量无线技术。
《MshPRFv1.0.1.pdf》
Mesh Device Properties 本规范包含Bluetooth Mesh 配置文件 和Bluetooth Mesh 模型规范所要求的设备属性的定义。
但是跟之前的有些区别,我主要看的之前的版本:《MMeshDeviceProperties_v1.2.pdf》
GATT Specification Supplement GATT Specification Supplement | Bluetooth® Technology Website。
好像可以在线看:《GATT Specification Supplement》
Assigned Numbers GATT的一些类型定义可以在这里找。
AliOS Things alios-things/AliOS-Things Gitee上的AliOSThings SDK源码仓库
alibaba/AliOS-Things GitHub上的AliOSThings SDK源码仓库
天猫精灵蓝牙Mesh协议栈 alibaba-archive/genie-bt-mesh-stack GitHub上的天猫精灵蓝牙Mesh协议栈源码仓库。
之前是在alibaba/genie-bt-mesh-stack这个仓库。
写笔记的时候最新提交为faf523618a6a2560090fc423222b9db80984bb7a
蓝牙Mesh设备开发指南 阿里云生活服务平台开发手册——蓝牙设备开发一节中的内容

一、多播配网代码在哪?

多播配网的相关代码在这里:

1
2
3
4
5
6
7
8
9
hk@vm:~/AliOS-Things-SDK/components/linkkit/wifi_provision$ tree -L 2
.
├── # ......
├── frameworks
├── # ......
├── mcast_smartconfig
│   └── mcast_smartconfig.c

15 directories, 55 files

为什么文件这么少?其实它有大部分的代码和零配是一样的,比如配网开始、配网初始化等,有一部分都是共用的,其实主要区别是在处理报文的过程,其他的大部分过程都是一样的。

二、多播报文解析

1. 多播报文在哪解析?

1.1 函数调用关系

1

1.1 ieee80211_data_extract()函数

我们看这个ieee80211_data_extract()函数,其实前面有提到过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
* ieee80211_data_extratct - extract 80211 frame info
*
* @in: [IN] 80211 frame
* @len: [IN] 80211 frame len
* @link_type: [IN] link type @see enum AWS_LINK_TYPE
* @res: [OUT] 80211 frame parser result, see struct parser_res.
*
* @warning: encry_type may collision with aes & tpip in some cases,
* then encry_type will be set to INVALID.
* @Return:
* @see enum ALINK_TYPE
*
* @Note: howto deal with radio RSSI signal
*/
int ieee80211_data_extract(uint8_t *in, int len, int link_type, struct parser_res *res, signed char rssi)
{
struct ieee80211_hdr *hdr;
int alink_type = ALINK_INVALID;
int pkt_type = PKG_INVALID;
int i, fc;

hdr = (struct ieee80211_hdr *)zconfig_remove_link_header(&in, &len, link_type);
if (len <= 0)
goto drop;
fc = hdr->frame_control;

for (i = 0; i < sizeof(awss_protocol_couple_array) / sizeof(awss_protocol_couple_array[0]); i ++) {
awss_protocol_process_func_type protocol_func = awss_protocol_couple_array[i].awss_protocol_process_func;
if (protocol_func == NULL)
continue;
alink_type = protocol_func((uint8_t *)hdr, len, link_type, res, rssi);
if (alink_type != ALINK_INVALID)
break;
}

if (alink_type == ALINK_INVALID)
goto drop;

if (alink_type != ALINK_HT_CTRL) {
/* convert IEEE 802.11 header + possible LLC headers into Ethernet header
* IEEE 802.11 address fields:
* ToDS FromDS Addr1 Addr2 Addr3 Addr4
* 0 0 DA SA BSSID n/a
* 0 1 DA BSSID SA n/a
* 1 0 BSSID SA DA n/a
* 1 1 RA TA DA SA
*/
res->src = ieee80211_get_SA(hdr);
res->dst = ieee80211_get_DA(hdr);
res->bssid = ieee80211_get_BSSID(hdr);
res->tods = ieee80211_has_tods(fc);
}

do {
awss_protocol_finish_func_type finish_func = awss_protocol_couple_array[i].awss_protocol_finish_func;
if (finish_func)
pkt_type = finish_func(res);
} while(0);

drop:
return pkt_type;
}

可以看到这里主要是调用了两个函数指针指向的函数:

1
2
awss_protocol_couple_array[i].awss_protocol_process_func
awss_protocol_couple_array[i].awss_protocol_finish_func

1.2 awss_protocol_couple_array[]数组

这个数组定义在:AOS_SDK_PATH/components/linkkit/wifi_provision/frameworks/ieee80211/zconfig_ieee80211.c中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct awss_protocol_couple_type awss_protocol_couple_array[] = {
#ifdef AWSS_SUPPORT_HT40
{ALINK_HT_CTRL, awss_ieee80211_ht_ctrl_process, awss_recv_callback_ht_ctrl},
#endif
#ifdef AWSS_SUPPORT_APLIST
{ALINK_APLIST, awss_ieee80211_aplist_process, NULL},
#endif
#ifdef AWSS_SUPPORT_AHA
{ALINK_DEFAULT_SSID, awss_ieee80211_aha_process, awss_recv_callback_aha_ssid},
#endif
#ifndef AWSS_DISABLE_ENROLLEE
{ALINK_ZERO_CONFIG, awss_ieee80211_zconfig_process, awss_recv_callback_zconfig},
#endif
#ifdef AWSS_SUPPORT_SMARTCONFIG_WPS
{ALINK_WPS, awss_ieee80211_wps_process, awss_recv_callback_wps},
#endif
#ifdef AWSS_SUPPORT_SMARTCONFIG
{ALINK_BROADCAST, awss_ieee80211_smartconfig_process, awss_recv_callback_smartconfig},
#endif
#ifdef AWSS_SUPPORT_SMARTCONFIG_MCAST
{ALINK_BROADCAST, awss_ieee80211_mcast_smartconfig_process, awss_recv_callback_mcast_smartconfig},
#endif
#ifdef AWSS_SUPPORT_DISCOVER
{ALINK_BROADCAST, aws_discover_callback, NULL},
#endif
};

我们这里分析广播配网的代码,所以这里主要是看这两个函数:

1
2
3
#ifdef AWSS_SUPPORT_SMARTCONFIG
{ALINK_BROADCAST, awss_ieee80211_smartconfig_process, awss_recv_callback_smartconfig},
#endif

2. awss_ieee80211_mcast_smartconfig_process()函数

awss_ieee80211_mcast_smartconfig_process() 函数定义在AOS_SDK_PATH/components/linkkit/wifi_provision/mcast_smartconfig/mcast_smartconfig.c中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
int awss_ieee80211_mcast_smartconfig_process(uint8_t *ieee80211, int len, int link_type, struct parser_res *res,
signed char rssi)
{
int hdrlen, fc, seq_ctrl;
struct ieee80211_hdr *hdr;
uint8_t *data, *bssid_mac, *dst_mac;
uint8_t encry = ZC_ENC_TYPE_INVALID;
uint8_t tods;

/*
* when device try to connect current router (include aha)
* skip the new packet.
*/
if (ieee80211 == NULL || zconfig_finished) {
return ALINK_INVALID;
}

/*
* we don't process smartconfig until user press configure button
*/
if (awss_get_config_press() == 0) {
return ALINK_INVALID;
}

hdr = (struct ieee80211_hdr *)ieee80211;
fc = hdr->frame_control;
seq_ctrl = hdr->seq_ctrl;

/*
* for smartconfig with bcast of data
*/
if (!ieee80211_is_data_exact(fc)) {
return ALINK_INVALID;
}

/* tods = 1, fromds = 0 || tods = 0, fromds = 1 */
if (ieee80211_has_tods(fc) == ieee80211_has_fromds(fc)) {
return ALINK_INVALID;
}

/* drop frag, more, order*/
if (ieee80211_has_frags(fc)) {
return ALINK_INVALID;
}

dst_mac = (uint8_t *)ieee80211_get_DA(hdr);
/* only multicast is passed */
if (0x1 != dst_mac[0] || 0x0 != dst_mac[1] || 0x5e != dst_mac[2]) {
#ifdef VERBOSE_MCAST_DEBUG
awss_debug("error type, %x, %x, %x\n", dst_mac[0], dst_mac[1], dst_mac[2]);
#endif
return ALINK_INVALID; /* only handle br frame */
}

bssid_mac = (uint8_t *)ieee80211_get_BSSID(hdr);

/*
* payload len = frame.len - (radio_header + wlan_hdr)
*/
hdrlen = ieee80211_hdrlen(fc);
if (hdrlen > len) {
return ALINK_INVALID;
}

#ifdef _PLATFORM_QCOM_
/* Note: http://stackoverflow.com/questions/17688710/802-11-qos-data-frames */
hdrlen = (hdrlen + 3) & 0xFC;/* align header to 32bit boundary */
#endif

res->u.br.data_len = len - hdrlen; /* eating the hdr */
res->u.br.sn = IEEE80211_SEQ_TO_SN(os_le16toh(seq_ctrl));

data = ieee80211 + hdrlen; /* eating the hdr */
tods = ieee80211_has_tods(fc);

do {
#ifdef AWSS_SUPPORT_APLIST
struct ap_info *ap_info;
ap_info = zconfig_get_apinfo(bssid_mac);
if (ap_info && ZC_ENC_TYPE_INVALID != ap_info->encry[tods]) {
encry = ap_info->encry[tods];
} else
#endif
{
if (!ieee80211_has_protected(fc)) {
encry = ZC_ENC_TYPE_NONE;
} else {
/* Note: avoid empty null data */
if (len < 8) { /* IV + ICV + DATA >= 8 */
return ALINK_INVALID;
}
if (!(ieee80211[3] & 0x3F)) {
encry = ZC_ENC_TYPE_WEP;
} else if (data[3] & (1 << 5)) {/* Extended IV */
if (data[1] == ((data[0] | 0x20) & 0x7F)) { /* tkip, WEPSeed = (TSC1 | 0x20 ) & 0x7F */
encry = ZC_ENC_TYPE_TKIP;
}
if (data[2] == 0 && (!(data[3] & 0x0F))) {
encry = ZC_ENC_TYPE_AES;
}

/*
* Note: above code use if(tkip) and if(ase)
* instead of if(tkip) else if(aes)
* beacause two condition may bother match.
*/
}
}
}
} while (0);

if (encry == ZC_ENC_TYPE_INVALID) {
awss_warn("invalid encry type!\r\n");
}
res->u.br.encry_type = encry;

/* convert IEEE 802.11 header + possible LLC headers into Ethernet header
* IEEE 802.11 address fields:
* ToDS FromDS Addr1 Addr2 Addr3 Addr4
* 0 0 DA SA BSSID n/a
* 0 1 DA BSSID SA n/a
* 1 0 BSSID SA DA n/a
* 1 1 RA TA DA SA
*/
res->src = ieee80211_get_SA(hdr);
res->dst = ieee80211_get_DA(hdr);
res->bssid = ieee80211_get_BSSID(hdr);
res->tods = ieee80211_has_tods(fc);

return ALINK_BROADCAST;
}

可以看出,这个函数主要是对基本信息进行解析,并且判断是否是smart config多播报文。

3. awss_recv_callback_mcast_smartconfig()函数

3.1 序列号sn

下面会有一个sn的概念,他是干嘛用的?我们看一下抓到的包:

image-20231222202846088

seq(Sequence Number)32bits,表示这个数据包的序列号,每个帧的seq都是不一样的。可以根据seq来确定顺序,并且能够确定是否有数据包丢失。我们在配网的时候,UDP广播报文是很可能会产生丢帧的,接收到的顺序可能不是发送的顺序,这个seq可以帮助设备分辨收到的帧是否是连续的。比如丢的,重复的,都可以被标记出来进行对应的处理。

3.2 函数解析

awss_recv_callback_mcast_smartconfig()函数定义在AOS_SDK_PATH/components/linkkit/wifi_provision/mcast_smartconfig/mcast_smartconfig.c中,这里摆一张有效数据的图片:

image-20231220204929482
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
int awss_recv_callback_mcast_smartconfig(struct parser_res *res)
{
uint8_t index = *(res->dst + 3) & (0x7f) ;
uint8_t payload1 = *(res->dst + 4);
uint8_t payload2 = *(res->dst + 5);
uint8_t frame_offset;
uint16_t len;
uint8_t encry_type;

/* since put 2 bytes one time, so it has to mulitplied by 2 */
index = index << 1;
if (1 == get_all) {
return FAILURE_RETURN;
}

if (index > MCAST_MAX_LEN) {
#ifdef VERBOSE_MCAST_DEBUG
awss_debug("error index\n");
#endif
return FAILURE_RETURN;
}
len = res->u.br.data_len;
encry_type = res->u.br.encry_type;

frame_offset = zconfig_fixed_offset[encry_type][0];

len -= frame_offset;
if (len != 1) {
#ifdef VERBOSE_MCAST_DEBUG
awss_debug("error len, len is %d\n", len);
#endif
return FAILURE_RETURN;
}
/* Filter out interference */
/*
* 1) src not equal, bssid equal, interference
* 2) src not equal, bssid not equal, interference
* 3) src equal, bssid equal, good, go on
* 4) src equal, bssid not equal
*/

if (!memcmp(mcast_src_mac, zero_mac, ETH_ALEN)) {
memcpy(mcast_bssid_mac, res->bssid, ETH_ALEN);
memcpy(mcast_src_mac, res->src, ETH_ALEN);
} else {
/* case 1, 2 */
if(memcmp(mcast_src_mac, res->src, ETH_ALEN)) {
return FAILURE_RETURN;
}
}

/* lock channel */
if (0 == find_channel_from_aplist) {
lock_mcast_channel(res, encry_type);
}
processed_packet++;

awss_debug("mcast: index is %d, %d, %d, %d\n", index, payload1, payload2, len);

mcast_smartconfig_data.data[index] = payload1;
receive_record[index] = 1;
index++;
mcast_smartconfig_data.data[index] = payload2;
receive_record[index] = 1;

if (0 != receive_record[0]) {
int remain = -1;
mcast_smartconfig_data.tlen = mcast_smartconfig_data.data[0];
remain = mcast_receive_done();
if (0 == remain) {
get_all = 1;
parse_result();
}
}
return PKG_MCAST_FRAME;
}

3.3 parse_result()

解析SSID和密码等相关数据的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
int parse_result()
{
int offset = 0;
int ret, valid_bssid;
uint8_t BIT0_passwd = 0;
uint8_t BIT1_ssid = 0;
uint8_t BIT2_token = 0;
uint8_t BIT6_7_version = 0;

awss_debug("mcast: tlen is %d\n", mcast_smartconfig_data.tlen);
mcast_smartconfig_data.flag = mcast_smartconfig_data.data[1];
offset = 2;
awss_debug("mcast: flag is %d\n", mcast_smartconfig_data.flag);

BIT0_passwd = 0x1 & mcast_smartconfig_data.flag;
BIT1_ssid = 0x1 & (mcast_smartconfig_data.flag >> 1);
BIT2_token = 0x1 & (mcast_smartconfig_data.flag >> 2);
BIT6_7_version = 0x3 & (mcast_smartconfig_data.flag >> 6);

if (0x3 != BIT6_7_version) {
awss_err("error version");
return -1;
}

if (1 == BIT0_passwd) {
mcast_smartconfig_data.passwd_len = mcast_smartconfig_data.data[2];
awss_debug("mcast: passwd_len is %d\n", mcast_smartconfig_data.passwd_len);
mcast_smartconfig_data.passwd = &(mcast_smartconfig_data.data[3]);
offset = 3 + mcast_smartconfig_data.passwd_len;
}

if (1 == BIT2_token) {
mcast_smartconfig_data.token_len = mcast_smartconfig_data.data[offset];
awss_debug("mcast: token_len is %d\n", mcast_smartconfig_data.token_len);
offset++;
mcast_smartconfig_data.token = &(mcast_smartconfig_data.data[offset]);
awss_debug("mcast: token is %.*s\n", mcast_smartconfig_data.token_len, mcast_smartconfig_data.token);
offset += mcast_smartconfig_data.token_len;
}

if (1 == BIT1_ssid) {
mcast_smartconfig_data.ssid_len = mcast_smartconfig_data.data[offset];
awss_debug("mcast: ssid_len is %d\n", mcast_smartconfig_data.ssid_len);
offset++;
mcast_smartconfig_data.ssid = &(mcast_smartconfig_data.data[offset]);
awss_debug("mcast: ssid is %.*s\n", mcast_smartconfig_data.ssid_len, mcast_smartconfig_data.ssid);
offset += mcast_smartconfig_data.ssid_len;
}

mcast_smartconfig_data.bssid_type_len = mcast_smartconfig_data.data[offset];
awss_debug("mcast: bssid_type_len is %d\n", mcast_smartconfig_data.bssid_type_len);
offset++;
mcast_smartconfig_data.bssid = &(mcast_smartconfig_data.data[offset]);
/* get bssid len from last 5 bits from bssid_type_len */
offset += mcast_smartconfig_data.bssid_type_len & 0x1f;
mcast_smartconfig_data.checksum = mcast_smartconfig_data.data[offset];
awss_debug("mcast: checksum is %d\n", mcast_smartconfig_data.checksum);
awss_debug("mcast: total processed %d packagets \n", processed_packet);

/* set zc_bssid*/
valid_bssid = set_zc_bssid();
/* set zc_ssid */
set_zc_ssid(valid_bssid);
ret = verify_checksum();
if (SUCCESS_RETURN != ret) {
awss_err("mcast: checksum mismatch\n");
return -1;
}
ret = decode_passwd();
gen16ByteToken();
reset_mcast_data();
if (SUCCESS_RETURN != ret) {
awss_err("mcast: passwd error\n");
return -1;
}
/* TODO: check channel info*/
zconfig_set_state(STATE_RCV_DONE, 0, mcast_locked_channel);
return SUCCESS_RETURN;
}

3.4 密码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int decode_passwd()
{
int passwd_len = 0;
/* CAN'T use snawss_debug here, because of SPACE char */
passwd_len = mcast_smartconfig_data.passwd_len;
memset(zc_passwd, 0, ZC_MAX_PASSWD_LEN);
aes_decrypt_string((char *)(mcast_smartconfig_data.passwd), (char *)zc_passwd,
passwd_len,
1, awss_get_encrypt_type(), 0, NULL);
if (is_utf8((const char *)zc_passwd, passwd_len) == 0) {
awss_trace("passwd err\r\n");
awss_event_post(IOTX_AWSS_PASSWD_ERR);
AWSS_UPDATE_STATIS(AWSS_STATIS_SM_IDX, AWSS_STATIS_TYPE_PASSWD_ERR);
return FAILURE_RETURN;
}
return SUCCESS_RETURN;
}