DTLS-PSK連接建立(基于Network框架)

<Network/Network.h>庫(kù)是iOS12以后蘋(píng)果才公開(kāi)的毫深,部分接口iOS13才可用。下面的代碼是DTLS連接控轿,基于純PSK建立的。具體的算法集可以根據(jù)需要切換拂封。

  1. DTLSNetworkManager.h文件
//
//  DTLSNetworkManager.h
//  SSDPDemo
//
//  Created by yuchern on 2019/11/15.
//  Copyright ? 2019 yuchern. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <Network/Network.h>

NS_ASSUME_NONNULL_BEGIN

#ifdef __IPHONE_13_0

typedef void (^DTLSMessageHandle)(NSData *_Nullable data, NSError *_Nullable error);

typedef void (^DTLSSessionCompletedHandle)(NSError *_Nullable error);

/*
 nw_connection_state_invalid = 0,
 @const nw_connection_state_waiting The connection is waiting for a usable network before re-attempting
 nw_connection_state_waiting = 1,
 @const nw_connection_state_preparing The connection is in the process of establishing
 nw_connection_state_preparing = 2,
 @const nw_connection_state_ready The connection is established and ready to send and receive data upon
 nw_connection_state_ready = 3,
 @const nw_connection_state_failed The connection has irrecoverably closed or failed
 nw_connection_state_failed = 4,
 @const nw_connection_state_cancelled The connection has been cancelled by the caller
 nw_connection_state_cancelled = 5,
 */
typedef void (^DTLSConnectStateHandle)(nw_connection_state_t state, NSError  *_Nullable error);

@interface DTLSNetworkManager : NSObject

/// Config nw_parameters which include pskId, psk, ciphersuite
/// @param pskId pskId
/// @param psk psk
/// @param ciphersuite ciphersuite
- (void)setDTLSParamWithPskId:(NSString *)pskId
                          psk:(NSString *)psk
                  ciphersuite:(tls_ciphersuite_t)ciphersuite;

/// Connect to host
/// @param host IP
/// @param port port
/// @param queue queue
/// @param stateHandle callback
- (void)connectDTLSToHost:(NSString *)host
                     port:(NSString *)port
                    queue:(dispatch_queue_t)queue
              stateHandle:(DTLSConnectStateHandle)stateHandle;

/// Cancel nw_connection and set nil
- (void)closeDTLSConnect;

/// Send message
/// @param message message
/// @param complete complete
- (void)sendDTLSMessage:(NSData *)message
               complete:(DTLSSessionCompletedHandle)complete;

/// Receive message
/// @param receiveMessageHandle callback
- (void)receiveDTLSMessage:(DTLSMessageHandle)receiveMessageHandle;

@end
#endif

NS_ASSUME_NONNULL_END
  1. DTLSNetworkManager.m文件茬射。
    代碼中包含了組包,如果你所做的項(xiàng)目數(shù)據(jù)頭不一樣冒签,需要根據(jù)具體修改在抛。
//
//  DTLSNetworkManager.m
//  SSDPDemo
//
//  Created by yuchern on 2019/11/15.
//  Copyright ? 2019 yuchern. All rights reserved.
//

#import "DTLSNetworkManager.h"


#ifdef __IPHONE_13_0

NSErrorDomain const DTLSConnectErrorDomain = @"com.home.DTLS.Network.connectHost.error";
NSErrorDomain const DTLSReceiveMessageErrorDomain = @"com..home.DTLS.Network.receiveMessage.error";
NSErrorDomain const DTLSSendMessageErrorDomain = @"com.home.DTLS.Network.sendMessage.error";

@interface DTLSNetworkManager()
@property (nonatomic, strong) nw_parameters_t params;
@property (nonatomic, strong) nw_connection_t connection;
@property (nonatomic, strong) dispatch_queue_t connectQueue;
@property (nonatomic, copy) DTLSMessageHandle receiveMessage;
@property (nonatomic, strong) NSMutableData *readBuf;
@end

@implementation DTLSNetworkManager

#pragma mark - Public

