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

<Network/Network.h>库是iOS12以后苹果才公开的,部分接口iOS13才可用。下面的代码是DTLS连接,基于纯PSK建立的。具体的算法集可以根据需要切换。

  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文件。
    代码中包含了组包,如果你所做的项目数据头不一样,需要根据具体修改。
//
//  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会报错
    //    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发送数据:%@",sendMessage);
        DDLogDebug(@"DTLS发送数据:%@",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接收数据:content=%@, context=%@, is_complete=%d, error=%@",content, context, is_complete, error);
        DDLogDebug(@"DTLS接收数据: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函数只读取一次消息,读取完需要再次调用继续读取
        }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);
            }
        }
    });
}

/// 发送消息前进行组包,拼接包头,0xfefe + 包长2字节。4个字节
/// @param data 数据
- (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;
}

/// 接收数据的拼包,解决粘包问题
/// @param data 数据
- (void)receiveMessagePack:(NSData *)data API_AVAILABLE(ios(13.0)) {
    //将数据存入缓存区
    [self.readBuf appendData:data];
    //包头4个字节,2个字节fefe,2个字节包总长度
    while (self.readBuf.length > 4) {
        //将消息转化成byte,计算总长度 = 数据的内容长度 + 前面4个字节的头长度
        Byte *bytes = (Byte *)[self.readBuf bytes];
        if ((bytes[0]<<8) + bytes[1] == 0xfefe) {
            NSUInteger allLength = (bytes[2]<<8) + bytes[3] + 4;
            //缓存区的长度大于总长度,证明有完整的数据包在缓存区,然后进行处理
            if (self.readBuf.length >= allLength) {
                //提取出前面4个字节的头内容,之所以提取出来,是因为在处理数据问题的时候,比如data转json的时候,
                //头内容里面包含非法字符,会导致转化出来的json内容为空,所以要先去掉再处理数据问题
                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);
                }
                //处理完数据后将处理过的数据移出缓存区
                self.readBuf = [NSMutableData dataWithData:[self.readBuf subdataWithRange:NSMakeRange(allLength, self.readBuf.length - allLength)]];
            }else{
                //缓存区内数据包不是完整的,再次从服务器获取数据,中断while循环
                [self receiveMsg];
                break;
            }
        } else {
            //如果包头不符合要求则丢弃
            self.readBuf = nil;
            DDLogDebug(@"DTLS拼包数据错误:%@",self.readBuf);
            break;
        }
    }
    //读取到服务端数据值后,能再次读取
    [self receiveMsg];
}


#pragma mark - 转换方法
/// 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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容

  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom阅读 2,694评论 0 3
  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 3,340评论 0 2
  • ## 可重入函数 ### 可重入性的理解 若一个程序或子程序可以安全的被并行执行,则称其为可重入的;即当该子程序正...
    夏至亦韵阅读 704评论 0 0
  • 原文地址:https://github.com/JuanitoFatas/slime-user-manual#24...
    四月不见阅读 3,114评论 0 2
  • 青春是一生中最美好的时光,穿着宽大的校服,背着书包,无忧无虑,和朋友们聊着八卦,女生们聊着哪个班有帅哥,哪个人长的...
    念lych阅读 235评论 3 0