Initial commit 🎉
fbshipit-source-id: b6fc29740c6875d2e78953b8a7123890a67930f2 Co-authored-by: Sebastian McKenzie <sebmck@fb.com> Co-authored-by: John Knox <jknox@fb.com> Co-authored-by: Emil Sjölander <emilsj@fb.com> Co-authored-by: Pritesh Nandgaonkar <prit91@fb.com>
This commit is contained in:
18
iOS/SonarKit/CppBridge/SonarCppBridgingConnection.h
Normal file
18
iOS/SonarKit/CppBridge/SonarCppBridgingConnection.h
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) 2004-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
#import <Sonar/SonarConnection.h>
|
||||
#import <SonarKit/SonarConnection.h>
|
||||
|
||||
/**
|
||||
SonarCppBridgingConnection is a simple ObjC wrapper around SonarConnection
|
||||
that forwards messages to the underlying C++ connection. This class allows
|
||||
pure Objective-C plugins to send messages to the underlying connection.
|
||||
*/
|
||||
@interface SonarCppBridgingConnection : NSObject <SonarConnection>
|
||||
- (instancetype)initWithCppConnection:(std::shared_ptr<facebook::sonar::SonarConnection>)conn;
|
||||
@end
|
||||
45
iOS/SonarKit/CppBridge/SonarCppBridgingConnection.mm
Normal file
45
iOS/SonarKit/CppBridge/SonarCppBridgingConnection.mm
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2018-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
#import "SonarCppBridgingConnection.h"
|
||||
|
||||
#import <FBCxxUtils/FBCxxFollyDynamicConvert.h>
|
||||
|
||||
#import "SonarCppBridgingResponder.h"
|
||||
|
||||
@implementation SonarCppBridgingConnection
|
||||
{
|
||||
std::shared_ptr<facebook::sonar::SonarConnection> conn_;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCppConnection:(std::shared_ptr<facebook::sonar::SonarConnection>)conn
|
||||
{
|
||||
if (self = [super init]) {
|
||||
conn_ = conn;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - SonarConnection
|
||||
|
||||
- (void)send:(NSString *)method withParams:(NSDictionary *)params
|
||||
{
|
||||
conn_->send([method UTF8String], facebook::cxxutils::convertIdToFollyDynamic(params));
|
||||
}
|
||||
|
||||
- (void)receive:(NSString *)method withBlock:(SonarReceiver)receiver
|
||||
{
|
||||
const auto lambda = [receiver](const folly::dynamic &message,
|
||||
std::unique_ptr<facebook::sonar::SonarResponder> responder) {
|
||||
SonarCppBridgingResponder *const objCResponder =
|
||||
[[SonarCppBridgingResponder alloc] initWithCppResponder:std::move(responder)];
|
||||
receiver(facebook::cxxutils::convertFollyDynamicToId(message), objCResponder);
|
||||
};
|
||||
conn_->receive([method UTF8String], lambda);
|
||||
}
|
||||
|
||||
@end
|
||||
18
iOS/SonarKit/CppBridge/SonarCppBridgingResponder.h
Normal file
18
iOS/SonarKit/CppBridge/SonarCppBridgingResponder.h
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) 2018-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
#import <Sonar/SonarResponder.h>
|
||||
#import <SonarKit/SonarResponder.h>
|
||||
|
||||
/**
|
||||
SonarCppBridgingResponder is a simple ObjC wrapper around SonarResponder
|
||||
that forwards messages to the underlying C++ responder. This class allows
|
||||
pure Objective-C plugins to send messages to the underlying responder.
|
||||
*/
|
||||
@interface SonarCppBridgingResponder : NSObject <SonarResponder>
|
||||
- (instancetype)initWithCppResponder:(std::unique_ptr<facebook::sonar::SonarResponder>)responder;
|
||||
@end
|
||||
35
iOS/SonarKit/CppBridge/SonarCppBridgingResponder.mm
Normal file
35
iOS/SonarKit/CppBridge/SonarCppBridgingResponder.mm
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2018-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
#import "SonarCppBridgingResponder.h"
|
||||
|
||||
#import <FBCxxUtils/FBCxxFollyDynamicConvert.h>
|
||||
|
||||
@implementation SonarCppBridgingResponder {
|
||||
std::unique_ptr<facebook::sonar::SonarResponder> responder_;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCppResponder:(std::unique_ptr<facebook::sonar::SonarResponder>)responder
|
||||
{
|
||||
if (!responder) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (self = [super init]) {
|
||||
responder_ = std::move(responder);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - SonarResponder
|
||||
|
||||
- (void)success:(NSDictionary *)response { responder_->success(facebook::cxxutils::convertIdToFollyDynamic(response)); }
|
||||
|
||||
- (void)error:(NSDictionary *)response { responder_->error(facebook::cxxutils::convertIdToFollyDynamic(response)); }
|
||||
|
||||
@end
|
||||
48
iOS/SonarKit/CppBridge/SonarCppWrapperPlugin.h
Normal file
48
iOS/SonarKit/CppBridge/SonarCppWrapperPlugin.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2018-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
#ifndef __OBJC__
|
||||
#error This header can only be included in .mm (ObjC++) files
|
||||
#endif
|
||||
|
||||
#import <Sonar/SonarPlugin.h>
|
||||
#import <SonarKit/SonarCppBridgingConnection.h>
|
||||
#import <SonarKit/SonarPlugin.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace sonar {
|
||||
|
||||
using ObjCPlugin = NSObject<SonarPlugin> *;
|
||||
|
||||
/**
|
||||
SonarCppWrapperPlugin is a simple C++ wrapper around Objective-C Sonar plugins
|
||||
that can be passed to SonarClient. This class allows developers to write pure
|
||||
Objective-C plugins if they want.
|
||||
*/
|
||||
class SonarCppWrapperPlugin final : public facebook::sonar::SonarPlugin {
|
||||
public:
|
||||
// Under ARC copying objCPlugin *does* increment its retain count
|
||||
SonarCppWrapperPlugin(ObjCPlugin objCPlugin) : _objCPlugin(objCPlugin) {}
|
||||
|
||||
std::string identifier() const override { return [[_objCPlugin identifier] UTF8String]; }
|
||||
|
||||
void didConnect(std::shared_ptr<facebook::sonar::SonarConnection> conn) override
|
||||
{
|
||||
SonarCppBridgingConnection *const bridgingConn = [[SonarCppBridgingConnection alloc] initWithCppConnection:conn];
|
||||
[_objCPlugin didConnect:bridgingConn];
|
||||
}
|
||||
|
||||
void didDisconnect() override { [_objCPlugin didDisconnect]; }
|
||||
|
||||
ObjCPlugin getObjCPlugin() { return _objCPlugin; }
|
||||
|
||||
private:
|
||||
ObjCPlugin _objCPlugin;
|
||||
};
|
||||
|
||||
} // namespace sonar
|
||||
} // namespace facebook
|
||||
16
iOS/SonarKit/FBDefines/FBMacros.h
Normal file
16
iOS/SonarKit/FBDefines/FBMacros.h
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2004-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
#ifndef FB_SK_MACROS_H
|
||||
#define FB_SK_MACROS_H
|
||||
|
||||
#define FB_LINK_REQUIRE_(NAME, UNIQUE)
|
||||
#define FB_LINKABLE(NAME)
|
||||
#define FB_LINK_REQUIRE(NAME)
|
||||
#define FB_LINK_REQUIRE_EXT(NAME)
|
||||
|
||||
#endif
|
||||
27
iOS/SonarKit/SKMacros.h
Normal file
27
iOS/SonarKit/SKMacros.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2004-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SKMACROS_H
|
||||
#define SKMACROS_H
|
||||
|
||||
#import <FBDefines/FBMacros.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
# define SK_EXTERN_C_BEGIN extern "C" {
|
||||
# define SK_EXTERN_C_END }
|
||||
# define SK_EXTERN_C extern "C"
|
||||
#else
|
||||
# define SK_EXTERN_C_BEGIN
|
||||
# define SK_EXTERN_C_END
|
||||
# define SK_EXTERN_C extern
|
||||
#endif
|
||||
|
||||
#define SKLog(...) NSLog(__VA_ARGS__)
|
||||
#define SKTrace(...) /*NSLog(__VA_ARGS__)*/
|
||||
|
||||
#endif
|
||||
52
iOS/SonarKit/SonarClient.h
Normal file
52
iOS/SonarKit/SonarClient.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2018-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "SonarPlugin.h"
|
||||
|
||||
/**
|
||||
Represents a connection between the Sonar desktop och client side. Manages the lifecycle of attached
|
||||
plugin instances.
|
||||
*/
|
||||
@interface SonarClient : NSObject
|
||||
|
||||
/**
|
||||
The shared singleton SonarClient instance. It is an error to call this on non-debug builds to avoid leaking data.
|
||||
*/
|
||||
+ (instancetype)sharedClient;
|
||||
|
||||
/**
|
||||
Register a plugin with the client.
|
||||
*/
|
||||
- (void)addPlugin:(NSObject<SonarPlugin> *)plugin;
|
||||
|
||||
/**
|
||||
Unregister a plugin with the client.
|
||||
*/
|
||||
- (void)removePlugin:(NSObject<SonarPlugin> *)plugin;
|
||||
|
||||
/**
|
||||
Retrieve the plugin with a given identifier which was previously registered with this client.
|
||||
*/
|
||||
- (NSObject<SonarPlugin> *)pluginWithIdentifier:(NSString *)identifier;
|
||||
|
||||
/**
|
||||
Establish a connection to the Sonar desktop.
|
||||
*/
|
||||
- (void)start;
|
||||
|
||||
/**
|
||||
Stop the connection to the Sonar desktop.
|
||||
*/
|
||||
- (void)stop;
|
||||
|
||||
// initializers are disabled. You must use `+[SonarClient sharedClient]` instance.
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
|
||||
@end
|
||||
117
iOS/SonarKit/SonarClient.mm
Normal file
117
iOS/SonarKit/SonarClient.mm
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (c) 2018-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
#if FB_SONARKIT_ENABLED
|
||||
|
||||
#import "SonarClient.h"
|
||||
#import "SonarCppWrapperPlugin.h"
|
||||
#import <Sonar/SonarClient.h>
|
||||
#include <folly/io/async/EventBase.h>
|
||||
#include <folly/io/async/ScopedEventBaseThread.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#if !TARGET_OS_SIMULATOR
|
||||
#import "SKPortForwardingServer.h"
|
||||
#endif
|
||||
|
||||
using WrapperPlugin = facebook::sonar::SonarCppWrapperPlugin;
|
||||
|
||||
@implementation SonarClient {
|
||||
facebook::sonar::SonarClient *_cppClient;
|
||||
folly::ScopedEventBaseThread eventBaseThread;
|
||||
#if !TARGET_OS_SIMULATOR
|
||||
SKPortForwardingServer *_server;
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (instancetype)sharedClient
|
||||
{
|
||||
static SonarClient *sharedClient = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedClient = [[self alloc] init];
|
||||
});
|
||||
return sharedClient;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
UIDevice *device = [UIDevice currentDevice];
|
||||
NSString *deviceName = [device name];
|
||||
NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleNameKey];
|
||||
NSString *deviceId = [[device identifierForVendor]UUIDString];
|
||||
NSString *appId = appName;
|
||||
NSString *privateAppDirectory = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES)[0];
|
||||
|
||||
#if TARGET_OS_SIMULATOR
|
||||
deviceName = [NSString stringWithFormat:@"%@ %@", [[UIDevice currentDevice] model], @"Simulator"];
|
||||
#endif
|
||||
|
||||
facebook::sonar::SonarClient::init({
|
||||
{
|
||||
"localhost",
|
||||
"iOS",
|
||||
[deviceName UTF8String],
|
||||
[deviceId UTF8String],
|
||||
[appName UTF8String],
|
||||
[appId UTF8String],
|
||||
[privateAppDirectory UTF8String],
|
||||
},
|
||||
eventBaseThread.getEventBase(),
|
||||
});
|
||||
_cppClient = facebook::sonar::SonarClient::instance();
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)refreshPlugins
|
||||
{
|
||||
_cppClient->refreshPlugins();
|
||||
}
|
||||
|
||||
- (void)addPlugin:(NSObject<SonarPlugin> *)plugin
|
||||
{
|
||||
_cppClient->addPlugin(std::make_shared<WrapperPlugin>(plugin));
|
||||
}
|
||||
|
||||
- (void)removePlugin:(NSObject<SonarPlugin> *)plugin
|
||||
{
|
||||
_cppClient->removePlugin(std::make_shared<WrapperPlugin>(plugin));
|
||||
}
|
||||
|
||||
- (NSObject<SonarPlugin> *)pluginWithIdentifier:(NSString *)identifier
|
||||
{
|
||||
auto cppPlugin = _cppClient->getPlugin([identifier UTF8String]);
|
||||
if (auto wrapper = dynamic_cast<WrapperPlugin *>(cppPlugin.get())) {
|
||||
return wrapper->getObjCPlugin();
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)start;
|
||||
{
|
||||
#if !TARGET_OS_SIMULATOR
|
||||
_server = [SKPortForwardingServer new];
|
||||
[_server forwardConnectionsFromPort:8088];
|
||||
[_server listenForMultiplexingChannelOnPort:8078];
|
||||
#endif
|
||||
_cppClient->start();
|
||||
}
|
||||
|
||||
- (void)stop
|
||||
{
|
||||
_cppClient->stop();
|
||||
#if !TARGET_OS_SIMULATOR
|
||||
[_server close];
|
||||
_server = nil;
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
31
iOS/SonarKit/SonarConnection.h
Normal file
31
iOS/SonarKit/SonarConnection.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2018-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol SonarResponder;
|
||||
@protocol SonarWebSocket;
|
||||
|
||||
typedef void (^SonarReceiver)(NSDictionary*, id<SonarResponder>);
|
||||
|
||||
/**
|
||||
Represents a connection between the Desktop and mobile plugins with corresponding identifiers.
|
||||
*/
|
||||
@protocol SonarConnection
|
||||
|
||||
/**
|
||||
Invoke a method on the Sonar desktop plugin with with a matching identifier.
|
||||
*/
|
||||
- (void)send:(NSString *)method withParams:(NSDictionary *)params;
|
||||
|
||||
/**
|
||||
Register a receiver to be notified of incoming calls of the given method from the Sonar desktop
|
||||
plugin with a matching identifier.
|
||||
*/
|
||||
- (void)receive:(NSString *)method withBlock:(SonarReceiver)receiver;
|
||||
|
||||
@end
|
||||
39
iOS/SonarKit/SonarPlugin.h
Normal file
39
iOS/SonarKit/SonarPlugin.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2004-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "SKMacros.h"
|
||||
|
||||
SK_EXTERN_C_BEGIN
|
||||
void SonarPerformBlockOnMainThread(void(^block)());
|
||||
SK_EXTERN_C_END
|
||||
|
||||
@protocol SonarConnection;
|
||||
|
||||
@protocol SonarPlugin
|
||||
|
||||
/**
|
||||
The plugin's identifier. This should map to a javascript plugin with the same identifier to ensure
|
||||
messages are sent correctly.
|
||||
*/
|
||||
- (NSString *)identifier;
|
||||
|
||||
/**
|
||||
Called when a connection has been established between this plugin and the corresponding plugin on
|
||||
the Sonar desktop app. The provided connection can be used to register method receivers as well
|
||||
as send messages back to the desktop app.
|
||||
*/
|
||||
- (void)didConnect:(id<SonarConnection>)connection;
|
||||
|
||||
/**
|
||||
Called when a plugin has been disconnected and the SonarConnection provided in didConnect is no
|
||||
longer valid to use.
|
||||
*/
|
||||
- (void)didDisconnect;
|
||||
|
||||
@end
|
||||
25
iOS/SonarKit/SonarResponder.h
Normal file
25
iOS/SonarKit/SonarResponder.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2018-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
Acts as a hook for providing return values to remote called from Sonar desktop plugins.
|
||||
*/
|
||||
@protocol SonarResponder
|
||||
|
||||
/**
|
||||
Respond with a successful return value.
|
||||
*/
|
||||
- (void)success:(NSDictionary *)response;
|
||||
|
||||
/**
|
||||
Respond with an error.
|
||||
*/
|
||||
- (void)error:(NSDictionary *)response;
|
||||
|
||||
@end
|
||||
18
iOS/SonarKit/SonarUtil.m
Normal file
18
iOS/SonarKit/SonarUtil.m
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) 2018-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "SonarPlugin.h"
|
||||
|
||||
void SonarPerformBlockOnMainThread(void(^block)())
|
||||
{
|
||||
if ([NSThread isMainThread]) {
|
||||
block();
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), block);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2018-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
enum {
|
||||
SKPortForwardingFrameTypeOpenPipe = 201,
|
||||
SKPortForwardingFrameTypeWriteToPipe = 202,
|
||||
SKPortForwardingFrameTypeClosePipe = 203,
|
||||
};
|
||||
|
||||
static dispatch_data_t NSDataToGCDData(NSData *data) {
|
||||
__block NSData *retainedData = data;
|
||||
return dispatch_data_create(data.bytes, data.length, nil, ^{
|
||||
retainedData = nil;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) 2018-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface SKPortForwardingServer : NSObject
|
||||
|
||||
- (instancetype)init;
|
||||
|
||||
- (void)listenForMultiplexingChannelOnPort:(NSUInteger)port;
|
||||
- (void)forwardConnectionsFromPort:(NSUInteger)port;
|
||||
- (void)close;
|
||||
|
||||
@end
|
||||
190
iOS/SonarKit/Utilities/PortForwarding/SKPortForwardingServer.m
Normal file
190
iOS/SonarKit/Utilities/PortForwarding/SKPortForwardingServer.m
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright (c) 2018-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
#import "SKPortForwardingServer.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <CocoaAsyncSocket/GCDAsyncSocket.h>
|
||||
#import <peertalk/PTChannel.h>
|
||||
|
||||
#import "SKMacros.h"
|
||||
#import "SKPortForwardingCommon.h"
|
||||
|
||||
@interface SKPortForwardingServer () <PTChannelDelegate, GCDAsyncSocketDelegate>
|
||||
|
||||
@property (nonatomic, weak) PTChannel *serverChannel;
|
||||
@property (nonatomic, weak) PTChannel *peerChannel;
|
||||
|
||||
@property (nonatomic, strong) GCDAsyncSocket *serverSocket;
|
||||
@property (nonatomic, strong) NSMutableDictionary *clientSockets;
|
||||
@property (nonatomic, assign) UInt32 lastClientSocketTag;
|
||||
@property (nonatomic, strong) dispatch_queue_t socketQueue;
|
||||
@property (nonatomic, strong) PTProtocol *protocol;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SKPortForwardingServer
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_socketQueue = dispatch_queue_create("SKPortForwardingServer", DISPATCH_QUEUE_SERIAL);
|
||||
_lastClientSocketTag = 0;
|
||||
_clientSockets = [NSMutableDictionary dictionary];
|
||||
_protocol = [[PTProtocol alloc] initWithDispatchQueue:_socketQueue];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self close];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)forwardConnectionsFromPort:(NSUInteger)port
|
||||
{
|
||||
[self _forwardConnectionsFromPort:port reportError:YES];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
|
||||
[self _forwardConnectionsFromPort:port reportError:NO];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_forwardConnectionsFromPort:(NSUInteger)port reportError:(BOOL)shouldReportError
|
||||
{
|
||||
GCDAsyncSocket *serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:_socketQueue];
|
||||
NSError *listenError;
|
||||
if ([serverSocket acceptOnPort:port error:&listenError]) {
|
||||
self.serverSocket = serverSocket;
|
||||
} else {
|
||||
if (shouldReportError) {
|
||||
SKLog(@"Failed to listen: %@", listenError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)listenForMultiplexingChannelOnPort:(NSUInteger)port
|
||||
{
|
||||
[self _listenForMultiplexingChannelOnPort:port reportError:YES];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
|
||||
[self _listenForMultiplexingChannelOnPort:port reportError:NO];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_listenForMultiplexingChannelOnPort:(NSUInteger)port reportError:(BOOL)shouldReportError
|
||||
{
|
||||
PTChannel *channel = [[PTChannel alloc] initWithProtocol:_protocol delegate:self];
|
||||
[channel listenOnPort:port IPv4Address:INADDR_LOOPBACK callback:^(NSError *error) {
|
||||
if (error) {
|
||||
if (shouldReportError) {
|
||||
SKLog(@"Failed to listen on 127.0.0.1:%lu: %@", (unsigned long)port, error);
|
||||
}
|
||||
} else {
|
||||
SKTrace(@"Listening on 127.0.0.1:%lu", (unsigned long)port);
|
||||
self.serverChannel = channel;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)close
|
||||
{
|
||||
if (self.serverChannel) {
|
||||
[self.serverChannel close];
|
||||
self.serverChannel = nil;
|
||||
}
|
||||
[self.serverSocket disconnect];
|
||||
}
|
||||
|
||||
#pragma mark - PTChannelDelegate
|
||||
|
||||
- (void)ioFrameChannel:(PTChannel *)channel didAcceptConnection:(PTChannel *)otherChannel fromAddress:(PTAddress *)address {
|
||||
// Cancel any other connection. We are FIFO, so the last connection
|
||||
// established will cancel any previous connection and "take its place".
|
||||
if (self.peerChannel) {
|
||||
[self.peerChannel cancel];
|
||||
}
|
||||
|
||||
// Weak pointer to current connection. Connection objects live by themselves
|
||||
// (owned by its parent dispatch queue) until they are closed.
|
||||
self.peerChannel = otherChannel;
|
||||
self.peerChannel.userInfo = address;
|
||||
SKTrace(@"Connected to %@", address);
|
||||
}
|
||||
|
||||
- (void)ioFrameChannel:(PTChannel *)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(PTData *)payload {
|
||||
//NSLog(@"didReceiveFrameOfType: %u, %u, %@", type, tag, payload);
|
||||
if (type == SKPortForwardingFrameTypeWriteToPipe) {
|
||||
GCDAsyncSocket *sock = self.clientSockets[@(tag)];
|
||||
[sock writeData:[NSData dataWithBytes:payload.data length:payload.length] withTimeout:-1 tag:0];
|
||||
SKTrace(@"channel -> socket (%d), %zu bytes", tag, payload.length);
|
||||
}
|
||||
|
||||
if (type == SKPortForwardingFrameTypeClosePipe) {
|
||||
GCDAsyncSocket *sock = self.clientSockets[@(tag)];
|
||||
[sock disconnectAfterWriting];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)ioFrameChannel:(PTChannel *)channel didEndWithError:(NSError *)error {
|
||||
for (GCDAsyncSocket *sock in [_clientSockets objectEnumerator]) {
|
||||
[sock setDelegate:nil];
|
||||
[sock disconnect];
|
||||
}
|
||||
[self.clientSockets removeAllObjects];
|
||||
SKTrace(@"Disconnected from %@, error = %@", channel.userInfo, error);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - GCDAsyncSocketDelegate
|
||||
|
||||
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
|
||||
{
|
||||
dispatch_block_t block = ^() {
|
||||
if (!self.peerChannel) {
|
||||
[newSocket setDelegate:nil];
|
||||
[newSocket disconnect];
|
||||
}
|
||||
|
||||
UInt32 tag = ++self->_lastClientSocketTag;
|
||||
newSocket.userData = @(tag);
|
||||
newSocket.delegate = self;
|
||||
self.clientSockets[@(tag)] = newSocket;
|
||||
[self.peerChannel sendFrameOfType:SKPortForwardingFrameTypeOpenPipe tag:self->_lastClientSocketTag withPayload:nil callback:^(NSError *error) {
|
||||
SKTrace(@"open socket (%d), error = %@", (unsigned int)tag, error);
|
||||
[newSocket readDataWithTimeout:-1 tag:0];
|
||||
}];
|
||||
};
|
||||
|
||||
if (_peerChannel) {
|
||||
block();
|
||||
} else {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), _socketQueue, block);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)_
|
||||
{
|
||||
UInt32 tag = [[sock userData] unsignedIntValue];
|
||||
SKTrace(@"Incoming data on socket (%d) - %lu bytes", (unsigned int)tag, (unsigned long)data.length);
|
||||
[_peerChannel sendFrameOfType:SKPortForwardingFrameTypeWriteToPipe tag:tag withPayload:NSDataToGCDData(data) callback:^(NSError *error) {
|
||||
SKTrace(@"socket (%d) -> channel %lu bytes, error = %@", (unsigned int)tag, (unsigned long)data.length, error);
|
||||
[sock readDataWithTimeout:-1 tag:_];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
|
||||
{
|
||||
UInt32 tag = [sock.userData unsignedIntValue];
|
||||
[_clientSockets removeObjectForKey:@(tag)];
|
||||
[_peerChannel sendFrameOfType:SKPortForwardingFrameTypeClosePipe tag:tag withPayload:nil callback:^(NSError *error) {
|
||||
SKTrace(@"socket (%d) disconnected, err = %@, peer error = %@", (unsigned int)tag, err, error);
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user