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:
4
xplat/AndroidManifest.xml
Normal file
4
xplat/AndroidManifest.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.facebook.xplat.sonar">
|
||||
</manifest>
|
||||
60
xplat/CMakeLists.txt
Normal file
60
xplat/CMakeLists.txt
Normal file
@@ -0,0 +1,60 @@
|
||||
cmake_minimum_required (VERSION 3.6.0)
|
||||
project(sonarcpp CXX)
|
||||
set(CMAKE_VERBOSE_MAKEFILE on)
|
||||
set(PACKAGE_NAME sonarcpp)
|
||||
|
||||
set(third_party_ndk ../android/build/third-party-ndk)
|
||||
set(libfolly_DIR ${third_party_ndk}/folly/)
|
||||
set(easywsclient_DIR ../libs/)
|
||||
set(glog_DIR ${third_party_ndk}/glog)
|
||||
set(BOOST_DIR ${third_party_ndk}/boost/boost_1_63_0/)
|
||||
|
||||
set(DOUBLECONVERSION_DIR ${third_party_ndk}/double-conversion/double-conversion-3.0.0/)
|
||||
|
||||
list(APPEND dir_list ./)
|
||||
list(APPEND dir_list ./Sonar)
|
||||
include_directories(${dir_list})
|
||||
|
||||
add_compile_options(-DFOLLY_NO_CONFIG
|
||||
-DFB_SONARKIT_ENABLED=1
|
||||
-DFOLLY_HAVE_MEMRCHR
|
||||
-DFOLLY_MOBILE=1
|
||||
-DFOLLY_USE_LIBCPP=1
|
||||
-DFOLLY_HAVE_LIBJEMALLOC=0
|
||||
-DFOLLY_HAVE_PREADV=0
|
||||
-frtti
|
||||
-fexceptions
|
||||
-std=c++14
|
||||
-Wno-error
|
||||
-Wno-unused-local-typedefs
|
||||
-Wno-unused-variable
|
||||
-Wno-sign-compare
|
||||
-Wno-comment
|
||||
-Wno-return-type
|
||||
-Wno-tautological-constant-compare
|
||||
)
|
||||
|
||||
|
||||
file(GLOB SOURCES Sonar/*.cpp)
|
||||
add_library(${PACKAGE_NAME} SHARED ${SOURCES})
|
||||
|
||||
set(build_DIR ${CMAKE_SOURCE_DIR}/build)
|
||||
set(libfolly_build_DIR ${build_DIR}/libfolly/${ANDROID_ABI})
|
||||
set(easywsclient_build_DIR ${build_DIR}/easywsclient/${ANDROID_ABI})
|
||||
|
||||
file(MAKE_DIRECTORY ${build_DIR})
|
||||
|
||||
add_subdirectory(${libfolly_DIR} ${libfolly_build_DIR})
|
||||
add_subdirectory(${easywsclient_DIR}/easywsclient ${easywsclient_build_DIR})
|
||||
|
||||
target_include_directories(${PACKAGE_NAME} PRIVATE
|
||||
${libfolly_DIR}
|
||||
${BOOST_DIR}
|
||||
${BOOST_DIR}/../
|
||||
${glog_DIR}
|
||||
${glog_DIR}/../
|
||||
${glog_DIR}/glog-0.3.5/src/
|
||||
${easywsclient_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(${PACKAGE_NAME} folly easywsclient glog double-conversion log)
|
||||
223
xplat/Sonar/CertificateUtils.cpp
Normal file
223
xplat/Sonar/CertificateUtils.cpp
Normal file
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#include "CertificateUtils.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <sys/stat.h>
|
||||
#include <cstring>
|
||||
|
||||
void free(
|
||||
EVP_PKEY* pKey,
|
||||
X509_REQ* x509_req,
|
||||
BIGNUM* bne,
|
||||
BIO* privateKey,
|
||||
BIO* csrBio);
|
||||
|
||||
bool generateCertSigningRequest(
|
||||
const char* appId,
|
||||
const char* csrFile,
|
||||
const char* privateKeyFile) {
|
||||
int ret = 0;
|
||||
BIGNUM* bne = NULL;
|
||||
|
||||
int nVersion = 1;
|
||||
int bits = 2048;
|
||||
|
||||
// Using 65537 as exponent
|
||||
unsigned long e = RSA_F4;
|
||||
|
||||
X509_NAME* x509_name = NULL;
|
||||
|
||||
const char* subjectCountry = "US";
|
||||
const char* subjectProvince = "CA";
|
||||
const char* subjectCity = "Menlo Park";
|
||||
const char* subjectOrganization = "Sonar";
|
||||
const char* subjectCommon = appId;
|
||||
|
||||
X509_REQ* x509_req = X509_REQ_new();
|
||||
EVP_PKEY* pKey = EVP_PKEY_new();
|
||||
RSA* rsa = RSA_new();
|
||||
BIO* privateKey = NULL;
|
||||
BIO* csrBio = NULL;
|
||||
|
||||
EVP_PKEY_assign_RSA(pKey, rsa);
|
||||
|
||||
// Generate rsa key
|
||||
bne = BN_new();
|
||||
BN_set_flags(bne, BN_FLG_CONSTTIME);
|
||||
ret = BN_set_word(bne, e);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = RSA_generate_key_ex(rsa, bits, bne, NULL);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
{
|
||||
// Write private key to a file
|
||||
int privateKeyFd =
|
||||
open(privateKeyFile, O_CREAT | O_WRONLY, S_IWUSR | S_IRUSR);
|
||||
if (privateKeyFd < 0) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return -1;
|
||||
}
|
||||
FILE* privateKeyFp = fdopen(privateKeyFd, "w");
|
||||
if (privateKeyFp == NULL) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return -1;
|
||||
}
|
||||
privateKey = BIO_new_fp(privateKeyFp, BIO_CLOSE);
|
||||
ret =
|
||||
PEM_write_bio_RSAPrivateKey(privateKey, rsa, NULL, NULL, 0, NULL, NULL);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
rsa = NULL;
|
||||
|
||||
ret = BIO_flush(privateKey);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = X509_REQ_set_version(x509_req, nVersion);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
x509_name = X509_REQ_get_subject_name(x509_req);
|
||||
|
||||
ret = X509_NAME_add_entry_by_txt(
|
||||
x509_name,
|
||||
"C",
|
||||
MBSTRING_ASC,
|
||||
(const unsigned char*)subjectCountry,
|
||||
-1,
|
||||
-1,
|
||||
0);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = X509_NAME_add_entry_by_txt(
|
||||
x509_name,
|
||||
"ST",
|
||||
MBSTRING_ASC,
|
||||
(const unsigned char*)subjectProvince,
|
||||
-1,
|
||||
-1,
|
||||
0);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = X509_NAME_add_entry_by_txt(
|
||||
x509_name,
|
||||
"L",
|
||||
MBSTRING_ASC,
|
||||
(const unsigned char*)subjectCity,
|
||||
-1,
|
||||
-1,
|
||||
0);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = X509_NAME_add_entry_by_txt(
|
||||
x509_name,
|
||||
"O",
|
||||
MBSTRING_ASC,
|
||||
(const unsigned char*)subjectOrganization,
|
||||
-1,
|
||||
-1,
|
||||
0);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = X509_NAME_add_entry_by_txt(
|
||||
x509_name,
|
||||
"CN",
|
||||
MBSTRING_ASC,
|
||||
(const unsigned char*)subjectCommon,
|
||||
-1,
|
||||
-1,
|
||||
0);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = X509_REQ_set_pubkey(x509_req, pKey);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = X509_REQ_sign(
|
||||
x509_req, pKey, EVP_sha256()); // returns x509_req->signature->length
|
||||
if (ret <= 0) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
{
|
||||
// Write CSR to a file
|
||||
int csrFd = open(csrFile, O_CREAT | O_WRONLY, S_IWUSR | S_IRUSR);
|
||||
if (csrFd < 0) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return -1;
|
||||
}
|
||||
FILE* csrFp = fdopen(csrFd, "w");
|
||||
if (csrFp == NULL) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return -1;
|
||||
}
|
||||
csrBio = BIO_new_fp(csrFp, BIO_CLOSE);
|
||||
ret = PEM_write_bio_X509_REQ(csrBio, x509_req);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = BIO_flush(csrBio);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return (ret == 1);
|
||||
}
|
||||
|
||||
void free(
|
||||
EVP_PKEY* pKey,
|
||||
X509_REQ* x509_req,
|
||||
BIGNUM* bne,
|
||||
BIO* privateKey,
|
||||
BIO* csrBio) {
|
||||
BN_free(bne);
|
||||
X509_REQ_free(x509_req);
|
||||
EVP_PKEY_free(pKey);
|
||||
BIO_free_all(privateKey);
|
||||
BIO_free_all(csrBio);
|
||||
}
|
||||
20
xplat/Sonar/CertificateUtils.h
Normal file
20
xplat/Sonar/CertificateUtils.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 CertificateUtils_hpp
|
||||
#define CertificateUtils_hpp
|
||||
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <stdio.h>
|
||||
|
||||
bool generateCertSigningRequest(
|
||||
const char* appId,
|
||||
const char* csrFile,
|
||||
const char* privateKeyFile);
|
||||
|
||||
#endif /* CertificateUtils_hpp */
|
||||
204
xplat/Sonar/SonarClient.cpp
Normal file
204
xplat/Sonar/SonarClient.cpp
Normal file
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "SonarClient.h"
|
||||
#include "SonarConnectionImpl.h"
|
||||
#include "SonarResponderImpl.h"
|
||||
#include "SonarWebSocketImpl.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/log.h>
|
||||
#define SONAR_LOG(message) \
|
||||
__android_log_print(ANDROID_LOG_INFO, "sonar", "sonar: %s", message)
|
||||
#else
|
||||
#define SONAR_LOG(message) printf("sonar: %s", message)
|
||||
#endif
|
||||
|
||||
#if FB_SONARKIT_ENABLED
|
||||
|
||||
namespace facebook {
|
||||
namespace sonar {
|
||||
|
||||
static SonarClient* kInstance;
|
||||
|
||||
using folly::dynamic;
|
||||
|
||||
void SonarClient::init(SonarInitConfig config) {
|
||||
kInstance =
|
||||
new SonarClient(std::make_unique<SonarWebSocketImpl>(std::move(config)));
|
||||
}
|
||||
|
||||
SonarClient* SonarClient::instance() {
|
||||
return kInstance;
|
||||
}
|
||||
|
||||
void SonarClient::addPlugin(std::shared_ptr<SonarPlugin> plugin) {
|
||||
SONAR_LOG(("SonarClient::addPlugin " + plugin->identifier()).c_str());
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
performAndReportError([this, plugin]() {
|
||||
if (plugins_.find(plugin->identifier()) != plugins_.end()) {
|
||||
throw std::out_of_range(
|
||||
"plugin " + plugin->identifier() + " already added.");
|
||||
}
|
||||
plugins_[plugin->identifier()] = plugin;
|
||||
if (connected_) {
|
||||
refreshPlugins();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SonarClient::removePlugin(std::shared_ptr<SonarPlugin> plugin) {
|
||||
SONAR_LOG(("SonarClient::removePlugin " + plugin->identifier()).c_str());
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
performAndReportError([this, plugin]() {
|
||||
if (plugins_.find(plugin->identifier()) == plugins_.end()) {
|
||||
throw std::out_of_range("plugin " + plugin->identifier() + " not added.");
|
||||
}
|
||||
disconnect(plugin);
|
||||
plugins_.erase(plugin->identifier());
|
||||
if (connected_) {
|
||||
refreshPlugins();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::shared_ptr<SonarPlugin> SonarClient::getPlugin(
|
||||
const std::string& identifier) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (plugins_.find(identifier) == plugins_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return plugins_.at(identifier);
|
||||
}
|
||||
|
||||
bool SonarClient::hasPlugin(const std::string& identifier) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return plugins_.find(identifier) != plugins_.end();
|
||||
}
|
||||
|
||||
void SonarClient::disconnect(std::shared_ptr<SonarPlugin> plugin) {
|
||||
const auto& conn = connections_.find(plugin->identifier());
|
||||
if (conn != connections_.end()) {
|
||||
connections_.erase(plugin->identifier());
|
||||
plugin->didDisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void SonarClient::refreshPlugins() {
|
||||
dynamic message = dynamic::object("method", "refreshPlugins");
|
||||
socket_->sendMessage(message);
|
||||
}
|
||||
|
||||
void SonarClient::onConnected() {
|
||||
SONAR_LOG("SonarClient::onConnected");
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
connected_ = true;
|
||||
}
|
||||
|
||||
void SonarClient::onDisconnected() {
|
||||
SONAR_LOG("SonarClient::onDisconnected");
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
connected_ = false;
|
||||
performAndReportError([this]() {
|
||||
for (const auto& iter : plugins_) {
|
||||
disconnect(iter.second);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SonarClient::onMessageReceived(const dynamic& message) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
performAndReportError([this, &message]() {
|
||||
const auto& method = message["method"];
|
||||
const auto& params = message.getDefault("params");
|
||||
|
||||
std::unique_ptr<SonarResponderImpl> responder;
|
||||
if (message.find("id") != message.items().end()) {
|
||||
responder.reset(
|
||||
new SonarResponderImpl(socket_.get(), message["id"].getInt()));
|
||||
}
|
||||
|
||||
if (method == "getPlugins") {
|
||||
dynamic identifiers = dynamic::array();
|
||||
for (const auto& elem : plugins_) {
|
||||
identifiers.push_back(elem.first);
|
||||
}
|
||||
dynamic response = dynamic::object("plugins", identifiers);
|
||||
responder->success(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (method == "init") {
|
||||
const auto identifier = params["plugin"].getString();
|
||||
if (plugins_.find(identifier) == plugins_.end()) {
|
||||
throw std::out_of_range(
|
||||
"plugin " + identifier + " not found for method " +
|
||||
method.getString());
|
||||
}
|
||||
const auto plugin = plugins_.at(identifier);
|
||||
auto& conn = connections_[plugin->identifier()];
|
||||
conn = std::make_shared<SonarConnectionImpl>(
|
||||
socket_.get(), plugin->identifier());
|
||||
plugin->didConnect(conn);
|
||||
return;
|
||||
}
|
||||
|
||||
if (method == "deinit") {
|
||||
const auto identifier = params["plugin"].getString();
|
||||
if (plugins_.find(identifier) == plugins_.end()) {
|
||||
throw std::out_of_range(
|
||||
"plugin " + identifier + " not found for method " +
|
||||
method.getString());
|
||||
}
|
||||
const auto plugin = plugins_.at(identifier);
|
||||
disconnect(plugin);
|
||||
return;
|
||||
}
|
||||
|
||||
if (method == "execute") {
|
||||
const auto identifier = params["api"].getString();
|
||||
if (connections_.find(identifier) == connections_.end()) {
|
||||
throw std::out_of_range(
|
||||
"connection " + identifier + " not found for method " +
|
||||
method.getString());
|
||||
}
|
||||
const auto& conn = connections_.at(params["api"].getString());
|
||||
conn->call(
|
||||
params["method"].getString(),
|
||||
params.getDefault("params"),
|
||||
std::move(responder));
|
||||
return;
|
||||
}
|
||||
|
||||
dynamic response =
|
||||
dynamic::object("message", "Received unknown method: " + method);
|
||||
responder->error(response);
|
||||
});
|
||||
}
|
||||
|
||||
void SonarClient::performAndReportError(const std::function<void()>& func) {
|
||||
try {
|
||||
func();
|
||||
} catch (std::exception& e) {
|
||||
if (connected_) {
|
||||
dynamic message = dynamic::object(
|
||||
"error",
|
||||
dynamic::object("message", e.what())("stacktrace", "<none>"));
|
||||
socket_->sendMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sonar
|
||||
} // namespace facebook
|
||||
|
||||
#endif
|
||||
87
xplat/Sonar/SonarClient.h
Normal file
87
xplat/Sonar/SonarClient.h
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Sonar/SonarConnectionImpl.h>
|
||||
#include <Sonar/SonarInitConfig.h>
|
||||
#include <Sonar/SonarPlugin.h>
|
||||
#include <Sonar/SonarWebSocket.h>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
namespace facebook {
|
||||
namespace sonar {
|
||||
|
||||
class SonarClient : public SonarWebSocket::Callbacks {
|
||||
public:
|
||||
/**
|
||||
Call before accessing instance with SonarClient::instance(). This will set up
|
||||
all the state needed to establish a Sonar connection.
|
||||
*/
|
||||
static void init(SonarInitConfig config);
|
||||
|
||||
/**
|
||||
Standard accessor for the shared SonarClient instance. This returns a
|
||||
singleton instance to a shared SonarClient. First call to this function will
|
||||
create the shared SonarClient. Must call SonarClient::initDeviceData() before
|
||||
first call to SonarClient::instance().
|
||||
*/
|
||||
static SonarClient* instance();
|
||||
|
||||
/**
|
||||
Only public for testing
|
||||
*/
|
||||
SonarClient(std::unique_ptr<SonarWebSocket> socket)
|
||||
: socket_(std::move(socket)) {
|
||||
socket_->setCallbacks(this);
|
||||
}
|
||||
|
||||
void start() {
|
||||
socket_->start();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
socket_->stop();
|
||||
}
|
||||
|
||||
void onConnected() override;
|
||||
|
||||
void onDisconnected() override;
|
||||
|
||||
void onMessageReceived(const folly::dynamic& message) override;
|
||||
|
||||
void addPlugin(std::shared_ptr<SonarPlugin> plugin);
|
||||
|
||||
void removePlugin(std::shared_ptr<SonarPlugin> plugin);
|
||||
|
||||
void refreshPlugins();
|
||||
|
||||
std::shared_ptr<SonarPlugin> getPlugin(const std::string& identifier);
|
||||
|
||||
template <typename P>
|
||||
std::shared_ptr<P> getPlugin(const std::string& identifier) {
|
||||
return std::static_pointer_cast<P>(getPlugin(identifier));
|
||||
}
|
||||
|
||||
bool hasPlugin(const std::string& identifier);
|
||||
|
||||
private:
|
||||
static SonarClient* instance_;
|
||||
bool connected_ = false;
|
||||
std::unique_ptr<SonarWebSocket> socket_;
|
||||
std::map<std::string, std::shared_ptr<SonarPlugin>> plugins_;
|
||||
std::map<std::string, std::shared_ptr<SonarConnectionImpl>> connections_;
|
||||
std::mutex mutex_;
|
||||
|
||||
void performAndReportError(const std::function<void()>& func);
|
||||
void disconnect(std::shared_ptr<SonarPlugin> plugin);
|
||||
};
|
||||
|
||||
} // namespace sonar
|
||||
} // namespace facebook
|
||||
54
xplat/Sonar/SonarConnection.h
Normal file
54
xplat/Sonar/SonarConnection.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Sonar/SonarResponder.h>
|
||||
#include <folly/json.h>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace facebook {
|
||||
namespace sonar {
|
||||
|
||||
/**
|
||||
Represents a connection between the Desktop and mobile plugins
|
||||
with corresponding identifiers.
|
||||
*/
|
||||
class SonarConnection {
|
||||
public:
|
||||
using SonarReceiver = std::function<
|
||||
void(const folly::dynamic&, std::unique_ptr<SonarResponder>)>;
|
||||
|
||||
virtual ~SonarConnection() {}
|
||||
|
||||
/**
|
||||
Invoke a method on the Sonar desktop plugin with with a matching identifier.
|
||||
*/
|
||||
virtual void send(
|
||||
const std::string& method,
|
||||
const folly::dynamic& params) = 0;
|
||||
|
||||
/**
|
||||
Report an error to the Sonar desktop app
|
||||
*/
|
||||
virtual void error(
|
||||
const std::string& message,
|
||||
const std::string& stacktrace) = 0;
|
||||
|
||||
/**
|
||||
Register a receiver to be notified of incoming calls of the given
|
||||
method from the Sonar desktop plugin with a matching identifier.
|
||||
*/
|
||||
virtual void receive(
|
||||
const std::string& method,
|
||||
const SonarReceiver& receiver) = 0;
|
||||
};
|
||||
|
||||
} // namespace sonar
|
||||
} // namespace facebook
|
||||
61
xplat/Sonar/SonarConnectionImpl.h
Normal file
61
xplat/Sonar/SonarConnectionImpl.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Sonar/SonarConnection.h>
|
||||
#include <Sonar/SonarWebSocket.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace facebook {
|
||||
namespace sonar {
|
||||
|
||||
class SonarConnectionImpl : public SonarConnection {
|
||||
public:
|
||||
SonarConnectionImpl(SonarWebSocket* socket, const std::string& name)
|
||||
: socket_(socket), name_(name) {}
|
||||
|
||||
void call(
|
||||
const std::string& method,
|
||||
const folly::dynamic& params,
|
||||
std::unique_ptr<SonarResponder> responder) {
|
||||
if (receivers_.find(method) == receivers_.end()) {
|
||||
throw std::out_of_range("receiver " + method + " not found.");
|
||||
}
|
||||
receivers_.at(method)(params, std::move(responder));
|
||||
}
|
||||
|
||||
void send(const std::string& method, const folly::dynamic& params) override {
|
||||
folly::dynamic message = folly::dynamic::object("method", "execute")(
|
||||
"params",
|
||||
folly::dynamic::object("api", name_)("method", method)(
|
||||
"params", params));
|
||||
socket_->sendMessage(message);
|
||||
}
|
||||
|
||||
void error(const std::string& message, const std::string& stacktrace)
|
||||
override {
|
||||
socket_->sendMessage(folly::dynamic::object(
|
||||
"error",
|
||||
folly::dynamic::object("message", message)("stacktrace", stacktrace)));
|
||||
}
|
||||
|
||||
void receive(const std::string& method, const SonarReceiver& receiver)
|
||||
override {
|
||||
receivers_[method] = receiver;
|
||||
}
|
||||
|
||||
private:
|
||||
SonarWebSocket* socket_;
|
||||
std::string name_;
|
||||
std::map<std::string, SonarReceiver> receivers_;
|
||||
};
|
||||
|
||||
} // namespace sonar
|
||||
} // namespace facebook
|
||||
40
xplat/Sonar/SonarInitConfig.h
Normal file
40
xplat/Sonar/SonarInitConfig.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <folly/io/async/EventBase.h>
|
||||
#include <map>
|
||||
|
||||
namespace facebook {
|
||||
namespace sonar {
|
||||
|
||||
struct DeviceData {
|
||||
std::string host;
|
||||
std::string os;
|
||||
std::string device;
|
||||
std::string deviceId;
|
||||
std::string app;
|
||||
std::string appId;
|
||||
std::string privateAppDirectory;
|
||||
};
|
||||
|
||||
struct SonarInitConfig {
|
||||
/**
|
||||
Map of client specific configuration data such as app name, device name, etc.
|
||||
*/
|
||||
DeviceData deviceData;
|
||||
|
||||
/**
|
||||
EventBase on which client callbacks should be called.
|
||||
*/
|
||||
folly::EventBase* worker;
|
||||
};
|
||||
|
||||
} // namespace sonar
|
||||
} // namespace facebook
|
||||
33
xplat/Sonar/SonarKitCPP.podspec
Normal file
33
xplat/Sonar/SonarKitCPP.podspec
Normal file
@@ -0,0 +1,33 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = 'Sonar'
|
||||
spec.version = '1.0.0'
|
||||
spec.license = { :type => 'MIT' }
|
||||
spec.homepage = 'https://github.com/facebook/sonar'
|
||||
spec.summary = 'SonarKit core cpp code with network implementation'
|
||||
spec.authors = 'Facebook'
|
||||
# spec.prepare_command = 'mv src double-conversion'
|
||||
spec.source = { :git => 'https://github.com/facebook/Sonar.git',
|
||||
:branch => 'master' }
|
||||
spec.module_name = 'Sonar'
|
||||
spec.public_header_files = 'xplat/Sonar/*.h'
|
||||
spec.source_files = 'xplat/Sonar/*.{h,cpp,m,mm}'
|
||||
spec.libraries = "stdc++"
|
||||
spec.dependency 'Folly'
|
||||
spec.dependency 'EasyWSClient'
|
||||
# spec.dependency 'boost-for-react-native'
|
||||
#
|
||||
# spec.dependency 'DoubleConversion'
|
||||
# spec.dependency 'Folly'
|
||||
# spec.dependency 'glog'
|
||||
spec.compiler_flags = '-DFB_SONARKIT_ENABLED=1 -DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_HAVE_LIBGFLAGS=0 -DFOLLY_HAVE_LIBJEMALLOC=0 -DFOLLY_HAVE_PREADV=0 -DFOLLY_HAVE_PWRITEV=0 -DFOLLY_HAVE_TFO=0 -DFOLLY_USE_SYMBOLIZER=0 -Wall
|
||||
-std=c++14
|
||||
-Wno-global-constructors'
|
||||
# spec.header_mappings_dir = 'folly'
|
||||
# spec.header_dir = 'folly'
|
||||
# spec.preserve_paths = 'xplat/**/*'
|
||||
# Pinning to the same version as React.podspec.
|
||||
spec.platforms = { :ios => "8.0", :tvos => "9.2" }
|
||||
spec.pod_target_xcconfig = { "USE_HEADERMAP" => "NO",
|
||||
"CLANG_CXX_LANGUAGE_STANDARD" => "c++14",
|
||||
"HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)\" \"$(PODS_ROOT)/boost-for-react-native\" \"$(PODS_ROOT)/EasyWSClient\" \"$(PODS_ROOT)/DoubleConversion\"" }
|
||||
end
|
||||
42
xplat/Sonar/SonarPlugin.h
Normal file
42
xplat/Sonar/SonarPlugin.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <Sonar/SonarConnection.h>
|
||||
#include <string>
|
||||
|
||||
namespace facebook {
|
||||
namespace sonar {
|
||||
|
||||
class SonarPlugin {
|
||||
public:
|
||||
virtual ~SonarPlugin() {}
|
||||
|
||||
/**
|
||||
The plugin's identifier. This should map to a javascript plugin
|
||||
with the same identifier to ensure messages are sent correctly.
|
||||
*/
|
||||
virtual std::string identifier() const = 0;
|
||||
|
||||
/**
|
||||
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.
|
||||
*/
|
||||
virtual void didConnect(std::shared_ptr<SonarConnection> conn) = 0;
|
||||
|
||||
/**
|
||||
Called when a plugin has been disconnected and the SonarConnection
|
||||
provided in didConnect is no longer valid to use.
|
||||
*/
|
||||
virtual void didDisconnect() = 0;
|
||||
};
|
||||
|
||||
} // namespace sonar
|
||||
} // namespace facebook
|
||||
36
xplat/Sonar/SonarResponder.h
Normal file
36
xplat/Sonar/SonarResponder.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <folly/json.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace sonar {
|
||||
|
||||
/**
|
||||
* SonarResponder is used to asynchronously respond to messages
|
||||
* received from the Sonar desktop app.
|
||||
*/
|
||||
class SonarResponder {
|
||||
public:
|
||||
virtual ~SonarResponder(){};
|
||||
|
||||
/**
|
||||
* Deliver a successful response to the Sonar desktop app.
|
||||
*/
|
||||
virtual void success(const folly::dynamic& response) const = 0;
|
||||
|
||||
/**
|
||||
* Inform the Sonar desktop app of an error in handling the request.
|
||||
*/
|
||||
virtual void error(const folly::dynamic& response) const = 0;
|
||||
};
|
||||
|
||||
} // namespace sonar
|
||||
} // namespace facebook
|
||||
41
xplat/Sonar/SonarResponderImpl.h
Normal file
41
xplat/Sonar/SonarResponderImpl.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Sonar/SonarResponder.h>
|
||||
#include <Sonar/SonarWebSocket.h>
|
||||
#include <folly/json.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace sonar {
|
||||
|
||||
class SonarResponderImpl : public SonarResponder {
|
||||
public:
|
||||
SonarResponderImpl(SonarWebSocket* socket, int64_t responseID)
|
||||
: socket_(socket), responseID_(responseID) {}
|
||||
|
||||
void success(const folly::dynamic& response) const override {
|
||||
const folly::dynamic message =
|
||||
folly::dynamic::object("id", responseID_)("success", response);
|
||||
socket_->sendMessage(message);
|
||||
}
|
||||
|
||||
void error(const folly::dynamic& response) const override {
|
||||
const folly::dynamic message =
|
||||
folly::dynamic::object("id", responseID_)("error", response);
|
||||
socket_->sendMessage(message);
|
||||
}
|
||||
|
||||
private:
|
||||
SonarWebSocket* socket_;
|
||||
int64_t responseID_;
|
||||
};
|
||||
|
||||
} // namespace sonar
|
||||
} // namespace facebook
|
||||
63
xplat/Sonar/SonarWebSocket.h
Normal file
63
xplat/Sonar/SonarWebSocket.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <folly/json.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace sonar {
|
||||
|
||||
class SonarWebSocket {
|
||||
public:
|
||||
class Callbacks;
|
||||
|
||||
public:
|
||||
virtual ~SonarWebSocket(){};
|
||||
|
||||
/**
|
||||
Establishes a connection to the ws server.
|
||||
*/
|
||||
virtual void start() = 0;
|
||||
|
||||
/**
|
||||
Closes an open connection to the ws server.
|
||||
*/
|
||||
virtual void stop() = 0;
|
||||
|
||||
/**
|
||||
True if there's an open connection.
|
||||
This method may block if the connection is busy.
|
||||
*/
|
||||
virtual bool isOpen() const = 0;
|
||||
|
||||
/**
|
||||
Send message to the ws server.
|
||||
*/
|
||||
virtual void sendMessage(const folly::dynamic& message) = 0;
|
||||
|
||||
/**
|
||||
Handler for connection and message receipt from the ws server.
|
||||
The callbacks should be set before a connection is established.
|
||||
*/
|
||||
virtual void setCallbacks(Callbacks* callbacks) = 0;
|
||||
};
|
||||
|
||||
class SonarWebSocket::Callbacks {
|
||||
public:
|
||||
virtual ~Callbacks(){};
|
||||
|
||||
virtual void onConnected() = 0;
|
||||
|
||||
virtual void onDisconnected() = 0;
|
||||
|
||||
virtual void onMessageReceived(const folly::dynamic& message) = 0;
|
||||
};
|
||||
|
||||
} // namespace sonar
|
||||
} // namespace facebook
|
||||
301
xplat/Sonar/SonarWebSocketImpl.cpp
Normal file
301
xplat/Sonar/SonarWebSocketImpl.cpp
Normal file
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "SonarWebSocketImpl.h"
|
||||
#include <folly/String.h>
|
||||
#include <folly/futures/Future.h>
|
||||
#include <folly/io/async/SSLContext.h>
|
||||
#include <folly/json.h>
|
||||
#include <rsocket/Payload.h>
|
||||
#include <rsocket/RSocket.h>
|
||||
#include <rsocket/transports/tcp/TcpConnectionFactory.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include "CertificateUtils.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/log.h>
|
||||
#define SONAR_LOG(message) \
|
||||
__android_log_print(ANDROID_LOG_INFO, "sonar", "sonar: %s", message)
|
||||
#else
|
||||
#define SONAR_LOG(message) printf("sonar: %s", message)
|
||||
#endif
|
||||
|
||||
#define CSR_FILE_NAME "app.csr"
|
||||
#define SONAR_CA_FILE_NAME "sonarCA.crt"
|
||||
#define CLIENT_CERT_FILE_NAME "device.crt"
|
||||
#define PRIVATE_KEY_FILE "privateKey.pem"
|
||||
|
||||
static constexpr int reconnectIntervalSeconds = 2;
|
||||
static constexpr int connectionKeepaliveSeconds = 2;
|
||||
static constexpr int securePort = 8088;
|
||||
static constexpr int insecurePort = 8089;
|
||||
|
||||
namespace facebook {
|
||||
namespace sonar {
|
||||
|
||||
class ConnectionEvents : public rsocket::RSocketConnectionEvents {
|
||||
private:
|
||||
SonarWebSocketImpl* websocket_;
|
||||
|
||||
public:
|
||||
ConnectionEvents(SonarWebSocketImpl* websocket) : websocket_(websocket) {}
|
||||
|
||||
void onConnected() {
|
||||
websocket_->isOpen_ = true;
|
||||
if (websocket_->connectionIsTrusted_) {
|
||||
websocket_->callbacks_->onConnected();
|
||||
}
|
||||
}
|
||||
|
||||
void onDisconnected(const folly::exception_wrapper&) {
|
||||
if (!websocket_->isOpen_)
|
||||
return;
|
||||
websocket_->isOpen_ = false;
|
||||
websocket_->connectionIsTrusted_ = false;
|
||||
if (websocket_->connectionIsTrusted_) {
|
||||
websocket_->callbacks_->onDisconnected();
|
||||
}
|
||||
websocket_->reconnect();
|
||||
}
|
||||
|
||||
void onClosed(const folly::exception_wrapper& e) {
|
||||
onDisconnected(e);
|
||||
}
|
||||
};
|
||||
|
||||
class Responder : public rsocket::RSocketResponder {
|
||||
private:
|
||||
SonarWebSocketImpl* websocket_;
|
||||
|
||||
public:
|
||||
Responder(SonarWebSocketImpl* websocket) : websocket_(websocket) {}
|
||||
|
||||
void handleFireAndForget(
|
||||
rsocket::Payload request,
|
||||
rsocket::StreamId streamId) {
|
||||
const auto payload = request.moveDataToString();
|
||||
websocket_->callbacks_->onMessageReceived(folly::parseJson(payload));
|
||||
}
|
||||
};
|
||||
|
||||
SonarWebSocketImpl::SonarWebSocketImpl(SonarInitConfig config)
|
||||
: deviceData_(config.deviceData), worker_(config.worker) {}
|
||||
|
||||
SonarWebSocketImpl::~SonarWebSocketImpl() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void SonarWebSocketImpl::start() {
|
||||
folly::makeFuture()
|
||||
.via(worker_->getEventBase())
|
||||
.delayed(std::chrono::milliseconds(0))
|
||||
.then([this]() { startSync(); });
|
||||
}
|
||||
|
||||
void SonarWebSocketImpl::startSync() {
|
||||
if (isOpen()) {
|
||||
SONAR_LOG("Already connected");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (isCertificateExchangeNeeded()) {
|
||||
doCertificateExchange();
|
||||
return;
|
||||
}
|
||||
|
||||
connectSecurely();
|
||||
} catch (const std::exception& e) {
|
||||
std::string errors = folly::SSLContext::getErrors();
|
||||
SONAR_LOG("Error connecting to sonar");
|
||||
SONAR_LOG(e.what());
|
||||
SONAR_LOG(errors.c_str());
|
||||
failedConnectionAttempts_++;
|
||||
reconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void SonarWebSocketImpl::doCertificateExchange() {
|
||||
SONAR_LOG("Starting certificate exchange");
|
||||
|
||||
rsocket::SetupParameters parameters;
|
||||
folly::SocketAddress address;
|
||||
|
||||
parameters.payload = rsocket::Payload(
|
||||
folly::toJson(folly::dynamic::object("os", deviceData_.os)));
|
||||
address.setFromHostPort(deviceData_.host, insecurePort);
|
||||
|
||||
connectionIsTrusted_ = false;
|
||||
client_ =
|
||||
rsocket::RSocket::createConnectedClient(
|
||||
std::make_unique<rsocket::TcpConnectionFactory>(
|
||||
*worker_->getEventBase(), std::move(address)),
|
||||
std::move(parameters),
|
||||
nullptr,
|
||||
std::chrono::seconds(connectionKeepaliveSeconds), // keepaliveInterval
|
||||
nullptr, // stats
|
||||
std::make_shared<ConnectionEvents>(this))
|
||||
.get();
|
||||
|
||||
ensureSonarDirExists();
|
||||
requestSignedCertFromSonar();
|
||||
}
|
||||
|
||||
void SonarWebSocketImpl::connectSecurely() {
|
||||
rsocket::SetupParameters parameters;
|
||||
folly::SocketAddress address;
|
||||
parameters.payload = rsocket::Payload(folly::toJson(folly::dynamic::object(
|
||||
"os", deviceData_.os)("device", deviceData_.device)(
|
||||
"device_id", deviceData_.deviceId)("app", deviceData_.app)));
|
||||
address.setFromHostPort(deviceData_.host, securePort);
|
||||
|
||||
std::shared_ptr<folly::SSLContext> sslContext =
|
||||
std::make_shared<folly::SSLContext>();
|
||||
sslContext->loadTrustedCertificates(
|
||||
absoluteFilePath(SONAR_CA_FILE_NAME).c_str());
|
||||
sslContext->setVerificationOption(
|
||||
folly::SSLContext::SSLVerifyPeerEnum::VERIFY);
|
||||
sslContext->loadCertKeyPairFromFiles(
|
||||
absoluteFilePath(CLIENT_CERT_FILE_NAME).c_str(),
|
||||
absoluteFilePath(PRIVATE_KEY_FILE).c_str());
|
||||
sslContext->authenticate(true, false);
|
||||
|
||||
connectionIsTrusted_ = true;
|
||||
client_ =
|
||||
rsocket::RSocket::createConnectedClient(
|
||||
std::make_unique<rsocket::TcpConnectionFactory>(
|
||||
*worker_->getEventBase(),
|
||||
std::move(address),
|
||||
std::move(sslContext)),
|
||||
std::move(parameters),
|
||||
std::make_shared<Responder>(this),
|
||||
std::chrono::seconds(connectionKeepaliveSeconds), // keepaliveInterval
|
||||
nullptr, // stats
|
||||
std::make_shared<ConnectionEvents>(this))
|
||||
.get();
|
||||
failedConnectionAttempts_ = 0;
|
||||
}
|
||||
|
||||
void SonarWebSocketImpl::reconnect() {
|
||||
folly::makeFuture()
|
||||
.via(worker_->getEventBase())
|
||||
.delayed(std::chrono::seconds(reconnectIntervalSeconds))
|
||||
.then([this]() { startSync(); });
|
||||
}
|
||||
|
||||
void SonarWebSocketImpl::stop() {
|
||||
client_->disconnect();
|
||||
client_ = nullptr;
|
||||
}
|
||||
|
||||
bool SonarWebSocketImpl::isOpen() const {
|
||||
return isOpen_ && connectionIsTrusted_;
|
||||
}
|
||||
|
||||
void SonarWebSocketImpl::setCallbacks(Callbacks* callbacks) {
|
||||
callbacks_ = callbacks;
|
||||
}
|
||||
|
||||
void SonarWebSocketImpl::sendMessage(const folly::dynamic& message) {
|
||||
worker_->add([this, message]() {
|
||||
if (client_) {
|
||||
client_->getRequester()
|
||||
->fireAndForget(rsocket::Payload(folly::toJson(message)))
|
||||
->subscribe([]() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool SonarWebSocketImpl::isCertificateExchangeNeeded() {
|
||||
if (failedConnectionAttempts_ >= 2) {
|
||||
auto format =
|
||||
"Requesting fresh certificate exchange after %d failed connection attempts";
|
||||
char buff[strlen(format) + 1];
|
||||
sprintf(buff, format, failedConnectionAttempts_);
|
||||
SONAR_LOG(buff);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string caCert = loadStringFromFile(absoluteFilePath(SONAR_CA_FILE_NAME));
|
||||
std::string clientCert =
|
||||
loadStringFromFile(absoluteFilePath(CLIENT_CERT_FILE_NAME));
|
||||
std::string privateKey =
|
||||
loadStringFromFile(absoluteFilePath(PRIVATE_KEY_FILE));
|
||||
|
||||
if (caCert == "" || clientCert == "" || privateKey == "") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SonarWebSocketImpl::requestSignedCertFromSonar() {
|
||||
SONAR_LOG("Requesting new client certificate from Sonar");
|
||||
std::string csr = loadStringFromFile(absoluteFilePath(CSR_FILE_NAME));
|
||||
if (csr == "") {
|
||||
generateCertSigningRequest(
|
||||
deviceData_.appId.c_str(),
|
||||
absoluteFilePath(CSR_FILE_NAME).c_str(),
|
||||
absoluteFilePath(PRIVATE_KEY_FILE).c_str());
|
||||
csr = loadStringFromFile(absoluteFilePath(CSR_FILE_NAME));
|
||||
}
|
||||
// Send CSR to Sonar desktop
|
||||
folly::dynamic message = folly::dynamic::object("method", "signCertificate")(
|
||||
"csr", csr.c_str())("destination", absoluteFilePath("").c_str());
|
||||
worker_->add([this, message]() {
|
||||
client_->getRequester()
|
||||
->fireAndForget(rsocket::Payload(folly::toJson(message)))
|
||||
->subscribe([this]() {
|
||||
// Disconnect after message sending is complete.
|
||||
// This will trigger a reconnect which should use the secure channel.
|
||||
client_ = nullptr;
|
||||
});
|
||||
});
|
||||
failedConnectionAttempts_ = 0;
|
||||
}
|
||||
|
||||
std::string SonarWebSocketImpl::loadStringFromFile(std::string fileName) {
|
||||
std::stringstream buffer;
|
||||
std::ifstream stream;
|
||||
std::string line;
|
||||
stream.open(fileName.c_str());
|
||||
if (!stream) {
|
||||
SONAR_LOG(
|
||||
std::string("ERROR: Unable to open ifstream: " + fileName).c_str());
|
||||
return "";
|
||||
}
|
||||
buffer << stream.rdbuf();
|
||||
std::string s = buffer.str();
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string SonarWebSocketImpl::absoluteFilePath(const char* filename) {
|
||||
return std::string(deviceData_.privateAppDirectory + "/sonar/" + filename);
|
||||
}
|
||||
|
||||
bool SonarWebSocketImpl::ensureSonarDirExists() {
|
||||
std::string dirPath = absoluteFilePath("");
|
||||
struct stat info;
|
||||
if (stat(dirPath.c_str(), &info) != 0) {
|
||||
int ret = mkdir(dirPath.c_str(), S_IRUSR | S_IWUSR | S_IXUSR);
|
||||
return ret == 0;
|
||||
} else if (info.st_mode & S_IFDIR) {
|
||||
return true;
|
||||
} else {
|
||||
SONAR_LOG(std::string(
|
||||
"ERROR: Sonar path exists but is not a directory: " + dirPath)
|
||||
.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sonar
|
||||
} // namespace facebook
|
||||
67
xplat/Sonar/SonarWebSocketImpl.h
Normal file
67
xplat/Sonar/SonarWebSocketImpl.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Sonar/SonarInitConfig.h>
|
||||
#include <Sonar/SonarWebSocket.h>
|
||||
#include <folly/Executor.h>
|
||||
#include <folly/io/async/EventBase.h>
|
||||
#include <rsocket/RSocket.h>
|
||||
#include <mutex>
|
||||
|
||||
namespace facebook {
|
||||
namespace sonar {
|
||||
|
||||
class ConnectionEvents;
|
||||
class Responder;
|
||||
|
||||
class SonarWebSocketImpl : public SonarWebSocket {
|
||||
friend ConnectionEvents;
|
||||
friend Responder;
|
||||
|
||||
public:
|
||||
SonarWebSocketImpl(SonarInitConfig config);
|
||||
|
||||
~SonarWebSocketImpl();
|
||||
|
||||
void start() override;
|
||||
|
||||
void stop() override;
|
||||
|
||||
bool isOpen() const override;
|
||||
|
||||
void setCallbacks(Callbacks* callbacks) override;
|
||||
|
||||
void sendMessage(const folly::dynamic& message) override;
|
||||
|
||||
void reconnect();
|
||||
|
||||
private:
|
||||
bool isOpen_ = false;
|
||||
Callbacks* callbacks_;
|
||||
DeviceData deviceData_;
|
||||
|
||||
folly::EventBase* worker_;
|
||||
std::unique_ptr<rsocket::RSocketClient> client_;
|
||||
bool connectionIsTrusted_;
|
||||
int failedConnectionAttempts_ = 0;
|
||||
|
||||
void startSync();
|
||||
void doCertificateExchange();
|
||||
void connectSecurely();
|
||||
std::string loadCSRFromFile();
|
||||
std::string loadStringFromFile(std::string fileName);
|
||||
std::string absoluteFilePath(const char* relativeFilePath);
|
||||
bool isCertificateExchangeNeeded();
|
||||
void requestSignedCertFromSonar();
|
||||
bool ensureSonarDirExists();
|
||||
};
|
||||
|
||||
} // namespace sonar
|
||||
} // namespace facebook
|
||||
34
xplat/SonarTestLib/SonarConnectionMock.h
Normal file
34
xplat/SonarTestLib/SonarConnectionMock.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Sonar/SonarConnection.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace facebook {
|
||||
namespace sonar {
|
||||
|
||||
class SonarConnectionMock : public SonarConnection {
|
||||
public:
|
||||
void send(const std::string& method, const folly::dynamic& params) override {
|
||||
sent_[method] = params;
|
||||
}
|
||||
|
||||
void receive(const std::string& method, const SonarReceiver& receiver)
|
||||
override {
|
||||
receivers_[method] = receiver;
|
||||
}
|
||||
|
||||
std::map<std::string, folly::dynamic> sent_;
|
||||
std::map<std::string, SonarReceiver> receivers_;
|
||||
};
|
||||
|
||||
} // namespace sonar
|
||||
} // namespace facebook
|
||||
62
xplat/SonarTestLib/SonarPluginMock.h
Normal file
62
xplat/SonarTestLib/SonarPluginMock.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Sonar/SonarPlugin.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace sonar {
|
||||
namespace test {
|
||||
|
||||
class SonarPluginMock : public SonarPlugin {
|
||||
using ConnectionCallback =
|
||||
std::function<void(std::shared_ptr<SonarConnection>)>;
|
||||
using DisconnectionCallback = std::function<void()>;
|
||||
|
||||
public:
|
||||
SonarPluginMock(const std::string& identifier) : identifier_(identifier) {}
|
||||
|
||||
SonarPluginMock(
|
||||
const std::string& identifier,
|
||||
const ConnectionCallback& connectionCallback)
|
||||
: identifier_(identifier), connectionCallback_(connectionCallback) {}
|
||||
|
||||
SonarPluginMock(
|
||||
const std::string& identifier,
|
||||
const ConnectionCallback& connectionCallback,
|
||||
const DisconnectionCallback& disconnectionCallback)
|
||||
: identifier_(identifier),
|
||||
connectionCallback_(connectionCallback),
|
||||
disconnectionCallback_(disconnectionCallback) {}
|
||||
|
||||
std::string identifier() const override {
|
||||
return identifier_;
|
||||
}
|
||||
|
||||
void didConnect(std::shared_ptr<SonarConnection> conn) override {
|
||||
if (connectionCallback_) {
|
||||
connectionCallback_(conn);
|
||||
}
|
||||
}
|
||||
|
||||
void didDisconnect() override {
|
||||
if (disconnectionCallback_) {
|
||||
disconnectionCallback_();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::string identifier_;
|
||||
ConnectionCallback connectionCallback_;
|
||||
DisconnectionCallback disconnectionCallback_;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace sonar
|
||||
} // namespace facebook
|
||||
43
xplat/SonarTestLib/SonarResponderMock.h
Normal file
43
xplat/SonarTestLib/SonarResponderMock.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Sonar/SonarResponder.h>
|
||||
#include <folly/json.h>
|
||||
#include <vector>
|
||||
|
||||
namespace facebook {
|
||||
namespace sonar {
|
||||
|
||||
class SonarResponderMock : public SonarResponder {
|
||||
public:
|
||||
SonarResponderMock(
|
||||
std::vector<folly::dynamic>* successes = nullptr,
|
||||
std::vector<folly::dynamic>* errors = nullptr)
|
||||
: successes_(successes), errors_(errors) {}
|
||||
|
||||
void success(const folly::dynamic& response) const override {
|
||||
if (successes_) {
|
||||
successes_->push_back(response);
|
||||
}
|
||||
}
|
||||
|
||||
void error(const folly::dynamic& response) const override {
|
||||
if (errors_) {
|
||||
errors_->push_back(response);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<folly::dynamic>* successes_;
|
||||
std::vector<folly::dynamic>* errors_;
|
||||
};
|
||||
|
||||
} // namespace sonar
|
||||
} // namespace facebook
|
||||
55
xplat/SonarTestLib/SonarWebSocketMock.h
Normal file
55
xplat/SonarTestLib/SonarWebSocketMock.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Sonar/SonarWebSocket.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace sonar {
|
||||
namespace test {
|
||||
|
||||
class SonarWebSocketMock : public SonarWebSocket {
|
||||
public:
|
||||
SonarWebSocketMock() : callbacks(nullptr) {}
|
||||
|
||||
void start() override {
|
||||
open = true;
|
||||
if (callbacks) {
|
||||
callbacks->onConnected();
|
||||
}
|
||||
}
|
||||
|
||||
void stop() override {
|
||||
open = false;
|
||||
if (callbacks) {
|
||||
callbacks->onDisconnected();
|
||||
}
|
||||
}
|
||||
|
||||
bool isOpen() const override {
|
||||
return open;
|
||||
}
|
||||
|
||||
void sendMessage(const folly::dynamic& message) override {
|
||||
messages.push_back(message);
|
||||
}
|
||||
|
||||
void setCallbacks(Callbacks* aCallbacks) override {
|
||||
callbacks = aCallbacks;
|
||||
}
|
||||
|
||||
public:
|
||||
bool open = false;
|
||||
Callbacks* callbacks;
|
||||
std::vector<folly::dynamic> messages;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace sonar
|
||||
} // namespace facebook
|
||||
292
xplat/SonarTests/SonarClientTests.cpp
Normal file
292
xplat/SonarTests/SonarClientTests.cpp
Normal file
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Sonar/SonarClient.h>
|
||||
#include <SonarTestLib/SonarPluginMock.h>
|
||||
#include <SonarTestLib/SonarWebSocketMock.h>
|
||||
|
||||
#include <folly/json.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace sonar {
|
||||
namespace test {
|
||||
|
||||
using folly::dynamic;
|
||||
|
||||
TEST(SonarClientTests, testSaneMocks) {
|
||||
SonarWebSocketMock socket;
|
||||
socket.start();
|
||||
EXPECT_TRUE(socket.isOpen());
|
||||
socket.stop();
|
||||
EXPECT_FALSE(socket.isOpen());
|
||||
|
||||
SonarPluginMock plugin("Test");
|
||||
EXPECT_EQ(plugin.identifier(), "Test");
|
||||
}
|
||||
|
||||
TEST(SonarClientTests, testGetPlugins) {
|
||||
auto socket = new SonarWebSocketMock;
|
||||
SonarClient client(std::unique_ptr<SonarWebSocketMock>{socket});
|
||||
client.start();
|
||||
|
||||
client.addPlugin(std::make_shared<SonarPluginMock>("Cat"));
|
||||
client.addPlugin(std::make_shared<SonarPluginMock>("Dog"));
|
||||
|
||||
dynamic message = dynamic::object("id", 1)("method", "getPlugins");
|
||||
socket->callbacks->onMessageReceived(message);
|
||||
|
||||
dynamic expected = dynamic::object("id", 1)(
|
||||
"success", dynamic::object("plugins", dynamic::array("Cat", "Dog")));
|
||||
EXPECT_EQ(socket->messages.back(), expected);
|
||||
}
|
||||
|
||||
TEST(SonarClientTests, testGetPlugin) {
|
||||
auto socket = new SonarWebSocketMock;
|
||||
SonarClient client(std::unique_ptr<SonarWebSocketMock>{socket});
|
||||
|
||||
const auto catPlugin = std::make_shared<SonarPluginMock>("Cat");
|
||||
client.addPlugin(catPlugin);
|
||||
const auto dogPlugin = std::make_shared<SonarPluginMock>("Dog");
|
||||
client.addPlugin(dogPlugin);
|
||||
|
||||
EXPECT_EQ(catPlugin, client.getPlugin("Cat"));
|
||||
EXPECT_EQ(dogPlugin, client.getPlugin("Dog"));
|
||||
}
|
||||
|
||||
TEST(SonarClientTests, testGetPluginWithDowncast) {
|
||||
auto socket = new SonarWebSocketMock;
|
||||
SonarClient client(std::unique_ptr<SonarWebSocketMock>{socket});
|
||||
|
||||
const auto catPlugin = std::make_shared<SonarPluginMock>("Cat");
|
||||
client.addPlugin(catPlugin);
|
||||
EXPECT_EQ(catPlugin, client.getPlugin<SonarPluginMock>("Cat"));
|
||||
}
|
||||
|
||||
TEST(SonarClientTests, testRemovePlugin) {
|
||||
auto socket = new SonarWebSocketMock;
|
||||
SonarClient client(std::unique_ptr<SonarWebSocketMock>{socket});
|
||||
client.start();
|
||||
|
||||
auto plugin = std::make_shared<SonarPluginMock>("Test");
|
||||
client.addPlugin(plugin);
|
||||
client.removePlugin(plugin);
|
||||
|
||||
dynamic message = dynamic::object("id", 1)("method", "getPlugins");
|
||||
socket->callbacks->onMessageReceived(message);
|
||||
|
||||
dynamic expected = dynamic::object("id", 1)(
|
||||
"success", dynamic::object("plugins", dynamic::array()));
|
||||
EXPECT_EQ(socket->messages.back(), expected);
|
||||
}
|
||||
|
||||
TEST(SonarClientTests, testStartStop) {
|
||||
auto socket = new SonarWebSocketMock;
|
||||
SonarClient client(std::unique_ptr<SonarWebSocketMock>{socket});
|
||||
|
||||
client.start();
|
||||
EXPECT_TRUE(socket->isOpen());
|
||||
|
||||
client.stop();
|
||||
EXPECT_FALSE(socket->isOpen());
|
||||
}
|
||||
|
||||
TEST(SonarClientTests, testConnectDisconnect) {
|
||||
auto socket = new SonarWebSocketMock;
|
||||
SonarClient client(std::unique_ptr<SonarWebSocketMock>{socket});
|
||||
|
||||
bool pluginConnected = false;
|
||||
const auto connectionCallback = [&](std::shared_ptr<SonarConnection> conn) {
|
||||
pluginConnected = true;
|
||||
};
|
||||
const auto disconnectionCallback = [&]() { pluginConnected = false; };
|
||||
auto plugin = std::make_shared<SonarPluginMock>("Test", connectionCallback,
|
||||
disconnectionCallback);
|
||||
client.addPlugin(plugin);
|
||||
|
||||
client.start();
|
||||
dynamic messageInit = dynamic::object("method", "init")(
|
||||
"params", dynamic::object("plugin", "Test"));
|
||||
socket->callbacks->onMessageReceived(messageInit);
|
||||
EXPECT_TRUE(pluginConnected);
|
||||
|
||||
client.stop();
|
||||
EXPECT_FALSE(pluginConnected);
|
||||
}
|
||||
|
||||
TEST(SonarClientTests, testInitDeinit) {
|
||||
auto socket = new SonarWebSocketMock;
|
||||
SonarClient client(std::unique_ptr<SonarWebSocketMock>{socket});
|
||||
|
||||
bool pluginConnected = false;
|
||||
const auto connectionCallback = [&](std::shared_ptr<SonarConnection> conn) {
|
||||
pluginConnected = true;
|
||||
};
|
||||
const auto disconnectionCallback = [&]() { pluginConnected = false; };
|
||||
auto plugin = std::make_shared<SonarPluginMock>("Test", connectionCallback,
|
||||
disconnectionCallback);
|
||||
|
||||
client.start();
|
||||
client.addPlugin(plugin);
|
||||
EXPECT_FALSE(pluginConnected);
|
||||
|
||||
dynamic expected = dynamic::object("method", "refreshPlugins");
|
||||
EXPECT_EQ(socket->messages.front(), expected);
|
||||
|
||||
dynamic messageInit = dynamic::object("method", "init")(
|
||||
"params", dynamic::object("plugin", "Test"));
|
||||
socket->callbacks->onMessageReceived(messageInit);
|
||||
EXPECT_TRUE(pluginConnected);
|
||||
|
||||
dynamic messageDeinit = dynamic::object("method", "deinit")(
|
||||
"params", dynamic::object("plugin", "Test"));
|
||||
socket->callbacks->onMessageReceived(messageDeinit);
|
||||
EXPECT_FALSE(pluginConnected);
|
||||
|
||||
dynamic messageReinit = dynamic::object("method", "init")(
|
||||
"params", dynamic::object("plugin", "Test"));
|
||||
socket->callbacks->onMessageReceived(messageReinit);
|
||||
EXPECT_TRUE(pluginConnected);
|
||||
|
||||
client.stop();
|
||||
EXPECT_FALSE(pluginConnected);
|
||||
}
|
||||
|
||||
TEST(SonarClientTests, testRemovePluginWhenConnected) {
|
||||
auto socket = new SonarWebSocketMock;
|
||||
SonarClient client(std::unique_ptr<SonarWebSocketMock>{socket});
|
||||
|
||||
bool pluginConnected = false;
|
||||
const auto connectionCallback = [&](std::shared_ptr<SonarConnection> conn) {
|
||||
pluginConnected = true;
|
||||
};
|
||||
const auto disconnectionCallback = [&]() { pluginConnected = false; };
|
||||
auto plugin = std::make_shared<SonarPluginMock>("Test", connectionCallback,
|
||||
disconnectionCallback);
|
||||
|
||||
client.addPlugin(plugin);
|
||||
client.start();
|
||||
client.removePlugin(plugin);
|
||||
EXPECT_FALSE(pluginConnected);
|
||||
|
||||
dynamic expected = dynamic::object("method", "refreshPlugins");
|
||||
EXPECT_EQ(socket->messages.back(), expected);
|
||||
}
|
||||
|
||||
TEST(SonarClientTests, testUnhandleableMethod) {
|
||||
auto socket = new SonarWebSocketMock;
|
||||
SonarClient client(std::unique_ptr<SonarWebSocketMock>{socket});
|
||||
|
||||
auto plugin = std::make_shared<SonarPluginMock>("Test");
|
||||
client.addPlugin(plugin);
|
||||
|
||||
dynamic messageInit = dynamic::object("method", "init")(
|
||||
"params", dynamic::object("plugin", "Test"));
|
||||
socket->callbacks->onMessageReceived(messageInit);
|
||||
|
||||
dynamic messageExecute = dynamic::object("id", 1)("method", "unexpected");
|
||||
socket->callbacks->onMessageReceived(messageExecute);
|
||||
|
||||
dynamic expected = dynamic::object("id", 1)(
|
||||
"error",
|
||||
dynamic::object("message", "Received unknown method: unexpected"));
|
||||
EXPECT_EQ(socket->messages.back(), expected);
|
||||
}
|
||||
|
||||
TEST(SonarClientTests, testExecute) {
|
||||
auto socket = new SonarWebSocketMock;
|
||||
SonarClient client(std::unique_ptr<SonarWebSocketMock>{socket});
|
||||
client.start();
|
||||
|
||||
const auto connectionCallback = [](std::shared_ptr<SonarConnection> conn) {
|
||||
const auto receiver = [](const dynamic ¶ms,
|
||||
std::unique_ptr<SonarResponder> responder) {
|
||||
dynamic payload = dynamic::object("message", "yes_i_hear_u");
|
||||
responder->success(payload);
|
||||
};
|
||||
conn->receive("plugin_can_u_hear_me", receiver);
|
||||
};
|
||||
auto plugin = std::make_shared<SonarPluginMock>("Test", connectionCallback);
|
||||
client.addPlugin(plugin);
|
||||
|
||||
dynamic messageInit = dynamic::object("method", "init")(
|
||||
"params", dynamic::object("plugin", "Test"));
|
||||
socket->callbacks->onMessageReceived(messageInit);
|
||||
|
||||
dynamic messageUnexpected = dynamic::object("id", 1)("method", "execute")(
|
||||
"params",
|
||||
dynamic::object("api", "Test")("method", "plugin_can_u_hear_me"));
|
||||
socket->callbacks->onMessageReceived(messageUnexpected);
|
||||
|
||||
dynamic expected = dynamic::object("id", 1)(
|
||||
"success", dynamic::object("message", "yes_i_hear_u"));
|
||||
EXPECT_EQ(socket->messages.back(), expected);
|
||||
}
|
||||
|
||||
TEST(SonarClientTests, testExecuteWithParams) {
|
||||
auto socket = new SonarWebSocketMock;
|
||||
SonarClient client(std::unique_ptr<SonarWebSocketMock>{socket});
|
||||
|
||||
const auto connectionCallback = [&](std::shared_ptr<SonarConnection> conn) {
|
||||
const auto receiver = [](const dynamic ¶ms,
|
||||
std::unique_ptr<SonarResponder> responder) {
|
||||
const auto &first = params["first"].asString();
|
||||
const auto &second = params["second"].asString();
|
||||
std::map<std::string, std::string> m{{"dog", "woof"}, {"cat", "meow"}};
|
||||
dynamic payload = dynamic::object(first, m[first])(second, m[second]);
|
||||
responder->success(payload);
|
||||
};
|
||||
conn->receive("animal_sounds", receiver);
|
||||
};
|
||||
auto plugin = std::make_shared<SonarPluginMock>("Test", connectionCallback);
|
||||
client.addPlugin(plugin);
|
||||
|
||||
dynamic messageInit = dynamic::object("method", "init")(
|
||||
"params", dynamic::object("plugin", "Test"));
|
||||
socket->callbacks->onMessageReceived(messageInit);
|
||||
|
||||
dynamic messageExecute = dynamic::object("id", 1)("method", "execute")(
|
||||
"params",
|
||||
dynamic::object("api", "Test")("method", "animal_sounds")(
|
||||
"params", dynamic::object("first", "dog")("second", "cat")));
|
||||
socket->callbacks->onMessageReceived(messageExecute);
|
||||
|
||||
dynamic expected = dynamic::object("id", 1)(
|
||||
"success", dynamic::object("dog", "woof")("cat", "meow"));
|
||||
EXPECT_EQ(socket->messages.back(), expected);
|
||||
}
|
||||
|
||||
TEST(SonarClientTests, testExceptionUnknownPlugin) {
|
||||
auto socket = new SonarWebSocketMock;
|
||||
SonarClient client(std::unique_ptr<SonarWebSocketMock>{socket});
|
||||
client.start();
|
||||
|
||||
dynamic messageInit = dynamic::object("method", "init")(
|
||||
"params", dynamic::object("plugin", "Unknown"));
|
||||
socket->callbacks->onMessageReceived(messageInit);
|
||||
|
||||
EXPECT_EQ(socket->messages.back()["error"]["message"],
|
||||
"plugin Unknown not found for method init");
|
||||
}
|
||||
|
||||
TEST(SonarClientTests, testExceptionUnknownApi) {
|
||||
auto socket = new SonarWebSocketMock;
|
||||
SonarClient client(std::unique_ptr<SonarWebSocketMock>{socket});
|
||||
client.start();
|
||||
|
||||
dynamic messageInit = dynamic::object("method", "execute")(
|
||||
"params", dynamic::object("api", "Unknown"));
|
||||
socket->callbacks->onMessageReceived(messageInit);
|
||||
|
||||
EXPECT_EQ(socket->messages.back()["error"]["message"],
|
||||
"connection Unknown not found for method execute");
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace sonar
|
||||
} // namespace facebook
|
||||
37
xplat/build.gradle
Normal file
37
xplat/build.gradle
Normal file
@@ -0,0 +1,37 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.compileSdkVersion
|
||||
buildToolsVersion rootProject.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.minSdkVersion
|
||||
targetSdkVersion rootProject.targetSdkVersion
|
||||
|
||||
ndk {
|
||||
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments '-DANDROID_TOOLCHAIN=clang'
|
||||
}
|
||||
}
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path './CMakeLists.txt'
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile './AndroidManifest.xml'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':easywsclient')
|
||||
implementation project(':folly')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user