最近动态

学习

啥都不懂…

这学期好像做了个死,选了一堆学不会的课~

什么机器学习啊(全是数学,全是概率,已弃疗)

什么Linux内核啊(除了学会了装内核,使用gdb,和装逼,别的都没听懂)

什么软件安全性分析啊(我自己实验永远做不出老师的效果)

安全通信啊(这个我还听得懂……)

报名了华为软件挑战赛(也弃疗了)

为了要听懂Linux内核的课和一部分软件安全性分析的课,我借了买了一些计算机组成原理,编译原理,Binary Hack的书.

好像也没咋看懂~

然后要做推荐算法,就看数据挖掘,好像还是没咋看懂~

所以我承认我笨了…

阅读剩下更多

编程

UIWindow 之坑

Context

做一个Pop View有两种方法.

1.用一个UIView盖在当前UIView或者UIWindow的最上面

2.做一个UIWindow,让他变成keyWindow

第一种的好处是…没想到什么特别的好处,大概写起来简单吧.

第二种的好处是,可以扔个ViewController在里面,哈哈哈.

而且第二种,我觉得做起来结构比较清晰.这个Pop View里面做的事情,可以全部封装好,包括显示和消失.
而使用UIView的话,做显示就一定需要把superview作为参数传入,才可以显示,这个还是蛮麻烦的,特别是当我
想做一个全局接收通知,并在UI上显示的东西的时候.由于很难确定当前最顶上的UIView是什么,所以还是UIWindow比较好.

Bug

然而,在水壶架iOS端中,遇到了一个很奇怪的问题.这个问题貌似只在iOS9.3.5之前的系统上出现.对于之后的系统,没有出现这个问题.

从Leancloud的错误报告中看到,这是一个消息传到了空指针的fetal错误.

那就好好找找,为啥变成了空指针吧.

Come on, Where are you

由于一开始这个问题只在9.3.5的手机上出现了,而我和一大票iOS10都没有这个问题.所以我甚至怀疑,这个是手机的问题.

直到我发现所有的老系统,都爆了!我才慌了.

当弹出这个搜索框,再让他消失的时候,做任何的触摸操作,整个app就挂掉了.

所以,问题肯定就在dismiss方法里面!

-(void)disappear
{
    [UIView animateWithDuration:0.3f animations:^{
        self.view.alpha=0;
    } completion:^(BOOL finished) {
        _displayWindows.rootViewController=nil;
            _displayWindows.hidden=YES;
            _displayWindows=nil;
        [[BluetoothManager sharedManager]stopScan];
        [BluetoothManager sharedManager].delegate=nil;
        if (self.delegate) {
            self.delegate=nil;
        }
    }];
}

每次disappear之后,我就中断了整个app,查看他的UI结构,发现有一个不听话的UIWindow还停留在哪儿~

而我明明都把他设置为hidden了,按理说它已经不是keyWindow了~可是他貌似还在.

而且为什么这个东西在iOS 10就没问题了呢~

于是我就去查了查iOS 10的开发这手册,看看他们都做了些什么改变,导致iOS 10不会闪退.

扯远了.

所以说,当这个alert级的UIWindow设置为hidden的时候,没有key window了……

于是乎Touch Event进来的时候,不知道应该给谁了,就爆掉了……

所以所以,得把之前的UIWindow重新恢复key window的地位.

之后改成这样了:

-(void)disappear
{
    [UIView animateWithDuration:0.3f animations:^{
        self.view.alpha=0;
    } completion:^(BOOL finished) {
        _displayWindows.windowLevel=UIWindowLevelNormal;
        _displayWindows.rootViewController=nil;
        [self.view removeFromSuperview];
            [_displayWindows setHidden:YES];
            _displayWindows=nil;
        [[BluetoothManager sharedManager]stopScan];
        [BluetoothManager sharedManager].delegate=nil;
        if (self.delegate) {
            self.delegate=nil;
        }
    }];
    if (_preWindows) {
        [_preWindows makeKeyAndVisible];
        _preWindows=nil;
    }
}

当然这样一改,在show的时候也要做一些修改了~得把之前的UIWindow给记录下来,不然就懵逼咯.

阅读剩下更多

编程

Block--方便而危险

Context

