Adds SocketRocket library for iOS/macOS
This commit is contained in:
parent
d003c44451
commit
9aacad356d
|
|
@ -0,0 +1,52 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
#import <SocketRocket/SRWebSocket.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
struct SRDelegateAvailableMethods {
|
||||||
|
BOOL didReceiveMessage : 1;
|
||||||
|
BOOL didReceiveMessageWithString : 1;
|
||||||
|
BOOL didReceiveMessageWithData : 1;
|
||||||
|
BOOL didOpen : 1;
|
||||||
|
BOOL didFailWithError : 1;
|
||||||
|
BOOL didCloseWithCode : 1;
|
||||||
|
BOOL didReceivePing : 1;
|
||||||
|
BOOL didReceivePong : 1;
|
||||||
|
BOOL shouldConvertTextFrameToString : 1;
|
||||||
|
};
|
||||||
|
typedef struct SRDelegateAvailableMethods SRDelegateAvailableMethods;
|
||||||
|
|
||||||
|
typedef void(^SRDelegateBlock)(id<SRWebSocketDelegate> _Nullable delegate, SRDelegateAvailableMethods availableMethods);
|
||||||
|
|
||||||
|
@interface SRDelegateController : NSObject
|
||||||
|
|
||||||
|
@property (nonatomic, weak) id<SRWebSocketDelegate> delegate;
|
||||||
|
@property (atomic, readonly) SRDelegateAvailableMethods availableDelegateMethods;
|
||||||
|
|
||||||
|
#if OS_OBJECT_USE_OBJC
|
||||||
|
@property (nullable, nonatomic, strong) dispatch_queue_t dispatchQueue;
|
||||||
|
#else
|
||||||
|
@property (nullable, nonatomic, assign) dispatch_queue_t dispatchQueue;
|
||||||
|
#endif
|
||||||
|
@property (nullable, nonatomic, strong) NSOperationQueue *operationQueue;
|
||||||
|
|
||||||
|
///--------------------------------------
|
||||||
|
#pragma mark - Perform
|
||||||
|
///--------------------------------------
|
||||||
|
|
||||||
|
- (void)performDelegateBlock:(SRDelegateBlock)block;
|
||||||
|
- (void)performDelegateQueueBlock:(dispatch_block_t)block;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SRDelegateController.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface SRDelegateController ()
|
||||||
|
|
||||||
|
@property (nonatomic, strong, readonly) dispatch_queue_t accessQueue;
|
||||||
|
|
||||||
|
@property (atomic, assign, readwrite) SRDelegateAvailableMethods availableDelegateMethods;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation SRDelegateController
|
||||||
|
|
||||||
|
@synthesize delegate = _delegate;
|
||||||
|
@synthesize dispatchQueue = _dispatchQueue;
|
||||||
|
@synthesize operationQueue = _operationQueue;
|
||||||
|
|
||||||
|
///--------------------------------------
|
||||||
|
#pragma mark - Init
|
||||||
|
///--------------------------------------
|
||||||
|
|
||||||
|
- (instancetype)init
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
if (!self) return self;
|
||||||
|
|
||||||
|
_accessQueue = dispatch_queue_create("com.facebook.socketrocket.delegate.access", DISPATCH_QUEUE_CONCURRENT);
|
||||||
|
_dispatchQueue = dispatch_get_main_queue();
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
///--------------------------------------
|
||||||
|
#pragma mark - Accessors
|
||||||
|
///--------------------------------------
|
||||||
|
|
||||||
|
- (void)setDelegate:(id<SRWebSocketDelegate> _Nullable)delegate
|
||||||
|
{
|
||||||
|
dispatch_barrier_async(self.accessQueue, ^{
|
||||||
|
_delegate = delegate;
|
||||||
|
|
||||||
|
self.availableDelegateMethods = (SRDelegateAvailableMethods){
|
||||||
|
.didReceiveMessage = [delegate respondsToSelector:@selector(webSocket:didReceiveMessage:)],
|
||||||
|
.didReceiveMessageWithString = [delegate respondsToSelector:@selector(webSocket:didReceiveMessageWithString:)],
|
||||||
|
.didReceiveMessageWithData = [delegate respondsToSelector:@selector(webSocket:didReceiveMessageWithData:)],
|
||||||
|
.didOpen = [delegate respondsToSelector:@selector(webSocketDidOpen:)],
|
||||||
|
.didFailWithError = [delegate respondsToSelector:@selector(webSocket:didFailWithError:)],
|
||||||
|
.didCloseWithCode = [delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)],
|
||||||
|
.didReceivePing = [delegate respondsToSelector:@selector(webSocket:didReceivePingWithData:)],
|
||||||
|
.didReceivePong = [delegate respondsToSelector:@selector(webSocket:didReceivePong:)],
|
||||||
|
.shouldConvertTextFrameToString = [delegate respondsToSelector:@selector(webSocketShouldConvertTextFrameToString:)]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id<SRWebSocketDelegate> _Nullable)delegate
|
||||||
|
{
|
||||||
|
__block id<SRWebSocketDelegate> delegate = nil;
|
||||||
|
dispatch_sync(self.accessQueue, ^{
|
||||||
|
delegate = _delegate;
|
||||||
|
});
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setDispatchQueue:(dispatch_queue_t _Nullable)queue
|
||||||
|
{
|
||||||
|
dispatch_barrier_async(self.accessQueue, ^{
|
||||||
|
_dispatchQueue = queue ?: dispatch_get_main_queue();
|
||||||
|
_operationQueue = nil;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
- (dispatch_queue_t _Nullable)dispatchQueue
|
||||||
|
{
|
||||||
|
__block dispatch_queue_t queue = nil;
|
||||||
|
dispatch_sync(self.accessQueue, ^{
|
||||||
|
queue = _dispatchQueue;
|
||||||
|
});
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setOperationQueue:(NSOperationQueue *_Nullable)queue
|
||||||
|
{
|
||||||
|
dispatch_barrier_async(self.accessQueue, ^{
|
||||||
|
_dispatchQueue = queue ? nil : dispatch_get_main_queue();
|
||||||
|
_operationQueue = queue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSOperationQueue *_Nullable)operationQueue
|
||||||
|
{
|
||||||
|
__block NSOperationQueue *queue = nil;
|
||||||
|
dispatch_sync(self.accessQueue, ^{
|
||||||
|
queue = _operationQueue;
|
||||||
|
});
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
///--------------------------------------
|
||||||
|
#pragma mark - Perform
|
||||||
|
///--------------------------------------
|
||||||
|
|
||||||
|
- (void)performDelegateBlock:(SRDelegateBlock)block
|
||||||
|
{
|
||||||
|
__block __strong id<SRWebSocketDelegate> delegate = nil;
|
||||||
|
__block SRDelegateAvailableMethods availableMethods = {};
|
||||||
|
dispatch_sync(self.accessQueue, ^{
|
||||||
|
delegate = _delegate; // Not `OK` to go through `self`, since queue sync.
|
||||||
|
availableMethods = self.availableDelegateMethods; // `OK` to call through `self`, since no queue sync.
|
||||||
|
});
|
||||||
|
[self performDelegateQueueBlock:^{
|
||||||
|
block(delegate, availableMethods);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)performDelegateQueueBlock:(dispatch_block_t)block
|
||||||
|
{
|
||||||
|
dispatch_queue_t dispatchQueue = self.dispatchQueue;
|
||||||
|
if (dispatchQueue) {
|
||||||
|
dispatch_async(dispatchQueue, block);
|
||||||
|
} else {
|
||||||
|
[self.operationQueue addOperationWithBlock:block];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
//
|
||||||
|
// Copyright 2012 Square Inc.
|
||||||
|
// Portions Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
@class SRWebSocket; // TODO: (nlutsenko) Remove dependency on SRWebSocket here.
|
||||||
|
|
||||||
|
// Returns number of bytes consumed. Returning 0 means you didn't match.
|
||||||
|
// Sends bytes to callback handler;
|
||||||
|
typedef size_t (^stream_scanner)(NSData *collected_data);
|
||||||
|
typedef void (^data_callback)(SRWebSocket *webSocket, NSData *data);
|
||||||
|
|
||||||
|
@interface SRIOConsumer : NSObject {
|
||||||
|
stream_scanner _scanner;
|
||||||
|
data_callback _handler;
|
||||||
|
size_t _bytesNeeded;
|
||||||
|
BOOL _readToCurrentFrame;
|
||||||
|
BOOL _unmaskBytes;
|
||||||
|
}
|
||||||
|
@property (nonatomic, copy, readonly) stream_scanner consumer;
|
||||||
|
@property (nonatomic, copy, readonly) data_callback handler;
|
||||||
|
@property (nonatomic, assign) size_t bytesNeeded;
|
||||||
|
@property (nonatomic, assign, readonly) BOOL readToCurrentFrame;
|
||||||
|
@property (nonatomic, assign, readonly) BOOL unmaskBytes;
|
||||||
|
|
||||||
|
- (void)resetWithScanner:(stream_scanner)scanner
|
||||||
|
handler:(data_callback)handler
|
||||||
|
bytesNeeded:(size_t)bytesNeeded
|
||||||
|
readToCurrentFrame:(BOOL)readToCurrentFrame
|
||||||
|
unmaskBytes:(BOOL)unmaskBytes;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
//
|
||||||
|
// Copyright 2012 Square Inc.
|
||||||
|
// Portions Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SRIOConsumer.h"
|
||||||
|
|
||||||
|
@implementation SRIOConsumer
|
||||||
|
|
||||||
|
@synthesize bytesNeeded = _bytesNeeded;
|
||||||
|
@synthesize consumer = _scanner;
|
||||||
|
@synthesize handler = _handler;
|
||||||
|
@synthesize readToCurrentFrame = _readToCurrentFrame;
|
||||||
|
@synthesize unmaskBytes = _unmaskBytes;
|
||||||
|
|
||||||
|
- (void)resetWithScanner:(stream_scanner)scanner
|
||||||
|
handler:(data_callback)handler
|
||||||
|
bytesNeeded:(size_t)bytesNeeded
|
||||||
|
readToCurrentFrame:(BOOL)readToCurrentFrame
|
||||||
|
unmaskBytes:(BOOL)unmaskBytes
|
||||||
|
{
|
||||||
|
_scanner = [scanner copy];
|
||||||
|
_handler = [handler copy];
|
||||||
|
_bytesNeeded = bytesNeeded;
|
||||||
|
_readToCurrentFrame = readToCurrentFrame;
|
||||||
|
_unmaskBytes = unmaskBytes;
|
||||||
|
assert(_scanner || _bytesNeeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
//
|
||||||
|
// Copyright 2012 Square Inc.
|
||||||
|
// Portions Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
#import "SRIOConsumer.h" // TODO: (nlutsenko) Convert to @class and constants file for block types
|
||||||
|
|
||||||
|
// This class is not thread-safe, and is expected to always be run on the same queue.
|
||||||
|
@interface SRIOConsumerPool : NSObject
|
||||||
|
|
||||||
|
- (instancetype)initWithBufferCapacity:(NSUInteger)poolSize;
|
||||||
|
|
||||||
|
- (SRIOConsumer *)consumerWithScanner:(stream_scanner)scanner
|
||||||
|
handler:(data_callback)handler
|
||||||
|
bytesNeeded:(size_t)bytesNeeded
|
||||||
|
readToCurrentFrame:(BOOL)readToCurrentFrame
|
||||||
|
unmaskBytes:(BOOL)unmaskBytes;
|
||||||
|
- (void)returnConsumer:(SRIOConsumer *)consumer;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
//
|
||||||
|
// Copyright 2012 Square Inc.
|
||||||
|
// Portions Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SRIOConsumerPool.h"
|
||||||
|
|
||||||
|
@implementation SRIOConsumerPool {
|
||||||
|
NSUInteger _poolSize;
|
||||||
|
NSMutableArray<SRIOConsumer *> *_bufferedConsumers;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithBufferCapacity:(NSUInteger)poolSize;
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
if (self) {
|
||||||
|
_poolSize = poolSize;
|
||||||
|
_bufferedConsumers = [NSMutableArray arrayWithCapacity:poolSize];
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)init
|
||||||
|
{
|
||||||
|
return [self initWithBufferCapacity:8];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (SRIOConsumer *)consumerWithScanner:(stream_scanner)scanner
|
||||||
|
handler:(data_callback)handler
|
||||||
|
bytesNeeded:(size_t)bytesNeeded
|
||||||
|
readToCurrentFrame:(BOOL)readToCurrentFrame
|
||||||
|
unmaskBytes:(BOOL)unmaskBytes
|
||||||
|
{
|
||||||
|
SRIOConsumer *consumer = nil;
|
||||||
|
if (_bufferedConsumers.count) {
|
||||||
|
consumer = [_bufferedConsumers lastObject];
|
||||||
|
[_bufferedConsumers removeLastObject];
|
||||||
|
} else {
|
||||||
|
consumer = [[SRIOConsumer alloc] init];
|
||||||
|
}
|
||||||
|
|
||||||
|
[consumer resetWithScanner:scanner
|
||||||
|
handler:handler
|
||||||
|
bytesNeeded:bytesNeeded
|
||||||
|
readToCurrentFrame:readToCurrentFrame
|
||||||
|
unmaskBytes:unmaskBytes];
|
||||||
|
|
||||||
|
return consumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)returnConsumer:(SRIOConsumer *)consumer;
|
||||||
|
{
|
||||||
|
if (_bufferedConsumers.count < _poolSize) {
|
||||||
|
[_bufferedConsumers addObject:consumer];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <SocketRocket/NSRunLoop+SRWebSocket.h>
|
||||||
|
|
||||||
|
// Empty function that force links the object file for the category.
|
||||||
|
extern void import_NSRunLoop_SRWebSocket(void);
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <SocketRocket/NSURLRequest+SRWebSocket.h>
|
||||||
|
|
||||||
|
// Empty function that force links the object file for the category.
|
||||||
|
extern void import_NSURLRequest_SRWebSocket(void);
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
typedef void(^SRProxyConnectCompletion)(NSError *_Nullable error,
|
||||||
|
NSInputStream *_Nullable readStream,
|
||||||
|
NSOutputStream *_Nullable writeStream);
|
||||||
|
|
||||||
|
@interface SRProxyConnect : NSObject
|
||||||
|
|
||||||
|
- (instancetype)initWithURL:(NSURL *)url;
|
||||||
|
|
||||||
|
- (void)openNetworkStreamWithCompletion:(SRProxyConnectCompletion)completion;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,481 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SRProxyConnect.h"
|
||||||
|
|
||||||
|
#import "NSRunLoop+SRWebSocket.h"
|
||||||
|
#import "SRConstants.h"
|
||||||
|
#import "SRError.h"
|
||||||
|
#import "SRLog.h"
|
||||||
|
#import "SRURLUtilities.h"
|
||||||
|
|
||||||
|
@interface SRProxyConnect() <NSStreamDelegate>
|
||||||
|
|
||||||
|
@property (nonatomic, strong) NSURL *url;
|
||||||
|
@property (nonatomic, strong) NSInputStream *inputStream;
|
||||||
|
@property (nonatomic, strong) NSOutputStream *outputStream;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation SRProxyConnect
|
||||||
|
{
|
||||||
|
SRProxyConnectCompletion _completion;
|
||||||
|
|
||||||
|
NSString *_httpProxyHost;
|
||||||
|
uint32_t _httpProxyPort;
|
||||||
|
|
||||||
|
CFHTTPMessageRef _receivedHTTPHeaders;
|
||||||
|
|
||||||
|
NSString *_socksProxyHost;
|
||||||
|
uint32_t _socksProxyPort;
|
||||||
|
NSString *_socksProxyUsername;
|
||||||
|
NSString *_socksProxyPassword;
|
||||||
|
|
||||||
|
BOOL _connectionRequiresSSL;
|
||||||
|
|
||||||
|
NSMutableArray<NSData *> *_inputQueue;
|
||||||
|
dispatch_queue_t _writeQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
///--------------------------------------
|
||||||
|
#pragma mark - Init
|
||||||
|
///--------------------------------------
|
||||||
|
|
||||||
|
-(instancetype)initWithURL:(NSURL *)url
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
if (!self) return self;
|
||||||
|
|
||||||
|
_url = url;
|
||||||
|
_connectionRequiresSSL = SRURLRequiresSSL(url);
|
||||||
|
|
||||||
|
_writeQueue = dispatch_queue_create("com.facebook.socketrocket.proxyconnect.write", DISPATCH_QUEUE_SERIAL);
|
||||||
|
_inputQueue = [NSMutableArray arrayWithCapacity:2];
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
// If we get deallocated before the socket open finishes - we need to cleanup everything.
|
||||||
|
|
||||||
|
[self.inputStream removeFromRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode];
|
||||||
|
self.inputStream.delegate = nil;
|
||||||
|
[self.inputStream close];
|
||||||
|
self.inputStream = nil;
|
||||||
|
|
||||||
|
self.outputStream.delegate = nil;
|
||||||
|
[self.outputStream close];
|
||||||
|
self.outputStream = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
///--------------------------------------
|
||||||
|
#pragma mark - Open
|
||||||
|
///--------------------------------------
|
||||||
|
|
||||||
|
- (void)openNetworkStreamWithCompletion:(SRProxyConnectCompletion)completion
|
||||||
|
{
|
||||||
|
_completion = completion;
|
||||||
|
[self _configureProxy];
|
||||||
|
}
|
||||||
|
|
||||||
|
///--------------------------------------
|
||||||
|
#pragma mark - Flow
|
||||||
|
///--------------------------------------
|
||||||
|
|
||||||
|
- (void)_didConnect
|
||||||
|
{
|
||||||
|
SRDebugLog(@"_didConnect, return streams");
|
||||||
|
if (_connectionRequiresSSL) {
|
||||||
|
if (_httpProxyHost) {
|
||||||
|
// Must set the real peer name before turning on SSL
|
||||||
|
SRDebugLog(@"proxy set peer name to real host %@", self.url.host);
|
||||||
|
[self.outputStream setProperty:self.url.host forKey:@"_kCFStreamPropertySocketPeerName"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_receivedHTTPHeaders) {
|
||||||
|
CFRelease(_receivedHTTPHeaders);
|
||||||
|
_receivedHTTPHeaders = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSInputStream *inputStream = self.inputStream;
|
||||||
|
NSOutputStream *outputStream = self.outputStream;
|
||||||
|
|
||||||
|
self.inputStream = nil;
|
||||||
|
self.outputStream = nil;
|
||||||
|
|
||||||
|
[inputStream removeFromRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode];
|
||||||
|
inputStream.delegate = nil;
|
||||||
|
outputStream.delegate = nil;
|
||||||
|
|
||||||
|
_completion(nil, inputStream, outputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_failWithError:(NSError *)error
|
||||||
|
{
|
||||||
|
SRDebugLog(@"_failWithError, return error");
|
||||||
|
if (!error) {
|
||||||
|
error = SRHTTPErrorWithCodeDescription(500, 2132,@"Proxy Error");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_receivedHTTPHeaders) {
|
||||||
|
CFRelease(_receivedHTTPHeaders);
|
||||||
|
_receivedHTTPHeaders = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.inputStream.delegate = nil;
|
||||||
|
self.outputStream.delegate = nil;
|
||||||
|
|
||||||
|
[self.inputStream removeFromRunLoop:[NSRunLoop SR_networkRunLoop]
|
||||||
|
forMode:NSDefaultRunLoopMode];
|
||||||
|
[self.inputStream close];
|
||||||
|
[self.outputStream close];
|
||||||
|
self.inputStream = nil;
|
||||||
|
self.outputStream = nil;
|
||||||
|
_completion(error, nil, nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get proxy setting from device setting
|
||||||
|
- (void)_configureProxy
|
||||||
|
{
|
||||||
|
SRDebugLog(@"configureProxy");
|
||||||
|
NSDictionary *proxySettings = CFBridgingRelease(CFNetworkCopySystemProxySettings());
|
||||||
|
|
||||||
|
// CFNetworkCopyProxiesForURL doesn't understand ws:// or wss://
|
||||||
|
NSURL *httpURL;
|
||||||
|
if (_connectionRequiresSSL) {
|
||||||
|
httpURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@", _url.host]];
|
||||||
|
} else {
|
||||||
|
httpURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@", _url.host]];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSArray *proxies = CFBridgingRelease(CFNetworkCopyProxiesForURL((__bridge CFURLRef)httpURL, (__bridge CFDictionaryRef)proxySettings));
|
||||||
|
if (proxies.count == 0) {
|
||||||
|
SRDebugLog(@"configureProxy no proxies");
|
||||||
|
[self _openConnection];
|
||||||
|
return; // no proxy
|
||||||
|
}
|
||||||
|
NSDictionary *settings = [proxies objectAtIndex:0];
|
||||||
|
NSString *proxyType = settings[(NSString *)kCFProxyTypeKey];
|
||||||
|
if ([proxyType isEqualToString:(NSString *)kCFProxyTypeAutoConfigurationURL]) {
|
||||||
|
NSURL *pacURL = settings[(NSString *)kCFProxyAutoConfigurationURLKey];
|
||||||
|
if (pacURL) {
|
||||||
|
[self _fetchPAC:pacURL withProxySettings:proxySettings];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ([proxyType isEqualToString:(__bridge NSString *)kCFProxyTypeAutoConfigurationJavaScript]) {
|
||||||
|
NSString *script = settings[(__bridge NSString *)kCFProxyAutoConfigurationJavaScriptKey];
|
||||||
|
if (script) {
|
||||||
|
[self _runPACScript:script withProxySettings:proxySettings];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[self _readProxySettingWithType:proxyType settings:settings];
|
||||||
|
|
||||||
|
[self _openConnection];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_readProxySettingWithType:(NSString *)proxyType settings:(NSDictionary *)settings
|
||||||
|
{
|
||||||
|
if ([proxyType isEqualToString:(NSString *)kCFProxyTypeHTTP] ||
|
||||||
|
[proxyType isEqualToString:(NSString *)kCFProxyTypeHTTPS]) {
|
||||||
|
_httpProxyHost = settings[(NSString *)kCFProxyHostNameKey];
|
||||||
|
NSNumber *portValue = settings[(NSString *)kCFProxyPortNumberKey];
|
||||||
|
if (portValue) {
|
||||||
|
_httpProxyPort = [portValue intValue];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ([proxyType isEqualToString:(NSString *)kCFProxyTypeSOCKS]) {
|
||||||
|
_socksProxyHost = settings[(NSString *)kCFProxyHostNameKey];
|
||||||
|
NSNumber *portValue = settings[(NSString *)kCFProxyPortNumberKey];
|
||||||
|
if (portValue)
|
||||||
|
_socksProxyPort = [portValue intValue];
|
||||||
|
_socksProxyUsername = settings[(NSString *)kCFProxyUsernameKey];
|
||||||
|
_socksProxyPassword = settings[(NSString *)kCFProxyPasswordKey];
|
||||||
|
}
|
||||||
|
if (_httpProxyHost) {
|
||||||
|
SRDebugLog(@"Using http proxy %@:%u", _httpProxyHost, _httpProxyPort);
|
||||||
|
} else if (_socksProxyHost) {
|
||||||
|
SRDebugLog(@"Using socks proxy %@:%u", _socksProxyHost, _socksProxyPort);
|
||||||
|
} else {
|
||||||
|
SRDebugLog(@"configureProxy no proxies");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_fetchPAC:(NSURL *)PACurl withProxySettings:(NSDictionary *)proxySettings
|
||||||
|
{
|
||||||
|
SRDebugLog(@"SRWebSocket fetchPAC:%@", PACurl);
|
||||||
|
|
||||||
|
if ([PACurl isFileURL]) {
|
||||||
|
NSError *error = nil;
|
||||||
|
NSString *script = [NSString stringWithContentsOfURL:PACurl
|
||||||
|
usedEncoding:NULL
|
||||||
|
error:&error];
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
[self _openConnection];
|
||||||
|
} else {
|
||||||
|
[self _runPACScript:script withProxySettings:proxySettings];
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *scheme = [PACurl.scheme lowercaseString];
|
||||||
|
if (![scheme isEqualToString:@"http"] && ![scheme isEqualToString:@"https"]) {
|
||||||
|
// Don't know how to read data from this URL, we'll have to give up
|
||||||
|
// We'll simply assume no proxies, and start the request as normal
|
||||||
|
[self _openConnection];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
__weak __typeof__(self) wself = self;
|
||||||
|
NSURLRequest *request = [NSURLRequest requestWithURL:PACurl];
|
||||||
|
NSURLSession *session = [NSURLSession sharedSession];
|
||||||
|
[[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||||
|
__strong __typeof__(wself) sself = wself;
|
||||||
|
if (!error) {
|
||||||
|
NSString *script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||||
|
[sself _runPACScript:script withProxySettings:proxySettings];
|
||||||
|
} else {
|
||||||
|
[sself _openConnection];
|
||||||
|
}
|
||||||
|
}] resume];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_runPACScript:(NSString *)script withProxySettings:(NSDictionary *)proxySettings
|
||||||
|
{
|
||||||
|
if (!script) {
|
||||||
|
[self _openConnection];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SRDebugLog(@"runPACScript");
|
||||||
|
// From: http://developer.apple.com/samplecode/CFProxySupportTool/listing1.html
|
||||||
|
// Work around <rdar://problem/5530166>. This dummy call to
|
||||||
|
// CFNetworkCopyProxiesForURL initialise some state within CFNetwork
|
||||||
|
// that is required by CFNetworkCopyProxiesForAutoConfigurationScript.
|
||||||
|
CFBridgingRelease(CFNetworkCopyProxiesForURL((__bridge CFURLRef)_url, (__bridge CFDictionaryRef)proxySettings));
|
||||||
|
|
||||||
|
// Obtain the list of proxies by running the autoconfiguration script
|
||||||
|
CFErrorRef err = NULL;
|
||||||
|
|
||||||
|
// CFNetworkCopyProxiesForAutoConfigurationScript doesn't understand ws:// or wss://
|
||||||
|
NSURL *httpURL;
|
||||||
|
if (_connectionRequiresSSL)
|
||||||
|
httpURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@", _url.host]];
|
||||||
|
else
|
||||||
|
httpURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@", _url.host]];
|
||||||
|
|
||||||
|
NSArray *proxies = CFBridgingRelease(CFNetworkCopyProxiesForAutoConfigurationScript((__bridge CFStringRef)script,(__bridge CFURLRef)httpURL, &err));
|
||||||
|
if (!err && [proxies count] > 0) {
|
||||||
|
NSDictionary *settings = [proxies objectAtIndex:0];
|
||||||
|
NSString *proxyType = settings[(NSString *)kCFProxyTypeKey];
|
||||||
|
[self _readProxySettingWithType:proxyType settings:settings];
|
||||||
|
}
|
||||||
|
[self _openConnection];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_openConnection
|
||||||
|
{
|
||||||
|
[self _initializeStreams];
|
||||||
|
|
||||||
|
[self.inputStream scheduleInRunLoop:[NSRunLoop SR_networkRunLoop]
|
||||||
|
forMode:NSDefaultRunLoopMode];
|
||||||
|
//[self.outputStream scheduleInRunLoop:[NSRunLoop SR_networkRunLoop]
|
||||||
|
// forMode:NSDefaultRunLoopMode];
|
||||||
|
[self.outputStream open];
|
||||||
|
[self.inputStream open];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_initializeStreams
|
||||||
|
{
|
||||||
|
assert(_url.port.unsignedIntValue <= UINT32_MAX);
|
||||||
|
uint32_t port = _url.port.unsignedIntValue;
|
||||||
|
if (port == 0) {
|
||||||
|
port = (_connectionRequiresSSL ? 443 : 80);
|
||||||
|
}
|
||||||
|
NSString *host = _url.host;
|
||||||
|
|
||||||
|
if (_httpProxyHost) {
|
||||||
|
host = _httpProxyHost;
|
||||||
|
port = (_httpProxyPort ?: 80);
|
||||||
|
}
|
||||||
|
|
||||||
|
CFReadStreamRef readStream = NULL;
|
||||||
|
CFWriteStreamRef writeStream = NULL;
|
||||||
|
|
||||||
|
SRDebugLog(@"ProxyConnect connect stream to %@:%u", host, port);
|
||||||
|
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream);
|
||||||
|
|
||||||
|
self.outputStream = CFBridgingRelease(writeStream);
|
||||||
|
self.inputStream = CFBridgingRelease(readStream);
|
||||||
|
|
||||||
|
if (_socksProxyHost) {
|
||||||
|
SRDebugLog(@"ProxyConnect set sock property stream to %@:%u user %@ password %@", _socksProxyHost, _socksProxyPort, _socksProxyUsername, _socksProxyPassword);
|
||||||
|
NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithCapacity:4];
|
||||||
|
settings[NSStreamSOCKSProxyHostKey] = _socksProxyHost;
|
||||||
|
if (_socksProxyPort) {
|
||||||
|
settings[NSStreamSOCKSProxyPortKey] = @(_socksProxyPort);
|
||||||
|
}
|
||||||
|
if (_socksProxyUsername) {
|
||||||
|
settings[NSStreamSOCKSProxyUserKey] = _socksProxyUsername;
|
||||||
|
}
|
||||||
|
if (_socksProxyPassword) {
|
||||||
|
settings[NSStreamSOCKSProxyPasswordKey] = _socksProxyPassword;
|
||||||
|
}
|
||||||
|
[self.inputStream setProperty:settings forKey:NSStreamSOCKSProxyConfigurationKey];
|
||||||
|
[self.outputStream setProperty:settings forKey:NSStreamSOCKSProxyConfigurationKey];
|
||||||
|
}
|
||||||
|
self.inputStream.delegate = self;
|
||||||
|
self.outputStream.delegate = self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode;
|
||||||
|
{
|
||||||
|
SRDebugLog(@"stream handleEvent %u", eventCode);
|
||||||
|
switch (eventCode) {
|
||||||
|
case NSStreamEventOpenCompleted: {
|
||||||
|
if (aStream == self.inputStream) {
|
||||||
|
if (_httpProxyHost) {
|
||||||
|
[self _proxyDidConnect];
|
||||||
|
} else {
|
||||||
|
[self _didConnect];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case NSStreamEventErrorOccurred: {
|
||||||
|
[self _failWithError:aStream.streamError];
|
||||||
|
} break;
|
||||||
|
case NSStreamEventEndEncountered: {
|
||||||
|
[self _failWithError:aStream.streamError];
|
||||||
|
} break;
|
||||||
|
case NSStreamEventHasBytesAvailable: {
|
||||||
|
if (aStream == _inputStream) {
|
||||||
|
[self _processInputStream];
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case NSStreamEventHasSpaceAvailable:
|
||||||
|
case NSStreamEventNone:
|
||||||
|
SRDebugLog(@"(default) %@", aStream);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_proxyDidConnect
|
||||||
|
{
|
||||||
|
SRDebugLog(@"Proxy Connected");
|
||||||
|
uint32_t port = _url.port.unsignedIntValue;
|
||||||
|
if (port == 0) {
|
||||||
|
port = (_connectionRequiresSSL ? 443 : 80);
|
||||||
|
}
|
||||||
|
// Send HTTP CONNECT Request
|
||||||
|
NSString *connectRequestStr = [NSString stringWithFormat:@"CONNECT %@:%u HTTP/1.1\r\nHost: %@\r\nConnection: keep-alive\r\nProxy-Connection: keep-alive\r\n\r\n", _url.host, port, _url.host];
|
||||||
|
|
||||||
|
NSData *message = [connectRequestStr dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
SRDebugLog(@"Proxy sending %@", connectRequestStr);
|
||||||
|
|
||||||
|
[self _writeData:message];
|
||||||
|
}
|
||||||
|
|
||||||
|
///handles the incoming bytes and sending them to the proper processing method
|
||||||
|
- (void)_processInputStream
|
||||||
|
{
|
||||||
|
NSMutableData *buf = [NSMutableData dataWithCapacity:SRDefaultBufferSize()];
|
||||||
|
uint8_t *buffer = buf.mutableBytes;
|
||||||
|
NSInteger length = [_inputStream read:buffer maxLength:SRDefaultBufferSize()];
|
||||||
|
|
||||||
|
if (length <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL process = (_inputQueue.count == 0);
|
||||||
|
[_inputQueue addObject:[NSData dataWithBytes:buffer length:length]];
|
||||||
|
|
||||||
|
if (process) {
|
||||||
|
[self _dequeueInput];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dequeue the incoming input so it is processed in order
|
||||||
|
|
||||||
|
- (void)_dequeueInput
|
||||||
|
{
|
||||||
|
while (_inputQueue.count > 0) {
|
||||||
|
NSData *data = _inputQueue.firstObject;
|
||||||
|
[_inputQueue removeObjectAtIndex:0];
|
||||||
|
|
||||||
|
// No need to process any data further, we got the full header data.
|
||||||
|
if ([self _proxyProcessHTTPResponseWithData:data]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//handle checking the proxy connection status
|
||||||
|
- (BOOL)_proxyProcessHTTPResponseWithData:(NSData *)data
|
||||||
|
{
|
||||||
|
if (_receivedHTTPHeaders == NULL) {
|
||||||
|
_receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO);
|
||||||
|
}
|
||||||
|
|
||||||
|
CFHTTPMessageAppendBytes(_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length);
|
||||||
|
if (CFHTTPMessageIsHeaderComplete(_receivedHTTPHeaders)) {
|
||||||
|
SRDebugLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_receivedHTTPHeaders)));
|
||||||
|
[self _proxyHTTPHeadersDidFinish];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_proxyHTTPHeadersDidFinish
|
||||||
|
{
|
||||||
|
NSInteger responseCode = CFHTTPMessageGetResponseStatusCode(_receivedHTTPHeaders);
|
||||||
|
|
||||||
|
if (responseCode >= 299) {
|
||||||
|
SRDebugLog(@"Connect to Proxy Request failed with response code %d", responseCode);
|
||||||
|
NSError *error = SRHTTPErrorWithCodeDescription(responseCode, 2132,
|
||||||
|
[NSString stringWithFormat:@"Received bad response code from proxy server: %d.",
|
||||||
|
(int)responseCode]);
|
||||||
|
[self _failWithError:error];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SRDebugLog(@"proxy connect return %d, call socket connect", responseCode);
|
||||||
|
[self _didConnect];
|
||||||
|
}
|
||||||
|
|
||||||
|
static NSTimeInterval const SRProxyConnectWriteTimeout = 5.0;
|
||||||
|
|
||||||
|
- (void)_writeData:(NSData *)data
|
||||||
|
{
|
||||||
|
const uint8_t * bytes = data.bytes;
|
||||||
|
__block NSInteger timeout = (NSInteger)(SRProxyConnectWriteTimeout * 1000000); // wait timeout before giving up
|
||||||
|
__weak __typeof__(self) wself = self;
|
||||||
|
dispatch_async(_writeQueue, ^{
|
||||||
|
__strong __typeof__(wself) sself = self;
|
||||||
|
if (!sself) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NSOutputStream *outStream = sself.outputStream;
|
||||||
|
if (!outStream) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (![outStream hasSpaceAvailable]) {
|
||||||
|
usleep(100); //wait until the socket is ready
|
||||||
|
timeout -= 100;
|
||||||
|
if (timeout < 0) {
|
||||||
|
NSError *error = SRHTTPErrorWithCodeDescription(408, 2132, @"Proxy timeout");
|
||||||
|
[sself _failWithError:error];
|
||||||
|
} else if (outStream.streamError != nil) {
|
||||||
|
[sself _failWithError:outStream.streamError];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[outStream write:bytes maxLength:data.length];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
//
|
||||||
|
// Copyright 2012 Square Inc.
|
||||||
|
// Portions Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface SRRunLoopThread : NSThread
|
||||||
|
|
||||||
|
@property (nonatomic, strong, readonly) NSRunLoop *runLoop;
|
||||||
|
|
||||||
|
+ (instancetype)sharedThread;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
//
|
||||||
|
// Copyright 2012 Square Inc.
|
||||||
|
// Portions Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SRRunLoopThread.h"
|
||||||
|
|
||||||
|
@interface SRRunLoopThread ()
|
||||||
|
{
|
||||||
|
dispatch_group_t _waitGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property (nonatomic, strong, readwrite) NSRunLoop *runLoop;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation SRRunLoopThread
|
||||||
|
|
||||||
|
+ (instancetype)sharedThread
|
||||||
|
{
|
||||||
|
static SRRunLoopThread *thread;
|
||||||
|
static dispatch_once_t onceToken;
|
||||||
|
dispatch_once(&onceToken, ^{
|
||||||
|
thread = [[SRRunLoopThread alloc] init];
|
||||||
|
thread.name = @"com.facebook.SocketRocket.NetworkThread";
|
||||||
|
[thread start];
|
||||||
|
});
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)init
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
if (self) {
|
||||||
|
_waitGroup = dispatch_group_create();
|
||||||
|
dispatch_group_enter(_waitGroup);
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)main
|
||||||
|
{
|
||||||
|
@autoreleasepool {
|
||||||
|
_runLoop = [NSRunLoop currentRunLoop];
|
||||||
|
dispatch_group_leave(_waitGroup);
|
||||||
|
|
||||||
|
// 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]]) {
|
||||||
|
|
||||||
|
}
|
||||||
|
assert(NO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSRunLoop *)runLoop;
|
||||||
|
{
|
||||||
|
dispatch_group_wait(_waitGroup, DISPATCH_TIME_FOREVER);
|
||||||
|
return _runLoop;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
typedef NS_ENUM(NSInteger, SROpCode)
|
||||||
|
{
|
||||||
|
SROpCodeTextFrame = 0x1,
|
||||||
|
SROpCodeBinaryFrame = 0x2,
|
||||||
|
// 3-7 reserved.
|
||||||
|
SROpCodeConnectionClose = 0x8,
|
||||||
|
SROpCodePing = 0x9,
|
||||||
|
SROpCodePong = 0xA,
|
||||||
|
// B-F reserved.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Default buffer size that is used for reading/writing to streams.
|
||||||
|
*/
|
||||||
|
extern size_t SRDefaultBufferSize(void);
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SRConstants.h"
|
||||||
|
|
||||||
|
size_t SRDefaultBufferSize(void) {
|
||||||
|
static size_t size;
|
||||||
|
static dispatch_once_t onceToken;
|
||||||
|
dispatch_once(&onceToken, ^{
|
||||||
|
size = getpagesize();
|
||||||
|
});
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
#import <SocketRocket/SRSecurityPolicy.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface SRPinningSecurityPolicy : SRSecurityPolicy
|
||||||
|
|
||||||
|
- (instancetype)initWithCertificates:(NSArray *)pinnedCertificates;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SRPinningSecurityPolicy.h"
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
#import "SRLog.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface SRPinningSecurityPolicy ()
|
||||||
|
|
||||||
|
@property (nonatomic, copy, readonly) NSArray *pinnedCertificates;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation SRPinningSecurityPolicy
|
||||||
|
|
||||||
|
- (instancetype)initWithCertificates:(NSArray *)pinnedCertificates
|
||||||
|
{
|
||||||
|
// Do not validate certificate chain since we're pinning to specific certificates.
|
||||||
|
self = [super initWithCertificateChainValidationEnabled:NO];
|
||||||
|
if (!self) { return self; }
|
||||||
|
|
||||||
|
if (pinnedCertificates.count == 0) {
|
||||||
|
@throw [NSException exceptionWithName:@"Creating security policy failed."
|
||||||
|
reason:@"Must specify at least one certificate when creating a pinning policy."
|
||||||
|
userInfo:nil];
|
||||||
|
}
|
||||||
|
_pinnedCertificates = [pinnedCertificates copy];
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain
|
||||||
|
{
|
||||||
|
SRDebugLog(@"Pinned cert count: %d", self.pinnedCertificates.count);
|
||||||
|
NSUInteger requiredCertCount = self.pinnedCertificates.count;
|
||||||
|
|
||||||
|
NSUInteger validatedCertCount = 0;
|
||||||
|
CFIndex serverCertCount = SecTrustGetCertificateCount(serverTrust);
|
||||||
|
for (CFIndex i = 0; i < serverCertCount; i++) {
|
||||||
|
SecCertificateRef cert = SecTrustGetCertificateAtIndex(serverTrust, i);
|
||||||
|
NSData *data = CFBridgingRelease(SecCertificateCopyData(cert));
|
||||||
|
for (id ref in self.pinnedCertificates) {
|
||||||
|
SecCertificateRef trustedCert = (__bridge SecCertificateRef)ref;
|
||||||
|
// TODO: (nlutsenko) Add caching, so we don't copy the data for every pinned cert all the time.
|
||||||
|
NSData *trustedCertData = CFBridgingRelease(SecCertificateCopyData(trustedCert));
|
||||||
|
if ([trustedCertData isEqualToData:data]) {
|
||||||
|
validatedCertCount++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (requiredCertCount == validatedCertCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
extern NSError *SRErrorWithDomainCodeDescription(NSString *domain, NSInteger code, NSString *description);
|
||||||
|
extern NSError *SRErrorWithCodeDescription(NSInteger code, NSString *description);
|
||||||
|
extern NSError *SRErrorWithCodeDescriptionUnderlyingError(NSInteger code, NSString *description, NSError *underlyingError);
|
||||||
|
|
||||||
|
extern NSError *SRHTTPErrorWithCodeDescription(NSInteger httpCode, NSInteger errorCode, NSString *description);
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SRError.h"
|
||||||
|
|
||||||
|
#import "SRWebSocket.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
NSError *SRErrorWithDomainCodeDescription(NSString *domain, NSInteger code, NSString *description)
|
||||||
|
{
|
||||||
|
return [NSError errorWithDomain:domain code:code userInfo:@{ NSLocalizedDescriptionKey: description }];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSError *SRErrorWithCodeDescription(NSInteger code, NSString *description)
|
||||||
|
{
|
||||||
|
return SRErrorWithDomainCodeDescription(SRWebSocketErrorDomain, code, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
NSError *SRErrorWithCodeDescriptionUnderlyingError(NSInteger code, NSString *description, NSError *underlyingError)
|
||||||
|
{
|
||||||
|
return [NSError errorWithDomain:SRWebSocketErrorDomain
|
||||||
|
code:code
|
||||||
|
userInfo:@{ NSLocalizedDescriptionKey: description,
|
||||||
|
NSUnderlyingErrorKey: underlyingError }];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSError *SRHTTPErrorWithCodeDescription(NSInteger httpCode, NSInteger errorCode, NSString *description)
|
||||||
|
{
|
||||||
|
return [NSError errorWithDomain:SRWebSocketErrorDomain
|
||||||
|
code:errorCode
|
||||||
|
userInfo:@{ NSLocalizedDescriptionKey: description,
|
||||||
|
SRHTTPResponseErrorKey: @(httpCode) }];
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
extern CFHTTPMessageRef SRHTTPConnectMessageCreate(NSURLRequest *request,
|
||||||
|
NSString *securityKey,
|
||||||
|
uint8_t webSocketProtocolVersion,
|
||||||
|
NSArray<NSHTTPCookie *> *_Nullable cookies,
|
||||||
|
NSArray<NSString *> *_Nullable requestedProtocols);
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SRHTTPConnectMessage.h"
|
||||||
|
|
||||||
|
#import "SRURLUtilities.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
static NSString *_SRHTTPConnectMessageHost(NSURL *url)
|
||||||
|
{
|
||||||
|
NSString *host = url.host;
|
||||||
|
if (url.port) {
|
||||||
|
host = [host stringByAppendingFormat:@":%@", url.port];
|
||||||
|
}
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
CFHTTPMessageRef SRHTTPConnectMessageCreate(NSURLRequest *request,
|
||||||
|
NSString *securityKey,
|
||||||
|
uint8_t webSocketProtocolVersion,
|
||||||
|
NSArray<NSHTTPCookie *> *_Nullable cookies,
|
||||||
|
NSArray<NSString *> *_Nullable requestedProtocols)
|
||||||
|
{
|
||||||
|
NSURL *url = request.URL;
|
||||||
|
|
||||||
|
CFHTTPMessageRef message = CFHTTPMessageCreateRequest(NULL, CFSTR("GET"), (__bridge CFURLRef)url, kCFHTTPVersion1_1);
|
||||||
|
|
||||||
|
// Set host first so it defaults
|
||||||
|
CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Host"), (__bridge CFStringRef)_SRHTTPConnectMessageHost(url));
|
||||||
|
|
||||||
|
NSMutableData *keyBytes = [[NSMutableData alloc] initWithLength:16];
|
||||||
|
int result = SecRandomCopyBytes(kSecRandomDefault, keyBytes.length, keyBytes.mutableBytes);
|
||||||
|
if (result != 0) {
|
||||||
|
//TODO: (nlutsenko) Check if there was an error.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply cookies if any have been provided
|
||||||
|
if (cookies) {
|
||||||
|
NSDictionary<NSString *, NSString *> *messageCookies = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
|
||||||
|
[messageCookies enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull obj, BOOL * _Nonnull stop) {
|
||||||
|
if (key.length && obj.length) {
|
||||||
|
CFHTTPMessageSetHeaderFieldValue(message, (__bridge CFStringRef)key, (__bridge CFStringRef)obj);
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
// set header for http basic auth
|
||||||
|
NSString *basicAuthorizationString = SRBasicAuthorizationHeaderFromURL(url);
|
||||||
|
if (basicAuthorizationString) {
|
||||||
|
CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Authorization"), (__bridge CFStringRef)basicAuthorizationString);
|
||||||
|
}
|
||||||
|
|
||||||
|
CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Upgrade"), CFSTR("websocket"));
|
||||||
|
CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Connection"), CFSTR("Upgrade"));
|
||||||
|
CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Sec-WebSocket-Key"), (__bridge CFStringRef)securityKey);
|
||||||
|
CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Sec-WebSocket-Version"), (__bridge CFStringRef)@(webSocketProtocolVersion).stringValue);
|
||||||
|
|
||||||
|
CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Origin"), (__bridge CFStringRef)SRURLOrigin(url));
|
||||||
|
|
||||||
|
if (requestedProtocols.count) {
|
||||||
|
CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Sec-WebSocket-Protocol"),
|
||||||
|
(__bridge CFStringRef)[requestedProtocols componentsJoinedByString:@", "]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[request.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
|
||||||
|
CFHTTPMessageSetHeaderFieldValue(message, (__bridge CFStringRef)key, (__bridge CFStringRef)obj);
|
||||||
|
}];
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
extern NSData *SRSHA1HashFromString(NSString *string);
|
||||||
|
extern NSData *SRSHA1HashFromBytes(const char *bytes, size_t length);
|
||||||
|
|
||||||
|
extern NSString *SRBase64EncodedStringFromData(NSData *data);
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SRHash.h"
|
||||||
|
|
||||||
|
#import <CommonCrypto/CommonDigest.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
NSData *SRSHA1HashFromString(NSString *string)
|
||||||
|
{
|
||||||
|
size_t length = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
return SRSHA1HashFromBytes(string.UTF8String, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
NSData *SRSHA1HashFromBytes(const char *bytes, size_t length)
|
||||||
|
{
|
||||||
|
uint8_t outputLength = CC_SHA1_DIGEST_LENGTH;
|
||||||
|
unsigned char output[outputLength];
|
||||||
|
CC_SHA1(bytes, (CC_LONG)length, output);
|
||||||
|
|
||||||
|
return [NSData dataWithBytes:output length:outputLength];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *SRBase64EncodedStringFromData(NSData *data)
|
||||||
|
{
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
// Uncomment this line to enable debug logging
|
||||||
|
//#define SR_DEBUG_LOG_ENABLED
|
||||||
|
|
||||||
|
extern void SRErrorLog(NSString *format, ...);
|
||||||
|
extern void SRDebugLog(NSString *format, ...);
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SRLog.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
extern void SRErrorLog(NSString *format, ...)
|
||||||
|
{
|
||||||
|
__block va_list arg_list;
|
||||||
|
va_start (arg_list, format);
|
||||||
|
|
||||||
|
NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:arg_list];
|
||||||
|
|
||||||
|
va_end(arg_list);
|
||||||
|
|
||||||
|
NSLog(@"[SocketRocket] %@", formattedString);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void SRDebugLog(NSString *format, ...)
|
||||||
|
{
|
||||||
|
#ifdef SR_DEBUG_LOG_ENABLED
|
||||||
|
SRErrorLog(tag, format);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
typedef __attribute__((capability("mutex"))) pthread_mutex_t *SRMutex;
|
||||||
|
|
||||||
|
extern SRMutex SRMutexInitRecursive(void);
|
||||||
|
extern void SRMutexDestroy(SRMutex mutex);
|
||||||
|
|
||||||
|
extern void SRMutexLock(SRMutex mutex) __attribute__((acquire_capability(mutex)));
|
||||||
|
extern void SRMutexUnlock(SRMutex mutex) __attribute__((release_capability(mutex)));
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SRMutex.h"
|
||||||
|
|
||||||
|
#import <pthread/pthread.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
SRMutex SRMutexInitRecursive(void)
|
||||||
|
{
|
||||||
|
pthread_mutex_t *mutex = malloc(sizeof(pthread_mutex_t));
|
||||||
|
pthread_mutexattr_t attributes;
|
||||||
|
|
||||||
|
pthread_mutexattr_init(&attributes);
|
||||||
|
pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE);
|
||||||
|
pthread_mutex_init(mutex, &attributes);
|
||||||
|
pthread_mutexattr_destroy(&attributes);
|
||||||
|
|
||||||
|
return mutex;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SRMutexDestroy(SRMutex mutex)
|
||||||
|
{
|
||||||
|
pthread_mutex_destroy(mutex);
|
||||||
|
free(mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((no_thread_safety_analysis))
|
||||||
|
void SRMutexLock(SRMutex mutex)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((no_thread_safety_analysis))
|
||||||
|
void SRMutexUnlock(SRMutex mutex)
|
||||||
|
{
|
||||||
|
pthread_mutex_unlock(mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
extern NSData *SRRandomData(NSUInteger length);
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SRRandom.h"
|
||||||
|
|
||||||
|
#import <Security/SecRandom.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
NSData *SRRandomData(NSUInteger length)
|
||||||
|
{
|
||||||
|
NSMutableData *data = [NSMutableData dataWithLength:length];
|
||||||
|
int result = SecRandomCopyBytes(kSecRandomDefault, data.length, data.mutableBytes);
|
||||||
|
if (result != 0) {
|
||||||
|
[NSException raise:NSInternalInconsistencyException format:@"Failed to generate random bytes with OSStatus: %d", result];
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
Unmask bytes using XOR via SIMD.
|
||||||
|
|
||||||
|
@param bytes The bytes to unmask.
|
||||||
|
@param length The number of bytes to unmask.
|
||||||
|
@param maskKey The mask to XOR with MUST be of length sizeof(uint32_t).
|
||||||
|
*/
|
||||||
|
void SRMaskBytesSIMD(uint8_t *bytes, size_t length, uint8_t *maskKey);
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SRSIMDHelpers.h"
|
||||||
|
|
||||||
|
typedef uint8_t uint8x32_t __attribute__((vector_size(32)));
|
||||||
|
|
||||||
|
static void SRMaskBytesManual(uint8_t *bytes, size_t length, uint8_t *maskKey) {
|
||||||
|
for (size_t i = 0; i < length; i++) {
|
||||||
|
bytes[i] = bytes[i] ^ maskKey[i % sizeof(uint32_t)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Right-shift the elements of a vector, circularly.
|
||||||
|
|
||||||
|
@param vector The vector to circular shift.
|
||||||
|
@param by The number of elements to shift by.
|
||||||
|
|
||||||
|
@return A shifted vector.
|
||||||
|
*/
|
||||||
|
static uint8x32_t SRShiftVector(uint8x32_t vector, size_t by) {
|
||||||
|
uint8x32_t vectorCopy = vector;
|
||||||
|
by = by % _Alignof(uint8x32_t);
|
||||||
|
|
||||||
|
uint8_t *vectorPointer = (uint8_t *)&vector;
|
||||||
|
uint8_t *vectorCopyPointer = (uint8_t *)&vectorCopy;
|
||||||
|
|
||||||
|
memmove(vectorPointer + by, vectorPointer, sizeof(vector) - by);
|
||||||
|
memcpy(vectorPointer, vectorCopyPointer + (sizeof(vector) - by), by);
|
||||||
|
|
||||||
|
return vector;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SRMaskBytesSIMD(uint8_t *bytes, size_t length, uint8_t *maskKey) {
|
||||||
|
size_t alignmentBytes = _Alignof(uint8x32_t) - ((uintptr_t)bytes % _Alignof(uint8x32_t));
|
||||||
|
if (alignmentBytes == _Alignof(uint8x32_t)) {
|
||||||
|
alignmentBytes = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the number of bytes that can be processed after aligning is
|
||||||
|
// less than the number of bytes we can put into a vector,
|
||||||
|
// then there's no work to do with SIMD, just call the manual version.
|
||||||
|
if (alignmentBytes > length || (length - alignmentBytes) < sizeof(uint8x32_t)) {
|
||||||
|
SRMaskBytesManual(bytes, length, maskKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t vectorLength = (length - alignmentBytes) / sizeof(uint8x32_t);
|
||||||
|
size_t manualStartOffset = alignmentBytes + (vectorLength * sizeof(uint8x32_t));
|
||||||
|
size_t manualLength = length - manualStartOffset;
|
||||||
|
|
||||||
|
uint8x32_t *vector = (uint8x32_t *)(bytes + alignmentBytes);
|
||||||
|
uint8x32_t maskVector = { };
|
||||||
|
|
||||||
|
memset_pattern4(&maskVector, maskKey, sizeof(uint8x32_t));
|
||||||
|
maskVector = SRShiftVector(maskVector, alignmentBytes);
|
||||||
|
|
||||||
|
SRMaskBytesManual(bytes, alignmentBytes, maskKey);
|
||||||
|
|
||||||
|
for (size_t vectorIndex = 0; vectorIndex < vectorLength; vectorIndex++) {
|
||||||
|
vector[vectorIndex] = vector[vectorIndex] ^ maskVector;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the shifted mask for the final manual part.
|
||||||
|
SRMaskBytesManual(bytes + manualStartOffset, manualLength, (uint8_t *) &maskVector);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
// The origin isn't really applicable for a native application.
|
||||||
|
// So instead, just map ws -> http and wss -> https.
|
||||||
|
extern NSString *SRURLOrigin(NSURL *url);
|
||||||
|
|
||||||
|
extern BOOL SRURLRequiresSSL(NSURL *url);
|
||||||
|
|
||||||
|
// Extracts `user` and `password` from url (if available) into `Basic base64(user:password)`.
|
||||||
|
extern NSString *_Nullable SRBasicAuthorizationHeaderFromURL(NSURL *url);
|
||||||
|
|
||||||
|
// Returns a valid value for `NSStreamNetworkServiceType` or `nil`.
|
||||||
|
extern NSString *_Nullable SRStreamNetworkServiceTypeFromURLRequest(NSURLRequest *request);
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SRURLUtilities.h"
|
||||||
|
|
||||||
|
#import "SRHash.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
NSString *SRURLOrigin(NSURL *url)
|
||||||
|
{
|
||||||
|
NSMutableString *origin = [NSMutableString string];
|
||||||
|
|
||||||
|
NSString *scheme = url.scheme.lowercaseString;
|
||||||
|
if ([scheme isEqualToString:@"wss"]) {
|
||||||
|
scheme = @"https";
|
||||||
|
} else if ([scheme isEqualToString:@"ws"]) {
|
||||||
|
scheme = @"http";
|
||||||
|
}
|
||||||
|
[origin appendFormat:@"%@://%@", scheme, url.host];
|
||||||
|
|
||||||
|
NSNumber *port = url.port;
|
||||||
|
BOOL portIsDefault = (!port ||
|
||||||
|
([scheme isEqualToString:@"http"] && port.integerValue == 80) ||
|
||||||
|
([scheme isEqualToString:@"https"] && port.integerValue == 443));
|
||||||
|
if (!portIsDefault) {
|
||||||
|
[origin appendFormat:@":%@", port.stringValue];
|
||||||
|
}
|
||||||
|
return origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern BOOL SRURLRequiresSSL(NSURL *url)
|
||||||
|
{
|
||||||
|
NSString *scheme = url.scheme.lowercaseString;
|
||||||
|
return ([scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern NSString *_Nullable SRBasicAuthorizationHeaderFromURL(NSURL *url)
|
||||||
|
{
|
||||||
|
NSData *data = [[NSString stringWithFormat:@"%@:%@", url.user, url.password] dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
return [NSString stringWithFormat:@"Basic %@", SRBase64EncodedStringFromData(data)];
|
||||||
|
}
|
||||||
|
|
||||||
|
extern NSString *_Nullable SRStreamNetworkServiceTypeFromURLRequest(NSURLRequest *request)
|
||||||
|
{
|
||||||
|
NSString *networkServiceType = nil;
|
||||||
|
switch (request.networkServiceType) {
|
||||||
|
case NSURLNetworkServiceTypeDefault:
|
||||||
|
break;
|
||||||
|
case NSURLNetworkServiceTypeVoIP:
|
||||||
|
networkServiceType = NSStreamNetworkServiceTypeVoIP;
|
||||||
|
break;
|
||||||
|
case NSURLNetworkServiceTypeVideo:
|
||||||
|
networkServiceType = NSStreamNetworkServiceTypeVideo;
|
||||||
|
break;
|
||||||
|
case NSURLNetworkServiceTypeBackground:
|
||||||
|
networkServiceType = NSStreamNetworkServiceTypeBackground;
|
||||||
|
break;
|
||||||
|
case NSURLNetworkServiceTypeVoice:
|
||||||
|
networkServiceType = NSStreamNetworkServiceTypeVoice;
|
||||||
|
break;
|
||||||
|
#if (__MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 || __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 || __TV_OS_VERSION_MAX_ALLOWED >= 100000 || __WATCH_OS_VERSION_MAX_ALLOWED >= 30000)
|
||||||
|
case NSURLNetworkServiceTypeCallSignaling:
|
||||||
|
networkServiceType = NSStreamNetworkServiceTypeCallSignaling;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return networkServiceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
//
|
||||||
|
// Copyright 2012 Square Inc.
|
||||||
|
// Portions Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface NSRunLoop (SRWebSocket)
|
||||||
|
|
||||||
|
/**
|
||||||
|
Default run loop that will be used to schedule all instances of `SRWebSocket`.
|
||||||
|
|
||||||
|
@return An instance of `NSRunLoop`.
|
||||||
|
*/
|
||||||
|
+ (NSRunLoop *)SR_networkRunLoop;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
//
|
||||||
|
// Copyright 2012 Square Inc.
|
||||||
|
// Portions Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "NSRunLoop+SRWebSocket.h"
|
||||||
|
#import "NSRunLoop+SRWebSocketPrivate.h"
|
||||||
|
|
||||||
|
#import "SRRunLoopThread.h"
|
||||||
|
|
||||||
|
// Required for object file to always be linked.
|
||||||
|
void import_NSRunLoop_SRWebSocket() { }
|
||||||
|
|
||||||
|
@implementation NSRunLoop (SRWebSocket)
|
||||||
|
|
||||||
|
+ (NSRunLoop *)SR_networkRunLoop
|
||||||
|
{
|
||||||
|
return [SRRunLoopThread sharedThread].runLoop;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
//
|
||||||
|
// Copyright 2012 Square Inc.
|
||||||
|
// Portions Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface NSURLRequest (SRWebSocket)
|
||||||
|
|
||||||
|
/**
|
||||||
|
An array of pinned `SecCertificateRef` SSL certificates that `SRWebSocket` will use for validation.
|
||||||
|
*/
|
||||||
|
@property (nullable, nonatomic, copy, readonly) NSArray *SR_SSLPinnedCertificates;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface NSMutableURLRequest (SRWebSocket)
|
||||||
|
|
||||||
|
/**
|
||||||
|
An array of pinned `SecCertificateRef` SSL certificates that `SRWebSocket` will use for validation.
|
||||||
|
*/
|
||||||
|
@property (nullable, nonatomic, copy) NSArray *SR_SSLPinnedCertificates;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
//
|
||||||
|
// Copyright 2012 Square Inc.
|
||||||
|
// Portions Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "NSURLRequest+SRWebSocket.h"
|
||||||
|
#import "NSURLRequest+SRWebSocketPrivate.h"
|
||||||
|
|
||||||
|
// Required for object file to always be linked.
|
||||||
|
void import_NSURLRequest_SRWebSocket() { }
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
static NSString *const SRSSLPinnnedCertificatesKey = @"SocketRocket_SSLPinnedCertificates";
|
||||||
|
|
||||||
|
@implementation NSURLRequest (SRWebSocket)
|
||||||
|
|
||||||
|
- (nullable NSArray *)SR_SSLPinnedCertificates
|
||||||
|
{
|
||||||
|
return [NSURLProtocol propertyForKey:SRSSLPinnnedCertificatesKey inRequest:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation NSMutableURLRequest (SRWebSocket)
|
||||||
|
|
||||||
|
- (nullable NSArray *)SR_SSLPinnedCertificates
|
||||||
|
{
|
||||||
|
return [NSURLProtocol propertyForKey:SRSSLPinnnedCertificatesKey inRequest:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setSR_SSLPinnedCertificates:(nullable NSArray *)SR_SSLPinnedCertificates
|
||||||
|
{
|
||||||
|
[NSURLProtocol setProperty:[SR_SSLPinnedCertificates copy] forKey:SRSSLPinnnedCertificatesKey inRequest:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <Security/Security.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface SRSecurityPolicy : NSObject
|
||||||
|
|
||||||
|
/**
|
||||||
|
A default `SRSecurityPolicy` implementation specifies socket security and
|
||||||
|
validates the certificate chain.
|
||||||
|
|
||||||
|
Use a subclass of `SRSecurityPolicy` for more fine grained customization.
|
||||||
|
*/
|
||||||
|
+ (instancetype)defaultPolicy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Specifies socket security and provider certificate pinning, disregarding certificate
|
||||||
|
chain validation.
|
||||||
|
|
||||||
|
@param pinnedCertificates Array of `SecCertificateRef` SSL certificates to use for validation.
|
||||||
|
*/
|
||||||
|
+ (instancetype)pinnningPolicyWithCertificates:(NSArray *)pinnedCertificates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Specifies socket security and optional certificate chain validation.
|
||||||
|
|
||||||
|
@param enabled Whether or not to validate the SSL certificate chain. If you
|
||||||
|
are considering using this method because your certificate was not issued by a
|
||||||
|
recognized certificate authority, consider using `pinningPolicyWithCertificates` instead.
|
||||||
|
*/
|
||||||
|
- (instancetype)initWithCertificateChainValidationEnabled:(BOOL)enabled NS_DESIGNATED_INITIALIZER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Updates all the security options for input and output streams, for example you
|
||||||
|
can set your socket security level here.
|
||||||
|
|
||||||
|
@param stream Stream to update the options in.
|
||||||
|
*/
|
||||||
|
- (void)updateSecurityOptionsInStream:(NSStream *)stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Whether or not the specified server trust should be accepted, based on the security policy.
|
||||||
|
|
||||||
|
This method should be used when responding to an authentication challenge from
|
||||||
|
a server. In the default implemenation, no further validation is done here, but
|
||||||
|
you're free to override it in a subclass. See `SRPinningSecurityPolicy.h` for
|
||||||
|
an example.
|
||||||
|
|
||||||
|
@param serverTrust The X.509 certificate trust of the server.
|
||||||
|
@param domain The domain of serverTrust.
|
||||||
|
|
||||||
|
@return Whether or not to trust the server.
|
||||||
|
*/
|
||||||
|
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SRSecurityPolicy.h"
|
||||||
|
#import "SRPinningSecurityPolicy.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface SRSecurityPolicy ()
|
||||||
|
|
||||||
|
@property (nonatomic, assign, readonly) BOOL certificateChainValidationEnabled;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation SRSecurityPolicy
|
||||||
|
|
||||||
|
+ (instancetype)defaultPolicy
|
||||||
|
{
|
||||||
|
return [self new];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (instancetype)pinnningPolicyWithCertificates:(NSArray *)pinnedCertificates
|
||||||
|
{
|
||||||
|
return [[SRPinningSecurityPolicy alloc] initWithCertificates:pinnedCertificates];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithCertificateChainValidationEnabled:(BOOL)enabled
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
if (!self) { return self; }
|
||||||
|
|
||||||
|
_certificateChainValidationEnabled = enabled;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)init
|
||||||
|
{
|
||||||
|
return [self initWithCertificateChainValidationEnabled:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateSecurityOptionsInStream:(NSStream *)stream
|
||||||
|
{
|
||||||
|
// Enforce TLS 1.2
|
||||||
|
[stream setProperty:(__bridge id)CFSTR("kCFStreamSocketSecurityLevelTLSv1_2") forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel];
|
||||||
|
|
||||||
|
// Validate certificate chain for this stream if enabled.
|
||||||
|
NSDictionary<NSString *, id> *sslOptions = @{ (__bridge NSString *)kCFStreamSSLValidatesCertificateChain : @(self.certificateChainValidationEnabled) };
|
||||||
|
[stream setProperty:sslOptions forKey:(__bridge NSString *)kCFStreamPropertySSLSettings];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain
|
||||||
|
{
|
||||||
|
// No further evaluation happens in the default policy.
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
@ -0,0 +1,413 @@
|
||||||
|
//
|
||||||
|
// Copyright 2012 Square Inc.
|
||||||
|
// Portions Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
typedef NS_ENUM(NSInteger, SRReadyState) {
|
||||||
|
SR_CONNECTING = 0,
|
||||||
|
SR_OPEN = 1,
|
||||||
|
SR_CLOSING = 2,
|
||||||
|
SR_CLOSED = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef NS_ENUM(NSInteger, SRStatusCode) {
|
||||||
|
// 0-999: Reserved and not used.
|
||||||
|
SRStatusCodeNormal = 1000,
|
||||||
|
SRStatusCodeGoingAway = 1001,
|
||||||
|
SRStatusCodeProtocolError = 1002,
|
||||||
|
SRStatusCodeUnhandledType = 1003,
|
||||||
|
// 1004 reserved.
|
||||||
|
SRStatusNoStatusReceived = 1005,
|
||||||
|
SRStatusCodeAbnormal = 1006,
|
||||||
|
SRStatusCodeInvalidUTF8 = 1007,
|
||||||
|
SRStatusCodePolicyViolated = 1008,
|
||||||
|
SRStatusCodeMessageTooBig = 1009,
|
||||||
|
SRStatusCodeMissingExtension = 1010,
|
||||||
|
SRStatusCodeInternalError = 1011,
|
||||||
|
SRStatusCodeServiceRestart = 1012,
|
||||||
|
SRStatusCodeTryAgainLater = 1013,
|
||||||
|
// 1014: Reserved for future use by the WebSocket standard.
|
||||||
|
SRStatusCodeTLSHandshake = 1015,
|
||||||
|
// 1016-1999: Reserved for future use by the WebSocket standard.
|
||||||
|
// 2000-2999: Reserved for use by WebSocket extensions.
|
||||||
|
// 3000-3999: Available for use by libraries and frameworks. May not be used by applications. Available for registration at the IANA via first-come, first-serve.
|
||||||
|
// 4000-4999: Available for use by applications.
|
||||||
|
};
|
||||||
|
|
||||||
|
@class SRWebSocket;
|
||||||
|
@class SRSecurityPolicy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Error domain used for errors reported by SRWebSocket.
|
||||||
|
*/
|
||||||
|
extern NSString *const SRWebSocketErrorDomain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Key used for HTTP status code if bad response was received from the server.
|
||||||
|
*/
|
||||||
|
extern NSString *const SRHTTPResponseErrorKey;
|
||||||
|
|
||||||
|
@protocol SRWebSocketDelegate;
|
||||||
|
|
||||||
|
///--------------------------------------
|
||||||
|
#pragma mark - SRWebSocket
|
||||||
|
///--------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
A `SRWebSocket` object lets you connect, send and receive data to a remote Web Socket.
|
||||||
|
*/
|
||||||
|
@interface SRWebSocket : NSObject <NSStreamDelegate>
|
||||||
|
|
||||||
|
/**
|
||||||
|
The delegate of the web socket.
|
||||||
|
|
||||||
|
The web socket delegate is notified on all state changes that happen to the web socket.
|
||||||
|
*/
|
||||||
|
@property (nonatomic, weak) id <SRWebSocketDelegate> delegate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
A dispatch queue for scheduling the delegate calls. The queue doesn't need be a serial queue.
|
||||||
|
|
||||||
|
If `nil` and `delegateOperationQueue` is `nil`, the socket uses main queue for performing all delegate method calls.
|
||||||
|
*/
|
||||||
|
@property (nullable, nonatomic, strong) dispatch_queue_t delegateDispatchQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
An operation queue for scheduling the delegate calls.
|
||||||
|
|
||||||
|
If `nil` and `delegateOperationQueue` is `nil`, the socket uses main queue for performing all delegate method calls.
|
||||||
|
*/
|
||||||
|
@property (nullable, nonatomic, strong) NSOperationQueue *delegateOperationQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Current ready state of the socket. Default: `SR_CONNECTING`.
|
||||||
|
|
||||||
|
This property is Key-Value Observable and fully thread-safe.
|
||||||
|
*/
|
||||||
|
@property (atomic, assign, readonly) SRReadyState readyState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
An instance of `NSURL` that this socket connects to.
|
||||||
|
*/
|
||||||
|
@property (nullable, nonatomic, strong, readonly) NSURL *url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
All HTTP headers that were received by socket or `nil` if none were received so far.
|
||||||
|
*/
|
||||||
|
@property (nullable, nonatomic, assign, readonly) CFHTTPMessageRef receivedHTTPHeaders;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Array of `NSHTTPCookie` cookies to apply to the connection.
|
||||||
|
*/
|
||||||
|
@property (nullable, nonatomic, copy) NSArray<NSHTTPCookie *> *requestCookies;
|
||||||
|
|
||||||
|
/**
|
||||||
|
The negotiated web socket protocol or `nil` if handshake did not yet complete.
|
||||||
|
*/
|
||||||
|
@property (nullable, nonatomic, copy, readonly) NSString *protocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
A boolean value indicating whether this socket will allow connection without SSL trust chain evaluation.
|
||||||
|
For DEBUG builds this flag is ignored, and SSL connections are allowed regardless of the certificate trust configuration
|
||||||
|
*/
|
||||||
|
@property (nonatomic, assign, readonly) BOOL allowsUntrustedSSLCertificates;
|
||||||
|
|
||||||
|
///--------------------------------------
|
||||||
|
#pragma mark - Constructors
|
||||||
|
///--------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
Initializes a web socket with a given `NSURLRequest`.
|
||||||
|
|
||||||
|
@param request Request to initialize with.
|
||||||
|
*/
|
||||||
|
- (instancetype)initWithURLRequest:(NSURLRequest *)request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Initializes a web socket with a given `NSURLRequest`, specifying a transport security policy (e.g. SSL configuration).
|
||||||
|
|
||||||
|
@param request Request to initialize with.
|
||||||
|
@param securityPolicy Policy object describing transport security behavior.
|
||||||
|
*/
|
||||||
|
- (instancetype)initWithURLRequest:(NSURLRequest *)request securityPolicy:(SRSecurityPolicy *)securityPolicy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Initializes a web socket with a given `NSURLRequest` and list of sub-protocols.
|
||||||
|
|
||||||
|
@param request Request to initialize with.
|
||||||
|
@param protocols An array of strings that turn into `Sec-WebSocket-Protocol`. Default: `nil`.
|
||||||
|
*/
|
||||||
|
- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(nullable NSArray<NSString *> *)protocols;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Initializes a web socket with a given `NSURLRequest`, list of sub-protocols and whether untrusted SSL certificates are allowed.
|
||||||
|
|
||||||
|
@param request Request to initialize with.
|
||||||
|
@param protocols An array of strings that turn into `Sec-WebSocket-Protocol`. Default: `nil`.
|
||||||
|
@param allowsUntrustedSSLCertificates Boolean value indicating whether untrusted SSL certificates are allowed. Default: `false`.
|
||||||
|
*/
|
||||||
|
- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(nullable NSArray<NSString *> *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Initializes a web socket with a given `NSURLRequest`, list of sub-protocols and whether untrusted SSL certificates are allowed.
|
||||||
|
|
||||||
|
@param request Request to initialize with.
|
||||||
|
@param protocols An array of strings that turn into `Sec-WebSocket-Protocol`. Default: `nil`.
|
||||||
|
@param securityPolicy Policy object describing transport security behavior.
|
||||||
|
*/
|
||||||
|
- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(nullable NSArray<NSString *> *)protocols securityPolicy:(SRSecurityPolicy *)securityPolicy NS_DESIGNATED_INITIALIZER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Initializes a web socket with a given `NSURL`.
|
||||||
|
|
||||||
|
@param url URL to initialize with.
|
||||||
|
*/
|
||||||
|
- (instancetype)initWithURL:(NSURL *)url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Initializes a web socket with a given `NSURL` and list of sub-protocols.
|
||||||
|
|
||||||
|
@param url URL to initialize with.
|
||||||
|
@param protocols An array of strings that turn into `Sec-WebSocket-Protocol`. Default: `nil`.
|
||||||
|
*/
|
||||||
|
- (instancetype)initWithURL:(NSURL *)url protocols:(nullable NSArray<NSString *> *)protocols;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Initializes a web socket with a given `NSURL`, specifying a transport security policy (e.g. SSL configuration).
|
||||||
|
|
||||||
|
@param url URL to initialize with.
|
||||||
|
@param securityPolicy Policy object describing transport security behavior.
|
||||||
|
*/
|
||||||
|
- (instancetype)initWithURL:(NSURL *)url securityPolicy:(SRSecurityPolicy *)securityPolicy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Initializes a web socket with a given `NSURL`, list of sub-protocols and whether untrusted SSL certificates are allowed.
|
||||||
|
|
||||||
|
@param url URL to initialize with.
|
||||||
|
@param protocols An array of strings that turn into `Sec-WebSocket-Protocol`. Default: `nil`.
|
||||||
|
@param allowsUntrustedSSLCertificates Boolean value indicating whether untrusted SSL certificates are allowed. Default: `false`.
|
||||||
|
*/
|
||||||
|
- (instancetype)initWithURL:(NSURL *)url protocols:(nullable NSArray<NSString *> *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Unavailable initializer. Please use any other initializer.
|
||||||
|
*/
|
||||||
|
- (instancetype)init NS_UNAVAILABLE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Unavailable constructor. Please use any other initializer.
|
||||||
|
*/
|
||||||
|
+ (instancetype)new NS_UNAVAILABLE;
|
||||||
|
|
||||||
|
///--------------------------------------
|
||||||
|
#pragma mark - Schedule
|
||||||
|
///--------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
Schedules a received on a given run loop in a given mode.
|
||||||
|
By default, a web socket will schedule itself on `+[NSRunLoop SR_networkRunLoop]` using `NSDefaultRunLoopMode`.
|
||||||
|
|
||||||
|
@param runLoop The run loop on which to schedule the receiver.
|
||||||
|
@param mode The mode for the run loop.
|
||||||
|
*/
|
||||||
|
- (void)scheduleInRunLoop:(NSRunLoop *)runLoop forMode:(NSString *)mode NS_SWIFT_NAME(schedule(in:forMode:));
|
||||||
|
|
||||||
|
/**
|
||||||
|
Removes the receiver from a given run loop running in a given mode.
|
||||||
|
|
||||||
|
@param runLoop The run loop on which the receiver was scheduled.
|
||||||
|
@param mode The mode for the run loop.
|
||||||
|
*/
|
||||||
|
- (void)unscheduleFromRunLoop:(NSRunLoop *)runLoop forMode:(NSString *)mode NS_SWIFT_NAME(unschedule(from:forMode:));
|
||||||
|
|
||||||
|
///--------------------------------------
|
||||||
|
#pragma mark - Open / Close
|
||||||
|
///--------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
Opens web socket, which will trigger connection, authentication and start receiving/sending events.
|
||||||
|
An instance of `SRWebSocket` is intended for one-time-use only. This method should be called once and only once.
|
||||||
|
*/
|
||||||
|
- (void)open;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Closes a web socket using `SRStatusCodeNormal` code and no reason.
|
||||||
|
*/
|
||||||
|
- (void)close;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Closes a web socket using a given code and reason.
|
||||||
|
|
||||||
|
@param code Code to close the socket with.
|
||||||
|
@param reason Reason to send to the server or `nil`.
|
||||||
|
*/
|
||||||
|
- (void)closeWithCode:(NSInteger)code reason:(nullable NSString *)reason;
|
||||||
|
|
||||||
|
///--------------------------------------
|
||||||
|
#pragma mark Send
|
||||||
|
///--------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
Send a UTF-8 string or binary data to the server.
|
||||||
|
|
||||||
|
@param message UTF-8 String or Data to send.
|
||||||
|
|
||||||
|
@deprecated Please use `sendString:` or `sendData` instead.
|
||||||
|
*/
|
||||||
|
- (void)send:(nullable id)message __attribute__((deprecated("Please use `sendString:error:` or `sendData:error:` instead.")));
|
||||||
|
|
||||||
|
/**
|
||||||
|
Send a UTF-8 String to the server.
|
||||||
|
|
||||||
|
@param string String to send.
|
||||||
|
@param error On input, a pointer to variable for an `NSError` object.
|
||||||
|
If an error occurs, this pointer is set to an `NSError` object containing information about the error.
|
||||||
|
You may specify `nil` to ignore the error information.
|
||||||
|
|
||||||
|
@return `YES` if the string was scheduled to send, otherwise - `NO`.
|
||||||
|
*/
|
||||||
|
- (BOOL)sendString:(NSString *)string error:(NSError **)error NS_SWIFT_NAME(send(string:));
|
||||||
|
|
||||||
|
/**
|
||||||
|
Send binary data to the server.
|
||||||
|
|
||||||
|
@param data Data to send.
|
||||||
|
@param error On input, a pointer to variable for an `NSError` object.
|
||||||
|
If an error occurs, this pointer is set to an `NSError` object containing information about the error.
|
||||||
|
You may specify `nil` to ignore the error information.
|
||||||
|
|
||||||
|
@return `YES` if the string was scheduled to send, otherwise - `NO`.
|
||||||
|
*/
|
||||||
|
- (BOOL)sendData:(nullable NSData *)data error:(NSError **)error NS_SWIFT_NAME(send(data:));
|
||||||
|
|
||||||
|
/**
|
||||||
|
Send binary data to the server, without making a defensive copy of it first.
|
||||||
|
|
||||||
|
@param data Data to send.
|
||||||
|
@param error On input, a pointer to variable for an `NSError` object.
|
||||||
|
If an error occurs, this pointer is set to an `NSError` object containing information about the error.
|
||||||
|
You may specify `nil` to ignore the error information.
|
||||||
|
|
||||||
|
@return `YES` if the string was scheduled to send, otherwise - `NO`.
|
||||||
|
*/
|
||||||
|
- (BOOL)sendDataNoCopy:(nullable NSData *)data error:(NSError **)error NS_SWIFT_NAME(send(dataNoCopy:));
|
||||||
|
|
||||||
|
/**
|
||||||
|
Send Ping message to the server with optional data.
|
||||||
|
|
||||||
|
@param data Instance of `NSData` or `nil`.
|
||||||
|
@param error On input, a pointer to variable for an `NSError` object.
|
||||||
|
If an error occurs, this pointer is set to an `NSError` object containing information about the error.
|
||||||
|
You may specify `nil` to ignore the error information.
|
||||||
|
|
||||||
|
@return `YES` if the string was scheduled to send, otherwise - `NO`.
|
||||||
|
*/
|
||||||
|
- (BOOL)sendPing:(nullable NSData *)data error:(NSError **)error NS_SWIFT_NAME(sendPing(_:));
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
///--------------------------------------
|
||||||
|
#pragma mark - SRWebSocketDelegate
|
||||||
|
///--------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
The `SRWebSocketDelegate` protocol describes the methods that `SRWebSocket` objects
|
||||||
|
call on their delegates to handle status and messsage events.
|
||||||
|
*/
|
||||||
|
@protocol SRWebSocketDelegate <NSObject>
|
||||||
|
|
||||||
|
@optional
|
||||||
|
|
||||||
|
#pragma mark Receive Messages
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when any message was received from a web socket.
|
||||||
|
This method is suboptimal and might be deprecated in a future release.
|
||||||
|
|
||||||
|
@param webSocket An instance of `SRWebSocket` that received a message.
|
||||||
|
@param message Received message. Either a `String` or `NSData`.
|
||||||
|
*/
|
||||||
|
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when a frame was received from a web socket.
|
||||||
|
|
||||||
|
@param webSocket An instance of `SRWebSocket` that received a message.
|
||||||
|
@param string Received text in a form of UTF-8 `String`.
|
||||||
|
*/
|
||||||
|
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithString:(NSString *)string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when a frame was received from a web socket.
|
||||||
|
|
||||||
|
@param webSocket An instance of `SRWebSocket` that received a message.
|
||||||
|
@param data Received data in a form of `NSData`.
|
||||||
|
*/
|
||||||
|
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithData:(NSData *)data;
|
||||||
|
|
||||||
|
#pragma mark Status & Connection
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when a given web socket was open and authenticated.
|
||||||
|
|
||||||
|
@param webSocket An instance of `SRWebSocket` that was open.
|
||||||
|
*/
|
||||||
|
- (void)webSocketDidOpen:(SRWebSocket *)webSocket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when a given web socket encountered an error.
|
||||||
|
|
||||||
|
@param webSocket An instance of `SRWebSocket` that failed with an error.
|
||||||
|
@param error An instance of `NSError`.
|
||||||
|
*/
|
||||||
|
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when a given web socket was closed.
|
||||||
|
|
||||||
|
@param webSocket An instance of `SRWebSocket` that was closed.
|
||||||
|
@param code Code reported by the server.
|
||||||
|
@param reason Reason in a form of a String that was reported by the server or `nil`.
|
||||||
|
@param wasClean Boolean value indicating whether a socket was closed in a clean state.
|
||||||
|
*/
|
||||||
|
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(nullable NSString *)reason wasClean:(BOOL)wasClean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called on receive of a ping message from the server.
|
||||||
|
|
||||||
|
@param webSocket An instance of `SRWebSocket` that received a ping frame.
|
||||||
|
@param data Payload that was received or `nil` if there was no payload.
|
||||||
|
*/
|
||||||
|
- (void)webSocket:(SRWebSocket *)webSocket didReceivePingWithData:(nullable NSData *)data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when a pong data was received in response to ping.
|
||||||
|
|
||||||
|
@param webSocket An instance of `SRWebSocket` that received a pong frame.
|
||||||
|
@param pongData Payload that was received or `nil` if there was no payload.
|
||||||
|
*/
|
||||||
|
- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(nullable NSData *)pongData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Sent before reporting a text frame to be able to configure if it shuold be convert to a UTF-8 String or passed as `NSData`.
|
||||||
|
If the method is not implemented - it will always convert text frames to String.
|
||||||
|
|
||||||
|
@param webSocket An instance of `SRWebSocket` that received a text frame.
|
||||||
|
|
||||||
|
@return `YES` if text frame should be converted to UTF-8 String, otherwise - `NO`. Default: `YES`.
|
||||||
|
*/
|
||||||
|
- (BOOL)webSocketShouldConvertTextFrameToString:(SRWebSocket *)webSocket NS_SWIFT_NAME(webSocketShouldConvertTextFrameToString(_:));
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,15 @@
|
||||||
|
//
|
||||||
|
// Copyright 2012 Square Inc.
|
||||||
|
// Portions Copyright (c) 2016-present, Facebook, Inc.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <SocketRocket/NSRunLoop+SRWebSocket.h>
|
||||||
|
#import <SocketRocket/NSURLRequest+SRWebSocket.h>
|
||||||
|
#import <SocketRocket/SRSecurityPolicy.h>
|
||||||
|
#import <SocketRocket/SRWebSocket.h>
|
||||||
Loading…
Reference in New Issue