/// Config nw_parameters which include pskId, psk, ciphersuite
/// @param pskId pskId
/// @param psk psk
/// @param ciphersuite ciphersuite
- (void)setDTLSParamWithPskId:(NSString *)pskId
                          psk:(NSString *)psk
                  ciphersuite:(tls_ciphersuite_t)ciphersuite API_AVAILABLE(ios(13.0)){
    if (pskId == nil || [pskId isEqualToString:@""]) {
        return;
    }
    if (psk == nil || [psk isEqualToString:@""]) {
        return;
    }
    self.params = nw_parameters_create_secure_udp(^(nw_protocol_options_t  _Nonnull options) {
        sec_protocol_options_t option = nw_tls_copy_sec_protocol_options(options);
        dispatch_data_t pskIdData = [self dispatchDataFromNsdata:[pskId dataUsingEncoding:NSUTF8StringEncoding]];
        dispatch_data_t pskData = [self dispatchDataFromNsdata:[psk dataUsingEncoding:NSUTF8StringEncoding]];
        if (pskIdData == nil || pskData == nil) {
            return;
        }
        sec_protocol_options_add_pre_shared_key(option, pskData, pskIdData);
        sec_protocol_options_append_tls_ciphersuite(option, ciphersuite);
        sec_protocol_options_set_min_tls_protocol_version(option, tls_protocol_version_DTLSv12);
    }, ^(nw_protocol_options_t  _Nonnull options) {
        NW_PARAMETERS_DEFAULT_CONFIGURATION;
    });
}

/// Connect to host
/// @param host IP
/// @param port port
/// @param queue queue
/// @param stateHandle callback
- (void)connectDTLSToHost:(NSString *)host
                     port:(NSString *)port
                    queue:(dispatch_queue_t)queue
              stateHandle:(DTLSConnectStateHandle)stateHandle API_AVAILABLE(ios(13.0)) {
    if (host == nil || [host isEqualToString:@""]) {
        return;
    }
    if (port == nil || [port isEqualToString:@""]) {
        return;
    }
    nw_endpoint_t endpoint = nw_endpoint_create_host([host UTF8String], [port UTF8String]);
    self.connection = nw_connection_create(endpoint, self.params);
    nw_connection_set_queue(self.connection, queue);
    nw_connection_start(self.connection);
    nw_connection_set_state_changed_handler(self.connection, ^(nw_connection_state_t state, nw_error_t  _Nullable error) {
        NSError *nserror;
        if (error != nil) {
            nserror = [[NSError alloc] initWithDomain:DTLSConnectErrorDomain code:nw_error_get_error_code(error) userInfo:@{@"nw_connection_set_state_changed_handler: nw_error_get_error_domain": @(nw_error_get_error_domain(error))}];
        }
        if (stateHandle) {
            stateHandle(state, nserror);
        }
    });
    
    [self receiveMsg];
}

/// Cancel nw_connection and set nil
- (void)closeDTLSConnect API_AVAILABLE(ios(13.0)){
    nw_connection_cancel(self.connection);
    //    self.connection = nil;//置nil會(huì)報(bào)錯(cuò)
    //    self.params = nil;
}

/// Send message
/// @param message message
/// @param complete complete
- (void)sendDTLSMessage:(NSData *)message
               complete:(DTLSSessionCompletedHandle)complete API_AVAILABLE(ios(13.0)) {
    NSData *sendMessage = [self sendMessagePack:message];
    dispatch_data_t data = [self dispatchDataFromNsdata:sendMessage];
    nw_connection_send(self.connection, data, NW_CONNECTION_FINAL_MESSAGE_CONTEXT, true, ^(nw_error_t  _Nullable error) {
        NSError *nserror;
        if (error != nil) {
            nserror = [[NSError alloc] initWithDomain:DTLSSendMessageErrorDomain code:nw_error_get_error_code(error) userInfo:@{@"nw_connection_send: nw_error_get_error_domain": @(nw_error_get_error_domain(error))}];
        }
        DEVELOPER_LOG_FORMAT(@"DTLS發(fā)送數(shù)據(jù):%@",sendMessage);
        DDLogDebug(@"DTLS發(fā)送數(shù)據(jù):%@",sendMessage);
        if (complete) {
            complete(nserror);
        }
    });
}

- (void)receiveDTLSMessage:(DTLSMessageHandle)receiveMessageHandle {
    self.receiveMessage = receiveMessageHandle;
}


