最近在研究字符编码问题,正好想到WiFi SSID的问题,今天就来说说这个SSID的编码和显示问题,尤其是大家一直关心的各种操作系统对中文SSID的显示乱码问题。

首先我们先简单聊下编码,编码最基本的就是UTF-8,UTF-16, UTF-32这些都属于Unicode的范畴,也就是国际编码可以针对全世界任何一个国家的文字和符号进行统一格式编码,具体格式就不再赘述,自行百度。而我国针对汉字和相关符号又有一套自己的编码,最典型的就是GB2312,这个是按照分区编码规则来实现的,具体自行百度。本文重点是研究针对多种编码的WiFi SSID系统如何处理字符编解码问题。

IEEE802.11中针对SSID一项的描述,其实就是32个字节,也没有明确规定这个32个字节需要什么编码方式,所以理论是这32个字节我们可以放入任意值的二进制数据,但是考虑到windows/linux/mac系统的编码问题,第一首选当然是UTF-8编码这是最通用的编码,也是可以被所有的系统识别和解析,最终显示。其他如GB2312等编码也是可以的,而英文字母具备天生的优势,UTF-8编码和ASCII针对26个字符和其他符号,即十进制32-127的数值都可以直接ASCII吗显示。如果SSID内容的某个/多个的字节内容不在32-127之内,那必定是包含非英文字母和符号了,那就需要进行有条件的判断,这个非英文字符是UTF-8编码呢还是非UTF-8编码,这个关键点是解决如何解码SSID内容最关键之处,因为如果获取不到SSID非英文内容的编码方式,就无法解码,最终的结果可能是系统显示乱码,而导致用户无法连接,因为系统本身识别出错了。

下面说说Android系统针对SSID识别问题,从Android系统的framework源代码看,其实系统默认就是把SSID的内容统一当作UTF-8编码处理了,后面当然是按照UTF-8格式去解码为UTF-16编码。如果默认SSID是UTF-8编码,那系统可以正确显示WiFi AP的SSID内容,如果是非UTF-8编码或是GB2312之类的,那就导致UI显示乱码了,这也正是很多网友疑惑的地方,包括很多Android的开发人员,那问题的本质是什么? 答案就是安卓系统没有对SSID的原始内容做编码格式判断,如果增加这一个判断的步骤,那所有问题都可以迎刃而解。这个方案是针对Android系统framework内部开发人员而言的。

Android针对SSID的解码在frameworks/base/wifi/java/android/net/wifi/WifiSsid.java文件中的WifiSsid类成员函数toString()实现的,默认是把SSID当UTF-8编码进行解码并准换成UTF-16的,问题的关键就在这里,那这里要解决的问题就是上述的方法,也就是在转换前,增加对SSID内容的编码识别和确认而不是直接按UTF-8处理。

既然有方案了,那采用什么样的功能函数来处理此类问题呢?有没有现成的函数/库/API供参考?还是要自己写Java代码来实现判断数据的编码方式呢? 思前顾后也没有想出方案来吧,而百度一下看到的网友的分享也有类似的实现,但是都没有一个标准的处理方法,那到底有没有呢,当然有了,而且量身定制。

既然是要完成编码识别任务,当然是找通用的Java库,而International Components for Unicode组织发布的Java/C++类库可以很好的解决这个问题,具体介绍参考ICU官网,那安卓呢?我们讨论的是安卓系统,安卓参考API官网,打不开请自行科学上网解决。Android从6.0开始(API 23),引进的ICU的API函数,但是针对字符识别的API是隐藏的,也就是官网没有针对这类函数的解释,因为不是针对普通APP开发者开放的,而我们这里讨论的是Android系统framework开发人员,所以针对这类开发人员这个API类函数的调用属于系统级调用,拥有足够高的权限来实现这个内部编码识别和解析功能。

Android在6.0以后引入的jar包名字为: android.icu, 而6.0以下版本需要自行集成com.ibm.icu包来实现,具体的代码在android/external/icu目录,里面有C++/Java库的源代码,这个icu就是针对字符处理的库函数,下面我们来描述如何实现。

在上述的WifiSsid.java的补丁文件:

diff --git a/wifi/java/android/net/wifi/WifiSsid.java b/wifi/java/android/net/wifi/WifiSsid.java
index c53cd3c6454..1ae5b102a09 100644
--- a/wifi/java/android/net/wifi/WifiSsid.java
+++ b/wifi/java/android/net/wifi/WifiSsid.java
@@ -28,6 +28,8 @@ import java.nio.charset.CoderResult;import java.nio.charset.CodingErrorAction;import java.util.Locale;+import android.icu.text.CharsetMatch;
+import android.icu.text.CharsetDetector;/*** Stores SSID octets and handles conversion.*
@@ -167,7 +169,12 @@ public class WifiSsid implements Parcelable {// behavior of returning empty string for this case.if (octets.size() <= 0 || isArrayAllZeroes(ssidBytes)) return "";// TODO: Handle conversion to other charsets upon failure
-        Charset charset = Charset.forName("UTF-8");
+        CharsetDetector encode_detect = new CharsetDetector();
+        CharsetMatch m;
+        encode_detect.setText(ssidBytes);
+        m =encode_detect.detect();
+        String char_encode = m.getName();
+        Charset charset = Charset.forName(char_encode);CharsetDecoder decoder = charset.newDecoder().onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(CodingErrorAction.REPLACE);

上述补丁是把默认的UTF-8编码取消,引入CharsetDetector()类来对原始SSID数据进行编码数据检测并得到char_encode, 再使用char_encode构造Charset类,最后实现对SSID数据的定向解码,即SSID原始数据什么编码格式,就按照检测到的格式进行解码,这样就实现了对WiFi 原始SSID的自适应解析,当然这个自适应的解析包括了所有可能的编码方式,而GB2312只是其中一个编码方式而已,这个补丁的关键解决自动判断编码方式,再按照检测出的编码方式进行解码。

编码自适应的解码我们解决了,但是由于SSID被转换,Java类中有关SSID的所有信息都是转后的UTF-16,而中间层wpa_supplicant里面保存的SSID可是原始的数据啊,这样等于的Java层和中间协议层内容不一致,这样的最终结果可能是WiFi如果有密码的,那可能无法连接,需要对上下做对应的处理。

那就需要对frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiConfigStore.java进行修改,这个函数涉及到Android Framework对APP层WiFi网络的新增连接和保存已连接的AP网络信息等,这里直接把补丁贴出来。

diff --git a/service/java/com/android/server/wifi/WifiConfigStore.java b/service/java/com/android/server/wifi/WifiConfigStore.java
index 25ab4493..fe810f25 100644
--- a/service/java/com/android/server/wifi/WifiConfigStore.java
+++ b/service/java/com/android/server/wifi/WifiConfigStore.java
@@ -24,6 +24,7 @@ import android.net.wifi.WifiEnterpriseConfig;import android.net.wifi.WifiSsid;import android.net.wifi.WpsInfo;import android.net.wifi.WpsResult;
+import android.net.wifi.ScanResult;import android.os.FileObserver;import android.os.Process;import android.security.Credentials;
@@ -47,6 +48,7 @@ import java.io.FileReader;import java.io.IOException;import java.net.URLDecoder;import java.nio.charset.StandardCharsets;
+import java.nio.charset.Charset;import java.security.PrivateKey;import java.security.cert.Certificate;import java.security.cert.CertificateException;
@@ -61,6 +63,9 @@ import java.util.List;import java.util.Map;import java.util.Set;+import android.icu.text.CharsetMatch;
+import android.icu.text.CharsetDetector;
+/*** This class provides the API's to save/load/modify network configurations from a persistent* config database.
@@ -170,14 +175,35 @@ public class WifiConfigStore {return TextUtils.join(" ", valueSet);}+    private static Charset getSSIDCharSet(WifiConfiguration config) {
+        ArrayList<ScanDetail> scanResults = mWifiNative.getScanResults();
+        String  charsetName = "";
+        for (int i = 0; i < scanResults.size(); ++i) {
+            ScanResult result = scanResults.get(i).getScanResult();
+            if(result.BSSID.equals(config.BSSID)) {
+                byte[] raw_ssid = result.wifiSsid.getOctets();
+                CharsetDetector encode_detect = new CharsetDetector();
+                CharsetMatch m;
+                encode_detect.setText(raw_ssid);
+                m =encode_detect.detect();
+                charsetName = m.getName();
+            }
+        }
+
+        if(i >= scanResults.size() && charsetName.isEmpty()) {
+            charsetName = "UTF-8";
+        }
+
+        return Charset.forName(charsetName);
+    }/** Convert string to Hexadecimal before passing to wifi native layer* In native function "doCommand()" have trouble in converting Unicode character string to UTF8* conversion to hex is required because SSIDs can have space characters in them;* and that can confuses the supplicant because it uses space charaters as delimiters*/
-    private static String encodeSSID(String str) {
-        return Utils.toHex(removeDoubleQuotes(str).getBytes(StandardCharsets.UTF_8));
+    private static String encodeSSID(String str, Charset charset) {
+        return Utils.toHex(removeDoubleQuotes(str).getBytes(charset));}// Certificate and private key management for EnterpriseConfig
@@ -609,10 +635,12 @@ public class WifiConfigStore {return false;}if (VDBG) localLog("saveNetwork: " + netId);
+
+        Charset charset = getSSIDCharSet(config);if (config.SSID != null && !mWifiNative.setNetworkVariable(netId,WifiConfiguration.ssidVarName,
-                encodeSSID(config.SSID))) {
+                encodeSSID(config.SSID, charset))) {loge("failed to set SSID: " + config.SSID);return false;}
@@ -922,8 +950,10 @@ public class WifiConfigStore {return false;}if (VDBG) localLog("setNetworkSSID: " + config.networkId);
+
+        Charset charset = getSSIDCharSet(config);if (!mWifiNative.setNetworkVariable(config.networkId, WifiConfiguration.ssidVarName,
-                encodeSSID(ssid))) {
+                encodeSSID(ssid, charset))) {loge("Set SSID of network in wpa_supplicant failed on " + config.networkId);return false;}