Block真的很好用对不对?记得很早以前用ASIHttp来做网络请求的时候,得用delegate,然后每次请求的
delegate回调中都要做一下判断,才能知道这个回调是回应了具体哪一次请求,这还是蛮麻烦的.

不过Block出现了,对网络请求的response操作,就可以直接写在block中,不用去判断这个response谁否对应与当前这个request.

除了网络请求,Alert的回调啊,asyncsocket啊,之类的异步操作,用了block都特别的方便.

Situation

然而,有一天我们在炫轮iOS端的DIY界面中发现了一个bug,而这个bug预示着,
我有实例没有成功释放.

Reason

于是我就一行一行的找,感觉没有什么地方出现.但是我发现了一个现象:

我在使用SCLAlertView了之后,就出现了这个bug,所以我就重点查看SCLAlertView的相关代码,
经过排查,我觉得问题只能出现在dismissBlock上.查看了dismissBlock的声明代码,他是个strong~

我想,这里应该是出现了一个循环引用…block中的内容,在声明的时候,会copy一份当时的数据.当AlertView 消失的时候,
调用了dismissBlock,然而,dismissBlock中的内容有引用当前的ViewController,所以.

他们互相引用了!

所以只能在dismissBlock的最后加一行,把这个dismissBlock设置为nil.解决了~

End

这次要不是遇到了这个坑,说不定永远也不会去仔细看Block实现的原理~

关键就是这个copy,所以在block中如果引用了(强引用了该block的)类,就一定要注意,检查会不会出现循环引用!

阅读剩下更多

编程

蛋疼的GATT权限

Context

定位水壶架App的iOS端已经完成了80%,在考完矩阵之前,只要把android端的网络和蓝牙交互做完,就可以没什么太大的压力咯.

然而,在搞android端的时候,还是遇到了很多很多问题的.

第一坑

几天前,当我把iOS端蓝牙配对的代码,翻译为Java版本之后,本来想,肯定会轻轻松松,正正常常的运行了.

但是,在连接上定位器之后,总是会卡在搜索服务或者写入xxx这个阶段,然后就断线了.

当时,我觉得,可能是一些小问题,可能是我写漏了什么东西,所以就卡住了.

也怀疑过是Google的亲儿子Nexus蓝牙硬件不给力~

然后就痛苦的去复习了几天矩阵理论…………

但是,就在两个小时之前,我一步一步调试的时候,发现了一个奇怪的现象.

public void onServicesDiscovered(BluetoothGatt gatt, int status);

BluetoothGattCallback中,搜索到service的时候,竟然没有我们自己的service!

所以for循环就那么做完了……什么service都没有找到,最后就被关闭连接了.

这时候,我开始怀疑人生了

难道说,这蓝牙是为iOS设计的!?

赶紧整了一个锤子过来测测.发现锤子是可以找到所有的service的……

难道说,这蓝牙不是为Google设计的!?

这时候,我突然想到了一个问题.和之前几天的实验,可能有点儿关系.

很久很久的几天前

添哥找我说,蓝牙可以设置一个加密密钥,这样子传输数据,就是加密的了.(虽然说,蓝牙和WiFi一样,是要连接之后才能通信的,但是,无线嘛,肯定是要被窃听的)

于是我们就试验了一下,但是蓝牙加密之后,需要手动在手机上点击配对按钮,才可以进行加密通信.

由于android端,这个配对不是特别的明显,不像iOS那么粗暴地弹出来.所以我们最后没有使用这个方案.

这里还可以顺便说说加密

小时候,以为加密的意思就是,给数据加了一把锁.只要用密码把锁打开了,就可以访问数据了.(真单纯)

原来,加密是把数据都给翻译了一遍.即使知道加密解密的算法,使用错误的密钥,可以得到数据,但是这数据,肯定是错的.

当蓝牙加密通信的时候,会分配一个密钥.咱们配对蓝牙鼠标,蓝牙键盘的时候,也常常看到,需要输入个什么pin码,才能配对.一旦这配对了,之后通信就是加密的了.

(假设蓝牙键盘不用配对,不加密通信……)那就是无线的按键记录器啊!

总而言之

这搜索不出我们需要的service就是这个配对的,当我进入系统蓝牙设置,把这个设备的配对给取消了之后,就正常了~.~

第二坑的Context

BLE的GATT的Characteristic有权限控制.

