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

本文主要是天猫精灵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
├── # ......
├── smartconfig
│   └── awss_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_smartconfig_process()函数

awss_ieee80211_smartconfig_process()函数定义在AOS_SDK_PATH/components/linkkit/wifi_provision/smartconfig/awss_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
132
133
int awss_ieee80211_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.
*/
// 1. 判断数据是否为空或者是否已经配过网了
if (ieee80211 == NULL || zconfig_finished) {
return ALINK_INVALID;
}

/*
* we don't process smartconfig until user press configure button
*/
// 2. 判断是否处于配网状态
if (awss_get_config_press() == 0) {
return ALINK_INVALID;
}
// 3. 获取报文数据的地址
hdr = (struct ieee80211_hdr *)ieee80211;
fc = hdr->frame_control;
seq_ctrl = hdr->seq_ctrl;

/*
* for smartconfig with bcast of data
*/
// 4. check if type is IEEE80211_FTYPE_DATA and only data
if (!ieee80211_is_data_exact(fc)) {
return ALINK_INVALID;
}

/* tods = 1, fromds = 0 || tods = 0, fromds = 1 */
// 5. tods和fromds代表数据报文的方向
if (ieee80211_has_tods(fc) == ieee80211_has_fromds(fc)) {
return ALINK_INVALID;
}
/* drop frag, more, order*/
// 6. 是否是分片报文?当IP数据报的总长度大于链路MTU时,就需要将IP数据报中的数据分装在两个或更多个较小的IP数据报中,这些较小的数据报叫做片。我们分析的smartconfig报文不是分片报文
if (ieee80211_has_frags(fc)) {
return ALINK_INVALID;
}
// 7. 获取目的mac地址
dst_mac = (uint8_t *)ieee80211_get_DA(hdr);
if (memcmp(dst_mac, br_mac, ETH_ALEN)) {
return ALINK_INVALID; /* only handle br frame */
}
// 7. 获取BSSID
bssid_mac = (uint8_t *)ieee80211_get_BSSID(hdr);

