最近接到客户反馈,通过web登录设备后,切换到"网络设置"时,网页弹出“设备连接服务异常,请检查设备后重新尝试”,在客户那边是必现。然后在公司技术支持那边也是必现,但我拿技术支持的设备来进行复现时,却没复现过。结合复现时设备里的日志信息,是在设备获取周围wifi信息时,没有返回,导致超时,当时想到的就是先规避掉,即把wifi关掉,但wifi使能就在“网络设置”页面,无法从页面上disable掉,悲催了。最后通过后台把wifi去使能后,在技术支持那边操作一切正常。一开始想出一版程序把wifi默认关掉,先解决客户的问题,不过分析代码后,发现了其中的问题,记录如下。
1,web请求wifi信息时,设备做了什么?
设备里定时搜索wifi信息并保存下来,当web来请求时,返回结果 。但在返回结果前进行了转码,通过查看web收到的信息,返回的是Unicode编码。转码主要代码如下 :
while(inSSID[i] != '\0')
{
if(i >= inMaxLen || j >= outMaxLen)
{
break;
}
if(inSSID[i] == '\\' && inSSID[i + 1] == 'x')
{
hexData1 = Type_CharToHex(inSSID[i + 2]);
hexData2 = Type_CharToHex(inSSID[i + 3]);
if(hexData1 != -1 && hexData2 != -1)
{
// DEBUG_INFO("hexData1 = 0x%02x, hexData2 = 0x%02x", hexData1, hexData2);
outSSID[j++] = hexData1*0x10 + hexData2;
i += 4;
// continue;
}
}
else if(inSSID[i] == '\r' || inSSID[i] == '\n')//clear CRLF
{
outSSID[j++] = '\0';
i++;
}
else
{
outSSID[j++] = inSSID[i++];
}
}
代码没有注释(看不懂的可以google一下utf8转unicode编码),我也是看不懂,我是通过结果来看的,最后返回给web的是unicode编码,所以这里应该是uft8转unicode,这里暂且不用管是怎么转码的,重点是这个函数里有问题。
2,utf8编码的了解
要发现上面这个函数的问题,首先得了解utf8编码,有兴趣的可以自行google。我也只是搜索了一下去了解了解,其编码格式有好几种,如:
而在设备里的,是\xXX的编码格式,看代码去判断了if(inSSID[i] == '\\' && inSSID[i + 1] == 'x'),所以设备系统里返回给上层应用的就是\xXX形式的utf8编码格式。然而一个中文的utf8编码格式应该是这样:\xXX\xXX\xXX,即3个\x,若不符合即不合法的uft8编码格式,通过在线转码可以验证,如:
了解这个编码格式后,添加打印日志,如有日志:
因为程序里保存SSID的buf只给了41个字节,所以"10\xe9\x87\x8f\xe8\xba\xab\xe5\xae\xa2\x"刚好40个字符,通过在线转码一看不是正确的编码格式:
正确的应该是:"10\xe9\x87\x8f\xe8\xba\xab\xe5\xae\xa2" ,如:
到这里只能说是找到转码有问题,但web怎么会超时而退出,然后就再不登录不上了呢?
3,转码函数的问题--------栈越界
分析代码后发现有一段如下:
if(inSSID[i] == '\\' && inSSID[i + 1] == 'x')
{
hexData1 = Type_CharToHex(inSSID[i + 2]);
hexData2 = Type_CharToHex(inSSID[i + 3]);
if(hexData1 != -1 && hexData2 != -1)
{
// DEBUG_INFO("hexData1 = 0x%02x, hexData2 = 0x%02x", hexData1, hexData2);
outSSID[j++] = hexData1*0x10 + hexData2;
i += 4;
// continue;
}
}
注意这一行:i += 4,i向后跳了4个字节,然而如果像上面的转码前是"10\xe9\x87\x8f\xe8\xba\xab\xe5\xae\xa2\x",最后的两个字符就是\x,满足了第一个if的判断,最后也进入了第二个if,然后i往后跳4个字节,跳到哪里去了呢?谁也不知道,最好的情况是跳过去满足while(inSSID[i] != '\0') 退出while循环,否则这里就是一个死循环了,web不超时才怪。于是添加了一条打印日志,如:
最后果然出现死循环:
问题的根因是找到,最后是怎么修改的问题了。这里贴一下我的修改方法,如有更好的方法欢迎@我啊!修改后测试正常,还有其他地方的修改,如:加大了保存SSID的buffer。下面转码时会进行截断,只保留正确的utf8编码格式的字符。
int Wifi_ConvertSSID(const char* inSSID, char* outSSID, int inMaxLen, int outMaxLen)
{
int i = 0;
int j = 0;
int hexData1 = -1;
int hexData2 = -1;
if(inSSID == NULL || outSSID == NULL)
{
return -1;
}
/**
* \xe9\xbe\x99\xe5\xa5\xb3 中文的utf-8编码必须符合3个\x代表一个中文字符
*
*/
char tmpSSID[WIFI_NAME_LEN + 1] = {0};
int idx = 0;
int charLen = inMaxLen > WIFI_NAME_LEN ? WIFI_NAME_LEN : inMaxLen;
for(idx = 0; idx < charLen;)
{
if(inSSID[idx] == '\\' && inSSID[idx + 1] == 'x')
{
if(inSSID[idx + 8] == '\\' && inSSID[idx + 9] == 'x')//这里是探测是否有3个\x
{
if(idx + 12 >= charLen)
{
break;
}
strncpy(tmpSSID + idx, inSSID + idx, 12); //一次拷贝12个字符,即3个\xXX
idx += 12;
}
else
{
break;
}
}
else //不是中文的直接拷贝
{
tmpSSID[idx] = inSSID[idx];
idx++;
}
}
tmpSSID[idx] = '\0';
printDebug("real ssID = %s, len = %d\n", tmpSSID, idx);
while(tmpSSID[i] != '\0')
{
if(i >= charLen || j >= outMaxLen)
{
break;
}
if(tmpSSID[i] == '\\' && tmpSSID[i + 1] == 'x')
{
hexData1 = Type_CharToHex(tmpSSID[i + 2]);
hexData2 = Type_CharToHex(tmpSSID[i + 3]);
if(hexData1 != -1 && hexData2 != -1)
{
// DEBUG_INFO("hexData1 = 0x%02x, hexData2 = 0x%02x", hexData1, hexData2);
outSSID[j++] = hexData1*0x10 + hexData2;
i += 4;
if(i > charLen) //最后这里加了一层保险
{
break;
}
}
}
else if(tmpSSID[i] == '\r' || tmpSSID[i] == '\n')//clear CRLF
{
outSSID[j++] = '\0';
i++;
}
else
{
outSSID[j++] = tmpSSID[i++];
}
}
return 0;
}