大概就是Read,Write,WriteWithoutResponse,Notify这四种常见权限.

他们用一个8位的flag来控制.因为不是重点,所以懒得讲了.

第二坑

在我解决了Service Discover的问题了之后,我觉得,这下一定可以顺利地跑下去了!

结果卡在了一个写入之后的读取上…

本来,在写入一个数据之后,需要去读取一个反馈的.但是不知道为什么,没有去读取这个数据.

仔细阅读Monitor里面的log.可以发现,之前的读取,都会调用系统的readCharacteristic()函数,而这一次的读取却没有读取,这是为啥子嘞?

权限!

我们有一个Characteristic在写入之后,需要读取反馈.所以需要Read的权限.

然而,在这个Characteristic没有Read的权限的情况下,iOS端竟然还是可以正确的读取到数据!

而Android直接忽略的readCharacteristic()这个函数调用……

虽然说,这硬件是我们自己设计的,讲道理的话,在连接设备之后,并不需要去检查各种权限的.(除非这个硬件是被山寨的)

但是,加上一个权限验证的话,或许可以提高一些软件的稳健性呢?说不定可以让这个系统可以在遇到山寨货的情况下,可以继续正常的工作下去呢~

End

2016马上要结束了,每年都能更加深入的了解一些蓝牙,真的是好刺激!

阅读剩下更多

编程

初用Volley

Context

水壶架iOS端终于撸完了

由于我Android的界面写的烂的一逼,所以只能拜托xiaopo来帮我撸界面了.

首要目标是干掉蓝牙类,不过蓝牙绑定设备的时候会需要网络请求.

那就顺便把网络也给撸掉吧~

基本用法

1.需要一个请求队列

RequestQueue mQueue = Volley.newRequestQueue(context);

2.好了直接撸请求吧

Form-Data这么搞

StringRequest stringRequest = new StringRequest("一个URL",new Response.Listener<String>() {
        @Override
        public void onResponse(String response) {
               Log.d("Network OK", response);
        }
        }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
               Log.e("Network Fail", error.getMessage(), error);
        }
});

JSON这么搞

JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("http://m.weather.com.cn/data/101010100.html", null,
    new Response.Listener<JSONObject>() {
        @Override
        public void onResponse(JSONObject response) {
            Log.d("Network OK", response.toString());
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            Log.e("Network Fail", error.getMessage(), error);
        }
    });

Q1 请求是form-data 响应是json

不过这里遇到了一个问题.

我们服务器大佬坑我,请求只能解析Form-Data的,返回的响应是Json

于是只能手动转换了

private static StringRequest FormDataRequest(String url, int method, Map parameter, VKNetworkSucceedBlock succeedBlock, VKNetworkFailBlock failBlock) {
    StringRequest request=new StringRequest(method, url, new Response.Listener<String>() {

        @Override
        public void onResponse(String response) {
            try {
                JSONObject jsonResponse = new JSONObject(response);
                if (jsonResponse.getInt("status") == 10086) {
                    //认证失败
                    failBlock.requestFail(new Exception("SessionExpired"));
                } else {
                    succeedBlock.getResponse(jsonResponse);
                }
            } catch (JSONException e) {
                e.printStackTrace();
                failBlock.requestFail(e);
            }
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            if (error.networkResponse.statusCode == 10000) {
                //认证失败
                failBlock.requestFail(new Exception("SessionExpired"));
            } else
                failBlock.requestFail(error);
        }}){
        @Override
        protected Map<String, String> getParams() throws AuthFailureError {
            return parameter;
        }
    };
    queue().add(request);
    return request;
}

用JSONObject自带的牛逼的初始化方法来解析响应

好不容易,把网络请求跑通了.

才发现,一个劲的403.

原来,Volley本身不处理Cookie的!!

啊席巴

于是就要手动处理