上面的补丁显示针对即将要保存的网络,先检测原始SSID的编码方式,然后把安卓现有的数据,也就是转换后UTF-16的数据再以原始SSID编码的方式转换回去,同时转换成16进制ASCII字符方式写入到wpa_supplicant, 这样就保持了对原始SSID数据的更新,而不是未经转换或是默认UTF-8的编码方式写入wpa_supplicant, 这样wpa_supplicant中的ssid保存的内容永远都是原始数据,而我们无需为支持GB2312等中文格式修改wpa_supplicant, 这样保证以最小的改动实现目的。

由于本人手头无设备验证测试,按照核心本质进行修改,希望有条件的开发人员能帮忙按照修改验证和测试以GB2312/GBK等非UTF-8编码的AP-SSID进行测试。

深度剖析WiFi的SSID问题相关推荐

  1. 极酷WIFI深度剖析免费WIFI

    极酷WIFI深度剖析免费WIFI   极酷WIFI分析,在未来几年内,广东居民将可在更多公共场所享受免费WiFi服务.在公共WiFi建设方面,广东除了发挥电信运营商作用外,还正在研究引入第三方机构.鼓 ...

  2. 【微信小程序控制硬件④】 深度剖析微信公众号配网 Airkiss 原理与过程,esp8266如何自定义回调参数给微信,实现绑定设备第一步!(附带源码)

    [微信小程序控制硬件第1篇 ] 全网首发,借助 emq 消息服务器带你如何搭建微信小程序的mqtt服务器,轻松控制智能硬件! [微信小程序控制硬件第2篇 ] 开始微信小程序之旅,导入小程序Mqtt客户 ...

  3. libevent源码深度剖析

    原文地址:http://blog.csdn.net/sparkliang/article/details/4957667 libevent源码深度剖析一 --序幕 张亮 1 前言 Libevent是一 ...

  4. libevent源码深度剖析十一

    libevent源码深度剖析十一 --时间管理 张亮 为了支持定时器,Libevent必须和系统时间打交道,这一部分的内容也比较简单,主要涉及到时间的加减辅助函数.时间缓存.时间校正和定时器堆的时间值 ...

  5. 《AngularJS深度剖析与最佳实践》一第1章 从实战开始

    本节书摘来自华章出版社<AngularJS深度剖析与最佳实践>一书中的第1章,作者 雪狼 破狼 彭洪伟,更多章节内容可以访问云栖社区"华章计算机"公众号查看 第1章 从 ...

  6. 深度剖析:Redis分布式锁到底安全吗?看完这篇文章彻底懂了!

    ‍‍‍‍‍‍‍‍‍‍‍‍阅读本文大约需要 20 分钟. 大家好,我是 Kaito. 这篇文章我想和你聊一聊,关于 Redis 分布式锁的「安全性」问题. Redis 分布式锁的话题,很多文章已经写烂了 ...

  7. [Android] Toast问题深度剖析(二)

    欢迎大家前往云+社区,获取更多腾讯海量技术实践干货哦~ 作者: QQ音乐技术团队 题记 Toast 作为 Android 系统中最常用的类之一,由于其方便的api设计和简洁的交互体验,被我们所广泛采用 ...

  8. Mysql binlog应用场景与原理深度剖析

    1 基于binlog的主从复制 Mysql 5.0以后,支持通过binary log(二进制日志)以支持主从复制.复制允许将来自一个MySQL数据库服务器(master) 的数据复制到一个或多个其他M ...

  9. SQL Server性能调优之执行计划深度剖析 第二节 执行计划第一次实践

    SQL Server性能调优之执行计划深度剖析 第二节 执行计划第一次实践 前言:自从上一篇文章发出之后,收到了很朋友的关注.很多朋友要求多多实践,而不是纯粹的理论.确实,从打算出这个系列开始,我就本 ...

  10. 嵌入式网络那些事LwIP协议深度剖析与实战演练pdf

    下载地址:网盘下载 <嵌入式网络那些事:LwIP协议深度剖析与实战演练>面向网络TCP/IP协议初学者以及大量嵌入式网络开发人员,从当下流行的嵌入式网络协议栈LwIP的源代码入手,详细讲解 ...

