|
@@ -34,9 +34,6 @@
|
|
|
#import <CommonCrypto/CommonDigest.h>
|
|
|
#import <Security/SecRandom.h>
|
|
|
|
|
|
-#import "base64.h"
|
|
|
-#import "NSData+SRB64Additions.h"
|
|
|
-
|
|
|
#if OS_OBJECT_USE_OBJC_RETAIN_RELEASE
|
|
|
#define sr_dispatch_retain(x)
|
|
|
#define sr_dispatch_release(x)
|
|
@@ -48,7 +45,7 @@
|
|
|
#endif
|
|
|
|
|
|
#if !__has_feature(objc_arc)
|
|
|
-#error SocketRocket muust be compiled with ARC enabled
|
|
|
+#error SocketRocket must be compiled with ARC enabled
|
|
|
#endif
|
|
|
|
|
|
|
|
@@ -62,19 +59,6 @@ typedef enum {
|
|
|
// B-F reserved.
|
|
|
} SROpCode;
|
|
|
|
|
|
-typedef enum {
|
|
|
- SRStatusCodeNormal = 1000,
|
|
|
- SRStatusCodeGoingAway = 1001,
|
|
|
- SRStatusCodeProtocolError = 1002,
|
|
|
- SRStatusCodeUnhandledType = 1003,
|
|
|
- // 1004 reserved.
|
|
|
- SRStatusNoStatusReceived = 1005,
|
|
|
- // 1004-1006 reserved.
|
|
|
- SRStatusCodeInvalidUTF8 = 1007,
|
|
|
- SRStatusCodePolicyViolated = 1008,
|
|
|
- SRStatusCodeMessageTooBig = 1009,
|
|
|
-} SRStatusCode;
|
|
|
-
|
|
|
typedef struct {
|
|
|
BOOL fin;
|
|
|
// BOOL rsv1;
|
|
@@ -88,7 +72,6 @@ typedef struct {
|
|
|
static NSString *const SRWebSocketAppendToSecKeyString = @"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
|
|
|
|
static inline int32_t validate_dispatch_data_partial_string(NSData *data);
|
|
|
-static inline dispatch_queue_t log_queue();
|
|
|
static inline void SRFastLog(NSString *format, ...);
|
|
|
|
|
|
@interface NSData (SRWebSocket)
|
|
@@ -123,20 +106,21 @@ static inline void SRFastLog(NSString *format, ...);
|
|
|
|
|
|
static NSString *newSHA1String(const char *bytes, size_t length) {
|
|
|
uint8_t md[CC_SHA1_DIGEST_LENGTH];
|
|
|
+
|
|
|
+ assert(length >= 0);
|
|
|
+ assert(length <= UINT32_MAX);
|
|
|
+ CC_SHA1(bytes, (CC_LONG)length, md);
|
|
|
|
|
|
- CC_SHA1(bytes, length, md);
|
|
|
-
|
|
|
- size_t buffer_size = ((sizeof(md) * 3 + 2) / 2);
|
|
|
-
|
|
|
- char *buffer = (char *)malloc(buffer_size);
|
|
|
+ NSData *data = [NSData dataWithBytes:md length:CC_SHA1_DIGEST_LENGTH];
|
|
|
|
|
|
- int len = b64_ntop(md, CC_SHA1_DIGEST_LENGTH, buffer, buffer_size);
|
|
|
- if (len == -1) {
|
|
|
- free(buffer);
|
|
|
- return nil;
|
|
|
- } else{
|
|
|
- return [[NSString alloc] initWithBytesNoCopy:buffer length:len encoding:NSASCIIStringEncoding freeWhenDone:YES];
|
|
|
+ if ([data respondsToSelector:@selector(base64EncodedStringWithOptions:)]) {
|
|
|
+ return [data base64EncodedStringWithOptions:0];
|
|
|
}
|
|
|
+
|
|
|
+#pragma clang diagnostic push
|
|
|
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
|
+ return [data base64Encoding];
|
|
|
+#pragma clang diagnostic pop
|
|
|
}
|
|
|
|
|
|
@implementation NSData (SRWebSocket)
|
|
@@ -159,6 +143,7 @@ static NSString *newSHA1String(const char *bytes, size_t length) {
|
|
|
@end
|
|
|
|
|
|
NSString *const SRWebSocketErrorDomain = @"SRWebSocketErrorDomain";
|
|
|
+NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
|
|
|
|
|
|
// Returns number of bytes consumed. Returning 0 means you didn't match.
|
|
|
// Sends bytes to callback handler;
|
|
@@ -193,33 +178,6 @@ typedef void (^data_callback)(SRWebSocket *webSocket, NSData *data);
|
|
|
|
|
|
@interface SRWebSocket () <NSStreamDelegate>
|
|
|
|
|
|
-- (void)_writeData:(NSData *)data;
|
|
|
-- (void)_closeWithProtocolError:(NSString *)message;
|
|
|
-- (void)_failWithError:(NSError *)error;
|
|
|
-
|
|
|
-- (void)_disconnect;
|
|
|
-
|
|
|
-- (void)_readFrameNew;
|
|
|
-- (void)_readFrameContinue;
|
|
|
-
|
|
|
-- (void)_pumpScanner;
|
|
|
-
|
|
|
-- (void)_pumpWriting;
|
|
|
-
|
|
|
-- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback;
|
|
|
-- (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)callback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes;
|
|
|
-- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback dataLength:(size_t)dataLength;
|
|
|
-- (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data_callback)dataHandler;
|
|
|
-- (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler;
|
|
|
-
|
|
|
-- (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data;
|
|
|
-
|
|
|
-- (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage;
|
|
|
-- (void)_SR_commonInit;
|
|
|
-
|
|
|
-- (void)_initializeStreams;
|
|
|
-- (void)_connect;
|
|
|
-
|
|
|
@property (nonatomic) SRReadyState readyState;
|
|
|
|
|
|
@property (nonatomic) NSOperationQueue *delegateOperationQueue;
|
|
@@ -270,7 +228,7 @@ typedef void (^data_callback)(SRWebSocket *webSocket, NSData *data);
|
|
|
BOOL _secure;
|
|
|
NSURLRequest *_urlRequest;
|
|
|
|
|
|
- CFHTTPMessageRef _receivedHTTPHeaders;
|
|
|
+
|
|
|
|
|
|
BOOL _sentClose;
|
|
|
BOOL _didFail;
|
|
@@ -382,8 +340,10 @@ static __strong NSData *CRLFCRLF;
|
|
|
[_inputStream close];
|
|
|
[_outputStream close];
|
|
|
|
|
|
- sr_dispatch_release(_workQueue);
|
|
|
- _workQueue = NULL;
|
|
|
+ if (_workQueue) {
|
|
|
+ sr_dispatch_release(_workQueue);
|
|
|
+ _workQueue = NULL;
|
|
|
+ }
|
|
|
|
|
|
if (_receivedHTTPHeaders) {
|
|
|
CFRelease(_receivedHTTPHeaders);
|
|
@@ -415,7 +375,7 @@ static __strong NSData *CRLFCRLF;
|
|
|
|
|
|
_selfRetain = self;
|
|
|
|
|
|
- [self _connect];
|
|
|
+ [self openConnection];
|
|
|
}
|
|
|
|
|
|
// Calls block on delegate queue
|
|
@@ -462,9 +422,8 @@ static __strong NSData *CRLFCRLF;
|
|
|
|
|
|
if (responseCode >= 400) {
|
|
|
SRFastLog(@"Request failed with response code %d", responseCode);
|
|
|
- [self _failWithError:[NSError errorWithDomain:@"org.lolrus.SocketRocket" code:2132 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"received bad response code from server %ld", (long)responseCode] forKey:NSLocalizedDescriptionKey]]];
|
|
|
+ [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2132 userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"received bad response code from server %ld", (long)responseCode], SRHTTPResponseErrorKey:@(responseCode)}]];
|
|
|
return;
|
|
|
-
|
|
|
}
|
|
|
|
|
|
if(![self _checkHandshake:_receivedHTTPHeaders]) {
|
|
@@ -525,15 +484,43 @@ static __strong NSData *CRLFCRLF;
|
|
|
|
|
|
NSMutableData *keyBytes = [[NSMutableData alloc] initWithLength:16];
|
|
|
SecRandomCopyBytes(kSecRandomDefault, keyBytes.length, keyBytes.mutableBytes);
|
|
|
- _secKey = [keyBytes SR_stringByBase64Encoding];
|
|
|
- assert([_secKey length] == 24);
|
|
|
|
|
|
+ if ([keyBytes respondsToSelector:@selector(base64EncodedStringWithOptions:)]) {
|
|
|
+ _secKey = [keyBytes base64EncodedStringWithOptions:0];
|
|
|
+ } else {
|
|
|
+#pragma clang diagnostic push
|
|
|
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
|
+ _secKey = [keyBytes base64Encoding];
|
|
|
+#pragma clang diagnostic pop
|
|
|
+ }
|
|
|
+
|
|
|
+ assert([_secKey length] == 24);
|
|
|
+
|
|
|
+ // Apply cookies if any have been provided
|
|
|
+ NSDictionary * cookies = [NSHTTPCookie requestHeaderFieldsWithCookies:[self requestCookies]];
|
|
|
+ for (NSString * cookieKey in cookies) {
|
|
|
+ NSString * cookieValue = [cookies objectForKey:cookieKey];
|
|
|
+ if ([cookieKey length] && [cookieValue length]) {
|
|
|
+ CFHTTPMessageSetHeaderFieldValue(request, (__bridge CFStringRef)cookieKey, (__bridge CFStringRef)cookieValue);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// set header for http basic auth
|
|
|
if (_url.user.length && _url.password.length) {
|
|
|
- NSString *userAndPasswordBase64Encoded = [[[NSString stringWithFormat:@"%@:%@", _url.user, _url.password] dataUsingEncoding:NSUTF8StringEncoding] SR_stringByBase64Encoding];
|
|
|
+ NSData *userAndPassword = [[NSString stringWithFormat:@"%@:%@", _url.user, _url.password] dataUsingEncoding:NSUTF8StringEncoding];
|
|
|
+ NSString *userAndPasswordBase64Encoded;
|
|
|
+ if ([keyBytes respondsToSelector:@selector(base64EncodedStringWithOptions:)]) {
|
|
|
+ userAndPasswordBase64Encoded = [userAndPassword base64EncodedStringWithOptions:0];
|
|
|
+ } else {
|
|
|
+#pragma clang diagnostic push
|
|
|
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
|
+ userAndPasswordBase64Encoded = [userAndPassword base64Encoding];
|
|
|
+#pragma clang diagnostic pop
|
|
|
+ }
|
|
|
_basicAuthorizationString = [NSString stringWithFormat:@"Basic %@", userAndPasswordBase64Encoded];
|
|
|
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Authorization"), (__bridge CFStringRef)_basicAuthorizationString);
|
|
|
}
|
|
|
+
|
|
|
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Upgrade"), CFSTR("websocket"));
|
|
|
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Connection"), CFSTR("Upgrade"));
|
|
|
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Key"), (__bridge CFStringRef)_secKey);
|
|
@@ -559,7 +546,8 @@ static __strong NSData *CRLFCRLF;
|
|
|
|
|
|
- (void)_initializeStreams;
|
|
|
{
|
|
|
- NSInteger port = _url.port.integerValue;
|
|
|
+ assert(_url.port.unsignedIntValue <= UINT32_MAX);
|
|
|
+ uint32_t port = _url.port.unsignedIntValue;
|
|
|
if (port == 0) {
|
|
|
if (!_secure) {
|
|
|
port = 80;
|
|
@@ -601,7 +589,7 @@ static __strong NSData *CRLFCRLF;
|
|
|
_outputStream.delegate = self;
|
|
|
}
|
|
|
|
|
|
-- (void)_connect;
|
|
|
+- (void)openConnection;
|
|
|
{
|
|
|
if (!_scheduledRunloops.count) {
|
|
|
[self scheduleInRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode];
|
|
@@ -630,7 +618,7 @@ static __strong NSData *CRLFCRLF;
|
|
|
|
|
|
- (void)close;
|
|
|
{
|
|
|
- [self closeWithCode:-1 reason:nil];
|
|
|
+ [self closeWithCode:SRStatusCodeNormal reason:nil];
|
|
|
}
|
|
|
|
|
|
- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason;
|
|
@@ -648,7 +636,7 @@ static __strong NSData *CRLFCRLF;
|
|
|
SRFastLog(@"Closing with code %d reason %@", code, reason);
|
|
|
|
|
|
if (wasConnecting) {
|
|
|
- [self _disconnect];
|
|
|
+ [self closeConnection];
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -664,6 +652,7 @@ static __strong NSData *CRLFCRLF;
|
|
|
NSUInteger usedLength = 0;
|
|
|
|
|
|
BOOL success = [reason getBytes:(char *)mutablePayload.mutableBytes + sizeof(uint16_t) maxLength:payload.length - sizeof(uint16_t) usedLength:&usedLength encoding:NSUTF8StringEncoding options:NSStringEncodingConversionExternalRepresentation range:NSMakeRange(0, reason.length) remainingRange:&remainingRange];
|
|
|
+ #pragma unused (success)
|
|
|
|
|
|
assert(success);
|
|
|
assert(remainingRange.length == 0);
|
|
@@ -684,7 +673,7 @@ static __strong NSData *CRLFCRLF;
|
|
|
[self _performDelegateBlock:^{
|
|
|
[self closeWithCode:SRStatusCodeProtocolError reason:message];
|
|
|
dispatch_async(_workQueue, ^{
|
|
|
- [self _disconnect];
|
|
|
+ [self closeConnection];
|
|
|
});
|
|
|
}];
|
|
|
}
|
|
@@ -705,7 +694,7 @@ static __strong NSData *CRLFCRLF;
|
|
|
|
|
|
SRFastLog(@"Failing with error %@", error.localizedDescription);
|
|
|
|
|
|
- [self _disconnect];
|
|
|
+ [self closeConnection];
|
|
|
}
|
|
|
});
|
|
|
}
|
|
@@ -720,6 +709,7 @@ static __strong NSData *CRLFCRLF;
|
|
|
[_outputBuffer appendData:data];
|
|
|
[self _pumpWriting];
|
|
|
}
|
|
|
+
|
|
|
- (void)send:(id)data;
|
|
|
{
|
|
|
NSAssert(self.readyState != SR_CONNECTING, @"Invalid State: Cannot call send: until connection is open");
|
|
@@ -738,6 +728,16 @@ static __strong NSData *CRLFCRLF;
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+- (void)sendPing:(NSData *)data;
|
|
|
+{
|
|
|
+ NSAssert(self.readyState == SR_OPEN, @"Invalid State: Cannot call send: until connection is open");
|
|
|
+ // TODO: maybe not copy this for performance
|
|
|
+ data = [data copy] ?: [NSData data]; // It's okay for a ping to be empty
|
|
|
+ dispatch_async(_workQueue, ^{
|
|
|
+ [self _sendFrameWithOpcode:SROpCodePing data:data];
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
- (void)handlePing:(NSData *)pingData;
|
|
|
{
|
|
|
// Need to pingpong this off _callbackQueue first to make sure messages happen in order
|
|
@@ -748,9 +748,14 @@ static __strong NSData *CRLFCRLF;
|
|
|
}];
|
|
|
}
|
|
|
|
|
|
-- (void)handlePong;
|
|
|
+- (void)handlePong:(NSData *)pongData;
|
|
|
{
|
|
|
- // NOOP
|
|
|
+ SRFastLog(@"Received pong");
|
|
|
+ [self _performDelegateBlock:^{
|
|
|
+ if ([self.delegate respondsToSelector:@selector(webSocket:didReceivePong:)]) {
|
|
|
+ [self.delegate webSocket:self didReceivePong:pongData];
|
|
|
+ }
|
|
|
+ }];
|
|
|
}
|
|
|
|
|
|
- (void)_handleMessage:(id)message
|
|
@@ -831,11 +836,11 @@ static inline BOOL closeCodeIsValid(int closeCode) {
|
|
|
[self closeWithCode:1000 reason:nil];
|
|
|
}
|
|
|
dispatch_async(_workQueue, ^{
|
|
|
- [self _disconnect];
|
|
|
+ [self closeConnection];
|
|
|
});
|
|
|
}
|
|
|
|
|
|
-- (void)_disconnect;
|
|
|
+- (void)closeConnection;
|
|
|
{
|
|
|
[self assertOnWorkQueue];
|
|
|
SRFastLog(@"Trying to disconnect");
|
|
@@ -862,7 +867,7 @@ static inline BOOL closeCodeIsValid(int closeCode) {
|
|
|
if (str == nil && frameData) {
|
|
|
[self closeWithCode:SRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8"];
|
|
|
dispatch_async(_workQueue, ^{
|
|
|
- [self _disconnect];
|
|
|
+ [self closeConnection];
|
|
|
});
|
|
|
|
|
|
return;
|
|
@@ -880,7 +885,7 @@ static inline BOOL closeCodeIsValid(int closeCode) {
|
|
|
[self handlePing:frameData];
|
|
|
break;
|
|
|
case SROpCodePong:
|
|
|
- [self handlePong];
|
|
|
+ [self handlePong:frameData];
|
|
|
break;
|
|
|
default:
|
|
|
[self _closeWithProtocolError:[NSString stringWithFormat:@"Unknown opcode %ld", (long)opcode]];
|
|
@@ -927,7 +932,8 @@ static inline BOOL closeCodeIsValid(int closeCode) {
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
- [self _addConsumerWithDataLength:frame_header.payload_length callback:^(SRWebSocket *self, NSData *newData) {
|
|
|
+ assert(frame_header.payload_length <= SIZE_T_MAX);
|
|
|
+ [self _addConsumerWithDataLength:(size_t)frame_header.payload_length callback:^(SRWebSocket *self, NSData *newData) {
|
|
|
if (isControlFrame) {
|
|
|
[self _handleFrameWithData:newData opCode:frame_header.opcode];
|
|
|
} else {
|
|
@@ -1028,6 +1034,7 @@ static const uint8_t SRPayloadLenMask = 0x7F;
|
|
|
} else {
|
|
|
[self _addConsumerWithDataLength:extra_bytes_needed callback:^(SRWebSocket *self, NSData *data) {
|
|
|
size_t mapped_size = data.length;
|
|
|
+ #pragma unused (mapped_size)
|
|
|
const void *mapped_buffer = data.bytes;
|
|
|
size_t offset = 0;
|
|
|
|
|
@@ -1044,7 +1051,6 @@ static const uint8_t SRPayloadLenMask = 0x7F;
|
|
|
assert(header.payload_length < 126 && header.payload_length >= 0);
|
|
|
}
|
|
|
|
|
|
-
|
|
|
if (header.masked) {
|
|
|
assert(mapped_size >= sizeof(_currentReadMaskOffset) + offset);
|
|
|
memcpy(self->_currentReadMaskKey, ((uint8_t *)mapped_buffer) + offset, sizeof(self->_currentReadMaskKey));
|
|
@@ -1078,7 +1084,7 @@ static const uint8_t SRPayloadLenMask = 0x7F;
|
|
|
if (dataLength - _outputBufferOffset > 0 && _outputStream.hasSpaceAvailable) {
|
|
|
NSInteger bytesWritten = [_outputStream write:_outputBuffer.bytes + _outputBufferOffset maxLength:dataLength - _outputBufferOffset];
|
|
|
if (bytesWritten == -1) {
|
|
|
- [self _failWithError:[NSError errorWithDomain:@"org.lolrus.SocketRocket" code:2145 userInfo:[NSDictionary dictionaryWithObject:@"Error writing to stream" forKey:NSLocalizedDescriptionKey]]];
|
|
|
+ [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2145 userInfo:[NSDictionary dictionaryWithObject:@"Error writing to stream" forKey:NSLocalizedDescriptionKey]]];
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -1252,7 +1258,7 @@ static const char CRLFCRLFBytes[] = {'\r', '\n', '\r', '\n'};
|
|
|
if (valid_utf8_size == -1) {
|
|
|
[self closeWithCode:SRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8"];
|
|
|
dispatch_async(_workQueue, ^{
|
|
|
- [self _disconnect];
|
|
|
+ [self closeConnection];
|
|
|
});
|
|
|
return didWork;
|
|
|
} else {
|
|
@@ -1305,7 +1311,11 @@ static const size_t SRFrameHeaderOverhead = 32;
|
|
|
{
|
|
|
[self assertOnWorkQueue];
|
|
|
|
|
|
- NSAssert(data == nil || [data isKindOfClass:[NSData class]] || [data isKindOfClass:[NSString class]], @"Function expects nil, NSString or NSData");
|
|
|
+ if (nil == data) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ NSAssert([data isKindOfClass:[NSData class]] || [data isKindOfClass:[NSString class]], @"NSString or NSData");
|
|
|
|
|
|
size_t payloadLength = [data isKindOfClass:[NSString class]] ? [(NSString *)data lengthOfBytesUsingEncoding:NSUTF8StringEncoding] : [data length];
|
|
|
|
|
@@ -1337,7 +1347,7 @@ static const size_t SRFrameHeaderOverhead = 32;
|
|
|
} else if ([data isKindOfClass:[NSString class]]) {
|
|
|
unmasked_payload = (const uint8_t *)[data UTF8String];
|
|
|
} else {
|
|
|
- assert(NO);
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
if (payloadLength < 126) {
|
|
@@ -1402,7 +1412,7 @@ static const size_t SRFrameHeaderOverhead = 32;
|
|
|
|
|
|
if (!_pinnedCertFound) {
|
|
|
dispatch_async(_workQueue, ^{
|
|
|
- [self _failWithError:[NSError errorWithDomain:@"org.lolrus.SocketRocket" code:23556 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Invalid server cert"] forKey:NSLocalizedDescriptionKey]]];
|
|
|
+ [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:23556 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Invalid server cert"] forKey:NSLocalizedDescriptionKey]]];
|
|
|
});
|
|
|
return;
|
|
|
}
|
|
@@ -1452,7 +1462,7 @@ static const size_t SRFrameHeaderOverhead = 32;
|
|
|
// If we get closed in this state it's probably not clean because we should be sending this when we send messages
|
|
|
[self _performDelegateBlock:^{
|
|
|
if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) {
|
|
|
- [self.delegate webSocket:self didCloseWithCode:0 reason:@"Stream end encountered" wasClean:NO];
|
|
|
+ [self.delegate webSocket:self didCloseWithCode:SRStatusCodeGoingAway reason:@"Stream end encountered" wasClean:NO];
|
|
|
}
|
|
|
}];
|
|
|
}
|
|
@@ -1467,7 +1477,7 @@ static const size_t SRFrameHeaderOverhead = 32;
|
|
|
uint8_t buffer[bufferSize];
|
|
|
|
|
|
while (_inputStream.hasBytesAvailable) {
|
|
|
- int bytes_read = [_inputStream read:buffer maxLength:bufferSize];
|
|
|
+ NSInteger bytes_read = [_inputStream read:buffer maxLength:bufferSize];
|
|
|
|
|
|
if (bytes_read > 0) {
|
|
|
[_readBuffer appendBytes:buffer length:bytes_read];
|
|
@@ -1610,16 +1620,6 @@ static const size_t SRFrameHeaderOverhead = 32;
|
|
|
|
|
|
@end
|
|
|
|
|
|
-static inline dispatch_queue_t log_queue() {
|
|
|
- static dispatch_queue_t queue = 0;
|
|
|
- static dispatch_once_t onceToken;
|
|
|
- dispatch_once(&onceToken, ^{
|
|
|
- queue = dispatch_queue_create("fast log queue", DISPATCH_QUEUE_SERIAL);
|
|
|
- });
|
|
|
-
|
|
|
- return queue;
|
|
|
-}
|
|
|
-
|
|
|
//#define SR_ENABLE_LOG
|
|
|
|
|
|
static inline void SRFastLog(NSString *format, ...) {
|
|
@@ -1639,9 +1639,14 @@ static inline void SRFastLog(NSString *format, ...) {
|
|
|
#ifdef HAS_ICU
|
|
|
|
|
|
static inline int32_t validate_dispatch_data_partial_string(NSData *data) {
|
|
|
+ if ([data length] > INT32_MAX) {
|
|
|
+ // INT32_MAX is the limit so long as this Framework is using 32 bit ints everywhere.
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ int32_t size = (int32_t)[data length];
|
|
|
+
|
|
|
const void * contents = [data bytes];
|
|
|
- long size = [data length];
|
|
|
-
|
|
|
const uint8_t *str = (const uint8_t *)contents;
|
|
|
|
|
|
UChar32 codepoint = 1;
|
|
@@ -1689,7 +1694,7 @@ static inline int32_t validate_dispatch_data_partial_string(NSData *data) {
|
|
|
for (int i = 0; i < maxCodepointSize; i++) {
|
|
|
NSString *str = [[NSString alloc] initWithBytesNoCopy:(char *)data.bytes length:data.length - i encoding:NSUTF8StringEncoding freeWhenDone:NO];
|
|
|
if (str) {
|
|
|
- return data.length - i;
|
|
|
+ return (int32_t)data.length - i;
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1745,8 +1750,22 @@ static NSRunLoop *networkRunLoop = nil;
|
|
|
_runLoop = [NSRunLoop currentRunLoop];
|
|
|
dispatch_group_leave(_waitGroup);
|
|
|
|
|
|
- NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate distantFuture] interval:0.0 target:nil selector:nil userInfo:nil repeats:NO];
|
|
|
- [_runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
|
|
|
+ // Add an empty run loop source to prevent runloop from spinning.
|
|
|
+ CFRunLoopSourceContext sourceCtx = {
|
|
|
+ .version = 0,
|
|
|
+ .info = NULL,
|
|
|
+ .retain = NULL,
|
|
|
+ .release = NULL,
|
|
|
+ .copyDescription = NULL,
|
|
|
+ .equal = NULL,
|
|
|
+ .hash = NULL,
|
|
|
+ .schedule = NULL,
|
|
|
+ .cancel = NULL,
|
|
|
+ .perform = NULL
|
|
|
+ };
|
|
|
+ CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &sourceCtx);
|
|
|
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
|
|
|
+ CFRelease(source);
|
|
|
|
|
|
while ([_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) {
|
|
|
|