private static StringRequest FormDataRequest(String url, int method, Map parameter, VKNetworkSucceedBlock succeedBlock, VKNetworkFailBlock failBlock) {
    StringRequest request=new StringRequest(method, url, new Response.Listener<String>() {

        @Override
        public void onResponse(String response) {
           //这些你都看过了
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            //这些你都看过了
            }){
        @Override
        protected Map<String, String> getParams() throws AuthFailureError {
            return parameter;
        }

        @Override
        public Map<String, String> getHeaders() throws AuthFailureError {
            Map<String,String> header=super.getHeaders();
            String cookie=getCookie();
            if (cookie!=null){
                header.put("Set-Cookie",cookie);
                //手动从把Cookie放到Header中
            }
            return header;
        }

        @Override
        protected Response<String> parseNetworkResponse(NetworkResponse response) {
            refreshCookie(response.headers);
                //手动从Response的Header中获取Cookie
            return super.parseNetworkResponse(response);
        }
    };
    queue().add(request);
    return request;
}

这其中的Cookie持久化什么的,就不说了.它不是重点.

而重点是……

Final 我放弃了

当Response的Header中有多个Set-Cookie字段时,只会被解析出来一个!

如果需要把所有的Cookie都拿出来的话,需要修改Volley源码!

然而,我使用Gradle来添加Volley工程的,所以,你逗我吗!

于是乎,我就放弃了.

姜还是老的辣

大三的时候做精益防伪,使用了Android-Async-Http

这个很牛逼,功能很强大,还自动缓存了Cookie,真是替我省心.

还好前面撸接口的时候,封装的比较好,所以没有改动太多的东西.

Tip

封装了两个基本网络操作函数

public static void getMethodFormData(String url, Map<String,String> parameter, VKNetworkSucceedBlock succeedBlock, VKNetworkFailBlock failBlock){
    getViaLoopj(url,parameter,succeedBlock,failBlock);
}

public static void postMethodFormData(String url, Map<String,String> parameter, VKNetworkSucceedBlock succeedBlock, VKNetworkFailBlock failBlock){
    postViaLoopj(url,parameter,succeedBlock,failBlock);
}

再来一发Android-Async-Http的实例(封装过了)

public static void postViaLoopj(String url, Map<String,String> params,VKNetworkSucceedBlock succeedBlock,VKNetworkFailBlock failBlock) {
    client.post(url, new RequestParams(params), new JsonHttpResponseHandler(){
        @Override
        public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
            if (statusCode==403){
                failBlock.requestFail(new Exception("SessionExpired"));
            }else
            {
                succeedBlock.getResponse(response);
            }
        }
        @Override
        public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) {
            super.onFailure(statusCode, headers, throwable, errorResponse);
            if (statusCode==403){
                failBlock.requestFail(new Exception("SessionExpired"));
            }else{
                failBlock.requestFail(new Exception(errorResponse.toString()));
            }
        }
        @Override
        public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) {
            super.onFailure(statusCode, headers, responseString, throwable);
            if (statusCode==403){
                failBlock.requestFail(new Exception("SessionExpired"));
            }else
                failBlock.requestFail(new Exception(responseString));
        }
    });
}

定义了两个interface

public interface VKNetworkSucceedBlock {
    void getResponse(JSONObject responseObject);
}

public interface VKNetworkFailBlock {
    void requestFail(Exception error);
}

于是乎做网络请求,就方便的很了

public static void mLogin(String email, String verify, boolean isChina, NetworkUtils.VKNetworkSucceedBlock succeed, NetworkUtils.VKNetworkFailBlock fail){
    HashMap<String,String> map=new HashMap<String, String>(){{
            put("hhh","123@163.com");
            put("jjj","3434");
            put("kkk",String.valueOf(1));
        }};
      NetworkUtils.postMethodFormData(UrlManager.URL_Login,map,succeed,fail);
}

最后网络的效果,就留到具体调用他的类去实现吧.

所以,封装了一遍就是好瞧把你能耐的,更换网络框架,不需要跑到每个Activity啊,Class里面去改.

response也是一套,

顶层完全不用去关注,底层的网络是如何实现的.(好像段老师还是郝老师这么说过)

当然我相信,来看这篇文章的大佬们,肯定早就知道这个了

阅读剩下更多

编程

用状态机来实现复杂操作

Context

这次新产品的蓝牙绑定过程有点儿复杂

与炫轮作比较

炫轮只有一个characteristic,不同的操作之间没有顺序关系,都是并列的

新产品有多个characteristic,每个characteristic有不同的意义和不同的指令,每个操作都有先后顺序

Solution

突然想起来,本科的微处理器,还是数字电路课,讲过状态机这个玩意儿.我觉得,可以拿来搞一搞.这样清晰明了,可以省去很多中间变量.

比如

