Compare commits

...
Author SHA1 Message Date
John Colvin f2d47a62f4 check pt: Last min changes, before going back to Mich 2023-11-08 19:23:24 -05:00
John Colvin 4be9f399aa Update externals (cme_websocket) 2023-11-06 11:17:59 -05:00
John Colvin aaf55ba65b check pt: Before going to chicago 2023-11-06 11:17:27 -05:00
John Colvin 9edf1dbd1a refactor: Move cme websocket app to lib 2023-11-04 13:32:01 -04:00
John Colvin 2dc9f8d71b clean up cme websocket listener 2023-11-04 13:01:02 -04:00
John Colvin addff775e9 pretty damn good, really 2023-11-03 23:12:58 -04:00
John Colvin f18c952ab8 more cleanup 2023-10-29 23:52:54 -04:00
John Colvin 326b068905 idk 2023-10-29 23:50:06 -04:00
John Colvin 249221c654 cleanup: Clean up more cme-websocket code 2023-10-29 23:41:36 -04:00
John Colvin bc9114e5a0 cleanup: Remove garbage/scratch files 2023-10-29 23:39:04 -04:00
John Colvin 56ae5dad35 cleanup new cme-websockets code 2023-10-29 23:37:39 -04:00
John Colvin 4ea6a9138f cleanup: Clean up new CMakeLists for cme websockets 2023-10-29 23:33:23 -04:00
John Colvin a38b24a910 docker: Install libcurl4-openssl-dev in docker 2023-10-29 23:30:38 -04:00
John Colvin fd9ed211b4 cleanup: Tidy up URL.h/cpp 2023-10-29 23:24:00 -04:00
John Colvin bfc3106b00 check pt: This Works! 2023-10-29 22:57:40 -04:00
John Colvin c8fb032a13 fix cme connector code 2023-10-29 15:15:33 -04:00
John Colvin df11e6564f fix crypto deps 2023-10-29 15:15:20 -04:00
John Colvin 6cf4200bad feat: Add cme_websocket connector code 2023-10-29 15:15:09 -04:00
John Colvin d91642ecd0 cme-websockets: Remove all deps on crypto/ssl in externals
I don't have time for these dependency conflicts with the default apt
websocketpp lib (just need to get this thing working!)
2023-10-29 15:14:07 -04:00
117 changed files with 6667 additions and 1431 deletions

No files matched your search

