diff --git a/xplat/Flipper/FlipperConnectionManagerImpl.cpp b/xplat/Flipper/FlipperConnectionManagerImpl.cpp index 4c942ed50..4c99d77c0 100644 --- a/xplat/Flipper/FlipperConnectionManagerImpl.cpp +++ b/xplat/Flipper/FlipperConnectionManagerImpl.cpp @@ -11,14 +11,11 @@ #include #include #include -#include -#include -#include #include #include #include "ConnectionContextStore.h" #include "FireAndForgetBasedFlipperResponder.h" -#include "FlipperRSocketResponder.h" +#include "FlipperRSocket.h" #include "FlipperResponderImpl.h" #include "FlipperStep.h" #include "Log.h" @@ -28,9 +25,6 @@ "ERROR: Aborting flipper initialization because it's not running in the flipper thread." static constexpr int reconnectIntervalSeconds = 2; -static constexpr int connectionKeepaliveSeconds = 10; - -static constexpr int maxPayloadSize = 0xFFFFFF; // Not a public-facing version number. // Used for compatibility checking with desktop flipper. @@ -42,35 +36,33 @@ using namespace folly; namespace facebook { namespace flipper { -class ConnectionEvents : public rsocket::RSocketConnectionEvents { - private: - FlipperConnectionManagerImpl* websocket_; - +class ConnectionEvents { public: - ConnectionEvents(FlipperConnectionManagerImpl* websocket) - : websocket_(websocket) {} - - void onConnected() { - websocket_->isOpen_ = true; - if (websocket_->connectionIsTrusted_) { - websocket_->callbacks_->onConnected(); + ConnectionEvents(FlipperConnectionManagerImpl* impl) : impl_(impl) {} + void operator()(const SocketEvent event) { + switch (event) { + case SocketEvent::OPEN: + impl_->isOpen_ = true; + if (impl_->connectionIsTrusted_) { + impl_->callbacks_->onConnected(); + } + break; + case SocketEvent::CLOSE: + case SocketEvent::ERROR: + if (!impl_->isOpen_) + return; + impl_->isOpen_ = false; + if (impl_->connectionIsTrusted_) { + impl_->connectionIsTrusted_ = false; + impl_->callbacks_->onDisconnected(); + } + impl_->reconnect(); + break; } } - void onDisconnected(const folly::exception_wrapper&) { - if (!websocket_->isOpen_) - return; - websocket_->isOpen_ = false; - if (websocket_->connectionIsTrusted_) { - websocket_->connectionIsTrusted_ = false; - websocket_->callbacks_->onDisconnected(); - } - websocket_->reconnect(); - } - - void onClosed(const folly::exception_wrapper& e) { - onDisconnected(e); - } + private: + FlipperConnectionManagerImpl* impl_; }; FlipperConnectionManagerImpl::FlipperConnectionManagerImpl( @@ -139,7 +131,7 @@ void FlipperConnectionManagerImpl::startSync() { : "Establish main connection"); try { if (isClientSetupStep) { - bool success = doCertificateExchange(); + bool success = connectAndExchangeCertificate(); if (!success) { reconnect(); return; @@ -178,41 +170,30 @@ void FlipperConnectionManagerImpl::startSync() { } } -bool FlipperConnectionManagerImpl::doCertificateExchange() { - rsocket::SetupParameters parameters; - folly::SocketAddress address; +bool FlipperConnectionManagerImpl::connectAndExchangeCertificate() { + auto endpoint = + FlipperConnectionEndpoint(deviceData_.host, insecurePort, false); + int medium = certProvider_ != nullptr ? certProvider_->getCertificateExchangeMedium() : FlipperCertificateExchangeMedium::FS_ACCESS; - parameters.payload = rsocket::Payload(folly::toJson(folly::dynamic::object( - "os", deviceData_.os)("device", deviceData_.device)( - "app", deviceData_.app)("sdk_version", sdkVersion)("medium", medium))); - address.setFromHostPort(deviceData_.host, insecurePort); + auto payload = std::make_unique(); + payload->os = deviceData_.os; + payload->device = deviceData_.device; + payload->device_id = "unknown"; + payload->app = deviceData_.app; + payload->sdk_version = sdkVersion; + payload->medium = medium; + + auto newClient = std::make_unique( + endpoint, std::move(payload), connectionEventBase_); + newClient->setEventHandler(ConnectionEvents(this)); auto connectingInsecurely = flipperState_->start("Connect insecurely"); connectionIsTrusted_ = false; - auto newClient = - rsocket::RSocket::createConnectedClient( - std::make_unique( - *connectionEventBase_->getEventBase(), std::move(address)), - std::move(parameters), - nullptr, - std::chrono::seconds(connectionKeepaliveSeconds), // keepaliveInterval - nullptr, // stats - std::make_shared(this)) - .thenError([](const auto& e) { - if (e.getType() == folly::AsyncSocketException::NOT_OPEN || - e.getType() == folly::AsyncSocketException::NETWORK_ERROR) { - // This is the state where no Flipper desktop client is connected. - // We don't want an exception thrown here. - return std::unique_ptr(nullptr); - } - throw e; - }) - .get(); - if (newClient.get() == nullptr) { + if (!newClient->connect(this)) { connectingInsecurely->fail("Failed to connect"); return false; } @@ -229,53 +210,36 @@ bool FlipperConnectionManagerImpl::doCertificateExchange() { } bool FlipperConnectionManagerImpl::connectSecurely() { - rsocket::SetupParameters parameters; - folly::SocketAddress address; + auto endpoint = FlipperConnectionEndpoint(deviceData_.host, securePort, true); + + int medium = certProvider_ != nullptr + ? certProvider_->getCertificateExchangeMedium() + : FlipperCertificateExchangeMedium::FS_ACCESS; auto loadingDeviceId = flipperState_->start("Load Device Id"); auto deviceId = contextStore_->getDeviceId(); if (deviceId.compare("unknown")) { loadingDeviceId->complete(); } - int medium = certProvider_ != nullptr - ? certProvider_->getCertificateExchangeMedium() - : FlipperCertificateExchangeMedium::FS_ACCESS; - parameters.payload = rsocket::Payload(folly::toJson(folly::dynamic::object( - "csr", contextStore_->getCertificateSigningRequest().c_str())( - "csr_path", contextStore_->getCertificateDirectoryPath().c_str())( - "os", deviceData_.os)("device", deviceData_.device)( - "device_id", deviceId)("app", deviceData_.app)("medium", medium)( - "sdk_version", sdkVersion))); - address.setFromHostPort(deviceData_.host, securePort); + auto payload = std::make_unique(); + payload->os = deviceData_.os; + payload->device = deviceData_.device; + payload->device_id = deviceId; + payload->app = deviceData_.app; + payload->sdk_version = sdkVersion; + payload->medium = medium; + payload->csr = contextStore_->getCertificateSigningRequest().c_str(); + payload->csr_path = contextStore_->getCertificateDirectoryPath().c_str(); + + auto newClient = std::make_unique( + endpoint, std::move(payload), connectionEventBase_, contextStore_.get()); + newClient->setEventHandler(ConnectionEvents(this)); - std::shared_ptr sslContext = - contextStore_->getSSLContext(); auto connectingSecurely = flipperState_->start("Connect securely"); connectionIsTrusted_ = true; - auto newClient = - rsocket::RSocket::createConnectedClient( - std::make_unique( - *connectionEventBase_->getEventBase(), - std::move(address), - std::move(sslContext)), - std::move(parameters), - std::make_shared(this, connectionEventBase_), - std::chrono::seconds(connectionKeepaliveSeconds), // keepaliveInterval - nullptr, // stats - std::make_shared(this)) - .thenError([](const auto& e) { - if (e.getType() == folly::AsyncSocketException::NOT_OPEN || - e.getType() == folly::AsyncSocketException::NETWORK_ERROR) { - // This is the state where no Flipper desktop client is connected. - // We don't want an exception thrown here. - return std::unique_ptr(nullptr); - } - throw e; - }) - .get(); - if (newClient.get() == nullptr) { + if (!newClient->connect(this)) { connectingSecurely->fail("Failed to connect"); return false; } @@ -324,11 +288,8 @@ void FlipperConnectionManagerImpl::setCallbacks(Callbacks* callbacks) { void FlipperConnectionManagerImpl::sendMessage(const folly::dynamic& message) { flipperEventBase_->add([this, message]() { try { - rsocket::Payload payload = toRSocketPayload(message); if (client_) { - client_->getRequester() - ->fireAndForget(std::move(payload)) - ->subscribe([]() {}); + client_->send(message, []() {}); } } catch (std::length_error& e) { // Skip sending messages that are too large. @@ -369,72 +330,76 @@ void FlipperConnectionManagerImpl::requestSignedCertFromFlipper() { "destination", contextStore_->getCertificateDirectoryPath().c_str())("medium", medium); auto gettingCert = flipperState_->start("Getting cert from desktop"); + bool handled = false; - flipperEventBase_->add([this, message, gettingCert]() { - client_->getRequester() - ->requestResponse(rsocket::Payload(folly::toJson(message))) - ->subscribe( - [this, gettingCert](rsocket::Payload p) { - auto response = p.moveDataToString(); - if (!response.empty()) { - folly::dynamic config = folly::parseJson(response); - contextStore_->storeConnectionConfig(config); - } - if (certProvider_) { - certProvider_->setFlipperState(flipperState_); - auto gettingCertFromProvider = - flipperState_->start("Getting cert from Cert Provider"); - - try { - // Certificates should be present in app's sandbox after it is - // returned. The reason we can't have a completion block here - // is because if the certs are not present after it returns - // then the flipper tries to reconnect on insecured channel - // and recreates the app.csr. By the time completion block is - // called the DeviceCA cert doesn't match app's csr and it - // throws an SSL error. - certProvider_->getCertificates( - contextStore_->getCertificateDirectoryPath(), - contextStore_->getDeviceId()); - gettingCertFromProvider->complete(); - } catch (std::exception& e) { - gettingCertFromProvider->fail(e.what()); - gettingCert->fail(e.what()); - } catch (...) { - gettingCertFromProvider->fail("Exception from certProvider"); - gettingCert->fail("Exception from certProvider"); - } - } - log("Certificate exchange complete."); - gettingCert->complete(); - - // Disconnect after message sending is complete. - // This will trigger a reconnect which should use the secure - // channel. - // TODO: Connect immediately, without waiting for reconnect + flipperEventBase_->add([this, &handled, message, gettingCert]() { + client_->sendExpectResponse( + folly::toJson(message), + [this, &handled, message, gettingCert]( + const std::string& response, bool isError) { + /** + Need to keep track of whether the response has been handled. + On success, the completion handler deallocates the socket which in + turn triggers a disconnect. A disconnect is called within the + context of a subscription handler. This means that the completion + handler can be called again to notify that the stream has been + interrupted because we are effectively still handing the response + read. So, if already handled, ignore and return; + */ + if (handled) + return; + handled = true; + if (isError) { + if (response.compare("not implemented")) { + auto error = + "Desktop failed to provide certificates. Error from flipper desktop:\n" + + response; + log(error); + gettingCert->fail(error); client_ = nullptr; - }, - [this, message, gettingCert](folly::exception_wrapper e) { - e.handle( - [&](rsocket::ErrorWithPayload& errorWithPayload) { - std::string errorMessage = - errorWithPayload.payload.moveDataToString(); + } else { + sendLegacyCertificateRequest(message); + } + return; + } + if (!response.empty()) { + folly::dynamic config = folly::parseJson(response); + contextStore_->storeConnectionConfig(config); + } + if (certProvider_) { + certProvider_->setFlipperState(flipperState_); + auto gettingCertFromProvider = + flipperState_->start("Getting cert from Cert Provider"); - if (errorMessage.compare("not implemented")) { - auto error = - "Desktop failed to provide certificates. Error from flipper desktop:\n" + - errorMessage; - log(error); - gettingCert->fail(error); - client_ = nullptr; - } else { - sendLegacyCertificateRequest(message); - } - }, - [e, gettingCert](...) { - gettingCert->fail(e.what().c_str()); - }); - }); + try { + // Certificates should be present in app's sandbox after it is + // returned. The reason we can't have a completion block here + // is because if the certs are not present after it returns + // then the flipper tries to reconnect on insecured channel + // and recreates the app.csr. By the time completion block is + // called the DeviceCA cert doesn't match app's csr and it + // throws an SSL error. + certProvider_->getCertificates( + contextStore_->getCertificateDirectoryPath(), + contextStore_->getDeviceId()); + gettingCertFromProvider->complete(); + } catch (std::exception& e) { + gettingCertFromProvider->fail(e.what()); + gettingCert->fail(e.what()); + } catch (...) { + gettingCertFromProvider->fail("Exception from certProvider"); + gettingCert->fail("Exception from certProvider"); + } + } + log("Certificate exchange complete."); + gettingCert->complete(); + + // Disconnect after message sending is complete. + // The client destructor will send a disconnected event + // which will be handled by Flipper which will initiate + // a reconnect sequence. + client_ = nullptr; + }); }); failedConnectionAttempts_ = 0; } @@ -445,35 +410,18 @@ void FlipperConnectionManagerImpl::sendLegacyCertificateRequest( // Fall back to fireAndForget, instead of requestResponse. auto sendingRequest = flipperState_->start("Sending fallback certificate request"); - client_->getRequester() - ->fireAndForget(rsocket::Payload(folly::toJson(message))) - ->subscribe([this, sendingRequest]() { - sendingRequest->complete(); - folly::dynamic config = folly::dynamic::object(); - contextStore_->storeConnectionConfig(config); - client_ = nullptr; - }); + + client_->send(message, [this, sendingRequest]() { + sendingRequest->complete(); + folly::dynamic config = folly::dynamic::object(); + contextStore_->storeConnectionConfig(config); + client_ = nullptr; + }); } bool FlipperConnectionManagerImpl::isRunningInOwnThread() { return flipperEventBase_->isInEventBaseThread(); } -rsocket::Payload toRSocketPayload(dynamic data) { - std::string json = folly::toJson(data); - rsocket::Payload payload = rsocket::Payload(json); - auto payloadLength = payload.data->computeChainDataLength(); - if (payloadLength > maxPayloadSize) { - auto logMessage = - std::string( - "Error: Skipping sending message larger than max rsocket payload: ") + - json.substr(0, 100) + "..."; - log(logMessage); - throw std::length_error(logMessage); - } - - return payload; -} - } // namespace flipper } // namespace facebook diff --git a/xplat/Flipper/FlipperConnectionManagerImpl.h b/xplat/Flipper/FlipperConnectionManagerImpl.h index 04a885359..e212025fe 100644 --- a/xplat/Flipper/FlipperConnectionManagerImpl.h +++ b/xplat/Flipper/FlipperConnectionManagerImpl.h @@ -9,10 +9,10 @@ #include #include -#include #include #include "FlipperConnectionManager.h" #include "FlipperInitConfig.h" +#include "FlipperSocket.h" #include "FlipperState.h" namespace facebook { @@ -22,8 +22,6 @@ class ConnectionEvents; class ConnectionContextStore; class FlipperRSocketResponder; -rsocket::Payload toRSocketPayload(folly::dynamic data); - class FlipperConnectionManagerImpl : public FlipperConnectionManager { friend ConnectionEvents; @@ -66,13 +64,15 @@ class FlipperConnectionManagerImpl : public FlipperConnectionManager { folly::EventBase* flipperEventBase_; folly::EventBase* connectionEventBase_; - std::unique_ptr client_; + + std::unique_ptr client_; + bool connectionIsTrusted_; int failedConnectionAttempts_ = 0; std::shared_ptr contextStore_; void startSync(); - bool doCertificateExchange(); + bool connectAndExchangeCertificate(); bool connectSecurely(); bool isCertificateExchangeNeeded(); void requestSignedCertFromFlipper(); diff --git a/xplat/Flipper/FlipperRSocket.cpp b/xplat/Flipper/FlipperRSocket.cpp new file mode 100644 index 000000000..5ebba7d40 --- /dev/null +++ b/xplat/Flipper/FlipperRSocket.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "FlipperRSocket.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ConnectionContextStore.h" +#include "FireAndForgetBasedFlipperResponder.h" +#include "FlipperRSocketResponder.h" +#include "FlipperResponderImpl.h" +#include "FlipperTransportTypes.h" +#include "Log.h" +#include "yarpl/Single.h" + +static constexpr int connectionKeepaliveSeconds = 10; +static constexpr int maxPayloadSize = 0xFFFFFF; + +namespace facebook { +namespace flipper { + +rsocket::Payload toRSocketPayload(folly::dynamic data); + +class RSocketEvents : public rsocket::RSocketConnectionEvents { + private: + const SocketEventHandler& handler_; + + public: + RSocketEvents(const SocketEventHandler& eventHandler) + : handler_(eventHandler) {} + + void onConnected() { + handler_(SocketEvent::OPEN); + } + + void onDisconnected(const folly::exception_wrapper&) { + handler_(SocketEvent::CLOSE); + } + + void onClosed(const folly::exception_wrapper& e) { + handler_(SocketEvent::CLOSE); + } +}; + +class RSocketSerializer : public FlipperPayloadSerializer { + public: + void put(std::string key, std::string value) override { + object_[key] = value; + } + void put(std::string key, int value) override { + object_[key] = value; + } + std::string serialize() override { + return folly::toJson(object_); + } + ~RSocketSerializer() {} + + private: + folly::dynamic object_ = folly::dynamic::object(); +}; + +rsocket::Payload toRSocketPayload(folly::dynamic data) { + std::string json = folly::toJson(data); + rsocket::Payload payload = rsocket::Payload(json); + auto payloadLength = payload.data->computeChainDataLength(); + if (payloadLength > maxPayloadSize) { + auto logMessage = + std::string( + "Error: Skipping sending message larger than max rsocket payload: ") + + json.substr(0, 100) + "..."; + log(logMessage); + throw std::length_error(logMessage); + } + + return payload; +} + +FlipperRSocket::FlipperRSocket( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + folly::EventBase* eventBase) + : endpoint_(std::move(endpoint)), + payload_(std::move(payload)), + eventBase_(eventBase) {} + +FlipperRSocket::FlipperRSocket( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + folly::EventBase* eventBase, + ConnectionContextStore* connectionContextStore) + : endpoint_(std::move(endpoint)), + payload_(std::move(payload)), + eventBase_(eventBase), + connectionContextStore_(connectionContextStore) {} + +void FlipperRSocket::setEventHandler(SocketEventHandler eventHandler) { + eventHandler_ = std::move(eventHandler); +} + +void FlipperRSocket::setMessageHandler(SocketMessageHandler messageHandler) { + messageHandler_ = std::move(messageHandler); +} + +bool FlipperRSocket::connect(FlipperConnectionManager* manager) { + folly::SocketAddress address; + address.setFromHostPort(endpoint_.host, endpoint_.port); + + auto serializer = RSocketSerializer{}; + payload_->serialize(serializer); + auto payload = serializer.serialize(); + + rsocket::SetupParameters parameters; + parameters.payload = rsocket::Payload(payload); + + std::unique_ptr tcpConnectionFactory = nullptr; + if (endpoint_.secure) { + tcpConnectionFactory = std::make_unique( + *eventBase_->getEventBase(), + std::move(address), + connectionContextStore_->getSSLContext()); + } else { + tcpConnectionFactory = std::make_unique( + *eventBase_->getEventBase(), std::move(address)); + } + + auto newClient = + rsocket::RSocket::createConnectedClient( + std::move(tcpConnectionFactory), + std::move(parameters), + endpoint_.secure + ? std::make_shared(manager, eventBase_) + : nullptr, + std::chrono::seconds(connectionKeepaliveSeconds), // keepaliveInterval + nullptr, // stats + std::make_shared(eventHandler_)) + .thenError([](const auto& e) { + if (e.getType() == folly::AsyncSocketException::NOT_OPEN || + e.getType() == folly::AsyncSocketException::NETWORK_ERROR) { + // This is the state where no Flipper desktop client is connected. + // We don't want an exception thrown here. + return std::unique_ptr(nullptr); + } + throw e; + }) + .get(); + + if (newClient.get() == nullptr) { + return false; + } + + client_ = std::move(newClient); + return true; +} + +void FlipperRSocket::disconnect() { + if (client_.get() == nullptr) + return; + client_->disconnect(); +} + +void FlipperRSocket::send( + const folly::dynamic& message, + SocketSendHandler completion) { + if (client_.get() == nullptr) + return; + rsocket::Payload payload = toRSocketPayload(message); + client_->getRequester() + ->fireAndForget(std::move(payload)) + ->subscribe(completion); +} + +void FlipperRSocket::send( + const std::string& message, + SocketSendHandler completion) { + if (client_.get() == nullptr) + return; + client_->getRequester() + ->fireAndForget(rsocket::Payload(message)) + ->subscribe(completion); +} + +void FlipperRSocket::sendExpectResponse( + const std::string& message, + SocketSendExpectResponseHandler completion) { + if (client_.get() == nullptr) + return; + client_->getRequester() + ->requestResponse(rsocket::Payload(message)) + ->subscribe( + [completion](rsocket::Payload payload) { + auto response = payload.moveDataToString(); + completion(response, false); + }, + [completion](folly::exception_wrapper e) { + e.handle( + [&](rsocket::ErrorWithPayload& errorWithPayload) { + auto error = errorWithPayload.payload.moveDataToString(); + completion(error, true); + }, + [e, completion](...) { completion(e.what().c_str(), true); }); + }); +} + +} // namespace flipper +} // namespace facebook diff --git a/xplat/Flipper/FlipperRSocket.h b/xplat/Flipper/FlipperRSocket.h new file mode 100644 index 000000000..f931a12fc --- /dev/null +++ b/xplat/Flipper/FlipperRSocket.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include +#include "FlipperSocket.h" +#include "FlipperTransportTypes.h" + +namespace facebook { +namespace flipper { + +class FlipperConnectionManager; +class ConnectionContextStore; +class FlipperRSocket : public FlipperSocket { + public: + FlipperRSocket( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + folly::EventBase* eventBase); + FlipperRSocket( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + folly::EventBase* eventBase, + ConnectionContextStore* connectionContextStore); + + virtual ~FlipperRSocket() {} + + virtual void setEventHandler(SocketEventHandler eventHandler) override; + virtual void setMessageHandler(SocketMessageHandler messageHandler) override; + + virtual bool connect(FlipperConnectionManager* manager) override; + virtual void disconnect() override; + + virtual void send(const folly::dynamic& message, SocketSendHandler completion) + override; + virtual void send(const std::string& message, SocketSendHandler completion) + override; + virtual void sendExpectResponse( + const std::string& message, + SocketSendExpectResponseHandler completion) override; + + private: + FlipperConnectionEndpoint endpoint_; + std::unique_ptr payload_; + folly::EventBase* eventBase_; + ConnectionContextStore* connectionContextStore_; + + std::unique_ptr client_; + + SocketEventHandler eventHandler_; + SocketMessageHandler messageHandler_; +}; + +} // namespace flipper +} // namespace facebook diff --git a/xplat/Flipper/FlipperSocket.h b/xplat/Flipper/FlipperSocket.h new file mode 100644 index 000000000..66f9128c8 --- /dev/null +++ b/xplat/Flipper/FlipperSocket.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include +#include "FlipperTransportTypes.h" + +namespace facebook { +namespace flipper { + +class FlipperConnectionManager; +class ConnectionContextStore; +class FlipperSocket { + public: + virtual ~FlipperSocket() {} + /** + Sets the socket event handler. Used to observe underlying socket state + changes. + @param eventHandler Observer to be notified of state changes. + */ + virtual void setEventHandler(SocketEventHandler eventHandler) = 0; + /** + Sets the socket message handler. Used to handle received messages. + @param messageHandler Received messages handler. + */ + virtual void setMessageHandler(SocketMessageHandler messageHandler) = 0; + /** + Connect the socket to the specified endpoint. This is a blocking call + meaning that it will return once the socket is connected and ready to be + used or error. + @param manager An instance of FlipperConnectionManager. + */ + virtual bool connect(FlipperConnectionManager* manager) = 0; + /** + Disconnect from the endpoint. + */ + virtual void disconnect() = 0; + /** + Send a message to the receiving end. + @param message A message to be sent. + @param completion A completion handler to be invoked when the message has + been sent. + */ + virtual void send( + const folly::dynamic& message, + SocketSendHandler completion) = 0; + /** + Send a message to the receiving end. + @param message A message to be sent. + @param completion A completion handler to be invoked when the message has + been sent. + */ + virtual void send( + const std::string& message, + SocketSendHandler completion) = 0; + /** + Send a message and expect a response. + @param message A message to be sent. + @param completion A completion handler to be invoked when a response is + received. + */ + virtual void sendExpectResponse( + const std::string& message, + SocketSendExpectResponseHandler completion) = 0; +}; + +} // namespace flipper +} // namespace facebook diff --git a/xplat/Flipper/FlipperTransportTypes.h b/xplat/Flipper/FlipperTransportTypes.h new file mode 100644 index 000000000..c6836e3d9 --- /dev/null +++ b/xplat/Flipper/FlipperTransportTypes.h @@ -0,0 +1,136 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +namespace facebook { +namespace flipper { + +/** + SocketEvent defines the socket states used by Flipper. + */ +enum class SocketEvent : int { + OPEN, + CLOSE, + ERROR, +}; + +/** + Defines a socket event handler. Used to notify changes in socket state. + Possible events are but not limited to: open, close, error. + */ +typedef std::function SocketEventHandler; + +/** + Defines a socket message received handler. + */ +typedef std::function SocketMessageHandler; + +/** + Defines a socket certificate provider. The provider is responsible of returning + the necessary client certificate used to establish a valid secure connection. + @param password Empty buffer which will be set by the provider with the + certificate password. + @param length Length of the password buffer. + */ +typedef std::function + SocketCertificateProvider; + +/** + Defines a socket send completion handler. This is used to notify the caller + that data has been sent to the receiving end. + */ +typedef std::function SocketSendHandler; + +/** + Defines a socket send completion handler for the cases where a response is + expected. This is used to notify the caller when a response has been received. + @param response Received response. + @param isError Indicates whether an error has taken place in which case the + response will contain text describing the error. + */ +typedef std::function + SocketSendExpectResponseHandler; + +/** + Defines a serializer than can be used to serialize socket payloads. + */ +struct FlipperPayloadSerializer { + virtual ~FlipperPayloadSerializer() {} + virtual void put(std::string key, std::string value) = 0; + virtual void put(std::string key, int value) = 0; + virtual std::string serialize() = 0; +}; + +/** + Defines the base payload used to establish a connection in Flipper. + */ +struct FlipperSocketBasePayload { + std::string os; + std::string device; + std::string device_id; + std::string app; + int sdk_version; + int medium; + FlipperSocketBasePayload() {} + FlipperSocketBasePayload( + std::string os, + std::string device, + std::string device_id, + std::string app) + : os(std::move(os)), + device(std::move(device)), + device_id(std::move(device_id)), + app(std::move(app)) {} + virtual ~FlipperSocketBasePayload() {} + virtual void serialize(FlipperPayloadSerializer& serializer) { + serializer.put("os", os); + serializer.put("device", device); + serializer.put("device_id", device_id); + serializer.put("app", app); + serializer.put("sdk_version", sdk_version); + serializer.put("medium", medium); + } +}; + +/** + Defines the secure payload used to establish a secure connection in Flipper. + */ +struct FlipperSocketSecurePayload : public FlipperSocketBasePayload { + std::string csr; + std::string csr_path; + FlipperSocketSecurePayload() {} + FlipperSocketSecurePayload( + std::string os, + std::string device, + std::string device_id, + std::string app) + : FlipperSocketBasePayload(os, device, device_id, app) {} + virtual ~FlipperSocketSecurePayload() {} + virtual void serialize(FlipperPayloadSerializer& serializer) { + FlipperSocketBasePayload::serialize(serializer); + serializer.put("csr", csr); + serializer.put("csr_path", csr_path); + } +}; + +/** + Defines a connection endpoint used by Flipper. + */ +struct FlipperConnectionEndpoint { + std::string host; + int port; + bool secure; + + FlipperConnectionEndpoint(std::string host, int port, bool secure) + : host(std::move(host)), port(port), secure(secure) {} +}; + +} // namespace flipper +} // namespace facebook