前言
這段時間參與了一款與藍芽外設互動的專案, 以前沒有涉及過資料傳輸方面的開發, 踩了不少坑, 同時也學到了很多東西. 此時, 專案也即將進入尾聲, 有時間把這些記錄一二. 本人才疏學淺, 如有錯誤,大佬輕噴.
BLE4.0開發
這方面網上的Demo一大堆, 暫時不做太多的贅述, 只對坑點做一個摘要.
- 需求使然, 要對裝置的接近遠離有一個比較精確的計算, 使用的方案是對藍芽的訊號強度進行分析. 然而, 訊號強度的波動值較大, 很難得出較為精確的值, 於是乎需要較多的訊號值進行計算, iOS可以通過
[self.peripheral readRSSI]
來讀取訊號值強度, 但是該方法最快1s只能返回一次, 如果需要更快速的獲取訊號值強度, 想到的方法只有使用定時器強行執行scanForPeripheralsWithServices
方法了, 也許iBeacon會是個不錯的選擇, 但是這邊硬體並不支援,也沒有進行實際的測試. - 獲取藍芽外設mac地址的問題, 眾所周知的隱私問題, 目前iOS並不能獲取到. 解決方法是讓硬體工程師把mac地址寫入到廣播包中的
kCBAdvDataManufacturerData
這個key
中,在發現外設的回撥centralManager: didDiscoverPeripheral: advertisementData: RSSI:
中的advertisementData
引數中獲取. (一定要寫在對應kCBAdvDataManufacturerData
的欄位中, 發現該裝置廣播包中沒有這個key
, 讓硬體工程師換一個欄位再試試, 各個廠家的藍芽模組不一樣, 很可能硬體工程師寫錯了)
資料傳輸
首先是平臺方面的人定好了資料傳輸協議, 我們按協議進行拼接, 然後使用拼接好的資料與外設進行互動.
資料傳輸協議一般分為包頭和包體, 包體中也許還會進行類似的巢狀. 協議中會定義傳輸 的資料型別, 比如拼接過程中需要傳入包體的長度(無符號雙位元組整型), 我們一般會用int
取到長度length, 這時候需要把int
轉化為兩個Byte.
// int轉兩個位元組Byte
+ (NSData *)dataFromShort:(short)value {
Byte bytes[2] = {};
for (int i = 0; i < 2; i++) {
int offset = 16 - (i + 1) * 8;
bytes[i] = (Byte) ((value >> offset) & 0xff);
}
NSData *data = [[NSData alloc] initWithBytes:bytes length:2];
return data;
}
// int轉四個位元組Byte
+ (NSData *)dataFromInt:(int)value {
Byte bytes[4] = {};
for (int i = 0; i < 4; i++) {
bytes[i] = (Byte)(value >> (24 - i * 8));
}
NSData *data= [[NSData alloc] initWithBytes:bytes length:4];
return data;
}
複製程式碼
更多:
一般也會有時間戳, 需要拼接這裡提供兩種格式的時間戳.
// 6個位元組的時間戳
+ (NSData *)currentTimeData {
Byte bytes[6];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSUInteger unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
NSDateComponents *dateComponent = [calendar components:unitFlags fromDate:[NSDate date]];
int year =(int) [dateComponent year];
int month = (int) [dateComponent month];
int day = (int) [dateComponent day];
int hour = (int) [dateComponent hour];
int minute = (int) [dateComponent minute];
int second = (int) [dateComponent second];
bytes[0] = year - 2000;
bytes[1] = month;
bytes[2] = day;
bytes[3] = hour;
bytes[4] = minute;
bytes[5] = second;
return [[NSData alloc] initWithBytes:bytes length:6];
}
// 4個位元組的時間戳
+ (NSData *)timestampData {
int time = [[NSDate date] timeIntervalSince1970];
return [NSData dataFromInt:time];
}
複製程式碼
常用演算法
PRF演算法
首先來看一下PRF演算法,這個之前一直想在網上download
一份, 奈何實在沒有找到. 猜測是前端一般不會用到, 安卓同事倒是從平臺處得到了封裝好的jar包可以使用. iOS這邊只能自己動手實現, 下面先看一下PRF演算法的實現原理:
實現如下:
+ (NSData *)tf_prfSecret:(NSData *)secret label:(NSData *)label seed:(NSData *)seed {
// 講label與seed進行拼接
NSMutableData *seedData = [NSMutableData data];
[seedData appendData:label];
[seedData appendData:seed];
return [self tf_prfSecret:secret seed:seedData];
}
+ (NSData *)tf_prfSecret:(NSData *)secret seed:(NSData *)seed {
NSMutableData *prfData = [NSMutableData data];
NSMutableData *mutableData = [NSMutableData dataWithData:seed];
NSData *AnData = [NSData dataWithData:seed];
// 需要prf演算法得出的長度
// kStaticPrfMinimumLength: 根據需求需要寫入
while (prfData.length < kStaticPrfMinimumLength) {
AnData = [self hmacSHA256WithSecret:secret content:AnData];
mutableData = [NSMutableData dataWithData:AnData];
[mutableData appendData:seed];
NSData *hmacData = [self hmacSHA256WithSecret:secret content:mutableData];
[prfData appendData:hmacData];
}
return prfData;
}
複製程式碼
HMAC_SHA256演算法
PRF演算法中, HMAC_hash演算法是可選的, 這邊使用的是SHA256, 實現如下:
// hmac sha256演算法
+ (NSData *)hmacSHA256WithSecret:(NSData *)secret content:(NSData *)content {
unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, secret.bytes, secret.length, content.bytes, content.length, cHMAC);
NSData *HMACData = [NSData dataWithBytes:cHMAC length:sizeof(cHMAC)];
// 將data按string輸出
// const unsigned char *buffer = (const unsigned char *)[HMACData bytes];
// NSMutableString *HMAC = [NSMutableString stringWithCapacity:HMACData.length * 2];
// for (int i = 0; i < HMACData.length; ++i){
// [HMAC appendFormat:@"%02x", buffer[i]];
return HMACData;
}
複製程式碼
AES128加密
網上這樣的加密真是一大堆,這邊因為是與硬體資料傳輸, 所以對資料進行的加密的金鑰與iv
向量也大概率是直接便是Data而不是常用的NSString, 這邊對兩種型別的key
與iv
都做了實現, 按實際情景使用.
- (NSData *)tf_encryptAES128WithKey:(NSString *)key iv:(NSString *)iv {
return [self tf_AES128Operation:kCCEncrypt key:key iv:iv];
}
- (NSData *)tf_decryptAES128WithKey:(NSString *)key iv:(NSString *)iv {
return [self tf_AES128Operation:kCCDecrypt key:key iv:iv];
}
- (NSData *)tf_encryptAES128WithKeyData:(NSData *)keyData ivData:(NSData *)ivData {
return [self tf_AES128Operation:kCCEncrypt keyData:keyData ivData:ivData];
}
- (NSData *)tf_decryptAES128WithKeyData:(NSData *)keyData ivData:(NSData *)ivData {
return [self tf_AES128Operation:kCCDecrypt keyData:keyData ivData:ivData];
}
/**
*
* @param operation kCCEncrypt:加密 kCCDecrypt:解密
* @param key 公鑰t:
* @param iv 偏移量
*
* @return 加密或者解密的NSData
*/
- (NSData *)tf_AES128Operation:(CCOperation)operation key:(NSString *)key iv:(NSString *)iv {
char keyBytes[kCCKeySizeAES128 + 1]; //kCCKeySizeAES128是加密位數 可以替換成256位的
// bzero函式:從字串第一位開始置0, 第二個引數代表置0的位數
// 相當於memset(keyBytes,0x00,sizeof(keyBytes));
bzero(keyBytes, sizeof(keyBytes));
[key getCString:keyBytes maxLength:sizeof(keyBytes) encoding:NSUTF8StringEncoding];
// iv
char ivBytes[kCCBlockSizeAES128 + 1];
bzero(ivBytes, sizeof(ivBytes));
[iv getCString:ivBytes maxLength:sizeof(ivBytes) encoding:NSUTF8StringEncoding];
return [self tf_cryptAES128Operation:operation keyBytes:keyBytes ivBytes:ivBytes];
}
- (NSData *)tf_AES128Operation:(CCOperation)operation keyData:(NSData *)keyData ivData:(NSData *)ivData {
char keyBytes[kCCKeySizeAES128 + 1];
bzero(keyBytes, sizeof(keyBytes));
[keyData getBytes:keyBytes length:sizeof(keyBytes)];
char ivBytes[kCCKeySizeAES128 + 1];
bzero(ivBytes, sizeof(ivBytes));
[ivData getBytes:ivBytes length:sizeof(ivBytes)];
return [self tf_cryptAES128Operation:operation keyBytes:keyBytes ivBytes:ivBytes];
}
- (NSData *)tf_cryptAES128Operation:(CCOperation)operation keyBytes:(void *)keyBytes ivBytes:(void *)ivBytes {
size_t bufferSize = self.length + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
size_t numBytesCrypted = 0;
/*
CCOptions 預設為CBC加密
選擇ECB加密填: kCCOptionPKCS7Padding | kCCOptionECBMode
kCCOptionPKCS7Padding: 7填充
直接填0x0000: 就是No padding填充
*/
CCCryptorStatus cryptorStatus = CCCrypt(operation, kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
keyBytes,
kCCKeySizeAES128,
ivBytes,
self.bytes,
self.length,
buffer,
bufferSize,
&numBytesCrypted);
if(cryptorStatus == kCCSuccess) {
NSLog(@"Crypt Successfully");
NSData *result = [NSData dataWithBytesNoCopy:buffer length:numBytesCrypted];
/* 轉16進位制字串
Byte *resultBytes = (Byte *)result.bytes;
NSMutableString *outPut = [[NSMutableString alloc] initWithCapacity:result.length * 2];
for (int i = 0; i < result.length; i++) {
[outPut appendFormat:@"%02x", resultBytes[i]];
}
*/
return result;
} else {
NSLog(@"Crypt Error");
free(buffer);
return nil;
}
}
複製程式碼
附上 原始碼Demo