BOOL isConnecting;
BOOL isDiscovering;
BOOL didWriteXXX;
BOOL didReadXXX;
.....

使用了状态机,并且规定好了状态转移的条件,那么,整个系统就一定在这个状态列表中滚.不会出现位置状态,导致程序跑飞.

因为从状态A–>状态B,只有当前条件是xxxx的时候才会实现.这时候不用再去管其他的变量,其他的事件.

使用了状态机,可以轻松地对已有逻辑进行扩充,因为只要拿出,状态转移图来.找到需要添加的地方,就能确定在代码中,有多少地方需要修改,如此一来,就不用在需要该需求的时候,重新阅读已有代码了!

使用了状态机,一般只需要一个记录状态的变量就够了,而且重写这个变量的Setter方法,还可以方便做一些其他的事情:

-(void)setStage:(VKLocatorPairState)stage{
    NSLog(@"pairing status %@",[PublicHelper localizedStringForPairStatus:stage]);
    //发送通知
    //如果是终止状态,发送完通知后,回到原始状态
    [[NSNotificationCenter defaultCenter]postNotificationName:kPairLocatorStatusUpdated object:@(stage)];
    if (stage==1) {  //透露太多了,要被周老板干掉的
        //透露太多了,要被周老板干掉的
    }
    if (stage==1) {  //透露太多了,要被周老板干掉的
        //透露太多了,要被周老板干掉的
    }
    if (stage==1) {  //透露太多了,要被周老板干掉的
        //透露太多了,要被周老板干掉的
    }
}

对,转移状态,和UI可以分开处理,免得以后要界面逻辑,还要去状态转移中找代码.

变量和类名都起得很不要脸啊,小伙子

总之

这一篇,看起来像硬广啊.

再总之

Android端也可以很轻松的做移植.

所以做Android的这个操作,差不多就是在做翻译工作,把Objective-C 翻译为 Java就好了.

阅读剩下更多

编程

Android上的蓝牙

Context

如果说Android和iOS一样,一出新版本,每个用户都回去升级,那我就不用写这篇咯

先说说iOS端的蓝牙扫描

@param serviceUUIDs A list of <code>CBUUID</code> objects representing the service(s) to scan for.
//serviceUUIDs是一个过滤NSArray
//根据蓝牙外设的广播信号中的Service UUID来过滤
- (void)scanForPeripheralsWithServices:(nullable NSArray<CBUUID *> *)serviceUUIDs options:(nullable NSDictionary<NSString *, id> *)options;

假定说,我们的app需要过滤两个产品的蓝牙信号.那么只要在serviceUUIDs里面填写他们特有Service UUID.

然后,在代理方法中做区分.

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI{
    // 从广播信号中获取Service UUID 的列表
    NSArray *serviceUUIDs=advertisementData[CBAdvertisementDataServiceUUIDsKey];
    //做一下判断
    if ([serviceUUIDs containsObject:[CBUUID UUIDWithString:@"FFFF"]]){
        //欧耶,这是我们的产品1
    }else if ([serviceUUIDs containsObject:[CBUUID UUIDWithString:@"FFFF"]]){
        //欧耶,这是产品2
        //直接else当然也可以.但是要保险一些嘛
    }
}

方便,真是方便

正题

Android中,貌似没有那么轻松

当然,这就要提起Context里面说的事情了.

BLE是android 4.3引入的,所以最早拥有蓝牙更能的版本,就是API 18.

大概是由于API 18中,蓝牙搜索的方法,有点儿蛋疼,所以,他们改了新的方法吧?

API 21

先看看API 21怎么搜索蓝牙的.

BluetoothAdapter的一个静态方法

void startScan (List<ScanFilter> filters,ScanSettings settings,ScanCallback callback)
参数 参数 解释
filters List ScanFilters for finding exact BLE devices.
settings ScanSettings Settings for the scan.
callback ScanCallback Callback used to deliver scan results.

其中ScanFilter很牛逼

Service UUIDs which identify the bluetooth gatt services running on the device.
Name of remote Bluetooth LE device.
Mac address of the remote device.
Service data which is the data associated with a service.
Manufacturer specific data which is the data associated with a particular manufacturer.

这过滤方法,可比iOS的搜索强多了

然而,这个是API 21才有的……

API 18