#pragma mark - Private
/// Receive message
- (void)receiveMsg API_AVAILABLE(ios(13.0)) {
    __weak typeof (self)weakSelf = self;
    nw_connection_receive_message(self.connection, ^(dispatch_data_t  _Nullable content, nw_content_context_t  _Nullable context, bool is_complete, nw_error_t  _Nullable error) {
        DEVELOPER_LOG_FORMAT(@"DTLS接收數(shù)據(jù):content=%@, context=%@, is_complete=%d, error=%@",content, context, is_complete, error);
        DDLogDebug(@"DTLS接收數(shù)據(jù):content=%@, context=%@, is_complete=%d, error=%@",content, context, is_complete, error);
        __strong typeof (weakSelf)strongSelf = weakSelf;
        if (error == nil && content != nil) {
            NSData *data = [strongSelf nsdataFromDispatchData:content];
            if (data != nil) {
                [self receiveMessagePack:data];
            } else {
                NSError *nserror = [[NSError alloc] initWithDomain:DTLSReceiveMessageErrorDomain code:nw_error_get_error_code(error) userInfo:@{@"nw_connection_receive_message: nw_error_get_error_domain": @(nw_error_get_error_domain(error))}];
                if (strongSelf.receiveMessage) {
                    strongSelf.receiveMessage(nil, nserror);
                }
            }
            //nw_connection_receive_message函數(shù)只讀取一次消息,讀取完需要再次調(diào)用繼續(xù)讀取
        }else{
            NSError *nserror = [[NSError alloc] initWithDomain:DTLSReceiveMessageErrorDomain code:nw_error_get_error_code(error) userInfo:@{@"nw_connection_receive_message: nw_error_get_error_domain": @(nw_error_get_error_domain(error))}];
            if (strongSelf.receiveMessage) {
                strongSelf.receiveMessage(nil, nserror);
            }
        }
    });
}

/// 發(fā)送消息前進(jìn)行組包萧恕,拼接包頭刚梭,0xfefe + 包長(zhǎng)2字節(jié)肠阱。4個(gè)字節(jié)
/// @param data 數(shù)據(jù)
- (NSData *)sendMessagePack:(NSData *)data {
    NSInteger length = data.length;
    Byte byte[4] = {0xfe, 0xfe, length >> 8, length & 0x00ff};
    NSMutableData *mulData = [[NSMutableData alloc] init];
    [mulData appendData:[NSData dataWithBytes:byte length:sizeof(byte)]];
    [mulData appendData:data];
    return mulData;
}

/// 接收數(shù)據(jù)的拼包,解決粘包問(wèn)題
/// @param data 數(shù)據(jù)
- (void)receiveMessagePack:(NSData *)data API_AVAILABLE(ios(13.0)) {
    //將數(shù)據(jù)存入緩存區(qū)
    [self.readBuf appendData:data];
    //包頭4個(gè)字節(jié)朴读,2個(gè)字節(jié)fefe屹徘,2個(gè)字節(jié)包總長(zhǎng)度
    while (self.readBuf.length > 4) {
        //將消息轉(zhuǎn)化成byte,計(jì)算總長(zhǎng)度 = 數(shù)據(jù)的內(nèi)容長(zhǎng)度 + 前面4個(gè)字節(jié)的頭長(zhǎng)度
        Byte *bytes = (Byte *)[self.readBuf bytes];
        if ((bytes[0]<<8) + bytes[1] == 0xfefe) {
            NSUInteger allLength = (bytes[2]<<8) + bytes[3] + 4;
            //緩存區(qū)的長(zhǎng)度大于總長(zhǎng)度衅金,證明有完整的數(shù)據(jù)包在緩存區(qū)噪伊,然后進(jìn)行處理
            if (self.readBuf.length >= allLength) {
                //提取出前面4個(gè)字節(jié)的頭內(nèi)容,之所以提取出來(lái)氮唯,是因?yàn)樵谔幚頂?shù)據(jù)問(wèn)題的時(shí)候鉴吹,比如data轉(zhuǎn)json的時(shí)候,
                //頭內(nèi)容里面包含非法字符惩琉,會(huì)導(dǎo)致轉(zhuǎn)化出來(lái)的json內(nèi)容為空豆励,所以要先去掉再處理數(shù)據(jù)問(wèn)題
                NSMutableData *msgData = [[self.readBuf subdataWithRange:NSMakeRange(0, allLength)] mutableCopy];
                [msgData replaceBytesInRange:NSMakeRange(0, 4) withBytes:NULL length:0];
                
                if (self.receiveMessage) {
                    self.receiveMessage(msgData, nil);
                }
                //處理完數(shù)據(jù)后將處理過(guò)的數(shù)據(jù)移出緩存區(qū)
                self.readBuf = [NSMutableData dataWithData:[self.readBuf subdataWithRange:NSMakeRange(allLength, self.readBuf.length - allLength)]];
            }else{
                //緩存區(qū)內(nèi)數(shù)據(jù)包不是完整的,再次從服務(wù)器獲取數(shù)據(jù)瞒渠,中斷while循環(huán)
                [self receiveMsg];
                break;
            }
        } else {
            //如果包頭不符合要求則丟棄
            self.readBuf = nil;
            DDLogDebug(@"DTLS拼包數(shù)據(jù)錯(cuò)誤:%@",self.readBuf);
            break;
        }
    }
    //讀取到服務(wù)端數(shù)據(jù)值后,能再次讀取
    [self receiveMsg];
}