最新文章

  1. Linux那些事儿之我是Sysfs(3)设备模型上层容器
  2. java js隐藏_Javascript匿名函数是否仍然可见? (使用Java applet,这是一种隐藏JS代码的方法)...
  3. torchtext建立词表build_vocab()时使用自己的word2vec模型
  4. 使用dlib 进行人脸识别
  5. 如何做一个国产数据库系统(一)
  6. 【angularjs】【学习心得】路由继续研究篇
  7. Kettle:创建资源库
  8. 百度题库西安交大程序C语言,程序设计基础试题(西安交大).doc
  9. 层次分析法详细讲解(小白必看电脑查看)
  10. 2008评估过期 server sql_SQLServer2008R2数据库评估版已经过期解决办法.doc
  11. sip协议的超时机制
  12. 中标麒麟如何安装mysql_中标麒麟操作系统安装mysql5.7.21
  13. Redis源码学习(11),t_hash.c 学习(二),hget 相关命令学习
  14. 关于道士打架的一些看法
  15. CSS之transform的translate平移属性【2D】(一)
  16. 程序员专属手机壁纸来了。。。
  17. Android 高级UI解密 (三) :Canvas裁剪 与 二维、三维Camera几何变换(图层Layer原理)
  18. 程序设计——第七周作业(Floyd:胜负未知场数;dijkstra:猫猫快线最快线路;SPFA:城市收税)
  19. 两行轨道报(TLE)简介
  20. 数据分析师三个等级_数据分析课|这三个等级的数据分析师报考条件,一定是你需要的...

热门文章

  1. vss备份,使用批处理,每次只能备份当前打开的项目,怎么才能备份所有的项目呢...
  2. 12个用一条语句写成的有关日期函数
  3. mt管理器怎么运行HTML文件,MT管理器怎么修改游戏数据 MT管理器修改内购教程
  4. 汽车维修企业管理【14】
  5. ALGORITHMIC COMPOSITION AS A MODEL OF CREATIVITY
  6. 乐优商城_第5章_-vue入门
  7. 2011QQ搞笑个性签名:小弟不才,大名耶稣.小名上帝.法号如来
  8. Day146.概述及环境搭建 -Linux
  9. 【pandas】reset_index函数详解
  10. 霍金实在论中的实践论