Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2d47a62f4 | ||
|
|
4be9f399aa | ||
|
|
aaf55ba65b | ||
|
|
9edf1dbd1a | ||
|
|
2dc9f8d71b | ||
|
|
addff775e9 | ||
|
|
f18c952ab8 | ||
|
|
326b068905 | ||
|
|
249221c654 | ||
|
|
bc9114e5a0 | ||
|
|
56ae5dad35 | ||
|
|
4ea6a9138f | ||
|
|
a38b24a910 | ||
|
|
fd9ed211b4 | ||
|
|
bfc3106b00 | ||
|
|
c8fb032a13 | ||
|
|
df11e6564f | ||
|
|
6cf4200bad | ||
|
|
d91642ecd0 |
No files matched your search
+7
-2
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 \
|
||||
|
||||
+1
-1
Submodule externals updated: de4b394a29...eb228e2b25.
@@ -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)
|
||||
+73
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace bts {
|
||||
namespace crypto {
|
||||
|
||||
class AlgoType {
|
||||
public:
|
||||
enum type { RSA = 0 };
|
||||
};
|
||||
|
||||
} // namespace crypto
|
||||
} // namespace bts
|
||||
@@ -1,10 +0,0 @@
|
||||
#include "CipherType.h"
|
||||
|
||||
namespace bts {
|
||||
namespace crypto {
|
||||
|
||||
const char *CipherType::name[] = {
|
||||
"None", "DES-EDE3-CBC", "3des", "idea", "aes128"};
|
||||
|
||||
}
|
||||
} // namespace bts
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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());
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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());
|
||||
@@ -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
|
||||
@@ -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());
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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); }
|
||||
// };
|
||||
// }
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
Reference in new issue
Block a user