/*
* payload len = frame.len - (radio_header + wlan_hdr)
*/
// 8. 获取头部长度
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
// 9. 获取报文数据长度,报文总长度-头部长度,我们广配网的时候主要依据就是这个数据长度,我们从数据长度中解析SSID和密码
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
{
// 获取加密类型,wap或者是其他类型
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_smartconfig()函数

3.1 序列号sn

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

image-20231222202846088

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

3.2 函数解析

awss_recv_callback_smartconfig()函数定义在AOS_SDK_PATH/components/linkkit/wifi_provision/smartconfig/awss_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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
int awss_recv_callback_smartconfig(struct parser_res *res)
{
static char statis = 0;
uint32_t timestamp = os_get_time_ms();

uint8_t *src = res->src;
uint8_t *dst = res->dst;
uint8_t *bssid = res->bssid;
uint8_t tods = res->tods;
uint8_t channel = res->channel;

uint16_t sn = res->u.br.sn;
uint16_t len = res->u.br.data_len;// 提取信息用
uint8_t encry_type = res->u.br.encry_type;

int ret, pkg_type = PKG_INVALID;
uint8_t score = 0, timeout = 0, equal = 0;

uint16_t pos = 0, index = 0;

awss_flow("len=%d, %c, sn=%d, enc=%d, chn=%d, src=%02x%02x%02x%02x%02x%02x\r\n",
len, flag_tods(tods), sn, encry_type, channel,
src[0], src[1], src[2], src[3], src[4], src[5]);
/*
* STATE_CHN_LOCKED_BY_P2P is set by v2 wps/action frame, which means
* APP is sending v2, but if v2 is fail, APP will rollback to v1,
* so still need to parse br frame here
* even zc_state == STATE_CHN_LOCKED_BY_P2P.
*/
// 1. 判断配网方式,这里应该是STATE_CHN_SCANNING
if (zc_state == STATE_CHN_LOCKED_BY_P2P ||
zc_state == STATE_CHN_LOCKED_BY_MCAST ||
zc_state == STATE_CHN_SCANNING) {
// 1.1 判断是否是起始帧或者分组帧,这里主要是为了寻找起始帧或者分组帧,为下一次或者之后的数据帧解析做铺垫
if (is_hint_frame(encry_type, len, bssid, src, channel, tods, sn)) {
if (statis == 0) {
statis = 1;
AWSS_UPDATE_STATIS(AWSS_STATIS_SM_IDX, AWSS_STATIS_TYPE_TIME_START);
}
awss_trace("hint frame: offset:%d, %c, sn:%x\r\n",
zc_frame_offset, flag_tods(tods), sn);

awss_trace("src:%02x%02x%02x%02x%02x%02x, bssid:%02x%02x%02x%02x%02x%02x\r\n",
src[0], src[1], src[2], src[3], src[4], src[5],
bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);

pkg_type = PKG_START_FRAME;
zconfig_set_state(STATE_CHN_LOCKED_BY_BR, tods, channel);

goto update_sn;// 直接跳到最后,更新sn
} else if (!memcmp(zc_android_src, src, ETH_ALEN)) {
#ifdef AWSS_SUPPORT_APLIST
struct ap_info *ap_info = zconfig_get_apinfo(bssid);
if (ap_info) {
if (ap_info->ssid[0] != 0x00 && ap_info->ssid[0] != 0xFF) {
strncpy((char *)zc_android_ssid, (const char *)ap_info->ssid, ZC_MAX_SSID_LEN - 1);
}
memcpy(zc_android_bssid, bssid, ETH_ALEN);
awss_trace("src %02x%02x%02x match %02x%02x%02x\r\n",
zc_android_src[0], zc_android_src[1], zc_android_src[2],
zc_android_bssid[0], zc_android_bssid[1], zc_android_bssid[2]);
}
#endif
}
}
// 2. 若状态已经是STATE_CHN_LOCKED_BY_BR,说明前一次我们已经找到了起始帧
else if (zc_state == STATE_CHN_LOCKED_BY_BR) {
/* same src mac & br & bssid */
if (memcmp(&src[0], zc_src_mac, ETH_ALEN) ||
memcmp(&dst[0], br_mac, sizeof(br_mac)) ||
memcmp(bssid, zc_bssid, ETH_ALEN)) { /* in case of WDS */
goto drop;
}

if (timestamp - zc_timestamp > time_interval) {
awss_debug("\t\t\t\t\ttimestamp = %d\r\n", timestamp - zc_timestamp);
timeout = 1;
}

ret = sn_compare(sn, zc_prev_sn);
if (ret <= 0) { /* retry packet, update timestamp */
zc_timestamp = timestamp;
}
if (ret == 0) {
awss_debug("drop: %3x == %3x\r\n", sn, zc_prev_sn);/* log level, too many retry pkg */
goto drop;
} else if (ret < 0 && !timeout) {/* if timeout, goto pos_unsync */
awss_debug("drop: %3x < %3x\r\n", sn, zc_prev_sn);/* TODO: better not drop */
goto update_sn;/* FIXME: update sn??? */
}

/* assert(sn > zc_prev_sn && !timeout); */

if (len <= zc_frame_offset) { /* length invalid */
goto drop;
}

len -= zc_frame_offset;
// 2.1 判断是否是数据帧
if (is_data_frame(len)) {
pkg_type = PKG_DATA_FRAME;
index = get_data_index(len);
pos = zc_group_pos + index;

if (index > GROUP_NUMBER || pos >= MAX_PKG_NUMS) {
goto drop;
}
/*
* pos_unsync: 进入条件,任一条
* case1: index rollback
* case2: index equal but len not equal
* case3: data frame & timeout
* 退出条件:
* case1: 进入条件同时也是退出条件
* case2: 收到同步帧
*/
// 2.2.1 索引变小了,或者长度不对了,这个时候就需要同步数据进行一个整理,我猜测就相当于我们继续往后找索引正确的数据包
if (index < zc_last_index ||
(index == zc_last_index && len != zc_last_len) || timeout) {
if (zc_pos_unsync) {/* already in pos_unsync state */
awss_trace("\texit try_to_sync_pos: re-enter!\r\n");
try_to_sync_pos(tods, zc_prev_sn, sn, zc_group_pos, -1);
}
zc_pos_unsync = 1;/* also a new start */
if (index < zc_last_index) {
awss_trace("\tenter try_to_sync_pos: rollback \r\n");
} else if (timeout) {
awss_trace("\tenter try_to_sync_pos: timeout \r\n");
} else {
awss_trace("\tenter try_to_sync_pos: != \r\n");
}
}
pos_unsync:
if (zc_pos_unsync) {/* tmp save */
package_save((uint8_t *)tmp(index), src, dst, tods, len);
if (zc_pos_unsync == 1) {
tmp_score(index) = 1;
} else {
tmp_score(index) = (sn - zc_prev_sn); /* TODO: index? last_tmp_score */
}
zc_pos_unsync ++; /* unsync pkg counter */
awss_trace("\tX+%d [%d] %-3x %c %-3x\r\n", index, tmp_score(index), sn, flag_tods(tods), len);
goto update_sn;/* FIXME: update prev_sn or not? */
}

/* assert(sn > zc_prev_sn && pos > zc_cur_pos) */
score = get_data_score(zc_group_sn, sn, zc_prev_sn, pos, zc_cur_pos, tods);
if (score == score_min) {/* better not drop any pkg here */
awss_trace("\t drop: group_sn:%x, sn:%x-%x=%x, pos:%d-%d, len:%x\r\n",
zc_group_sn, sn, zc_prev_sn, sn_minus(sn, zc_group_sn), pos, zc_cur_pos, len);
goto update_sn;
} else {
if (zc_score_uplimit > score) {
zc_score_uplimit = score; /* inherit last limit */
}

zc_group_sn = sn;/* TODO */
awss_trace("%d+%d [%d] %-3x %c %-3x\r\n", zc_group_pos, index, score, sn, flag_tods(tods), len);
}
}
// 2.2 判断是否是起始帧,是否是分组帧
else {
if (is_start_frame(len) || is_group_frame(len)) {
uint8_t group = get_group_index(len);
// 2.2.1 实际就是我们要从一大堆数据中找到符合我们要求的数据包,它会遍历所有分组,找到最多匹配的分组: 分组号,匹配的起止位置,匹配的最小score。遍历分两步:遍历相等的数量 和 遍历空位置的数量
if (zc_pos_unsync) {
awss_trace("\texit try_to_sync_pos: group frame\r\n");
try_to_sync_pos(tods, zc_prev_sn, sn, zc_group_pos, group);
}

zc_cur_pos = group;
zc_group_pos = group;
zc_group_sn = sn;
zc_score_uplimit = score_max;

awss_trace("%d+%d [%d] %-3x %c %-3x\r\n", group, 0, zc_score_uplimit, sn, flag_tods(tods), len);

/* ignore PKG_GROUP_FRAME here */
pkg_type = PKG_START_FRAME;

/*
* keep calling zconfig_set_state(), see Note about
* zconfig_callback_channel_locked()
*/
zconfig_set_state(STATE_CHN_LOCKED_BY_BR, tods, channel);

/* zc_replace may happen in try_to_sync_pos(), so goto is_recv_completed */
goto is_recv_completed;
} else {
awss_trace("\t invalid len = %d\r\n", len + zc_frame_offset);
goto drop;
}
}

/* start from pkg(1), leave pkg(0) for start frame */
if (pos >= MAX_PKG_NUMS || pos <= 0) {
awss_warn("msg index(%d) out of range!\r\n", pos);
goto drop;
}

zc_cur_pos = pos;

/*
score now > last:
1) data equal: pkg_score = now
2) not equal: pkg_score = now, data replace
score now == last:
1) data equal: pkg_score++ and ???
2) not equal: pkg_score cut down & give warning & try_to_replace
score now < last:
1) data equal: score_uplimit up???
2) not equal: goto pos_unsync
*/
for (tods = 0; tods < 2; tods ++) {
equal = !package_cmp((uint8_t *)pkg(pos), src, dst, tods, len);

if (score > pkg_score(pos)) {
pkg_score(pos) = score; /* update score first */
if (equal) {
continue;
}
/* not equal */
zc_replace = 1;
package_save((uint8_t *)pkg(pos), src, dst, tods, len);
} else if (score == pkg_score(pos)) {/* range check ? */
int replace;
if (equal) {
pkg_score(pos) ++;
continue;
}
/* not equal */
replace = try_to_replace_same_pos(tods, pos, len);
if (replace) {
awss_trace("\t replace @ %d, len=%x\r\n", pos, len);
continue;
}
pkg_score(pos) /= 2;
if (score >= score_mid) /* better not happen */
awss_warn("xxxxxxxx warn: pos=%d, score=[%d], %x != %x\r\n",
pos, score, pkg_len(pos), len);

} else if (tods == res->tods) {/* pkg_score(pos) > score */
if (!equal) {/* data not equal */
if (zc_pos_unsync) {
continue;
}
zc_pos_unsync = 1;
awss_trace("\tenter try_to_sync_pos: data mismatch\r\n");
tods = res->tods;
goto pos_unsync;
} else if (zc_score_uplimit >= score_mid && pkg_score(pos) - score < 10) { /* data equal */
uint8_t uplimit = (zc_score_uplimit + pkg_score(pos)) / 2;
if (zc_score_uplimit != uplimit) {
awss_trace("\t\t\t uplimit [%d] -> [%d]\r\n", zc_score_uplimit, uplimit);
}
zc_score_uplimit = uplimit;
}
}
}
tods = res->tods;

is_recv_completed:
zc_max_pos = (zc_max_pos < zc_cur_pos) ? zc_cur_pos : zc_max_pos;
if (zc_replace && zconfig_recv_completed(tods)) {
zc_replace = 0;
memcpy(zc_bssid, res->bssid, ETH_ALEN);
if (!zconfig_get_ssid_passwd(tods)) {
/* we got it! */
AWSS_UPDATE_STATIS(AWSS_STATIS_SM_IDX, AWSS_STATIS_TYPE_TIME_SUC);
statis = 0;
zconfig_set_state(STATE_RCV_DONE, tods, channel);
return PKG_END;
}
}
}

update_sn:
zc_prev_sn = sn;

zc_timestamp = timestamp;
zc_last_index = index;
zc_last_len = len;

return pkg_type;

drop:
return PKG_INVALID;
}

3.3 try_to_sync_pos()函数

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
132
133
134
135
136
137
138
139
140
141
142
143
144
/*
遍历所有分组,找到最多匹配的分组: 分组号,匹配的起止位置,匹配的最小score
遍历分两步:遍历相等的数量 和 遍历空位置的数量
guess_pos: 根据前后位置确定的pos
match_pos: 根据匹配计算的pos

1) 如果guess_pos && match_pos存在,且相等,则score += 5, pos = match_pos
2) 不相等,则score -= 5, pos = match_pos
3) 只有guess_pos存在,则score = 2
4) 只有match_pos存在,则score不变
*/
int try_to_sync_pos(uint8_t tods, uint16_t last_sn, uint8_t sn,
int last_group_pos, int group_pos)
{
int ret = -1, empty_match = 0, reason = 0;
int guess_pos = -1, final_pos = -1;

int max_match = 0, match_group = -1, match_end = GROUP_NUMBER, match_score = 0;
int match, i, j, score; /* loop variable */

retry:
// 这里的zconfig_get_data_len()获取的是广播配网的时候数据帧中有效数据Payload数据的总长度,GROUP_NUMBER=8,等于是每次遍历一组
for (i = 0; i <= zconfig_get_data_len(); i += GROUP_NUMBER) {
for (match = 0, score = score_max, j = 1; j <= GROUP_NUMBER; j ++) {
if (!tmp_score(j)) {
continue;
}

if (empty_match) {
if (pkg_score(i + j) <= 1) {
match++;
score = 1;
}
} else {
if (!pkg_len(i + j)) {
continue;
}
if (pkg_len(i + j) == tmp_len(j)) {
match ++;
score = (score > pkg_score(i + j)) ? pkg_score(i + j) : score;
} else {/* encounter first unmatch */
awss_trace("[%d]=%x, [%d]=%x\r\n", i + j, pkg_len(i + j), j, tmp_len(j));
break;
}
}
}
if (match > max_match) {
max_match = match;
match_group = i;
match_end = j - 1;
match_score = score;
awss_trace("match=%d, match_group=%d, match_end=%d\r\n",
match, match_group, match_end);
}
}

if (!max_match && !empty_match) {/* retry empty place match */
empty_match = 1;
goto retry;
}

if (group_pos != -1) {/* 根据后位置确定 */
guess_pos = group_pos - GROUP_NUMBER;/* 前一组 */
if (guess_pos < 0) {
guess_pos = (zconfig_get_data_len() / GROUP_NUMBER) * GROUP_NUMBER;
}

if (!max_match || empty_match) {/* case 3 */
match_score = 2;
final_pos = guess_pos;
reason = 3;
goto replace;
/* can not del goto, cause guess_pos has higher priority than empty match */
}
}
/* 前位置 有效性难以判断,忽略 */

if (max_match > 0) {
if (max_match == 1) {
match_score = match_score > 10 ? 10 : match_score;
} else if (max_match == 2) {
match_score = match_score > 20 ? 20 : match_score;
} else if (max_match <= GROUP_NUMBER) {
match_score = match_score > 30 ? 30 : match_score;
} else {
goto clear;
}

if (guess_pos != -1) {
if (guess_pos == match_group) {/* case 1 */
match_end = GROUP_NUMBER;
match_score += 2;/*bonus */
final_pos = match_group;
reason = 1;
} else {/*case 2*/
match_score -= 0;/*bonus*/
if (max_match >= 2 && !empty_match) {
final_pos = match_group;
} else {
final_pos = guess_pos;
}
reason = 2;
}
} else {/*case 4: 只有match_pos存在*/
final_pos = match_group;

reason = 4;
}
} else {
goto clear;
}

replace:
if (final_pos != -1) {
reason = reason;
awss_trace("\tX = %d, score=%d, match=%d, reason=%d\r\n", final_pos, match_score, max_match, reason);
if (match_end != GROUP_NUMBER) {
awss_trace("\t match from [1-%d]\r\n", match_end);
}
for (i = final_pos + 1, j = 1; i <= final_pos + match_end; i++, j++) {
if (j > GROUP_NUMBER || i >= MAX_PKG_NUMS) {
break;
}
if (pkg_score(i) < match_score && tmp_score(j)) {
pkg_len(i) = tmp_len(j);
pkg_score(i) = (match_score > tmp_score(j) - 1) ?
(match_score - (tmp_score(j) - 1)) : match_score;/*TODO*/
awss_trace("\t%d+%d [%d] %c %-3x\r\n", final_pos, j, pkg_score(i), flag_tods(tods), tmp_len(j));

zc_replace = 1;
if (zc_max_pos < i) {
zc_max_pos = i;
}

ret = 0;
}
}
}

clear:
zc_pos_unsync = 0;
memset((uint8_t *)tmp(0), 0, sizeof(zconfig_data->tmp_pkg[0]));
return ret;
}

3.4 get_data_score()函数

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
/*
* get_data_score()
*
* calc package score
*
* @Return:
* score between [0, 100]
*/
uint8_t get_data_score(uint16_t group_sn, uint16_t sn_now, uint16_t sn_last,
uint8_t index_now, uint8_t index_last, uint8_t tods)
{
/*
example: 1
8+3 250 0 d0e cc:fa:00:c8:cf:d2 > ff:ff:ff:ff:ff:ff
8+4 2bf 0 d15 cc:fa:00:c8:cf:d2 > ff:ff:ff:ff:ff:ff //两个包index_delta=1, sn_delta=7

example: 2
8+0, 3e1, 9a5
8+1, 13f, 9a7
group_sn=9a7, sn=9ab-9a7, pos=e-9, len=3ce //here, index_delta=5, sn_delta=4
group_sn=9a7, sn=9ac-9ab, pos=f-9, len=454
group_sn=9a7, sn=9ad-9ac, pos=10-9, len=4d2
example: 3
8+3, 225, a32
8+6, 3c7, a39 //此处应该是16+6, index_delta=3, sn_delta=7
example: 4
0+0, 4e0, da5
0+7, 441, dab //此处应该是8+7, index_delta=7, sn_delta=6
0+0, 4e0, d89
0+8, 4c2, d8f //此处应该是8+8, index_delta=8, sn_delta=6

//example: 4
0+0 [100] 294 0 4e0
0+1 [60] 2a2 0 11a
0+2 [40] 2aa 0 181
group_sn:2aa, sn:2b8-2aa=14, pos:3-2, len:20a
group_sn:2aa, sn:2bc-2b8=18, pos:4-2, len:28a
group_sn:2aa, sn:2c0-2bc=22, pos:5-2, len:310
group_sn:2aa, sn:2c4-2c0=26, pos:6-2, len:391
group_sn:2aa, sn:2c8-2c4=30, pos:7-2, len:412
group_sn:2aa, sn:2cc-2c8=34, pos:8-2, len:493
*/
static const uint16_t score_level[][2] = {
{0, 0},
{1, 2}, /* include, example 1, 3 */
{4, 8},
{8, 16},/* example 1 */
{15, 30},
{40, 40},
{0xFFFF, score_max} /* the end missing seq, example 2 */
};

uint16_t sn_delta = sn_minus(sn_now, group_sn) - 1;
uint16_t index_delta = (index_now - index_last) - 1;
uint16_t delta = sn_delta + index_delta;

uint8_t i = 0;
uint8_t res;

/* suppose: sn > zc_prev_sn, pos > zc_cur_pos */
// 判断是否倒序了,倒序了的话,分数就直接最低
if (sn_compare(sn_now, group_sn) <= 0 || sn_compare(sn_now, zc_prev_sn) <= 0) {
return score_min;
} else if (index_now <= index_last) {
return score_min;
}

while (delta > score_level[i][0]) { /* include */
i ++;
}

res = score_level[i][1];

if (zc_score_uplimit > res) {
return zc_score_uplimit - res;
} else {
return score_low;
}
}