From f1e1ceea3b96707208a54125bbb47378cffc0e51 Mon Sep 17 00:00:00 2001 From: NathanLi Date: Sun, 14 Aug 2016 21:26:42 +0800 Subject: [PATCH 1/9] Add the callback of send operation. Add feature: Callback of send data. --- SocketRocket/SRWebSocket.h | 26 +++++++ SocketRocket/SRWebSocket.m | 152 +++++++++++++++++++++++++++++-------- 2 files changed, 147 insertions(+), 31 deletions(-) diff --git a/SocketRocket/SRWebSocket.h b/SocketRocket/SRWebSocket.h index 9ec234ecf..deee7e1d2 100644 --- a/SocketRocket/SRWebSocket.h +++ b/SocketRocket/SRWebSocket.h @@ -59,6 +59,8 @@ extern NSString *const SRHTTPResponseErrorKey; @protocol SRWebSocketDelegate; +typedef void (^SRSendCompleteBlock)(NSError * _Nullable error); + ///-------------------------------------- #pragma mark - SRWebSocket ///-------------------------------------- @@ -278,6 +280,18 @@ extern NSString *const SRHTTPResponseErrorKey; */ - (BOOL)sendString:(NSString *)string error:(NSError **)error NS_SWIFT_NAME(send(string:)); +/** + Send a UTF-8 String to the server. + + @param string String to send. + @param complete The call back of send result. + If an error occurs, this block will invoked with an `NSerror` object cantaining information about the error; Otherwise + this block will invoked with `nil`. + + @return `YES` if the string was scheduled to send, otherwise - `NO`. + */ +- (BOOL)sendString:(NSString *)string complete:(SRSendCompleteBlock)complete; + /** Send binary data to the server. @@ -302,6 +316,18 @@ extern NSString *const SRHTTPResponseErrorKey; */ - (BOOL)sendDataNoCopy:(nullable NSData *)data error:(NSError **)error NS_SWIFT_NAME(send(dataNoCopy:)); +/** + Send binary data to the server, without making a defensive copy of it first. + + @param data Data to send. + @param complete The call back of send result. + If an error occurs, this block will invoked with an `NSerror` object cantaining information about the error; Otherwise + this block will invoked with `nil`. + + @return `YES` if the string was scheduled to send, otherwise - `NO`. + */ +- (BOOL)sendDataNoCopy:(nullable NSData *)data completed:(SRSendCompleteBlock)complete; + /** Send Ping message to the server with optional data. diff --git a/SocketRocket/SRWebSocket.m b/SocketRocket/SRWebSocket.m index 86c47a1b4..aec9b77fe 100644 --- a/SocketRocket/SRWebSocket.m +++ b/SocketRocket/SRWebSocket.m @@ -69,6 +69,14 @@ NSString *const SRWebSocketErrorDomain = @"SRWebSocketErrorDomain"; NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode"; +@interface SRDataCallback : NSObject +@property (nonatomic, assign) NSRange range; +@property (nonatomic, copy) SRSendCompleteBlock completeBlock; +@end + +@implementation SRDataCallback +@end + @interface SRWebSocket () @property (atomic, assign, readwrite) SRReadyState readyState; @@ -138,6 +146,9 @@ @implementation SRWebSocket { // proxy support SRProxyConnect *_proxyConnect; + + NSMutableDictionary *_sendCallbacks; + } @synthesize readyState = _readyState; @@ -179,6 +190,8 @@ - (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray _Nullable delegate, SRDelegateAvailableMethods availableMethods) { @@ -563,14 +583,27 @@ - (void)_failWithError:(NSError *)error; }); } -- (void)_writeData:(NSData *)data; +- (void)_writeData:(NSData *)data { - [self assertOnWorkQueue]; + [self _writeData:data complete:NULL]; +} +- (void)_writeData:(NSData *)data complete:(SRSendCompleteBlock)complete { + [self assertOnWorkQueue]; + if (_closeWhenFinishedWriting) { + if (complete) { + complete(SRErrorWithCodeDescription(2134, @"socket is closed")); + } return; } - + + SRDataCallback *record = [[SRDataCallback alloc] init]; + record.completeBlock = complete; + NSUInteger location = dispatch_data_get_size(_outputBuffer); + record.range = NSMakeRange(location, [data length]); + _sendCallbacks[@([data hash])] = record; + __block NSData *strongData = data; dispatch_data_t newData = dispatch_data_create(data.bytes, data.length, nil, ^{ strongData = nil; @@ -594,18 +627,32 @@ - (void)send:(nullable id)message - (BOOL)sendString:(NSString *)string error:(NSError **)error { + return [self sendString:string error:error complete:NULL]; +} + +- (BOOL)sendString:(NSString *)string complete:(SRSendCompleteBlock)complete { + return [self sendString:string error:NULL complete:complete]; +} + +- (BOOL)sendString:(NSString *)string error:(NSError **)error complete:(SRSendCompleteBlock)complete { if (self.readyState != SR_OPEN) { NSString *message = @"Invalid State: Cannot call `sendString:error:` until connection is open."; + NSError *locialError = SRErrorWithCodeDescription(2134, message); if (error) { - *error = SRErrorWithCodeDescription(2134, message); + *error = locialError; + } + + if (complete) { + complete(locialError); } + SRDebugLog(message); return NO; } - + string = [string copy]; dispatch_async(_workQueue, ^{ - [self _sendFrameWithOpcode:SROpCodeTextFrame data:[string dataUsingEncoding:NSUTF8StringEncoding]]; + [self _sendFrameWithOpcode:SROpCodeTextFrame data:[string dataUsingEncoding:NSUTF8StringEncoding] complete:complete]; }); return YES; } @@ -618,20 +665,33 @@ - (BOOL)sendData:(nullable NSData *)data error:(NSError **)error - (BOOL)sendDataNoCopy:(nullable NSData *)data error:(NSError **)error { + return [self sendDataNoCopy:data error:error completed:NULL]; +} + +- (BOOL)sendDataNoCopy:(nullable NSData *)data completed:(SRSendCompleteBlock)complete { + return [self sendDataNoCopy:data error:NULL completed:complete]; +} + +- (BOOL)sendDataNoCopy:(nullable NSData *)data error:(NSError **)error completed:(SRSendCompleteBlock)complete { if (self.readyState != SR_OPEN) { NSString *message = @"Invalid State: Cannot call `sendDataNoCopy:error:` until connection is open."; + NSError *locialError = SRErrorWithCodeDescription(2134, message); if (error) { - *error = SRErrorWithCodeDescription(2134, message); + *error = locialError; + } + + if (complete) { + complete(locialError); } SRDebugLog(message); return NO; } - + dispatch_async(_workQueue, ^{ if (data) { - [self _sendFrameWithOpcode:SROpCodeBinaryFrame data:data]; + [self _sendFrameWithOpcode:SROpCodeBinaryFrame data:data complete:complete]; } else { - [self _sendFrameWithOpcode:SROpCodeTextFrame data:nil]; + [self _sendFrameWithOpcode:SROpCodeTextFrame data:nil complete:complete]; } }); return YES; @@ -1051,10 +1111,29 @@ - (void)_pumpWriting; [self _failWithError:error]; return; } - + _outputBufferOffset += bytesWritten; + + NSMutableArray *removeKeys = [NSMutableArray array]; + [_sendCallbacks enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, SRDataCallback * _Nonnull obj, BOOL * _Nonnull stop) { + if (NSMaxRange(obj.range) <= _outputBufferOffset) { + [removeKeys addObject:key]; + if (obj.completeBlock) { + obj.completeBlock(nil); + } + } + + + }]; + [_sendCallbacks removeObjectsForKeys:removeKeys]; if (_outputBufferOffset > SRDefaultBufferSize() && _outputBufferOffset > dataLength / 2) { + [_sendCallbacks enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, SRDataCallback * _Nonnull obj, BOOL * _Nonnull stop) { + NSRange range = obj.range; + range.location -= _outputBufferOffset; + obj.range = range; + }]; + _outputBuffer = dispatch_data_create_subrange(_outputBuffer, _outputBufferOffset, dataLength - _outputBufferOffset); _outputBufferOffset = 0; } @@ -1314,74 +1393,85 @@ -(void)_pumpScanner; static const size_t SRFrameHeaderOverhead = 32; -- (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data -{ +- (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data complete:(SRSendCompleteBlock)complete { [self assertOnWorkQueue]; - + if (!data) { + if (complete) { + complete(nil); + } return; } - + size_t payloadLength = data.length; - + NSMutableData *frameData = [[NSMutableData alloc] initWithLength:payloadLength + SRFrameHeaderOverhead]; if (!frameData) { - [self closeWithCode:SRStatusCodeMessageTooBig reason:@"Message too big"]; + NSString *reason = @"Message too big"; + [self closeWithCode:SRStatusCodeMessageTooBig reason:reason]; + if (complete) { + complete(SRErrorWithCodeDescription(SRStatusCodeMessageTooBig, reason)); + } return; } uint8_t *frameBuffer = (uint8_t *)frameData.mutableBytes; - + // set fin frameBuffer[0] = SRFinMask | opCode; - + // set the mask and header frameBuffer[1] |= SRMaskMask; - + size_t frameBufferSize = 2; - + if (payloadLength < 126) { frameBuffer[1] |= payloadLength; } else { uint64_t declaredPayloadLength = 0; size_t declaredPayloadLengthSize = 0; - + if (payloadLength <= UINT16_MAX) { frameBuffer[1] |= 126; - + declaredPayloadLength = CFSwapInt16BigToHost((uint16_t)payloadLength); declaredPayloadLengthSize = sizeof(uint16_t); } else { frameBuffer[1] |= 127; - + declaredPayloadLength = CFSwapInt64BigToHost((uint64_t)payloadLength); declaredPayloadLengthSize = sizeof(uint64_t); } - + memcpy((frameBuffer + frameBufferSize), &declaredPayloadLength, declaredPayloadLengthSize); frameBufferSize += declaredPayloadLengthSize; } - + const uint8_t *unmaskedPayloadBuffer = (uint8_t *)data.bytes; uint8_t *maskKey = frameBuffer + frameBufferSize; - + size_t randomBytesSize = sizeof(uint32_t); int result = SecRandomCopyBytes(kSecRandomDefault, randomBytesSize, maskKey); if (result != 0) { //TODO: (nlutsenko) Check if there was an error. } frameBufferSize += randomBytesSize; - + // Copy and unmask the buffer uint8_t *frameBufferPayloadPointer = frameBuffer + frameBufferSize; - + memcpy(frameBufferPayloadPointer, unmaskedPayloadBuffer, payloadLength); SRMaskBytesSIMD(frameBufferPayloadPointer, payloadLength, maskKey); frameBufferSize += payloadLength; - + assert(frameBufferSize <= frameData.length); frameData.length = frameBufferSize; + + [self _writeData:frameData complete:complete]; +} - [self _writeData:frameData]; +- (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data +{ + [self _sendFrameWithOpcode:opCode data:data complete:NULL]; } - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode From 296c1512461fa68e51bb28862aa65b82ff969ce4 Mon Sep 17 00:00:00 2001 From: NathanLi Date: Thu, 3 Nov 2016 19:47:30 +0800 Subject: [PATCH 2/9] Rename SRSendCompleteBlock to SRSendCompletionBlock --- SocketRocket/SRWebSocket.h | 6 +++--- SocketRocket/SRWebSocket.m | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/SocketRocket/SRWebSocket.h b/SocketRocket/SRWebSocket.h index c1d00d98e..3399a5322 100644 --- a/SocketRocket/SRWebSocket.h +++ b/SocketRocket/SRWebSocket.h @@ -59,7 +59,7 @@ extern NSString *const SRHTTPResponseErrorKey; @protocol SRWebSocketDelegate; -typedef void (^SRSendCompleteBlock)(NSError * _Nullable error); +typedef void (^SRSendCompletionBlock)(NSError * _Nullable error); ///-------------------------------------- #pragma mark - SRWebSocket @@ -290,7 +290,7 @@ typedef void (^SRSendCompleteBlock)(NSError * _Nullable error); @return `YES` if the string was scheduled to send, otherwise - `NO`. */ -- (BOOL)sendString:(NSString *)string complete:(SRSendCompleteBlock)complete; +- (BOOL)sendString:(NSString *)string complete:(SRSendCompletionBlock)complete; /** Send binary data to the server. @@ -326,7 +326,7 @@ typedef void (^SRSendCompleteBlock)(NSError * _Nullable error); @return `YES` if the string was scheduled to send, otherwise - `NO`. */ -- (BOOL)sendDataNoCopy:(nullable NSData *)data completed:(SRSendCompleteBlock)complete; +- (BOOL)sendDataNoCopy:(nullable NSData *)data completed:(SRSendCompletionBlock)complete; /** Send Ping message to the server with optional data. diff --git a/SocketRocket/SRWebSocket.m b/SocketRocket/SRWebSocket.m index f23a01b44..011da5b4d 100644 --- a/SocketRocket/SRWebSocket.m +++ b/SocketRocket/SRWebSocket.m @@ -71,7 +71,7 @@ @interface SRDataCallback : NSObject @property (nonatomic, assign) NSRange range; -@property (nonatomic, copy) SRSendCompleteBlock completeBlock; +@property (nonatomic, copy) SRSendCompletionBlock completeBlock; @end @implementation SRDataCallback @@ -591,7 +591,7 @@ - (void)_writeData:(NSData *)data [self _writeData:data complete:NULL]; } -- (void)_writeData:(NSData *)data complete:(SRSendCompleteBlock)complete { +- (void)_writeData:(NSData *)data complete:(SRSendCompletionBlock)complete { [self assertOnWorkQueue]; if (_closeWhenFinishedWriting) { @@ -633,11 +633,11 @@ - (BOOL)sendString:(NSString *)string error:(NSError **)error return [self sendString:string error:error complete:NULL]; } -- (BOOL)sendString:(NSString *)string complete:(SRSendCompleteBlock)complete { +- (BOOL)sendString:(NSString *)string complete:(SRSendCompletionBlock)complete { return [self sendString:string error:NULL complete:complete]; } -- (BOOL)sendString:(NSString *)string error:(NSError **)error complete:(SRSendCompleteBlock)complete { +- (BOOL)sendString:(NSString *)string error:(NSError **)error complete:(SRSendCompletionBlock)complete { if (self.readyState != SR_OPEN) { NSString *message = @"Invalid State: Cannot call `sendString:error:` until connection is open."; NSError *locialError = SRErrorWithCodeDescription(2134, message); @@ -671,11 +671,11 @@ - (BOOL)sendDataNoCopy:(nullable NSData *)data error:(NSError **)error return [self sendDataNoCopy:data error:error completed:NULL]; } -- (BOOL)sendDataNoCopy:(nullable NSData *)data completed:(SRSendCompleteBlock)complete { +- (BOOL)sendDataNoCopy:(nullable NSData *)data completed:(SRSendCompletionBlock)complete { return [self sendDataNoCopy:data error:NULL completed:complete]; } -- (BOOL)sendDataNoCopy:(nullable NSData *)data error:(NSError **)error completed:(SRSendCompleteBlock)complete { +- (BOOL)sendDataNoCopy:(nullable NSData *)data error:(NSError **)error completed:(SRSendCompletionBlock)complete { if (self.readyState != SR_OPEN) { NSString *message = @"Invalid State: Cannot call `sendDataNoCopy:error:` until connection is open."; NSError *locialError = SRErrorWithCodeDescription(2134, message); @@ -1399,7 +1399,7 @@ -(void)_pumpScanner; static const size_t SRFrameHeaderOverhead = 32; -- (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data complete:(SRSendCompleteBlock)complete { +- (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data complete:(SRSendCompletionBlock)complete { [self assertOnWorkQueue]; if (!data) { From f599d0c29c25a44278317ed1611e81c54b5e3e31 Mon Sep 17 00:00:00 2001 From: NathanLi Date: Thu, 3 Nov 2016 20:34:37 +0800 Subject: [PATCH 3/9] Fixed naming problems --- SocketRocket/SRWebSocket.h | 18 +++--- SocketRocket/SRWebSocket.m | 119 +++++++++++++++++++++---------------- 2 files changed, 77 insertions(+), 60 deletions(-) diff --git a/SocketRocket/SRWebSocket.h b/SocketRocket/SRWebSocket.h index 3399a5322..a85e89bd0 100644 --- a/SocketRocket/SRWebSocket.h +++ b/SocketRocket/SRWebSocket.h @@ -283,14 +283,13 @@ typedef void (^SRSendCompletionBlock)(NSError * _Nullable error); /** Send a UTF-8 String to the server. - @param string String to send. - @param complete The call back of send result. - If an error occurs, this block will invoked with an `NSerror` object cantaining information about the error; Otherwise - this block will invoked with `nil`. + @param string String to send. + @param completion The call back of send result. + If an error occurs, this block will invoked with an `NSError` object containing information about the error, otherwise this block will be invoked with `nil`. @return `YES` if the string was scheduled to send, otherwise - `NO`. */ -- (BOOL)sendString:(NSString *)string complete:(SRSendCompletionBlock)complete; +- (BOOL)sendString:(NSString *)string completion:(nullable SRSendCompletionBlock)completion NS_SWIFT_NAME(send(string:completion:)); /** Send binary data to the server. @@ -319,14 +318,13 @@ typedef void (^SRSendCompletionBlock)(NSError * _Nullable error); /** Send binary data to the server, without making a defensive copy of it first. - @param data Data to send. - @param complete The call back of send result. - If an error occurs, this block will invoked with an `NSerror` object cantaining information about the error; Otherwise - this block will invoked with `nil`. + @param data Data to send. + @param completion The call back of send result. + If an error occurs, this block will invoked with an `NSError` object containing information about the error, otherwise this block will be invoked with `nil`. @return `YES` if the string was scheduled to send, otherwise - `NO`. */ -- (BOOL)sendDataNoCopy:(nullable NSData *)data completed:(SRSendCompletionBlock)complete; +- (BOOL)sendDataNoCopy:(nullable NSData *)data completion:(nullable SRSendCompletionBlock)completion NS_SWIFT_NAME(send(dataNoCopy:completion:)); /** Send Ping message to the server with optional data. diff --git a/SocketRocket/SRWebSocket.m b/SocketRocket/SRWebSocket.m index 011da5b4d..60e334b41 100644 --- a/SocketRocket/SRWebSocket.m +++ b/SocketRocket/SRWebSocket.m @@ -71,10 +71,27 @@ @interface SRDataCallback : NSObject @property (nonatomic, assign) NSRange range; -@property (nonatomic, copy) SRSendCompletionBlock completeBlock; +@property (nonatomic, copy, readonly) SRSendCompletionBlock completion; + ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithRange:(NSRange)range + completion:(SRSendCompletionBlock)completion NS_DESIGNATED_INITIALIZER; + @end @implementation SRDataCallback + +- (instancetype)initWithRange:(NSRange)range completion:(SRSendCompletionBlock)completion +{ + self = [super init]; + + _range = range; + _completion = [completion copy]; + + return self; +} + @end @interface SRWebSocket () @@ -146,9 +163,8 @@ @implementation SRWebSocket { // proxy support SRProxyConnect *_proxyConnect; - + NSMutableDictionary *_sendCallbacks; - } @synthesize readyState = _readyState; @@ -191,7 +207,7 @@ - (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *removeKeys = [NSMutableArray array]; [_sendCallbacks enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, SRDataCallback * _Nonnull obj, BOOL * _Nonnull stop) { if (NSMaxRange(obj.range) <= _outputBufferOffset) { [removeKeys addObject:key]; - if (obj.completeBlock) { - obj.completeBlock(nil); - } + obj.completion(nil); } - - }]; + [_sendCallbacks removeObjectsForKeys:removeKeys]; if (_outputBufferOffset > SRDefaultBufferSize() && _outputBufferOffset > dataLength / 2) { @@ -1399,24 +1417,25 @@ -(void)_pumpScanner; static const size_t SRFrameHeaderOverhead = 32; -- (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data complete:(SRSendCompletionBlock)complete { +- (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data completion:(SRSendCompletionBlock)completion +{ [self assertOnWorkQueue]; - + if (!data) { - if (complete) { - complete(nil); + if (completion) { + completion(nil); } return; } - + size_t payloadLength = data.length; NSMutableData *frameData = [[NSMutableData alloc] initWithLength:payloadLength + SRFrameHeaderOverhead]; if (!frameData) { NSString *reason = @"Message too big"; [self closeWithCode:SRStatusCodeMessageTooBig reason:reason]; - if (complete) { - complete(SRErrorWithCodeDescription(SRStatusCodeMessageTooBig, reason)); + if (completion) { + completion(SRErrorWithCodeDescription(SRStatusCodeMessageTooBig, reason)); } return; } @@ -1472,12 +1491,12 @@ - (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data complete:(SRSe assert(frameBufferSize <= frameData.length); frameData.length = frameBufferSize; - [self _writeData:frameData complete:complete]; + [self _writeData:frameData completion:completion]; } - (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data { - [self _sendFrameWithOpcode:opCode data:data complete:NULL]; + [self _sendFrameWithOpcode:opCode data:data completion:nil]; } - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode From ac1c632504d1bb850a3a19fb3ddf6af84e59ca8a Mon Sep 17 00:00:00 2001 From: NathanLi Date: Thu, 3 Nov 2016 23:16:35 +0800 Subject: [PATCH 4/9] Change the call back key for uniqune. --- SocketRocket/SRWebSocket.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SocketRocket/SRWebSocket.m b/SocketRocket/SRWebSocket.m index 60e334b41..0fefbb74e 100644 --- a/SocketRocket/SRWebSocket.m +++ b/SocketRocket/SRWebSocket.m @@ -623,7 +623,8 @@ - (void)_writeData:(NSData *)data completion:(nullable SRSendCompletionBlock)com NSRange dataRange = NSMakeRange(location, data.length); SRDataCallback *record = [[SRDataCallback alloc] initWithRange:dataRange completion:completion]; - _sendCallbacks[@([data hash])] = record; + static NSUInteger keyCount = 0; + _sendCallbacks[@(keyCount++)] = record; } __block NSData *strongData = data; From 399e25b4397166a2714a510effd447e23f9a5cef Mon Sep 17 00:00:00 2001 From: NathanLi Date: Thu, 3 Nov 2016 23:31:49 +0800 Subject: [PATCH 5/9] Remove useless white space. --- SocketRocket/SRWebSocket.m | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/SocketRocket/SRWebSocket.m b/SocketRocket/SRWebSocket.m index 0fefbb74e..c76536b29 100644 --- a/SocketRocket/SRWebSocket.m +++ b/SocketRocket/SRWebSocket.m @@ -672,7 +672,7 @@ - (BOOL)sendString:(NSString *)string error:(NSError **)error completion:(nullab SRDebugLog(message); return NO; } - + string = [string copy]; dispatch_async(_workQueue, ^{ [self _sendFrameWithOpcode:SROpCodeTextFrame data:[string dataUsingEncoding:NSUTF8StringEncoding] completion:completion]; @@ -1430,7 +1430,7 @@ - (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data completion:(SR } size_t payloadLength = data.length; - + NSMutableData *frameData = [[NSMutableData alloc] initWithLength:payloadLength + SRFrameHeaderOverhead]; if (!frameData) { NSString *reason = @"Message too big"; @@ -1444,54 +1444,54 @@ - (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data completion:(SR // set fin frameBuffer[0] = SRFinMask | opCode; - + // set the mask and header frameBuffer[1] |= SRMaskMask; - + size_t frameBufferSize = 2; - + if (payloadLength < 126) { frameBuffer[1] |= payloadLength; } else { uint64_t declaredPayloadLength = 0; size_t declaredPayloadLengthSize = 0; - + if (payloadLength <= UINT16_MAX) { frameBuffer[1] |= 126; - + declaredPayloadLength = CFSwapInt16BigToHost((uint16_t)payloadLength); declaredPayloadLengthSize = sizeof(uint16_t); } else { frameBuffer[1] |= 127; - + declaredPayloadLength = CFSwapInt64BigToHost((uint64_t)payloadLength); declaredPayloadLengthSize = sizeof(uint64_t); } - + memcpy((frameBuffer + frameBufferSize), &declaredPayloadLength, declaredPayloadLengthSize); frameBufferSize += declaredPayloadLengthSize; } - + const uint8_t *unmaskedPayloadBuffer = (uint8_t *)data.bytes; uint8_t *maskKey = frameBuffer + frameBufferSize; - + size_t randomBytesSize = sizeof(uint32_t); int result = SecRandomCopyBytes(kSecRandomDefault, randomBytesSize, maskKey); if (result != 0) { //TODO: (nlutsenko) Check if there was an error. } frameBufferSize += randomBytesSize; - + // Copy and unmask the buffer uint8_t *frameBufferPayloadPointer = frameBuffer + frameBufferSize; - + memcpy(frameBufferPayloadPointer, unmaskedPayloadBuffer, payloadLength); SRMaskBytesSIMD(frameBufferPayloadPointer, payloadLength, maskKey); frameBufferSize += payloadLength; - + assert(frameBufferSize <= frameData.length); frameData.length = frameBufferSize; - + [self _writeData:frameData completion:completion]; } From 00545e533d66a5c21a70b13b22c2b84b46faf771 Mon Sep 17 00:00:00 2001 From: NathanLi Date: Thu, 3 Nov 2016 23:34:14 +0800 Subject: [PATCH 6/9] Remove useless white space. --- SocketRocket/SRWebSocket.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SocketRocket/SRWebSocket.m b/SocketRocket/SRWebSocket.m index c76536b29..1888e922c 100644 --- a/SocketRocket/SRWebSocket.m +++ b/SocketRocket/SRWebSocket.m @@ -1441,7 +1441,7 @@ - (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data completion:(SR return; } uint8_t *frameBuffer = (uint8_t *)frameData.mutableBytes; - + // set fin frameBuffer[0] = SRFinMask | opCode; From b313fe82379336a362354bee5f6ebfe3d9a2c006 Mon Sep 17 00:00:00 2001 From: NathanLi Date: Tue, 8 Nov 2016 23:15:31 +0800 Subject: [PATCH 7/9] Improving the method chains and signatures for error:completion: style methods --- SocketRocket/SRWebSocket.m | 68 +++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/SocketRocket/SRWebSocket.m b/SocketRocket/SRWebSocket.m index 1888e922c..64cbbb2c9 100644 --- a/SocketRocket/SRWebSocket.m +++ b/SocketRocket/SRWebSocket.m @@ -70,7 +70,7 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode"; @interface SRDataCallback : NSObject -@property (nonatomic, assign) NSRange range; +@property (nonatomic, assign) NSRange range; @property (nonatomic, copy, readonly) SRSendCompletionBlock completion; + (instancetype)new NS_UNAVAILABLE; @@ -578,9 +578,7 @@ - (void)_failWithError:(NSError *)error; { dispatch_async(_workQueue, ^{ [_sendCallbacks enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, SRDataCallback * _Nonnull obj, BOOL * _Nonnull stop) { - if (obj.completion) { - obj.completion(error); - } + obj.completion(error); }]; [_sendCallbacks removeAllObjects]; @@ -618,7 +616,7 @@ - (void)_writeData:(NSData *)data completion:(nullable SRSendCompletionBlock)com return; } - if (completion != nil) { + if (completion) { NSUInteger location = dispatch_data_get_size(_outputBuffer); NSRange dataRange = NSMakeRange(location, data.length); SRDataCallback *record = [[SRDataCallback alloc] initWithRange:dataRange completion:completion]; @@ -650,25 +648,29 @@ - (void)send:(nullable id)message - (BOOL)sendString:(NSString *)string error:(NSError **)error { - return [self sendString:string error:error completion:nil]; -} + if (self.readyState != SR_OPEN) { + NSString *message = @"Invalid State: Cannot call `sendString:error:` until connection is open."; + if (error) { + *error = SRErrorWithCodeDescription(2134, message); + } + SRDebugLog(message); + return NO; + } -- (BOOL)sendString:(NSString *)string completion:(nullable SRSendCompletionBlock)completion { - return [self sendString:string error:NULL completion:completion]; + string = [string copy]; + dispatch_async(_workQueue, ^{ + [self _sendFrameWithOpcode:SROpCodeTextFrame data:[string dataUsingEncoding:NSUTF8StringEncoding]]; + }); + return YES; } -- (BOOL)sendString:(NSString *)string error:(NSError **)error completion:(nullable SRSendCompletionBlock)completion { +- (BOOL)sendString:(NSString *)string completion:(nullable SRSendCompletionBlock)completion +{ if (self.readyState != SR_OPEN) { NSString *message = @"Invalid State: Cannot call `sendString:error:` until connection is open."; - NSError *locialError = SRErrorWithCodeDescription(2134, message); - if (error) { - *error = locialError; - } - if (completion) { - completion(locialError); + completion(SRErrorWithCodeDescription(2134, message)); } - SRDebugLog(message); return NO; } @@ -688,25 +690,31 @@ - (BOOL)sendData:(nullable NSData *)data error:(NSError **)error - (BOOL)sendDataNoCopy:(nullable NSData *)data error:(NSError **)error { - return [self sendDataNoCopy:data error:error completion:nil]; -} + if (self.readyState != SR_OPEN) { + NSString *message = @"Invalid State: Cannot call `sendDataNoCopy:error:` until connection is open."; + if (error) { + *error = SRErrorWithCodeDescription(2134, message); + } + SRDebugLog(message); + return NO; + } -- (BOOL)sendDataNoCopy:(nullable NSData *)data completion:(nullable SRSendCompletionBlock)completion -{ - return [self sendDataNoCopy:data error:NULL completion:completion]; + dispatch_async(_workQueue, ^{ + if (data) { + [self _sendFrameWithOpcode:SROpCodeBinaryFrame data:data]; + } else { + [self _sendFrameWithOpcode:SROpCodeTextFrame data:nil]; + } + }); + return YES; } -- (BOOL)sendDataNoCopy:(nullable NSData *)data error:(NSError **)error completion:(SRSendCompletionBlock)completion +- (BOOL)sendDataNoCopy:(nullable NSData *)data completion:(nullable SRSendCompletionBlock)completion { if (self.readyState != SR_OPEN) { NSString *message = @"Invalid State: Cannot call `sendDataNoCopy:error:` until connection is open."; - NSError *localError = SRErrorWithCodeDescription(2134, message); - if (error) { - *error = localError; - } - if (completion) { - completion(localError); + completion(SRErrorWithCodeDescription(2134, message)); } SRDebugLog(message); return NO; @@ -1418,7 +1426,7 @@ -(void)_pumpScanner; static const size_t SRFrameHeaderOverhead = 32; -- (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data completion:(SRSendCompletionBlock)completion +- (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data completion:(nullable SRSendCompletionBlock)completion { [self assertOnWorkQueue]; From 706623c30e42686fd6f48fc8c208baeb16f80741 Mon Sep 17 00:00:00 2001 From: NathanLi Date: Wed, 9 Nov 2016 00:30:13 +0800 Subject: [PATCH 8/9] Use `[NSValue valueWithRange:dataRange]` as a key of _sendCallbacks. --- SocketRocket/SRWebSocket.m | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/SocketRocket/SRWebSocket.m b/SocketRocket/SRWebSocket.m index 64cbbb2c9..607a47fcd 100644 --- a/SocketRocket/SRWebSocket.m +++ b/SocketRocket/SRWebSocket.m @@ -164,7 +164,7 @@ @implementation SRWebSocket { // proxy support SRProxyConnect *_proxyConnect; - NSMutableDictionary *_sendCallbacks; + NSMutableDictionary *_sendCallbacks; } @synthesize readyState = _readyState; @@ -577,7 +577,7 @@ - (void)_closeWithProtocolError:(NSString *)message; - (void)_failWithError:(NSError *)error; { dispatch_async(_workQueue, ^{ - [_sendCallbacks enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, SRDataCallback * _Nonnull obj, BOOL * _Nonnull stop) { + [_sendCallbacks enumerateKeysAndObjectsUsingBlock:^(NSValue * _Nonnull key, SRDataCallback * _Nonnull obj, BOOL * _Nonnull stop) { obj.completion(error); }]; [_sendCallbacks removeAllObjects]; @@ -621,8 +621,7 @@ - (void)_writeData:(NSData *)data completion:(nullable SRSendCompletionBlock)com NSRange dataRange = NSMakeRange(location, data.length); SRDataCallback *record = [[SRDataCallback alloc] initWithRange:dataRange completion:completion]; - static NSUInteger keyCount = 0; - _sendCallbacks[@(keyCount++)] = record; + _sendCallbacks[[NSValue valueWithRange:dataRange]] = record; } __block NSData *strongData = data; @@ -1150,23 +1149,27 @@ - (void)_pumpWriting; _outputBufferOffset += bytesWritten; - NSMutableArray *removeKeys = [NSMutableArray array]; - [_sendCallbacks enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, SRDataCallback * _Nonnull obj, BOOL * _Nonnull stop) { + NSMutableArray *removeKeys = [NSMutableArray array]; + [_sendCallbacks enumerateKeysAndObjectsUsingBlock:^(NSValue * _Nonnull key, SRDataCallback * _Nonnull obj, BOOL * _Nonnull stop) { if (NSMaxRange(obj.range) <= _outputBufferOffset) { [removeKeys addObject:key]; obj.completion(nil); } }]; - [_sendCallbacks removeObjectsForKeys:removeKeys]; if (_outputBufferOffset > SRDefaultBufferSize() && _outputBufferOffset > dataLength / 2) { - [_sendCallbacks enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, SRDataCallback * _Nonnull obj, BOOL * _Nonnull stop) { - NSRange range = obj.range; - range.location -= _outputBufferOffset; - obj.range = range; + + NSArray *callbacks = _sendCallbacks.allValues; + [_sendCallbacks removeAllObjects]; + [callbacks enumerateObjectsUsingBlock:^(SRDataCallback * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + NSRange dataRange = obj.range; + dataRange.location -= _outputBufferOffset; + obj.range = dataRange; + _sendCallbacks[[NSValue valueWithRange:dataRange]] = obj; }]; - + + _outputBuffer = dispatch_data_create_subrange(_outputBuffer, _outputBufferOffset, dataLength - _outputBufferOffset); _outputBufferOffset = 0; } From f630b95a08896e4d3dabff2a07a3643c2dece636 Mon Sep 17 00:00:00 2001 From: NathanLi Date: Fri, 23 Dec 2016 11:55:48 +0800 Subject: [PATCH 9/9] Change the error message. --- SocketRocket/SRWebSocket.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SocketRocket/SRWebSocket.m b/SocketRocket/SRWebSocket.m index 607a47fcd..6bd274b21 100644 --- a/SocketRocket/SRWebSocket.m +++ b/SocketRocket/SRWebSocket.m @@ -666,7 +666,7 @@ - (BOOL)sendString:(NSString *)string error:(NSError **)error - (BOOL)sendString:(NSString *)string completion:(nullable SRSendCompletionBlock)completion { if (self.readyState != SR_OPEN) { - NSString *message = @"Invalid State: Cannot call `sendString:error:` until connection is open."; + NSString *message = @"Invalid State: Cannot call `sendString:completion:` until connection is open."; if (completion) { completion(SRErrorWithCodeDescription(2134, message)); } @@ -711,7 +711,7 @@ - (BOOL)sendDataNoCopy:(nullable NSData *)data error:(NSError **)error - (BOOL)sendDataNoCopy:(nullable NSData *)data completion:(nullable SRSendCompletionBlock)completion { if (self.readyState != SR_OPEN) { - NSString *message = @"Invalid State: Cannot call `sendDataNoCopy:error:` until connection is open."; + NSString *message = @"Invalid State: Cannot call `sendDataNoCopy:completion:` until connection is open."; if (completion) { completion(SRErrorWithCodeDescription(2134, message)); }