由于,咱们要覆盖所有的,拥有BLE功能的手机用户

所以,只能用API 18了.

boolean startLeScan (UUID[] serviceUuids, BluetoothAdapter.LeScanCallback callback);

这过滤,和iOS一样了,只能用serviceUUID来过滤.

不过,通过我的实际测试,我发现serviceUuids填写的不能是不同设备的service uuid.

这样做,总是会显示没有match uuid list (有机会再好好研究一下)

那过滤两种设备,可以选择,做两遍这个方法,给不同的callback,好像还省去了用if去判断设备这个工作.

但是,如果有一大排设备需要去过滤呢……

boolean startLeScan (BluetoothAdapter.LeScanCallback callback);

直接全部搜索吧……还过滤啥呀

手动解析广播数据,进行分类,做不同的操作

BluetoothAdapter.LeScanCallback bluetoothScanCallback = new BluetoothAdapter.LeScanCallback() {

    @Override
    public void onLeScan(BluetoothDevice bluetoothDevice, int i, byte[] bytes) {
        //已经搜索过的设备,就不用重新解析啦
        if (locatorList.containsKey(bluetoothDevice.getAddress())){
            LocatorPeripheral peripheral=locatorList.get(bluetoothDevice.getAddress());
            peripheral.updateInformation(i);
        }else if(keyList.containsKey(bluetoothDevice.getAddress())){
            KeyPeripheral peripheral=keyList.get(bluetoothDevice.getAddress());
            peripheral.updateInformation(i);
        }else if(ignoreList.contains(bluetoothDevice)){
            return;
        } else {
            //这里开始重新解析
            VKPeripheral peripheral = VKPeripheral.createPeripheral(i,bytes,bluetoothDevice);
            if (peripheral instanceof LocatorPeripheral){
                //这是设备1
                locatorList.put(bluetoothDevice.getAddress(),(LocatorPeripheral) peripheral);
            }else if (peripheral instanceof KeyPeripheral){
                //这是设备2
                keyList.put(bluetoothDevice.getAddress(),(KeyPeripheral) peripheral);
            }else {
                //这不是咱们的东西
                ignoreList.add(bluetoothDevice);
                return;
            }
            //通知UI

        }
    }
};

我水平有限,所以就是这样写的~

这里有个VKPeripheral 类名也是直接从iOS版移植过来了

用来根据广播信息来新建我自己的蓝牙设备实例.

他生成的KeyPeripheral和LocatorPeripheral都是他的子类.如果不是我们需要的设备,就加到忽略的列表中去.

还有几个小坑

1.从android 6.0开始,蓝牙扫描如果需要得到结果,就必须获得定位权限(你逗我的吧…凭什么呀)

所以要把这个加到Manifest里面去

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

2.还是android 6.0的锅. 由于6.0可以动态获取权限,所以上面这个权限,直接写在Manifest里面,说不定还没用呢…
还要手动的去获取权限

找个Activity加上这一段(不唯一)

if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION},1);
}else if(ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.BLUETOOTH)!=PackageManager.PERMISSION_GRANTED){
    ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.BLUETOOTH,Manifest.permission.BLUETOOTH_ADMIN},1);
}else {
    //你是不是不想用这个App!!
}

End

哎,灵活的Android呀.开放的Android呀.

阅读剩下更多

黑科技

图片的小秘密

搞了一下凝聚最新的招新题目,说起来我也算是凝聚的大粉丝了,5年了,每年都做.

每年都有图片的隐写术.

都是套路

我也从最早的只知道改个图片后缀名,用zip/unrar把它解压掉,到了后来的用了各种各样的新奇工具,对图片进行处理.

去年和之前的套路感觉差不多,好像是

--图片
|
|
--压缩包
    |
    |
    --图片--答案

只不过最后那个图片我没看懂它什么意思罢了~(那时候在考研…)

扑通掉坑里

今年的,竟然不套路了!

如果你也想搞搞看的话,先下载图片下来,不要看后面的文章

先安利一个工具foremost,以前用的binwalk只能算出偏移量,然后用了ultraEditor手动的分离文件…这个好累(虽然直接用dd命令也可以做,但是那时候我还太嫩~)

foremost不仅可以得到一个文件里面各个其他文件的偏移量,还可以将他们直接分离成单独的文件

foremost

可以看到,这次的图片里面依然有个rar!

