From b3cf7e1ad12cae96292ddb9c9bcc00065b2be8be Mon Sep 17 00:00:00 2001 From: Lorenzo Blasa Date: Thu, 10 Feb 2022 05:23:40 -0800 Subject: [PATCH] C++ WebSocket client for Flipper Summary: Introducing a Flipper WebSocket client implemented in C++. The requirement came from Spark AR (Skylight) as they have a macOS/Linux/Windows clients. For reviewers: - This is an implementation of the existing FlipperSocket interface. Effectively, the only type that needs to be reviewed is WebSocketTLSClient. - BaseClient defined a base class for WebSocketClient and WebSocketTLSClient. - WebSocketClient is a simplified version of WebSocketTLSClient as there's no TLS configuration. Reviewed By: mweststrate Differential Revision: D34081943 fbshipit-source-id: 619a83f5a6783a21069d0f5111d139bb180f9e97 --- xplat/FlipperWebSocket/BaseClient.h | 98 ++++++ xplat/FlipperWebSocket/FlipperWebSocket.cpp | 99 ++++++ xplat/FlipperWebSocket/FlipperWebSocket.h | 85 +++++ xplat/FlipperWebSocket/WebSocketClient.cpp | 249 ++++++++++++++ xplat/FlipperWebSocket/WebSocketClient.h | 81 +++++ xplat/FlipperWebSocket/WebSocketTLSClient.cpp | 313 ++++++++++++++++++ xplat/FlipperWebSocket/WebSocketTLSClient.h | 86 +++++ 7 files changed, 1011 insertions(+) create mode 100644 xplat/FlipperWebSocket/BaseClient.h create mode 100644 xplat/FlipperWebSocket/FlipperWebSocket.cpp create mode 100644 xplat/FlipperWebSocket/FlipperWebSocket.h create mode 100644 xplat/FlipperWebSocket/WebSocketClient.cpp create mode 100644 xplat/FlipperWebSocket/WebSocketClient.h create mode 100644 xplat/FlipperWebSocket/WebSocketTLSClient.cpp create mode 100644 xplat/FlipperWebSocket/WebSocketTLSClient.h diff --git a/xplat/FlipperWebSocket/BaseClient.h b/xplat/FlipperWebSocket/BaseClient.h new file mode 100644 index 000000000..36fd59e8e --- /dev/null +++ b/xplat/FlipperWebSocket/BaseClient.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#ifdef FB_SONARKIT_ENABLED + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace facebook { +namespace flipper { + +class FlipperConnectionManager; +class ConnectionContextStore; +class BaseClient { + public: + enum Status { + Unconnected, + Connecting, + Initializing, + Open, + ServerNotFound, + Failed, + Closed + }; + BaseClient( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + folly::EventBase* eventBase) + : endpoint_(std::move(endpoint)), + payload_(std::move(payload)), + eventBase_(eventBase) {} + BaseClient( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + folly::EventBase* eventBase, + ConnectionContextStore* connectionContextStore) + : endpoint_(std::move(endpoint)), + payload_(std::move(payload)), + eventBase_(eventBase), + connectionContextStore_(connectionContextStore) {} + + BaseClient(const BaseClient&) = delete; + BaseClient& operator=(const BaseClient&) = delete; + + virtual ~BaseClient() {} + + Status status() const { + return status_; + } + + void setEventHandler(SocketEventHandler eventHandler) { + eventHandler_ = std::move(eventHandler); + } + + void setMessageHandler(SocketMessageHandler messageHandler) { + messageHandler_ = std::move(messageHandler); + } + + virtual bool connect(FlipperConnectionManager* manager) = 0; + virtual void disconnect() = 0; + + virtual void send( + const folly::dynamic& message, + SocketSendHandler completion) = 0; + virtual void send( + const std::string& message, + SocketSendHandler completion) = 0; + virtual void sendExpectResponse( + const std::string& message, + SocketSendExpectResponseHandler completion) = 0; + + protected: + FlipperConnectionEndpoint endpoint_; + std::unique_ptr payload_; + folly::EventBase* eventBase_; + ConnectionContextStore* connectionContextStore_; + + SocketEventHandler eventHandler_; + SocketMessageHandler messageHandler_; + std::atomic status_; +}; + +} // namespace flipper +} // namespace facebook + +#endif diff --git a/xplat/FlipperWebSocket/FlipperWebSocket.cpp b/xplat/FlipperWebSocket/FlipperWebSocket.cpp new file mode 100644 index 000000000..2d74afbfe --- /dev/null +++ b/xplat/FlipperWebSocket/FlipperWebSocket.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#ifdef FB_SONARKIT_ENABLED + +#include "FlipperWebSocket.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "WebSocketClient.h" +#include "WebSocketTLSClient.h" + +namespace facebook { +namespace flipper { + +FlipperWebSocket::FlipperWebSocket( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + folly::EventBase* eventBase) + : FlipperWebSocket( + std::move(endpoint), + std::move(payload), + eventBase, + nullptr) {} + +FlipperWebSocket::FlipperWebSocket( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + folly::EventBase* eventBase, + ConnectionContextStore* connectionContextStore) { + if (endpoint.secure) { + socket_ = std::make_unique( + endpoint, std::move(payload), eventBase, connectionContextStore); + } else { + socket_ = std::make_unique( + endpoint, std::move(payload), eventBase, connectionContextStore); + } +} + +FlipperWebSocket::~FlipperWebSocket() {} + +void FlipperWebSocket::setEventHandler(SocketEventHandler eventHandler) { + socket_->setEventHandler(eventHandler); +} +void FlipperWebSocket::setMessageHandler(SocketMessageHandler messageHandler) { + socket_->setMessageHandler(messageHandler); +} + +bool FlipperWebSocket::connect(FlipperConnectionManager* manager) { + return socket_->connect(manager); +} + +void FlipperWebSocket::disconnect() { + socket_->disconnect(); +} + +void FlipperWebSocket::send( + const folly::dynamic& message, + SocketSendHandler completion) { + socket_->send(message, completion); +} + +void FlipperWebSocket::send( + const std::string& message, + SocketSendHandler completion) { + socket_->send(message, completion); +} + +/** + Only ever used for insecure connections to receive the device_id from a + signCertificate request. If the intended usage ever changes, then a better + approach needs to be put in place. + */ +void FlipperWebSocket::sendExpectResponse( + const std::string& message, + SocketSendExpectResponseHandler completion) { + socket_->sendExpectResponse(message, completion); +} + +} // namespace flipper +} // namespace facebook + +#endif diff --git a/xplat/FlipperWebSocket/FlipperWebSocket.h b/xplat/FlipperWebSocket/FlipperWebSocket.h new file mode 100644 index 000000000..ebe202004 --- /dev/null +++ b/xplat/FlipperWebSocket/FlipperWebSocket.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#ifdef FB_SONARKIT_ENABLED + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include "BaseClient.h" + +namespace facebook { +namespace flipper { + +class FlipperConnectionManager; +class ConnectionContextStore; +class FlipperWebSocket : public FlipperSocket { + public: + FlipperWebSocket( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + folly::EventBase* eventBase); + FlipperWebSocket( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + folly::EventBase* eventBase, + ConnectionContextStore* connectionContextStore); + + virtual ~FlipperWebSocket(); + + 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: + std::unique_ptr socket_; +}; + +class FlipperWebSocketProvider : public FlipperSocketProvider { + public: + FlipperWebSocketProvider() {} + virtual std::unique_ptr create( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + folly::EventBase* eventBase) override { + return std::make_unique( + std::move(endpoint), std::move(payload), eventBase); + } + virtual std::unique_ptr create( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + folly::EventBase* eventBase, + ConnectionContextStore* connectionContextStore) override { + return std::make_unique( + std::move(endpoint), + std::move(payload), + eventBase, + connectionContextStore); + } +}; + +} // namespace flipper +} // namespace facebook + +#endif diff --git a/xplat/FlipperWebSocket/WebSocketClient.cpp b/xplat/FlipperWebSocket/WebSocketClient.cpp new file mode 100644 index 000000000..5e85007ab --- /dev/null +++ b/xplat/FlipperWebSocket/WebSocketClient.cpp @@ -0,0 +1,249 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#ifdef FB_SONARKIT_ENABLED + +#include "WebSocketClient.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace facebook { +namespace flipper { + +WebSocketClient::WebSocketClient( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + folly::EventBase* eventBase) + : WebSocketClient( + std::move(endpoint), + std::move(payload), + eventBase, + nullptr) {} + +WebSocketClient::WebSocketClient( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + folly::EventBase* eventBase, + ConnectionContextStore* connectionContextStore) + : BaseClient( + std::move(endpoint), + std::move(payload), + eventBase, + connectionContextStore) { + status_ = Status::Unconnected; + + socket_.clear_access_channels(websocketpp::log::alevel::all); + socket_.clear_error_channels(websocketpp::log::elevel::all); + socket_.init_asio(); + socket_.start_perpetual(); + + thread_ = websocketpp::lib::make_shared( + &SocketClient::run, &socket_); +} + +WebSocketClient::~WebSocketClient() { + disconnect(); +} + +bool WebSocketClient::connect(FlipperConnectionManager* manager) { + if (status_ != Status::Unconnected) { + return false; + } + + status_ = Status::Connecting; + + std::string connectionURL = endpoint_.secure ? "wss://" : "ws://"; + connectionURL += endpoint_.host; + connectionURL += ":"; + connectionURL += std::to_string(endpoint_.port); + + auto serializer = URLSerializer{}; + payload_->serialize(serializer); + auto payload = serializer.serialize(); + + if (payload.size()) { + connectionURL += "/?"; + connectionURL += payload; + } + + auto uri = websocketpp::lib::make_shared(connectionURL); + websocketpp::lib::error_code ec; + connection_ = socket_.get_connection(uri, ec); + + if (ec) { + status_ = Status::Failed; + return false; + } + + handle_ = connection_->get_handle(); + + connection_->set_open_handler(websocketpp::lib::bind( + &WebSocketClient::onOpen, + this, + &socket_, + websocketpp::lib::placeholders::_1)); + + connection_->set_message_handler(websocketpp::lib::bind( + &WebSocketClient::onMessage, + this, + &socket_, + websocketpp::lib::placeholders::_1, + websocketpp::lib::placeholders::_2)); + + connection_->set_fail_handler(websocketpp::lib::bind( + &WebSocketClient::onFail, + this, + &socket_, + websocketpp::lib::placeholders::_1)); + + connection_->set_close_handler(websocketpp::lib::bind( + &WebSocketClient::onClose, + this, + &socket_, + websocketpp::lib::placeholders::_1)); + + auto connected = connected_.get_future(); + + socket_.connect(connection_); + + auto state = connected.wait_for(std::chrono::seconds(10)); + if (state == std::future_status::ready) { + return connected.get(); + } + + disconnect(); + + return false; +} + +void WebSocketClient::disconnect() { + socket_.stop_perpetual(); + + if (status_ == Status::Connecting || status_ == Status::Open || + status_ == Status::Failed) { + websocketpp::lib::error_code ec; + socket_.close(handle_, websocketpp::close::status::going_away, "", ec); + } + socket_.stop(); + status_ = Status::Closed; + if (thread_ && thread_->joinable()) { + thread_->join(); + } + thread_ = nullptr; + eventBase_->add( + [eventHandler = eventHandler_]() { eventHandler(SocketEvent::CLOSE); }); +} + +void WebSocketClient::send( + const folly::dynamic& message, + SocketSendHandler completion) { + std::string json = folly::toJson(message); + send(json, std::move(completion)); +} + +void WebSocketClient::send( + const std::string& message, + SocketSendHandler completion) { + websocketpp::lib::error_code ec; + socket_.send( + handle_, + &message[0], + message.size(), + websocketpp::frame::opcode::text, + ec); + completion(); +} + +/** + Only ever used for insecure connections to receive the device_id from a + signCertificate request. If the intended usage ever changes, then a better + approach needs to be put in place. + */ +void WebSocketClient::sendExpectResponse( + const std::string& message, + SocketSendExpectResponseHandler completion) { + connection_->set_message_handler( + [completion, eventBase = eventBase_]( + websocketpp::connection_hdl hdl, SocketClient::message_ptr msg) { + const std::string& payload = msg->get_payload(); + eventBase->add([completion, payload] { completion(payload, false); }); + }); + websocketpp::lib::error_code ec; + socket_.send( + handle_, + &message[0], + message.size(), + websocketpp::frame::opcode::text, + ec); + if (ec) { + auto reason = ec.message(); + completion(reason, true); + } +} + +void WebSocketClient::onOpen(SocketClient* c, websocketpp::connection_hdl hdl) { + if (status_ == Status::Connecting) { + connected_.set_value(true); + } + + status_ = Status::Initializing; + eventBase_->add( + [eventHandler = eventHandler_]() { eventHandler(SocketEvent::OPEN); }); +} + +void WebSocketClient::onMessage( + SocketClient* c, + websocketpp::connection_hdl hdl, + SocketClient::message_ptr msg) { + const std::string& payload = msg->get_payload(); + if (messageHandler_) { + eventBase_->add([payload, messageHandler = messageHandler_]() { + messageHandler(payload); + }); + } +} + +void WebSocketClient::onFail(SocketClient* c, websocketpp::connection_hdl hdl) { + SocketClient::connection_ptr con = c->get_con_from_hdl(hdl); + auto server = con->get_response_header("Server"); + auto reason = con->get_ec().message(); + + if (status_ == Status::Connecting) { + connected_.set_value(false); + } + status_ = Status::Failed; + eventBase_->add( + [eventHandler = eventHandler_]() { eventHandler(SocketEvent::ERROR); }); +} + +void WebSocketClient::onClose( + SocketClient* c, + websocketpp::connection_hdl hdl) { + status_ = Status::Closed; + eventBase_->add( + [eventHandler = eventHandler_]() { eventHandler(SocketEvent::CLOSE); }); +} + +} // namespace flipper +} // namespace facebook + +#endif diff --git a/xplat/FlipperWebSocket/WebSocketClient.h b/xplat/FlipperWebSocket/WebSocketClient.h new file mode 100644 index 000000000..d08a915c7 --- /dev/null +++ b/xplat/FlipperWebSocket/WebSocketClient.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#ifdef FB_SONARKIT_ENABLED + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include "BaseClient.h" + +#include +#include + +namespace facebook { +namespace flipper { + +typedef websocketpp::client SocketClient; +typedef websocketpp::lib::shared_ptr SocketThread; + +class FlipperConnectionManager; +class ConnectionContextStore; +class WebSocketClient : public BaseClient { + public: + WebSocketClient( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + folly::EventBase* eventBase); + WebSocketClient( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + folly::EventBase* eventBase, + ConnectionContextStore* connectionContextStore); + + WebSocketClient(const WebSocketClient&) = delete; + WebSocketClient& operator=(const WebSocketClient&) = delete; + + virtual ~WebSocketClient(); + + 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: + void onOpen(SocketClient*, websocketpp::connection_hdl); + void onMessage( + SocketClient*, + websocketpp::connection_hdl, + SocketClient::message_ptr); + void onFail(SocketClient*, websocketpp::connection_hdl); + void onClose(SocketClient*, websocketpp::connection_hdl); + + SocketClient socket_; + SocketClient::connection_ptr connection_; + + SocketThread thread_; + websocketpp::connection_hdl handle_; + std::promise connected_; +}; + +} // namespace flipper +} // namespace facebook + +#endif diff --git a/xplat/FlipperWebSocket/WebSocketTLSClient.cpp b/xplat/FlipperWebSocket/WebSocketTLSClient.cpp new file mode 100644 index 000000000..0b77243f9 --- /dev/null +++ b/xplat/FlipperWebSocket/WebSocketTLSClient.cpp @@ -0,0 +1,313 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#ifdef FB_SONARKIT_ENABLED + +#include "WebSocketTLSClient.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace facebook { +namespace flipper { + +WebSocketTLSClient::WebSocketTLSClient( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + folly::EventBase* eventBase) + : WebSocketTLSClient( + std::move(endpoint), + std::move(payload), + eventBase, + nullptr) {} + +WebSocketTLSClient::WebSocketTLSClient( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + folly::EventBase* eventBase, + ConnectionContextStore* connectionContextStore) + : BaseClient( + std::move(endpoint), + std::move(payload), + eventBase, + connectionContextStore) { + status_ = Status::Unconnected; + + socket_.clear_access_channels(websocketpp::log::alevel::all); + socket_.clear_error_channels(websocketpp::log::elevel::all); + socket_.init_asio(); + socket_.start_perpetual(); + + thread_ = websocketpp::lib::make_shared( + &SocketTLSClient::run, &socket_); +} + +WebSocketTLSClient::~WebSocketTLSClient() { + disconnect(); +} + +bool WebSocketTLSClient::connect(FlipperConnectionManager* manager) { + if (status_ != Status::Unconnected) { + return false; + } + + status_ = Status::Connecting; + + std::string connectionURL = endpoint_.secure ? "wss://" : "ws://"; + connectionURL += endpoint_.host; + connectionURL += ":"; + connectionURL += std::to_string(endpoint_.port); + + auto serializer = URLSerializer{}; + payload_->serialize(serializer); + auto payload = serializer.serialize(); + + if (payload.size()) { + connectionURL += "/?"; + connectionURL += payload; + } + + socket_.set_tls_init_handler(bind( + &WebSocketTLSClient::onTLSInit, + this, + endpoint_.host.c_str(), + websocketpp::lib::placeholders::_1)); + + auto uri = websocketpp::lib::make_shared(connectionURL); + websocketpp::lib::error_code ec; + connection_ = socket_.get_connection(uri, ec); + + if (ec) { + status_ = Status::Failed; + return false; + } + + handle_ = connection_->get_handle(); + + connection_->set_open_handler(websocketpp::lib::bind( + &WebSocketTLSClient::onOpen, + this, + &socket_, + websocketpp::lib::placeholders::_1)); + + connection_->set_message_handler(websocketpp::lib::bind( + &WebSocketTLSClient::onMessage, + this, + &socket_, + websocketpp::lib::placeholders::_1, + websocketpp::lib::placeholders::_2)); + + connection_->set_fail_handler(websocketpp::lib::bind( + &WebSocketTLSClient::onFail, + this, + &socket_, + websocketpp::lib::placeholders::_1)); + + connection_->set_close_handler(websocketpp::lib::bind( + &WebSocketTLSClient::onClose, + this, + &socket_, + websocketpp::lib::placeholders::_1)); + + auto connected = connected_.get_future(); + + socket_.connect(connection_); + + auto state = connected.wait_for(std::chrono::seconds(10)); + if (state == std::future_status::ready) { + return connected.get(); + } + + disconnect(); + + return false; +} + +void WebSocketTLSClient::disconnect() { + socket_.stop_perpetual(); + + if (status_ == Status::Connecting || status_ == Status::Open || + status_ == Status::Failed) { + websocketpp::lib::error_code ec; + socket_.close(handle_, websocketpp::close::status::going_away, "", ec); + } + socket_.stop(); + status_ = Status::Closed; + if (thread_ && thread_->joinable()) { + thread_->join(); + } + + thread_ = nullptr; + eventBase_->add( + [eventHandler = eventHandler_]() { eventHandler(SocketEvent::CLOSE); }); +} + +void WebSocketTLSClient::send( + const folly::dynamic& message, + SocketSendHandler completion) { + std::string json = folly::toJson(message); + send(json, std::move(completion)); +} + +void WebSocketTLSClient::send( + const std::string& message, + SocketSendHandler completion) { + websocketpp::lib::error_code ec; + socket_.send( + handle_, + &message[0], + message.size(), + websocketpp::frame::opcode::text, + ec); + completion(); +} + +/** + Only ever used for insecure connections to receive the device_id from a + signCertificate request. If the intended usage ever changes, then a better + approach needs to be put in place. + */ +void WebSocketTLSClient::sendExpectResponse( + const std::string& message, + SocketSendExpectResponseHandler completion) { + connection_->set_message_handler( + [completion, eventBase = eventBase_]( + websocketpp::connection_hdl hdl, SocketTLSClient::message_ptr msg) { + const std::string& payload = msg->get_payload(); + eventBase->add([completion, payload] { completion(payload, false); }); + }); + websocketpp::lib::error_code ec; + socket_.send( + handle_, + &message[0], + message.size(), + websocketpp::frame::opcode::text, + ec); + if (ec) { + auto reason = ec.message(); + completion(reason, true); + } +} + +void WebSocketTLSClient::onOpen( + SocketTLSClient* c, + websocketpp::connection_hdl hdl) { + if (status_ == Status::Connecting) { + connected_.set_value(true); + } + + status_ = Status::Initializing; + eventBase_->add( + [eventHandler = eventHandler_]() { eventHandler(SocketEvent::OPEN); }); +} + +void WebSocketTLSClient::onMessage( + SocketTLSClient* c, + websocketpp::connection_hdl hdl, + SocketTLSClient::message_ptr msg) { + const std::string& payload = msg->get_payload(); + if (messageHandler_) { + eventBase_->add([payload, messageHandler = messageHandler_]() { + messageHandler(payload); + }); + } +} + +void WebSocketTLSClient::onFail( + SocketTLSClient* c, + websocketpp::connection_hdl hdl) { + SocketTLSClient::connection_ptr con = c->get_con_from_hdl(hdl); + auto server = con->get_response_header("Server"); + auto reason = con->get_ec().message(); + auto sslError = + (reason.find("TLS handshake failed") != std::string::npos || + reason.find("Generic TLS related error") != std::string::npos); + + if (status_ == Status::Connecting) { + if (sslError) { + try { + connected_.set_exception( + std::make_exception_ptr(folly::AsyncSocketException( + folly::AsyncSocketException::SSL_ERROR, + "SSL handshake failed"))); + } catch (...) { + // set_exception() may throw an exception + // In that case, just set the value to false. + connected_.set_value(false); + } + } else { + connected_.set_value(false); + } + } + status_ = Status::Failed; + eventBase_->add([eventHandler = eventHandler_, sslError]() { + if (sslError) { + eventHandler(SocketEvent::SSL_ERROR); + } else { + eventHandler(SocketEvent::ERROR); + } + }); +} + +void WebSocketTLSClient::onClose( + SocketTLSClient* c, + websocketpp::connection_hdl hdl) { + status_ = Status::Closed; + eventBase_->add( + [eventHandler = eventHandler_]() { eventHandler(SocketEvent::CLOSE); }); +} + +SocketTLSContext WebSocketTLSClient::onTLSInit( + const char* hostname, + websocketpp::connection_hdl) { + SocketTLSContext ctx = + websocketpp::lib::make_shared( + boost::asio::ssl::context::sslv23); + + ctx->set_options( + boost::asio::ssl::context::default_workarounds | + boost::asio::ssl::context::no_sslv2 | + boost::asio::ssl::context::no_sslv3 | + boost::asio::ssl::context::single_dh_use); + + ctx->set_verify_mode(boost::asio::ssl::verify_peer); + ctx->load_verify_file(connectionContextStore_->getPath( + ConnectionContextStore::StoreItem::FLIPPER_CA)); + + boost::system::error_code error; + ctx->use_certificate_file( + connectionContextStore_->getPath( + ConnectionContextStore::StoreItem::CLIENT_CERT), + boost::asio::ssl::context::pem, + error); + ctx->use_private_key_file( + connectionContextStore_->getPath( + ConnectionContextStore::StoreItem::PRIVATE_KEY), + boost::asio::ssl::context::pem, + error); + + return ctx; +} + +} // namespace flipper +} // namespace facebook + +#endif diff --git a/xplat/FlipperWebSocket/WebSocketTLSClient.h b/xplat/FlipperWebSocket/WebSocketTLSClient.h new file mode 100644 index 000000000..69dfa0abd --- /dev/null +++ b/xplat/FlipperWebSocket/WebSocketTLSClient.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#ifdef FB_SONARKIT_ENABLED + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include "BaseClient.h" + +#include +#include +#include + +namespace facebook { +namespace flipper { + +typedef websocketpp::client + SocketTLSClient; +typedef websocketpp::lib::shared_ptr SocketTLSThread; +typedef websocketpp::lib::shared_ptr + SocketTLSContext; + +class FlipperConnectionManager; +class ConnectionContextStore; +class WebSocketTLSClient : public BaseClient { + public: + WebSocketTLSClient( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + folly::EventBase* eventBase); + WebSocketTLSClient( + FlipperConnectionEndpoint endpoint, + std::unique_ptr payload, + folly::EventBase* eventBase, + ConnectionContextStore* connectionContextStore); + + WebSocketTLSClient(const WebSocketTLSClient&) = delete; + WebSocketTLSClient& operator=(const WebSocketTLSClient&) = delete; + + virtual ~WebSocketTLSClient(); + + 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: + void onOpen(SocketTLSClient*, websocketpp::connection_hdl); + void onMessage( + SocketTLSClient*, + websocketpp::connection_hdl, + SocketTLSClient::message_ptr); + void onFail(SocketTLSClient*, websocketpp::connection_hdl); + void onClose(SocketTLSClient*, websocketpp::connection_hdl); + SocketTLSContext onTLSInit(const char*, websocketpp::connection_hdl); + + SocketTLSClient socket_; + SocketTLSClient::connection_ptr connection_; + + SocketTLSThread thread_; + websocketpp::connection_hdl handle_; + std::promise connected_; +}; + +} // namespace flipper +} // namespace facebook + +#endif