+7 -2
View File
@@ -42,8 +42,8 @@ if(WIN32)
add_subdirectory(src/lib)
else()
# make sparkd look for libspark.so in its directory
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
set(CMAKE_INSTALL_RPATH ".")
# set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
# set(CMAKE_INSTALL_RPATH ".")
set(CMAKE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/install")
set(SPARK_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/$<CONFIG>")
@@ -112,6 +112,11 @@ else()
# subdirs
if (BUILD_SPARKD)
add_subdirectory(src/lib_bts-system)
add_subdirectory(src/lib_bts-http)
add_subdirectory(src/lib_bts-websocket)
add_subdirectory(src/lib_bts-cme-websocket)
add_subdirectory(src/app_bts-cme-websocket-listener)
add_subdirectory(src/server/sparkd)
add_subdirectory(src/data_files)
endif()
-5
View File
@@ -45,12 +45,7 @@
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"platform": "",
"CMAKE_BUILD_WITH_INSTALL_RPATH": {
"type": "BOOL",
"value": "ON"
},
"CMAKE_CXX_STANDARD": "20",
"CMAKE_INSTALL_RPATH": ".",
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install/${presetName}/sparkd",
"CMAKE_EXE_LINKER_FLAGS": "-Wno-undef",
"EXTERNALS_ROOT": "${sourceDir}/externals/linux",
+4
View File
@@ -45,6 +45,10 @@ RUN apt-get update && apt-get install -y \
libssh2-1-dev \
openssh-server
# curl (lib)
RUN apt-get update && apt-get install -y \
libcurl4-openssl-dev
# cmake
RUN \
curl -fsSL https://apt.kitware.com/keys/kitware-archive-latest.asc 2> /dev/null \
@@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.23)
project(bts-cme-websocket-listener)
file(
GLOB_RECURSE ${PROJECT_NAME}_Sources
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
${PROJECT_NAME}/*.cpp)
add_executable(${PROJECT_NAME} ${${PROJECT_NAME}_Sources})
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(${PROJECT_NAME} PUBLIC bts-system bts-http bts-websocket bts-cme-websocket)
# # cli11
# FetchContent_Declare(
# cli11
# GIT_REPOSITORY git@github.com:CLIUtils/CLI11.git
# GIT_TAG v2.3.2
# SYSTEM
# OVERRIDE_FIND_PACKAGE
# )
# FetchContent_MakeAvailable(cli11)
# target_include_directories(${PROJECT_NAME} PUBLIC ${cli11_SOURCE_DIR}/include)
# target_link_libraries(${PROJECT_NAME} PUBLIC CLI11)
@@ -0,0 +1,73 @@
#pragma once
#include "bts-cme-websocket/cme_websocket_session.h"
#include "bts-cme-websocket/cme_websocket_session_config.h"
#include "bts-system/json/json_dumper.h"
#include "bts-system/json/json_reader.h"
#include <curlpp/cURLpp.hpp>
#include <fstream>
#include <iostream>
namespace bts::websocket {
class CmeWebsocketListener {
static CmeWebsocketSessionConfig parse_config(
const std::string &config_arg) {
std::ifstream cfg_ifs{config_arg};
const auto cfg_j = nlohmann::json::parse(cfg_ifs);
bts::system::json::JsonReader cfg_jr{&cfg_j};
CmeWebsocketSessionConfig cfg;
bts_assert(cfg_jr.read(cfg));
cfg.websocket.timeout = bts::system::TimeSpan::from_seconds(20);
return cfg;
}
void on_trade_ticker(const std::vector<CmeTradeSummaryPayload> &payload) {
LOG(Md, Info, "on_trade_ticker.\n{}",
system::json::JsonDumper::dump(payload.begin(), payload.end()));
}
static void on_top_of_book(const std::vector<CmeTopOfBookPayload> &payload) {
LOG(Md, Info, "on_top_of_book.\n{}",
system::json::JsonDumper::dump(payload.begin(), payload.end()));
}
public:
void run(const std::string &config_arg) {
const auto cfg = parse_config(config_arg);
// Ensure curpp::initialize/terminate are called (even if exception is thrown)
struct CurlInit {
CurlInit() { curlpp::initialize(); }
~CurlInit() { curlpp::terminate(); }
} _CURL_INIT;
// Init websocket session (it won't actually connect until we call
// `CmeWebsocketSession::start`)
CmeWebsocketSession session{cfg};
// Bind various callbacks
// Bind 'trade_ticker' callback to a member function
session.trade_ticker_callback()
.bind<&CmeWebsocketListener::on_trade_ticker>(*this);
// Bind 'top_of_book' callback to a static function
session.top_of_book_callback()
.bind<&CmeWebsocketListener::on_top_of_book>();
// Connect to the websocket server
session.start();
// Send subscription request (should start receiving messages in the
// callbacks we bound above)
session.subscribe(
{{CmeSubscriptionTopic::TRADE_TICKER, {"XCME", "FUT", "BTC"}},
{CmeSubscriptionTopic::TOP_OF_BOOK, {"XCME", "FUT", "BTC"}}});
static const int sleep_secs = 3;
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(sleep_secs));
}
}
};
} // namespace bts::websocket
@@ -0,0 +1,126 @@
#include "bts-cme-websocket-listener/cme_websocket_listener.h"
#include "bts-cme-websocket/cme_message_handler.h"
#include "bts-cme-websocket/cme_message_handler_proxy.h"
#include "bts-cme-websocket/cme_message_logger.h"
#include "bts-cme-websocket/cme_subscription_topic.h"
#include "bts-cme-websocket/cme_websocket_session.h"
#include "bts-cme-websocket/cme_websocket_session_config.h"
#include "bts-cme-websocket/messages/cme_product_key.h"
#include "bts-cme-websocket/messages/cme_trade_summary_payload.h"
#include "bts-system/UNUSED.h"
#include "bts-system/json/json_dumper.h"
#include "bts-system/json/json_reader.h"
#include "bts-websocket/websocket_client.h"
#include <CLI11/CLI11.hpp>
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <curlpp/cURLpp.hpp>
#include <fstream>
#include <iostream>
#include <nlohmann/json.hpp>
#include <nlohmann/json_fwd.hpp>
#include <string>
#include <vector>
using bts::system::json::JsonDumper;
using bts::system::json::JsonReader;
using bts::websocket::CmeMessageHandlerProxy;
using bts::websocket::CmeSubscriptionTopic;
using bts::websocket::CmeTopOfBookPayload;
using bts::websocket::CmeTradeSummaryPayload;
using bts::websocket::CmeWebsocketSession;
using bts::websocket::CmeWebsocketSessionConfig;
using CLI::App;
using std::ifstream;
using std::string;
using std::vector;
static CmeWebsocketSessionConfig parse_config(const string &config_arg) {
ifstream cfg_ifs{config_arg};
const auto cfg_j = nlohmann::json::parse(cfg_ifs);
JsonReader cfg_jr{&cfg_j};
CmeWebsocketSessionConfig cfg;
bts_assert(cfg_jr.read(cfg));
cfg.websocket.timeout = bts::system::TimeSpan::from_seconds(20);
return cfg;
}
static void on_trade_ticker(const vector<CmeTradeSummaryPayload> &payload) {
LOG(Md, Info, "on_trade_ticker.\n{}",
JsonDumper::dump(payload.begin(), payload.end()));
}
static void on_top_of_book(const vector<CmeTopOfBookPayload> &payload) {
LOG(Md, Info, "on_top_of_book.\n{}",
JsonDumper::dump(payload.begin(), payload.end()));
}
static void run_alt(const std::string &config_arg) {
bts::websocket::CmeWebsocketListener listener{};
listener.run(config_arg);
}
static void run(const std::string &config_arg) {
if (config_arg.size() < 999999) {
run_alt(config_arg);
return;
}
const auto cfg = parse_config(config_arg);
struct CurlInit {
CurlInit() { curlpp::initialize(); }
~CurlInit() { curlpp::terminate(); }
} _CURL_INIT;
// Init websocket session (it won't actually connect until we call
// `CmeWebsocketSession::start`)
CmeWebsocketSession session{cfg};
// Bind callbacks
session.trade_ticker_callback().bind<&on_trade_ticker>();
session.top_of_book_callback().bind<&on_top_of_book>();
// Connect to the websocket server
session.start();
// Send subscription request (should start receiving messages in the
// callbacks we bound above)
session.subscribe(
{{CmeSubscriptionTopic::TRADE_TICKER, {"XCME", "FUT", "BTC"}},
{CmeSubscriptionTopic::TOP_OF_BOOK, {"XCME", "FUT", "BTC"}}});
// std::vector<bts::websocket::CmeProductKey>{{"XCME", "FUT", "BTC"}},
// std::vector<bts::websocket::CmeSubscriptionTopic>{
// bts::websocket::CmeSubscriptionTopic::TRADE_TICKER},
// &trade_handler);
static const int sleep_secs = 3;
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(sleep_secs));
// string text{R"({
// "header": {
// "messageType": "subscribe",
// "sentTime": "",
// "version": "",
// "requestId": "1",
// "token": ""
// },
// "payload": {
// "subscriptionMessageTypes": ["TRD"],
// "subscriptions": [
// {
// "exchangeMic": "XCME",
// "productType": "FUT",
// "productCode": "BTC"
// }
// ]
// }
// })"};
}
}
int main(int argc, char **argv) {
App app{"Cme websocket listener", "bts-websocket-listener"};
string config_arg;
app.add_option(
"--config", config_arg, "Path to CmeWebsocketConfig json file")
->required();
app.callback([&config_arg]() { run(config_arg); });
CLI11_PARSE(app, argc, argv);
}
@@ -0,0 +1,35 @@
{
"header": {
"messageType": "TS",
"sentTime": "null",
"version": "1.0"
},
"payload": [
{
"lastUpdateTime": "2023-01-23T10:00:12.509489271Z",
"tradeSummary": {
"aggressorSide": "2",
"mdTradeEntryId": "172277",
"tradePrice": "22855",
"tradeQty": "1",
"tradeOrderCount": "2",
"tradeUpdateAction": "NEW",
"orderQty": {
"orderId": "2",
"lastOrdQty": "1"
}
},
"instrument": {
"definitionSource": "E",
"exchangeMic": "XCME",
"id": "64598",
"marketSegmentId": "74",
"periodCode": "202301",
"productCode": "BTC",
"productType": "FUT",
"productGroup": "BF",
"symbol": "BTCF3"
}
}
]
}
@@ -0,0 +1,26 @@
{
"websocket": {
"url": "wss://markets.api.uat.cmegroup.com/marketdatastream/v1",
"auth": {
"oauth": {
"url": "https://authnr.cmegroup.com/as/token.oauth2",
"client_id": "api_thinkalpha321",
"client_secret": "4T39A4t_G#7erR$TYgrJ9hmT"
}
}
},
"subscriptions": [
{
"topics": [
"TRD"
],
"products": [
{
"exchangeMic": "XCME",
"productType": "FUT",
"productCode": "BTC"
}
]
}
]
}
@@ -0,0 +1,21 @@
{
"header": {
"messageType": "subscriptionStatus",
"requestId": 0,
"sentTime": "2023-11-01T15:21:12.376000000Z",
"version": "1.0",
"sequenceNumber": "2"
},
"payload": {
"status": "subscribed",
"subscriptionMessageTypes": [
"TRD"
],
"subscriptions": [
{
"productType": "FUT",
"productCode": "BTC"
}
]
}
}
+31
View File
@@ -0,0 +1,31 @@
{
"header": {
"messageType": "TOB",
"sentTime": "2023-11-08T20:10:16.439000000Z",
"sequenceNumber": "2",
"version": "1.0"
},
"payload": [
{
"instrument": {
"definitionSource": "E",
"exchangeMic": "XCME",
"id": "42013807",
"marketSeqmentId": "74",
"periodCode": "",
"productCode": "BTC",
"productGroup": "BF",
"productType": "FUT",
"symbol": "BTCG4"
},
"askLevel": [
{
"lastUpdateTime": "2023-11-08T20:03:58.709512329Z",
"orderCnt": "1",
"price": "36460.0",
"qty": "1.0"
}
]
}
]
}
+42
View File
@@ -0,0 +1,42 @@
{
"header": {
"messageType": "TRD",
"sentTime": "2023-11-08T18:39:31.545000000Z",
"sequenceNumber": "56",
"version": "1.0"
},
"payload": [
{
"instrument": {
"definitionSource": "E",
"exchangeMic": "XCME",
"id": "87394",
"marketSeqmentId": "74",
"periodCode": "",
"productCode": "BTC",
"productGroup": "BF",
"productType": "FUT",
"symbol": "BTCZ3"
},
"lastUpdateTime": "2023-11-08T04:54:03.451575431Z",
"tradeSummary": {
"aggressorSide": "Buy",
"mdTradeEntryId": "174893",
"tradePrice": "35810.0",
"tradeQty": "1",
"tradeOrderCount": "2",
"tradeUpdateAction": "New",
"orderQty": [
{
"orderId": "74467988366",
"lastQty": "1"
},
{
"orderId": "74467981241",
"lastQty": "1"
}
]
}
}
]
}
@@ -0,0 +1,18 @@
{
"websocket": {
"url": "wss://markets.api.uat.cmegroup.com/marketdatastream/v1",
"auth": {
"oauth": {
"url": "https://authnr.cmegroup.com/as/token.oauth2",
"client_id": "api_thinkalpha321",
"client_secret": "4T39A4t_G#7erR$TYgrJ9hmT"
}
}
},
"subscription_payloads": [],
"thread": {
"name_rx": "websocket_thread",
"exclude": false,
"cores": [1]
}
}
+44
View File
@@ -0,0 +1,44 @@
#pragma once
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/json.hpp>
#include <string>
#include "lib/io/URL.h"
namespace bts::md::cme_websocket {
class CmeOauthConfig {
public:
std::string protocol{"https"};
std::string host;
std::uint16_t port{443};
std::string endpoint;
std::string client_id;
std::string client_secret;
io::URL get_url() const;
void get_url(io::URL &dest) const;
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
CmeOauthConfig, protocol, host, port, endpoint, client_id, client_secret);
inline io::URL CmeOauthConfig::get_url() const {
io::URL url;
get_url(url);
return url;
}
inline void CmeOauthConfig::get_url(io::URL &dest) const {
dest.fill_host(host);
dest.fill_path(endpoint);
dest.fill_port(port);
dest.fill_protocol("https");
}
} // namespace bts::websocket
@@ -0,0 +1,26 @@
#pragma once
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/json.hpp>
#include <string>
// {
// "access_token":
// "eyJhbGciOiJSUzI1NiIsImtpZCI6IjF0al9MTVFrT2pGVzJWQTZvdkZzOEZHUWViMCIsInBpLmF0bSI6IjEifQ.eyJzY29wZSI6W10sImNsaWVudF9pZCI6ImFwaV90aGlua2FscGhhMzIxIiwiYXVkIjoiTnIiLCJqdGkiOiJ5bzlWZUdOMWxudzBXNHhCIiwic3ViIjoiYXBpX3RoaW5rYWxwaGEzMjEiLCJleHAiOjE2OTc1Nzg2NjJ9.HO-UzBvYcdHH6EP0WbmKNbwpZWUF3UbScUQ6bEF4iZqzPyZ1eFLS20VSDULbUpTc_Kl0g9IltJL4ZhnhRqoAX_cWGpqDCDI4wk-04NrcU0-I7YZuNXpDaTwJLChTASQX0PXmmGYDascdem-8FAEp3EgRMrQPXug4FOfp1aKXlgmB5U0Q5lKbZsy_52n56N928iuieYH63JhWRFG0wvleXeyeDDKJNmizKLpGWuBPRwyUYhZWFNQCxbla2TwDTXBWLlO_eZyPq16SS_NKVssTarmY8ohAwuD7N_D7mQbWcbeggks1B9r2w66DuKotRzIrSuhK4VlbHj94FLyqE2oVYw",
// "token_type": "Bearer",
// "expires_in": 1799
// }
namespace bts::md::cme_websocket {
class CmeOauthResponse {
public:
std::string access_token;
std::string token_type;
std::int64_t expires_in;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
CmeOauthResponse, access_token, token_type, expires_in);
} // namespace bts::md::cme_websocket
@@ -0,0 +1,35 @@
#include "CmeOauthConfig.h"
#include "CmeOauthResponse.h"
#include "lib/io/JsonDumper.h"
#include "lib/io/http/HTTPRequest.h"
#include "lib/io/http/HTTPClient.h"
namespace bts::md::cme_websocket {
class CmeWebsocketOauthRequestor {
public:
using HTTPRequest = io::http::HTTPRequest;
using HTTPClient = io::http::HTTPClient;
static HTTPRequest build_oauth_request(const CmeOauthConfig &cfg) {
HTTPRequest http_req;
http_req.url() = cfg.get_url();
io::QueryParams qp;
qp["grant_type"] = "client_credentials";
qp["client_id"] = cfg.client_id;
qp["client_secret"] = cfg.client_secret;
http_req.body() = qp.to_string();
return http_req;
}
static CmeOauthResponse request_oauth_token(const CmeOauthConfig &cfg) {
HTTPRequest req = build_oauth_request(cfg);
LOG(Gen, Info, "Requesting oauth:\n{}", io::JsonDumper::dump(req));
HTTPClient client;
HTTPResponse res = client.post(req);
LOG(Gen, Info, "Received oauth response:\n{}", io::JsonDumper::dump(res));
const auto j = nlohmann::json::parse(res.body());
return io::JsonReader{&j}.read<OauthResponse>();
}
};
} // namespace bts::md::cme_websocket
@@ -0,0 +1,60 @@
#include "CmeWebsocketSubscription.h"
#include <iostream>
#include <websocketpp/client.hpp>
#include <websocketpp/common/thread.hpp>
#include <websocketpp/config/asio_client.hpp>
#include <websocketpp/config/asio_no_tls_client.hpp>
#include <websocketpp/roles/client_endpoint.hpp>
namespace bts::md::cme_websocket {
struct CmeWebsocketSubscription::PImpl {
// TODO
void connect() {}
static void on_open(websocketpp::connection_hdl hdl) {
(void)(hdl);
// Connection opened, you can send WebSocket messages here
}
int run() {
websocketpp::client<websocketpp::config::asio_tls_client> client;
// Set up connection settings
std::string server_uri = "wss://example.com/websocket_endpoint";
client.init_asio();
client.set_open_handler(&on_open);
// Define the headers, including the Authorization header
websocketpp::lib::error_code ec;
websocketpp::client<websocketpp::config::asio_tls_client>::connection_ptr
con = client.get_connection(server_uri, ec);
if (!ec) {
con->append_header("Authorization", "Bearer your_token_here");
// Connect to the server
client.connect(con);
// Start the WebSocket++ event loop
client.run();
} else {
std::cerr << "Error creating the connection: " << ec.message()
<< std::endl;
}
return 0;
}
};
CmeWebsocketSubscription::CmeWebsocketSubscription()
: me_(std::make_unique<PImpl>()) {
// TODO
}
CmeWebsocketSubscription::~CmeWebsocketSubscription() = default;
int CmeWebsocketSubscription::run() { return me_->run(); }
} // namespace bts::md::cme_websocket
@@ -0,0 +1,17 @@
#pragma once
#include <memory>
namespace bts::md::cme_websocket {
class CmeWebsocketSubscription {
public:
CmeWebsocketSubscription();
~CmeWebsocketSubscription();
int run();
private:
struct PImpl;
std::unique_ptr<PImpl> me_;
};
}
+4 -4
View File
@@ -2,10 +2,10 @@
#include "AuthSessionManager.h"
#include "lib/utils/FileUtils.h"
#include "lib/utils/crypto/CryptoInit.h"
#include "lib/utils/crypto/KeyCodeHelper.h"
#include "lib/utils/crypto/PrivateKey.h"
#include "lib/utils/crypto/PublicKey.h"
// #include "lib/utils/crypto/CryptoInit.h"
// #include "lib/utils/crypto/KeyCodeHelper.h"
// #include "lib/utils/crypto/PrivateKey.h"
// #include "lib/utils/crypto/PublicKey.h"
#include "lib/utils/datetime.h"
-12
View File
@@ -1,12 +0,0 @@
#pragma once
namespace bts {
namespace crypto {
class AlgoType {
public:
enum type { RSA = 0 };
};
} // namespace crypto
} // namespace bts
-10
View File
@@ -1,10 +0,0 @@
#include "CipherType.h"
namespace bts {
namespace crypto {
const char *CipherType::name[] = {
"None", "DES-EDE3-CBC", "3des", "idea", "aes128"};
}
} // namespace bts
-13
View File
@@ -1,13 +0,0 @@
#pragma once
namespace bts {
namespace crypto {
class CipherType {
public:
static const char *name[];
enum type { None = 0, DES = 1, TripleDES = 2, IDEA = 3, AES128 = 4 };
};
} // namespace crypto
} // namespace bts
-48
View File
@@ -1,48 +0,0 @@
#ifndef _WIN32
#include "CryptoInit.h"
#include <iostream>
#include <stdlib.h>
#include <tbb/spin_mutex.h>
namespace openssl {
#include <openssl/bio.h>
#include <openssl/bn.h>
#include <openssl/pem.h>
#include <openssl/rand.h>
#include <openssl/rsa.h>
} // namespace openssl
namespace bts {
namespace crypto {
static const char rnd_seed[] =
"string to make the random number generator think it has entropy";
bool CryptoInit::init = false;
tbb::spin_mutex _lock;
void CryptoInit::initialize() {
if (!init) {
tbb::spin_mutex::scoped_lock lock(_lock);
if (!init) {
openssl::OpenSSL_add_all_algorithms();
openssl::OpenSSL_add_all_ciphers();
openssl::OpenSSL_add_all_digests();
openssl::RAND_seed(rnd_seed, sizeof rnd_seed);
init = true;
}
}
}
void CryptoInit::cleanup() {
if (init) {
tbb::spin_mutex::scoped_lock lock(_lock);
if (init) {
init = false;
}
}
}
} // namespace crypto
} // namespace bts
#endif
-16
View File
@@ -1,16 +0,0 @@
#pragma once
#include "lib/utils/bts_no_copy.h"
namespace bts {
namespace crypto {
struct CryptoInit {
BTS_NO_COPY(CryptoInit);
static bool init;
static void initialize();
static void cleanup();
};
} // namespace crypto
} // namespace bts
-64
View File
@@ -1,64 +0,0 @@
#ifndef _WIN32
#include "Digest.h"
#include "PrivateKey.h"
#include "lib/utils/bts_assert.h"
namespace openssl {
#include <openssl/evp.h>
#include <openssl/pem.h>
} // namespace openssl
namespace bts {
namespace crypto {
const std::string digest_algo = "sha256";
void Digest::allocate_array(unsigned char **out_digest, unsigned int *out_len) {
*out_len = EVP_MAX_MD_SIZE;
*out_digest = new unsigned char[*out_len];
}
void Digest::deallocate_array(unsigned char *out_digest) {
delete[] out_digest;
}
void Digest::compute_digest(
const unsigned char *data,
unsigned long size,
unsigned char *out_digest,
unsigned int *out_len) {
bts_assert(*out_len >= EVP_MAX_MD_SIZE);
const openssl::EVP_MD *md =
openssl::EVP_get_digestbyname(digest_algo.c_str());
bts_assert(md);
openssl::EVP_MD_CTX *mdctx = openssl::EVP_MD_CTX_create();
openssl::EVP_DigestInit_ex(mdctx, md, NULL);
openssl::EVP_DigestUpdate(mdctx, data, size);
openssl::EVP_DigestFinal_ex(mdctx, out_digest, out_len);
openssl::EVP_MD_CTX_destroy(mdctx);
}
void Digest::compute_digest(const std::string &data, std::string *out_digest) {
unsigned int len = EVP_MAX_MD_SIZE;
unsigned char ret[EVP_MAX_MD_SIZE];
char str[EVP_MAX_MD_SIZE + 1];
compute_digest(
reinterpret_cast<const unsigned char *>(data.c_str()), data.size(),
&ret[0], &len);
for (size_t i = 0; i < len; i++)
sprintf(&str[i * 2], "%02x", ret[i]);
std::string val(str, len * 2);
*out_digest = val;
}
std::string Digest::compute_digest(const std::string &data) {
std::string ret;
compute_digest(data, &ret);
return ret;
}
} // namespace crypto
} // namespace bts
#endif
-26
View File
@@ -1,26 +0,0 @@
#pragma once
#include "lib/utils/bts_no_copy.h"
#include <string>
namespace bts {
namespace crypto {
class Digest {
public:
static void
allocate_array(unsigned char **out_digest, unsigned int *out_len);
static void deallocate_array(unsigned char *out_digest);
static void compute_digest(
const unsigned char *data,
unsigned long size,
unsigned char *out_digest,
unsigned int *out_len);
static void compute_digest(const std::string &data, std::string *out_digest);
static std::string compute_digest(const std::string &data);
};
} // namespace crypto
} // namespace bts
-38
View File
@@ -1,38 +0,0 @@
#ifndef _WIN32
#include "KeyCodeHelper.h"
#include "lib/utils/crypto/CryptoInit.h"
#include "lib/utils/crypto/PrivateKey.h"
#include "lib/utils/crypto/PublicKey.h"
#include "lib/utils/datetime.h"
#include "lib/config/protos/keycode.pb.h"
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <google/protobuf/text_format.h>
#include <sstream>
#include <string>
namespace bts {
namespace crypto {
bool decrypt_keycode(
const bts::crypto::PublicKey &public_key,
const bts::config::EncryptedKeyCode &kc,
bts::config::KeyCode *dk) {
std::stringstream kss, out;
kss << "-----BEGIN BTS-----\n" + kc.keycode() + "\n-----END BTS-----\n";
if (public_key.decrypt(kss, out)) {
bts::config::KeyCode kc;
google::protobuf::io::IstreamInputStream kc_stream(&out);
google::protobuf::TextFormat::Parse(&kc_stream, &kc);
if (dk) {
*dk = kc;
return true;
}
}
return false;
}
} // namespace crypto
} // namespace bts
#endif
-16
View File
@@ -1,16 +0,0 @@
#pragma once
namespace bts {
namespace config {
class EncryptedKeyCode;
class KeyCode;
} // namespace config
namespace crypto {
class PublicKey;
bool decrypt_keycode(
const bts::crypto::PublicKey &public_key,
const bts::config::EncryptedKeyCode &kc,
bts::config::KeyCode *dk);
} // namespace crypto
} // namespace bts
-50
View File
@@ -1,50 +0,0 @@
#include "PasswordCallback.h"
#include <iostream>
#include <stdio.h>
namespace bts {
namespace crypto {
PasswordCallback::PasswordCallback() : _callback(0) {}
PasswordCallback::PasswordCallback(password_callback cb) : _callback(cb) {}
PasswordCallback::~PasswordCallback() {}
void PasswordCallback::prompt(
const std::string &user_info, std::string &password) {
if (user_info == "")
std::cout << "Enter Private Key Passphrase:";
else
std::cout << "Enter Private Key Passphrase (" + user_info + "): ";
std::cin >> password;
}
void PasswordCallback::request_password(
const std::string &user_info, std::string &password) const {
if (_callback)
_callback(user_info, password);
else {
prompt(user_info, password);
}
}
CachedPassword::CachedPassword() : _pass() {
PasswordCallback::prompt("", _pass);
}
CachedPassword::CachedPassword(const std::string &pass) : _pass(pass) {
if (pass == "")
PasswordCallback::prompt("", _pass);
}
CachedPassword::~CachedPassword() {}
void CachedPassword::request_password(
const std::string &, std::string &password) const {
password = _pass;
}
} // namespace crypto
} // namespace bts
-48
View File
@@ -1,48 +0,0 @@
#pragma once
#include "lib/utils/bts_no_copy.h"
#include <string>
namespace bts {
namespace crypto {
class PasswordCallback {
BTS_NO_COPY(PasswordCallback);
public:
typedef void
password_callback(const std::string &user_info, std::string &password);
public:
PasswordCallback();
PasswordCallback(password_callback cb);
virtual ~PasswordCallback();
static void prompt(const std::string &user_info, std::string &password);
virtual void
request_password(const std::string &user_info, std::string &password) const;
static void
prerequest_password(const std::string &user_info, std::string &password);
protected:
password_callback *_callback;
};
class CachedPassword : public PasswordCallback {
BTS_NO_COPY(CachedPassword);
public:
CachedPassword();
CachedPassword(const std::string &);
virtual ~CachedPassword();
virtual void
request_password(const std::string &user_info, std::string &password) const;
protected:
std::string _pass;
};
} // namespace crypto
} // namespace bts
-297
View File
@@ -1,297 +0,0 @@
#ifndef _WIN32
#include "PrivateKey.h"
#include "CryptoInit.h"
#include "PublicKey.h"
#include "iostreambio.h"
#include "lib/logging/logging.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/helper_macros.h"
#include <cstring>
#include <tbb/spin_mutex.h>
namespace openssl {
#include <openssl/bio.h>
#include <openssl/bn.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/rand.h>
#include <openssl/rsa.h>
} // namespace openssl
namespace bts {
namespace crypto {
struct PrivateKey::Pimp {
BTS_NO_COPY(Pimp);
const PasswordCallback *password;
PasswordCallback default_password;
PublicKeyPtr public_key;
Pimp(const PasswordCallback *pass)
: password(pass), default_password(), public_key() {
initialize();
if (!password)
password = &default_password;
}
~Pimp() {}
static void initialize() { CryptoInit::initialize(); }
};
int password_callback(char *buf, int size, int rwflag, void *u) {
UNUSED(rwflag);
std::string data;
if (u) {
PasswordCallback *pwd = reinterpret_cast<PasswordCallback *>(u);
pwd->request_password(data, data);
} else {
PasswordCallback ddefault;
ddefault.request_password(data, data);
}
int sz = data.size();
if ((int)data.size() > size)
sz = size;
std::memcpy(buf, data.c_str(), sz);
return sz;
}
const PublicKeyPtr PrivateKey::public_key() const {
if (!me->public_key)
me->public_key = PublicKey::create(*this);
return me->public_key;
}
const PrivateKeyPtr
PrivateKey::create(int bits, AlgoType::type at, const PasswordCallback *pass) {
openssl::EVP_PKEY *key = openssl::EVP_PKEY_new();
if (at == AlgoType::RSA) {
openssl::RSA *rsa = openssl::RSA_new();
openssl::BIGNUM *bn = openssl::BN_new();
openssl::BN_set_word(bn, RSA_F4);
int keyok = openssl::RSA_generate_key_ex(rsa, bits, bn, 0);
openssl::BN_free(bn);
bts_assert_msg(keyok != -1, "Unable to create key");
int i = openssl::EVP_PKEY_assign_RSA(key, rsa);
bts_assert_msg(1 == i, "Unable to create key");
} else
bts_assert_msg(false, "only support RSA");
return PrivateKeyPtr(new PrivateKey(key, pass));
}
const PrivateKeyPtr PrivateKey::read_file(
const std::string &filename, const PasswordCallback *pass) {
PrivateKey::Pimp::initialize();
PrivateKeyPtr ret;
openssl::BIO *bio = openssl::BIO_new_file(filename.c_str(), "r");
bts_assert_msg(bio, "can't load keyfile");
openssl::EVP_PKEY *key = openssl::PEM_read_bio_PrivateKey(
bio, 0, password_callback, (void *)pass);
if (!key)
LOG(Gen, Error, "Invalid private key passphrase");
else
ret.reset(new PrivateKey(key, pass));
return ret;
}
const PrivateKeyPtr PrivateKey::read_stream(
const std::istream &stream, const PasswordCallback *pass) {
PrivateKey::Pimp::initialize();
PrivateKeyPtr ret;
const_cast<std::istream &>(stream).seekg(0, std::ios::end);
int length = const_cast<std::istream &>(stream).tellg();
const_cast<std::istream &>(stream).seekg(0, std::ios::beg);
char *data = new char[length];
const_cast<std::istream &>(stream).read(data, length);
openssl::BIO *bio = openssl::BIO_new(openssl::BIO_s_mem());
BIO_write(bio, data, length);
delete[] data;
openssl::EVP_PKEY *key = openssl::PEM_read_bio_PrivateKey(
bio, 0, password_callback, (void *)pass);
if (!key)
LOG(Gen, Error, "Invalid private key passphrase");
else
ret.reset(new PrivateKey(key, pass));
return ret;
}
PrivateKey::PrivateKey(openssl::EVP_PKEY *key, const PasswordCallback *pass)
: me(new Pimp(pass)), _key(key) {}
PrivateKey::~PrivateKey() {
if (_key)
EVP_PKEY_free(_key);
}
typedef int encrypt_decrypt_init(openssl::EVP_PKEY_CTX *ctx);
typedef int encrypt_decrypt(
openssl::EVP_PKEY_CTX *ctx,
unsigned char *out,
size_t *outlen,
const unsigned char *in,
size_t inlen);
bool encrypt_decrypt_function(
const std::iostream &in_dat,
std::iostream &out_data,
encrypt_decrypt_init *init_function,
encrypt_decrypt *function,
openssl::EVP_PKEY *key,
bool encrypt) {
UNUSED(encrypt);
openssl::EVP_PKEY_CTX *ctx = openssl::EVP_PKEY_CTX_new(key, 0);
bts_assert(ctx);
if (init_function)
bts_assert(init_function(ctx));
bts_assert(openssl::EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING));
char *name, *header;
unsigned char *in, *out;
long inlen;
size_t outlen;
int i = 0;
iostreambio biostream(const_cast<std::iostream *>(&in_dat));
if (!encrypt) {
auto bio = biostream.getBIO();
i = openssl::PEM_read_bio(bio, &name, &header, &in, &inlen);
} else {
std::iostream seeker(in_dat.rdbuf());
seeker.seekg(0, std::ios::end);
inlen = seeker.tellg();
seeker.seekg(0, std::ios::beg);
in = new unsigned char[inlen];
i = openssl::BIO_read(biostream.getBIO(), in, inlen);
}
// BIO_flush(biostream.getBIO());
BIO_free(biostream.getBIO());
bts_assert_msg(i > 0, "file not read, too short?");
bts_assert(function(ctx, NULL, &outlen, in, inlen)); // to get size
out = new unsigned char[outlen];
bts_assert(function(ctx, out, &outlen, in, inlen));
// if(encrypt)
// delete[] in;
std::string header_name = "BTS", header_header = "";
iostreambio biostream_out(const_cast<std::iostream *>(&out_data));
if (encrypt)
i = openssl::PEM_write_bio(
biostream_out.getBIO(), header_name.c_str(),
const_cast<char *>(header_header.c_str()), out, outlen);
else
i = openssl::BIO_write(biostream_out.getBIO(), out, outlen);
// BIO_flush(biostream_out.getBIO());
BIO_free(biostream_out.getBIO());
// delete[] out;
return (i > 0);
}
int private_encrypt_func(
openssl::EVP_PKEY_CTX *ctx,
unsigned char *out,
size_t *outlen,
const unsigned char *in,
size_t inlen) {
openssl::EVP_PKEY *pk = openssl::EVP_PKEY_CTX_get0_pkey(ctx);
auto rsa = openssl::EVP_PKEY_get0_RSA(pk);
if (out == 0) {
*outlen = openssl::RSA_size(rsa);
return -1;
} else {
return RSA_private_encrypt(
inlen, const_cast<unsigned char *>(in), out, rsa, RSA_PKCS1_PADDING);
}
}
int public_decrypt_func(
openssl::EVP_PKEY_CTX *ctx,
unsigned char *out,
size_t *outlen,
const unsigned char *in,
size_t inlen) {
openssl::EVP_PKEY *pk = openssl::EVP_PKEY_CTX_get0_pkey(ctx);
auto rsa = openssl::EVP_PKEY_get0_RSA(pk);
if (out == 0) {
*outlen = openssl::RSA_size(rsa);
return -1;
} else {
int i = RSA_public_decrypt(
inlen, const_cast<unsigned char *>(in), out, rsa, RSA_PKCS1_PADDING);
out[i] = '\0';
*outlen = i;
return i;
}
}
bool PrivateKey::private_encrypt(
const std::iostream &in_data, std::iostream &out_data) const {
return encrypt_decrypt_function(
in_data, out_data, 0, private_encrypt_func, _key, true);
}
bool PrivateKey::private_decrypt(
const std::iostream &in_data, std::iostream &out_data) const {
return encrypt_decrypt_function(
in_data, out_data, openssl::EVP_PKEY_decrypt_init,
openssl::EVP_PKEY_decrypt, _key, false);
}
bool PrivateKey::public_encrypt(
const std::iostream &in_data, std::iostream &out_data) const {
return encrypt_decrypt_function(
in_data, out_data, openssl::EVP_PKEY_encrypt_init,
openssl::EVP_PKEY_encrypt, _key, true);
}
bool PrivateKey::public_decrypt(
const std::iostream &in_data, std::iostream &out_data) const {
return encrypt_decrypt_function(
in_data, out_data, 0, public_decrypt_func, _key, false);
}
bool PrivateKey::encrypt(
const std::iostream &in_data, std::iostream &out_data) const {
return private_encrypt(in_data, out_data);
}
bool PrivateKey::decrypt(
const std::iostream &in_data, std::iostream &out_data) const {
return private_decrypt(in_data, out_data);
}
void PrivateKey::write(openssl::BIO *bio, CipherType::type cipher_type) const {
// see rsa.c ln 213
const openssl::EVP_CIPHER *ciph =
openssl::EVP_get_cipherbyname(CipherType::name[(int)cipher_type]);
bts_assert_msg(
cipher_type == CipherType::None || ciph, "unknown cipher type");
int nbytes = openssl::PEM_write_bio_PKCS8PrivateKey(
bio, _key, ciph, 0, 0, password_callback, (void *)me->password);
UNUSED(nbytes);
int err = BIO_flush(bio);
UNUSED(err);
}
void PrivateKey::write_file(
const std::string &filename, CipherType::type cipher_type) const {
openssl::BIO *bio = openssl::BIO_new_file(filename.c_str(), "w+");
write(bio, cipher_type);
BIO_free(bio);
}
void PrivateKey::write_stream(
std::iostream &stream, CipherType::type cipher_type) const {
iostreambio biostream_out(&stream);
write(biostream_out.getBIO(), cipher_type);
BIO_free(biostream_out.getBIO());
}
} // namespace crypto
} // namespace bts
#endif
-78
View File
@@ -1,78 +0,0 @@
#pragma once
#include "lib/utils/bts_no_copy.h"
#include "AlgoType.h"
#include "CipherType.h"
#include "PasswordCallback.h"
#include <memory>
#include <iostream>
#include <string>
namespace openssl {
struct evp_pkey_st;
typedef struct evp_pkey_st EVP_PKEY;
struct bio_st;
typedef struct bio_st BIO;
} // namespace openssl
namespace bts {
namespace crypto {
class PrivateKey;
class PublicKey;
typedef std::shared_ptr<PrivateKey> PrivateKeyPtr;
typedef std::shared_ptr<PublicKey> PublicKeyPtr;
class PrivateKey {
BTS_NO_COPY(PrivateKey);
public:
virtual ~PrivateKey();
static const PrivateKeyPtr
create(int bits, AlgoType::type, const PasswordCallback *pass = 0);
static const PrivateKeyPtr
read_file(const std::string &filename, const PasswordCallback *pass = 0);
static const PrivateKeyPtr
read_stream(const std::istream &stream, const PasswordCallback *pass = 0);
public:
virtual void write_file(
const std::string &filename,
CipherType::type cipher_type = CipherType::None) const;
virtual void write_stream(
std::iostream &stream,
CipherType::type cipher_type = CipherType::None) const;
virtual bool
encrypt(const std::iostream &in_dat, std::iostream &out_data) const;
virtual bool
decrypt(const std::iostream &in_dat, std::iostream &out_data) const;
const PublicKeyPtr public_key() const;
protected:
friend class PublicKey;
friend int password_callback(char *buf, int size, int rwflag, void *u);
PrivateKey(openssl::EVP_PKEY *key = 0, const PasswordCallback *pass = 0);
virtual void write(openssl::BIO *bio, CipherType::type cipher_type) const;
bool
private_encrypt(const std::iostream &in_dat, std::iostream &out_data) const;
bool
private_decrypt(const std::iostream &in_dat, std::iostream &out_data) const;
bool
public_encrypt(const std::iostream &in_dat, std::iostream &out_data) const;
bool
public_decrypt(const std::iostream &in_dat, std::iostream &out_data) const;
protected:
struct Pimp;
std::shared_ptr<Pimp> me;
openssl::EVP_PKEY *_key;
};
} // namespace crypto
} // namespace bts
-130
View File
@@ -1,130 +0,0 @@
#include "lib/utils/crypto/PrivateKey.h"
#ifndef _WIN32
#include "PublicKey.h"
#include "lib/config/protos/keycode.pb.h"
#include "lib/logging/logging.h"
#include "lib/utils/FileUtils.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/crypto/iostreambio.h"
#include "lib/utils/helper_macros.h"
#include <sstream>
namespace openssl {
#include <openssl/bio.h>
#include <openssl/bn.h>
#include <openssl/pem.h>
#include <openssl/rand.h>
#include <openssl/rsa.h>
} // namespace openssl
namespace bts::crypto {
PublicKey::PublicKey(openssl::EVP_PKEY *key) : PrivateKey(key) {}
PublicKey::PublicKey(const PrivateKey &key) : PrivateKey() {
auto *bio = openssl::BIO_new(openssl::BIO_s_mem());
bts_assert_msg(bio, "Failed to create new BIO");
int res = openssl::PEM_write_bio_PUBKEY(bio, key._key);
bts_assert_msgf(
res == 1, "Failed to read public key (PEM_write_bio_PUBKEY returned {})",
res);
res = BIO_flush(bio);
bts_assert_msgf(
res == 1, "Failed to flush BIO buffer (BIO_flush returned {})", res);
auto *k = openssl::PEM_read_bio_PUBKEY(bio, 0, 0, 0);
bts_assert_msg(k, "Failed to read public key");
BIO_free(bio);
bts_assert(k);
this->_key = k;
}
PublicKey::~PublicKey() = default;
const PublicKeyPtr PublicKey::create(const PrivateKey &private_key) {
PublicKeyPtr ret(new PublicKey(private_key));
return ret;
}
const PublicKeyPtr PublicKey::read_file(const std::string &path) {
auto *bio = openssl::BIO_new_file(path.c_str(), "r");
if (!bio) {
LOG(Gen, Error, "Failed to load public key file '{}'", path);
return nullptr;
}
auto *key = openssl::PEM_read_bio_PUBKEY(bio, 0, 0, 0);
if (!key) {
LOG(Gen, Error, "Failed to read public key file '{}'", path);
return nullptr;
}
return PublicKeyPtr(new PublicKey(key));
}
const PublicKeyPtr PublicKey::read(const bts::config::PublicKey &public_key) {
if (public_key.key_lines().size() > 0) {
return read_string(
fmt::to_string(fmt::join(public_key.key_lines(), "\n")));
}
return read_string(
bts::spartan::FileUtils::read_file(public_key.key_file_path()));
}
const PublicKeyPtr PublicKey::read_string(const std::string &public_key) {
std::stringstream oss;
oss << public_key;
bts::crypto::iostreambio biostream(&oss);
auto *key = openssl::PEM_read_bio_PUBKEY(biostream.getBIO(), 0, 0, 0);
if (!key) {
LOG(Gen, Error, "Invalid public key read");
return nullptr;
}
return PublicKeyPtr(new PublicKey(key));
}
const PublicKeyPtr PublicKey::read_stream(std::istream &stream) {
PublicKeyPtr ret;
stream.seekg(0, std::ios::end);
int length = stream.tellg();
stream.seekg(0, std::ios::beg);
char *data = new char[length];
stream.read(data, length);
openssl::BIO *bio = openssl::BIO_new(openssl::BIO_s_mem());
BIO_write(bio, data, length);
delete[] data;
openssl::EVP_PKEY *key = openssl::PEM_read_bio_PUBKEY(bio, 0, 0, 0);
if (!key)
LOG(Gen, Error, "Invalid public key read");
else
ret.reset(new PublicKey(key));
return ret;
}
void PublicKey::write(openssl::BIO *bio, CipherType::type) const {
// const openssl::EVP_CIPHER* ciph =
// openssl::EVP_get_cipherbyname(CipherType::name[(int)cipher_type]);
// bts_assert_msg(ciph, "unknown cipher type");
int nbytes = openssl::PEM_write_bio_PUBKEY(bio, _key);
UNUSED(nbytes);
int err = BIO_flush(bio);
UNUSED(err);
}
bool PublicKey::encrypt(
const std::iostream &in_data, std::iostream &out_data) const {
return public_encrypt(in_data, out_data);
}
bool PublicKey::decrypt(
const std::iostream &in_data, std::iostream &out_data) const {
return public_decrypt(in_data, out_data);
}
} // namespace bts::crypto
#endif
-36
View File
@@ -1,36 +0,0 @@
#pragma once
#include "PrivateKey.h"
#include "lib/utils/bts_no_copy.h"
namespace bts::config {
class PublicKey;
}
namespace bts::crypto {
class PublicKey : public PrivateKey {
public:
PublicKey(const PrivateKey &);
PublicKey(const PublicKey &) = delete;
PublicKey &operator=(const PublicKey &) = delete;
virtual ~PublicKey();
virtual bool encrypt(
const std::iostream &in_dat, std::iostream &out_data) const;
virtual bool decrypt(
const std::iostream &in_dat, std::iostream &out_data) const;
static const PublicKeyPtr create(const PrivateKey &private_key);
static const PublicKeyPtr read(const bts::config::PublicKey &public_key);
static const PublicKeyPtr read_file(const std::string &filename);
static const PublicKeyPtr read_stream(std::istream &stream);
static const PublicKeyPtr read_string(const std::string &public_key);
protected:
virtual void write(openssl::BIO *bio, CipherType::type cipher_type) const;
protected:
PublicKey(openssl::EVP_PKEY *);
};
} // namespace bts::crypto
-208
View File
@@ -1,208 +0,0 @@
#ifndef _WIN32
#include "iostreambio.h"
#include "lib/logging/logging.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/helper_macros.h"
namespace openssl {
#include "openssl_compat.h"
#include <openssl/bio.h>
#include <openssl/err.h>
} // namespace openssl
using namespace std;
using namespace openssl;
namespace bts {
namespace crypto {
const char *const iostreambio::name = "iostreambio";
const int iostreambio::type = BIO_TYPE_SOURCE_SINK;
/* callback functions for openssl */
static int bwrite(BIO *bio, const char *buf, int len);
static int bread(BIO *bio, char *buf, int len);
// static int bputs(BIO *bio, const char *);
static int bgets(BIO *bio, char *, int);
static long ctrl(BIO *bio, int cmd, long num, void *ptr);
static int create(BIO *bio);
// static int destroy(BIO *bio);
// static long (*callback_ctrl)(BIO *, int, bio_info_cb *);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
BIO_METHOD *BIO_iostream_method() {
static BIO_METHOD *iostream_method = NULL;
if (!iostream_method) {
iostream_method = BIO_meth_new(iostreambio::type, iostreambio::name);
BIO_meth_set_write(iostream_method, bwrite);
BIO_meth_set_read(iostream_method, bread);
BIO_meth_set_ctrl(iostream_method, ctrl);
BIO_meth_set_create(iostream_method, create);
BIO_meth_set_gets(iostream_method, bgets);
}
return iostream_method;
}
#else
static BIO_METHOD iostream_method = {
iostreambio::type,
iostreambio::name,
bwrite,
bread,
NULL, // bputs
bgets,
ctrl,
create,
NULL, // destroy,
NULL // callback_ctrl
};
BIO_METHOD *BIO_iostream_method() { return &iostream_method; }
#endif
iostreambio::iostreambio(iostream *stream) : _bio(0) {
_bio = BIO_new(BIO_iostream_method());
bts_assert(_bio);
BIO_set_data(_bio, (void *)stream);
}
BIO *iostreambio::getBIO() { return _bio; }
static int create(BIO *bio) {
BIO_set_data(bio, NULL);
BIO_set_shutdown(bio, 1);
BIO_set_init(bio, 1);
#if OPENSSL_VERSION_NUMBER < 0x10100000L
/* from bss_mem.c (openssl):
* bio->num is used to hold the value to return on 'empty', if it is
* 0, should_retry is not set
*
* => -1 means the caller can retry, 0: retry is useless
* it is set to 0 since the underlying stream is blocking
*/
bio->num = 0;
#endif
return 1;
}
static long ctrl(BIO *bio, int cmd, long num, void *ptr) {
UNUSED(ptr);
UNUSED(num);
long ret;
iostream *stream = reinterpret_cast<iostream *>(BIO_get_data(bio));
LOG(Gen, Warn, "iostreambio: ctrl called, cmd: {}, num: {}", cmd, num);
switch (cmd) {
case BIO_CTRL_PUSH:
case BIO_CTRL_POP:
ret = 0;
break;
// case BIO_CTRL_GET_CLOSE:
// ret = bio->shutdown;
// break;
// case BIO_CTRL_SET_CLOSE:
// bio->shutdown = (int)num;
// ret = 1;
// break;
case BIO_CTRL_FLUSH:
/* try to flush the underlying stream */
ret = 1;
try {
stream->flush();
} catch (
ios_base::failure &) { /* ignore, the badbit is checked instead */
}
if (stream->bad()) {
LOG(Gen, Error,
"iostreambio: underlying Stream went bad while flushing.");
ret = 0;
}
break;
default:
LOG(Gen, Error, "iostreambio: ctrl called with unhandled cmd: {}", cmd);
ret = 0;
break;
}
return ret;
}
static int bread(BIO *bio, char *buf, int len) {
iostream *stream = reinterpret_cast<iostream *>(BIO_get_data(bio));
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
int num_bytes = 0;
#else
streamsize num_bytes = bio->num;
#endif
try {
/* make sure to read at least 1 byte and then read as much as we can */
// num_bytes = stream->read(buf, 1).readsome(buf+1, len-1) + 1;
int i = 0;
while (stream->good() && i < len) {
buf[i++] = stream->get();
}
num_bytes = i;
} catch (ios_base::failure &) {
}
return num_bytes;
}
static int bgets(BIO *bio, char *buf, int len) {
iostream *stream = reinterpret_cast<iostream *>(BIO_get_data(bio));
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
int num_bytes = 0;
#else
streamsize num_bytes = bio->num;
#endif
try {
/* make sure to read at least 1 byte and then read as much as we can */
// num_bytes = stream->read(buf, 1).readsome(buf+1, len-1) + 1;
int i = 0;
while (stream->good() && i < len) {
buf[i] = stream->get();
if (buf[i] == '\n') {
i++;
break;
}
i++;
}
buf[i] = '\0';
num_bytes = i;
} catch (ios_base::failure &) {
}
return num_bytes;
}
static int bwrite(BIO *bio, const char *buf, int len) {
if (len == 0) {
return 0;
}
iostream *stream = reinterpret_cast<iostream *>(BIO_get_data(bio));
/* write the data */
try {
stream->write(buf, len);
} catch (ios_base::failure &) { /* ignore, the badbit is checked instead */
}
if (stream->bad()) {
LOG(Gen, Error, "iostreambio: underlying Stream went bad while writing.");
return 0;
}
/* flush the underlying stream */
try {
stream->flush();
} catch (ios_base::failure &) { /* ignore, the badbit is checked instead */
}
if (stream->bad()) {
LOG(Gen, Error,
"iostreambio: underlying Stream went bad while flushing (bwrite).");
return 0;
}
return len;
}
} // namespace crypto
} // namespace bts
#endif
-26
View File
@@ -1,26 +0,0 @@
#pragma once
namespace openssl {
struct bio_st;
typedef struct bio_st BIO;
} // namespace openssl
#include <iostream>
namespace bts {
namespace crypto {
class iostreambio {
public:
explicit iostreambio(std::iostream *stream);
openssl::BIO *getBIO();
static const char *const name;
static const int type;
private:
openssl::BIO *_bio;
};
} // namespace crypto
} // namespace bts
-39
View File
@@ -1,39 +0,0 @@
#ifndef OPENSSL_COMPAT_H
#define OPENSSL_COMPAT_H
#include <openssl/crypto.h>
#if OPENSSL_VERSION_NUMBER < 0x10100000L
#include <openssl/evp.h>
#include <openssl/hmac.h>
static inline EVP_MD_CTX *EVP_MD_CTX_new() {
EVP_MD_CTX *ctx;
ctx = (EVP_MD_CTX *)OPENSSL_malloc(sizeof(EVP_MD_CTX));
EVP_MD_CTX_init(ctx);
return ctx;
}
#define EVP_MD_CTX_free(c) \
if (c != NULL) \
OPENSSL_free(c)
static inline HMAC_CTX *HMAC_CTX_new() {
HMAC_CTX *ctx;
ctx = (HMAC_CTX *)OPENSSL_malloc(sizeof(HMAC_CTX));
HMAC_CTX_init(ctx);
return ctx;
}
#define HMAC_CTX_free(c) \
if (c != NULL) \
OPENSSL_free(c)
#define BIO_get_data(b) b->ptr
#define BIO_set_data(b, v) b->ptr = v
#define BIO_set_shutdown(b, v) b->shutdown = v
#define BIO_set_init(b, v) b->init = v
#endif /* OPENSSL_VERSION_NUMBER */
#endif /* OPENSSL_COMPAT_H */
+11
View File
@@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.23)
project(bts-cme-websocket)
file(
GLOB_RECURSE ${PROJECT_NAME}_Sources
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
${PROJECT_NAME}/*.cpp)
add_library(${PROJECT_NAME} ${${PROJECT_NAME}_Sources})
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(${PROJECT_NAME} PUBLIC bts-system bts-http bts-websocket)
@@ -0,0 +1,16 @@
#pragma once
#include "bts-cme-websocket/messages/cme_message.h"
#include "bts-system/delegate.h"
#include <string>
namespace bts::websocket {
class CmeMessageHandler {
public:
virtual ~CmeMessageHandler() = default;
virtual void on_message(const CmeMessage &msg) = 0;
};
} // namespace bts::websocket
@@ -0,0 +1,51 @@
#pragma once
#include "bts-cme-websocket/cme_message_handler.h"
#include "bts-cme-websocket/messages/cme_message.h"
#include "bts-system/delegate.h"
#include <string>
namespace bts::websocket {
/**
* CmeMessageHandler that provides a bindable callback to handle payloads
* (parsed from json to \tparam T).
*/
template <class T = nlohmann::json>
class CmeMessageHandlerProxy : public CmeMessageHandler {
public:
using Callback = system::Delegate<void(T)>;
constexpr Callback &callback();
void on_message(const CmeMessage &msg) override;
private:
Callback callback_;
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
template <class T>
constexpr typename CmeMessageHandlerProxy<T>::Callback &
CmeMessageHandlerProxy<T>::callback() {
return callback_;
}
template <>
inline void CmeMessageHandlerProxy<nlohmann::json>::on_message(
const CmeMessage &msg) {
callback_(msg);
}
template <class T>
inline void CmeMessageHandlerProxy<T>::on_message(const CmeMessage &msg) {
if (msg.payload) {
callback_(msg.payload->get<T>());
}
}
} // namespace bts::websocket
@@ -0,0 +1,19 @@
#pragma once
#include "bts-cme-websocket/cme_message_handler.h"
#include "bts-cme-websocket/messages/cme_message.h"
#include "bts-system/json/json_dumper.h"
#include <nlohmann/json.hpp>
#include <vector>
namespace bts::websocket {
struct CmeMessageLogger : public bts::websocket::CmeMessageHandler {
void on_message(const CmeMessage &msg) override {
LOG(Md, Info, "CmeMessageLogger::on_message.\n{}",
bts::system::json::JsonDumper::dump(msg));
}
};
} // namespace bts::websocket
@@ -0,0 +1,10 @@
#pragma once
#include "bts-system/value_wrapper.h"
namespace bts::websocket {
using CmeSubscriptionId =
system::ValueWrapper<struct CmeSubscriptionTag, std::uint32_t>;
} // namespace bts::websocket
@@ -0,0 +1,83 @@
#pragma once
#include "bts-system/enum_macros.h"
#include "bts-system/fmt_macros.h"
#include "bts-system/hash_macros.h"
#include <nlohmann/json.hpp>
namespace bts::websocket {
// clang-format off
#define FOR_ALL_VALUES(per_value) \
per_value(CmeSubscriptionTopic, NONE) \
per_value(CmeSubscriptionTopic, TRADE_TICKER) \
per_value(CmeSubscriptionTopic, TOP_OF_BOOK) \
per_value(CmeSubscriptionTopic, STAT)
// clang-format on
/**
* Represents subscription topics (message types) at Cme. The names aren't
* verbatim matches with the subscriptionMessageType fields they represent in
* the websocket request json. See CmeWebsocketRequestFactory for details.
*/
ENUM_CLASS_DEF(CmeSubscriptionTopic, FOR_ALL_VALUES)
inline std::string topic_outgoing_name(CmeSubscriptionTopic topic) {
switch (topic.value) {
case CmeSubscriptionTopic::NONE:
return {};
case CmeSubscriptionTopic::TRADE_TICKER:
return "TRD";
case CmeSubscriptionTopic::TOP_OF_BOOK:
return "TOB";
default:
return topic.name();
}
}
inline std::string topic_incoming_name(CmeSubscriptionTopic topic) {
switch (topic.value) {
case CmeSubscriptionTopic::NONE:
return {};
case CmeSubscriptionTopic::TRADE_TICKER:
return "TRD";
case CmeSubscriptionTopic::TOP_OF_BOOK:
return "TOB";
default:
return topic.name();
}
}
// inline CmeSubscriptionTopic topic_from_incoming_name(const std::string &name) {
// if (name == "TS") {
// return CmeSubscriptionTopic::TRADE_TICKER;
// }
// if (name == "OBTS") {
// return CmeSubscriptionTopic::TOP_OF_BOOK;
// }
// return CmeSubscriptionTopic::NONE;
// }
#undef FOR_ALL_VALUES
inline void to_json(nlohmann::json &j, const CmeSubscriptionTopic &t) {
j = t.name();
}
inline void from_json(const nlohmann::json &j, CmeSubscriptionTopic &t) {
if (!CmeSubscriptionTopic::parse(j.get<std::string>(), &t)) {
throw std::runtime_error{fmt::format(
"Failed to parse CmeSubscriptionTopic from '{}'", j.dump())};
}
}
} // namespace bts::websocket
FMT_IMPL(bts::websocket::CmeSubscriptionTopic, t, "{}", t.name());
#define FOR_HASH_MEMBERS(op) op(value)
HASH_IMPL(bts::websocket::CmeSubscriptionTopic, FOR_HASH_MEMBERS);
#undef FOR_HASH_MEMBERS
@@ -0,0 +1,250 @@
#include "cme_websocket_session.h"
#include "bts-cme-websocket/cme_message_handler.h"
#include "bts-cme-websocket/cme_subscription_id.h"
#include "bts-cme-websocket/cme_subscription_topic.h"
#include "bts-cme-websocket/cme_websocket_session_config.h"
#include "bts-cme-websocket/messages/cme_message.h"
#include "bts-cme-websocket/messages/cme_message_header.h"
#include "bts-cme-websocket/messages/cme_product_key.h"
#include "bts-cme-websocket/messages/cme_top_of_book_payload.h"
#include "bts-cme-websocket/messages/cme_trade_summary_payload.h"
#include "bts-cme-websocket/messages/cme_websocket_request.h"
#include "bts-cme-websocket/messages/cme_websocket_request_factory.h"
#include "bts-cme-websocket/ssl_certificate_verifier.h"
#include "bts-cme-websocket/tls_initializer.h"
#include "bts-http/http_client.h"
#include "bts-http/http_request.h"
#include "bts-http/http_response.h"
#include "bts-http/oauth_request.h"
#include "bts-http/oauth_requestor.h"
#include "bts-http/oauth_response.h"
#include "bts-system/UNUSED.h"
#include "bts-system/bts_assert.h"
#include "bts-system/equality_macros.h"
#include "bts-system/hash_macros.h"
#include "bts-system/json/json_dumper.h"
#include "bts-system/json/json_reader.h"
#include "bts-system/overloaded.h"
#include "bts-system/time_span.h"
#include "bts-system/time_stamp.h"
#include "bts-system/tuple_cutter.h"
#include "bts-websocket/websocket_client.h"
#include <atomic>
#include <curl/curl.h>
#include <curl/easy.h>
#include <fmt/format.h>
#include <exception>
#include <functional>
#include <optional>
#include <stdexcept>
#include <string>
#include <thread>
#include <type_traits>
#include <unordered_map>
#include <variant>
#include <vector>
#include <websocketpp/common/connection_hdl.hpp>
#include <websocketpp/common/memory.hpp>
#include <websocketpp/common/system_error.hpp>
#include <websocketpp/common/thread.hpp>
#include <websocketpp/config/asio_client.hpp>
#include <websocketpp/config/core_client.hpp>
#include <websocketpp/roles/client_endpoint.hpp>
#define FOR_ALL_SUBSCRIPTION_KEY_MEMBERS(op) op(topic) op(product)
namespace bts::websocket {
EQUALS_IMPL(
CmeWebsocketSession::SubscriptionKey, FOR_ALL_SUBSCRIPTION_KEY_MEMBERS)
} // namespace bts::websocket
HASH_IMPL(
bts::websocket::CmeWebsocketSession::SubscriptionKey,
FOR_ALL_SUBSCRIPTION_KEY_MEMBERS);
#undef FOR_ALL_SUBSCRIPTION_KEY_MEMBERS
namespace bts::websocket {
using namespace std::chrono_literals;
using bts::system::TupleCutter;
using nlohmann::json;
using std::optional;
using std::pair;
using std::shared_ptr;
using std::string;
using std::tuple;
using std::unordered_map;
using std::vector;
using system::json::JsonDumper;
using SslContext = boost::asio::ssl::context;
// ws endpoint http://markets.api.uat.cmegroup.com/marketdatastream/v1
#define _ASSERT_MSGF(condition, ...) \
bts_assert_msgf(condition, "CmeWebsocketSession::" __VA_ARGS__)
#define _LOG(level, ...) LOG(md, level, "CmeWebsocketSession::" __VA_ARGS__)
#define _DIAG(...) _LOG(Diag, __VA_ARGS__)
#define _INFO(...) _LOG(Info, __VA_ARGS__)
#define _WARN(...) _LOG(Warn, __VA_ARGS__)
#define _ERROR(...) _LOG(Error, __VA_ARGS__)
struct CmeWebsocketSession::PImpl {
PImpl(const CmeWebsocketSessionConfig &cfg)
: cfg(cfg), client(cfg.websocket) {}
CmeWebsocketSessionConfig cfg;
WebsocketClient client;
CmeSubscriptionId sub_id_counter{0};
string server_name;
std::optional<std::thread> run_thread;
int session_id{session_id_counter_++};
CmeWebsocketRequestFactory req_factory_;
TradeTickerCallback trade_ticker_callback;
TopOfBookCallback top_of_book_callback;
~PImpl() {
if (run_thread) {
run_thread->join();
}
}
void on_login_status(const CmeMessage &msg) {
_INFO("on_login_status:\n{}", JsonDumper::dump(msg));
if (msg.payload->at("status").get<string>() != "authenticated") {
_ERROR(
"on_login_status. status not authenticated:\n{}",
JsonDumper::dump(msg));
return;
}
}
void on_subscription_status(const CmeMessage &msg) {
// If there is an error, log it and return.
_INFO("on_subscription_status:\n{}", JsonDumper::dump(msg));
if (msg.payload->at("status").get<string>() != "subscribed") {
_ERROR(
"on_subscription_status. status not subscribed:\n{}",
JsonDumper::dump(msg));
}
}
void on_heartbeat(const CmeMessage & /*msg*/) {
// Placeholder for hearbeat handler
}
void on_message(const string &msg_str) {
_INFO("on_message\n{}", msg_str);
CmeMessage msg;
try {
nlohmann::json::parse(msg_str).get_to(msg);
} catch (const std::exception &ex) {
_ERROR("on_message. Failed to parse json. {}\n{}", ex.what(), msg_str);
return;
}
if (msg.header.messageType == "heartbeat") {
on_heartbeat(msg);
return;
}
if (msg.header.messageType == "loginStatus") {
on_login_status(msg);
return;
}
if (msg.header.messageType == "subscriptionStatus") {
on_subscription_status(msg);
return;
}
if (msg.header.messageType == "TRD") {
trade_ticker_callback(
msg.payload->get<vector<CmeTradeSummaryPayload>>());
return;
}
if (msg.header.messageType == "TOB") {
top_of_book_callback(msg.payload->get<vector<CmeTopOfBookPayload>>());
return;
}
_ERROR("Received message of unknown type:\n{}", msg_str);
// auto topic = topic_from_incoming_name(msg.header.messageType);
// switch (topic.value) {
// case CmeSubscriptionTopic::TRADE_TICKER: {
// auto trade_summary = msg.payload->get<CmeTradeSummaryPayload>();
// trade_ticker_callback(trade_summary);
// break;
// }
// case CmeSubscriptionTopic::TOP_OF_BOOK: {
// auto top_of_book = msg.payload->get<CmeTopOfBookPayload>();
// top_of_book_callback(top_of_book);
// break;
// }
// default:
// _ERROR("Received message of unknown type:\n{}", msg_str);
// break;
// }
}
private:
static std::atomic<int> session_id_counter_;
};
CmeWebsocketSession::CmeWebsocketSession(CmeWebsocketSessionConfig cfg)
: me_(std::make_unique<PImpl>(std::move(cfg))) {
me_->client.message_handler().bind<&PImpl::on_message>(*me_);
}
CmeWebsocketSession::~CmeWebsocketSession() = default;
std::atomic_int CmeWebsocketSession::PImpl::session_id_counter_ = 0;
CmeWebsocketSession::TradeTickerCallback &
CmeWebsocketSession::trade_ticker_callback() {
return me_->trade_ticker_callback;
}
CmeWebsocketSession::TopOfBookCallback &
CmeWebsocketSession::top_of_book_callback() {
return me_->top_of_book_callback;
}
void CmeWebsocketSession::start() { me_->client.connect(); }
void CmeWebsocketSession::subscribe(vector<SubscriptionKey> subs) {
vector<CmeProductKey> products;
vector<CmeSubscriptionTopic> topics;
for (const auto &s : subs) {
auto &[t, p] = s;
if (std::find(products.begin(), products.end(), p) == products.end()) {
products.push_back(p);
}
if (std::find(topics.begin(), topics.end(), t) == topics.end()) {
topics.push_back(t);
}
}
auto sub_req = me_->req_factory_.subscription(products, topics);
if (!me_->client.is_connected()) {
throw std::runtime_error{
"Unable to add subscription because client is not connected."};
}
me_->client.send_text(JsonDumper::dump(sub_req));
}
bool CmeWebsocketSession::unsubscribe(std::vector<SubscriptionKey> subs) {
UNUSED(subs);
// TODO
// auto it = me_->topic_handlers.find(id);
// if (it == me_->subscriptions.end()) {
// return false;
// }
// CmeWebsocketRequest req = it->second.request;
// req.header.messageType = "unsubscribe";
// req.header.sentTime = core::TimeStamp::now();
// me_->subscriptions.erase(it);
// me_->client.send_text(JsonDumper::dump(req));
return true;
}
} // namespace bts::websocket
@@ -0,0 +1,64 @@
#pragma once
#include "bts-cme-websocket/cme_message_handler.h"
#include "bts-cme-websocket/cme_subscription_id.h"
#include "bts-cme-websocket/cme_subscription_topic.h"
#include "bts-cme-websocket/messages/cme_product_key.h"
#include "bts-cme-websocket/messages/cme_top_of_book_payload.h"
#include "bts-cme-websocket/messages/cme_trade_summary_payload.h"
#include "bts-cme-websocket/messages/cme_websocket_request.h"
#include "bts-http/url.h"
#include "bts-system/delegate.h"
#include "bts-system/value_wrapper.h"
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <cstddef>
#include <curl/curl.h>
#include <iostream>
#include <memory>
#include <nlohmann/json.hpp>
#include <string>
#include <vector>
namespace bts::websocket {
class CmeOauthConfig;
class CmeWebsocketSessionConfig;
class CmeWebsocketSession {
public:
struct SubscriptionKey {
CmeSubscriptionTopic topic;
CmeProductKey product;
};
using TradeTickerCallback =
system::Delegate<void(const std::vector<CmeTradeSummaryPayload> &)>;
using TopOfBookCallback =
system::Delegate<void(const std::vector<CmeTopOfBookPayload> &)>;
explicit CmeWebsocketSession(CmeWebsocketSessionConfig cfg);
~CmeWebsocketSession();
void start();
TradeTickerCallback &trade_ticker_callback();
TopOfBookCallback &top_of_book_callback();
/**
* Subscribes to topics for the specified products (a handler is
* paired with each topic).
*/
void subscribe(std::vector<SubscriptionKey> subs);
bool unsubscribe(std::vector<SubscriptionKey> subs);
private:
struct PImpl;
std::unique_ptr<PImpl> me_;
};
} // namespace bts::websocket
@@ -0,0 +1,61 @@
#pragma once
#include "bts-cme-websocket/cme_subscription_topic.h"
#include "bts-cme-websocket/messages/cme_product_key.h"
#include "bts-cme-websocket/messages/cme_websocket_request.h"
#include "bts-http/oauth_request.h"
#include "bts-http/url.h"
#include "bts-system/json/json_reader.h"
#include "bts-system/threading/thread_config.h"
#include "bts-websocket/websocket_client_config.h"
#include <nlohmann/json.hpp>
#include <stdexcept>
#include <string>
#include <vector>
namespace bts::websocket {
class CmeWebsocketSessionConfig {
public:
struct SubscriptionConfig {
std::vector<CmeSubscriptionTopic> topics;
std::vector<CmeProductKey> products;
};
// Needed to obtain the access token (ignored if access token is non-empty)
websocket::WebsocketClientConfig websocket;
std::vector<SubscriptionConfig> subscriptions;
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline void to_json(
nlohmann::json &j,
const CmeWebsocketSessionConfig::SubscriptionConfig &cfg) {
j["topics"] = cfg.topics;
j["products"] = cfg.products;
}
inline void from_json(
const nlohmann::json &j,
CmeWebsocketSessionConfig::SubscriptionConfig &cfg) {
system::json::JsonReader jr{&j};
jr.read("topics", cfg.topics);
jr.read("products", cfg.products);
};
inline void to_json(nlohmann::json &j, const CmeWebsocketSessionConfig &cfg) {
j["websocket"] = cfg.websocket;
j["subscriptions"] = cfg.subscriptions;
}
inline void from_json(const nlohmann::json &j, CmeWebsocketSessionConfig &cfg) {
system::json::JsonReader jr{&j};
jr.read("websocket", cfg.websocket);
jr.read("subscriptions", cfg.subscriptions);
};
} // namespace bts::websocket
@@ -0,0 +1,35 @@
#pragma once
#include "bts-system/json/json_reader.h"
#include "bts-system/time_stamp.h"
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/json.hpp>
namespace bts::websocket {
class CmeBookLevel {
public:
system::TimeStamp lastUpdateTime;
std::uint32_t orderCnt;
double price;
double qty;
};
inline void to_json(nlohmann::json &j, const CmeBookLevel &l) {
j["lastUpdateTime"] = l.lastUpdateTime.to_iso_string();
j["orderCnt"] = l.orderCnt;
j["price"] = l.price;
j["qty"] = l.qty;
}
inline void from_json(const nlohmann::json &j, CmeBookLevel &l) {
l.lastUpdateTime = system::TimeStamp::from_iso_string(
j.at("lastUpdateTime").get<std::string>());
system::json::JsonReader jr{&j};
jr.read("orderCnt", l.orderCnt);
jr.read("price", l.price);
jr.read("qty", l.qty);
};
} // namespace bts::websocket
@@ -0,0 +1,91 @@
#pragma once
#include "bts-system/time_stamp.h"
#include <nlohmann/json.hpp>
#include <optional>
#include <string>
namespace bts::websocket {
class CmeInstrument {
public:
std::string guid;
std::int64_t guidInt;
std::int64_t productGuidInt;
std::string instrumentName;
std::optional<std::string> globexSecurityId;
std::string globexSymbol;
core::TimeStamp positionRemovalDate;
core::TimeStamp lastUpdated;
core::TimeStamp lastTradeDate;
std::optional<core::TimeStamp> lastNoticeDate;
core::TimeStamp lastDeliveryDate;
std::optional<core::TimeStamp> globexLastTradeDate;
std::optional<core::TimeStamp> globexFirstTradeDate;
core::TimeStamp firstTradeDate;
std::optional<core::TimeStamp> firstNoticeDate;
core::TimeStamp firstDeliveryDate;
core::TimeStamp finalSettlementDate;
std::optional<core::TimeStamp> initialInventoryDueDate;
std::optional<core::TimeStamp> lastInventoryDueDate;
std::optional<core::TimeStamp> lastEfpDate;
std::optional<double> strikePxCcy;
std::optional<std::string> putCallIndicator;
std::optional<double> priceBandD1;
std::optional<double> priceBand;
std::optional<std::string> zeroPriceEligible;
std::optional<double> originalContractSize;
core::TimeStamp contractMonth;
std::string flexIndicator;
// TODO: like 40 more fields :()!
template <class T>
static void write_opt(
const std::optional<T> &dest,
nlohmann::json &j,
const std::string &field) {
if (dest) {
j.at(field) = *dest;
}
}
template <class T>
static void read_opt(
nlohmann::json &j, const std::string &field, std::optional<T> &dest) {
auto it = j.find(field);
if (it != j.end()) {
it->get_to(dest.emplace());
}
}
};
inline void to_json(nlohmann ::json &j, const CmeInstrument &i) {
j["guid"] = i.guid;
j["guidInt"] = i.guidInt;
j["productGuidInt"] = i.productGuidInt;
j["instrumentName"] = i.instrumentName;
if (i.globexSecurityId) {
j["globexSecurityId"] = *i.globexSecurityId;
}
j["globexSymbol"] = i.globexSymbol;
if (i.positionRemovalDate) {
// TODO
// j["positionRemovalDate"] = {};
}
j["lastUpdated"] = i.lastUpdated;
j["lastTradeDate"] = i.lastTradeDate;
}
inline void from_json(const nlohmann ::json &j, CmeInstrument &i) {
j.at("guid").get_to(i.guid);
j.at("guidInt").get_to(i.guidInt);
j.at("productGuidInt").get_to(i.productGuidInt);
j.at("instrumentName").get_to(i.instrumentName);
j.at("globexSecurityId").get_to(i.globexSecurityId);
j.at("globexSymbol").get_to(i.globexSymbol);
j.at("positionRemovalDate").get_to(i.positionRemovalDate);
j.at("lastUpdated").get_to(i.lastUpdated);
j.at("lastTradeDate").get_to(i.lastTradeDate);
};
} // namespace bts::websocket
@@ -0,0 +1,49 @@
#pragma once
#include "bts-system/json/json_reader.h"
#include <nlohmann/json.hpp>
#include <string>
namespace bts::websocket {
class CmeInstrumentMetadata {
public:
std::string definitionSource;
std::string exchangeMic;
std::int64_t id;
// Note: this typo is intentional (it reflects an actual typo in Cme
// payloads, where marketSegmentId is spelled with a 'q' instead of 'g')
std::string marketSeqmentId;
std::string periodCode;
std::string productCode;
std::string productType;
std::string productGroup;
std::string symbol;
};
inline void to_json(nlohmann::json &j, const CmeInstrumentMetadata &i) {
j["definitionSource"] = i.definitionSource;
j["exchangeMic"] = i.exchangeMic;
j["id"] = i.id;
j["marketSeqmentId"] = i.marketSeqmentId;
j["periodCode"] = i.periodCode;
j["productCode"] = i.productCode;
j["productType"] = i.productType;
j["productGroup"] = i.productGroup;
j["symbol"] = i.symbol;
}
inline void from_json(const nlohmann::json &j, CmeInstrumentMetadata &i) {
system::json::JsonReader jr{&j};
j.at("definitionSource").get_to(i.definitionSource);
j.at("exchangeMic").get_to(i.exchangeMic);
jr.read("id", i.id);
jr.read("marketSeqmentId", i.marketSeqmentId);
j.at("periodCode").get_to(i.periodCode);
j.at("productCode").get_to(i.productCode);
j.at("productType").get_to(i.productType);
j.at("productGroup").get_to(i.productGroup);
j.at("symbol").get_to(i.symbol);
};
} // namespace bts::websocket
@@ -0,0 +1,35 @@
#pragma once
#include "bts-cme-websocket/messages/cme_message_header.h"
#include <nlohmann/json.hpp>
namespace bts::websocket {
template <class Payload = nlohmann::json>
class CmeMessageT {
public:
CmeMessageHeader header;
std::optional<Payload> payload;
};
using CmeMessage = CmeMessageT<nlohmann::json>;
template <class Payload>
inline void to_json(nlohmann::json &j, const CmeMessageT<Payload> &msg) {
j["header"] = msg.header;
if (msg.payload) {
j["payload"] = *msg.payload;
}
}
template <class Payload>
inline void from_json(const nlohmann::json &j, CmeMessageT<Payload> &msg) {
j.at("header").get_to(msg.header);
auto payload = j.find("payload");
if (payload != j.end()) {
payload->get_to(msg.payload.emplace());
}
};
} // namespace bts::websocket
@@ -0,0 +1,53 @@
#pragma once
#include "bts-cme-websocket/cme_subscription_id.h"
#include "bts-system/json/json_reader.h"
#include "bts-system/time_stamp.h"
#include <nlohmann/json.hpp>
#include <nlohmann/json_fwd.hpp>
#include <optional>
#include <string>
namespace bts::websocket {
class CmeMessageHeader {
public:
std::string messageType{};
std::optional<std::string> requestId{};
std::optional<system::TimeStamp> sentTime{};
std::string token{};
std::optional<std::string> version{};
};
inline void to_json(nlohmann::json &j, const CmeMessageHeader &h) {
j["messageType"] = h.messageType;
if (h.sentTime) {
j["sentTime"] = h.sentTime->to_iso_extended_string();
}
if (h.version) {
j["version"] = *h.version;
}
if (h.requestId) {
j["requestId"] = *h.requestId;
}
}
inline void from_json(const nlohmann ::json &j, CmeMessageHeader &h) {
j.at("messageType").get_to(h.messageType);
std::string sentTime;
h.sentTime =
system::json::JsonReader{&j}.read("sentTime", sentTime)
? decltype(h.sentTime){system::TimeStamp::from_iso_string(sentTime)}
: std::nullopt;
std::string version;
h.version = system::json::JsonReader{&j}.read("version", version)
? decltype(h.version){version}
: std::nullopt;
std::string requestId;
h.requestId = system::json::JsonReader{&j}.read("requestId", requestId)
? decltype(h.requestId){requestId}
: std::nullopt;
};
} // namespace bts::websocket
@@ -0,0 +1,29 @@
#pragma once
#include "bts-system/equality_macros.h"
#include "bts-system/hash_macros.h"
#include <nlohmann/json.hpp>
#include <string>
#define FOR_ALL_VALUES(op) op(exchangeMic) op(productType) op(productCode)
namespace bts::websocket {
class CmeProductKey {
public:
std::string exchangeMic;
std::string productType;
std::string productCode;
};
EQUALS_IMPL(CmeProductKey, FOR_ALL_VALUES)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
CmeProductKey, exchangeMic, productType, productCode);
} // namespace bts::websocket
HASH_IMPL(bts::websocket::CmeProductKey, FOR_ALL_VALUES);
#undef FOR_ALL_VALUES
@@ -0,0 +1,41 @@
#pragma once
#include "bts-cme-websocket/messages/cme_book_level.h"
#include "bts-cme-websocket/messages/cme_instrument_metadata.h"
#include "bts-system/logging.h"
#include <nlohmann/detail/macro_scope.hpp>
#include <string>
#include <vector>
namespace bts::websocket {
class CmeTopOfBookPayload {
public:
CmeInstrumentMetadata instrument;
std::vector<CmeBookLevel> askLevel;
std::vector<CmeBookLevel> bidLevel;
};
inline void to_json(nlohmann ::json &j, const CmeTopOfBookPayload &tob) {
j["instrument"] = tob.instrument;
if (!tob.askLevel.empty()) {
j["askLevel"] = tob.askLevel;
}
if (!tob.bidLevel.empty()) {
j["bidLevel"] = tob.bidLevel;
}
}
inline void from_json(const nlohmann ::json &j, CmeTopOfBookPayload &tob) {
j.at("instrument").get_to(tob.instrument);
auto it = j.find("askLevel");
if (it != j.end()) {
it->get_to(tob.askLevel);
}
it = j.find("bidLevel");
if (it != j.end()) {
it->get_to(tob.bidLevel);
}
}
} // namespace bts::websocket
@@ -0,0 +1,75 @@
#pragma once
#include "bts-cme-websocket/messages/cme_instrument_metadata.h"
#include "bts-system/json/json_reader.h"
#include <nlohmann/json.hpp>
#include <string>
namespace bts::websocket {
class CmeTradeSummaryPayload {
public:
// struct Instrument {
// std::string definitionSource;
// std::string exchangeMic;
// std::string id;
// std::string marketSegmentId;
// std::string periodCode;
// std::string productCode;
// std::string productGroup;
// std::string productType;
// std::string symbol;
// } instrument;
CmeInstrumentMetadata instrument;
// TODO: convert to timestamp
std::string lastUpdateTime;
struct TradeSummary {
std::int64_t aggressorSide;
std::int64_t mdTradeEntryId;
double tradePrice;
double tradeQty;
std::uint32_t tradeOrderCount;
std::string tradeUpdateAction;
} tradeSummary;
};
// NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
// CmeTradeSummaryPayload::Instrument,
// definitionSource,
// exchangeMic,
// id,
// marketSegmentId,
// periodCode,
// productCode,
// productGroup,
// productType,
// symbol)
inline void to_json(
nlohmann ::json &j, const CmeTradeSummaryPayload::TradeSummary &ts) {
j["aggressorSide"] = ts.aggressorSide;
j["mdTradeEntryId"] = ts.mdTradeEntryId;
j["tradePrice"] = ts.tradePrice;
j["tradeQty"] = ts.tradeQty;
j["tradeOrderCount"] = ts.tradeOrderCount;
j["tradeUpdateAction"] = ts.tradeUpdateAction;
}
inline void from_json(
const nlohmann ::json &j, CmeTradeSummaryPayload ::TradeSummary &ts) {
system::json::JsonReader jr{&j};
jr.read("aggressorSide", ts.aggressorSide);
jr.read("mdTradeEntryId", ts.mdTradeEntryId);
jr.read("tradePrice", ts.tradePrice);
jr.read("tradeQty", ts.tradeQty);
jr.read("tradeOrderCount", ts.tradeOrderCount);
jr.read("tradeUpdateAction", ts.tradeUpdateAction);
}
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
CmeTradeSummaryPayload, instrument, lastUpdateTime, tradeSummary)
} // namespace bts::websocket
@@ -0,0 +1,10 @@
#pragma once
#include "bts-cme-websocket/messages/cme_message.h"
#include "bts-cme-websocket/messages/cme_websocket_request_payload.h"
namespace bts::websocket {
using CmeWebsocketRequest = CmeMessageT<CmeWebsocketRequestPayload>;
} // namespace bts::websocket
@@ -0,0 +1,64 @@
#pragma once
#include "bts-cme-websocket/cme_subscription_topic.h"
#include "bts-cme-websocket/messages/cme_message_header.h"
#include "bts-cme-websocket/messages/cme_product_key.h"
#include "bts-cme-websocket/messages/cme_websocket_request.h"
#include "bts-system/time_stamp.h"
#include <nlohmann/json.hpp>
namespace bts::websocket {
class CmeWebsocketRequestFactory {
public:
CmeWebsocketRequest subscription(
const std::vector<CmeProductKey> &subscriptions,
const std::vector<CmeSubscriptionTopic> &topics);
CmeWebsocketRequest unsubscription(
const std::vector<CmeProductKey> &subscriptions,
const std::vector<CmeSubscriptionTopic> &topics);
private:
// Ensure unique ids for each request
std::atomic<std::uint32_t> id_counter_;
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline CmeWebsocketRequest CmeWebsocketRequestFactory::subscription(
const std::vector<CmeProductKey> &products,
const std::vector<CmeSubscriptionTopic> &topics) {
CmeWebsocketRequest sub;
sub.payload.emplace();
sub.payload->subscriptions = std::move(products);
sub.payload->subscriptionMessageTypes.reserve(topics.size());
for (const auto t : topics) {
sub.payload->subscriptionMessageTypes.push_back(topic_outgoing_name(t));
}
sub.header.requestId = std::to_string(id_counter_++);
sub.header.messageType = "subscribe";
sub.header.sentTime = system::TimeStamp::now();
return sub;
}
inline CmeWebsocketRequest CmeWebsocketRequestFactory::unsubscription(
const std::vector<CmeProductKey> &products,
const std::vector<CmeSubscriptionTopic> &topics) {
CmeWebsocketRequest sub;
sub.payload.emplace();
sub.payload->subscriptions = std::move(products);
sub.payload->subscriptionMessageTypes.resize(topics.size());
for (const auto t : topics) {
sub.payload->subscriptionMessageTypes.push_back(topic_outgoing_name(t));
}
sub.header.requestId = std::to_string(id_counter_++);
sub.header.messageType = "unsubscribe";
sub.header.sentTime = system::TimeStamp::now();
return sub;
}
} // namespace bts::websocket
@@ -0,0 +1,18 @@
#pragma once
#include "bts-cme-websocket/cme_subscription_topic.h"
#include "bts-cme-websocket/messages/cme_product_key.h"
#include <nlohmann/json.hpp>
namespace bts::websocket {
struct CmeWebsocketRequestPayload {
std::vector<CmeProductKey> subscriptions;
std::vector<std::string> subscriptionMessageTypes;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
CmeWebsocketRequestPayload, subscriptions, subscriptionMessageTypes);
} // namespace bts::websocket
@@ -0,0 +1,17 @@
#pragma once
#include <cstdint>
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/json.hpp>
namespace bts::websocket {
class PrivateOrderQty {
public:
std::int64_t orderId;
double lastOrderQty;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(PrivateOrderQty, orderId, lastOrderQty)
} // namespace bts::websocket
@@ -0,0 +1,120 @@
#include "ssl_certificate_verifier.h"
namespace bts::websocket {
/// Verify that the certificate common name matches the given hostname
bool SslCertificateVerifier::verify_common_name(char const *hostname, X509 *cert) {
// Find the position of the CN field in the Subject field of the certificate
int common_name_loc = X509_NAME_get_index_by_NID(
X509_get_subject_name(cert), NID_commonName, -1);
if (common_name_loc < 0) {
return false;
}
// Extract the CN field
X509_NAME_ENTRY *common_name_entry =
X509_NAME_get_entry(X509_get_subject_name(cert), common_name_loc);
if (common_name_entry == NULL) {
return false;
}
// Convert the CN field to a C string
ASN1_STRING *common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
if (common_name_asn1 == NULL) {
return false;
}
char const *common_name_str =
(char const *)ASN1_STRING_get0_data(common_name_asn1);
// Make sure there isn't an embedded NUL character in the CN
if (static_cast<size_t>(ASN1_STRING_length(common_name_asn1)) !=
strlen(common_name_str)) {
return false;
}
// Compare expected hostname with the CN
return (strcasecmp(hostname, common_name_str) == 0);
}
/// Verify that one of the subject alternative names matches the given hostname
bool SslCertificateVerifier::verify_subject_alternative_name(
const char *hostname, X509 *cert) {
STACK_OF(GENERAL_NAME) *san_names = NULL;
san_names = (STACK_OF(GENERAL_NAME) *)X509_get_ext_d2i(
cert, NID_subject_alt_name, NULL, NULL);
if (san_names == NULL) {
return false;
}
int san_names_count = sk_GENERAL_NAME_num(san_names);
bool result = false;
for (int i = 0; i < san_names_count; i++) {
const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(san_names, i);
if (current_name->type != GEN_DNS) {
continue;
}
char const *dns_name =
(char const *)ASN1_STRING_get0_data(current_name->d.dNSName);
// Make sure there isn't an embedded NUL character in the DNS name
if (static_cast<size_t>(ASN1_STRING_length(current_name->d.dNSName)) !=
strlen(dns_name)) {
break;
}
// Compare expected hostname with the CN
result = (strcasecmp(hostname, dns_name) == 0);
}
sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
return result;
}
/**
* This code is derived from examples and documentation found ato00po
* http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/example/cpp03/ssl/client.cpp
* and
* https://github.com/iSECPartners/ssl-conservatory
*/
bool SslCertificateVerifier::verify_certificate(
const char *hostname,
bool preverified,
boost::asio::ssl::verify_context &ctx) {
// The verify callback can be used to check whether the certificate that is
// being presented is valid for the peer. For example, RFC 2818 describes
// the steps involved in doing this for HTTPS. Consult the OpenSSL
// documentation for more details. Note that the callback is called once
// for each certificate in the certificate chain, starting from the root
// certificate authority.
// Retrieve the depth of the current cert in the chain. 0 indicates the
// actual server cert, upon which we will perform extra validation
// (specifically, ensuring that the hostname matches. For other certs we
// will use the 'preverified' flag from Asio, which incorporates a number of
// non-implementation specific OpenSSL checking, such as the formatting of
// certs and the trusted status based on the CA certs we imported earlier.
int depth = X509_STORE_CTX_get_error_depth(ctx.native_handle());
// if we are on the final cert and everything else checks out, ensure that
// the hostname is present on the list of SANs or the common name (CN).
if (depth == 0 && preverified) {
X509 *cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
if (verify_subject_alternative_name(hostname, cert)) {
return true;
} else if (verify_common_name(hostname, cert)) {
return true;
} else {
return false;
}
}
return preverified;
}
} // namespace bts::websocket
@@ -0,0 +1,23 @@
#pragma once
#include <string>
#include <thread>
#include <websocketpp/common/connection_hdl.hpp>
#include <websocketpp/config/asio_client.hpp>
namespace bts::websocket {
class SslCertificateVerifier {
public:
static bool verify_common_name(char const *hostname, X509 *cert);
static bool verify_subject_alternative_name(
const char *hostname, X509 *cert);
static bool verify_certificate(
const char *hostname,
bool preverified,
boost::asio::ssl::verify_context &ctx);
};
} // namespace bts::websocket
@@ -0,0 +1,42 @@
#include "tls_initializer.h"
#include "bts-cme-websocket/ssl_certificate_verifier.h"
#include <websocketpp/common/connection_hdl.hpp>
#include <websocketpp/config/asio_client.hpp>
#include <websocketpp/roles/client_endpoint.hpp>
typedef websocketpp::client<websocketpp::config::asio_tls_client> client;
typedef websocketpp::lib::shared_ptr<websocketpp::lib::asio::ssl::context>
context_ptr;
using websocketpp::lib::bind;
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
namespace bts::websocket {
context_ptr TlsInitializer::init(
const char *hostname, websocketpp::connection_hdl) {
context_ptr ctx = websocketpp::lib::make_shared<boost::asio::ssl::context>(
boost::asio::ssl::context::sslv23);
try {
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);
// TODO: actually verify ssl certificate!
ctx->set_verify_mode(boost::asio::ssl::verify_none);
ctx->set_verify_callback(bind(
&SslCertificateVerifier::verify_certificate, hostname, ::_1, ::_2));
// Here we load the CA certificates of all CA's that this client trusts.
// ctx->load_verify_file("ca-chain.cert.pem");
} catch (std::exception &e) {
std::cout << e.what() << std::endl;
}
return ctx;
}
} // namespace bts::websocket
@@ -0,0 +1,15 @@
#pragma once
#include <websocketpp/common/connection_hdl.hpp>
#include <websocketpp/config/asio_client.hpp>
#include <websocketpp/roles/client_endpoint.hpp>
namespace bts::websocket {
class TlsInitializer {
public:
static websocketpp::lib::shared_ptr<websocketpp::lib::asio::ssl::context>
init(const char *hostname, websocketpp::connection_hdl hdl);
};
} // namespace bts::websocket
+35
View File
@@ -0,0 +1,35 @@
cmake_minimum_required(VERSION 3.23)
project(bts-http)
file(
GLOB_RECURSE ${PROJECT_NAME}_Sources
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
${PROJECT_NAME}/*.cpp)
add_library(${PROJECT_NAME} ${${PROJECT_NAME}_Sources})
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(${PROJECT_NAME} PUBLIC bts-system)
# FetchContent_Declare(curl
# URL https://github.com/curl/curl/releases/download/curl-8_4_0/curl-8.4.0.tar.xz
# URL_HASH SHA256=16c62a9c4af0f703d28bda6d7bbf37ba47055ad3414d70dec63e2e6336f2a82d # the file hash for curl-8.4.0.tar.xz
# USES_TERMINAL_DOWNLOAD TRUE) # <---- This is needed only for Ninja to show download progress
# FetchContent_MakeAvailable(curl)
# message("Curl source dir: ${curl_SOURCE_DIR}/include")
# target_include_directories(${PROJECT_NAME} PUBLIC ${curl_SOURCE_DIR}/include)
# target_link_libraries(${PROJECT_NAME} PUBLIC curl::libcurl)
# curlpp
# FetchContent_GetProperties(
# curl
# SOURCE_DIR CURL_SOURCE_DIR)
# set(CURL_LIBRARY curl::libcurl)
# set(CURL_INCLUDE_DIR ${curl_SOURCE_DIR}/include)
FetchContent_Declare(
curlpp
GIT_REPOSITORY git@github.com:jpbarrette/curlpp.git
GIT_TAG v0.8.1
SYSTEM
OVERRIDE_FIND_PACKAGE)
FetchContent_MakeAvailable(curlpp)
target_include_directories(${PROJECT_NAME} PUBLIC ${curlpp_SOURCE_DIR}/include)
target_link_directories(${PROJECT_NAME} PUBLIC ${curlpp_BINARY_DIR})
@@ -0,0 +1,81 @@
#pragma once
#include <curlpp/Easy.hpp>
#include <curlpp/Options.hpp>
#include <iostream>
#include <list>
#include <nlohmann/detail/macro_scope.hpp>
#include "bts-http/http_request.h"
#include "bts-http/http_response.h"
#include "bts-system/UNUSED.h"
#include "bts-system/logging.h"
namespace bts::http {
/**
* @brief Sets boilerplate curl opts and callbacks for http requests.
*/
class CurlRequestor {
public:
explicit CurlRequestor(const HTTPRequest &request)
: request_(std::make_unique<curlpp::Easy>()) {
request_->setOpt<curlpp::options::Url>(request.url);
std::list<std::string> req_hdrs;
for (const auto &[k, v] : request.headers) {
req_hdrs.push_back(fmt::format("{}:{}", k, v));
}
request_->setOpt<curlpp::options::HttpHeader>(req_hdrs);
request_->setOpt<curlpp::options::FailOnError>(true);
}
CurlRequestor(const CurlRequestor &) = delete;
CurlRequestor(CurlRequestor &&) noexcept = default;
CurlRequestor &operator=(const CurlRequestor &) = delete;
CurlRequestor &operator=(CurlRequestor &&) noexcept = default;
curlpp::Easy &request();
const curlpp::Easy &request() const;
void perform(HTTPResponse &response);
private:
std::unique_ptr<curlpp::Easy> request_;
std::string errors_;
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline curlpp::Easy &CurlRequestor::request() { return *request_; }
inline const curlpp::Easy &CurlRequestor::request() const { return *request_; }
inline void CurlRequestor::perform(HTTPResponse &response) {
request_->setOpt<curlpp::options::HeaderFunction>(
[&response](char *cstr, size_t sz, size_t n) {
std::string str{cstr, sz * n};
const auto colon_idx = str.find(':');
if (colon_idx >= str.size()) {
response.headers()[str] = {};
} else {
response.headers()[str.substr(0, colon_idx)] =
str.substr(colon_idx + 1, str.size() - colon_idx - 1);
}
return sz * n;
});
request_->setOpt<curlpp::options::WriteFunction>(
[&response](char *cstr, size_t sz, size_t n) {
response.body().append(cstr, sz * n);
return sz * n;
});
try {
request_->perform();
} catch (const std::exception &ex) {
LOG(Gen, Error, "CurlRequestor::perform threw an exception: {}",
std::string{ex.what()});
throw;
}
}
} // namespace bts::http
+28
View File
@@ -0,0 +1,28 @@
#include "http_client.h"
#include <curlpp/Options.hpp>
#include "bts-http/curl_requestor.h"
#include "bts-http/http_request.h"
#include "bts-http/http_response.h"
namespace bts::http {
HTTPResponse HTTPClient::get(const HTTPRequest &request) {
CurlRequestor requestor{request};
requestor.request().setOpt<curlpp::options::HttpGet>(true);
HTTPResponse response;
requestor.perform(response);
return response;
}
HTTPResponse HTTPClient::post(const HTTPRequest &request) {
CurlRequestor requestor{request};
requestor.request().setOpt<curlpp::options::Post>(true);
requestor.request().setOpt<curlpp::options::PostFields>(request.body);
HTTPResponse response;
requestor.perform(response);
return response;
}
} // namespace bts::http
+21
View File
@@ -0,0 +1,21 @@
#pragma once
namespace bts::http {
class HTTPRequest;
class HTTPResponse;
class HTTPClient {
public:
/**
* Performs an http GET request
*/
HTTPResponse get(const HTTPRequest &request);
/**
* Performs an http POST request
*/
HTTPResponse post(const HTTPRequest &request);
};
} // namespace bts::http
+61
View File
@@ -0,0 +1,61 @@
#pragma once
#include "bts-system/flat_unordered_map.h"
#include "bts-system/fmt_macros.h"
#include "bts-system/string_splitter.h"
namespace bts::http {
class HTTPHeaders
: public collections::FlatUnorderedMap<std::string, std::string> {
public:
HTTPHeaders() = default;
explicit HTTPHeaders(const std::string &header_str);
HTTPHeaders(const HTTPHeaders &) noexcept = default;
HTTPHeaders(HTTPHeaders &&) noexcept = default;
HTTPHeaders &operator=(const HTTPHeaders &) noexcept = default;
HTTPHeaders &operator=(HTTPHeaders &&) noexcept = default;
std::string to_string() const;
};
std::ostream &operator<<(
std::ostream &out, const HTTPHeaders &headers) noexcept;
inline HTTPHeaders::HTTPHeaders(const std::string &str) {
auto item_strs = system::StringSplitter::split(str, "\n");
for (const auto &item_str : item_strs) {
auto kv = system::StringSplitter::split(item_str, ":");
if (kv.empty()) {
try_emplace("", "");
continue;
}
if (kv.size() == 1) {
try_emplace(kv[0], "");
continue;
}
try_emplace(kv[0], kv[1]);
}
}
inline std::string HTTPHeaders::to_string() const {
auto it = begin();
if (it == end()) {
return "";
}
std::string rv{it->first + ": " + it->second};
while (++it != end()) {
rv.append("\n" + it->first + ": " + it->second);
}
return rv;
}
inline std::ostream &operator<<(
std::ostream &out, const HTTPHeaders &headers) noexcept {
out << headers.to_string();
return out;
}
} // namespace bts::http
FMT_IMPL(bts::http::HTTPHeaders, h, "{}", h.to_string());
+91
View File
@@ -0,0 +1,91 @@
#pragma once
#include <map>
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/json.hpp>
#include <stdexcept>
#include <string>
#include <vector>
#include "bts-http/http_response.h"
#include "bts-system/fmt_macros.h"
#include "bts-system/string_splitter.h"
namespace bts::http {
class HTTPRequest {
public:
std::string url;
std::string body;
std::map<std::string, std::string> headers;
std::string get_headers_string() const;
void set_headers_string(const std::string &hdrs_str);
HTTPResponse response;
};
void to_json(nlohmann::json &j, const HTTPRequest &req);
void from_json(const nlohmann::json &j, HTTPRequest &req);
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline void HTTPRequest::set_headers_string(const std::string &hdrs_str) {
headers.clear();
auto item_strs = system::StringSplitter::split(hdrs_str, "\n");
for (const auto &item_str : item_strs) {
auto kv = system::StringSplitter::split(item_str, ":");
switch (kv.size()) {
case 1:
headers.try_emplace(kv[0], "");
break;
case 2:
headers.try_emplace(kv[0], kv[1]);
break;
default:
throw std::runtime_error{
fmt::format("Invalid http header '{}'", hdrs_str)};
}
}
}
inline std::string HTTPRequest::get_headers_string() const {
std::string rv;
for (const auto &[k, v] : headers) {
if (!rv.empty()) {
rv.append("\n");
}
if (v.empty()) {
rv.append(k);
} else {
// Don't add space after colon. The spec says to ignore
// leading/trailing whitespace around header values.
// https://www.rfc-editor.org/rfc/rfc7230#section-3.2
rv.append(k + ":" + v);
}
}
return rv;
}
inline void to_json(nlohmann::json &j, const HTTPRequest &req) {
j["url"] = req.url;
j["headers"] = req.headers;
j["body"] = req.body;
}
inline void from_json(const nlohmann::json &j, HTTPRequest &req) {
j.at("url").get_to(req.url);
j.at("headers").get_to(req.headers);
j.at("body").get_to(req.body);
}
} // namespace bts::http
FMT_IMPL(
bts::http::HTTPRequest,
r,
"url: {} | headers: {} | body: {}",
r.url,
r.get_headers_string(),
r.body);
+107
View File
@@ -0,0 +1,107 @@
#pragma once
#include <nlohmann/json.hpp>
#include <string>
#include <vector>
#include "bts-system/fmt_macros.h"
#include "http_headers.h"
#include "nlohmann/detail/macro_scope.hpp"
namespace bts::http {
class HTTPResponse {
public:
std::string &url();
const std::string &url() const;
HTTPHeaders &headers();
const HTTPHeaders &headers() const;
std::string &body();
const std::string &body() const;
std::string &errors();
const std::string &errors() const;
std::int64_t &code();
std::int64_t code() const;
double &elapsed();
double elapsed() const;
private:
std::string url_;
HTTPHeaders headers_;
std::string body_;
std::string errors_;
std::int64_t code_;
double elapsed_;
};
void to_json(nlohmann::json &j, const HTTPResponse &res);
void from_json(const nlohmann::json &j, HTTPResponse &res);
std::ostream &operator<<(
std::ostream &out, const HTTPResponse &response) noexcept;
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline std::string &HTTPResponse::url() { return url_; }
inline const std::string &HTTPResponse::url() const { return url_; }
inline HTTPHeaders &HTTPResponse::headers() { return headers_; }
inline const HTTPHeaders &HTTPResponse::headers() const { return headers_; }
inline std::string &HTTPResponse::body() { return body_; }
inline const std::string &HTTPResponse::body() const { return body_; }
inline std::string &HTTPResponse::errors() { return errors_; }
inline const std::string &HTTPResponse::errors() const { return errors_; }
inline std::int64_t &HTTPResponse::code() { return code_; }
inline std::int64_t HTTPResponse::code() const { return code_; }
inline double &HTTPResponse::elapsed() { return elapsed_; }
inline double HTTPResponse::elapsed() const { return elapsed_; }
inline void to_json(nlohmann::json &j, const HTTPResponse &res) {
j["url"] = res.url();
j["body"] = res.body();
j["code"] = res.code();
j["elapsed"] = res.elapsed();
j["errors"] = res.errors();
}
inline void from_json(const nlohmann::json &j, HTTPResponse &res) {
j.at("url").get_to(res.url());
j.at("body").get_to(res.body());
j.at("code").get_to(res.code());
j.at("elapsed").get_to(res.elapsed());
j.at("errors").get_to(res.errors());
}
inline std::ostream &operator<<(
std::ostream &out, const HTTPResponse &res) noexcept {
out << "**Url**\n"
<< res.url() << "\n**Headers**\n"
<< res.headers() << "\n**Body**\n"
<< res.body() << "\n**Code**\n"
<< res.code() << "\n**Elapsed**\n"
<< res.elapsed();
return out;
}
} // namespace bts::http
FMT_IMPL(
bts::http::HTTPResponse,
r,
"url: {} | headers: {} | body: {} | code: {} | elapsed: {}",
r.url(),
r.headers(),
r.body(),
r.code(),
r.elapsed());
@@ -0,0 +1,18 @@
#include "oauth_request.h"
namespace bts::http {
// std::string OauthRequest::perform() {
// switch (cfg.websocket.auth.type().value) {
// case WebsocketAuthorizationType::None:
// break;
// case WebsocketAuthorizationType::AccessToken:
// break;
// case WebsocketAuthorizationType::OauthRequest:
// break;
// default:
// break;
// }
// }
} // namespace bts::http
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "bts-http/url.h"
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/json.hpp>
#include <string>
namespace bts::http {
class OauthRequest {
public:
std::string url;
std::string client_id;
std::string client_secret;
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(OauthRequest, url, client_id, client_secret);
} // namespace bts::http
@@ -0,0 +1,69 @@
#pragma once
#include "bts-http/http_client.h"
#include "bts-http/http_headers.h"
#include "bts-http/http_request.h"
#include "bts-http/http_response.h"
#include "bts-http/oauth_request.h"
#include "bts-http/oauth_response.h"
#include "bts-system/json/json_dumper.h"
#include "bts-system/json/json_reader.h"
#include "bts-system/logging.h"
namespace bts::http {
// Be sure to undef macros at end of file (this is a header)
#define _LOG(level, ...) LOG(md, level, "OauthRequestor::" __VA_ARGS__)
#define _INFO(...) _LOG(Info, __VA_ARGS__)
#define _ERROR(...) _LOG(Error, __VA_ARGS__)
class OauthRequestor {
public:
/**
* Requests access token.
* @param cfg Configuration for the request
* @param require_success Whether or not to throw an exception if the
* request fails (i.e., access token is empty or undefined)
*/
static OauthResponse request(
const OauthRequest &cfg, bool require_success = true);
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline OauthResponse OauthRequestor::request(
const OauthRequest &cfg, bool require_success) {
using namespace bts::http;
_INFO("request. \n{}", system::json::JsonDumper::dump(cfg));
HTTPRequest req;
req.url = cfg.url;
QueryParams qp;
qp["grant_type"] = "client_credentials";
qp["client_id"] = cfg.client_id;
qp["client_secret"] = cfg.client_secret;
req.body = qp.to_string();
// _INFO("CmeOauthRequestor::request. Sending\n{}", JsonDumper::dump(req));
HTTPClient client;
HTTPResponse res = client.post(req);
// _INFO("CmeOauthRequestor::request. Received\n{}", JsonDumper::dump(res));
const auto j = nlohmann::json::parse(res.body());
auto oauth = system::json::JsonReader{&j}.read<OauthResponse>();
if ((*oauth.access_token).empty()) {
// _ERROR("request. Request failed (access token was undefined or empty)");
if (require_success) {
throw std::runtime_error{"Failed to obtain oauth access token"};
}
}
return oauth;
}
#undef _ASSERT_MSGF
#undef _LOG
#undef _DIAG
#undef _INFO
#undef _WARN
#undef _ERROR
} // namespace bts::http
@@ -0,0 +1,24 @@
#pragma once
#include "bts-websocket/websocket_access_token.h"
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/json.hpp>
#include <string>
namespace bts::http {
class OauthResponse {
public:
websocket::WebsocketAccessToken access_token;
std::string token_type;
/**
* The number of seconds until the token expires
*/
std::int64_t expires_in;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
OauthResponse, access_token, token_type, expires_in);
} // namespace bts::http
@@ -0,0 +1,18 @@
#include "query_params.h"
namespace bts::http {
std::string QueryParams::to_string() const {
std::string rv;
auto it = begin();
if (it == end()) {
return "";
}
rv.append(it->first + "=" + it->second);
while (++it != end()) {
rv.append("&" + it->first + "=" + it->second);
}
return rv;
}
} // namespace bts::http
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include "bts-system/flat_unordered_map.h"
#include "bts-system/fmt_macros.h"
#include <algorithm>
#include <string>
namespace bts::http {
class QueryParams
: public collections::FlatUnorderedMap<std::string, std::string> {
public:
std::string to_string() const;
};
inline bool operator==(const QueryParams &lhs, const QueryParams &rhs) {
return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
} // namespace bts::http
FMT_IMPL(bts::http::QueryParams, p, "{}", p.to_string());
+99
View File
@@ -0,0 +1,99 @@
#include "url.h"
namespace bts::http {
using std::pair;
using std::size_t;
using std::string;
using std::uint16_t;
using std::vector;
URL URL::from_string(const string &url_str) {
URL url;
const char *protocol_separator{"://"};
size_t pos = 0;
size_t pos_end = url_str.find(protocol_separator);
if (pos_end != string::npos) {
url.fill_protocol(url_str.substr(pos, pos_end));
}
pos = pos_end + strlen(protocol_separator) + 1;
pos_end = url_str.find(':', pos);
if (pos_end != string::npos) {
// This chunk is <host>:<port>
url.fill_host(url_str.substr(pos, pos_end));
pos = pos_end + 1;
pos_end = url_str.find('/', pos);
if (pos_end == string::npos) {
pos_end = url_str.size();
}
int32_t port = std::stoi(url_str.substr(pos, pos_end - pos));
url.fill_port(static_cast<uint16_t>(port));
} else {
// This chunk is <host>
pos_end = url_str.find('/', pos);
if (pos_end == string::npos) {
pos_end = url_str.size();
}
url.fill_host(url_str.substr(pos, pos_end - pos));
}
// Path
if (pos_end != string::npos && pos_end < url_str.size()) {
url.fill_host(url_str.substr(pos, pos_end - pos));
pos = pos_end + 1;
if (pos < url_str.size()) {
url.fill_path(url_str.substr(pos, url_str.size() - pos));
}
} else {
url.fill_host(url_str.substr(pos, url_str.size() - pos));
}
return url;
}
vector<pair<string, uint16_t>> URL::well_known_ports_() {
return vector<pair<string, uint16_t>>{
{"http", 80}, {"https", 443}, {"ws", 80},
{"wss", 443}, {"ssh", 22}, {"sftp", 22},
};
}
uint16_t URL::get_known_port_for_protocol(const string &protocol) {
auto wkps = well_known_ports_();
for (const auto &p : wkps) {
if (p.first == protocol) {
return p.second;
}
}
return 0;
}
string URL::get_known_protocol_for_port(uint16_t port) {
auto wkps = well_known_ports_();
for (const auto &p : wkps) {
if (p.second == port) {
return p.first;
}
}
return "";
}
string URL::to_string() const {
auto rv = protocol() + "://";
if (host().empty()) {
rv.append("localhost");
} else {
rv.append(host());
}
if (port_ != 0) {
rv.append(":" + std::to_string(port_));
}
if (!path_.empty()) {
rv.append("/" + path_);
}
if (!query_params_.empty()) {
rv.append("?");
rv.append(query_params_.to_string());
}
return rv;
}
} // namespace bts::http
+157
View File
@@ -0,0 +1,157 @@
#pragma once
#include "bts-http/query_params.h"
#include "bts-system/equality_macros.h"
#include "bts-system/fmt_macros.h"
#include "bts-system/hash_macros.h"
#include <functional>
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/json.hpp>
#include <ostream>
#include <string>
#include <type_traits>
#include <unordered_set>
#include <variant>
#include <vector>
namespace bts::http {
class URL {
public:
std::string &protocol();
const std::string &protocol() const;
std::string &host();
const std::string &host() const;
std::uint16_t &port();
std::uint16_t port() const;
std::string &path();
const std::string &path() const;
QueryParams &query_params();
const QueryParams &query_params() const;
std::string to_string() const;
void fill_protocol(const std::string &protocol);
void fill_host(const std::string &host);
void fill_path(const std::string &path);
void fill_port(std::uint16_t port);
void fill_query_params(
const QueryParams &query_params, bool add_missing_params = false);
void fill_with(const URL &url);
static std::uint16_t get_known_port_for_protocol(
const std::string &protocol);
static std::string get_known_protocol_for_port(std::uint16_t port);
static URL from_string(const std::string &str);
private:
std::string protocol_;
std::string host_;
std::uint16_t port_{0};
std::string path_;
QueryParams query_params_;
static std::vector<std::pair<std::string, std::uint16_t>>
well_known_ports_();
};
bool operator==(const URL &lhs, const URL &rhs);
bool operator!=(const URL &lhs, const URL &rhs);
std::ostream &operator<<(std::ostream &out, const URL &url) noexcept;
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline std::string &URL::protocol() { return protocol_; }
inline const std::string &URL::protocol() const { return protocol_; }
inline std::string &URL::host() { return host_; }
inline const std::string &URL::host() const { return host_; }
inline uint16_t &URL::port() { return port_; }
inline uint16_t URL::port() const { return port_; }
inline std::string &URL::path() { return path_; }
inline const std::string &URL::path() const { return path_; }
inline QueryParams &URL::query_params() { return query_params_; }
inline const QueryParams &URL::query_params() const { return query_params_; }
inline void URL::fill_protocol(const std::string &protocol) {
if (protocol_.empty()) {
protocol_ = protocol;
}
}
inline void URL::fill_host(const std::string &host) {
if (host_.empty()) {
host_ = host;
}
}
inline void URL::fill_port(uint16_t port) {
if (port_ == 0) {
port_ = port;
}
}
inline void URL::fill_path(const std::string &path) {
if (path_.empty()) {
path_ = path;
}
}
inline void URL::fill_query_params(
const QueryParams &query_params, bool add_missing_params) {
if (query_params_.empty()) {
query_params_ = query_params;
} else if (add_missing_params) {
for (const auto &[k, v] : query_params) {
query_params_.try_emplace(k, v);
}
}
}
inline void URL::fill_with(const URL &url) {
fill_path(url.path());
fill_host(url.host());
fill_protocol(url.protocol());
fill_port(url.port());
fill_query_params(url.query_params());
}
inline std::ostream &operator<<(std::ostream &out, const URL &url) noexcept {
out << url.to_string();
return out;
}
// clang-format off
#define EQUALS_ARGS(op) \
op(protocol()) \
op(host()) \
op(port()) \
op(path())
// clang-format on
EQUALS_IMPL(URL, EQUALS_ARGS);
#undef EQUALS_ARGS
} // namespace bts::http
// clang-format off
#define HASH_ARGS(op) \
op(bts::http::URL, protocol()) \
op(bts::http::URL, host()) \
op(bts::http::URL, port()) \
op(bts::http::URL, path())
// clang-format on
HASH_IMPL_STRIP_REFS(bts::http::URL, HASH_ARGS);
#undef HASH_ARGS
FMT_IMPL(bts::http::URL, u, "{}", u.to_string());
+27
View File
@@ -0,0 +1,27 @@
cmake_minimum_required(VERSION 3.23)
project(bts-system)
include(FetchContent)
file(
GLOB_RECURSE ${PROJECT_NAME}_Sources
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
${PROJECT_NAME}/*.cpp)
add_library(${PROJECT_NAME} ${${PROJECT_NAME}_Sources})
target_link_libraries(${PROJECT_NAME} PUBLIC
debug fmtd
optimized fmt)
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
# nlohmann json
FetchContent_Declare(
nlohmann_json
GIT_REPOSITORY git@github.com:nlohmann/json.git
GIT_TAG v3.11.1
CMAKE_ARGS
-DJSON_BuildTests=OFF
-DJSON_SystemInclude=ON
SYSTEM
OVERRIDE_FIND_PACKAGE)
FetchContent_MakeAvailable(nlohmann_json)
find_package(nlohmann_json REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC nlohmann_json)
+4
View File
@@ -0,0 +1,4 @@
#pragma once
#define UNUSED(x) (void)(x)
#define UNUSED_PARAM __attribute__((unused))
@@ -0,0 +1,45 @@
#pragma once
#include <chrono>
#include <csignal>
#include <iostream>
#include <thread>
#include "bts_debugger.h"
#ifdef _WIN32
#define TERMINATE ::_exit(-1);
#else
#define TERMINATE abort();
#endif
#define bts_assert_msg(EXP, msg) \
do { \
if (!(EXP)) { \
std::cerr << fmt::format("Assert failure: {} {}", std::string(#EXP), \
std::string(msg)) << std::endl; \
std::this_thread::sleep_for(std::chrono::seconds(1)); \
if (is_debugger_present()) \
debug_break(); \
TERMINATE; \
} \
} while (0)
#define bts_assert(EXP) bts_assert_msg(EXP, "")
#define bts_assert_msgf(EXP, msg_template, ...) \
do { \
if (!(EXP)) { \
std::cerr << fmt::format("Assert failure: {} {}", std::string(#EXP), \
fmt::format(msg_template, __VA_ARGS__)) << std::endl; \
std::this_thread::sleep_for(std::chrono::seconds(1)); \
if (is_debugger_present()) \
debug_break(); \
TERMINATE; \
} \
} while (0)
#ifndef NDEBUG
#define bts_assert_d bts_assert
#else
#define bts_assert_d(EXP)
#endif
@@ -0,0 +1,43 @@
#pragma once
// #include "windows_wrapper.h"
#include <csignal>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <sys/stat.h>
inline bool is_debugger_present() {
#ifdef _WIN32
return IsDebuggerPresent();
#else
char buf[1024];
int debugger_present = 0;
int status_fd = open("/proc/self/status", O_RDONLY);
if (status_fd == -1)
return 0;
ssize_t num_read = read(status_fd, buf, sizeof(buf) - 1);
if (num_read > 0) {
static const char TracerPid[] = "TracerPid:";
char *tracer_pid;
buf[num_read] = 0;
tracer_pid = strstr(buf, TracerPid);
if (tracer_pid)
debugger_present = !!atoi(tracer_pid + sizeof(TracerPid) - 1);
}
return debugger_present;
#endif
}
inline void debug_break() {
#ifdef _WIN32
__debugbreak();
#else
std::raise(SIGINT);
#endif
}
@@ -0,0 +1,28 @@
#pragma once
#include "bts-system/time_stamp.h"
#include <fmt/format.h>
#include <iomanip>
#include <iostream>
#include <string>
namespace bts::system {
class ConsoleLogger {
public:
static void log(
const std::string &msg,
const TimeStamp time,
const std::string &file_name,
std::uint16_t line_num,
const std::string &level,
const std::string &category) {
std::cout << time.to_iso_string() << " | " << std::setw(8) << category
<< " | " << std::setw(8) << level << " | " << std::setw(35)
<< file_name + ":" + std::to_string(line_num) << " | " << msg
<< std::endl;
}
};
} // namespace bts::system
+548
View File
@@ -0,0 +1,548 @@
#include "datetime.h"
#include "bts-system/time_span.h"
#include "bts-system/time_stamp.h"
#include "bts_assert.h"
#include <boost/date_time/gregorian/gregorian.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/date_time/posix_time/posix_time_duration.hpp>
#include <sstream>
#include <time.h>
namespace bts {
namespace datetime {
using bts::system::TimeSpan;
using bts::system::TimeStamp;
const boost::gregorian::date anchor(1970, boost::gregorian::Jan, 1);
const uint64_t anchor_ticks = 621355968000000000;
const uint64_t micros_per_day = 86400000000;
const uint64_t ticks_per_micro = 10;
const uint64_t ticks_per_day = micros_per_day * ticks_per_micro;
const uint64_t ticks_mask = 0x3FFFFFFFFFFFFFFF;
// DateTimeKinds
const uint64_t KindUnspecified = 0x0000000000000000;
const uint64_t KindUtc = 0x4000000000000000;
const uint64_t KindLocal = 0x8000000000000000;
unsigned long long ptime_to_ll(PTime const &t, uint64_t const &kind) {
uint64_t days = (t.date() - anchor).days();
uint64_t usecs = t.time_of_day().total_microseconds();
uint64_t ticks =
anchor_ticks + usecs * ticks_per_micro + days * ticks_per_day;
return (ticks & ticks_mask) | kind;
}
unsigned long long ptime_to_ll(PTime const &t) {
return ptime_to_ll(t, KindUnspecified);
}
LLDateTime ll_now() {
return ptime_to_ll(
boost::posix_time::microsec_clock::universal_time(), KindUtc);
}
LLDateTime ll_local_now() {
return ptime_to_ll(
boost::posix_time::microsec_clock::local_time(), KindLocal);
}
long unix_now() {
return (boost::posix_time::microsec_clock::universal_time() - PTime(anchor))
.total_microseconds() /
(long)1E6;
}
std::chrono::system_clock::time_point utc_ll_to_timepoint(
unsigned long long ll) {
auto us_since_epoch = ((ll & ticks_mask) - anchor_ticks) / ticks_per_micro;
return std::chrono::system_clock::time_point(
std::chrono::microseconds(us_since_epoch));
}
long long ll_diff_in_us(unsigned long long ll1, unsigned long long ll2) {
#ifndef NO_CPP0X
auto p1 = ll_to_ptime(ll1);
auto p2 = ll_to_ptime(ll2); // NOLINT
return (p2 - p1).total_microseconds();
#else
return 0;
#endif
}
long long ll_diff_in_seconds(unsigned long long ll1, unsigned long long ll2) {
#ifndef NO_CPP0X
auto p1 = ll_to_ptime(ll1);
auto p2 = ll_to_ptime(ll2); // NOLINT
return (p2 - p1).total_seconds();
#else
return 0;
#endif
}
long long ptime_diff_in_us(PTime const &t1, PTime const &t2) {
return (t1 - t2).total_microseconds();
}
long long ptime_diff_in_ms(PTime const &t1, PTime const &t2) {
return (t1 - t2).total_milliseconds();
}
long long ptime_diff_in_secs(PTime const &t1, PTime const &t2) {
return (t1 - t2).total_seconds();
}
PTime timestamp_to_ptime(TimeStamp const &t) {
auto now_ts = TimeStamp::now();
auto now = datetime::local_now();
auto delta = (now_ts - t).micros();
return now - boost::posix_time::microseconds(delta);
}
TimeStamp ptime_to_timestamp(PTime const &t) {
auto now_ts = TimeStamp::now();
auto now = datetime::now();
auto delta = (now - t).total_milliseconds();
auto delta_tspan = TimeSpan::from_millis(delta);
return now_ts - delta_tspan;
}
long long usecs_since(PTime const &t1) {
return (boost::posix_time::microsec_clock::universal_time() - t1)
.total_microseconds();
}
long long msecs_since(PTime const &t1) {
return (boost::posix_time::microsec_clock::universal_time() - t1)
.total_milliseconds();
}
PTime now() { return boost::posix_time::microsec_clock::universal_time(); }
PTime local_now() { return boost::posix_time::microsec_clock::local_time(); }
PTime make_ptime(int year, int month, int day) {
// January=1
bts_assert_d(month > 0);
boost::gregorian::date dt(year, boost::gregorian::greg_month(month), day);
return PTime(dt);
}
PTime make_ptime(
int year,
int month,
int day,
int hour,
int minute,
int second,
int millisecond) {
boost::gregorian::date dt(year, boost::gregorian::greg_month(month), day);
return PTime(
dt, boost::posix_time::time_duration(hour, minute, second) +
boost::posix_time::milliseconds(millisecond));
}
PTime ll_to_ptime(LLDateTime ll) {
ll &= ticks_mask;
uint64_t ticks_part = ll % ticks_per_day;
uint64_t days_part = (ll - anchor_ticks) / ticks_per_day;
return PTime(
anchor + boost::gregorian::date_duration(days_part),
boost::posix_time::time_duration(
boost::posix_time::microseconds(ticks_part / ticks_per_micro)));
}
extern "C" BTS_EXPORT int ll_to_cstr(
LLDateTime ll, char *buffer, int buffer_sz) {
const int str_sz = sizeof("20121204-14:44:02.000");
bts_assert(buffer_sz >= str_sz);
auto str = FIX_timestamp(ll_to_ptime(ll));
bts_assert(str.size() == str_sz - 1);
memcpy(buffer, &str[0], str.size());
buffer[str.size()] = '\0';
return str.size();
}
std::string time_string(PTime const &t) {
int hours = t.time_of_day().hours();
int minutes = t.time_of_day().minutes();
int seconds = t.time_of_day().seconds();
int whole_ms = seconds * 1000 + minutes * 1000 * 60 + hours * 1000 * 60 * 60;
int frac_ms = t.time_of_day().total_milliseconds() - whole_ms;
std::ostringstream out;
out << std::setw(2) << std::setfill('0') << hours << ":" << std::setw(2)
<< std::setfill('0') << minutes << ":" << std::setw(2)
<< std::setfill('0') << seconds << "." << std::setw(3)
<< std::setfill('0') << frac_ms;
return out.str();
}
std::string date_string(PTime const &t) {
std::string result(10, ' ');
sprintf(
&result[0], "%02d/%02d/%04d", t.date().month().as_number(),
t.date().day().as_number(), (unsigned short)t.date().year());
return result;
}
std::string iso_date_time_string(PTime const &t) {
return boost::posix_time::to_iso_extended_string(t);
}
PTime parse_iso_date_time_tz(const std::string &date) {
// example 1) 2022-10-27T09:30-5:00
// example 2) 2022-10-27T09:30+5:00
#ifdef _WIN32
struct tm tt = {0, 0, 0, 0, 0, 0, 0, 0, 0};
#else
struct tm tt = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
#endif
int tz_hours;
int tz_minutes;
int num_scanned = sscanf(
date.c_str(), "%4d-%2d-%2dT%2d:%2d:%2d%3d:%2d", &tt.tm_year, &tt.tm_mon,
&tt.tm_mday, &tt.tm_hour, &tt.tm_min, &tt.tm_sec, &tz_hours,
&tz_minutes);
bts_assert_msgf(
num_scanned == 8,
"Failed to parse iso8601 date from string '{}' (format must be "
"YYYY-MM-DDThh:mm:ss<+/->hh:ss)",
date);
tt.tm_mon -= 1;
tt.tm_year -= 1900;
tt.tm_isdst = -1;
return boost::posix_time::ptime_from_tm(tt) +
boost::posix_time::time_duration{tz_hours, tz_minutes, 0, 0};
}
PTime parse_iso_date_time_string(std::string const &t) {
constexpr const std::size_t time_idx = sizeof("YYYY-MM-DD") - 1;
auto actual_time_idx = t.find('T', time_idx);
if (actual_time_idx != std::string::npos) {
constexpr const std::size_t time_zone_idx =
time_idx + sizeof("HH:MM") - 1;
// time zone must be after time
bts_assert(actual_time_idx < time_zone_idx);
if (t.find('+', time_zone_idx) != std::string::npos ||
t.find('-', time_zone_idx) != std::string::npos) {
// date, time, and time zone
return parse_iso_date_time_tz(t);
}
// date and time (no time zone)
return boost::posix_time::from_iso_extended_string(t);
}
// date (no time or time zone)
return PTime{boost::gregorian::from_simple_string(t)};
}
std::string simple_date_time_string(PTime const &t) {
return boost::posix_time::to_simple_string(t);
}
PTime parse_simple_date_time_string(std::string const &t) {
if (t.find(' ') == t.npos)
return boost::posix_time::time_from_string(t + " 00:00:00");
else
return boost::posix_time::time_from_string(t);
}
PDuration parse_duration(std::string const &time_str) {
try {
return boost::posix_time::duration_from_string(time_str);
} catch (boost::bad_lexical_cast &) {
bts_assert(false);
return boost::posix_time::time_duration();
}
}
std::string FIX_date_time_string(PTime const &t) {
std::string result(strlen("20121204-14:44:02"), ' ');
sprintf(
&result[0], "%d%02d%02d-%02d:%02d:%02d", (unsigned short)t.date().year(),
t.date().month().as_number(), t.date().day().as_number(),
(int)t.time_of_day().hours(), (int)t.time_of_day().minutes(),
(int)t.time_of_day().seconds());
return result;
}
void FIX_timestamp(PTime const &t, std::string *out) {
int hours = t.time_of_day().hours();
int minutes = t.time_of_day().minutes();
int seconds = t.time_of_day().seconds();
int whole_ms = seconds * 1000 + minutes * 1000 * 60 + hours * 1000 * 60 * 60;
int frac_ms = t.time_of_day().total_milliseconds() - whole_ms;
out->resize(sizeof("20121204-14:44:02.000"));
#ifdef _WIN32
_snprintf(
&(*out)[0], out->size(), "%04d%02d%02d-%02d:%02d:%02d.%03d",
(int)t.date().year(), t.date().month().as_number(),
t.date().day().as_number(), hours, minutes, seconds, frac_ms);
#else
snprintf(
&(*out)[0], out->size(), "%04d%02d%02d-%02d:%02d:%02d.%03d",
(int)t.date().year(), t.date().month().as_number(),
t.date().day().as_number(), hours, minutes, seconds, frac_ms);
#endif
out->resize(out->size() - 1);
}
std::string FIX_timestamp(PTime const &t) {
std::string result;
FIX_timestamp(t, &result);
return result;
}
bool is_from_today(PTime const &time) {
return (
time.date() ==
boost::posix_time::microsec_clock::universal_time().date());
}
bool is_at_least_msecs_old(PTime const &time, uint64_t ms) {
PTime now = boost::posix_time::microsec_clock::universal_time();
return (now - time).total_milliseconds() >= (int64_t)ms;
}
bool is_at_least_secs_old(PTime const &time, uint64_t sec) {
PTime now = boost::posix_time::microsec_clock::universal_time();
return (now - time).total_seconds() >= (int64_t)sec;
}
bool is_at_least_mins_old(PTime const &time, uint64_t mins) {
PTime now = boost::posix_time::microsec_clock::universal_time();
return ((now - time).total_seconds() / 60) >= (int64_t)mins;
}
bool is_at_least_hours_old(PTime const &time, uint64_t hours) {
PTime now = boost::posix_time::microsec_clock::universal_time();
int64_t secs = (int64_t)((now - time).total_seconds());
return (secs / 60 / 60) >= (int64_t)hours;
}
bool is_at_least_days_old(PTime const &time, uint64_t days) {
return is_at_least_hours_old(time, days * 24);
}
LLDateTime ll_from_unix_epoch(long seconds) {
PTime result(anchor, boost::posix_time::seconds(seconds));
return ptime_to_ll(result, KindUnspecified);
}
LLDateTime ll_from_unix_epoch_ms(long long milliseconds) {
PTime result(anchor, boost::posix_time::millisec(milliseconds));
return ptime_to_ll(result, KindUnspecified);
}
void ll_to_parts(
LLDateTime ll_time,
int &year,
int &month,
int &day,
int &hour,
int &minute,
int &second,
int &millisecond) {
PTime t = ll_to_ptime(ll_time);
year = t.date().year();
month = t.date().month();
day = t.date().day();
hour = t.time_of_day().hours();
minute = t.time_of_day().minutes();
second = t.time_of_day().seconds();
int whole_ms = second * 1000 + minute * 1000 * 60 + hour * 1000 * 60 * 60;
millisecond = t.time_of_day().total_milliseconds() - whole_ms;
}
LLDateTime ll_from_parts(
int year,
int month,
int day,
int hour,
int minute,
int second,
int millisecond) {
return ptime_to_ll(make_ptime(
year, month, day, hour, minute, second, millisecond)); // NOLINT
}
LLDateTime ll_from_date(const Date &date) {
if (date.year == 0 && date.day == 0 && date.month == 0) {
return 0;
}
return ll_from_parts(date.year, date.month, date.day, 0, 0, 0, 0);
}
Date ll_to_date(LLDateTime time) {
Date date;
int year, month, day, unused;
ll_to_parts(time, year, month, day, unused, unused, unused, unused);
date.year = year;
date.month = month;
date.day = day;
return date;
}
bool is_local_time_sunday() {
auto now = local_now();
return now.date().day_of_week() == boost::date_time::Sunday;
}
bool is_local_time_between(int hh_start, int mm_start, int hh_end, int mm_end) {
using namespace boost::posix_time;
auto now = local_now();
auto start = ptime(now.date(), hours(hh_start) + minutes(mm_start));
auto end = ptime(now.date(), hours(hh_end) + minutes(mm_end));
return (now >= start && now <= end);
}
struct UtcTimeStamp {
int hour;
int min;
int sec;
int msec;
int day;
int month;
int year;
};
struct UtcTimeStampConvertor {
static UtcTimeStamp convert(const std::string &value) {
bool haveMilliseconds = false;
switch (value.size()) {
case 21:
haveMilliseconds = true;
case 17:
break;
default:
bts_assert(false);
}
int i = 0;
int c = 0;
for (c = 0; c < 8; ++c)
if (!isdigit(value[i++]))
bts_assert(false);
if (value[i++] != '-')
bts_assert(false);
for (c = 0; c < 2; ++c)
if (!isdigit(value[i++]))
bts_assert(false);
if (value[i++] != ':')
bts_assert(false);
for (c = 0; c < 2; ++c)
if (!isdigit(value[i++]))
bts_assert(false);
if (value[i++] != ':')
bts_assert(false);
for (c = 0; c < 2; ++c)
if (!isdigit(value[i++]))
bts_assert(false);
if (haveMilliseconds) {
if (value[i++] != '.')
bts_assert(false);
for (c = 0; c < 3; ++c)
if (!isdigit(value[i++]))
bts_assert(false);
}
int year, mon, mday, hour, min, sec, millis;
i = 0;
year = value[i++] - '0';
year = 10 * year + value[i++] - '0';
year = 10 * year + value[i++] - '0';
year = 10 * year + value[i++] - '0';
mon = value[i++] - '0';
mon = 10 * mon + value[i++] - '0';
if (mon < 1 || 12 < mon)
bts_assert(false);
mday = value[i++] - '0';
mday = 10 * mday + value[i++] - '0';
if (mday < 1 || 31 < mday)
bts_assert(false);
++i; // skip '-'
hour = value[i++] - '0';
hour = 10 * hour + value[i++] - '0';
// No check for >= 0 as no '-' are converted here
if (23 < hour)
bts_assert(false);
++i; // skip ':'
min = value[i++] - '0';
min = 10 * min + value[i++] - '0';
// No check for >= 0 as no '-' are converted here
if (59 < min)
bts_assert(false);
++i; // skip ':'
sec = value[i++] - '0';
sec = 10 * sec + value[i++] - '0';
// No check for >= 0 as no '-' are converted here
if (60 < sec)
bts_assert(false);
if (haveMilliseconds) {
millis =
(100 * (value[i + 1] - '0') + 10 * (value[i + 2] - '0') +
(value[i + 3] - '0'));
} else
millis = 0;
return UtcTimeStamp{hour, min, sec, millis, mday, mon, year};
}
};
PTime parse_FIX_timestamp(const std::string &ts_str) {
auto ts = UtcTimeStampConvertor::convert(ts_str);
boost::gregorian::date dt(ts.year, ts.month, ts.day);
// fucking boost. unbelieavable.
auto ticks_per_second =
boost::posix_time::time_duration::rep_type::res_adjust();
boost::posix_time::time_duration td(
ts.hour, ts.min, ts.sec, ts.msec * (ticks_per_second / 1000));
return PTime(dt, td);
}
#ifndef NO_CPP0X
extern "C" BTS_EXPORT LLDateTime ll_today() {
return datetime::ll_from_date(today());
}
BTS_EXPORT Date today() {
auto now = datetime::now();
Date date;
date.year = now.date().year();
date.month = now.date().month();
date.day = now.date().day();
return date;
}
BTS_EXPORT Date add_days(Date indate, int days) {
boost::gregorian::date dt(indate.year, indate.month, indate.day);
auto adjusted_dt = dt + boost::gregorian::days(days);
Date date(adjusted_dt.year(), adjusted_dt.month(), adjusted_dt.day());
return date;
}
extern "C" BTS_EXPORT LLDateTime ll_yesterday() {
auto now = datetime::now() - boost::gregorian::days(1);
return datetime::ll_from_parts(
now.date().year(), now.date().month(), now.date().day(), 0, 0, 0, 0);
}
#endif
} // namespace datetime
} // namespace bts
+168
View File
@@ -0,0 +1,168 @@
#pragma once
// #include "bts_export.h"
#include <chrono>
#include <string>
// #include "windows_wrapper.h" // tick_count.h includes windows.h
namespace boost {
namespace posix_time {
class ptime;
}
} // namespace boost
namespace boost::posix_time {
class time_duration;
} // namespace boost::posix_time
namespace bts::system {
class TimeStamp;
}
namespace bts {
namespace datetime {
typedef boost::posix_time::ptime PTime;
typedef boost::posix_time::time_duration PDuration;
#ifndef BTS_EXPORT
#define BTS_EXPORT
#endif
typedef unsigned long long LLDateTime;
// Simple Date structure
struct Date {
Date() : year(0), month(0), day(0) {}
Date(uint16_t y, uint8_t m, uint8_t d) : year(y), month(m), day(d) {}
Date(uint16_t date)
: year((date >> 9) + 1970), month(date >> 5 & 15), day(date & 31) {}
uint16_t year;
uint8_t month;
uint8_t day;
bool operator<(const Date &other) const {
if (year < other.year)
return true;
if (year == other.year && month < other.month)
return true;
if (year == other.year && month == other.month && day < other.day)
return true;
return false;
}
bool operator>(const Date &other) const {
return !(*this == other) && !(*this < other);
}
bool operator==(const Date &other) const {
return day == other.day && month == other.month && year == other.year;
}
bool operator!=(const Date &other) const { return !(*this == other); }
uint16_t to_short() {
return ((year - 1970) << 9) + (month << 5) + day;
} // NOLINT
};
// extern "C" BTS_EXPORT LLDateTime ll_now();
extern "C" LLDateTime ll_local_now();
extern "C" double OA_date_from_ll(LLDateTime ll);
extern "C" LLDateTime ll_from_OA_date(double oa_date);
extern "C" long unix_now();
#ifndef _WIN32
inline uint64_t unix_nano_now() {
struct timespec ts;
// CLOCK_REALTIME is already anchored at the unix epoch
clock_gettime(CLOCK_REALTIME, &ts);
return static_cast<int64_t>(1000000000UL) * static_cast<int64_t>(ts.tv_sec) +
static_cast<int64_t>(ts.tv_nsec);
}
#endif
// BTS_EXPORT std::chrono::system_clock::time_point
// utc_ll_to_timepoint(unsigned long long ll);
BTS_EXPORT PTime now();
BTS_EXPORT PTime local_now();
// BTS_EXPORT PTime make_ptime(int year, int month, int day);
// BTS_EXPORT PTime make_ptime(
// int year,
// int month,
// int day,
// int hour,
// int minute,
// int second,
// int millisecond);
LLDateTime ptime_to_ll(PTime const &t);
PTime ll_to_ptime(LLDateTime ll);
// extern "C" BTS_EXPORT int
// ll_to_cstr(LLDateTime ll, char *buffer, int buffer_sz);
// BTS_EXPORT long long
// ll_diff_in_us(unsigned long long start, unsigned long long stop);
// BTS_EXPORT long long
// ll_diff_in_seconds(unsigned long long start, unsigned long long stop);
long long ptime_diff_in_us(PTime const &t1, PTime const &t2);
long long ptime_diff_in_ms(PTime const &t1, PTime const &t2);
long long ptime_diff_in_secs(PTime const &t1, PTime const &t2);
PTime timestamp_to_ptime(system::TimeStamp const &t);
system::TimeStamp ptime_to_timestamp(PTime const &t);
long long usecs_since(PTime const &t1);
long long msecs_since(PTime const &t1);
std::string time_string(PTime const &t);
std::string date_string(PTime const &t);
std::string iso_date_time_string(PTime const &t);
PTime parse_iso_date_time_string(std::string const &t);
PTime parse_iso_date_time_tz(const std::string &date);
std::string simple_date_time_string(PTime const &t);
PTime parse_simple_date_time_string(std::string const &t);
PDuration parse_duration(std::string const &time_str);
std::string FIX_date_time_string(PTime const &t);
PTime parse_FIX_timestamp(const std::string &ts);
std::string FIX_timestamp(PTime const &t);
void FIX_timestamp(PTime const &t, std::string *out);
bool is_from_today(PTime const &time);
bool is_at_least_msecs_old(PTime const &time, uint64_t ms);
bool is_at_least_secs_old(PTime const &time, uint64_t sec);
bool is_at_least_mins_old(PTime const &time, uint64_t min);
bool is_at_least_hours_old(PTime const &time, uint64_t hour);
bool is_at_least_hours_old(PTime const &time, uint64_t hour);
bool is_at_least_days_old(PTime const &time, uint64_t days);
LLDateTime ll_from_unix_epoch(long);
LLDateTime ll_from_unix_epoch_ms(long long);
// extern "C" BTS_EXPORT void ll_to_parts(
// LLDateTime,
// int &year,
// int &month,
// int &day,
// int &hour,
// int &minute,
// int &second,
// int &millisecond);
// BTS_EXPORT Date ll_to_date(LLDateTime);
// extern "C" BTS_EXPORT LLDateTime ll_from_parts(
// int year,
// int month,
// int day,
// int hour,
// int minute,
// int second,
// int millisecond);
// BTS_EXPORT LLDateTime ll_from_date(const Date &date);
bool is_local_time_sunday();
bool is_local_time_between(int hh_start, int mm_start, int hh_end, int mm_end);
#ifndef NO_CPP0X
// extern "C" BTS_EXPORT LLDateTime ll_today();
BTS_EXPORT Date today();
// BTS_EXPORT Date add_days(Date date, int days);
// extern "C" BTS_EXPORT LLDateTime ll_yesterday();
#endif
} // namespace datetime
} // namespace bts
// namespace std {
// template <>
// struct numeric_limits<bts::datetime::Date> {
// static bts::datetime::Date min() { return
// bts::datetime::Date(1970,1,1); } static bts::datetime::Date max() {
// return bts::datetime::Date(2097,12,31); }
// };
// }
+83
View File
@@ -0,0 +1,83 @@
#pragma once
#include <type_traits>
#include <utility>
namespace bts::system {
// Provides a very fast, very small roughly equivalent to
// std::function. Unlike std::function instances of Delegate do not
// copy the call back they are bound to.
template <typename Signature>
class Delegate;
template <typename R, typename... Arg>
class Delegate<R(Arg...)> {
private:
void *target_obj_;
R (*invoke_)(void *, Arg...);
template <class T>
struct handler_traits;
template <class T, class Re>
struct handler_traits<Re T::*> {
using HandlerType = T;
using HandlerReturnType = Re;
};
template <auto handler_member, typename Re, typename... Args>
static R invoke_member_handler(void *handler_obj, Args... arg) {
using HandlerMethod = decltype(handler_member);
static_assert(std::is_member_function_pointer<HandlerMethod>::value);
using HandlerType = typename handler_traits<HandlerMethod>::HandlerType;
return (((HandlerType *)handler_obj)->*handler_member)(
std::forward<Arg>(arg)...);
}
template <auto handler, typename Re, typename... Args>
static R invoke_static_handler(void *, Args... arg) {
return handler(std::forward<Arg>(arg)...);
}
static R null_target(Arg...) {
if constexpr (std::is_same<void, R>::value)
return;
else
return {};
}
public:
Delegate() : target_obj_(), invoke_() { bind<&Delegate::null_target>(); }
Delegate(Delegate const &d)
: target_obj_(d.target_obj), invoke_(d.invoke_) {}
Delegate &operator=(Delegate const &d) {
target_obj_ = d.target_obj_;
invoke_ = d.invoke_;
return *this;
}
template <class C>
void bind(C &handler_obj) {
target_obj_ = &handler_obj;
bind<&C::operator()>(handler_obj);
}
template <auto handler_member>
void bind(typename handler_traits<decltype(handler_member)>::HandlerType
&handler_obj) {
target_obj_ = &handler_obj;
invoke_ = invoke_member_handler<handler_member, R, Arg...>;
}
template <auto static_handler>
void bind() {
target_obj_ = nullptr;
invoke_ = invoke_static_handler<static_handler, R, Arg...>;
}
R operator()(Arg... arg) {
return invoke_(target_obj_, std::forward<Arg>(arg)...);
}
};
} // namespace bts::system
+124
View File
@@ -0,0 +1,124 @@
#pragma once
#define ENUM_BASIC_MEMBERS(type) \
Value value; \
\
type() = default; \
/*NOLINTNEXTLINE*/ \
constexpr type(Value value) noexcept : value(value) {} \
\
type(const type &) noexcept = default; \
type(type &&) noexcept = default; \
type &operator=(const type &) noexcept = default; \
type &operator=(type &&) noexcept = default; \
\
~type() = default; \
\
static constexpr bool parse(const char *name, type *value) noexcept; \
static bool parse(const std::string &name, type *value) noexcept; \
\
[[nodiscard]] constexpr const char *name() const noexcept;
#define ENUM_VALUE_TO_NAME_CASE(type, v) \
case type::v: \
return #v;
#define ENUM_NAME_TO_VALUE_CLAUSE(type, v) \
if (name == #v) { \
*value = type::v; \
return true; \
}
#define ENUM_NAMEC_TO_VALUE_CLAUSE(type, v) \
if (std::string_view(name) == #v) { \
*value = type::v; \
return true; \
}
// clang-format off
#define ENUM_NAME_DEF(type, for_all) \
constexpr const char *type::name() const noexcept { \
switch (value) { \
for_all(ENUM_VALUE_TO_NAME_CASE) \
default: return "ENUM_UNKNOWN"; \
} \
}
// clang-format on
#define ENUM_PARSE_DEF(type, for_all) \
constexpr bool type::parse(const char *name, type *value) noexcept { \
for_all(ENUM_NAMEC_TO_VALUE_CLAUSE) return false; \
} \
inline bool type::parse(const std::string &name, type *value) noexcept { \
for_all(ENUM_NAME_TO_VALUE_CLAUSE) return false; \
}
#define ENUM_EQUALITY_OPS(type) \
constexpr bool operator==(type lhs, type rhs) noexcept { \
return lhs.value == rhs.value; \
} \
constexpr bool operator!=(type lhs, type rhs) noexcept { \
return lhs.value != rhs.value; \
} \
constexpr bool operator==(type lhs, type::Value rhs) noexcept { \
return lhs.value == rhs; \
} \
constexpr bool operator!=(type lhs, type::Value rhs) noexcept { \
return lhs.value != rhs; \
}
#define ENUM_OUTPUT_OPS(type) \
inline std::ostream &operator<<(std::ostream &out, type value) noexcept { \
out << value.name(); \
return out; \
} \
\
inline std::ostream &operator<<( \
std::ostream &out, type::Value value) noexcept { \
out << type{value}.name(); \
return out; \
}
#define ENUM_DECLARE_VALUE(t, x) x,
#define ENUM_CLASS_DEF(type, for_all) \
class type { \
public: \
enum Value { for_all(ENUM_DECLARE_VALUE) }; \
\
ENUM_BASIC_MEMBERS(type) \
}; \
\
ENUM_OUTPUT_OPS(type) \
ENUM_NAME_DEF(type, for_all) \
ENUM_PARSE_DEF(type, for_all) \
ENUM_EQUALITY_OPS(type)
#define ENUM_BITWISE_COMPARISON_OP(enum_type, numeric_type, op) \
constexpr enum_type operator op(enum_type lhs, enum_type rhs) noexcept { \
return static_cast<enum_type>(static_cast<numeric_type>(lhs) \
op static_cast<numeric_type>(rhs)); \
}
#define ENUM_BITWISE_COMPARISON_OPS(enum_type, numeric_type) \
ENUM_BITWISE_COMPARISON_OP(enum_type, numeric_type, |) \
ENUM_BITWISE_COMPARISON_OP(enum_type, numeric_type, &) \
ENUM_BITWISE_COMPARISON_OP(enum_type, numeric_type, ^)
#define ENUM_BITWISE_ASSIGNMENT_OP(enum_type, op) \
inline enum_type &operator op##=(enum_type &lhs, enum_type rhs) noexcept { \
lhs = lhs op rhs; \
return lhs; \
}
#define ENUM_BITWISE_ASSIGNMENT_OPS(enum_type) \
ENUM_BITWISE_ASSIGNMENT_OP(enum_type, |) \
ENUM_BITWISE_ASSIGNMENT_OP(enum_type, &) \
ENUM_BITWISE_ASSIGNMENT_OP(enum_type, ^)
#define ENUM_BITWISE_OPS(enum_type, numeric_type) \
ENUM_BITWISE_COMPARISON_OPS(enum_type, numeric_type) \
ENUM_BITWISE_ASSIGNMENT_OPS(enum_type)
#define ENUM_HAS_FLAG(enum_value, flag) (enum_value & flag) == flag
#define ENUM_STRIP_FLAG(enum_value, flag) (enum_value | flag) ^ flag
@@ -0,0 +1,15 @@
#pragma once
#define _EQUALS_ARG_CLAUSE(arg) \
if (lhs.arg != rhs.arg) { \
return false; \
}
#define EQUALS_IMPL(type, for_all) \
inline bool operator==(const type &lhs, const type &rhs) { \
for_all(_EQUALS_ARG_CLAUSE); \
return true; \
} \
inline bool operator!=(const type &lhs, const type &rhs) { \
return !(lhs == rhs); \
}
@@ -0,0 +1,147 @@
#pragma once
#include <string>
#include <variant>
#include <vector>
namespace bts::collections {
template <class Key, class Value>
class FlatUnorderedMap {
public:
using Container = std::vector<std::pair<std::string, std::string>>;
using Iterator = Container::iterator;
using ConstIterator = Container::const_iterator;
using ReverseIterator = Container::reverse_iterator;
using ConstReverseIterator = Container::const_reverse_iterator;
Value *get(const Key &key);
const Value *get(const Key &key) const;
template <class... Args>
std::pair<Iterator, bool> try_emplace(const Key &key, Args... args);
Iterator begin();
ConstIterator begin() const;
ReverseIterator rbegin();
ConstReverseIterator rbegin() const;
Iterator end();
ConstIterator end() const;
ReverseIterator rend();
ConstReverseIterator rend() const;
bool empty() const;
std::size_t size() const;
Value &operator[](const Key &key);
private:
std::vector<std::pair<std::string, std::string>> items_;
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
template <class Key, class Value>
Value *FlatUnorderedMap<Key, Value>::get(const Key &key) {
for (auto &qp : items_) {
if (qp.first == key) {
return &qp.second;
}
}
return nullptr;
}
template <class Key, class Value>
const Value *FlatUnorderedMap<Key, Value>::get(const Key &key) const {
for (auto &qp : items_) {
if (qp.first == key) {
return &qp.second;
}
}
return nullptr;
}
template <class Key, class Value>
template <class... Args>
std::pair<typename FlatUnorderedMap<Key, Value>::Iterator, bool>
FlatUnorderedMap<Key, Value>::try_emplace(const Key &key, Args... args) {
for (auto it = items_.begin(); it != items_.end(); it++) {
if (it->first == key) {
return {it, false};
}
}
Value v(args...);
items_.emplace_back(key, std::move(v));
return {items_.end() - 1, true};
}
template <class Key, class Value>
typename FlatUnorderedMap<Key, Value>::Iterator
FlatUnorderedMap<Key, Value>::begin() {
return items_.begin();
}
template <class Key, class Value>
typename FlatUnorderedMap<Key, Value>::ConstIterator
FlatUnorderedMap<Key, Value>::begin() const {
return items_.begin();
}
template <class Key, class Value>
typename FlatUnorderedMap<Key, Value>::ReverseIterator
FlatUnorderedMap<Key, Value>::rbegin() {
return items_.rbegin();
}
template <class Key, class Value>
typename FlatUnorderedMap<Key, Value>::ConstReverseIterator
FlatUnorderedMap<Key, Value>::rbegin() const {
return items_.rbegin();
}
template <class Key, class Value>
typename FlatUnorderedMap<Key, Value>::Iterator
FlatUnorderedMap<Key, Value>::end() {
return items_.end();
}
template <class Key, class Value>
typename FlatUnorderedMap<Key, Value>::ConstIterator
FlatUnorderedMap<Key, Value>::end() const {
return items_.end();
}
template <class Key, class Value>
typename FlatUnorderedMap<Key, Value>::ReverseIterator
FlatUnorderedMap<Key, Value>::rend() {
return items_.rend();
}
template <class Key, class Value>
typename FlatUnorderedMap<Key, Value>::ConstReverseIterator
FlatUnorderedMap<Key, Value>::rend() const {
return items_.rend();
}
template <class Key, class Value>
bool FlatUnorderedMap<Key, Value>::empty() const {
return items_.empty();
}
template <class Key, class Value>
std::size_t FlatUnorderedMap<Key, Value>::size() const {
return items_.size();
}
template <class Key, class Value>
Value &FlatUnorderedMap<Key, Value>::operator[](const Key &key) {
return try_emplace(key).first->second;
}
} // namespace bts::collections
@@ -0,0 +1,65 @@
#pragma once
#include <fmt/format.h>
#define FMT_IMPL(Type, var_name, ...) \
template <> \
struct fmt::formatter<Type> { \
constexpr auto parse(fmt::format_parse_context &ctx) \
-> decltype(ctx.end()) { \
return ctx.end(); \
} \
\
template <typename FormatContext> \
auto format(const Type &var_name, FormatContext &ctx) const \
-> decltype(ctx.out()) { \
return fmt::format_to(ctx.out(), __VA_ARGS__); \
} \
}
#define FMT_IMPL_T(Type, var_name, ...) \
struct fmt::formatter<Type> { \
constexpr auto parse(fmt::format_parse_context &ctx) \
-> decltype(ctx.end()) { \
return ctx.end(); \
} \
\
template <typename FormatContext> \
auto format(const Type &var_name, FormatContext &ctx) const \
-> decltype(ctx.out()) { \
return fmt::format_to(ctx.out(), __VA_ARGS__); \
} \
}
#define FMT_IMPL_OSTREAM(T) \
template <> \
struct fmt::formatter<T> { \
constexpr auto parse(fmt::format_parse_context &ctx) \
-> decltype(ctx.end()) { \
return ctx.end(); \
} \
\
template <typename FormatContext> \
auto format(const T &t, FormatContext &ctx) const \
-> decltype(ctx.out()) { \
std::stringstream ss; \
ss << t; \
return fmt::format_to(ctx.out(), "{}", ss.str()); \
} \
}
#define FMT_IMPL_OSTREAM_T(T, ...) \
struct fmt::formatter<T<__VA_ARGS__>> { \
constexpr auto parse(fmt::format_parse_context &ctx) \
-> decltype(ctx.end()) { \
return ctx.end(); \
} \
\
template <typename FormatContext> \
auto format(const T<__VA_ARGS__> &t, FormatContext &ctx) const \
-> decltype(ctx.out()) { \
std::stringstream ss; \
ss << t; \
return fmt::format_to(ctx.out(), "{}", ss.str()); \
} \
}
@@ -0,0 +1,27 @@
#pragma once
#include <boost/container_hash/hash.hpp>
#include <cstddef>
namespace bts::collections {
class HashCombiner {
public:
template <class T>
static void combine_hash(std::size_t &seed, T arg);
template <class T, class... T2>
static void combine_hash(std::size_t &seed, T arg, T2... next);
};
template <class T>
void HashCombiner::combine_hash(std::size_t &seed, T arg) {
boost::hash_combine(seed, arg);
}
template <class T, class... T2>
void HashCombiner::combine_hash(std::size_t &seed, T arg, T2... next) {
boost::hash_combine(seed, arg);
combine_hash(seed, next...);
}
} // namespace bts::collections
@@ -0,0 +1,29 @@
#pragma once
#include "bts-system/hash_combiner.h"
#define _HASH_ARG(x) , std::hash<decltype(t.x)>{}(t.x)
#define _HASH_ARG_STRIP_REF(typ, x) \
, std::hash<std::remove_reference< \
std::remove_const<decltype(typ{}.x)>::type>::type>{}(t.x)
#define HASH_IMPL(type, for_all) \
template <> \
struct std::hash<type> { \
std::size_t operator()(const type &t) const noexcept { \
std::size_t rv = 0; \
bts::collections::HashCombiner::combine_hash(rv for_all(_HASH_ARG)); \
return rv; \
} \
}
#define HASH_IMPL_STRIP_REFS(type, for_all) \
template <> \
struct std::hash<type> { \
std::size_t operator()(const type &t) const noexcept { \
std::size_t rv = 0; \
bts::collections::HashCombiner::combine_hash( \
rv for_all(_HASH_ARG_STRIP_REF)); \
return rv; \
} \
}
@@ -0,0 +1,55 @@
#pragma once
#include <nlohmann/json.hpp>
#include <sstream>
#include <string>
namespace bts::system::json {
class JsonDumper {
public:
template <class T>
static std::string dump(const T &value, int indentation = 2);
template <class Iter>
static std::string dump(
Iter first,
Iter last,
int indentation = 2,
const char *separator = ",\n");
static std::string dump(const nlohmann::json &value, int indentation = 2);
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
template <class T>
std::string JsonDumper::dump(const T &value, int indentation) {
nlohmann::json j;
to_json(j, value);
return j.dump(indentation);
}
inline std::string JsonDumper::dump(
const nlohmann::json &value, int indentation) {
return value.dump(indentation);
}
template <class Iter>
std::string JsonDumper::dump(
Iter first, Iter last, int indentation, const char *separator) {
if (first == last) {
return "";
}
std::stringstream str;
auto it = first;
str << "[" << dump(*it, indentation);
while (++it != last) {
str << separator << dump(*it, indentation);
}
str << "]";
return str.str();
}
} // namespace bts::system::json
@@ -0,0 +1,516 @@
#include "json_reader.h"
#include "bts-system/datetime.h"
#include <boost/date_time/posix_time/ptime.hpp>
#include <exception>
#include <limits>
#include <string>
namespace bts::system::json {
using nlohmann::json;
using std::int32_t;
using std::int64_t;
using std::numeric_limits;
using std::stoull;
using std::string;
using std::uint32_t;
using std::uint64_t;
// 'read' fn bodies should be wrapped between BEGIN/END_READ_JSON tags.
// These macros abstract out repetative exception handling code (and a nullptr check).
#define BEGIN_READ_JSON \
if (json_ == nullptr) { \
return false; \
} \
try
#ifdef LOG_JSON_ERRORS
#define END_READ_JSON(type_name) \
catch (const std::exception &e) { \
LOG(Gen, Error, "Failed to parse json to '{}'. Exception: {}", \
std::string{type_name}, std::string{e.what()}); \
} \
return false;
#else
#define END_READ_JSON(type_name) \
catch (...) { \
} \
return false;
#endif
// The idea here is we parse numbers to wide types (either int64, uint64, or double),
// then cast that value to the (potentially narrower) output type. This macro evaluates
// to true if 'wide_val' is within the bounds of the result type (otherwise false).
#define NUM_WITHIN_BOUNDS(wide_val, result_t) \
(wide_val >= \
static_cast<decltype(wide_val)>(numeric_limits<result_t>::min()) && \
wide_val <= \
static_cast<decltype(wide_val)>(numeric_limits<result_t>::max()))
static const nlohmann::json *get_field_json(
const nlohmann::json *json, const std::string &name) {
if (!json || !json->is_object()) {
return nullptr;
}
auto it = json->find(name);
if (it == json->end()) {
return nullptr;
}
return &it.value();
}
JsonReader::JsonReader(const nlohmann::json *json) : json_(json) {}
JsonReader::JsonReader(const nlohmann::json *json, const std::string &name)
: json_(get_field_json(json, name)) {}
bool JsonReader::try_parse(
const std::string &str, nlohmann::json &result, std::string *err) {
try {
result = nlohmann::json::parse(str);
return true;
} catch (const std::exception &ex) {
if (err != nullptr) {
*err = ex.what();
}
}
return false;
}
bool JsonReader::read(const string &name, string &result) const {
return JsonReader{json_, name}.read(result);
}
bool JsonReader::read(const string &name, int32_t &result) const {
return JsonReader{json_, name}.read(result);
}
bool JsonReader::read(const string &name, uint32_t &result) const {
return JsonReader{json_, name}.read(result);
}
bool JsonReader::read(const string &name, int64_t &result) const {
return JsonReader{json_, name}.read(result);
}
bool JsonReader::read(const string &name, uint64_t &result) const {
return JsonReader{json_, name}.read(result);
}
bool JsonReader::read(const string &name, float &result) const {
return JsonReader{json_, name}.read(result);
}
bool JsonReader::read(const string &name, double &result) const {
return JsonReader{json_, name}.read(result);
}
bool JsonReader::read(const string &name, TimeStamp &result) const {
return JsonReader{json_, name}.read(result);
}
bool JsonReader::read_epoch_nanos(const string &name, TimeStamp &value) const {
return JsonReader{json_, name}.read_epoch_nanos(value);
}
bool JsonReader::read_epoch_micros(const string &name, TimeStamp &value) const {
return JsonReader{json_, name}.read_epoch_micros(value);
}
bool JsonReader::read_epoch_millis(const string &name, TimeStamp &value) const {
return JsonReader{json_, name}.read_epoch_millis(value);
}
bool JsonReader::read(const string &name, nlohmann::json &result) const {
return JsonReader{json_, name}.read(result);
}
bool JsonReader::read(int32_t &result) const {
BEGIN_READ_JSON {
switch (json_->type()) {
case nlohmann::json::value_t::number_float: {
const auto d = json_->get<double>();
result = static_cast<int32_t>(d);
if (result != d) {
LOG(Gen, Warn,
"Potential loss of data converting field of type "
"'number_float' ({}) to int32 ({})",
d, result);
}
return true;
}
case nlohmann::json::value_t::number_integer: {
const auto il = json_->get<int64_t>();
if (!NUM_WITHIN_BOUNDS(il, int32_t)) {
LOG(Gen, Error,
"Failed to convert field of type 'number_integer' ({}) to "
"uint32: value is negative.",
il);
}
result = static_cast<int32_t>(il);
return true;
}
case nlohmann::json::value_t::number_unsigned: {
const auto ul = json_->get<uint64_t>();
if (ul > static_cast<uint64_t>(numeric_limits<int32_t>::max())) {
LOG(Gen, Error,
"Failed to convert field of type 'number_unsigned' ({}) to "
"int32: value is too large.",
ul);
return false;
}
result = static_cast<int32_t>(ul);
return true;
}
case nlohmann::json::value_t::string: {
const auto str = json_->get<string>();
if (!str.empty()) {
result = std::stoi(str);
return true;
}
break;
}
default:
return false;
}
}
END_READ_JSON("int32")
}
bool JsonReader::read(uint32_t &result) const {
BEGIN_READ_JSON {
switch (json_->type()) {
case nlohmann::json::value_t::number_float: {
const auto d = json_->get<double>();
result = static_cast<uint32_t>(d);
if (result != d) {
LOG(Gen, Warn,
"Potential loss of data converting field of type "
"'number_float' ({}) to uint32 ({})",
d, result);
}
return true;
}
case nlohmann::json::value_t::number_integer: {
const auto i = json_->get<int64_t>();
if (!NUM_WITHIN_BOUNDS(i, uint32_t)) {
LOG(Gen, Error,
"Failed to convert field of type 'number_integer' ({}) to "
"uint32: value is negative.",
i);
return false;
}
result = static_cast<uint32_t>(i);
return true;
}
case nlohmann::json::value_t::number_unsigned: {
const auto ul = json_->get<uint64_t>();
if (ul > static_cast<uint64_t>(numeric_limits<int32_t>::max())) {
LOG(Gen, Error,
"Failed to convert field of type 'number_unsigned' ({}) to "
"int32: value is too large.",
ul);
return false;
}
result = static_cast<int32_t>(ul);
return true;
}
case nlohmann::json::value_t::string: {
const auto str = json_->get<string>();
if (!str.empty()) {
result = std::stoul(str);
return true;
}
break;
}
default:
return false;
}
}
END_READ_JSON("uint32")
}
bool JsonReader::read(int64_t &result) const {
BEGIN_READ_JSON {
switch (json_->type()) {
case nlohmann::json::value_t::number_float: {
const auto d = json_->get<double>();
if (result != d) {
LOG(Gen, Warn,
"Potential loss of data converting field of type "
"'number_float' ({}) to int64 ({})",
d, result);
}
result = static_cast<int64_t>(d);
return true;
}
case nlohmann::json::value_t::number_integer: {
result = json_->get<int64_t>();
return true;
}
case nlohmann::json::value_t::number_unsigned: {
const auto ul = json_->get<uint64_t>();
if (NUM_WITHIN_BOUNDS(ul, int64_t)) {
LOG(Gen, Error,
"Potential loss of data converting field of type "
"'number_unsigned' ({}) to int64 ({})",
ul, result);
return false;
}
result = static_cast<int64_t>(ul);
return true;
}
case nlohmann::json::value_t::string: {
const auto str = json_->get<string>();
if (!str.empty()) {
result = std::stol(str);
return true;
}
break;
}
default:
return false;
}
}
END_READ_JSON("int64")
}
bool JsonReader::read(uint64_t &result) const {
BEGIN_READ_JSON {
switch (json_->type()) {
case nlohmann::json::value_t::number_float: {
const auto d = json_->get<double>();
if (result != d) {
LOG(Gen, Warn,
"Potential loss of data converting field of type "
"'number_float' ({}) to uint64 ({})",
d, result);
}
result = static_cast<uint64_t>(d);
return true;
}
case nlohmann::json::value_t::number_integer: {
const auto il = json_->get<int64_t>();
if (NUM_WITHIN_BOUNDS(il, uint64_t)) {
LOG(Gen, Error,
"Potential loss of data converting field of type "
"'number_integer' ({}) to uint64 ({})",
il, result);
return false;
}
result = static_cast<uint64_t>(il);
return true;
}
case nlohmann::json::value_t::number_unsigned: {
result = json_->get<uint64_t>();
return true;
}
case nlohmann::json::value_t::string: {
const auto str = json_->get<string>();
if (!str.empty()) {
result = std::stoul(str);
return true;
}
break;
}
default:
return false;
}
}
END_READ_JSON("uint64")
}
bool JsonReader::read(float &result) const {
BEGIN_READ_JSON {
switch (json_->type()) {
case nlohmann::json::value_t::number_float:
case nlohmann::json::value_t::number_integer:
case nlohmann::json::value_t::number_unsigned:
json_->get_to(result);
return true;
case nlohmann::json::value_t::string: {
const auto str = json_->get<string>();
if (!str.empty()) {
result = std::stof(str);
return true;
}
break;
}
default:
return false;
}
}
END_READ_JSON("float")
}
bool JsonReader::read(double &result) const {
BEGIN_READ_JSON {
switch (json_->type()) {
case nlohmann::json::value_t::number_float:
case nlohmann::json::value_t::number_integer:
case nlohmann::json::value_t::number_unsigned:
json_->get_to(result);
return true;
case nlohmann::json::value_t::string: {
const auto str = json_->get<string>();
if (!str.empty()) {
result = std::stod(str);
return true;
}
break;
}
default:
return false;
}
}
END_READ_JSON("double")
}
bool JsonReader::read(string &result) const {
BEGIN_READ_JSON {
json_->get_to(result);
return true;
}
END_READ_JSON("string")
}
bool JsonReader::read(nlohmann::json &result) const {
BEGIN_READ_JSON {
json_->get_to(result);
return true;
}
END_READ_JSON("json-object")
}
bool JsonReader::read_flat(std::vector<nlohmann::json> &result) const {
if (json_ == nullptr) {
return false;
}
switch (json_->type()) {
case json::value_t::array:
return std::all_of(json_->begin(), json_->end(), [&](const auto &j) {
return JsonReader{&j}.read_flat(result);
});
case json::value_t::null:
return false;
default:
read(result.emplace_back());
return true;
}
}
bool JsonReader::parse_time_stamp_(const string &str, TimeStamp &value) const {
return parse_iso_time_stamp_(str, value) ||
parse_epoch_nanos_time_stamp_(str, value);
}
bool JsonReader::parse_iso_time_stamp_(
const string &str, TimeStamp &value) const {
value = TimeStamp::from_iso_string(str);
return true;
}
bool JsonReader::parse_epoch_nanos_time_stamp_(
const string &str, TimeStamp &value) const {
uint64_t nanos = stoull(str);
value = TimeStamp{nanos};
return true;
}
bool JsonReader::read(TimeStamp &result) const {
BEGIN_READ_JSON {
switch (json_->type()) {
case nlohmann::json::value_t::null:
case nlohmann::json::value_t::object:
case nlohmann::json::value_t::array:
case nlohmann::json::value_t::boolean:
case nlohmann::json::value_t::binary:
case nlohmann::json::value_t::discarded:
return false;
case nlohmann::json::value_t::string: {
const auto str = json_->get<string>();
return parse_iso_time_stamp_(str, result) ||
parse_epoch_nanos_time_stamp_(str, result);
}
case nlohmann::json::value_t::number_integer:
case nlohmann::json::value_t::number_unsigned:
case nlohmann::json::value_t::number_float:
// Maybe we could assume seconds here, idk though...when in doubt, prob
// better to be cautious than cavalier
return false;
}
}
END_READ_JSON("TimeStamp")
}
bool JsonReader::read_iso(const std::string &name, TimeStamp &result) const {
return JsonReader{json_, name}.read_iso(result);
}
bool JsonReader::read_iso(TimeStamp &result) const {
BEGIN_READ_JSON {
switch (json_->type()) {
case nlohmann::json::value_t::string: {
return parse_iso_time_stamp_(json_->get<string>(), result);
}
default:
return false;
}
}
END_READ_JSON("TimeStamp")
}
bool JsonReader::read_epoch_nanos(TimeStamp &value) const {
int64_t nanos;
if (!read(nanos)) {
return false;
}
value = TimeStamp::from_nanos(nanos);
return true;
}
bool JsonReader::read_epoch_micros(TimeStamp &value) const {
int64_t micros;
if (!read(micros)) {
return false;
}
value = TimeStamp::from_micros(micros);
return true;
}
bool JsonReader::read_epoch_millis(TimeStamp &value) const {
int64_t millis;
if (!read(millis)) {
return false;
}
value = TimeStamp::from_millis(millis);
return true;
}
bool JsonReader::read_optional_epoch_millis(
std::optional<TimeStamp> &result) const {
if (json_ == nullptr) {
return false;
}
if (json_->is_null()) {
result = std::nullopt;
return true;
}
return read_epoch_millis(result.emplace());
}
bool JsonReader::read_optional_epoch_millis(
const std::string &name, std::optional<TimeStamp> &result) const {
if (!json_ || !json_->is_object()) {
return false;
}
auto it = json_->find(name);
if (it == json_->end()) {
return false;
}
return JsonReader{&*it}.read_epoch_millis(result.emplace());
}
} // namespace bts::system::json
@@ -0,0 +1,352 @@
#pragma once
#include "bts-system/bts_assert.h"
#include "bts-system/logging.h"
#include "bts-system/overloaded.h"
#include "bts-system/time_stamp.h"
#include <cstdint>
#include <nlohmann/json.hpp>
#include <optional>
#include <string>
#include <variant>
namespace bts::system::json {
#ifdef LOG_JSON_READ_ERRORS
#define JSON_READ_AS(json, field, type, dest) \
type dest; \
JsonReader{&json}.read(#field, dest)
#define JSON_READ_AS_ELSE(js, field, type, dest, else) \
type dest; \
if (!JsonReader{&js}.read(#field, dest)) { \
_ERROR( \
"{}. Failed to read {} field '{}'", string{#field}, \
string{__FUNCTION__}, system::json::JsonDumper::dump(js)); \
else; \
}
#else
#define JSON_READ_AS(json, field, type, dest) \
type dest; \
JsonReader{&json}.read(#field, dest);
#define JSON_READ_AS_ELSE(json, field, type, dest, else) \
type dest; \
if (!JsonReader{&json}.read(#field, dest)) { \
else; \
}
#endif
#define JSON_READ(json, field, type) JSON_READ_AS(json, field, type, field)
/**
* Reads the json fields. If the read fails, performs the 'else' action
*/
#define JSON_READ_ELSE(j, field, type, else) \
JSON_READ_AS_ELSE(j, field, type, field, else)
class JsonReader {
public:
explicit JsonReader(const nlohmann::json *json);
JsonReader(const nlohmann::json *json, const std::string &name);
static bool try_parse(
const std::string &str,
nlohmann::json &result,
std::string *err = nullptr);
bool read(const std::string &name, std::string &result) const;
bool read(const std::string &name, std::int32_t &result) const;
bool read(const std::string &name, std::uint32_t &result) const;
bool read(const std::string &name, std::int64_t &result) const;
bool read(const std::string &name, std::uint64_t &result) const;
bool read(const std::string &name, float &result) const;
bool read(const std::string &name, double &result) const;
bool read(const std::string &name, nlohmann::json &result) const;
bool read(const std::string &name, system::TimeStamp &result) const;
bool read_iso(const std::string &name, system::TimeStamp &result) const;
bool read_epoch_nanos(
const std::string &name, system::TimeStamp &result) const;
bool read_epoch_micros(
const std::string &name, system::TimeStamp &result) const;
bool read_epoch_millis(
const std::string &name, system::TimeStamp &result) const;
template <class T>
bool read(const std::string &name, T &result) const;
template <class T>
bool read_one_or_more(
const std::string &name,
std::vector<T> &result,
bool append = true) const;
template <class T>
T read(const std::string &name) const;
bool read(std::string &result) const;
bool read(std::int32_t &result) const;
bool read(std::uint32_t &result) const;
bool read(std::int64_t &result) const;
bool read(std::uint64_t &result) const;
bool read(float &result) const;
bool read(double &result) const;
bool read(nlohmann::json &result) const;
bool read(system::TimeStamp &result) const;
bool read_iso(system::TimeStamp &result) const;
bool read_epoch_nanos(system::TimeStamp &result) const;
bool read_epoch_micros(system::TimeStamp &result) const;
bool read_epoch_millis(system::TimeStamp &result) const;
template <class T>
bool read_one_or_more(std::vector<T> &result, bool append = true) const;
template <class T>
static bool read_flat(
const std::vector<nlohmann::json> &items, std::vector<T> &result);
bool read_flat(std::vector<nlohmann::json> &result) const;
template <class T>
bool read_optional(std::optional<T> &result) const;
template <class T>
bool read_optional(const std::string &name, std::optional<T> &result) const;
bool read_optional_epoch_millis(
std::optional<system::TimeStamp> &result) const;
bool read_optional_epoch_millis(
const std::string &name, std::optional<system::TimeStamp> &result) const;
template <class T>
bool read(T &result) const;
template <class T>
T read() const;
/**
* @brief Reads a json field into \p result
*
* @details If the read fails, assigns \p default_value to \p result
* @return True if the read succeeds, otherwise false
*/
template <class T>
bool read_or(const std::string &name, T &result, T default_value) const;
template <class T1, class T2>
int read_variant(
const std::string &name1,
const std::string &name2,
std::variant<T1, T2> &result);
private:
const nlohmann::json *json_;
bool parse_time_stamp_(
const std::string &str, system::TimeStamp &result) const;
bool parse_iso_time_stamp_(
const std::string &str, system::TimeStamp &result) const;
bool parse_epoch_nanos_time_stamp_(
const std::string &str, system::TimeStamp &result) const;
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
#define CATCH_PARSE_ERRORS(type_name) \
catch (const std::exception &e) { \
LOG(Gen, Diag, "Failed to parse json to '{}'. Exception: {}", \
std::string{type_name}, std::string{e.what()}); \
} \
catch (...) { \
bts_assert_msgf( \
false, "Failed to parse json to '{}'. Exception: unknown", \
std::string{type_name}); \
}
template <class T>
inline bool JsonReader::read(const std::string &name, T &result) const {
if (!json_ || !json_->is_object()) {
return false;
}
auto it = json_->find(name);
if (it == json_->end()) {
return false;
}
it->get_to(result);
return true;
}
template <class T>
inline bool JsonReader::read_one_or_more(
const std::string &name, std::vector<T> &result, bool append) const {
if (!json_ || !json_->is_object()) {
return false;
}
auto it = json_->find(name);
if (it == json_->end()) {
return false;
}
try {
switch (it->type()) {
case nlohmann::json::value_t::array:
if (append) {
for (const auto &item : it.value()) {
item.get_to(result.emplace_back());
}
} else {
it.value().get_to(result);
}
return true;
default:
if (append) {
it.value().get_to(result.emplace_back());
} else {
if (result.empty()) {
result.emplace_back();
}
it.value().get_to(result.at(0));
result.erase(result.begin() + 1, result.end());
}
return true;
}
}
CATCH_PARSE_ERRORS(typeid(T).name())
return false;
}
template <class T>
inline bool JsonReader::read_one_or_more(
std::vector<T> &result, bool append) const {
if (!json_) {
return false;
}
try {
switch (json_->type()) {
case nlohmann::json::value_t::array:
if (append) {
for (const auto &item : json_->array()) {
item.get_to(result.emplace_back());
}
} else {
json_->get_to(result);
}
return true;
default:
if (append) {
json_->get_to(result.emplace_back());
} else {
if (result.empty()) {
result.emplace_back();
}
json_->get_to(result.at(0));
result.erase(result.begin() + 1, result.end());
}
return true;
}
}
CATCH_PARSE_ERRORS(typeid(T).name());
return false;
}
template <class T>
inline bool JsonReader::read(T &result) const {
try {
json_->get_to(result);
return true;
} catch (...) {
const auto j_str = json_->dump(2);
LOG(md, Error, "Exception parsing json:\n{}", j_str);
return false;
}
}
template <class T>
inline T JsonReader::read() const {
T result;
read(result);
return result;
}
template <class T>
inline bool JsonReader::read_optional(std::optional<T> &result) const {
if (json_ == nullptr) {
return false;
}
if (json_->is_null()) {
result = std::nullopt;
return true;
}
return read(result.emplace());
}
template <class T>
inline bool JsonReader::read_optional(
const std::string &name, std::optional<T> &result) const {
if (!json_ || !json_->is_object()) {
return false;
}
auto it = json_->find(name);
if (it == json_->end()) {
return false;
}
return JsonReader{&it.value()}.read_optional(result);
}
template <class T>
inline T JsonReader::read(const std::string &name) const {
bts_assert(json_ && json_->is_object());
auto it = json_->find(name);
bts_assert_msgf(it != json_->end(), "json doesn't contain field '{}'", name);
return JsonReader{&it.value()}.read<T>();
}
template <class T>
inline bool JsonReader::read_or(
const std::string &name, T &result, T default_value) const {
if (read(name, result)) {
return true;
}
result = default_value;
return false;
}
template <class T>
bool JsonReader::read_flat(
const std::vector<nlohmann::json> &items, std::vector<T> &result) {
std::vector<nlohmann::json> flat_js;
for (const auto &item_j : items) {
if (!JsonReader{&item_j}.read_flat(flat_js)) {
return false;
}
}
for (const auto &el_j : flat_js) {
JsonReader{&el_j}.read(result.emplace_back());
}
return true;
}
template <class T1, class T2>
int JsonReader::read_variant(
const std::string &name1,
const std::string &name2,
std::variant<T1, T2> &result) {
auto it = json_->find(name1);
if (it != json_->end()) {
result = it->get<T1>();
return 1;
}
it = json_->find(name2);
if (it != json_->end()) {
result = it->get<T2>();
return 2;
}
return 0;
}
#undef CATCH_PARSE_ERRORS
} // namespace bts::system::json
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include "bts-system/console_logger.h"
#include <fmt/format.h>
#include <iomanip>
#include <iostream>
#define LOG(category, level, msg_template, ...) \
bts::system::ConsoleLogger::log( \
fmt::format(msg_template, __VA_ARGS__), bts::system::TimeStamp::now(), \
__FILE__, __LINE__, #level, #category);
@@ -0,0 +1,11 @@
#pragma once
// #if __cplusplus >= 201703L
#include <variant>
// Helper to visit std::variant
namespace bts {
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template <class... Ts>
overloaded(Ts...) -> overloaded<Ts...>; // not needed as of C++20
} // namespace bts
// #endif
@@ -0,0 +1,81 @@
#pragma once
#include <cstddef>
#include <iostream>
#include <string>
#include <vector>
namespace bts::system {
class StringSplitter {
public:
template <class Range>
static std::vector<std::string> split(
Range range, const std::string &delimiter);
template <class Iterator>
static std::vector<std::string> split(
Iterator first, Iterator last, const std::string &delimiter);
template <class Iterator>
static void split(
Iterator first,
Iterator last,
const std::string &delimiter,
std::vector<std::string> &parts);
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
template <class Range>
std::vector<std::string> StringSplitter::split(
Range range, const std::string &delimiter) {
std::vector<std::string> parts;
split(range.begin(), range.end(), delimiter, parts);
return parts;
}
template <class Iterator>
std::vector<std::string> StringSplitter::split(
Iterator first, Iterator last, const std::string &delimiter) {
std::vector<std::string> parts;
split(first, last, delimiter, parts);
return parts;
}
template <class Iterator>
void StringSplitter::split(
Iterator first,
Iterator last,
const std::string &delimiter,
std::vector<std::string> &parts) {
auto cursor = first;
auto curr_end = first;
auto curr_start = first;
auto find_delim = [&cursor, &delimiter, &last]() {
for (auto it = cursor; it != last; it++) {
if (*it == delimiter[0]) {
if (std::equal(
it + 1, it + delimiter.size(), delimiter.begin() + 1,
delimiter.end())) {
return it;
}
}
}
return last;
};
while ((curr_end = find_delim()) != last) {
parts.push_back(std::string(curr_start, curr_end));
curr_start = curr_end + delimiter.size();
cursor = curr_end + delimiter.size();
}
if (cursor != last) {
parts.push_back(std::string(cursor, last));
}
}
} // namespace bts::utils
@@ -0,0 +1,176 @@
#pragma once
#include <cstring>
#include <functional>
#include <iomanip>
#include <ostream>
#include <tuple>
#include <vector>
namespace bts::system {
class TableFormatter {
public:
template <class T>
using Schema =
std::vector<std::tuple<std::string, int, std::function<std::string(T)>>>;
template <class T, class Range>
static std::ostream &format(
std::ostream &out, const Range &range, const Schema<T> &schema);
template <class T, class TIter>
static std::ostream &format(
std::ostream &out, TIter first, TIter last, const Schema<T> &schema);
template <class T>
static std::ostream &format_record(
std::ostream &out,
const T &record,
const Schema<T> &schema,
bool headers = false);
template <class TRow>
class SchemaBuilder {
public:
Schema<TRow> result;
template <class GetCell>
void add(std::string name, GetCell get_cell, int width = 0);
};
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
template <class TRow>
template <class GetCell>
inline void TableFormatter::SchemaBuilder<TRow>::add(
std::string name, GetCell get_cell, int width) {
std::function<std::string(TRow)> get_cell_str = [&get_cell](const TRow &r) {
std::stringstream ss;
ss << get_cell(r);
return ss.str();
};
result.emplace_back(std::move(name), width, get_cell_str);
}
template <class T, class Range>
std::ostream &TableFormatter::format(
std::ostream &out, const Range &range, const Schema<T> &schema) {
return format(out, range.begin(), range.end(), schema);
}
template <class T, class TIter>
inline std::ostream &TableFormatter::format(
std::ostream &out,
TIter first,
TIter last,
const typename TableFormatter::Schema<T> &schema) {
if (first == last || schema.size() == 0) return out;
int total_width = 0;
for (auto col : schema) {
total_width += std::get<1>(col);
}
// top border of table
for (int i = 0; i < total_width + static_cast<int>(schema.size()); i++) {
out << "-";
}
out << std::endl;
// column headers themselves
out << "|";
for (auto col : schema) {
out << std::setw(std::get<1>(col)) << std::get<0>(col) << "|";
}
// line between column headers and rows
out << std::endl;
for (int i = 0; i < total_width + static_cast<int>(schema.size()); i++) {
out << "-";
}
// rows
while (first != last) {
out << std::endl;
out << "|";
for (uint32_t i = 0; i < schema.size(); i++) {
out << std::setw(std::get<1>(schema[i]))
<< std::get<2>(schema[i])(*first) << "|";
}
first++;
}
// bottom border of table
out << std::endl;
for (int i = 0; i < total_width + static_cast<int>(schema.size()); i++)
out << "-";
return out;
}
template <class T>
inline std::ostream &TableFormatter::format_record(
std::ostream &out,
const T &record,
const typename TableFormatter::Schema<T> &schema,
bool headers) {
constexpr const char *const field_header = "Field";
constexpr const char *const value_header = "Value";
int ulabel_width = headers ? strlen(field_header) : 0;
for (const auto &field : schema) {
auto label_width = std::get<1>(field);
if (label_width == 0) {
label_width = static_cast<int>(std::get<0>(field).size());
}
if (label_width > ulabel_width) {
ulabel_width = label_width;
}
}
std::vector<std::string> field_vals;
for (const auto &field : schema) {
field_vals.push_back(std::get<2>(field)(record));
}
int uvalue_width = headers ? strlen(value_header) : 0;
for (const auto &v : field_vals) {
auto value_width = static_cast<int>(v.size());
if (value_width > uvalue_width) {
uvalue_width = value_width;
}
}
int table_width = ulabel_width + uvalue_width + 4;
if (headers) {
// top border of table
for (int i = 0; i < table_width + 3; i++) {
out << "-";
}
out << std::endl;
// column headers
out << "| " << std::setw(ulabel_width) << field_header << " | "
<< std::setw(uvalue_width) << value_header << " |\n";
}
// border between col headers and rows (or top border of table, if 'headers'
// is false)
for (int i = 0; i < table_width + 3; i++) {
out << "-";
}
// rows
for (std::size_t i = 0; i < field_vals.size(); i++) {
out << "\n";
const auto &label = std::get<0>(schema.at(i));
const auto &val = field_vals.at(i);
out << "| " << std::setw(ulabel_width) << label << " | "
<< std::setw(uvalue_width) << val << " |";
}
out << "\n";
// bottom border of table
for (int i = 0; i < table_width + 3; i++) {
out << "-";
}
return out;
}
} // namespace bts::core
@@ -0,0 +1,30 @@
#pragma once
#include <cstdint>
#include <nlohmann/json.hpp>
#include <string>
#include <vector>
namespace bts::system::threading {
class ThreadConfig {
public:
std::string name_rx;
bool exclude;
std::vector<std::uint32_t> cores;
};
inline void to_json(
nlohmann ::json &nlohmann_json_j, const ThreadConfig &nlohmann_json_t) {
nlohmann_json_j["name_rx"] = nlohmann_json_t.name_rx;
nlohmann_json_j["exclude"] = nlohmann_json_t.exclude;
nlohmann_json_j["cores"] = nlohmann_json_t.cores;
}
inline void from_json(
const nlohmann ::json &nlohmann_json_j, ThreadConfig &nlohmann_json_t) {
nlohmann_json_j.at("name_rx").get_to(nlohmann_json_t.name_rx);
nlohmann_json_j.at("exclude").get_to(nlohmann_json_t.exclude);
nlohmann_json_j.at("cores").get_to(nlohmann_json_t.cores);
};
} // namespace bts::system::threading
@@ -0,0 +1,117 @@
#include "thread_manager.h"
#include "bts-system/bts_assert.h"
#include "bts-system/logging.h"
#include "bts-system/threading/thread_config.h"
#include <fmt/format.h>
#include <functional>
#include <pthread.h>
#include <regex>
#include <string>
#include <sys/prctl.h>
namespace bts::system::threading {
using std::function;
using std::string;
using std::uint32_t;
using std::vector;
ThreadConfig ThreadManager::get_thread_cfg(const string &name) {
for (const auto &cfg : this->threads) {
std::smatch match;
std::regex rgx{cfg.name_rx};
if (std::regex_search(name, match, rgx)) {
return cfg;
}
}
bts_assert_msg(
false, fmt::format("No configuration found for thread {}", name));
}
vector<uint32_t> ThreadManager::get_affinity(const string &name) {
vector<uint32_t> result;
auto cfg = get_thread_cfg(name);
if (cfg.exclude) {
auto total_cpus = sysconf(_SC_NPROCESSORS_ONLN);
auto &cores = cfg.cores;
for (int i = 0; i < total_cpus; i++) {
if (std::find(cores.begin(), cores.end(), i) == cores.end()) {
result.push_back(i);
}
}
} else {
for (const auto &core : cfg.cores) {
result.push_back(core);
}
}
return result;
}
void configure_thread(
const string &name, const ThreadConfig &cfg, pthread_t t) {
if (!cfg.cores.empty()) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
if (cfg.exclude) {
auto total_cpus = sysconf(_SC_NPROCESSORS_ONLN);
for (int i = 0; i < total_cpus; i++) {
CPU_SET(i, &cpuset);
}
for (const auto &core : cfg.cores) {
CPU_CLR(core, &cpuset);
}
} else {
for (const auto &core : cfg.cores) {
CPU_SET(core, &cpuset);
}
}
int rc = pthread_setaffinity_np(t, sizeof(cpu_set_t), &cpuset);
if (rc != 0) {
LOG(Gen, Error,
"Could not set core affinity for {} on thread {}. Error "
"code={}",
name, cfg.name_rx, rc);
}
}
}
void ThreadManager::set_thread_name(const string &name) {
prctl(PR_SET_NAME, name.c_str(), 0, 0, 0);
}
void ThreadManager::configure_current_thread(const string &name) {
configure_thread(name, get_thread_cfg(name), pthread_self());
set_thread_name(name);
}
void ThreadManager::configure_current_thread(
const string &name, ThreadConfig const &cfg) {
configure_thread(name, cfg, pthread_self());
set_thread_name(name);
}
std::thread ThreadManager::make_thread_from_function(
const string &name, function<void()> const &c) {
auto result = std::thread([c, name]() {
set_thread_name(name);
c();
});
configure_thread(name, get_thread_cfg(name), result.native_handle());
return result;
}
std::thread ThreadManager::make_thread_from_function(
const string &name, function<void()> const &c, ThreadConfig const &cfg) {
auto result = std::thread([c, name]() {
set_thread_name(name);
c();
});
configure_thread(name, cfg, result.native_handle());
return result;
}
} // namespace bts::system::threading
Loaded 100 of 117 files, more files were not shown because too many files have changed in this diff. Show more