#pragma mark - 轉(zhuǎn)換方法
/// Convert NSData to dispatch_data_t
/// @param nsdata NSData
- (dispatch_data_t)dispatchDataFromNsdata:(NSData *)nsdata API_AVAILABLE(ios(13.0)) {
    if (nsdata == nil) {
        return nil;
    }
    Byte byte[nsdata.length];
    [nsdata getBytes:byte length:nsdata.length];
    dispatch_data_t data = dispatch_data_create(byte, nsdata.length, nil, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
    return data;
}

/// Convert dispatch_data_t to NSData
/// @param dispatchData dispatch_data_t
- (NSData *)nsdataFromDispatchData:(dispatch_data_t)dispatchData API_AVAILABLE(ios(13.0)) {
    if (dispatchData == nil) {
        return nil;
    }
    const void *buffer = NULL;
    size_t size = 0;
    dispatch_data_t new_data_file = dispatch_data_create_map(dispatchData, &buffer, &size);
    if(new_data_file) {/* to avoid warning really - since dispatch_data_create_map demands we
                        care about the return arg */}
    NSData *nsdata = [[NSData alloc] initWithBytes:buffer length:size];
    return nsdata;
}

#pragma mark - 懶加載
- (NSMutableData *)readBuf {
    if (_readBuf == nil) {
        _readBuf = [[NSMutableData alloc] init];
    }
    return _readBuf;
}

@end
#endif
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末肆糕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子在孝,更是在濱河造成了極大的恐慌,老刑警劉巖淮摔,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件私沮,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡和橙,警方通過(guò)查閱死者的電腦和手機(jī)仔燕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)魔招,“玉大人晰搀,你說(shuō)我怎么就攤上這事“彀撸” “怎么了外恕?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)乡翅。 經(jīng)常有香客問(wèn)我鳞疲,道長(zhǎng),這世上最難降的妖魔是什么蠕蚜? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任尚洽,我火速辦了婚禮,結(jié)果婚禮上靶累,老公的妹妹穿的比我還像新娘腺毫。我一直安慰自己癣疟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布潮酒。 她就那樣靜靜地躺著睛挚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪澈灼。 梳的紋絲不亂的頭發(fā)上竞川,一...
    開(kāi)封第一講書(shū)人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音叁熔,去河邊找鬼委乌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛荣回,可吹牛的內(nèi)容都是我干的遭贸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼心软,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼壕吹!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起删铃,我...
    開(kāi)封第一講書(shū)人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤耳贬,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后猎唁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體咒劲,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年诫隅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腐魂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡逐纬,死狀恐怖蛔屹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情豁生,我是刑警寧澤兔毒,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站甸箱,受9級(jí)特大地震影響眼刃,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜摇肌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一擂红、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦昵骤、人聲如沸树碱。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)成榜。三九已至,卻和暖如春蹦玫,著一層夾襖步出監(jiān)牢的瞬間赎婚,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工樱溉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留挣输,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓福贞,卻偏偏與公主長(zhǎng)得像撩嚼,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挖帘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom閱讀 2,696評(píng)論 0 3
  • 在C語(yǔ)言中,五種基本數(shù)據(jù)類(lèi)型存儲(chǔ)空間長(zhǎng)度的排列順序是: A)char B)char=int<=float C)ch...
    夏天再來(lái)閱讀 3,342評(píng)論 0 2
  • ## 可重入函數(shù) ### 可重入性的理解 若一個(gè)程序或子程序可以安全的被并行執(zhí)行完丽,則稱(chēng)其為可重入的;即當(dāng)該子程序正...
    夏至亦韻閱讀 707評(píng)論 0 0
  • 原文地址:https://github.com/JuanitoFatas/slime-user-manual#24...
    四月不見(jiàn)閱讀 3,130評(píng)論 0 2
  • 青春是一生中最美好的時(shí)光拇舀,穿著寬大的校服逻族,背著書(shū)包,無(wú)憂(yōu)無(wú)慮骄崩,和朋友們聊著八卦瓷耙,女生們聊著哪個(gè)班有帥哥,哪個(gè)人長(zhǎng)的...
    念lych閱讀 236評(píng)論 3 0