兴高采烈的解压缩了它,看到一个flag.txt,又兴高采烈的打开了它.里面写着:

is this the flag??

感觉被骗了.不不不,感觉被套路了.

一脸懵逼,没有思路的时候,突然发现这个图片的缩略图是一个穿着黑丝的MM的腿,而打开之后显示的却是:

displayed

Mac OS X的Preview就是好~Linux和Windows都没有,喔哈哈哈哈哈哈

难道说…

缩略图!

马上去网上查了一下,图片和缩略图的关系~

原来缩略图不是系统根据原始图片生成的,而是在图片的EXIF中存着缩略图信息.

然后又跑过去查EXIF,在EXIF中发现了一些小秘密.

标签号 定义
0x010E 图像描述
0x0112 方向
0xA002 EXIF宽度信息
0xA003 EXIF高度信息
0x011A 水平分辨率
0x011B 垂直分辨率

EXIF高度和垂直分辨率,到底应该改那个呢?试一下就是知道了!

再次使用ultraEditor打开图片,检索A0 03,找到了原始写着的高度为512的hex:02 00.改成了1280的hex:05 00.保存!

结果发现,只是缩略图变大了,原始图片没变大~

不过在缩略图里面发现了一些红色的小点点,估计是flag!

再次用ultraEditor打开图片,检索01 1B,依然是02 00,把他修改成了05 00.保存!

成功了,果然是个黑丝福利

总结一下

隐写术实在是有意思~当然今年这个题目也不是无缘无故的出来的,据说是在微博上面,9个图片写着1,2,3…9.点击看大图的时候,却是帅哥.

做技术真是有趣~.~

在 CSI:Cyber 里面也有很多差不多的东西,分析员们也会查看图片的meta data(元数据),在里面发现了很多秘密.

最早网络大侠们把种子藏在图片里面,现在把图片藏在缩略图后面,真是越来越有意思了.看看明年的题目是什么吧~

阅读剩下更多

编程

微信小网页的安全性

微信网页的安全性

这篇文章其实很早以前就可以写一下了,但是当时担心自己的方案不够安全,所以,没有敢写.

为啥子要做防御呢?

因为,我们这次的产品涉及到了比较隐私的信息,严重点儿说,还会涉及到用户的安全.

所以,至少在前端,不能让第三方可以轻松的获取到用户的信息.

由于这次做的网页,不是单网页程序,所以,每个页面所需要的初始数据,都是在URL中体现的,所以第一步需要对URL进行防御

当然,我们也不希望用户在微信外浏览网页.因为这样他们可以看到请求的参数,而且也不利于我们控制入口~

直接开始吧

一个网页需要获取到点击用户的openid,需要通过微信用户访问https://open.weixin.qq.com/connect/oauth2/authorize?appid=公众号ID&redirect_uri=回调URL

用户点击了授权,或者以前早就授权过了,才会访问跳转的URL,并且带着openid的参数

这一块好像没什么可以攻击的,所以我也没做什么处理

服务器收到了redirectURL的请求的时候,是可以获取到openid的.假设,直接根据openid来返回数据,那么微信所在的手机如果网络数据被抓包.就可以很轻松的知道了如何使用接口不使用HTTPS

那么伪造openid来获取他人的数据,也变得简单了~所以,我给他加了密,用了很多很多位的对称加密算法,把openid做了加密.每次请求都需要openid和eid(我称为加密的id…)
,配对正确,才可以正常的请求,否则就送一个403的大礼包.

这上面算是最后一道防线吧?

以前参与精益防伪项目的时候,学了好多的保密措施,感觉还是蛮有用的~

当然,这一套还存是在问题的.比如说现在的密钥是固定的,如果根据服务器的Mac来生成,或者再建立一个动态的密钥.就可以防止一个服务器被攻破之后,
另外的服务器也被轻松攻破的尴尬现象了.

当然咯

我们不希望用户在微信外防伪网页,最好的方法就是,阻止用户点击右上角的按钮,选择里面的复制链接,分享给朋友,用浏览器打开……

还好Wechat JS SDK里面就做了这个功能,可以控制微信浏览器,隐藏那些按钮~

各位大大如果找到了漏洞,一定要告诉我呀!这个关系到用户的人身安全的!

阅读剩下更多

返回顶部