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
@@ -1,6 +1,3 @@
|
||||
[submodule "externals"]
|
||||
path = externals
|
||||
url = git@bitbucket.org:bluetradesys/externals.git
|
||||
[submodule "vcpkg"]
|
||||
path = vcpkg
|
||||
url = https://github.com/microsoft/vcpkg.git
|
||||
+7
-40
@@ -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,12 +112,14 @@ 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()
|
||||
if (BUILD_ACTIV_MD)
|
||||
add_subdirectory(src/app_activ-tester)
|
||||
endif()
|
||||
add_subdirectory(src/lib)
|
||||
if (BUILD_ADMIN_WEB_APP)
|
||||
add_subdirectory(src/Spark.Admin.WebApp)
|
||||
@@ -130,36 +132,6 @@ else()
|
||||
endif()
|
||||
|
||||
if(BUILD_SPARKD)
|
||||
function(copy_shared_lib lib_file_name)
|
||||
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/src/server/sparkd/${lib_file_name}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${EXTERNALS_ROOT}/lib/Debug/${lib_file_name}
|
||||
${CMAKE_BINARY_DIR}/src/server/sparkd/${lib_file_name}
|
||||
DEPENDS ${EXTERNALS_ROOT}/lib/Debug/${lib_file_name})
|
||||
endfunction()
|
||||
|
||||
copy_shared_lib(libActivAgentApplication_x86-64_ubuntu22-04_gcc123_mdid.so)
|
||||
copy_shared_lib(libActivBase_x86-64_ubuntu22-04_gcc123_mdid.so)
|
||||
copy_shared_lib(libActivFramework_x86-64_ubuntu22-04_gcc123_mdid.so)
|
||||
copy_shared_lib(libActivOneApi_x86-64_ubuntu22-04_gcc123_mdid.so)
|
||||
copy_shared_lib(libActivDatabase_x86-64_ubuntu22-04_gcc123_mdid.so)
|
||||
copy_shared_lib(libActivServices_x86-64_ubuntu22-04_gcc123_mdid.so)
|
||||
copy_shared_lib(libBoostAtomic_x86-64_ubuntu22-04_gcc123_mdid.so)
|
||||
copy_shared_lib(libBoostChrono_x86-64_ubuntu22-04_gcc123_mdid.so)
|
||||
copy_shared_lib(libBoostFilesystem_x86-64_ubuntu22-04_gcc123_mdid.so)
|
||||
copy_shared_lib(libBoostProgramOptions_x86-64_ubuntu22-04_gcc123_mdid.so)
|
||||
add_custom_target(activ_shared_libs ALL DEPENDS
|
||||
${CMAKE_BINARY_DIR}/src/server/sparkd/libActivAgentApplication_x86-64_ubuntu22-04_gcc123_mdid.so
|
||||
${CMAKE_BINARY_DIR}/src/server/sparkd/libActivBase_x86-64_ubuntu22-04_gcc123_mdid.so
|
||||
${CMAKE_BINARY_DIR}/src/server/sparkd/libActivFramework_x86-64_ubuntu22-04_gcc123_mdid.so
|
||||
${CMAKE_BINARY_DIR}/src/server/sparkd/libActivOneApi_x86-64_ubuntu22-04_gcc123_mdid.so
|
||||
${CMAKE_BINARY_DIR}/src/server/sparkd/libActivDatabase_x86-64_ubuntu22-04_gcc123_mdid.so
|
||||
${CMAKE_BINARY_DIR}/src/server/sparkd/libActivServices_x86-64_ubuntu22-04_gcc123_mdid.so
|
||||
${CMAKE_BINARY_DIR}/src/server/sparkd/libBoostAtomic_x86-64_ubuntu22-04_gcc123_mdid.so
|
||||
${CMAKE_BINARY_DIR}/src/server/sparkd/libBoostChrono_x86-64_ubuntu22-04_gcc123_mdid.so
|
||||
${CMAKE_BINARY_DIR}/src/server/sparkd/libBoostFilesystem_x86-64_ubuntu22-04_gcc123_mdid.so
|
||||
${CMAKE_BINARY_DIR}/src/server/sparkd/libBoostProgramOptions_x86-64_ubuntu22-04_gcc123_mdid.so)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/server
|
||||
@@ -184,11 +156,6 @@ else()
|
||||
FILES
|
||||
"${SPARK_LIB}/libACE-5.7.5.so"
|
||||
"${SPARK_LIB}/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.16.0.0"
|
||||
"${SPARK_LIB}/libActivFramework_x86-64_ubuntu22-04_gcc123_mdid.so"
|
||||
"${SPARK_LIB}/libActivOneApi_x86-64_ubuntu22-04_gcc123_mdid.so"
|
||||
"${SPARK_LIB}/libActivServices_x86-64_ubuntu22-04_gcc123_mdid.so"
|
||||
"${SPARK_LIB}/libBoostAtomic_x86-64_ubuntu22-04_gcc123_mdid.so"
|
||||
"${SPARK_LIB}/libBoostFilesystem_x86-64_ubuntu22-04_gcc123_mdid.so"
|
||||
DESTINATION "${SPARK_INSTALL_PREFIX}/bin")
|
||||
endif()
|
||||
endif()
|
||||
+2
-31
@@ -26,7 +26,6 @@
|
||||
"CMAKE_CXX_STANDARD": "20",
|
||||
"CMAKE_SHARED_LINKER_FLAGS_DEBUG": "/DEBUG",
|
||||
"CMAKE_SHARED_LINKER_FLAGS_RELEASE": "/OPT:REF /OPT:ICF /LTCG",
|
||||
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
|
||||
"EXTERNALS_ROOT": "${sourceDir}/externals/windows",
|
||||
"CONFIG_DEFAULTS_PATH": "config/profiles/default/*.conf",
|
||||
"DOTNET_BINARY_DIR": "${sourceDir}/build/$<CONFIG>/bin/net7.0",
|
||||
@@ -46,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",
|
||||
@@ -113,29 +107,13 @@
|
||||
"description": "Debug build compiled w/ gcc",
|
||||
"binaryDir": "${sourceDir}/build/gcc/debug",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER": "gcc-12",
|
||||
"CMAKE_CXX_COMPILER": "g++-12",
|
||||
"CMAKE_C_COMPILER": "gcc",
|
||||
"CMAKE_CXX_COMPILER": "g++",
|
||||
"CMAKE_CXX_FLAGS_DEBUG": "-g -fPIC -Wall -pedantic -Wextra -Werror -Wno-missing-braces -Wno-ignored-qualifiers --std=c++20 -fdiagnostics-color -Wno-stringop-overread -DDEBUG",
|
||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install/gcc/debug/sparkd"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "activ-tester",
|
||||
"inherits": "sparkd-debug-gcc",
|
||||
"displayName": "Activ Tester Debug (gcc)",
|
||||
"description": "Activ Tester Debug build compiled w/ gcc",
|
||||
"binaryDir": "${sourceDir}/build/${presetName}",
|
||||
"cacheVariables": {
|
||||
"BUILD_ACTIV_MD": {
|
||||
"type": "BOOL",
|
||||
"value": "ON"
|
||||
},
|
||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install/${presetName}/sparkd",
|
||||
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "sparkd-release-clang",
|
||||
"inherits": "sparkd-release",
|
||||
@@ -307,13 +285,6 @@
|
||||
"inherits": "sparkd"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "activ-tester",
|
||||
"configurePreset": "activ-tester",
|
||||
"displayName": "Activ Tester Debug (gcc)",
|
||||
"inherits": "sparkd"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "sparkd-release-clang",
|
||||
"configurePreset": "sparkd-release-clang",
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
# Symbols {
|
||||
# value {
|
||||
# venues {
|
||||
# name: "VENUE_BINANCE_US"
|
||||
# spot: "BTC"
|
||||
# spot: "ETH"
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# MarketDataConfig {
|
||||
# value {
|
||||
# binance_us {
|
||||
# num_depth_levels: 5
|
||||
# security_criteria {
|
||||
# clauses {
|
||||
# security_types: SECURITY_TYPE_SPOT
|
||||
# symbols: "BTC"
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
|
||||
|
||||
MarketDataConfig {
|
||||
value {
|
||||
# binance {
|
||||
# num_depth_levels: 5
|
||||
# security_criteria {
|
||||
# clauses {
|
||||
# security_types: SECURITY_TYPE_SPOT
|
||||
# symbols: "BTC"
|
||||
# symbols: "ETH"
|
||||
# }
|
||||
# }
|
||||
# url_rest_base: "https://testnet.binance.vision"
|
||||
# url_ws_base: "wss://testnet.binance.vision"
|
||||
# }
|
||||
# binance_usds_futures {
|
||||
# num_depth_levels: 5
|
||||
# security_criteria {
|
||||
# clauses {
|
||||
# security_types: SECURITY_TYPE_FUTURE
|
||||
# symbols: "BTC"
|
||||
# symbols: "ETH"
|
||||
# }
|
||||
# }
|
||||
# security_desc_formattings {
|
||||
# expression: "^(.+)(USD)(?=_)(.*)"
|
||||
# format: "$1USDT$3"
|
||||
# }
|
||||
# url_rest_base: "https://testnet.binancefuture.com"
|
||||
# url_ws_base: "wss://fstream.binance.com"
|
||||
# }
|
||||
binance_us {
|
||||
num_depth_levels: 5
|
||||
security_criteria {
|
||||
clauses {
|
||||
security_types: SECURITY_TYPE_SPOT
|
||||
symbols: "BTC"
|
||||
symbols: "ETH"
|
||||
}
|
||||
}
|
||||
url_rest_base: "https://api.binance.us"
|
||||
url_ws_base: "wss://stream.binance.us:9443"
|
||||
}
|
||||
# deribit {
|
||||
# num_depth_levels: 10
|
||||
# security_criteria {
|
||||
# clauses {
|
||||
# security_types: SECURITY_TYPE_SPOT
|
||||
# security_types: SECURITY_TYPE_FUTURE
|
||||
# security_types: SECURITY_TYPE_OPTION
|
||||
# security_types: SECURITY_TYPE_SPREAD
|
||||
# symbols: "BTC"
|
||||
# symbols: "ETH"
|
||||
# }
|
||||
# }
|
||||
# currencies: "BTC"
|
||||
# currencies: "USDC"
|
||||
# url_rest_base: "https://test.deribit.com"
|
||||
# url_ws_base: "wss://test.deribit.com"
|
||||
# }
|
||||
# okx {
|
||||
# num_depth_levels: 5
|
||||
# security_criteria {
|
||||
# clauses {
|
||||
# security_types: SECURITY_TYPE_SPOT
|
||||
# security_types: SECURITY_TYPE_FUTURE
|
||||
# security_types: SECURITY_TYPE_OPTION
|
||||
# symbols: "BTC"
|
||||
# symbols: "ETH"
|
||||
# }
|
||||
# }
|
||||
# url_rest_base: "https://aws.okx.com"
|
||||
# url_ws_base: "wss://wsaws.okx.com:8443"
|
||||
# is_protocol_ping_pong_enabled: true
|
||||
# protocol_ping_interval_ms: 60000
|
||||
# protocol_pong_timeout_ms: 30000
|
||||
# is_application_ping_pong_enabled: true
|
||||
# application_ping_interval_ms: 20000
|
||||
# application_pong_timeout_ms: 10000
|
||||
# }
|
||||
# recording {
|
||||
# num_of_seconds_to_run: 300
|
||||
# venue: VENUE_DERIBIT
|
||||
# }
|
||||
# replay {
|
||||
# venue: VENUE_DERIBIT
|
||||
# }
|
||||
}
|
||||
}
|
||||
|
||||
Symbols {
|
||||
value {
|
||||
venues {
|
||||
name: "VENUE_BINANCE_US"
|
||||
spot: "BTC"
|
||||
spot: "ETH"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.12)
|
||||
project(spark)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
if(WIN32)
|
||||
set(platform "windows")
|
||||
else()
|
||||
set(platform "linux")
|
||||
endif()
|
||||
|
||||
set(
|
||||
EXTERNALS_ROOT
|
||||
"${CMAKE_SOURCE_DIR}/externals/${platform}"
|
||||
CACHE
|
||||
STRING
|
||||
"External dependencies root folder (should contain folders: lib, bin, include")
|
||||
set(SPARK_BIN "${EXTERNALS_ROOT}/bin")
|
||||
set(SPARK_INCLUDE "${EXTERNALS_ROOT}/include")
|
||||
set(SPARK_LIB "${EXTERNALS_ROOT}/lib/$<CONFIG>")
|
||||
set(BUILD_API_SERVER OFF CACHE BOOL "Build the api server")
|
||||
set(BUILD_DOCS OFF CACHE BOOL "Build documentation")
|
||||
set(BUILD_SDK OFF CACHE BOOL "Build the sdk")
|
||||
if (WIN32)
|
||||
set(BUILD_SPARKD OFF CACHE BOOL "Build sparkd")
|
||||
set(BUILD_ADMIN_WEB_APP OFF CACHE BOOL "Build the Admin WebApp")
|
||||
else()
|
||||
set(BUILD_SPARKD ON CACHE BOOL "Build sparkd")
|
||||
set(BUILD_ADMIN_WEB_APP ON CACHE BOOL "Build the Admin WebApp")
|
||||
endif()
|
||||
|
||||
include_directories(BEFORE SYSTEM ${SPARK_INCLUDE} ${EXTERNALS_ROOT}/include/zlib)
|
||||
link_directories(${SPARK_LIB})
|
||||
|
||||
if(BUILD_API_SERVER)
|
||||
add_compile_definitions(SPARK_ENABLE_API_SERVER)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
add_compile_definitions(SHARED_MEMORY_PREFIX="spark")
|
||||
add_compile_definitions(APP_DATA_DIR="\$\{APPDATA\}/bts/")
|
||||
# subdirs
|
||||
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_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/install")
|
||||
set(SPARK_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/$<CONFIG>")
|
||||
|
||||
if(BUILD_API_SERVER)
|
||||
add_compile_definitions(APP_DATA_DIR="\$\{HOME\}/spark_api/")
|
||||
add_compile_definitions(SHARED_MEMORY_PREFIX="spark_api")
|
||||
elseif(BUILD_SPARKD)
|
||||
add_compile_definitions(SHARED_MEMORY_PREFIX="sparkd")
|
||||
add_compile_definitions(APP_DATA_DIR="\$\{HOME\}/sparkdata/")
|
||||
|
||||
# Note: All macros that begin w/ 'CCAPI' are ccapi's macros (defined and/or
|
||||
# read by their code). We override some below.
|
||||
add_compile_definitions(SPARK_ENABLE_CCAPI)
|
||||
|
||||
# This allows us to override hardcoded exchange-urls with ones read from
|
||||
# config (i.e., resolved at runtime rather than compile-time)
|
||||
add_compile_definitions(CCAPI_EXPOSE_INTERNAL)
|
||||
|
||||
# Enable ccapi services
|
||||
add_compile_definitions(CCAPI_ENABLE_SERVICE_MARKET_DATA)
|
||||
add_compile_definitions(CCAPI_ENABLE_SERVICE_EXECUTION_MANAGEMENT)
|
||||
# Enable ccapi exchanges
|
||||
add_compile_definitions(CCAPI_ENABLE_EXCHANGE_BINANCE)
|
||||
add_compile_definitions(CCAPI_ENABLE_EXCHANGE_BINANCE_COIN_FUTURES)
|
||||
add_compile_definitions(CCAPI_ENABLE_EXCHANGE_BINANCE_US)
|
||||
add_compile_definitions(CCAPI_ENABLE_EXCHANGE_BINANCE_USDS_FUTURES)
|
||||
add_compile_definitions(CCAPI_ENABLE_EXCHANGE_COINBASE)
|
||||
add_compile_definitions(CCAPI_ENABLE_EXCHANGE_DERIBIT)
|
||||
add_compile_definitions(CCAPI_ENABLE_EXCHANGE_OKX)
|
||||
|
||||
set(CCAPI_LOG_LEVEL
|
||||
"Warn"
|
||||
CACHE STRING "<Fatal|Error|Warn|Info|Debug|Trace> Log level for ccapi")
|
||||
string(TOUPPER "${CCAPI_LOG_LEVEL}" _ccapi_log_level_upper)
|
||||
add_compile_definitions("CCAPI_ENABLE_LOG_${_ccapi_log_level_upper}")
|
||||
|
||||
# ** Binance **
|
||||
add_compile_definitions(CCAPI_BINANCE_URL_REST_BASE="https://api.binance.com")
|
||||
add_compile_definitions(BINANCE_INSTRUMENTS_PATH="/api/v3/exchangeInfo")
|
||||
add_compile_definitions(BINANCE_PRICE_STATS_PATH="/api/v3/ticker/24hr")
|
||||
|
||||
# ** Binance US **
|
||||
add_compile_definitions(BINANCE_US_INSTRUMENTS_PATH="/api/v3/exchangeInfo")
|
||||
add_compile_definitions(BINANCE_US_PRICE_STATS_PATH="/api/v3/ticker/24hr")
|
||||
|
||||
# ** Binance USDS Futures **
|
||||
add_compile_definitions(CCAPI_BINANCE_USDS_FUTURES_URL_REST_BASE="https://dapi.binance.com")
|
||||
add_compile_definitions(BINANCE_USDS_FUTURES_INSTRUMENTS_PATH="/dapi/v1/exchangeInfo")
|
||||
add_compile_definitions(BINANCE_USDS_FUTURES_PRICE_STATS_PATH="/dapi/v1/ticker/24hr")
|
||||
|
||||
# ** Deribit **
|
||||
add_compile_definitions(DERIBIT_INSTRUMENT_PATH="/api/v2/public/get_instrument")
|
||||
add_compile_definitions(DERIBIT_INSTRUMENTS_PATH="/api/v2/public/get_instruments")
|
||||
add_compile_definitions(DERIBIT_COMBOS_PATH="/api/v2/public/get_combos")
|
||||
add_compile_definitions(DERIBIT_TICKER_PATH="/api/v2/public/ticker")
|
||||
add_compile_definitions(DERIBIT_CURRENCIES_METHOD="public/get_currencies")
|
||||
add_compile_definitions(DERIBIT_INSTRUMENT_METHOD="public/get_instrument")
|
||||
add_compile_definitions(DERIBIT_INSTRUMENTS_METHOD="public/get_instruments")
|
||||
add_compile_definitions(DERIBIT_COMBO_DETAILS_METHOD="public/get_combo_details")
|
||||
add_compile_definitions(DERIBIT_COMBOS_METHOD="public/get_combos")
|
||||
|
||||
# ** Okx **
|
||||
add_compile_definitions(OKX_INSTRUMENTS_PATH="/api/v5/public/instruments")
|
||||
add_compile_definitions(OKX_UNDERLYING_PATH="/api/v5/public/underlying")
|
||||
endif()
|
||||
|
||||
# subdirs
|
||||
if (BUILD_SPARKD)
|
||||
add_subdirectory(src/server/sparkd)
|
||||
add_subdirectory(src/data_files)
|
||||
endif()
|
||||
add_subdirectory(src/lib)
|
||||
if (BUILD_ADMIN_WEB_APP)
|
||||
add_subdirectory(src/Spark.Admin.WebApp)
|
||||
endif()
|
||||
if(BUILD_DOCS)
|
||||
add_subdirectory(doc/user)
|
||||
endif()
|
||||
if(BUILD_SDK)
|
||||
add_subdirectory(src/sdk)
|
||||
endif()
|
||||
|
||||
if(BUILD_SPARKD)
|
||||
function(copy_shared_lib lib_file_name)
|
||||
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/src/server/sparkd/${lib_file_name}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${EXTERNALS_ROOT}/lib/Debug/${lib_file_name}
|
||||
${CMAKE_BINARY_DIR}/src/server/sparkd/${lib_file_name}
|
||||
DEPENDS ${EXTERNALS_ROOT}/lib/Debug/${lib_file_name})
|
||||
add_custom_target(copy_shared_lib_${lib_file_name} ALL DEPENDS ${CMAKE_BINARY_DIR}/src/server/sparkd/${lib_file_name})
|
||||
endfunction()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/src/server/sparkd/sparkd ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/data_files ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/src/lib/libspark.so ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND
|
||||
ln ARGS -sf
|
||||
${EXTERNALS_ROOT}/lib/Release/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.16.0.0
|
||||
${CMAKE_BINARY_DIR}/server
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E copy
|
||||
${EXTERNALS_ROOT}/lib/Debug/libActivAgentApplication_x86-64_ubuntu22-04_gcc114_mdid.so
|
||||
${CMAKE_BINARY_DIR}/server
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E copy
|
||||
${EXTERNALS_ROOT}/lib/Debug/libActivBase_x86-64_ubuntu22-04_gcc114_mdid.so
|
||||
${CMAKE_BINARY_DIR}/server
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E copy
|
||||
${EXTERNALS_ROOT}/lib/Debug/libActivFramework_x86-64_ubuntu22-04_gcc114_mdid.so
|
||||
${CMAKE_BINARY_DIR}/server
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E copy
|
||||
${EXTERNALS_ROOT}/lib/Debug/libActivOneApi_x86-64_ubuntu22-04_gcc114_mdid.so
|
||||
${CMAKE_BINARY_DIR}/server
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E copy
|
||||
${EXTERNALS_ROOT}/lib/Debug/libActivDatabase_x86-64_ubuntu22-04_gcc114_mdid.so
|
||||
${CMAKE_BINARY_DIR}/server
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E copy
|
||||
${EXTERNALS_ROOT}/lib/Debug/libActivServices_x86-64_ubuntu22-04_gcc114_mdid.so
|
||||
${CMAKE_BINARY_DIR}/server
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E copy
|
||||
${EXTERNALS_ROOT}/lib/Debug/libBoostAtomic_x86-64_ubuntu22-04_gcc114_mdid.so
|
||||
${CMAKE_BINARY_DIR}/server
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E copy
|
||||
${EXTERNALS_ROOT}/lib/Debug/libBoostChrono_x86-64_ubuntu22-04_gcc114_mdid.so
|
||||
${CMAKE_BINARY_DIR}/server
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E copy
|
||||
${EXTERNALS_ROOT}/lib/Debug/libBoostFilesystem_x86-64_ubuntu22-04_gcc114_mdid.so
|
||||
${CMAKE_BINARY_DIR}/server
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E copy
|
||||
${EXTERNALS_ROOT}/lib/Debug/libBoostProgramOptions_x86-64_ubuntu22-04_gcc114_mdid.so
|
||||
${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libACE-5.7.5.so ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND mkdir ARGS -p ${CMAKE_BINARY_DIR}/server/plugins
|
||||
COMMENT "Creating server runtime layout"
|
||||
DEPENDS sparkd data_files_dir)
|
||||
|
||||
add_custom_target(server_layout ALL DEPENDS ${CMAKE_BINARY_DIR}/server)
|
||||
|
||||
install(
|
||||
DIRECTORY "${CMAKE_BINARY_DIR}/server/plugins"
|
||||
DESTINATION "${SPARK_INSTALL_PREFIX}/bin")
|
||||
install(
|
||||
FILES
|
||||
"${SPARK_LIB}/libACE-5.7.5.so"
|
||||
"${SPARK_LIB}/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.16.0.0"
|
||||
"${SPARK_LIB}/libActivFramework_x86-64_ubuntu22-04_gcc114_mdid.so"
|
||||
"${SPARK_LIB}/libActivOneApi_x86-64_ubuntu22-04_gcc114_mdid.so"
|
||||
"${SPARK_LIB}/libActivServices_x86-64_ubuntu22-04_gcc114_mdid.so"
|
||||
"${SPARK_LIB}/libBoostAtomic_x86-64_ubuntu22-04_gcc114_mdid.so"
|
||||
"${SPARK_LIB}/libBoostFilesystem_x86-64_ubuntu22-04_gcc114_mdid.so"
|
||||
DESTINATION "${SPARK_INSTALL_PREFIX}/bin")
|
||||
endif()
|
||||
endif()
|
||||
@@ -1,33 +0,0 @@
|
||||
diff.astextplain.textconv=astextplain
|
||||
filter.lfs.clean=git-lfs clean -- %f
|
||||
filter.lfs.smudge=git-lfs smudge -- %f
|
||||
filter.lfs.process=git-lfs filter-process
|
||||
filter.lfs.required=true
|
||||
http.sslbackend=openssl
|
||||
http.sslcainfo=C:/Program Files/Git/mingw64/etc/ssl/certs/ca-bundle.crt
|
||||
core.autocrlf=true
|
||||
core.fscache=true
|
||||
core.symlinks=false
|
||||
pull.rebase=false
|
||||
credential.helper=manager
|
||||
credential.https://dev.azure.com.usehttppath=true
|
||||
init.defaultbranch=master
|
||||
status.short=true
|
||||
core.repositoryformatversion=0
|
||||
core.filemode=false
|
||||
core.bare=false
|
||||
core.logallrefupdates=true
|
||||
core.symlinks=false
|
||||
core.ignorecase=true
|
||||
submodule.active=.
|
||||
remote.origin.url=git@bitbucket.org:bluetradesys/spark.git
|
||||
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
|
||||
branch.spark_crypto.dev.remote=origin
|
||||
branch.spark_crypto.dev.merge=refs/heads/spark_crypto.dev
|
||||
submodule.docker/linux.url=git@bitbucket.org:bluetradesys/linux_docker.git
|
||||
submodule.docker/windows.url=git@bitbucket.org:bluetradesys/windows_docker.git
|
||||
submodule.externals.url=git@bitbucket.org:bluetradesys/externals.git
|
||||
branch.development.remote=origin
|
||||
branch.development.merge=refs/heads/development
|
||||
user.name=John Colvin
|
||||
user.email=john.colvin@bluetradesys.com
|
||||
@@ -1,12 +0,0 @@
|
||||
core.repositoryformatversion=0
|
||||
core.filemode=true
|
||||
core.bare=false
|
||||
core.logallrefupdates=true
|
||||
remote.origin.url=git@bitbucket.org:bluetradesys/spark.git
|
||||
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
|
||||
branch.spark_crypto.dev.remote=origin
|
||||
branch.spark_crypto.dev.merge=refs/heads/spark_crypto.dev
|
||||
branch.development.remote=origin
|
||||
branch.development.merge=refs/heads/development
|
||||
submodule.externals.active=true
|
||||
submodule.externals.url=git@bitbucket.org:bluetradesys/externals.git
|
||||
@@ -1,8 +0,0 @@
|
||||
function(init_executable source_dir)
|
||||
file(
|
||||
GLOB_RECURSE ${PROJECT_NAME}_Sources
|
||||
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${source_dir}/*.cpp)
|
||||
add_executable(${PROJECT_NAME} ${${PROJECT_NAME}_Sources})
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
endfunction(init_executable)
|
||||
@@ -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: 7086753a10...eb228e2b25.
@@ -24,6 +24,3 @@
|
||||
those re-entered explicitly
|
||||
- With this release we add per user location and smp id tags. At least
|
||||
the location will have to be set through the admin UI for every user
|
||||
|
||||
libBoostFilesystem_x86-64_ubuntu22-04_gcc123_mdid.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, BuildID[sha1]=75427020663511112b3aadeef5023e4f368aafd4, stripped
|
||||
/usr/lib/x86_64-linux-gnu/libboost_filesystem.so.1.74.0: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=f0cb6f85ac07ed93816bae06c584d4a1fabb0e70, stripped
|
||||
@@ -1,13 +1,13 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Collections;
|
||||
using BCG.Utilities;
|
||||
using BCG.Utilities.General;
|
||||
namespace BCG
|
||||
{
|
||||
// using System;
|
||||
// using System.Data;
|
||||
// using System.Collections;
|
||||
// using BCG.Utilities;
|
||||
// using BCG.Utilities.General;
|
||||
// namespace BCG
|
||||
// {
|
||||
|
||||
public class AddColumnsStream : MapStream
|
||||
{
|
||||
public AddColumnsStream(ViewStream vs, string columnDef) : base (vs, new ColumnAdder (columnDef)) {}
|
||||
}
|
||||
}
|
||||
// public class AddColumnsStream : MapStream
|
||||
// {
|
||||
// public AddColumnsStream(ViewStream vs, string columnDef) : base (vs, new ColumnAdder (columnDef)) {}
|
||||
// }
|
||||
// }
|
||||
@@ -104,13 +104,13 @@ namespace BCG {
|
||||
if (!p.replayed)
|
||||
return p;
|
||||
}
|
||||
else
|
||||
{
|
||||
double secondsElapsed = new BinaryReader(myFileReader).ReadDouble();
|
||||
myNextPacketTime = myPlaybackStart + new TimeSpan(0, 0, 0, 0, (int)(1000 * secondsElapsed / myPlaybackSpeed));
|
||||
if (onProgress != null)
|
||||
onProgress(myFileReader.Position, myFileReader.Length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private PlaybackPacket _get_previous_packet(PlaybackPacket p) {
|
||||
if (p.idx == 0)
|
||||
return null;
|
||||
return _packets[p.idx - 1];
|
||||
}
|
||||
|
||||
#region IWorker Members
|
||||
|
||||
@@ -102,6 +102,14 @@ namespace BCG
|
||||
System.Threading.Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
// Task.Run(async () =>
|
||||
// {
|
||||
// while (!_conn.isDisposed)
|
||||
// {
|
||||
// await _conn.send_async(buff);
|
||||
// await Task.Delay(100);
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
public void onData(RemoteMessage msg)
|
||||
|
||||
Generated
+13016
-1
File diff suppressed because it is too large.
Load diff
@@ -1,51 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.23)
|
||||
project(activ-tester)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/InitExecutable.cmake)
|
||||
init_executable(activ-tester)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC
|
||||
protobuf
|
||||
double-conversion
|
||||
boost_system
|
||||
boost_thread
|
||||
boost_filesystem
|
||||
boost_regex
|
||||
boost_chrono
|
||||
boost_date_time
|
||||
boost_atomic
|
||||
SQLiteCpp
|
||||
sqlite3
|
||||
curl
|
||||
uuid
|
||||
ssl
|
||||
crypto
|
||||
debug fmtd
|
||||
optimized fmt
|
||||
tbb
|
||||
cryptopp
|
||||
pugixml
|
||||
libspark
|
||||
lttng-ust
|
||||
Pcap++
|
||||
Packet++
|
||||
Common++
|
||||
ssh2
|
||||
BoostAtomic_x86-64_ubuntu22-04_gcc123_mdid
|
||||
BoostChrono_x86-64_ubuntu22-04_gcc123_mdid
|
||||
BoostFilesystem_x86-64_ubuntu22-04_gcc123_mdid
|
||||
BoostProgramOptions_x86-64_ubuntu22-04_gcc123_mdid
|
||||
ActivAgentApplication_x86-64_ubuntu22-04_gcc123_mdid
|
||||
ActivBase_x86-64_ubuntu22-04_gcc123_mdid
|
||||
ActivDatabase_x86-64_ubuntu22-04_gcc123_mdid
|
||||
ActivFramework_x86-64_ubuntu22-04_gcc123_mdid
|
||||
ActivOneApi_x86-64_ubuntu22-04_gcc123_mdid
|
||||
ActivServices_x86-64_ubuntu22-04_gcc123_mdid
|
||||
-lrt
|
||||
-ldl
|
||||
-lpcap
|
||||
-static-libgcc
|
||||
-static-libstdc++
|
||||
-pthread)
|
||||
|
||||
# cli11
|
||||
find_package(CLI11 CONFIG REQUIRED)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC CLI11::CLI11)
|
||||
@@ -1,75 +0,0 @@
|
||||
// #include "lib/md/cme/CMEActivSession.h"
|
||||
// #include "lib/testing/catch_test_runner.h"
|
||||
// #include "lib/utils/init_spark_lib.h"
|
||||
// #include "server/sparkd/cmds/AdminReport.h"
|
||||
// #include "server/sparkd/cmds/ConfigCmd.h"
|
||||
// #include "server/sparkd/cmds/KeyCmd.h"
|
||||
// #include "server/sparkd/cmds/Md.h"
|
||||
// #include "server/sparkd/cmds/QuickTest.h"
|
||||
// #include "server/sparkd/cmds/Run.h"
|
||||
// #include "server/sparkd/cmds/Web.h"
|
||||
// #include "server/sparkd/cmds/crypto/CryptoCmd.h"
|
||||
|
||||
#include <CLI/CLI.hpp>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
// using namespace bts;
|
||||
|
||||
void print_args(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
std::cout << "No args" << std::endl;
|
||||
return;
|
||||
}
|
||||
for (int i = 1; i < argc; i++) {
|
||||
std::cout << "arg " << i << ": " << argv[i] << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
print_args(argc, argv);
|
||||
CLI::App app("Futures trading system", "sparkd");
|
||||
app.require_subcommand();
|
||||
// app.add_subcommand("")
|
||||
return 0;
|
||||
}
|
||||
|
||||
// int main(int argc, char **argv) {
|
||||
// utils::SparkLibInit init;
|
||||
|
||||
// // The catch testing framework has its own command line parser so
|
||||
// // if we are testing we just forward the entire command to catch
|
||||
// if (argc > 1 && std::string(argv[1]) == "test")
|
||||
// return run_tests(argc, argv);
|
||||
// else {
|
||||
// CLI::App app("Futures trading system", "sparkd");
|
||||
// app.require_subcommand();
|
||||
// app.add_subcommand(CLI::App_p(new cmds::AdminReport()));
|
||||
// app.add_subcommand(CLI::App_p(new cmds::CryptoCmd()));
|
||||
// app.add_subcommand(CLI::App_p(new cmds::ConfigCmd()));
|
||||
// app.add_subcommand(CLI::App_p(new cmds::Md()));
|
||||
// app.add_subcommand(CLI::App_p(new cmds::QuickTest()));
|
||||
// app.add_subcommand(CLI::App_p(new cmds::Run()));
|
||||
// app.add_subcommand(CLI::App_p(new cmds::KeyCmd()));
|
||||
// app.add_subcommand(CLI::App_p(new cmds::Web()));
|
||||
|
||||
// // Activ md example
|
||||
// struct {
|
||||
// std::string host{"aop-ny4-replay-1w.activfinancial.com"};
|
||||
// std::string user{"bltdsy-replay"};
|
||||
// std::string password{"redhawk0281"};
|
||||
// CLI::App *cmd;
|
||||
// } activ;
|
||||
// activ.cmd = app.add_subcommand("activ-md", "Run Activ md demo");
|
||||
// activ.cmd->add_option("--host", activ.host, "Activ host (server)");
|
||||
// activ.cmd->add_option("--user", activ.user, "Activ login user");
|
||||
// activ.cmd->add_option("--pass", activ.password, "Activ login password");
|
||||
// activ.cmd->callback([&activ]() {
|
||||
// md::cme::CMEActivSession::run(activ.host, activ.user, activ.password);
|
||||
// });
|
||||
|
||||
// app.add_subcommand(
|
||||
// "test", "Run unit test(s)"); // so this shows up in help
|
||||
// CLI11_PARSE(app, argc, argv);
|
||||
// }
|
||||
// }
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1197,6 +1197,7 @@ namespace System.Collections.Generic {
|
||||
/// A tuple whose items each represent the sum of their repsective items in
|
||||
/// <paramref name="source"/>
|
||||
/// </returns>
|
||||
[RequiresPreviewFeatures]
|
||||
public static (T1, T2) Sum<T1, T2>(this IEnumerable<(T1, T2)> source)
|
||||
where T1 : INumber<T1>
|
||||
where T2 : INumber<T2> =>
|
||||
@@ -1209,6 +1210,7 @@ namespace System.Collections.Generic {
|
||||
/// A tuple whose items each represent the sum of their repsective items in
|
||||
/// <paramref name="source"/>
|
||||
/// </returns>
|
||||
[RequiresPreviewFeatures]
|
||||
public static (T1, T2) Sum<T1, T2>(this IEnumerable<(T1, T2)> source, T1 seed1, T2 seed2)
|
||||
where T1 : IAdditionOperators<T1, T1, T1>
|
||||
where T2 : IAdditionOperators<T2, T2, T2> =>
|
||||
@@ -1222,6 +1224,7 @@ namespace System.Collections.Generic {
|
||||
/// A tuple whose items each represent the sum of their repsective items in
|
||||
/// <paramref name="source"/>
|
||||
/// </returns>
|
||||
[RequiresPreviewFeatures]
|
||||
public static (T1, T2, T3) Sum<T1, T2, T3>(this IEnumerable<(T1, T2, T3)> source)
|
||||
where T1 : INumber<T1>
|
||||
where T2 : INumber<T2>
|
||||
@@ -1235,6 +1238,7 @@ namespace System.Collections.Generic {
|
||||
/// A tuple whose items each represent the sum of their repsective items in
|
||||
/// <paramref name="source"/>
|
||||
/// </returns>
|
||||
[RequiresPreviewFeatures]
|
||||
public static (T1, T2, T3) Sum<T1, T2, T3>(
|
||||
this IEnumerable<(T1, T2, T3)> source, T1 seed1, T2 seed2, T3 seed3)
|
||||
where T1 : IAdditionOperators<T1, T1, T1>
|
||||
@@ -1251,6 +1255,7 @@ namespace System.Collections.Generic {
|
||||
/// A tuple whose items each represent the sum of their repsective items in
|
||||
/// <paramref name="source"/>
|
||||
/// </returns>
|
||||
[RequiresPreviewFeatures]
|
||||
public static (T1, T2, T3, T4) Sum<T1, T2, T3, T4>(this IEnumerable<(T1, T2, T3, T4)> source)
|
||||
where T1 : INumber<T1>
|
||||
where T2 : INumber<T2>
|
||||
@@ -1265,6 +1270,7 @@ namespace System.Collections.Generic {
|
||||
/// A tuple whose items each represent the sum of their repsective items in
|
||||
/// <paramref name="source"/>
|
||||
/// </returns>
|
||||
[RequiresPreviewFeatures]
|
||||
public static (T1, T2, T3, T4) Sum<T1, T2, T3, T4>(
|
||||
this IEnumerable<(T1, T2, T3, T4)> source, T1 seed1, T2 seed2, T3 seed3, T4 seed4)
|
||||
where T1 : IAdditionOperators<T1, T1, T1>
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ target_include_directories(
|
||||
|
||||
get_link_libraries(_link_libs)
|
||||
print_list(_link_libs VERBOSE)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC ${_link_libs})
|
||||
target_link_libraries(${PROJECT_NAME} ${_link_libs})
|
||||
|
||||
# Copy binaries to Dotnet build dirs (for csprojs that depend on libspark)
|
||||
if (DEFINED DOTNET_BINARY_DIR)
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "lib/api/domain/SecurityCriteria.proto";
|
||||
|
||||
package bts.api.md;
|
||||
|
||||
option csharp_namespace = "bts.api.md";
|
||||
|
||||
|
||||
message GlbxMarketDataConfig {
|
||||
uint32 unique_check_value = 1;
|
||||
//uint32 num_depth_levels = 1;
|
||||
//uint32 depth_throttle_ms = 2;
|
||||
bts.api.domain.SecurityCriteria security_criteria = 3;
|
||||
// Market data isn't subscribed for securities listed here (even if they are
|
||||
// included by 'security_criteria'). There are some securities that don't
|
||||
// fit into our system just yet, such as 'BTCBIDR', which trades in prices
|
||||
// larger than our cpp 'domain::Price' class supports.
|
||||
//repeated string excluded_securities = 4;
|
||||
//string url_rest_base = 5;
|
||||
//string url_ws_base = 6;
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import "lib/api/md/MarketDataReplayConfig.proto";
|
||||
import "lib/api/md/DeribitMarketDataConfig.proto";
|
||||
import "lib/api/md/EmumbaDeribitMarketDataConfig.proto";
|
||||
import "lib/api/md/OkxMarketDataConfig.proto";
|
||||
import "lib/api/md/GlbxMarketDataConfig.proto";
|
||||
|
||||
|
||||
option csharp_namespace = "bts.api.md";
|
||||
|
||||
@@ -24,7 +24,6 @@ message MarketDataConfig {
|
||||
repeated DeribitMarketDataConfig deribit = 5;
|
||||
repeated CoinbaseMarketDataConfig coinbase =6;
|
||||
repeated OkxMarketDataConfig okx = 7;
|
||||
repeated GlbxMarketDataConfig glbx = 8;
|
||||
// Add an entry here for all the exchanges that need their market data to be recorded
|
||||
|
||||
repeated EmumbaDeribitMarketDataConfig emumba_deribit = 100;
|
||||
|
||||
@@ -19,8 +19,6 @@ std::set<bts::api::Venue> MarketDataConfigUtils::get_live_exchanges(
|
||||
rv.insert(bts::api::VENUE_BINANCE_US);
|
||||
if (cfg.deribit_size() > 0)
|
||||
rv.insert(bts::api::VENUE_DERIBIT);
|
||||
if (cfg.glbx_size()>0)
|
||||
rv.insert(bts::api::VENUE_GLBX);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,5 +11,4 @@ option cc_enable_arenas = true;
|
||||
message MarketDataRecordingConfig {
|
||||
uint32 num_of_seconds_to_run = 10;
|
||||
bts.api.Venue venue = 20;
|
||||
uint32 n_book_levels = 30;
|
||||
}
|
||||
@@ -25,16 +25,6 @@ function(get_link_libraries dest)
|
||||
Packet++
|
||||
Common++
|
||||
ssh2
|
||||
BoostAtomic_x86-64_ubuntu22-04_gcc123_mdid
|
||||
BoostChrono_x86-64_ubuntu22-04_gcc123_mdid
|
||||
BoostFilesystem_x86-64_ubuntu22-04_gcc123_mdid
|
||||
BoostProgramOptions_x86-64_ubuntu22-04_gcc123_mdid
|
||||
ActivAgentApplication_x86-64_ubuntu22-04_gcc123_mdid
|
||||
ActivBase_x86-64_ubuntu22-04_gcc123_mdid
|
||||
ActivDatabase_x86-64_ubuntu22-04_gcc123_mdid
|
||||
ActivFramework_x86-64_ubuntu22-04_gcc123_mdid
|
||||
ActivOneApi_x86-64_ubuntu22-04_gcc123_mdid
|
||||
ActivServices_x86-64_ubuntu22-04_gcc123_mdid
|
||||
-lrt
|
||||
-ldl
|
||||
-lpcap
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
function(get_link_libraries dest)
|
||||
find_package(fmt CONFIG REQUIRED)
|
||||
set(${dest}
|
||||
crypt32
|
||||
fmt::fmt
|
||||
debug fmtd
|
||||
optimized fmt
|
||||
libcrypto
|
||||
libcurl
|
||||
libboost_system
|
||||
|
||||
@@ -9,7 +9,7 @@ HistoricMarketDataConfig {
|
||||
|
||||
PlatformMap {
|
||||
value {
|
||||
glbx_platform: "PLATFORM_MDP3ONIXS"
|
||||
glbx_platform: "PLATFORM_SIM"
|
||||
ice_platform: "PLATFORM_ICEONIXS"
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,6 @@ PublisherOptions {
|
||||
hot_growth: 1024
|
||||
cold_initial: 8192
|
||||
cold_growth: 2048
|
||||
channel_prefix: ""
|
||||
disable_heartbeat: false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,18 +97,11 @@ CMEOptions {
|
||||
feed_engine {
|
||||
name: "mdp3feed"
|
||||
spin_time: 0
|
||||
channels_per_thread: 3
|
||||
data_wait: 10
|
||||
upp_socket_buffer_size: 268435456
|
||||
}
|
||||
channel {
|
||||
id: "*"
|
||||
feed_engine: "mdp3feed"
|
||||
use_tcp_replay: false
|
||||
recovery_queue_size: 640000
|
||||
lost_packet_wait_timeout_ms: 10
|
||||
use_natural_refresh: false
|
||||
join_snapshot_on_gap: false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -231,10 +222,3 @@ MarketDataConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
OnixsOptions {
|
||||
value{
|
||||
onixs_license: "{default}"
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,7 @@ OrdersSoftCleanup {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
PlacementInQueue {
|
||||
vlaue{
|
||||
enabled: true;
|
||||
}
|
||||
}
|
||||
|
||||
RiskLimit {
|
||||
value{
|
||||
risk_limits{
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include <any>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <typeindex>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
// this file has all the classes that represent streaming tabular data
|
||||
// schema ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace bts::streams {
|
||||
|
||||
using RowID = int;
|
||||
|
||||
// Represents a column of a schema (tuple of type, name and col number). For the type we use
|
||||
// std::type_index
|
||||
|
||||
class ColumnInfo {
|
||||
public:
|
||||
ColumnInfo(
|
||||
const std::string &column_name,
|
||||
const std::type_index &column_type,
|
||||
size_t col_index)
|
||||
: _name(column_name), _type(column_type), _index(col_index) {}
|
||||
const std::string &column_name() const { return _name; }
|
||||
size_t col_index() const { return _index; }
|
||||
const std::type_index &column_type() const { return _type; }
|
||||
|
||||
private:
|
||||
const std::string _name;
|
||||
const std::type_index _type;
|
||||
const size_t _index;
|
||||
};
|
||||
class StreamSchema {
|
||||
public:
|
||||
bool add_column(
|
||||
const std::string &column_name, const std::type_index &column_type) {
|
||||
auto it = _col_map.find(column_name);
|
||||
if (it != _col_map.end()) {
|
||||
return false;
|
||||
} else {
|
||||
_col_map.insert(
|
||||
{column_name, {column_name, column_type, _col_map.size()}});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
ColumnInfo const *get_column(const std::string &col_name) const {
|
||||
auto it = _col_map.find(col_name);
|
||||
if (it == _col_map.end())
|
||||
return nullptr;
|
||||
return &it->second;
|
||||
}
|
||||
ColumnInfo *get_column(const std::string &col_name) {
|
||||
auto it = _col_map.find(col_name);
|
||||
if (it == _col_map.end())
|
||||
return nullptr;
|
||||
return &it->second;
|
||||
}
|
||||
size_t size() const { return _col_map.size(); }
|
||||
|
||||
private:
|
||||
std::map<std::string, ColumnInfo> _col_map = {};
|
||||
};
|
||||
// row ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
class StreamRow {
|
||||
public:
|
||||
StreamRow(const StreamSchema &schema) : schema(schema) {
|
||||
data.reserve(schema.size());
|
||||
for (size_t i = 0; i < schema.size(); i++) {
|
||||
std::any any;
|
||||
data.push_back(any);
|
||||
}
|
||||
}
|
||||
RowID id() const { return get_value<int>("id"); }
|
||||
template <typename T>
|
||||
bool set_value(const std::string &columnName, const T &value) {
|
||||
ColumnInfo const *col_info = schema.get_column(columnName);
|
||||
if (col_info == nullptr) {
|
||||
throw std::invalid_argument(
|
||||
"Column " + columnName + " not found in the schema");
|
||||
return false;
|
||||
}
|
||||
if (col_info->column_type() != typeid(T)) {
|
||||
throw std::invalid_argument("Column " + columnName + " type mismatch");
|
||||
return false;
|
||||
}
|
||||
data[col_info->col_index()] = value;
|
||||
return true;
|
||||
}
|
||||
template <typename T>
|
||||
T get_value(const std::string &columnName) const {
|
||||
ColumnInfo const *col_info = schema.get_column(columnName);
|
||||
if (col_info == nullptr) {
|
||||
throw std::invalid_argument(
|
||||
"Column " + columnName + " not found in the schema");
|
||||
}
|
||||
if (col_info->column_type() != typeid(T)) {
|
||||
throw std::invalid_argument("Column " + columnName + " type mismatch");
|
||||
}
|
||||
return std::any_cast<T>(data[col_info->col_index()]);
|
||||
}
|
||||
// template <typename T> // T operator[](const std::string &column_name) const { // return get_value<T>(column_name); // }
|
||||
private:
|
||||
const StreamSchema &schema; // this is where the actual memory is held
|
||||
std::vector<std::any> data;
|
||||
};
|
||||
|
||||
// table /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
class StreamTable {
|
||||
private:
|
||||
std::map<RowID, StreamRow const *> _row_map;
|
||||
|
||||
public:
|
||||
StreamTable(const StreamSchema &schema_) : _schema(schema_) {}
|
||||
bool add_row(StreamRow const *row) {
|
||||
RowID id = row->id();
|
||||
auto it = _row_map.find(id);
|
||||
if (it != _row_map.end()) {
|
||||
return false;
|
||||
} else {
|
||||
_row_map.insert({id, row});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
StreamRow const *get_row(RowID id) const {
|
||||
const auto &it = _row_map.find(id);
|
||||
if (it == _row_map.end())
|
||||
return nullptr;
|
||||
return it->second;
|
||||
}
|
||||
StreamRow const *operator[](RowID id) const { return get_row(id); }
|
||||
size_t size() const { return _row_map.size(); }
|
||||
const StreamSchema &schema() const { return _schema; }
|
||||
|
||||
private:
|
||||
const StreamSchema &_schema;
|
||||
};
|
||||
// stream updates and subscription ////////////////////////////////////////////
|
||||
class StreamUpdate {
|
||||
public:
|
||||
const StreamTable &complete_state;
|
||||
std::set<RowID> deleted_ids;
|
||||
std::vector<StreamRow const *> added_rows;
|
||||
StreamUpdate(
|
||||
const StreamTable &complete_state_,
|
||||
const std::set<RowID> deleted_ids_,
|
||||
const std::vector<StreamRow const *> &added_rows_)
|
||||
: complete_state(complete_state_), deleted_ids(deleted_ids_),
|
||||
added_rows(added_rows_) {}
|
||||
};
|
||||
|
||||
class DirectSubsription {};
|
||||
class CloudSubscirption {};
|
||||
// template <
|
||||
// typename T,
|
||||
// typename = std::enable_if_t<
|
||||
// std::is_same<T, DirectSubsription>::value ||
|
||||
// std::is_same<T, CloudSubscirption>::value>>
|
||||
|
||||
class StreamSubscription {
|
||||
private:
|
||||
std::variant<DirectSubsription, CloudSubscirption> subscription;
|
||||
const size_t _sub_id;
|
||||
|
||||
public:
|
||||
StreamSubscription(const DirectSubsription &, size_t subscription_id_)
|
||||
: _sub_id(subscription_id_) {}
|
||||
StreamSubscription(const CloudSubscirption &, size_t subscription_id_)
|
||||
: _sub_id(subscription_id_) {}
|
||||
size_t sub_id() const { return _sub_id; }
|
||||
};
|
||||
|
||||
class IStreamSync {
|
||||
public:
|
||||
virtual void on_stream_update(
|
||||
const StreamSubscription &, const StreamUpdate &stream_update) = 0;
|
||||
};
|
||||
|
||||
class StreamSource {
|
||||
public:
|
||||
void subscribe_stream_expression(const StreamSubscription &, IStreamSync &) {
|
||||
}
|
||||
};
|
||||
} // namespace bts::streams
|
||||
@@ -1,97 +0,0 @@
|
||||
|
||||
#include "catch/catch.hpp"
|
||||
// #include "lib/api/service_helpers/AdminServiceUtils.h"
|
||||
// #include "lib/api/services/AdminService.server.pb.h"
|
||||
// #include "lib/config/config.h"
|
||||
#include "lib/datastreams/Tables/StreamData.h"
|
||||
// #include "lib/utils/bts_chrono.h"
|
||||
// #include "lib/utils/helper_macros.h"
|
||||
#include <memory>
|
||||
|
||||
using namespace bts::streams;
|
||||
|
||||
TEST_CASE("schema", "[streams]") {
|
||||
// create schema
|
||||
bts::streams::StreamSchema schema = {};
|
||||
schema.add_column("id", std::type_index(typeid(int)));
|
||||
schema.add_column("sec_id", std::type_index(typeid(uint32_t)));
|
||||
schema.add_column("bid_price", std::type_index(typeid(double)));
|
||||
schema.add_column("bid_qty", std::type_index(typeid(uint32_t)));
|
||||
schema.add_column("ask_price", std::type_index(typeid(double)));
|
||||
schema.add_column("ask_qty", std::type_index(typeid(uint32_t)));
|
||||
ColumnInfo const *ci = schema.get_column("bid_price");
|
||||
REQUIRE(ci != nullptr);
|
||||
REQUIRE(ci->col_index() == 2);
|
||||
REQUIRE(ci->column_type() == typeid(double));
|
||||
}
|
||||
|
||||
TEST_CASE("row", "[streams]") {
|
||||
// create schema
|
||||
bts::streams::StreamSchema schema = {};
|
||||
schema.add_column("id", std::type_index(typeid(int)));
|
||||
schema.add_column("sec_id", std::type_index(typeid(uint32_t)));
|
||||
schema.add_column("bid_price", std::type_index(typeid(double)));
|
||||
schema.add_column("bid_qty", std::type_index(typeid(uint32_t)));
|
||||
schema.add_column("ask_price", std::type_index(typeid(double)));
|
||||
schema.add_column("ask_qty", std::type_index(typeid(uint32_t)));
|
||||
ColumnInfo const *ci = schema.get_column("bid_price");
|
||||
REQUIRE(ci != nullptr);
|
||||
REQUIRE(ci->col_index() == 2);
|
||||
REQUIRE(ci->column_type() == typeid(double));
|
||||
bts::streams::StreamRow r(schema);
|
||||
r.set_value("id", 0);
|
||||
r.set_value("sec_id", (uint)0);
|
||||
r.set_value("bid_price", 1.0);
|
||||
r.set_value("bid_qty", (uint32_t)42);
|
||||
r.set_value("ask_price", 1.0);
|
||||
r.set_value("ask_qty", (uint32_t)42);
|
||||
REQUIRE(r.get_value<uint32_t>("bid_qty") == 42);
|
||||
REQUIRE(r.get_value<double>("bid_price") == 1.0);
|
||||
}
|
||||
|
||||
TEST_CASE("table", "[streams]") {
|
||||
// create schema
|
||||
bts::streams::StreamSchema schema = {};
|
||||
schema.add_column("id", std::type_index(typeid(int)));
|
||||
schema.add_column("sec_id", std::type_index(typeid(uint32_t)));
|
||||
schema.add_column("bid_price", std::type_index(typeid(double)));
|
||||
schema.add_column("bid_qty", std::type_index(typeid(uint32_t)));
|
||||
schema.add_column("ask_price", std::type_index(typeid(double)));
|
||||
schema.add_column("ask_qty", std::type_index(typeid(uint32_t)));
|
||||
ColumnInfo const *ci = schema.get_column("bid_price");
|
||||
REQUIRE(ci != nullptr);
|
||||
REQUIRE(ci->col_index() == 2);
|
||||
REQUIRE(ci->column_type() == typeid(double));
|
||||
bts::streams::StreamRow r1(schema);
|
||||
r1.set_value("id", 0);
|
||||
r1.set_value("sec_id", (uint)0);
|
||||
r1.set_value("bid_price", 1.0);
|
||||
r1.set_value("bid_qty", (uint32_t)42);
|
||||
r1.set_value("ask_price", 1.0);
|
||||
r1.set_value("ask_qty", (uint32_t)42);
|
||||
REQUIRE(r1.get_value<uint32_t>("bid_qty") == 42);
|
||||
REQUIRE(r1.get_value<double>("bid_price") == 1.0);
|
||||
bts::streams::StreamTable table(schema);
|
||||
table.add_row(&r1);
|
||||
bts::streams::StreamRow r2(schema);
|
||||
r2.set_value("id", 1);
|
||||
r2.set_value("sec_id", (uint)1);
|
||||
r2.set_value("bid_price", 1.0);
|
||||
r2.set_value("bid_qty", (uint32_t)52);
|
||||
r2.set_value("ask_price", 1.0);
|
||||
r2.set_value("ask_qty", (uint32_t)52);
|
||||
// REQUIRE(r2["ask_qty"]<uint32_t> == 52);
|
||||
REQUIRE(r2.get_value<uint32_t>("bid_qty") == 52);
|
||||
REQUIRE(r2.get_value<double>("bid_price") == 1.0);
|
||||
table.add_row(&r2);
|
||||
auto r1_ret = table[0];
|
||||
REQUIRE(r1_ret != nullptr);
|
||||
REQUIRE(r1_ret->id() == 0);
|
||||
REQUIRE(r1_ret->get_value<uint32_t>("bid_qty") == 42);
|
||||
REQUIRE(r1_ret->get_value<double>("bid_price") == 1.0);
|
||||
auto r2_ret = table[1];
|
||||
REQUIRE(r2_ret != nullptr);
|
||||
REQUIRE(r2_ret->id() == 1);
|
||||
REQUIRE(r2_ret->get_value<uint32_t>("bid_qty") == 52);
|
||||
REQUIRE(r2_ret->get_value<double>("bid_price") == 1.0);
|
||||
}
|
||||
+1
-40
@@ -37,8 +37,6 @@ class JsonReader {
|
||||
|
||||
template <class T>
|
||||
bool read(const std::string &name, T &result) const;
|
||||
template <class T>
|
||||
bool try_read(const std::string &name, T &result) const;
|
||||
|
||||
template <class T>
|
||||
bool read_one_or_more(
|
||||
@@ -86,14 +84,6 @@ class JsonReader {
|
||||
template <class T>
|
||||
bool read(T &result) const;
|
||||
|
||||
/**
|
||||
* Reads a json field into \p result
|
||||
*
|
||||
* @note Doesn't log error if read op fails (unlike `read`)
|
||||
*/
|
||||
template <class T>
|
||||
bool try_read(T &result) const;
|
||||
|
||||
template <class T>
|
||||
T read() const;
|
||||
|
||||
@@ -145,23 +135,6 @@ inline bool JsonReader::read(const std::string &name, T &result) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline bool JsonReader::try_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;
|
||||
}
|
||||
try {
|
||||
it->get_to(result);
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline bool JsonReader::read_one_or_more(
|
||||
const std::string &name, std::vector<T> &result, bool append) const {
|
||||
@@ -246,18 +219,6 @@ inline bool JsonReader::read(T &result) const {
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline bool JsonReader::try_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;
|
||||
@@ -301,7 +262,7 @@ inline T JsonReader::read(const std::string &name) const {
|
||||
template <class T>
|
||||
inline bool JsonReader::read_or(
|
||||
const std::string &name, T &result, T default_value) const {
|
||||
if (try_read(name, result)) {
|
||||
if (read(name, result)) {
|
||||
return true;
|
||||
}
|
||||
result = default_value;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#if !defined(_WIN32) && defined(SPARK_ENABLE_CCAPI)
|
||||
#include "server/sparkd/md/platforms/mdp3_onixs/MDP3Main.h"
|
||||
#include "MarketDataGateway.h"
|
||||
#include "lib/api/md/EmumbaDeribitMarketDataConfig.pb.h"
|
||||
#include "lib/api/md/MarketDataConfig.pb.h"
|
||||
@@ -142,10 +141,6 @@ void MarketDataGateway::run() {
|
||||
bts_assert(me_->md_config.emumba_deribit().size() == 1);
|
||||
_start_emumba_deribit(me_->md_config.emumba_deribit().at(0));
|
||||
}
|
||||
if(!me_->md_config.glbx().empty()){
|
||||
bts_assert(me_->md_config.glbx().size() == 1);
|
||||
_start_onix_glbx(me_->md_config.glbx().at(0));
|
||||
}
|
||||
_start_replays(me_->md_config);
|
||||
while (true) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
@@ -241,46 +236,6 @@ void MarketDataGateway::unsubscribe(MarketDataListenerID id) {
|
||||
LOG(md, Info, "Unsubscribing listener {}", id);
|
||||
me_->listeners.erase(id);
|
||||
}
|
||||
/// glbx //////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
static void run_onixs() {
|
||||
LOG(md, Info, "MarketDataGateway::run_onixs t_id:{} t_name", os::get_thread_id(),
|
||||
os::get_thread_name());
|
||||
bts::md::MBOWriter *mbo = nullptr;
|
||||
bts::md::TradeSummarySignalDispatcher *tssd = nullptr;
|
||||
std::vector<std::shared_ptr<bts::md::IMDPlatform>> platforms;
|
||||
// get all glbx symbols
|
||||
auto cme_symbols =
|
||||
SymbolLoader::load(bts::domain::SecurityCriteria::create({api::domain::VENUE_GLBX}));
|
||||
if (!cme_symbols.empty()) {
|
||||
auto *main = new bts::md::MDP3Main();
|
||||
main->start(
|
||||
{cme_symbols.begin(), cme_symbols.end()}, mbo, tssd);
|
||||
platforms.emplace_back(main);
|
||||
}
|
||||
// deribit
|
||||
// auto *d_platform = new bts::md::EmMDListener()
|
||||
// Timers
|
||||
auto &disp = utils::Dispatcher::current();
|
||||
utils::DispatcherTimer heartbeat_timer(disp, 250, [&]() {
|
||||
for (auto &p : platforms)
|
||||
p->send_heartbeats();
|
||||
});
|
||||
heartbeat_timer.start();
|
||||
|
||||
disp.run();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void MarketDataGateway::_start_onix_glbx( bts::api::md::GlbxMarketDataConfig cfg){
|
||||
LOG(md, Info,
|
||||
"MarketDataGateway::_start_onix_glbx starting t_id:{} t_name:{}. "
|
||||
"cfg:\n{}",
|
||||
os::get_thread_id(), os::get_thread_name(), cfg.DebugString());
|
||||
bts::md::run_onixs();
|
||||
}
|
||||
|
||||
|
||||
// deribit ////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#if !defined(_WIN32) && defined(SPARK_ENABLE_CCAPI)
|
||||
#include "lib/api/md/GlbxMarketDataConfig.pb.h"
|
||||
#include "MarketDataListenerID.h"
|
||||
#include <memory>
|
||||
|
||||
@@ -33,8 +31,6 @@ class MarketDataGateway {
|
||||
MarketDataRegistration _start_cc(const api::md::MarketDataConfig &) noexcept;
|
||||
static void _start_emumba_deribit(
|
||||
bts::api::md::EmumbaDeribitMarketDataConfig cfg);
|
||||
static void _start_onix_glbx(
|
||||
bts::api::md::GlbxMarketDataConfig cfg);
|
||||
void _start_replays(const bts::api::md::MarketDataConfig &cfg);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
#include "CMEActivSession.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/md/cme/CMEActivSessionHandler.h"
|
||||
|
||||
#include <ActivMiddleware/ActivBase/Exception.h>
|
||||
#include <ActivMiddleware/ActivBase/FieldTypes/ActivFieldTypes.h>
|
||||
#include <ActivMiddleware/ActivBase/FieldTypes/FieldMap.h>
|
||||
#include <ActivOneApi/Constants/FieldIds.h>
|
||||
#include <ActivOneApi/Session.h>
|
||||
#include <exception>
|
||||
|
||||
namespace bts::md::cme {
|
||||
|
||||
using Activ::FieldMap;
|
||||
using Activ::OneApi::Session;
|
||||
using std::string;
|
||||
|
||||
void CMEActivSession::run(const string &host, const string &user, const string &password) {
|
||||
try {
|
||||
// Session callback handler.
|
||||
CMEActivSessionHandler sessionHandler;
|
||||
|
||||
FieldMap fields;
|
||||
// Construct the Session with a default configuration passing in our sessionHandler.
|
||||
Session session(fields, sessionHandler);
|
||||
|
||||
FieldMap connectParameters;
|
||||
|
||||
connectParameters.Set(Activ::OneApi::FID_HOST, host.c_str());
|
||||
connectParameters.Set(Activ::OneApi::FID_USER_ID, user.c_str());
|
||||
connectParameters.Set(Activ::OneApi::FID_PASSWORD, password.c_str());
|
||||
|
||||
// Connect synchronously with a 10 second timeout (will throw on failure).
|
||||
session.Connect(connectParameters, 10000);
|
||||
|
||||
// Alternatively an asynchronous connection can be used.
|
||||
// Connection completion is notified via ISessionHandler::OnSessionConnect().
|
||||
// session.AsyncConnect(connectParameters);
|
||||
|
||||
// Run the internal session message loop.
|
||||
session.Run();
|
||||
} catch (const Activ::Exception &ex) {
|
||||
std::cerr << "Error running CMEActivSession (activ): " << ex.GetMessage()
|
||||
<< std::endl;
|
||||
} catch (const std::exception &ex) {
|
||||
std::cerr << "Error running CMEActivSession: " << ex.what() << std::endl;
|
||||
// LOG(md, Error, "Error running CMEActivSession: {}",
|
||||
// std::string{ex.what()});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace bts::md::cme
|
||||
@@ -1,15 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace bts::md::cme {
|
||||
|
||||
class CMEActivSession {
|
||||
public:
|
||||
static void run(
|
||||
const std::string &host,
|
||||
const std::string &user,
|
||||
const std::string &password);
|
||||
};
|
||||
|
||||
} // namespace bts::md::cme
|
||||
@@ -1,32 +0,0 @@
|
||||
#include "CMEActivSessionHandler.h"
|
||||
#include <ActivMiddleware/ActivBase/ActivBase.h>
|
||||
#include <ActivOneApi/Session.h>
|
||||
|
||||
namespace bts::md::cme {
|
||||
|
||||
using Activ::LogType;
|
||||
using Activ::StatusCode;
|
||||
using Activ::String;
|
||||
using Activ::OneApi::Session;
|
||||
|
||||
void CMEActivSessionHandler::OnSessionLogMessage(
|
||||
Session & /*session*/, const LogType log_type, const String &message) {
|
||||
std::cout << Activ::LogTypeToString(log_type) << ": " << message
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
void CMEActivSessionHandler::OnSessionConnect(Session & /*session*/) {
|
||||
std::cout << "connected" << std::endl;
|
||||
}
|
||||
|
||||
void CMEActivSessionHandler::OnSessionDisconnect(Session & /*session*/) {
|
||||
std::cout << "disconnected" << std::endl;
|
||||
}
|
||||
|
||||
void CMEActivSessionHandler::OnSessionError(
|
||||
Session & /*session*/, const StatusCode statusCode) {
|
||||
std::string err{Activ::StatusCodeToString(statusCode)};
|
||||
std::cerr << "CMEActivSessionHandler::OnSessionError. " << err << std::endl;
|
||||
}
|
||||
|
||||
} // namespace bts::md::cme
|
||||
@@ -1,24 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <ActivMiddleware/ActivBase/ActivBase.h>
|
||||
#include <ActivOneApi/ISessionHandler.h>
|
||||
|
||||
namespace bts::md::cme {
|
||||
|
||||
class CMEActivSessionHandler : public Activ::OneApi::ISessionHandler {
|
||||
public:
|
||||
static void run();
|
||||
|
||||
void OnSessionLogMessage(
|
||||
Activ::OneApi::Session &session,
|
||||
const Activ::LogType log_type,
|
||||
const Activ::String &message) override;
|
||||
|
||||
void OnSessionConnect(Activ::OneApi::Session &session) override;
|
||||
void OnSessionDisconnect(Activ::OneApi::Session &session) override;
|
||||
void OnSessionError(
|
||||
Activ::OneApi::Session &session,
|
||||
const Activ::StatusCode statusCode) override;
|
||||
};
|
||||
|
||||
} // namespace bts::md::cme
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "lib/utils/datetime.h"
|
||||
#include "lib/utils/download.h"
|
||||
|
||||
#include <ActivOneApi/OneApi.h>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace bts {
|
||||
namespace md {
|
||||
|
||||
class IMDPlatform {
|
||||
public:
|
||||
virtual void send_heartbeats();
|
||||
virtual ~IMDPlatform() {}
|
||||
};
|
||||
|
||||
} // namespace md
|
||||
} // namespace bts
|
||||
@@ -1,204 +0,0 @@
|
||||
#include "DeribitDemoConfiguration.h"
|
||||
#include "lib/io/JsonReader.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/utils/FileUtils.h"
|
||||
#include "rapidjson/istreamwrapper.h"
|
||||
#include <emumba-deribit-connector/deribit_utils.h>
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <list>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <optional>
|
||||
|
||||
namespace emumba::deribit_connector::configuration {
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
|
||||
emumba::deribit_connector::configuration::subscription_config,
|
||||
public_book,
|
||||
public_index,
|
||||
public_markprice,
|
||||
public_quote,
|
||||
public_ticker,
|
||||
public_trade,
|
||||
private_portfolio,
|
||||
use_raw_interval,
|
||||
book_depth,
|
||||
requests_limit);
|
||||
// NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
|
||||
// emumba::deribit_connector::configuration::symbol_config,
|
||||
// symbol_name,
|
||||
// currency_type,
|
||||
// cred_index,
|
||||
// interested_instruments,
|
||||
// interested_subscriptions,
|
||||
// interested_in_future,
|
||||
// interested_in_future_combo,
|
||||
// interested_in_option,
|
||||
// interested_in_option_combo);
|
||||
|
||||
inline void to_json(
|
||||
nlohmann::json &j,
|
||||
const emumba ::deribit_connector::configuration::symbol_config &s) {
|
||||
j["symbol_name"] = s.symbol_name;
|
||||
j["currency_type"] = s.currency_type;
|
||||
j["cred_index"] = s.cred_index;
|
||||
j["interested_instruments"] = s.interested_instruments;
|
||||
j["interested_subscriptions"] = s.interested_subscriptions;
|
||||
j["interested_in_future"] = s.interested_in_future;
|
||||
j["interested_in_future_combo"] = s.interested_in_future_combo;
|
||||
j["interested_in_option"] = s.interested_in_option;
|
||||
j["interested_in_option_combo"] = s.interested_in_option_combo;
|
||||
}
|
||||
|
||||
inline void from_json(
|
||||
const nlohmann::json &j,
|
||||
emumba::deribit_connector::configuration::symbol_config &s) {
|
||||
j.at("symbol_name").get_to(s.symbol_name);
|
||||
j.at("currency_type").get_to(s.currency_type);
|
||||
j.at("cred_index").get_to(s.cred_index);
|
||||
j.at("interested_instruments").get_to(s.interested_instruments);
|
||||
j.at("interested_subscriptions").get_to(s.interested_subscriptions);
|
||||
j.at("interested_in_future").get_to(s.interested_in_future);
|
||||
j.at("interested_in_future_combo").get_to(s.interested_in_future_combo);
|
||||
j.at("interested_in_option").get_to(s.interested_in_option);
|
||||
j.at("interested_in_option_combo").get_to(s.interested_in_option_combo);
|
||||
}
|
||||
|
||||
inline void to_json(nlohmann::json &j, const logging_config &l) {
|
||||
j["severity"] = l.severity;
|
||||
j["console"] = l.is_console_enabled;
|
||||
j["file"] = l.is_file_enabled;
|
||||
j["queuesize"] = l.queue_size;
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json &j, logging_config &l) {
|
||||
bts::io::JsonReader jr{&j};
|
||||
jr.read("severity", l.severity);
|
||||
jr.read("console", l.is_console_enabled);
|
||||
jr.read("file", l.is_file_enabled);
|
||||
jr.read("queuesize", l.queue_size);
|
||||
}
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
|
||||
reconnection_config, retry_count, retry_interval, reception_timeout);
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(order_tracker, uuid, start_id);
|
||||
|
||||
inline void to_json(nlohmann::json &j, const trading_session_config &t) {
|
||||
j["orders_tracker"] = t.orders_tracker;
|
||||
j["core"] = t.core;
|
||||
j["cancel_on_disconnect"] = t.cancel_on_disconnect;
|
||||
j["session"] = nlohmann::json{{"heartbeat_interval", t.heartbeat_interval}};
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json &j, trading_session_config &ts) {
|
||||
j.at("orders_tracker").get_to(ts.orders_tracker);
|
||||
j.at("core").get_to(ts.core);
|
||||
j.at("cancel_on_disconnect").get_to(ts.cancel_on_disconnect);
|
||||
j.at("session").at("heartbeat_interval").get_to(ts.heartbeat_interval);
|
||||
}
|
||||
|
||||
} // namespace emumba::deribit_connector::configuration
|
||||
|
||||
namespace bts::cmds {
|
||||
|
||||
static constexpr const char *test_site() {
|
||||
return "wss://test.deribit.com/ws/api/v2";
|
||||
}
|
||||
|
||||
static std::string read_url(nlohmann::json &doc) {
|
||||
io::JsonReader jr{&doc};
|
||||
std::string url;
|
||||
jr.try_read("uri", url);
|
||||
if (url.empty()) {
|
||||
return test_site();
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
template <class Obj>
|
||||
static void read_instruments(
|
||||
Obj &obj, std::unordered_set<std::string> &instruments) {
|
||||
const char *instruments_field = "instruments";
|
||||
bts_assert(obj.HasMember(instruments_field));
|
||||
for (auto &instrument : obj[instruments_field].GetArray()) {
|
||||
instruments.insert(instrument.GetString());
|
||||
}
|
||||
}
|
||||
|
||||
static std::list<emumba::deribit_connector::configuration::symbol_config>
|
||||
read_symbol_config(rapidjson::Document &doc) {
|
||||
using RJR = RapidJsonReader;
|
||||
|
||||
std::list<emumba::deribit_connector::configuration::symbol_config> syms_out;
|
||||
for (auto &sym_in : doc["symbol"].GetArray()) {
|
||||
auto &sym = syms_out.emplace_back();
|
||||
sym.symbol_name = RJR::get_string(sym_in, "id");
|
||||
sym.interested_in_future = RJR::get_bool(sym_in, "interested_in_future");
|
||||
sym.interested_in_option = RJR::get_bool(sym_in, "interested_in_option");
|
||||
sym.currency_type = RJR::get_string(sym_in, "currency_type", "usd");
|
||||
sym.cred_index = 1;
|
||||
|
||||
read_instruments(sym_in, sym.interested_instruments);
|
||||
auto chan_in = sym_in["channel"].GetObject();
|
||||
|
||||
auto &subs = sym.interested_subscriptions;
|
||||
subs.requests_limit = RJR::get_uint(sym_in, "requests_limit", 20u);
|
||||
subs.book_depth = RJR::get_uint(sym_in, "book_depth", 5u);
|
||||
subs.public_book = RJR::get_bool(chan_in, "PUBLIC_BOOK");
|
||||
subs.public_quote = RJR::get_bool(chan_in, "PUBLIC_QUOTE");
|
||||
subs.public_trade = RJR::get_bool(chan_in, "PUBLIC_TRADE");
|
||||
subs.public_ticker = RJR::get_bool(chan_in, "PUBLIC_TICKER");
|
||||
subs.public_markprice = RJR::get_bool(chan_in, "PUBLIC_MARKPRICE");
|
||||
subs.public_index = RJR::get_bool(chan_in, "PUBLIC_INDEX");
|
||||
// subs.private_orders = RJR::get_bool(chan_in, "PRIVATE_ORDERS");
|
||||
// subs.private_trades = RJR::get_bool(chan_in, "PRIVATE_TRADES");
|
||||
subs.private_portfolio = RJR::get_bool(chan_in, "PRIVATE_PORTFOLIO");
|
||||
subs.use_raw_interval = RJR::get_bool(sym_in, "raw_enable", true);
|
||||
}
|
||||
return syms_out;
|
||||
}
|
||||
|
||||
static std::
|
||||
map<uint, emumba::deribit_connector::configuration::credential_config>
|
||||
read_credential_config(rapidjson::Document &doc) {
|
||||
using RJR = RapidJsonReader;
|
||||
using namespace emumba::deribit_connector;
|
||||
|
||||
std::map<uint, configuration::credential_config> rv;
|
||||
for (auto &cred_in : doc["credentials"].GetArray()) {
|
||||
configuration::credential_config rv_i;
|
||||
rv_i.client_name = RJR::get_string(cred_in, "client_name");
|
||||
rv_i.client_id = RJR::get_string(cred_in, "id");
|
||||
rv_i.index = RJR::get_uint(cred_in, "subaccount_index");
|
||||
rv_i.secret_key = RJR::get_string(cred_in, "secret_key");
|
||||
rv.insert(
|
||||
std::pair<uint, configuration::credential_config>(rv_i.index, rv_i));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
std::optional<DeribitDemoConfiguration> DeribitDemoConfiguration::read_file(
|
||||
const std::string &config_path) {
|
||||
nlohmann::json conf_j;
|
||||
try {
|
||||
conf_j =
|
||||
nlohmann::json::parse(bts::spartan::FileUtils::read_file(config_path));
|
||||
} catch (const std::exception &ex) {
|
||||
LOG(md, Error, "Error in parsing config from {}: {}", config_path,
|
||||
static_cast<int>(conf_j.GetParseError()));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
DeribitDemoConfiguration conf;
|
||||
io::JsonReader jr{&conf_j};
|
||||
jr.try_read("app_logging", conf.logging_configuration);
|
||||
conf.logging_configuration = read_app_logging(doc);
|
||||
conf.deribit_url = read_url(conf_j);
|
||||
conf.trading_config = read_trading_session_config(doc);
|
||||
conf.symbol_config_list = read_symbol_config(doc);
|
||||
conf.credential_map = read_credential_config(doc);
|
||||
return conf;
|
||||
}
|
||||
|
||||
} // namespace bts::cmds
|
||||
@@ -1,54 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
|
||||
#include <emumba-deribit-connector/deribit_utils.h>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace bts::cmds {
|
||||
|
||||
struct DeribitDemoConfiguration {
|
||||
std::string deribit_url;
|
||||
std::list<emumba::deribit_connector::configuration::symbol_config>
|
||||
symbol_config_list;
|
||||
emumba::deribit_connector::configuration::logging_config
|
||||
logging_configuration;
|
||||
emumba::deribit_connector::configuration::trading_session_config
|
||||
trading_config;
|
||||
std::map<uint, emumba::deribit_connector::configuration::credential_config>
|
||||
credential_map;
|
||||
|
||||
static std::optional<DeribitDemoConfiguration> read_file(
|
||||
const std::string &path);
|
||||
};
|
||||
|
||||
inline std::ostream &operator<<(
|
||||
std::ostream &out,
|
||||
emumba::deribit_connector::configuration::credential_config const &c) {
|
||||
out << "index: " << c.index << " client_name: " << c.client_name
|
||||
<< " client_id: " << c.client_id << " secret_key: " << c.secret_key;
|
||||
return out;
|
||||
}
|
||||
|
||||
inline std::ostream &operator<<(
|
||||
std::ostream &out, DeribitDemoConfiguration const &c) {
|
||||
out << "deribit_url: " << c.deribit_url
|
||||
<< " symbols: " << c.symbol_config_list.size()
|
||||
<< " core: " << c.trading_config.core
|
||||
<< " heartbeat_interval: " << c.trading_config.heartbeat_interval
|
||||
<< " cancel_on_disconnect: " << c.trading_config.cancel_on_disconnect
|
||||
<< " credential_map: " << c.credential_map.size();
|
||||
for (const auto &p : c.credential_map) {
|
||||
out << " " << p.second;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace bts::cmds
|
||||
|
||||
FMT_IMPL_OSTREAM(bts::cmds::DeribitDemoConfiguration);
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/md/platforms/emumba/DeribitDemoConfiguration.h"
|
||||
#include <emumba-deribit-connector/deribit_handler.h>
|
||||
#include <emumba-deribit-connector/deribit_listener.h>
|
||||
#include <emumba-deribit-connector/trading_session_interface.h>
|
||||
|
||||
namespace bts::orderman {
|
||||
|
||||
struct EMTradingSession {
|
||||
emumba::deribit_connector::configuration::credential_config credential;
|
||||
std::shared_ptr<emumba::deribit_connector::trading_session_interface>
|
||||
trading_interface;
|
||||
};
|
||||
|
||||
} // namespace bts::orderman
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/md/platforms/IMDPlatform.h"
|
||||
#include <memory>
|
||||
|
||||
namespace bts::md {
|
||||
|
||||
class EmumbaMain : public IMDPlatform {
|
||||
public:
|
||||
EmumbaMain();
|
||||
virtual ~EmumbaMain();
|
||||
// IMDPlatform implementation
|
||||
void send_heartbeats() override;
|
||||
void run();
|
||||
|
||||
private:
|
||||
struct PImpl;
|
||||
std::unique_ptr<PImpl> me;
|
||||
};
|
||||
|
||||
} // namespace bts::md
|
||||
@@ -143,7 +143,7 @@ struct MDRecorder::PImpl {
|
||||
const bts::api::md::MarketDataRecordingConfig &recording_config)
|
||||
: venue(venue),
|
||||
settings(MDRecordingSettings{
|
||||
recording_config.n_book_levels(),
|
||||
10,
|
||||
static_cast<uint64_t>(recording_config.num_of_seconds_to_run()) *
|
||||
1000 * 1000 * 1000,
|
||||
100000, 1000000000}),
|
||||
@@ -184,8 +184,7 @@ void MDRecorder::_poll_md() {
|
||||
uint64_t now_ns = core::TimeStamp::now_ns();
|
||||
for (auto &kv : me->handles) {
|
||||
MDRecordingSecEntry &md_entry = kv.second;
|
||||
if(me->settings.n_book_levels > 0)
|
||||
_poll_books(md_entry);
|
||||
_poll_books(md_entry);
|
||||
_poll_trades(md_entry);
|
||||
_poll_misc(md_entry);
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -41,7 +41,7 @@ OnixsTemplLicense onixs_create_temporary_license() {
|
||||
const auto &options = config::get<OnixsOptions>();
|
||||
OnixsTemplLicense linfo;
|
||||
linfo.path = options.onixs_license();
|
||||
if (linfo.path == "{default}" || linfo.path =="") {
|
||||
if (linfo.path == "{default}") {
|
||||
linfo.path = Path{config::get<SystemConfig>().data_path()} / "licenses";
|
||||
auto ident = boost::filesystem::unique_path("%%%%-%%%%-%%%%-%%%%.lic");
|
||||
linfo.license = {linfo.path / ident};
|
||||
|
||||
@@ -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
|
||||
Loaded 100 of 168 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user