Compare commits
58 Commits
feature/wi
...
feature/lu
Author | SHA1 | Date | |
---|---|---|---|
|
22fc8e981d | ||
|
fb0de53efd | ||
|
62a7483e41 | ||
|
36fbdd0daa | ||
|
feff2e38c0 | ||
|
a040ff06e8 | ||
|
755493eaf3 | ||
|
4c61aede2e | ||
|
fbca513008 | ||
|
33ebd00932 | ||
|
fbe5e74109 | ||
|
a9f5d5353f | ||
|
22e0083832 | ||
|
5632360fbd | ||
|
20294841b3 | ||
|
74efdfebba | ||
|
0ab04f51fe | ||
|
4ed7968b05 | ||
|
287e48962f | ||
|
953c680eee | ||
|
2802cad8c4 | ||
|
9f770b10c0 | ||
|
677f79b0ea | ||
|
646b18bf28 | ||
|
0670954faf | ||
|
2469d7102e | ||
|
79acb915ce | ||
|
bad3adb6b4 | ||
|
e3dd4e60c0 | ||
|
c70f1d09a8 | ||
|
4b2b133c10 | ||
|
cd5fae6a5b | ||
|
5860c5c80b | ||
|
36257cbfe4 | ||
|
68ee57a6a7 | ||
|
9d79596629 | ||
|
0b6fd989f5 | ||
|
1c19a57fef | ||
|
a2abe861d3 | ||
|
0f5d15aa11 | ||
|
ccfd196863 | ||
|
9b8cfa0a37 | ||
|
85f6b1e0b7 | ||
|
64754df66c | ||
|
71a421eefc | ||
|
386ef3ab04 | ||
|
2c4bf8f4bd | ||
|
3a2c446225 | ||
|
35630fe7ed | ||
|
bea582c208 | ||
|
783d1d92dd | ||
|
415f6b4832 | ||
|
13d3300a40 | ||
|
432f0570f4 | ||
|
37a054723a | ||
|
c57cf413fb | ||
|
f1c106728b | ||
|
2eb5c9480e |
30
.github/workflows/ccpp.yml
vendored
30
.github/workflows/ccpp.yml
vendored
@@ -19,15 +19,6 @@ jobs:
|
|||||||
- name: make test_tsan
|
- name: make test_tsan
|
||||||
run: make test_tsan
|
run: make test_tsan
|
||||||
|
|
||||||
mac_tsan_openssl:
|
|
||||||
runs-on: macOS-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- name: install openssl
|
|
||||||
run: brew install openssl
|
|
||||||
- name: make test
|
|
||||||
run: make test_tsan_openssl
|
|
||||||
|
|
||||||
mac_tsan_mbedtls:
|
mac_tsan_mbedtls:
|
||||||
runs-on: macOS-latest
|
runs-on: macOS-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -37,7 +28,7 @@ jobs:
|
|||||||
- name: make test
|
- name: make test
|
||||||
run: make test_tsan_mbedtls
|
run: make test_tsan_mbedtls
|
||||||
|
|
||||||
windows_no_tls:
|
windows_openssl:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
@@ -45,25 +36,8 @@ jobs:
|
|||||||
- run: |
|
- run: |
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_TEST=1 ..
|
cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_WS=1 -DUSE_TEST=1 ..
|
||||||
- run: cmake --build build
|
- run: cmake --build build
|
||||||
- run: |
|
|
||||||
cd test
|
|
||||||
..\build\test\ixwebsocket_unittest.exe
|
|
||||||
|
|
||||||
# windows_mbedtls:
|
|
||||||
# runs-on: windows-latest
|
|
||||||
# steps:
|
|
||||||
# - uses: actions/checkout@v1
|
|
||||||
# - uses: seanmiddleditch/gha-setup-vsdevenv@master
|
|
||||||
# - run: |
|
|
||||||
# vcpkg install zlib:x64-windows
|
|
||||||
# vcpkg install mbedtls:x64-windows
|
|
||||||
# - run: |
|
|
||||||
# mkdir build
|
|
||||||
# cd build
|
|
||||||
# cmake -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_MBED_TLS=1 -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 ..
|
|
||||||
# - run: cmake --build build
|
|
||||||
|
|
||||||
# Running the unittest does not work, the binary cannot be found
|
# Running the unittest does not work, the binary cannot be found
|
||||||
#- run: ../build/test/ixwebsocket_unittest.exe
|
#- run: ../build/test/ixwebsocket_unittest.exe
|
||||||
|
19
.github/workflows/stale.yml
vendored
Normal file
19
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: Mark stale issues and pull requests
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 0 * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v1
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
stale-issue-message: 'Stale issue message'
|
||||||
|
stale-pr-message: 'Stale pull request message'
|
||||||
|
stale-issue-label: 'no-issue-activity'
|
||||||
|
stale-pr-label: 'no-pr-activity'
|
@@ -1,7 +1,12 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v2.3.0
|
rev: v2.5.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
|
- repo: https://github.com/pocc/pre-commit-hooks
|
||||||
|
rev: v1.1.1
|
||||||
|
hooks:
|
||||||
|
- id: clang-format
|
||||||
|
args: [-i, -style=file]
|
||||||
|
@@ -5,7 +5,7 @@ include(FindPackageHandleStandardArgs)
|
|||||||
find_path(JSONCPP_INCLUDE_DIRS json/json.h)
|
find_path(JSONCPP_INCLUDE_DIRS json/json.h)
|
||||||
find_library(JSONCPP_LIBRARY jsoncpp)
|
find_library(JSONCPP_LIBRARY jsoncpp)
|
||||||
|
|
||||||
find_package_handle_standard_args(JSONCPP
|
find_package_handle_standard_args(JsonCpp
|
||||||
FOUND_VAR
|
FOUND_VAR
|
||||||
JSONCPP_FOUND
|
JSONCPP_FOUND
|
||||||
REQUIRED_VARS
|
REQUIRED_VARS
|
||||||
|
111
CMakeLists.txt
111
CMakeLists.txt
@@ -44,13 +44,11 @@ set( IXWEBSOCKET_SOURCES
|
|||||||
ixwebsocket/IXWebSocketCloseConstants.cpp
|
ixwebsocket/IXWebSocketCloseConstants.cpp
|
||||||
ixwebsocket/IXWebSocketHandshake.cpp
|
ixwebsocket/IXWebSocketHandshake.cpp
|
||||||
ixwebsocket/IXWebSocketHttpHeaders.cpp
|
ixwebsocket/IXWebSocketHttpHeaders.cpp
|
||||||
ixwebsocket/IXWebSocketMessageQueue.cpp
|
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflate.cpp
|
ixwebsocket/IXWebSocketPerMessageDeflate.cpp
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp
|
ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp
|
ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp
|
||||||
ixwebsocket/IXWebSocketServer.cpp
|
ixwebsocket/IXWebSocketServer.cpp
|
||||||
ixwebsocket/IXWebSocketTransport.cpp
|
ixwebsocket/IXWebSocketTransport.cpp
|
||||||
ixwebsocket/LUrlParser.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set( IXWEBSOCKET_HEADERS
|
set( IXWEBSOCKET_HEADERS
|
||||||
@@ -81,10 +79,10 @@ set( IXWEBSOCKET_HEADERS
|
|||||||
ixwebsocket/IXWebSocketCloseInfo.h
|
ixwebsocket/IXWebSocketCloseInfo.h
|
||||||
ixwebsocket/IXWebSocketErrorInfo.h
|
ixwebsocket/IXWebSocketErrorInfo.h
|
||||||
ixwebsocket/IXWebSocketHandshake.h
|
ixwebsocket/IXWebSocketHandshake.h
|
||||||
|
ixwebsocket/IXWebSocketHandshakeKeyGen.h
|
||||||
ixwebsocket/IXWebSocketHttpHeaders.h
|
ixwebsocket/IXWebSocketHttpHeaders.h
|
||||||
ixwebsocket/IXWebSocketInitResult.h
|
ixwebsocket/IXWebSocketInitResult.h
|
||||||
ixwebsocket/IXWebSocketMessage.h
|
ixwebsocket/IXWebSocketMessage.h
|
||||||
ixwebsocket/IXWebSocketMessageQueue.h
|
|
||||||
ixwebsocket/IXWebSocketMessageType.h
|
ixwebsocket/IXWebSocketMessageType.h
|
||||||
ixwebsocket/IXWebSocketOpenInfo.h
|
ixwebsocket/IXWebSocketOpenInfo.h
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflate.h
|
ixwebsocket/IXWebSocketPerMessageDeflate.h
|
||||||
@@ -94,8 +92,6 @@ set( IXWEBSOCKET_HEADERS
|
|||||||
ixwebsocket/IXWebSocketServer.h
|
ixwebsocket/IXWebSocketServer.h
|
||||||
ixwebsocket/IXWebSocketTransport.h
|
ixwebsocket/IXWebSocketTransport.h
|
||||||
ixwebsocket/IXWebSocketVersion.h
|
ixwebsocket/IXWebSocketVersion.h
|
||||||
ixwebsocket/LUrlParser.h
|
|
||||||
ixwebsocket/libwshandshake.hpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (UNIX)
|
if (UNIX)
|
||||||
@@ -113,31 +109,33 @@ elseif (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
|
|||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/freebsd/IXSetThreadName_freebsd.cpp)
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/freebsd/IXSetThreadName_freebsd.cpp)
|
||||||
else()
|
else()
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/linux/IXSetThreadName_linux.cpp)
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/linux/IXSetThreadName_linux.cpp)
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptEventFd.cpp)
|
|
||||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSelectInterruptEventFd.h)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
option(USE_TLS "Enable TLS support" FALSE)
|
option(USE_TLS "Enable TLS support" FALSE)
|
||||||
|
|
||||||
if (USE_TLS)
|
if (USE_TLS)
|
||||||
option(USE_MBED_TLS "Use Mbed TLS" OFF)
|
# default to securetranport on Apple if nothing is configured
|
||||||
option(USE_OPEN_SSL "Use OpenSSL" OFF)
|
if (APPLE)
|
||||||
|
if (NOT USE_MBED_TLS AND NOT USE_OPEN_SSL) # unless we want something else
|
||||||
# default to mbedtls on windows if nothing is configured
|
set(USE_SECURE_TRANSPORT ON)
|
||||||
if (WIN32 AND NOT USE_OPEN_SSL AND NOT USE_MBED_TLS)
|
endif()
|
||||||
option(USE_MBED_TLS "Use Mbed TLS" ON)
|
else() # default to OpenSSL on all other platforms
|
||||||
|
if (NOT USE_MBED_TLS) # Unless mbedtls is requested
|
||||||
|
set(USE_OPEN_SSL ON)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (USE_MBED_TLS)
|
if (USE_MBED_TLS)
|
||||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketMbedTLS.h)
|
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketMbedTLS.h)
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketMbedTLS.cpp)
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketMbedTLS.cpp)
|
||||||
elseif (APPLE AND NOT USE_OPEN_SSL)
|
elseif (USE_SECURE_TRANSPORT)
|
||||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketAppleSSL.h)
|
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketAppleSSL.h)
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketAppleSSL.cpp)
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketAppleSSL.cpp)
|
||||||
else()
|
elseif (USE_OPEN_SSL)
|
||||||
set(USE_OPEN_SSL ON)
|
|
||||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketOpenSSL.h)
|
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketOpenSSL.h)
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketOpenSSL.cpp)
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketOpenSSL.cpp)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "TLS Configuration error: unknown backend")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -152,55 +150,45 @@ if (USE_TLS)
|
|||||||
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_MBED_TLS)
|
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_MBED_TLS)
|
||||||
elseif (USE_OPEN_SSL)
|
elseif (USE_OPEN_SSL)
|
||||||
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_OPEN_SSL)
|
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_OPEN_SSL)
|
||||||
|
elseif (USE_SECURE_TRANSPORT)
|
||||||
|
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_SECURE_TRANSPORT)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "TLS Configuration error: unknown backend")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (APPLE AND USE_TLS AND NOT USE_MBED_TLS AND NOT USE_OPEN_SSL)
|
if (USE_TLS)
|
||||||
target_link_libraries(ixwebsocket "-framework foundation" "-framework security")
|
if (USE_OPEN_SSL)
|
||||||
endif()
|
message(STATUS "TLS configured to use openssl")
|
||||||
|
|
||||||
if (WIN32)
|
# Help finding Homebrew's OpenSSL on macOS
|
||||||
target_link_libraries(ixwebsocket wsock32 ws2_32 shlwapi)
|
if (APPLE)
|
||||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
set(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} /usr/local/opt/openssl/lib)
|
||||||
endif()
|
set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /usr/local/opt/openssl/include)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (UNIX)
|
# This OPENSSL_FOUND check is to help find a cmake manually configured OpenSSL
|
||||||
find_package(Threads)
|
if (NOT OPENSSL_FOUND)
|
||||||
target_link_libraries(ixwebsocket ${CMAKE_THREAD_LIBS_INIT})
|
find_package(OpenSSL REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
|
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
|
||||||
|
|
||||||
if (USE_TLS AND USE_OPEN_SSL)
|
add_definitions(${OPENSSL_DEFINITIONS})
|
||||||
|
target_include_directories(ixwebsocket PUBLIC ${OPENSSL_INCLUDE_DIR})
|
||||||
|
target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES})
|
||||||
|
elseif (USE_MBED_TLS)
|
||||||
|
message(STATUS "TLS configured to use mbedtls")
|
||||||
|
|
||||||
# Help finding Homebrew's OpenSSL on macOS
|
|
||||||
if (APPLE)
|
|
||||||
set(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} /usr/local/opt/openssl/lib)
|
|
||||||
set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /usr/local/opt/openssl/include)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(NOT OPENSSL_FOUND)
|
|
||||||
find_package(OpenSSL REQUIRED)
|
|
||||||
endif()
|
|
||||||
add_definitions(${OPENSSL_DEFINITIONS})
|
|
||||||
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
|
|
||||||
include_directories(${OPENSSL_INCLUDE_DIR})
|
|
||||||
target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (USE_TLS AND USE_MBED_TLS)
|
|
||||||
# FIXME I'm not too sure that this USE_VENDORED_THIRD_PARTY thing works
|
|
||||||
if (USE_VENDORED_THIRD_PARTY)
|
|
||||||
set (ENABLE_PROGRAMS OFF)
|
|
||||||
add_subdirectory(third_party/mbedtls)
|
|
||||||
include_directories(third_party/mbedtls/include)
|
|
||||||
|
|
||||||
target_link_libraries(ixwebsocket mbedtls)
|
|
||||||
else()
|
|
||||||
find_package(MbedTLS REQUIRED)
|
find_package(MbedTLS REQUIRED)
|
||||||
target_include_directories(ixwebsocket PUBLIC ${MBEDTLS_INCLUDE_DIRS})
|
target_include_directories(ixwebsocket PUBLIC ${MBEDTLS_INCLUDE_DIRS})
|
||||||
target_link_libraries(ixwebsocket ${MBEDTLS_LIBRARIES})
|
target_link_libraries(ixwebsocket ${MBEDTLS_LIBRARIES})
|
||||||
|
elseif (USE_SECURE_TRANSPORT)
|
||||||
|
message(STATUS "TLS configured to use secure transport")
|
||||||
|
target_link_libraries(ixwebsocket "-framework foundation" "-framework security")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# This ZLIB_FOUND check is to help find a cmake manually configured zlib
|
||||||
if (NOT ZLIB_FOUND)
|
if (NOT ZLIB_FOUND)
|
||||||
find_package(ZLIB)
|
find_package(ZLIB)
|
||||||
endif()
|
endif()
|
||||||
@@ -213,6 +201,21 @@ else()
|
|||||||
target_link_libraries(ixwebsocket zlibstatic)
|
target_link_libraries(ixwebsocket zlibstatic)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (WIN32)
|
||||||
|
target_link_libraries(ixwebsocket wsock32 ws2_32 shlwapi)
|
||||||
|
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||||
|
|
||||||
|
if (USE_TLS)
|
||||||
|
target_link_libraries(ixwebsocket Crypt32)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (UNIX)
|
||||||
|
find_package(Threads)
|
||||||
|
target_link_libraries(ixwebsocket ${CMAKE_THREAD_LIBS_INIT})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
set( IXWEBSOCKET_INCLUDE_DIRS
|
set( IXWEBSOCKET_INCLUDE_DIRS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
)
|
)
|
||||||
@@ -248,3 +251,7 @@ if (USE_WS OR USE_TEST)
|
|||||||
add_subdirectory(test)
|
add_subdirectory(test)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (USE_LUAROCKS)
|
||||||
|
add_subdirectory(luarocks)
|
||||||
|
endif()
|
||||||
|
@@ -1,6 +1,74 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
All changes to this project will be documented in this file.
|
All changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [9.5.2] - 2020-04-27
|
||||||
|
|
||||||
|
(cmake) fix cmake broken tls option parsing
|
||||||
|
|
||||||
|
## [9.5.1] - 2020-04-27
|
||||||
|
|
||||||
|
(http client) Set default values for most HttpRequestArgs struct members (fix #185)
|
||||||
|
|
||||||
|
## [9.5.0] - 2020-04-25
|
||||||
|
|
||||||
|
(ssl) Default to OpenSSL on Windows, since it can load the system certificates by default
|
||||||
|
|
||||||
|
## [9.4.1] - 2020-04-25
|
||||||
|
|
||||||
|
(header) Add a space between header name and header value since most http parsers expects it, although it it not required. Cf #184 and #155
|
||||||
|
|
||||||
|
## [9.4.0] - 2020-04-24
|
||||||
|
|
||||||
|
(ssl) Add support for supplying SSL CA from memory, for OpenSSL and MbedTLS backends
|
||||||
|
|
||||||
|
## [9.3.3] - 2020-04-17
|
||||||
|
|
||||||
|
(ixbots) display sent/receive message, per seconds as accumulated
|
||||||
|
|
||||||
|
## [9.3.2] - 2020-04-17
|
||||||
|
|
||||||
|
(ws) add a --logfile option to configure all logs to go to a file
|
||||||
|
|
||||||
|
## [9.3.1] - 2020-04-16
|
||||||
|
|
||||||
|
(cobra bots) add a utility class to factor out the common bots features (heartbeat) and move all bots to used it + convert cobra_subscribe to be a bot and add a unittest for it
|
||||||
|
|
||||||
|
## [9.3.0] - 2020-04-15
|
||||||
|
|
||||||
|
(websocket) add a positive number to the heartbeat message sent, incremented each time the heartbeat is sent
|
||||||
|
|
||||||
|
## [9.2.9] - 2020-04-15
|
||||||
|
|
||||||
|
(ixcobra) change cobra event callback to use a struct instead of several objects, which is more flexible/extensible
|
||||||
|
|
||||||
|
## [9.2.8] - 2020-04-15
|
||||||
|
|
||||||
|
(ixcobra) make CobraConnection_EventType an enum class (CobraEventType)
|
||||||
|
|
||||||
|
## [9.2.7] - 2020-04-14
|
||||||
|
|
||||||
|
(ixsentry) add a library method to upload a payload directly to sentry
|
||||||
|
|
||||||
|
## [9.2.6] - 2020-04-14
|
||||||
|
|
||||||
|
(ixcobra) snake server / handle invalid incoming json messages + cobra subscriber in fluentd mode insert a created_at timestamp entry
|
||||||
|
|
||||||
|
## [9.2.5] - 2020-04-13
|
||||||
|
|
||||||
|
(websocket) WebSocketMessagePtr is a unique_ptr instead of a shared_ptr
|
||||||
|
|
||||||
|
## [9.2.4] - 2020-04-13
|
||||||
|
|
||||||
|
(websocket) use persistent member variable as temp variables to encode/decode zlib messages in order to reduce transient allocations
|
||||||
|
|
||||||
|
## [9.2.3] - 2020-04-13
|
||||||
|
|
||||||
|
(ws) add a --runtime option to ws cobra_subscribe to optionally limit how much time it will run
|
||||||
|
|
||||||
|
## [9.2.2] - 2020-04-04
|
||||||
|
|
||||||
|
(third_party deps) fix #177, update bundled spdlog to 1.6.0
|
||||||
|
|
||||||
## [9.2.1] - 2020-04-04
|
## [9.2.1] - 2020-04-04
|
||||||
|
|
||||||
(windows) when using OpenSSL, the system store is used to populate the cacert. No need to ship a cacert.pem file with your app.
|
(windows) when using OpenSSL, the system store is used to populate the cacert. No need to ship a cacert.pem file with your app.
|
||||||
|
@@ -18,10 +18,12 @@ There is a unittest which can be executed by typing `make test`.
|
|||||||
Options for building:
|
Options for building:
|
||||||
|
|
||||||
* `-DUSE_TLS=1` will enable TLS support
|
* `-DUSE_TLS=1` will enable TLS support
|
||||||
* `-DUSE_MBED_TLS=1` will use [mbedlts](https://tls.mbed.org/) for the TLS support (default on Windows)
|
* `-DUSE_OPEN_SSL=1` will use [openssl](https://www.openssl.org/) for the TLS support (default on Linux and Windows)
|
||||||
|
* `-DUSE_MBED_TLS=1` will use [mbedlts](https://tls.mbed.org/) for the TLS support
|
||||||
* `-DUSE_WS=1` will build the ws interactive command line tool
|
* `-DUSE_WS=1` will build the ws interactive command line tool
|
||||||
|
* `-DUSE_TEST=1` will build the unittest
|
||||||
|
|
||||||
If you are on Windows, look at the [appveyor](https://github.com/machinezone/IXWebSocket/blob/master/appveyor.yml) file that has instructions for building dependencies.
|
If you are on Windows, look at the [appveyor](https://github.com/machinezone/IXWebSocket/blob/master/appveyor.yml) file (not maintained much though) or rather the [github actions](https://github.com/machinezone/IXWebSocket/blob/master/.github/workflows/ccpp.yml#L40) which have instructions for building dependencies.
|
||||||
|
|
||||||
It is also possible to externally include the project, so that everything is fetched over the wire when you build like so:
|
It is also possible to externally include the project, so that everything is fetched over the wire when you build like so:
|
||||||
|
|
||||||
@@ -59,7 +61,7 @@ Note that the version listed here might not be the latest one. See Bintray or th
|
|||||||
There is a Dockerfile for running the unittest on Linux, and to run the `ws` tool. It is also available on the docker registry.
|
There is a Dockerfile for running the unittest on Linux, and to run the `ws` tool. It is also available on the docker registry.
|
||||||
|
|
||||||
```
|
```
|
||||||
docker run bsergean/ws
|
docker run docker.pkg.github.com/machinezone/ixwebsocket/ws:latest --help
|
||||||
```
|
```
|
||||||
|
|
||||||
To use docker-compose you must make a docker container first.
|
To use docker-compose you must make a docker container first.
|
||||||
|
@@ -38,8 +38,7 @@ The regression test is running after each commit on github actions for multiple
|
|||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
* On Windows and Android certificate validation needs to be setup so that SocketTLSOptions.caFile point to a pem file, such as the one distributed by Firefox. Unless that setup is done connecting to a wss endpoint will display an error. On Windows with mbedtls the message will contain `error in handshake : X509 - Certificate verification failed, e.g. CRL, CA or signature check failed`.
|
* On some configuration (mostly Android) certificate validation needs to be setup so that SocketTLSOptions.caFile point to a pem file, such as the one distributed by Firefox. Unless that setup is done connecting to a wss endpoint will display an error. With mbedtls the message will contain `error in handshake : X509 - Certificate verification failed, e.g. CRL, CA or signature check failed`.
|
||||||
* There is no convenient way to embed a ca cert.
|
|
||||||
* Automatic reconnection works at the TCP socket level, and will detect remote end disconnects. However, if the device/computer network become unreachable (by turning off wifi), it is quite hard to reliably and timely detect it at the socket level using `recv` and `send` error codes. [Here](https://stackoverflow.com/questions/14782143/linux-socket-how-to-detect-disconnected-network-in-a-client-program) is a good discussion on the subject. This behavior is consistent with other runtimes such as node.js. One way to detect a disconnected device with low level C code is to do a name resolution with DNS but this can be expensive. Mobile devices have good and reliable API to do that.
|
* Automatic reconnection works at the TCP socket level, and will detect remote end disconnects. However, if the device/computer network become unreachable (by turning off wifi), it is quite hard to reliably and timely detect it at the socket level using `recv` and `send` error codes. [Here](https://stackoverflow.com/questions/14782143/linux-socket-how-to-detect-disconnected-network-in-a-client-program) is a good discussion on the subject. This behavior is consistent with other runtimes such as node.js. One way to detect a disconnected device with low level C code is to do a name resolution with DNS but this can be expensive. Mobile devices have good and reliable API to do that.
|
||||||
* The server code is using select to detect incoming data, and creates one OS thread per connection. This is not as scalable as strategies using epoll or kqueue.
|
* The server code is using select to detect incoming data, and creates one OS thread per connection. This is not as scalable as strategies using epoll or kqueue.
|
||||||
|
|
||||||
|
@@ -447,7 +447,7 @@ Additional TLS options can be configured by passing a `ix::SocketTLSOptions` ins
|
|||||||
webSocket.setTLSOptions({
|
webSocket.setTLSOptions({
|
||||||
.certFile = "path/to/cert/file.pem",
|
.certFile = "path/to/cert/file.pem",
|
||||||
.keyFile = "path/to/key/file.pem",
|
.keyFile = "path/to/key/file.pem",
|
||||||
.caFile = "path/to/trust/bundle/file.pem",
|
.caFile = "path/to/trust/bundle/file.pem", // as a file, or in memory buffer in PEM format
|
||||||
.tls = true // required in server mode
|
.tls = true // required in server mode
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
@@ -461,6 +461,7 @@ On a server, this is necessary for TLS support.
|
|||||||
Specifying `caFile` configures the trusted roots bundle file (in PEM format) that will be used to verify peer certificates.
|
Specifying `caFile` configures the trusted roots bundle file (in PEM format) that will be used to verify peer certificates.
|
||||||
- The special value of `SYSTEM` (the default) indicates that the system-configured trust bundle should be used; this is generally what you want when connecting to any publicly exposed API/server.
|
- The special value of `SYSTEM` (the default) indicates that the system-configured trust bundle should be used; this is generally what you want when connecting to any publicly exposed API/server.
|
||||||
- The special value of `NONE` can be used to disable peer verification; this is only recommended to rule out certificate verification when testing connectivity.
|
- The special value of `NONE` can be used to disable peer verification; this is only recommended to rule out certificate verification when testing connectivity.
|
||||||
|
- If the value contain the special value `-----BEGIN CERTIFICATE-----`, the value will be read from memory, and not from a file. This is convenient on platforms like Android where reading / writing to the file system can be challenging without proper permissions, or without knowing the location of a temp directory.
|
||||||
|
|
||||||
For a client, specifying `caFile` can be used if connecting to a server that uses a self-signed cert, or when using a custom CA in an internal environment.
|
For a client, specifying `caFile` can be used if connecting to a server that uses a self-signed cert, or when using a custom CA in an internal environment.
|
||||||
|
|
||||||
|
@@ -4,15 +4,19 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
set (IXBOTS_SOURCES
|
set (IXBOTS_SOURCES
|
||||||
|
ixbots/IXCobraBot.cpp
|
||||||
ixbots/IXCobraToSentryBot.cpp
|
ixbots/IXCobraToSentryBot.cpp
|
||||||
ixbots/IXCobraToStatsdBot.cpp
|
ixbots/IXCobraToStatsdBot.cpp
|
||||||
|
ixbots/IXCobraToStdoutBot.cpp
|
||||||
ixbots/IXQueueManager.cpp
|
ixbots/IXQueueManager.cpp
|
||||||
ixbots/IXStatsdClient.cpp
|
ixbots/IXStatsdClient.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set (IXBOTS_HEADERS
|
set (IXBOTS_HEADERS
|
||||||
|
ixbots/IXCobraBot.h
|
||||||
ixbots/IXCobraToSentryBot.h
|
ixbots/IXCobraToSentryBot.h
|
||||||
ixbots/IXCobraToStatsdBot.h
|
ixbots/IXCobraToStatsdBot.h
|
||||||
|
ixbots/IXCobraToStdoutBot.h
|
||||||
ixbots/IXQueueManager.h
|
ixbots/IXQueueManager.h
|
||||||
ixbots/IXStatsdClient.h
|
ixbots/IXStatsdClient.h
|
||||||
)
|
)
|
||||||
@@ -27,11 +31,6 @@ if (NOT JSONCPP_FOUND)
|
|||||||
set(JSONCPP_INCLUDE_DIRS ../third_party/jsoncpp)
|
set(JSONCPP_INCLUDE_DIRS ../third_party/jsoncpp)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(SpdLog)
|
|
||||||
if (NOT SPDLOG_FOUND)
|
|
||||||
set(SPDLOG_INCLUDE_DIRS ../third_party/spdlog/include)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(IXBOTS_INCLUDE_DIRS
|
set(IXBOTS_INCLUDE_DIRS
|
||||||
.
|
.
|
||||||
..
|
..
|
||||||
|
321
ixbots/ixbots/IXCobraBot.cpp
Normal file
321
ixbots/ixbots/IXCobraBot.cpp
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
/*
|
||||||
|
* IXCobraBot.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IXCobraBot.h"
|
||||||
|
|
||||||
|
#include "IXQueueManager.h"
|
||||||
|
#include <ixcobra/IXCobraConnection.h>
|
||||||
|
#include <ixcore/utils/IXCoreLogger.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <sstream>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
int64_t CobraBot::run(const CobraConfig& config,
|
||||||
|
const std::string& channel,
|
||||||
|
const std::string& filter,
|
||||||
|
const std::string& position,
|
||||||
|
bool verbose,
|
||||||
|
size_t maxQueueSize,
|
||||||
|
bool useQueue,
|
||||||
|
bool enableHeartbeat,
|
||||||
|
int runtime)
|
||||||
|
{
|
||||||
|
ix::CobraConnection conn;
|
||||||
|
conn.configure(config);
|
||||||
|
conn.connect();
|
||||||
|
|
||||||
|
Json::FastWriter jsonWriter;
|
||||||
|
std::atomic<uint64_t> sentCount(0);
|
||||||
|
std::atomic<uint64_t> receivedCount(0);
|
||||||
|
uint64_t sentCountTotal(0);
|
||||||
|
uint64_t receivedCountTotal(0);
|
||||||
|
uint64_t sentCountPerSecs(0);
|
||||||
|
uint64_t receivedCountPerSecs(0);
|
||||||
|
std::atomic<bool> stop(false);
|
||||||
|
std::atomic<bool> throttled(false);
|
||||||
|
std::atomic<bool> fatalCobraError(false);
|
||||||
|
|
||||||
|
QueueManager queueManager(maxQueueSize);
|
||||||
|
|
||||||
|
auto timer = [&sentCount,
|
||||||
|
&receivedCount,
|
||||||
|
&sentCountTotal,
|
||||||
|
&receivedCountTotal,
|
||||||
|
&sentCountPerSecs,
|
||||||
|
&receivedCountPerSecs,
|
||||||
|
&stop] {
|
||||||
|
while (!stop)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// We cannot write to sentCount and receivedCount
|
||||||
|
// as those are used externally, so we need to introduce
|
||||||
|
// our own counters
|
||||||
|
//
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "messages received "
|
||||||
|
<< receivedCountPerSecs
|
||||||
|
<< " "
|
||||||
|
<< receivedCountTotal
|
||||||
|
<< " sent "
|
||||||
|
<< sentCountPerSecs
|
||||||
|
<< " "
|
||||||
|
<< sentCountTotal;
|
||||||
|
CoreLogger::info(ss.str());
|
||||||
|
|
||||||
|
receivedCountPerSecs = receivedCount - receivedCountTotal;
|
||||||
|
sentCountPerSecs = sentCount - receivedCountTotal;
|
||||||
|
|
||||||
|
receivedCountTotal += receivedCountPerSecs;
|
||||||
|
sentCountTotal += sentCountPerSecs;
|
||||||
|
|
||||||
|
auto duration = std::chrono::seconds(1);
|
||||||
|
std::this_thread::sleep_for(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreLogger::info("timer thread done");
|
||||||
|
};
|
||||||
|
|
||||||
|
std::thread t1(timer);
|
||||||
|
|
||||||
|
auto heartbeat = [&sentCount, &receivedCount, &stop, &enableHeartbeat] {
|
||||||
|
std::string state("na");
|
||||||
|
|
||||||
|
if (!enableHeartbeat) return;
|
||||||
|
|
||||||
|
while (!stop)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "messages received " << receivedCount;
|
||||||
|
ss << "messages sent " << sentCount;
|
||||||
|
|
||||||
|
std::string currentState = ss.str();
|
||||||
|
|
||||||
|
if (currentState == state)
|
||||||
|
{
|
||||||
|
CoreLogger::error("no messages received or sent for 1 minute, exiting");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
state = currentState;
|
||||||
|
|
||||||
|
auto duration = std::chrono::minutes(1);
|
||||||
|
std::this_thread::sleep_for(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreLogger::info("heartbeat thread done");
|
||||||
|
};
|
||||||
|
|
||||||
|
std::thread t2(heartbeat);
|
||||||
|
|
||||||
|
auto sender =
|
||||||
|
[this, &queueManager, verbose, &sentCount, &stop, &throttled, &fatalCobraError] {
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
auto data = queueManager.pop();
|
||||||
|
Json::Value msg = data.first;
|
||||||
|
std::string position = data.second;
|
||||||
|
|
||||||
|
if (stop) break;
|
||||||
|
if (msg.isNull()) continue;
|
||||||
|
|
||||||
|
if (_onBotMessageCallback &&
|
||||||
|
_onBotMessageCallback(msg, position, verbose, throttled, fatalCobraError))
|
||||||
|
{
|
||||||
|
// That might be too noisy
|
||||||
|
if (verbose)
|
||||||
|
{
|
||||||
|
CoreLogger::info("cobra bot: sending succesfull");
|
||||||
|
}
|
||||||
|
++sentCount;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CoreLogger::error("cobra bot: error sending");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stop) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreLogger::info("sender thread done");
|
||||||
|
};
|
||||||
|
|
||||||
|
std::thread t3(sender);
|
||||||
|
|
||||||
|
std::string subscriptionPosition(position);
|
||||||
|
|
||||||
|
conn.setEventCallback([this,
|
||||||
|
&conn,
|
||||||
|
&channel,
|
||||||
|
&filter,
|
||||||
|
&subscriptionPosition,
|
||||||
|
&jsonWriter,
|
||||||
|
verbose,
|
||||||
|
&throttled,
|
||||||
|
&receivedCount,
|
||||||
|
&fatalCobraError,
|
||||||
|
&useQueue,
|
||||||
|
&queueManager,
|
||||||
|
&sentCount](const CobraEventPtr& event) {
|
||||||
|
if (event->type == ix::CobraEventType::Open)
|
||||||
|
{
|
||||||
|
CoreLogger::info("Subscriber connected");
|
||||||
|
|
||||||
|
for (auto&& it : event->headers)
|
||||||
|
{
|
||||||
|
CoreLogger::info(it.first + "::" + it.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (event->type == ix::CobraEventType::Closed)
|
||||||
|
{
|
||||||
|
CoreLogger::info("Subscriber closed: {}" + event->errMsg);
|
||||||
|
}
|
||||||
|
else if (event->type == ix::CobraEventType::Authenticated)
|
||||||
|
{
|
||||||
|
CoreLogger::info("Subscriber authenticated");
|
||||||
|
CoreLogger::info("Subscribing to " + channel);
|
||||||
|
CoreLogger::info("Subscribing at position " + subscriptionPosition);
|
||||||
|
CoreLogger::info("Subscribing with filter " + filter);
|
||||||
|
conn.subscribe(channel,
|
||||||
|
filter,
|
||||||
|
subscriptionPosition,
|
||||||
|
[this,
|
||||||
|
&jsonWriter,
|
||||||
|
verbose,
|
||||||
|
&throttled,
|
||||||
|
&receivedCount,
|
||||||
|
&queueManager,
|
||||||
|
&useQueue,
|
||||||
|
&subscriptionPosition,
|
||||||
|
&fatalCobraError,
|
||||||
|
&sentCount](const Json::Value& msg, const std::string& position) {
|
||||||
|
if (verbose)
|
||||||
|
{
|
||||||
|
CoreLogger::info("Subscriber received message "
|
||||||
|
+ position + " -> " + jsonWriter.write(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriptionPosition = position;
|
||||||
|
|
||||||
|
// If we cannot send to sentry fast enough, drop the message
|
||||||
|
if (throttled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
++receivedCount;
|
||||||
|
|
||||||
|
if (useQueue)
|
||||||
|
{
|
||||||
|
queueManager.add(msg, position);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_onBotMessageCallback &&
|
||||||
|
_onBotMessageCallback(
|
||||||
|
msg, position, verbose, throttled, fatalCobraError))
|
||||||
|
{
|
||||||
|
// That might be too noisy
|
||||||
|
if (verbose)
|
||||||
|
{
|
||||||
|
CoreLogger::info("cobra bot: sending succesfull");
|
||||||
|
}
|
||||||
|
++sentCount;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CoreLogger::error("cobra bot: error sending");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (event->type == ix::CobraEventType::Subscribed)
|
||||||
|
{
|
||||||
|
CoreLogger::info("Subscriber: subscribed to channel " + event->subscriptionId);
|
||||||
|
}
|
||||||
|
else if (event->type == ix::CobraEventType::UnSubscribed)
|
||||||
|
{
|
||||||
|
CoreLogger::info("Subscriber: unsubscribed from channel " + event->subscriptionId);
|
||||||
|
}
|
||||||
|
else if (event->type == ix::CobraEventType::Error)
|
||||||
|
{
|
||||||
|
CoreLogger::error("Subscriber: error " + event->errMsg);
|
||||||
|
}
|
||||||
|
else if (event->type == ix::CobraEventType::Published)
|
||||||
|
{
|
||||||
|
CoreLogger::error("Published message hacked: " + std::to_string(event->msgId));
|
||||||
|
}
|
||||||
|
else if (event->type == ix::CobraEventType::Pong)
|
||||||
|
{
|
||||||
|
CoreLogger::info("Received websocket pong: " + event->errMsg);
|
||||||
|
}
|
||||||
|
else if (event->type == ix::CobraEventType::HandshakeError)
|
||||||
|
{
|
||||||
|
CoreLogger::error("Subscriber: Handshake error: " + event->errMsg);
|
||||||
|
fatalCobraError = true;
|
||||||
|
}
|
||||||
|
else if (event->type == ix::CobraEventType::AuthenticationError)
|
||||||
|
{
|
||||||
|
CoreLogger::error("Subscriber: Authentication error: " + event->errMsg);
|
||||||
|
fatalCobraError = true;
|
||||||
|
}
|
||||||
|
else if (event->type == ix::CobraEventType::SubscriptionError)
|
||||||
|
{
|
||||||
|
CoreLogger::error("Subscriber: Subscription error: " + event->errMsg);
|
||||||
|
fatalCobraError = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run forever
|
||||||
|
if (runtime == -1)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
auto duration = std::chrono::seconds(1);
|
||||||
|
std::this_thread::sleep_for(duration);
|
||||||
|
|
||||||
|
if (fatalCobraError) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Run for a duration, used by unittesting now
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < runtime; ++i)
|
||||||
|
{
|
||||||
|
auto duration = std::chrono::seconds(1);
|
||||||
|
std::this_thread::sleep_for(duration);
|
||||||
|
|
||||||
|
if (fatalCobraError) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Cleanup.
|
||||||
|
// join all the bg threads and stop them.
|
||||||
|
//
|
||||||
|
conn.disconnect();
|
||||||
|
stop = true;
|
||||||
|
|
||||||
|
// progress thread
|
||||||
|
t1.join();
|
||||||
|
|
||||||
|
// heartbeat thread
|
||||||
|
if (t2.joinable()) t2.join();
|
||||||
|
|
||||||
|
// sentry sender thread
|
||||||
|
t3.join();
|
||||||
|
|
||||||
|
return fatalCobraError ? -1 : (int64_t) sentCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CobraBot::setOnBotMessageCallback(const OnBotMessageCallback& callback)
|
||||||
|
{
|
||||||
|
_onBotMessageCallback = callback;
|
||||||
|
}
|
||||||
|
} // namespace ix
|
43
ixbots/ixbots/IXCobraBot.h
Normal file
43
ixbots/ixbots/IXCobraBot.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* IXCobraBot.h
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <functional>
|
||||||
|
#include <ixcobra/IXCobraConfig.h>
|
||||||
|
#include <json/json.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
using OnBotMessageCallback = std::function<bool(const Json::Value&,
|
||||||
|
const std::string&,
|
||||||
|
const bool verbose,
|
||||||
|
std::atomic<bool>&,
|
||||||
|
std::atomic<bool>&)>;
|
||||||
|
|
||||||
|
class CobraBot
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CobraBot() = default;
|
||||||
|
|
||||||
|
int64_t run(const CobraConfig& config,
|
||||||
|
const std::string& channel,
|
||||||
|
const std::string& filter,
|
||||||
|
const std::string& position,
|
||||||
|
bool verbose,
|
||||||
|
size_t maxQueueSize,
|
||||||
|
bool useQueue,
|
||||||
|
bool enableHeartbeat,
|
||||||
|
int runtime);
|
||||||
|
|
||||||
|
void setOnBotMessageCallback(const OnBotMessageCallback& callback);
|
||||||
|
|
||||||
|
private:
|
||||||
|
OnBotMessageCallback _onBotMessageCallback;
|
||||||
|
};
|
||||||
|
} // namespace ix
|
@@ -5,301 +5,113 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "IXCobraToSentryBot.h"
|
#include "IXCobraToSentryBot.h"
|
||||||
|
|
||||||
|
#include "IXCobraBot.h"
|
||||||
#include "IXQueueManager.h"
|
#include "IXQueueManager.h"
|
||||||
|
#include <ixcobra/IXCobraConnection.h>
|
||||||
|
#include <ixcore/utils/IXCoreLogger.h>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <ixcobra/IXCobraConnection.h>
|
|
||||||
#include <spdlog/spdlog.h>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <thread>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
int cobra_to_sentry_bot(const CobraConfig& config,
|
int64_t cobra_to_sentry_bot(const CobraConfig& config,
|
||||||
const std::string& channel,
|
const std::string& channel,
|
||||||
const std::string& filter,
|
const std::string& filter,
|
||||||
const std::string& position,
|
const std::string& position,
|
||||||
SentryClient& sentryClient,
|
SentryClient& sentryClient,
|
||||||
bool verbose,
|
bool verbose,
|
||||||
bool strict,
|
size_t maxQueueSize,
|
||||||
size_t maxQueueSize,
|
bool enableHeartbeat,
|
||||||
bool enableHeartbeat,
|
int runtime)
|
||||||
int runtime)
|
|
||||||
{
|
{
|
||||||
ix::CobraConnection conn;
|
CobraBot bot;
|
||||||
conn.configure(config);
|
bot.setOnBotMessageCallback([&sentryClient](const Json::Value& msg,
|
||||||
conn.connect();
|
const std::string& /*position*/,
|
||||||
|
const bool verbose,
|
||||||
|
std::atomic<bool>& throttled,
|
||||||
|
std::atomic<bool> &
|
||||||
|
/*fatalCobraError*/) -> bool {
|
||||||
|
auto ret = sentryClient.send(msg, verbose);
|
||||||
|
HttpResponsePtr response = ret.first;
|
||||||
|
|
||||||
Json::FastWriter jsonWriter;
|
if (!response)
|
||||||
std::atomic<uint64_t> sentCount(0);
|
|
||||||
std::atomic<uint64_t> receivedCount(0);
|
|
||||||
std::atomic<bool> errorSending(false);
|
|
||||||
std::atomic<bool> stop(false);
|
|
||||||
std::atomic<bool> throttled(false);
|
|
||||||
std::atomic<bool> fatalCobraError(false);
|
|
||||||
|
|
||||||
QueueManager queueManager(maxQueueSize);
|
|
||||||
|
|
||||||
auto timer = [&sentCount, &receivedCount, &stop] {
|
|
||||||
while (!stop)
|
|
||||||
{
|
{
|
||||||
spdlog::info("messages received {} sent {}", receivedCount, sentCount);
|
CoreLogger::warn("Null HTTP Response");
|
||||||
|
return false;
|
||||||
auto duration = std::chrono::seconds(1);
|
|
||||||
std::this_thread::sleep_for(duration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
spdlog::info("timer thread done");
|
if (verbose)
|
||||||
};
|
|
||||||
|
|
||||||
std::thread t1(timer);
|
|
||||||
|
|
||||||
auto heartbeat = [&sentCount, &receivedCount, &stop, &enableHeartbeat] {
|
|
||||||
std::string state("na");
|
|
||||||
|
|
||||||
if (!enableHeartbeat) return;
|
|
||||||
|
|
||||||
while (!stop)
|
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
for (auto it : response->headers)
|
||||||
ss << "messages received " << receivedCount;
|
|
||||||
ss << "messages sent " << sentCount;
|
|
||||||
|
|
||||||
std::string currentState = ss.str();
|
|
||||||
|
|
||||||
if (currentState == state)
|
|
||||||
{
|
{
|
||||||
spdlog::error("no messages received or sent for 1 minute, exiting");
|
CoreLogger::info(it.first + ": " + it.second);
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
state = currentState;
|
|
||||||
|
|
||||||
auto duration = std::chrono::minutes(1);
|
|
||||||
std::this_thread::sleep_for(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
spdlog::info("heartbeat thread done");
|
|
||||||
};
|
|
||||||
|
|
||||||
std::thread t2(heartbeat);
|
|
||||||
|
|
||||||
auto sentrySender =
|
|
||||||
[&queueManager, verbose, &errorSending, &sentCount, &stop, &throttled, &sentryClient] {
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
Json::Value msg = queueManager.pop();
|
|
||||||
|
|
||||||
if (stop) break;
|
|
||||||
if (msg.isNull()) continue;
|
|
||||||
|
|
||||||
auto ret = sentryClient.send(msg, verbose);
|
|
||||||
HttpResponsePtr response = ret.first;
|
|
||||||
|
|
||||||
if (!response)
|
|
||||||
{
|
|
||||||
spdlog::warn("Null HTTP Response");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (verbose)
|
|
||||||
{
|
|
||||||
for (auto it : response->headers)
|
|
||||||
{
|
|
||||||
spdlog::info("{}: {}", it.first, it.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
spdlog::info("Upload size: {}", response->uploadSize);
|
|
||||||
spdlog::info("Download size: {}", response->downloadSize);
|
|
||||||
|
|
||||||
spdlog::info("Status: {}", response->statusCode);
|
|
||||||
if (response->errorCode != HttpErrorCode::Ok)
|
|
||||||
{
|
|
||||||
spdlog::info("error message: {}", response->errorMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response->headers["Content-Type"] != "application/octet-stream")
|
|
||||||
{
|
|
||||||
spdlog::info("payload: {}", response->payload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response->statusCode != 200)
|
|
||||||
{
|
|
||||||
spdlog::error("Error sending data to sentry: {}", response->statusCode);
|
|
||||||
spdlog::error("Body: {}", ret.second);
|
|
||||||
spdlog::error("Response: {}", response->payload);
|
|
||||||
errorSending = true;
|
|
||||||
|
|
||||||
// Error 429 Too Many Requests
|
|
||||||
if (response->statusCode == 429)
|
|
||||||
{
|
|
||||||
auto retryAfter = response->headers["Retry-After"];
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << retryAfter;
|
|
||||||
int seconds;
|
|
||||||
ss >> seconds;
|
|
||||||
|
|
||||||
if (!ss.eof() || ss.fail())
|
|
||||||
{
|
|
||||||
seconds = 30;
|
|
||||||
spdlog::warn("Error parsing Retry-After header. "
|
|
||||||
"Using {} for the sleep duration",
|
|
||||||
seconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
spdlog::warn("Error 429 - Too Many Requests. ws will sleep "
|
|
||||||
"and retry after {} seconds",
|
|
||||||
retryAfter);
|
|
||||||
|
|
||||||
throttled = true;
|
|
||||||
auto duration = std::chrono::seconds(seconds);
|
|
||||||
std::this_thread::sleep_for(duration);
|
|
||||||
throttled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
++sentCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stop) break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
spdlog::info("sentrySender thread done");
|
CoreLogger::info("Upload size: " + std::to_string(response->uploadSize));
|
||||||
};
|
CoreLogger::info("Download size: " + std::to_string(response->downloadSize));
|
||||||
|
|
||||||
std::thread t3(sentrySender);
|
CoreLogger::info("Status: " + std::to_string(response->statusCode));
|
||||||
|
if (response->errorCode != HttpErrorCode::Ok)
|
||||||
conn.setEventCallback([&conn,
|
|
||||||
&channel,
|
|
||||||
&filter,
|
|
||||||
&position,
|
|
||||||
&jsonWriter,
|
|
||||||
verbose,
|
|
||||||
&throttled,
|
|
||||||
&receivedCount,
|
|
||||||
&fatalCobraError,
|
|
||||||
&queueManager](ix::CobraConnectionEventType eventType,
|
|
||||||
const std::string& errMsg,
|
|
||||||
const ix::WebSocketHttpHeaders& headers,
|
|
||||||
const std::string& subscriptionId,
|
|
||||||
CobraConnection::MsgId msgId) {
|
|
||||||
if (eventType == ix::CobraConnection_EventType_Open)
|
|
||||||
{
|
|
||||||
spdlog::info("Subscriber connected");
|
|
||||||
|
|
||||||
for (auto it : headers)
|
|
||||||
{
|
{
|
||||||
spdlog::info("{}: {}", it.first, it.second);
|
CoreLogger::info("error message: " + response->errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response->headers["Content-Type"] != "application/octet-stream")
|
||||||
|
{
|
||||||
|
CoreLogger::info("payload: " + response->payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (eventType == ix::CobraConnection_EventType_Closed)
|
|
||||||
{
|
|
||||||
spdlog::info("Subscriber closed");
|
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_Authenticated)
|
|
||||||
{
|
|
||||||
spdlog::info("Subscriber authenticated");
|
|
||||||
conn.subscribe(channel,
|
|
||||||
filter,
|
|
||||||
position,
|
|
||||||
[&jsonWriter, verbose, &throttled, &receivedCount, &queueManager](
|
|
||||||
const Json::Value& msg, const std::string& position) {
|
|
||||||
if (verbose)
|
|
||||||
{
|
|
||||||
spdlog::info("Subscriber received message {} -> {}", position, jsonWriter.write(msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we cannot send to sentry fast enough, drop the message
|
bool success = response->statusCode == 200;
|
||||||
if (throttled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
++receivedCount;
|
if (!success)
|
||||||
queueManager.add(msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_Subscribed)
|
|
||||||
{
|
{
|
||||||
spdlog::info("Subscriber: subscribed to channel {}", subscriptionId);
|
CoreLogger::error("Error sending data to sentry: " + std::to_string(response->statusCode));
|
||||||
}
|
CoreLogger::error("Body: " + ret.second);
|
||||||
else if (eventType == ix::CobraConnection_EventType_UnSubscribed)
|
CoreLogger::error("Response: " + response->payload);
|
||||||
{
|
|
||||||
spdlog::info("Subscriber: unsubscribed from channel {}", subscriptionId);
|
// Error 429 Too Many Requests
|
||||||
}
|
if (response->statusCode == 429)
|
||||||
else if (eventType == ix::CobraConnection_EventType_Error)
|
{
|
||||||
{
|
auto retryAfter = response->headers["Retry-After"];
|
||||||
spdlog::error("Subscriber: error {}", errMsg);
|
std::stringstream ss;
|
||||||
}
|
ss << retryAfter;
|
||||||
else if (eventType == ix::CobraConnection_EventType_Published)
|
int seconds;
|
||||||
{
|
ss >> seconds;
|
||||||
spdlog::error("Published message hacked: {}", msgId);
|
|
||||||
}
|
if (!ss.eof() || ss.fail())
|
||||||
else if (eventType == ix::CobraConnection_EventType_Pong)
|
{
|
||||||
{
|
seconds = 30;
|
||||||
spdlog::info("Received websocket pong");
|
CoreLogger::warn("Error parsing Retry-After header. "
|
||||||
}
|
"Using " + retryAfter + " for the sleep duration");
|
||||||
else if (eventType == ix::CobraConnection_EventType_Handshake_Error)
|
}
|
||||||
{
|
|
||||||
spdlog::error("Subscriber: Handshake error: {}", errMsg);
|
CoreLogger::warn("Error 429 - Too Many Requests. ws will sleep "
|
||||||
fatalCobraError = true;
|
"and retry after " + retryAfter + " seconds");
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_Authentication_Error)
|
throttled = true;
|
||||||
{
|
auto duration = std::chrono::seconds(seconds);
|
||||||
spdlog::error("Subscriber: Authentication error: {}", errMsg);
|
std::this_thread::sleep_for(duration);
|
||||||
fatalCobraError = true;
|
throttled = false;
|
||||||
}
|
}
|
||||||
else if (eventType == ix::CobraConnection_EventType_Subscription_Error)
|
|
||||||
{
|
|
||||||
spdlog::error("Subscriber: Subscription error: {}", errMsg);
|
|
||||||
fatalCobraError = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Run forever
|
bool useQueue = true;
|
||||||
if (runtime == -1)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
auto duration = std::chrono::seconds(1);
|
|
||||||
std::this_thread::sleep_for(duration);
|
|
||||||
|
|
||||||
if (strict && errorSending) break;
|
return bot.run(config,
|
||||||
if (fatalCobraError) break;
|
channel,
|
||||||
}
|
filter,
|
||||||
}
|
position,
|
||||||
// Run for a duration, used by unittesting now
|
verbose,
|
||||||
else
|
maxQueueSize,
|
||||||
{
|
useQueue,
|
||||||
for (int i = 0 ; i < runtime; ++i)
|
enableHeartbeat,
|
||||||
{
|
runtime);
|
||||||
auto duration = std::chrono::seconds(1);
|
|
||||||
std::this_thread::sleep_for(duration);
|
|
||||||
|
|
||||||
if (strict && errorSending) break;
|
|
||||||
if (fatalCobraError) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Cleanup.
|
|
||||||
// join all the bg threads and stop them.
|
|
||||||
//
|
|
||||||
conn.disconnect();
|
|
||||||
stop = true;
|
|
||||||
|
|
||||||
// progress thread
|
|
||||||
t1.join();
|
|
||||||
|
|
||||||
// heartbeat thread
|
|
||||||
if (t2.joinable()) t2.join();
|
|
||||||
|
|
||||||
// sentry sender thread
|
|
||||||
t3.join();
|
|
||||||
|
|
||||||
return ((strict && errorSending) || fatalCobraError) ? -1 : (int) sentCount;
|
|
||||||
}
|
}
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@@ -5,20 +5,20 @@
|
|||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <ixcobra/IXCobraConfig.h>
|
#include <ixcobra/IXCobraConfig.h>
|
||||||
#include <ixsentry/IXSentryClient.h>
|
#include <ixsentry/IXSentryClient.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
int cobra_to_sentry_bot(const CobraConfig& config,
|
int64_t cobra_to_sentry_bot(const CobraConfig& config,
|
||||||
const std::string& channel,
|
const std::string& channel,
|
||||||
const std::string& filter,
|
const std::string& filter,
|
||||||
const std::string& position,
|
const std::string& position,
|
||||||
SentryClient& sentryClient,
|
SentryClient& sentryClient,
|
||||||
bool verbose,
|
bool verbose,
|
||||||
bool strict,
|
size_t maxQueueSize,
|
||||||
size_t maxQueueSize,
|
bool enableHeartbeat,
|
||||||
bool enableHeartbeat,
|
int runtime);
|
||||||
int runtime);
|
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@@ -5,16 +5,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "IXCobraToStatsdBot.h"
|
#include "IXCobraToStatsdBot.h"
|
||||||
|
|
||||||
|
#include "IXCobraBot.h"
|
||||||
#include "IXQueueManager.h"
|
#include "IXQueueManager.h"
|
||||||
#include "IXStatsdClient.h"
|
#include "IXStatsdClient.h"
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <condition_variable>
|
|
||||||
#include <ixcobra/IXCobraConnection.h>
|
#include <ixcobra/IXCobraConnection.h>
|
||||||
#include <spdlog/spdlog.h>
|
#include <ixcore/utils/IXCoreLogger.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <thread>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
@@ -56,18 +54,18 @@ namespace ix
|
|||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cobra_to_statsd_bot(const ix::CobraConfig& config,
|
int64_t cobra_to_statsd_bot(const ix::CobraConfig& config,
|
||||||
const std::string& channel,
|
const std::string& channel,
|
||||||
const std::string& filter,
|
const std::string& filter,
|
||||||
const std::string& position,
|
const std::string& position,
|
||||||
StatsdClient& statsdClient,
|
StatsdClient& statsdClient,
|
||||||
const std::string& fields,
|
const std::string& fields,
|
||||||
const std::string& gauge,
|
const std::string& gauge,
|
||||||
const std::string& timer,
|
const std::string& timer,
|
||||||
bool verbose,
|
bool verbose,
|
||||||
size_t maxQueueSize,
|
size_t maxQueueSize,
|
||||||
bool enableHeartbeat,
|
bool enableHeartbeat,
|
||||||
int runtime)
|
int runtime)
|
||||||
{
|
{
|
||||||
ix::CobraConnection conn;
|
ix::CobraConnection conn;
|
||||||
conn.configure(config);
|
conn.configure(config);
|
||||||
@@ -75,65 +73,13 @@ namespace ix
|
|||||||
|
|
||||||
auto tokens = parseFields(fields);
|
auto tokens = parseFields(fields);
|
||||||
|
|
||||||
Json::FastWriter jsonWriter;
|
CobraBot bot;
|
||||||
std::atomic<uint64_t> sentCount(0);
|
bot.setOnBotMessageCallback(
|
||||||
std::atomic<uint64_t> receivedCount(0);
|
[&statsdClient, &tokens, &gauge, &timer](const Json::Value& msg,
|
||||||
std::atomic<bool> stop(false);
|
const std::string& /*position*/,
|
||||||
std::atomic<bool> fatalCobraError(false);
|
const bool verbose,
|
||||||
|
std::atomic<bool>& /*throttled*/,
|
||||||
QueueManager queueManager(maxQueueSize);
|
std::atomic<bool>& fatalCobraError) -> bool {
|
||||||
|
|
||||||
auto progress = [&sentCount, &receivedCount, &stop] {
|
|
||||||
while (!stop)
|
|
||||||
{
|
|
||||||
spdlog::info("messages received {} sent {}", receivedCount, sentCount);
|
|
||||||
|
|
||||||
auto duration = std::chrono::seconds(1);
|
|
||||||
std::this_thread::sleep_for(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
spdlog::info("timer thread done");
|
|
||||||
};
|
|
||||||
|
|
||||||
std::thread t1(progress);
|
|
||||||
|
|
||||||
auto heartbeat = [&sentCount, &receivedCount, &stop, &enableHeartbeat] {
|
|
||||||
std::string state("na");
|
|
||||||
|
|
||||||
if (!enableHeartbeat) return;
|
|
||||||
|
|
||||||
while (!stop)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "messages received " << receivedCount;
|
|
||||||
ss << "messages sent " << sentCount;
|
|
||||||
|
|
||||||
std::string currentState = ss.str();
|
|
||||||
|
|
||||||
if (currentState == state)
|
|
||||||
{
|
|
||||||
spdlog::error("no messages received or sent for 1 minute, exiting");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
state = currentState;
|
|
||||||
|
|
||||||
auto duration = std::chrono::minutes(1);
|
|
||||||
std::this_thread::sleep_for(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
spdlog::info("heartbeat thread done");
|
|
||||||
};
|
|
||||||
|
|
||||||
std::thread t2(heartbeat);
|
|
||||||
|
|
||||||
auto statsdSender = [&statsdClient, &queueManager, &sentCount, &tokens, &stop, &gauge, &timer, &fatalCobraError, &verbose] {
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
Json::Value msg = queueManager.pop();
|
|
||||||
|
|
||||||
if (stop) return;
|
|
||||||
if (msg.isNull()) continue;
|
|
||||||
|
|
||||||
std::string id;
|
std::string id;
|
||||||
for (auto&& attr : tokens)
|
for (auto&& attr : tokens)
|
||||||
{
|
{
|
||||||
@@ -174,14 +120,14 @@ namespace ix
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
spdlog::error("Gauge {} is not a numberic type", gauge);
|
CoreLogger::error("Gauge " + gauge + " is not a numeric type");
|
||||||
fatalCobraError = true;
|
fatalCobraError = true;
|
||||||
break;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (verbose)
|
if (verbose)
|
||||||
{
|
{
|
||||||
spdlog::info("{} - {} -> {}", id, attrName, x);
|
CoreLogger::info(id + " - " + attrName + " -> " + std::to_string(x));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!gauge.empty())
|
if (!gauge.empty())
|
||||||
@@ -194,127 +140,19 @@ namespace ix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sentCount += 1;
|
return true;
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::thread t3(statsdSender);
|
|
||||||
|
|
||||||
conn.setEventCallback(
|
|
||||||
[&conn, &channel, &filter, &position, &jsonWriter, verbose, &queueManager, &receivedCount, &fatalCobraError](
|
|
||||||
ix::CobraConnectionEventType eventType,
|
|
||||||
const std::string& errMsg,
|
|
||||||
const ix::WebSocketHttpHeaders& headers,
|
|
||||||
const std::string& subscriptionId,
|
|
||||||
CobraConnection::MsgId msgId) {
|
|
||||||
if (eventType == ix::CobraConnection_EventType_Open)
|
|
||||||
{
|
|
||||||
spdlog::info("Subscriber connected");
|
|
||||||
|
|
||||||
for (auto it : headers)
|
|
||||||
{
|
|
||||||
spdlog::info("{}: {}", it.first, it.second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (eventType == ix::CobraConnection_EventType_Closed)
|
|
||||||
{
|
|
||||||
spdlog::info("Subscriber closed");
|
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_Authenticated)
|
|
||||||
{
|
|
||||||
spdlog::info("Subscriber authenticated");
|
|
||||||
conn.subscribe(channel,
|
|
||||||
filter,
|
|
||||||
position,
|
|
||||||
[&jsonWriter, &queueManager, verbose, &receivedCount](
|
|
||||||
const Json::Value& msg, const std::string& position) {
|
|
||||||
if (verbose)
|
|
||||||
{
|
|
||||||
spdlog::info("Subscriber received message {} -> {}", position, jsonWriter.write(msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
receivedCount++;
|
|
||||||
|
|
||||||
++receivedCount;
|
|
||||||
queueManager.add(msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_Subscribed)
|
|
||||||
{
|
|
||||||
spdlog::info("Subscriber: subscribed to channel {}", subscriptionId);
|
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_UnSubscribed)
|
|
||||||
{
|
|
||||||
spdlog::info("Subscriber: unsubscribed from channel {}", subscriptionId);
|
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_Error)
|
|
||||||
{
|
|
||||||
spdlog::error("Subscriber: error {}", errMsg);
|
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_Published)
|
|
||||||
{
|
|
||||||
spdlog::error("Published message hacked: {}", msgId);
|
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_Pong)
|
|
||||||
{
|
|
||||||
spdlog::info("Received websocket pong");
|
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_Handshake_Error)
|
|
||||||
{
|
|
||||||
spdlog::error("Subscriber: Handshake error: {}", errMsg);
|
|
||||||
fatalCobraError = true;
|
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_Authentication_Error)
|
|
||||||
{
|
|
||||||
spdlog::error("Subscriber: Authentication error: {}", errMsg);
|
|
||||||
fatalCobraError = true;
|
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_Subscription_Error)
|
|
||||||
{
|
|
||||||
spdlog::error("Subscriber: Subscription error: {}", errMsg);
|
|
||||||
fatalCobraError = true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Run forever
|
bool useQueue = true;
|
||||||
if (runtime == -1)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
auto duration = std::chrono::seconds(1);
|
|
||||||
std::this_thread::sleep_for(duration);
|
|
||||||
|
|
||||||
if (fatalCobraError) break;
|
return bot.run(config,
|
||||||
}
|
channel,
|
||||||
}
|
filter,
|
||||||
// Run for a duration, used by unittesting now
|
position,
|
||||||
else
|
verbose,
|
||||||
{
|
maxQueueSize,
|
||||||
for (int i = 0 ; i < runtime; ++i)
|
useQueue,
|
||||||
{
|
enableHeartbeat,
|
||||||
auto duration = std::chrono::seconds(1);
|
runtime);
|
||||||
std::this_thread::sleep_for(duration);
|
|
||||||
|
|
||||||
if (fatalCobraError) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Cleanup.
|
|
||||||
// join all the bg threads and stop them.
|
|
||||||
//
|
|
||||||
conn.disconnect();
|
|
||||||
stop = true;
|
|
||||||
|
|
||||||
// progress thread
|
|
||||||
t1.join();
|
|
||||||
|
|
||||||
// heartbeat thread
|
|
||||||
if (t2.joinable()) t2.join();
|
|
||||||
|
|
||||||
// statsd sender thread
|
|
||||||
t3.join();
|
|
||||||
|
|
||||||
return fatalCobraError ? -1 : (int) sentCount;
|
|
||||||
}
|
}
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@@ -5,23 +5,24 @@
|
|||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <ixcobra/IXCobraConfig.h>
|
#include <cstdint>
|
||||||
#include <ixbots/IXStatsdClient.h>
|
#include <ixbots/IXStatsdClient.h>
|
||||||
#include <string>
|
#include <ixcobra/IXCobraConfig.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
int cobra_to_statsd_bot(const ix::CobraConfig& config,
|
int64_t cobra_to_statsd_bot(const ix::CobraConfig& config,
|
||||||
const std::string& channel,
|
const std::string& channel,
|
||||||
const std::string& filter,
|
const std::string& filter,
|
||||||
const std::string& position,
|
const std::string& position,
|
||||||
StatsdClient& statsdClient,
|
StatsdClient& statsdClient,
|
||||||
const std::string& fields,
|
const std::string& fields,
|
||||||
const std::string& gauge,
|
const std::string& gauge,
|
||||||
const std::string& timer,
|
const std::string& timer,
|
||||||
bool verbose,
|
bool verbose,
|
||||||
size_t maxQueueSize,
|
size_t maxQueueSize,
|
||||||
bool enableHeartbeat,
|
bool enableHeartbeat,
|
||||||
int runtime);
|
int runtime);
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
107
ixbots/ixbots/IXCobraToStdoutBot.cpp
Normal file
107
ixbots/ixbots/IXCobraToStdoutBot.cpp
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* IXCobraToStdoutBot.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IXCobraToStdoutBot.h"
|
||||||
|
|
||||||
|
#include "IXCobraBot.h"
|
||||||
|
#include "IXQueueManager.h"
|
||||||
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
using StreamWriterPtr = std::unique_ptr<Json::StreamWriter>;
|
||||||
|
|
||||||
|
StreamWriterPtr makeStreamWriter()
|
||||||
|
{
|
||||||
|
Json::StreamWriterBuilder builder;
|
||||||
|
builder["commentStyle"] = "None";
|
||||||
|
builder["indentation"] = ""; // will make the JSON object compact
|
||||||
|
std::unique_ptr<Json::StreamWriter> jsonWriter(builder.newStreamWriter());
|
||||||
|
return jsonWriter;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string timeSinceEpoch()
|
||||||
|
{
|
||||||
|
std::chrono::system_clock::time_point tp = std::chrono::system_clock::now();
|
||||||
|
std::chrono::system_clock::duration dtn = tp.time_since_epoch();
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << dtn.count() * std::chrono::system_clock::period::num /
|
||||||
|
std::chrono::system_clock::period::den;
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeToStdout(bool fluentd,
|
||||||
|
const StreamWriterPtr& jsonWriter,
|
||||||
|
const Json::Value& msg,
|
||||||
|
const std::string& position)
|
||||||
|
{
|
||||||
|
Json::Value enveloppe;
|
||||||
|
if (fluentd)
|
||||||
|
{
|
||||||
|
enveloppe["producer"] = "cobra";
|
||||||
|
enveloppe["consumer"] = "fluentd";
|
||||||
|
|
||||||
|
Json::Value nestedMessage(msg);
|
||||||
|
nestedMessage["position"] = position;
|
||||||
|
nestedMessage["created_at"] = timeSinceEpoch();
|
||||||
|
enveloppe["message"] = nestedMessage;
|
||||||
|
|
||||||
|
jsonWriter->write(enveloppe, &std::cout);
|
||||||
|
std::cout << std::endl; // add lf and flush
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
enveloppe = msg;
|
||||||
|
std::cout << position << " ";
|
||||||
|
jsonWriter->write(enveloppe, &std::cout);
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t cobra_to_stdout_bot(const CobraConfig& config,
|
||||||
|
const std::string& channel,
|
||||||
|
const std::string& filter,
|
||||||
|
const std::string& position,
|
||||||
|
bool fluentd,
|
||||||
|
bool quiet,
|
||||||
|
bool verbose,
|
||||||
|
size_t maxQueueSize,
|
||||||
|
bool enableHeartbeat,
|
||||||
|
int runtime)
|
||||||
|
{
|
||||||
|
CobraBot bot;
|
||||||
|
auto jsonWriter = makeStreamWriter();
|
||||||
|
|
||||||
|
bot.setOnBotMessageCallback(
|
||||||
|
[&fluentd, &quiet, &jsonWriter](const Json::Value& msg,
|
||||||
|
const std::string& position,
|
||||||
|
const bool /*verbose*/,
|
||||||
|
std::atomic<bool>& /*throttled*/,
|
||||||
|
std::atomic<bool> &
|
||||||
|
/*fatalCobraError*/) -> bool {
|
||||||
|
if (!quiet)
|
||||||
|
{
|
||||||
|
writeToStdout(fluentd, jsonWriter, msg, position);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
bool useQueue = false;
|
||||||
|
|
||||||
|
return bot.run(config,
|
||||||
|
channel,
|
||||||
|
filter,
|
||||||
|
position,
|
||||||
|
verbose,
|
||||||
|
maxQueueSize,
|
||||||
|
useQueue,
|
||||||
|
enableHeartbeat,
|
||||||
|
runtime);
|
||||||
|
}
|
||||||
|
} // namespace ix
|
25
ixbots/ixbots/IXCobraToStdoutBot.h
Normal file
25
ixbots/ixbots/IXCobraToStdoutBot.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* IXCobraToStdoutBot.h
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <ixcobra/IXCobraConfig.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
int64_t cobra_to_stdout_bot(const ix::CobraConfig& config,
|
||||||
|
const std::string& channel,
|
||||||
|
const std::string& filter,
|
||||||
|
const std::string& position,
|
||||||
|
bool fluentd,
|
||||||
|
bool quiet,
|
||||||
|
bool verbose,
|
||||||
|
size_t maxQueueSize,
|
||||||
|
bool enableHeartbeat,
|
||||||
|
int runtime);
|
||||||
|
} // namespace ix
|
@@ -5,19 +5,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "IXQueueManager.h"
|
#include "IXQueueManager.h"
|
||||||
#include <vector>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
Json::Value QueueManager::pop()
|
std::pair<Json::Value, std::string> QueueManager::pop()
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(_mutex);
|
std::unique_lock<std::mutex> lock(_mutex);
|
||||||
|
|
||||||
if (_queues.empty())
|
if (_queues.empty())
|
||||||
{
|
{
|
||||||
Json::Value val;
|
Json::Value val;
|
||||||
return val;
|
return std::make_pair(val, std::string());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> games;
|
std::vector<std::string> games;
|
||||||
@@ -35,7 +36,7 @@ namespace ix
|
|||||||
if (_queues[game].empty())
|
if (_queues[game].empty())
|
||||||
{
|
{
|
||||||
Json::Value val;
|
Json::Value val;
|
||||||
return val;
|
return std::make_pair(val, std::string());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto msg = _queues[game].front();
|
auto msg = _queues[game].front();
|
||||||
@@ -43,7 +44,7 @@ namespace ix
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QueueManager::add(Json::Value msg)
|
void QueueManager::add(const Json::Value& msg, const std::string& position)
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(_mutex);
|
std::unique_lock<std::mutex> lock(_mutex);
|
||||||
|
|
||||||
@@ -59,8 +60,8 @@ namespace ix
|
|||||||
// in queuing too many events.
|
// in queuing too many events.
|
||||||
if (_queues[game].size() < _maxQueueSize)
|
if (_queues[game].size() < _maxQueueSize)
|
||||||
{
|
{
|
||||||
_queues[game].push(msg);
|
_queues[game].push(std::make_pair(msg, position));
|
||||||
_condition.notify_one();
|
_condition.notify_one();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} // namespace ix
|
||||||
|
@@ -6,12 +6,12 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <json/json.h>
|
|
||||||
#include <mutex>
|
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <queue>
|
#include <json/json.h>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
@@ -23,13 +23,13 @@ namespace ix
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Json::Value pop();
|
std::pair<Json::Value, std::string> pop();
|
||||||
void add(Json::Value msg);
|
void add(const Json::Value& msg, const std::string& position);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::map<std::string, std::queue<Json::Value>> _queues;
|
std::map<std::string, std::queue<std::pair<Json::Value, std::string>>> _queues;
|
||||||
std::mutex _mutex;
|
std::mutex _mutex;
|
||||||
std::condition_variable _condition;
|
std::condition_variable _condition;
|
||||||
size_t _maxQueueSize;
|
size_t _maxQueueSize;
|
||||||
};
|
};
|
||||||
}
|
} // namespace ix
|
||||||
|
@@ -39,24 +39,21 @@
|
|||||||
|
|
||||||
#include "IXStatsdClient.h"
|
#include "IXStatsdClient.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
#include <ixwebsocket/IXNetSystem.h>
|
#include <ixwebsocket/IXNetSystem.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
StatsdClient::StatsdClient(const std::string& host,
|
StatsdClient::StatsdClient(const std::string& host, int port, const std::string& prefix)
|
||||||
int port,
|
: _host(host)
|
||||||
const std::string& prefix)
|
, _port(port)
|
||||||
: _host(host)
|
, _prefix(prefix)
|
||||||
, _port(port)
|
, _stop(false)
|
||||||
, _prefix(prefix)
|
|
||||||
, _stop(false)
|
|
||||||
{
|
{
|
||||||
_thread = std::thread([this]
|
_thread = std::thread([this] {
|
||||||
{
|
|
||||||
while (!_stop)
|
while (!_stop)
|
||||||
{
|
{
|
||||||
flushQueue();
|
flushQueue();
|
||||||
@@ -119,8 +116,8 @@ namespace ix
|
|||||||
cleanup(key);
|
cleanup(key);
|
||||||
|
|
||||||
char buf[256];
|
char buf[256];
|
||||||
snprintf(buf, sizeof(buf), "%s%s:%zd|%s\n",
|
snprintf(
|
||||||
_prefix.c_str(), key.c_str(), value, type.c_str());
|
buf, sizeof(buf), "%s%s:%zd|%s\n", _prefix.c_str(), key.c_str(), value, type.c_str());
|
||||||
|
|
||||||
enqueue(buf);
|
enqueue(buf);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -142,9 +139,7 @@ namespace ix
|
|||||||
auto ret = _socket.sendto(message);
|
auto ret = _socket.sendto(message);
|
||||||
if (ret != 0)
|
if (ret != 0)
|
||||||
{
|
{
|
||||||
std::cerr << "error: "
|
std::cerr << "error: " << strerror(UdpSocket::getErrno()) << std::endl;
|
||||||
<< strerror(UdpSocket::getErrno())
|
|
||||||
<< std::endl;
|
|
||||||
}
|
}
|
||||||
_queue.pop_front();
|
_queue.pop_front();
|
||||||
}
|
}
|
||||||
|
@@ -6,21 +6,20 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <deque>
|
||||||
#include <ixwebsocket/IXUdpSocket.h>
|
#include <ixwebsocket/IXUdpSocket.h>
|
||||||
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <deque>
|
|
||||||
#include <mutex>
|
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class StatsdClient
|
class StatsdClient
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
StatsdClient(const std::string& host="127.0.0.1",
|
StatsdClient(const std::string& host = "127.0.0.1",
|
||||||
int port=8125,
|
int port = 8125,
|
||||||
const std::string& prefix = "");
|
const std::string& prefix = "");
|
||||||
~StatsdClient();
|
~StatsdClient();
|
||||||
|
|
||||||
|
@@ -14,6 +14,7 @@ set (IXCOBRA_HEADERS
|
|||||||
ixcobra/IXCobraMetricsThreadedPublisher.h
|
ixcobra/IXCobraMetricsThreadedPublisher.h
|
||||||
ixcobra/IXCobraMetricsPublisher.h
|
ixcobra/IXCobraMetricsPublisher.h
|
||||||
ixcobra/IXCobraConfig.h
|
ixcobra/IXCobraConfig.h
|
||||||
|
ixcobra/IXCobraEventType.h
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(ixcobra STATIC
|
add_library(ixcobra STATIC
|
||||||
|
@@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
|
||||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
|
@@ -5,17 +5,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "IXCobraConnection.h"
|
#include "IXCobraConnection.h"
|
||||||
#include <ixcrypto/IXHMac.h>
|
|
||||||
#include <ixwebsocket/IXWebSocket.h>
|
|
||||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <stdexcept>
|
|
||||||
#include <cmath>
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <ixcrypto/IXHMac.h>
|
||||||
|
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||||
|
#include <ixwebsocket/IXWebSocket.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
@@ -26,12 +26,12 @@ namespace ix
|
|||||||
constexpr CobraConnection::MsgId CobraConnection::kInvalidMsgId;
|
constexpr CobraConnection::MsgId CobraConnection::kInvalidMsgId;
|
||||||
constexpr int CobraConnection::kPingIntervalSecs;
|
constexpr int CobraConnection::kPingIntervalSecs;
|
||||||
|
|
||||||
CobraConnection::CobraConnection() :
|
CobraConnection::CobraConnection()
|
||||||
_webSocket(new WebSocket()),
|
: _webSocket(new WebSocket())
|
||||||
_publishMode(CobraConnection_PublishMode_Immediate),
|
, _publishMode(CobraConnection_PublishMode_Immediate)
|
||||||
_authenticated(false),
|
, _authenticated(false)
|
||||||
_eventCallback(nullptr),
|
, _eventCallback(nullptr)
|
||||||
_id(1)
|
, _id(1)
|
||||||
{
|
{
|
||||||
_pdu["action"] = "rtm/publish";
|
_pdu["action"] = "rtm/publish";
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ namespace ix
|
|||||||
_eventCallback = eventCallback;
|
_eventCallback = eventCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CobraConnection::invokeEventCallback(ix::CobraConnectionEventType eventType,
|
void CobraConnection::invokeEventCallback(ix::CobraEventType eventType,
|
||||||
const std::string& errorMsg,
|
const std::string& errorMsg,
|
||||||
const WebSocketHttpHeaders& headers,
|
const WebSocketHttpHeaders& headers,
|
||||||
const std::string& subscriptionId,
|
const std::string& subscriptionId,
|
||||||
@@ -96,7 +96,8 @@ namespace ix
|
|||||||
std::lock_guard<std::mutex> lock(_eventCallbackMutex);
|
std::lock_guard<std::mutex> lock(_eventCallbackMutex);
|
||||||
if (_eventCallback)
|
if (_eventCallback)
|
||||||
{
|
{
|
||||||
_eventCallback(eventType, errorMsg, headers, subscriptionId, msgId);
|
_eventCallback(
|
||||||
|
std::make_unique<CobraEvent>(eventType, errorMsg, headers, subscriptionId, msgId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +106,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << errorMsg << " : received pdu => " << serializedPdu;
|
ss << errorMsg << " : received pdu => " << serializedPdu;
|
||||||
invokeEventCallback(ix::CobraConnection_EventType_Error, ss.str());
|
invokeEventCallback(ix::CobraEventType::Error, ss.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CobraConnection::disconnect()
|
void CobraConnection::disconnect()
|
||||||
@@ -116,126 +117,119 @@ namespace ix
|
|||||||
|
|
||||||
void CobraConnection::initWebSocketOnMessageCallback()
|
void CobraConnection::initWebSocketOnMessageCallback()
|
||||||
{
|
{
|
||||||
_webSocket->setOnMessageCallback(
|
_webSocket->setOnMessageCallback([this](const ix::WebSocketMessagePtr& msg) {
|
||||||
[this](const ix::WebSocketMessagePtr& msg)
|
CobraConnection::invokeTrafficTrackerCallback(msg->wireSize, true);
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
{
|
{
|
||||||
CobraConnection::invokeTrafficTrackerCallback(msg->wireSize, true);
|
invokeEventCallback(ix::CobraEventType::Open, std::string(), msg->openInfo.headers);
|
||||||
|
sendHandshakeMessage();
|
||||||
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||||
|
{
|
||||||
|
_authenticated = false;
|
||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
if (msg->type == ix::WebSocketMessageType::Open)
|
ss << "Close code " << msg->closeInfo.code;
|
||||||
|
ss << " reason " << msg->closeInfo.reason;
|
||||||
|
invokeEventCallback(ix::CobraEventType::Closed, ss.str());
|
||||||
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
|
{
|
||||||
|
Json::Value data;
|
||||||
|
Json::Reader reader;
|
||||||
|
if (!reader.parse(msg->str, data))
|
||||||
{
|
{
|
||||||
invokeEventCallback(ix::CobraConnection_EventType_Open,
|
invokeErrorCallback("Invalid json", msg->str);
|
||||||
std::string(),
|
return;
|
||||||
msg->openInfo.headers);
|
|
||||||
sendHandshakeMessage();
|
|
||||||
}
|
}
|
||||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
|
||||||
{
|
|
||||||
_authenticated = false;
|
|
||||||
|
|
||||||
std::stringstream ss;
|
if (!data.isMember("action"))
|
||||||
ss << "Close code " << msg->closeInfo.code;
|
{
|
||||||
ss << " reason " << msg->closeInfo.reason;
|
invokeErrorCallback("Missing action", msg->str);
|
||||||
invokeEventCallback(ix::CobraConnection_EventType_Closed,
|
return;
|
||||||
ss.str());
|
|
||||||
}
|
}
|
||||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
|
||||||
|
auto action = data["action"].asString();
|
||||||
|
|
||||||
|
if (action == "auth/handshake/ok")
|
||||||
{
|
{
|
||||||
Json::Value data;
|
if (!handleHandshakeResponse(data))
|
||||||
Json::Reader reader;
|
|
||||||
if (!reader.parse(msg->str, data))
|
|
||||||
{
|
{
|
||||||
invokeErrorCallback("Invalid json", msg->str);
|
invokeErrorCallback("Error extracting nonce from handshake response",
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data.isMember("action"))
|
|
||||||
{
|
|
||||||
invokeErrorCallback("Missing action", msg->str);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto action = data["action"].asString();
|
|
||||||
|
|
||||||
if (action == "auth/handshake/ok")
|
|
||||||
{
|
|
||||||
if (!handleHandshakeResponse(data))
|
|
||||||
{
|
|
||||||
invokeErrorCallback("Error extracting nonce from handshake response", msg->str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (action == "auth/handshake/error")
|
|
||||||
{
|
|
||||||
invokeEventCallback(ix::CobraConnection_EventType_Handshake_Error,
|
|
||||||
msg->str);
|
msg->str);
|
||||||
}
|
}
|
||||||
else if (action == "auth/authenticate/ok")
|
|
||||||
{
|
|
||||||
_authenticated = true;
|
|
||||||
invokeEventCallback(ix::CobraConnection_EventType_Authenticated);
|
|
||||||
flushQueue();
|
|
||||||
}
|
|
||||||
else if (action == "auth/authenticate/error")
|
|
||||||
{
|
|
||||||
invokeEventCallback(ix::CobraConnection_EventType_Authentication_Error,
|
|
||||||
msg->str);
|
|
||||||
}
|
|
||||||
else if (action == "rtm/subscription/data")
|
|
||||||
{
|
|
||||||
handleSubscriptionData(data);
|
|
||||||
}
|
|
||||||
else if (action == "rtm/subscribe/ok")
|
|
||||||
{
|
|
||||||
if (!handleSubscriptionResponse(data))
|
|
||||||
{
|
|
||||||
invokeErrorCallback("Error processing subscribe response", msg->str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (action == "rtm/subscribe/error")
|
|
||||||
{
|
|
||||||
invokeEventCallback(ix::CobraConnection_EventType_Subscription_Error,
|
|
||||||
msg->str);
|
|
||||||
}
|
|
||||||
else if (action == "rtm/unsubscribe/ok")
|
|
||||||
{
|
|
||||||
if (!handleUnsubscriptionResponse(data))
|
|
||||||
{
|
|
||||||
invokeErrorCallback("Error processing unsubscribe response", msg->str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (action == "rtm/unsubscribe/error")
|
|
||||||
{
|
|
||||||
invokeErrorCallback("Unsubscription error", msg->str);
|
|
||||||
}
|
|
||||||
else if (action == "rtm/publish/ok")
|
|
||||||
{
|
|
||||||
if (!handlePublishResponse(data))
|
|
||||||
{
|
|
||||||
invokeErrorCallback("Error processing publish response", msg->str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (action == "rtm/publish/error")
|
|
||||||
{
|
|
||||||
invokeErrorCallback("Publish error", msg->str);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
invokeErrorCallback("Un-handled message type", msg->str);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (msg->type == ix::WebSocketMessageType::Error)
|
else if (action == "auth/handshake/error")
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
invokeEventCallback(ix::CobraEventType::HandshakeError, msg->str);
|
||||||
ss << "Connection error: " << msg->errorInfo.reason << std::endl;
|
|
||||||
ss << "#retries: " << msg->errorInfo.retries << std::endl;
|
|
||||||
ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl;
|
|
||||||
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
|
|
||||||
invokeErrorCallback(ss.str(), std::string());
|
|
||||||
}
|
}
|
||||||
else if (msg->type == ix::WebSocketMessageType::Pong)
|
else if (action == "auth/authenticate/ok")
|
||||||
{
|
{
|
||||||
invokeEventCallback(ix::CobraConnection_EventType_Pong);
|
_authenticated = true;
|
||||||
|
invokeEventCallback(ix::CobraEventType::Authenticated);
|
||||||
|
flushQueue();
|
||||||
}
|
}
|
||||||
|
else if (action == "auth/authenticate/error")
|
||||||
|
{
|
||||||
|
invokeEventCallback(ix::CobraEventType::AuthenticationError, msg->str);
|
||||||
|
}
|
||||||
|
else if (action == "rtm/subscription/data")
|
||||||
|
{
|
||||||
|
handleSubscriptionData(data);
|
||||||
|
}
|
||||||
|
else if (action == "rtm/subscribe/ok")
|
||||||
|
{
|
||||||
|
if (!handleSubscriptionResponse(data))
|
||||||
|
{
|
||||||
|
invokeErrorCallback("Error processing subscribe response", msg->str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (action == "rtm/subscribe/error")
|
||||||
|
{
|
||||||
|
invokeEventCallback(ix::CobraEventType::SubscriptionError, msg->str);
|
||||||
|
}
|
||||||
|
else if (action == "rtm/unsubscribe/ok")
|
||||||
|
{
|
||||||
|
if (!handleUnsubscriptionResponse(data))
|
||||||
|
{
|
||||||
|
invokeErrorCallback("Error processing unsubscribe response", msg->str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (action == "rtm/unsubscribe/error")
|
||||||
|
{
|
||||||
|
invokeErrorCallback("Unsubscription error", msg->str);
|
||||||
|
}
|
||||||
|
else if (action == "rtm/publish/ok")
|
||||||
|
{
|
||||||
|
if (!handlePublishResponse(data))
|
||||||
|
{
|
||||||
|
invokeErrorCallback("Error processing publish response", msg->str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (action == "rtm/publish/error")
|
||||||
|
{
|
||||||
|
invokeErrorCallback("Publish error", msg->str);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
invokeErrorCallback("Un-handled message type", msg->str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Error)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "Connection error: " << msg->errorInfo.reason << std::endl;
|
||||||
|
ss << "#retries: " << msg->errorInfo.retries << std::endl;
|
||||||
|
ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl;
|
||||||
|
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
|
||||||
|
invokeErrorCallback(ss.str(), std::string());
|
||||||
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Pong)
|
||||||
|
{
|
||||||
|
invokeEventCallback(ix::CobraEventType::Pong, msg->str);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,12 +243,13 @@ namespace ix
|
|||||||
return _publishMode;
|
return _publishMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CobraConnection::configure(const std::string& appkey,
|
void CobraConnection::configure(
|
||||||
const std::string& endpoint,
|
const std::string& appkey,
|
||||||
const std::string& rolename,
|
const std::string& endpoint,
|
||||||
const std::string& rolesecret,
|
const std::string& rolename,
|
||||||
const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions,
|
const std::string& rolesecret,
|
||||||
const SocketTLSOptions& socketTLSOptions)
|
const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions,
|
||||||
|
const SocketTLSOptions& socketTLSOptions)
|
||||||
{
|
{
|
||||||
_roleName = rolename;
|
_roleName = rolename;
|
||||||
_roleSecret = rolesecret;
|
_roleSecret = rolesecret;
|
||||||
@@ -396,8 +391,9 @@ namespace ix
|
|||||||
|
|
||||||
if (!subscriptionId.isString()) return false;
|
if (!subscriptionId.isString()) return false;
|
||||||
|
|
||||||
invokeEventCallback(ix::CobraConnection_EventType_Subscribed,
|
invokeEventCallback(ix::CobraEventType::Subscribed,
|
||||||
std::string(), WebSocketHttpHeaders(),
|
std::string(),
|
||||||
|
WebSocketHttpHeaders(),
|
||||||
subscriptionId.asString());
|
subscriptionId.asString());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -414,8 +410,9 @@ namespace ix
|
|||||||
|
|
||||||
if (!subscriptionId.isString()) return false;
|
if (!subscriptionId.isString()) return false;
|
||||||
|
|
||||||
invokeEventCallback(ix::CobraConnection_EventType_UnSubscribed,
|
invokeEventCallback(ix::CobraEventType::UnSubscribed,
|
||||||
std::string(), WebSocketHttpHeaders(),
|
std::string(),
|
||||||
|
WebSocketHttpHeaders(),
|
||||||
subscriptionId.asString());
|
subscriptionId.asString());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -462,9 +459,11 @@ namespace ix
|
|||||||
|
|
||||||
uint64_t msgId = id.asUInt64();
|
uint64_t msgId = id.asUInt64();
|
||||||
|
|
||||||
invokeEventCallback(ix::CobraConnection_EventType_Published,
|
invokeEventCallback(ix::CobraEventType::Published,
|
||||||
std::string(), WebSocketHttpHeaders(),
|
std::string(),
|
||||||
std::string(), msgId);
|
WebSocketHttpHeaders(),
|
||||||
|
std::string(),
|
||||||
|
msgId);
|
||||||
|
|
||||||
invokePublishTrackerCallback(false, true);
|
invokePublishTrackerCallback(false, true);
|
||||||
|
|
||||||
@@ -494,9 +493,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::pair<CobraConnection::MsgId, std::string> CobraConnection::prePublish(
|
std::pair<CobraConnection::MsgId, std::string> CobraConnection::prePublish(
|
||||||
const Json::Value& channels,
|
const Json::Value& channels, const Json::Value& msg, bool addToQueue)
|
||||||
const Json::Value& msg,
|
|
||||||
bool addToQueue)
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_prePublishMutex);
|
std::lock_guard<std::mutex> lock(_prePublishMutex);
|
||||||
|
|
||||||
@@ -662,8 +659,7 @@ namespace ix
|
|||||||
bool CobraConnection::publishMessage(const std::string& serializedJson)
|
bool CobraConnection::publishMessage(const std::string& serializedJson)
|
||||||
{
|
{
|
||||||
auto webSocketSendInfo = _webSocket->send(serializedJson);
|
auto webSocketSendInfo = _webSocket->send(serializedJson);
|
||||||
CobraConnection::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize,
|
CobraConnection::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false);
|
||||||
false);
|
|
||||||
return webSocketSendInfo.success;
|
return webSocketSendInfo.success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,18 +6,19 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "IXCobraConfig.h"
|
||||||
|
#include "IXCobraEvent.h"
|
||||||
|
#include "IXCobraEventType.h"
|
||||||
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||||
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
||||||
#include <json/json.h>
|
#include <json/json.h>
|
||||||
|
#include <limits>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <limits>
|
|
||||||
|
|
||||||
#include "IXCobraConfig.h"
|
|
||||||
|
|
||||||
#ifdef max
|
#ifdef max
|
||||||
#undef max
|
#undef max
|
||||||
@@ -28,21 +29,6 @@ namespace ix
|
|||||||
class WebSocket;
|
class WebSocket;
|
||||||
struct SocketTLSOptions;
|
struct SocketTLSOptions;
|
||||||
|
|
||||||
enum CobraConnectionEventType
|
|
||||||
{
|
|
||||||
CobraConnection_EventType_Authenticated = 0,
|
|
||||||
CobraConnection_EventType_Error = 1,
|
|
||||||
CobraConnection_EventType_Open = 2,
|
|
||||||
CobraConnection_EventType_Closed = 3,
|
|
||||||
CobraConnection_EventType_Subscribed = 4,
|
|
||||||
CobraConnection_EventType_UnSubscribed = 5,
|
|
||||||
CobraConnection_EventType_Published = 6,
|
|
||||||
CobraConnection_EventType_Pong = 7,
|
|
||||||
CobraConnection_EventType_Handshake_Error = 8,
|
|
||||||
CobraConnection_EventType_Authentication_Error = 9,
|
|
||||||
CobraConnection_EventType_Subscription_Error = 10
|
|
||||||
};
|
|
||||||
|
|
||||||
enum CobraConnectionPublishMode
|
enum CobraConnectionPublishMode
|
||||||
{
|
{
|
||||||
CobraConnection_PublishMode_Immediate = 0,
|
CobraConnection_PublishMode_Immediate = 0,
|
||||||
@@ -50,11 +36,7 @@ namespace ix
|
|||||||
};
|
};
|
||||||
|
|
||||||
using SubscriptionCallback = std::function<void(const Json::Value&, const std::string&)>;
|
using SubscriptionCallback = std::function<void(const Json::Value&, const std::string&)>;
|
||||||
using EventCallback = std::function<void(CobraConnectionEventType,
|
using EventCallback = std::function<void(const CobraEventPtr&)>;
|
||||||
const std::string&,
|
|
||||||
const WebSocketHttpHeaders&,
|
|
||||||
const std::string&,
|
|
||||||
uint64_t msgId)>;
|
|
||||||
|
|
||||||
using TrafficTrackerCallback = std::function<void(size_t size, bool incoming)>;
|
using TrafficTrackerCallback = std::function<void(size_t size, bool incoming)>;
|
||||||
using PublishTrackerCallback = std::function<void(bool sent, bool acked)>;
|
using PublishTrackerCallback = std::function<void(bool sent, bool acked)>;
|
||||||
@@ -138,10 +120,9 @@ namespace ix
|
|||||||
|
|
||||||
/// Prepare a message for transmission
|
/// Prepare a message for transmission
|
||||||
/// (update the pdu, compute a msgId, serialize json to a string)
|
/// (update the pdu, compute a msgId, serialize json to a string)
|
||||||
std::pair<CobraConnection::MsgId, std::string> prePublish(
|
std::pair<CobraConnection::MsgId, std::string> prePublish(const Json::Value& channels,
|
||||||
const Json::Value& channels,
|
const Json::Value& msg,
|
||||||
const Json::Value& msg,
|
bool addToQueue);
|
||||||
bool addToQueue);
|
|
||||||
|
|
||||||
/// Attempt to send next message from the internal queue
|
/// Attempt to send next message from the internal queue
|
||||||
bool publishNext();
|
bool publishNext();
|
||||||
@@ -171,7 +152,7 @@ namespace ix
|
|||||||
static void invokePublishTrackerCallback(bool sent, bool acked);
|
static void invokePublishTrackerCallback(bool sent, bool acked);
|
||||||
|
|
||||||
/// Invoke event callbacks
|
/// Invoke event callbacks
|
||||||
void invokeEventCallback(CobraConnectionEventType eventType,
|
void invokeEventCallback(CobraEventType eventType,
|
||||||
const std::string& errorMsg = std::string(),
|
const std::string& errorMsg = std::string(),
|
||||||
const WebSocketHttpHeaders& headers = WebSocketHttpHeaders(),
|
const WebSocketHttpHeaders& headers = WebSocketHttpHeaders(),
|
||||||
const std::string& subscriptionId = std::string(),
|
const std::string& subscriptionId = std::string(),
|
||||||
|
41
ixcobra/ixcobra/IXCobraEvent.h
Normal file
41
ixcobra/ixcobra/IXCobraEvent.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* IXCobraEvent.h
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IXCobraEventType.h"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
struct CobraEvent
|
||||||
|
{
|
||||||
|
ix::CobraEventType type;
|
||||||
|
const std::string& errMsg;
|
||||||
|
const ix::WebSocketHttpHeaders& headers;
|
||||||
|
const std::string& subscriptionId;
|
||||||
|
uint64_t msgId; // CobraConnection::MsgId
|
||||||
|
|
||||||
|
CobraEvent(ix::CobraEventType t,
|
||||||
|
const std::string& e,
|
||||||
|
const ix::WebSocketHttpHeaders& h,
|
||||||
|
const std::string& s,
|
||||||
|
uint64_t m)
|
||||||
|
: type(t)
|
||||||
|
, errMsg(e)
|
||||||
|
, headers(h)
|
||||||
|
, subscriptionId(s)
|
||||||
|
, msgId(m)
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using CobraEventPtr = std::unique_ptr<CobraEvent>;
|
||||||
|
} // namespace ix
|
25
ixcobra/ixcobra/IXCobraEventType.h
Normal file
25
ixcobra/ixcobra/IXCobraEventType.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* IXCobraEventType.h
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
enum class CobraEventType
|
||||||
|
{
|
||||||
|
Authenticated = 0,
|
||||||
|
Error = 1,
|
||||||
|
Open = 2,
|
||||||
|
Closed = 3,
|
||||||
|
Subscribed = 4,
|
||||||
|
UnSubscribed = 5,
|
||||||
|
Published = 6,
|
||||||
|
Pong = 7,
|
||||||
|
HandshakeError = 8,
|
||||||
|
AuthenticationError = 9,
|
||||||
|
SubscriptionError = 10
|
||||||
|
};
|
||||||
|
}
|
@@ -5,9 +5,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "IXCobraMetricsPublisher.h"
|
#include "IXCobraMetricsPublisher.h"
|
||||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
|
|
||||||
@@ -17,8 +17,8 @@ namespace ix
|
|||||||
const std::string CobraMetricsPublisher::kSetRateControlId = "sms_set_rate_control_id";
|
const std::string CobraMetricsPublisher::kSetRateControlId = "sms_set_rate_control_id";
|
||||||
const std::string CobraMetricsPublisher::kSetBlacklistId = "sms_set_blacklist_id";
|
const std::string CobraMetricsPublisher::kSetBlacklistId = "sms_set_blacklist_id";
|
||||||
|
|
||||||
CobraMetricsPublisher::CobraMetricsPublisher() :
|
CobraMetricsPublisher::CobraMetricsPublisher()
|
||||||
_enabled(true)
|
: _enabled(true)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,8 +27,7 @@ namespace ix
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CobraMetricsPublisher::configure(const CobraConfig& config,
|
void CobraMetricsPublisher::configure(const CobraConfig& config, const std::string& channel)
|
||||||
const std::string& channel)
|
|
||||||
{
|
{
|
||||||
// Configure the satori connection and start its publish background thread
|
// Configure the satori connection and start its publish background thread
|
||||||
_cobra_metrics_theaded_publisher.configure(config, channel);
|
_cobra_metrics_theaded_publisher.configure(config, channel);
|
||||||
@@ -42,7 +41,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CobraMetricsPublisher::setGenericAttributes(const std::string& attrName,
|
void CobraMetricsPublisher::setGenericAttributes(const std::string& attrName,
|
||||||
const Json::Value& value)
|
const Json::Value& value)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_device_mutex);
|
std::lock_guard<std::mutex> lock(_device_mutex);
|
||||||
_device[attrName] = value;
|
_device[attrName] = value;
|
||||||
@@ -107,8 +106,7 @@ namespace ix
|
|||||||
auto last_update = _last_update.find(id);
|
auto last_update = _last_update.find(id);
|
||||||
if (last_update == _last_update.end()) return false;
|
if (last_update == _last_update.end()) return false;
|
||||||
|
|
||||||
auto timeDeltaFromLastSend =
|
auto timeDeltaFromLastSend = std::chrono::steady_clock::now() - last_update->second;
|
||||||
std::chrono::steady_clock::now() - last_update->second;
|
|
||||||
|
|
||||||
return timeDeltaFromLastSend < std::chrono::seconds(rate_control_it->second);
|
return timeDeltaFromLastSend < std::chrono::seconds(rate_control_it->second);
|
||||||
}
|
}
|
||||||
@@ -123,8 +121,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
auto now = std::chrono::system_clock::now();
|
auto now = std::chrono::system_clock::now();
|
||||||
auto ms =
|
auto ms =
|
||||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
|
||||||
now.time_since_epoch()).count();
|
|
||||||
|
|
||||||
return ms;
|
return ms;
|
||||||
}
|
}
|
||||||
@@ -165,10 +162,9 @@ namespace ix
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
CobraConnection::MsgId CobraMetricsPublisher::push(
|
CobraConnection::MsgId CobraMetricsPublisher::push(const std::string& id,
|
||||||
const std::string& id,
|
const Json::Value& data,
|
||||||
const Json::Value& data,
|
bool shouldPushTest)
|
||||||
bool shouldPushTest)
|
|
||||||
{
|
{
|
||||||
if (shouldPushTest && !shouldPush(id)) return CobraConnection::kInvalidMsgId;
|
if (shouldPushTest && !shouldPush(id)) return CobraConnection::kInvalidMsgId;
|
||||||
|
|
||||||
|
@@ -40,8 +40,7 @@ namespace ix
|
|||||||
|
|
||||||
/// Configuration / set keys, etc...
|
/// Configuration / set keys, etc...
|
||||||
/// All input data but the channel name is encrypted with rc4
|
/// All input data but the channel name is encrypted with rc4
|
||||||
void configure(const CobraConfig& config,
|
void configure(const CobraConfig& config, const std::string& channel);
|
||||||
const std::string& channel);
|
|
||||||
|
|
||||||
/// Setter for the list of blacklisted metrics ids.
|
/// Setter for the list of blacklisted metrics ids.
|
||||||
/// That list is sorted internally for fast lookups
|
/// That list is sorted internally for fast lookups
|
||||||
@@ -68,10 +67,14 @@ namespace ix
|
|||||||
/// shouldPush method for places where we want to be as lightweight as possible when
|
/// shouldPush method for places where we want to be as lightweight as possible when
|
||||||
/// collecting metrics. When set to false, it is used so that we don't do double work when
|
/// collecting metrics. When set to false, it is used so that we don't do double work when
|
||||||
/// computing whether a metrics should be sent or not.
|
/// computing whether a metrics should be sent or not.
|
||||||
CobraConnection::MsgId push(const std::string& id, const Json::Value& data, bool shouldPushTest = true);
|
CobraConnection::MsgId push(const std::string& id,
|
||||||
|
const Json::Value& data,
|
||||||
|
bool shouldPushTest = true);
|
||||||
|
|
||||||
/// Interface used by lua. msg is a json encoded string.
|
/// Interface used by lua. msg is a json encoded string.
|
||||||
CobraConnection::MsgId push(const std::string& id, const std::string& data, bool shouldPushTest = true);
|
CobraConnection::MsgId push(const std::string& id,
|
||||||
|
const std::string& data,
|
||||||
|
bool shouldPushTest = true);
|
||||||
|
|
||||||
/// Tells whether a metric can be pushed.
|
/// Tells whether a metric can be pushed.
|
||||||
/// A metric can be pushed if it satisfies those conditions:
|
/// A metric can be pushed if it satisfies those conditions:
|
||||||
@@ -89,10 +92,16 @@ namespace ix
|
|||||||
void setGenericAttributes(const std::string& attrName, const Json::Value& value);
|
void setGenericAttributes(const std::string& attrName, const Json::Value& value);
|
||||||
|
|
||||||
/// Set a unique id for the session. A uuid can be used.
|
/// Set a unique id for the session. A uuid can be used.
|
||||||
void setSession(const std::string& session) { _session = session; }
|
void setSession(const std::string& session)
|
||||||
|
{
|
||||||
|
_session = session;
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the unique id used to identify the current session
|
/// Get the unique id used to identify the current session
|
||||||
const std::string& getSession() const { return _session; }
|
const std::string& getSession() const
|
||||||
|
{
|
||||||
|
return _session;
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the number of milliseconds since the epoch (~1970)
|
/// Return the number of milliseconds since the epoch (~1970)
|
||||||
uint64_t getMillisecondsSinceEpoch() const;
|
uint64_t getMillisecondsSinceEpoch() const;
|
||||||
|
@@ -5,72 +5,77 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "IXCobraMetricsThreadedPublisher.h"
|
#include "IXCobraMetricsThreadedPublisher.h"
|
||||||
#include <ixwebsocket/IXSetThreadName.h>
|
|
||||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
|
||||||
#include <ixcore/utils/IXCoreLogger.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <stdexcept>
|
|
||||||
#include <cmath>
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <ixcore/utils/IXCoreLogger.h>
|
||||||
|
#include <ixwebsocket/IXSetThreadName.h>
|
||||||
|
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
CobraMetricsThreadedPublisher::CobraMetricsThreadedPublisher() :
|
CobraMetricsThreadedPublisher::CobraMetricsThreadedPublisher()
|
||||||
_stop(false)
|
: _stop(false)
|
||||||
{
|
{
|
||||||
_cobra_connection.setEventCallback(
|
_cobra_connection.setEventCallback([](const CobraEventPtr& event) {
|
||||||
[]
|
std::stringstream ss;
|
||||||
(ix::CobraConnectionEventType eventType,
|
|
||||||
const std::string& errMsg,
|
if (event->type == ix::CobraEventType::Open)
|
||||||
const ix::WebSocketHttpHeaders& headers,
|
|
||||||
const std::string& subscriptionId,
|
|
||||||
CobraConnection::MsgId msgId)
|
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
ss << "Handshake headers" << std::endl;
|
||||||
|
|
||||||
if (eventType == ix::CobraConnection_EventType_Open)
|
for (auto&& it : event->headers)
|
||||||
{
|
{
|
||||||
ss << "Handshake headers" << std::endl;
|
ss << it.first << ": " << it.second << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (event->type == ix::CobraEventType::Authenticated)
|
||||||
|
{
|
||||||
|
ss << "Authenticated";
|
||||||
|
}
|
||||||
|
else if (event->type == ix::CobraEventType::Error)
|
||||||
|
{
|
||||||
|
ss << "Error: " << event->errMsg;
|
||||||
|
}
|
||||||
|
else if (event->type == ix::CobraEventType::Closed)
|
||||||
|
{
|
||||||
|
ss << "Connection closed: " << event->errMsg;
|
||||||
|
}
|
||||||
|
else if (event->type == ix::CobraEventType::Subscribed)
|
||||||
|
{
|
||||||
|
ss << "Subscribed through subscription id: " << event->subscriptionId;
|
||||||
|
}
|
||||||
|
else if (event->type == ix::CobraEventType::UnSubscribed)
|
||||||
|
{
|
||||||
|
ss << "Unsubscribed through subscription id: " << event->subscriptionId;
|
||||||
|
}
|
||||||
|
else if (event->type == ix::CobraEventType::Published)
|
||||||
|
{
|
||||||
|
ss << "Published message " << event->msgId << " acked";
|
||||||
|
}
|
||||||
|
else if (event->type == ix::CobraEventType::Pong)
|
||||||
|
{
|
||||||
|
ss << "Received websocket pong";
|
||||||
|
}
|
||||||
|
else if (event->type == ix::CobraEventType::HandshakeError)
|
||||||
|
{
|
||||||
|
ss << "Handshake error: " << event->errMsg;
|
||||||
|
}
|
||||||
|
else if (event->type == ix::CobraEventType::AuthenticationError)
|
||||||
|
{
|
||||||
|
ss << "Authentication error: " << event->errMsg;
|
||||||
|
}
|
||||||
|
else if (event->type == ix::CobraEventType::SubscriptionError)
|
||||||
|
{
|
||||||
|
ss << "Subscription error: " << event->errMsg;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto it : headers)
|
CoreLogger::log(ss.str().c_str());
|
||||||
{
|
|
||||||
ss << it.first << ": " << it.second << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_Authenticated)
|
|
||||||
{
|
|
||||||
ss << "Authenticated";
|
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_Error)
|
|
||||||
{
|
|
||||||
ss << "Error: " << errMsg;
|
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_Closed)
|
|
||||||
{
|
|
||||||
ss << "Connection closed: " << errMsg;
|
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_Subscribed)
|
|
||||||
{
|
|
||||||
ss << "Subscribed through subscription id: " << subscriptionId;
|
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_UnSubscribed)
|
|
||||||
{
|
|
||||||
ss << "Unsubscribed through subscription id: " << subscriptionId;
|
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_Published)
|
|
||||||
{
|
|
||||||
ss << "Published message " << msgId << " acked";
|
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_Pong)
|
|
||||||
{
|
|
||||||
ss << "Received websocket pong";
|
|
||||||
}
|
|
||||||
|
|
||||||
ix::IXCoreLogger::Log(ss.str().c_str());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,11 +100,10 @@ namespace ix
|
|||||||
void CobraMetricsThreadedPublisher::configure(const CobraConfig& config,
|
void CobraMetricsThreadedPublisher::configure(const CobraConfig& config,
|
||||||
const std::string& channel)
|
const std::string& channel)
|
||||||
{
|
{
|
||||||
ix::IXCoreLogger::Log(config.socketTLSOptions.getDescription().c_str());
|
CoreLogger::log(config.socketTLSOptions.getDescription().c_str());
|
||||||
|
|
||||||
_channel = channel;
|
_channel = channel;
|
||||||
_cobra_connection.configure(config);
|
_cobra_connection.configure(config);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CobraMetricsThreadedPublisher::pushMessage(MessageKind messageKind)
|
void CobraMetricsThreadedPublisher::pushMessage(MessageKind messageKind)
|
||||||
@@ -157,13 +161,15 @@ namespace ix
|
|||||||
{
|
{
|
||||||
_cobra_connection.suspend();
|
_cobra_connection.suspend();
|
||||||
continue;
|
continue;
|
||||||
}; break;
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
case MessageKind::Resume:
|
case MessageKind::Resume:
|
||||||
{
|
{
|
||||||
_cobra_connection.resume();
|
_cobra_connection.resume();
|
||||||
continue;
|
continue;
|
||||||
}; break;
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
case MessageKind::Message:
|
case MessageKind::Message:
|
||||||
{
|
{
|
||||||
@@ -171,7 +177,8 @@ namespace ix
|
|||||||
{
|
{
|
||||||
_cobra_connection.publishNext();
|
_cobra_connection.publishNext();
|
||||||
}
|
}
|
||||||
}; break;
|
};
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,8 +27,7 @@ namespace ix
|
|||||||
~CobraMetricsThreadedPublisher();
|
~CobraMetricsThreadedPublisher();
|
||||||
|
|
||||||
/// Configuration / set keys, etc...
|
/// Configuration / set keys, etc...
|
||||||
void configure(const CobraConfig& config,
|
void configure(const CobraConfig& config, const std::string& channel);
|
||||||
const std::string& channel);
|
|
||||||
|
|
||||||
/// Start the worker thread, used for background publishing
|
/// Start the worker thread, used for background publishing
|
||||||
void start();
|
void start();
|
||||||
|
@@ -1,14 +1,44 @@
|
|||||||
#include "ixcore/utils/IXCoreLogger.h"
|
/*
|
||||||
|
* IXCoreLogger.cpp
|
||||||
|
* Author: Thomas Wells, Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ixcore/utils/IXCoreLogger.h"
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
// Default do nothing logger
|
// Default do a no-op logger
|
||||||
IXCoreLogger::LogFunc IXCoreLogger::_currentLogger = [](const char* /*msg*/){};
|
CoreLogger::LogFunc CoreLogger::_currentLogger = [](const char*, LogLevel) {};
|
||||||
|
|
||||||
void IXCoreLogger::Log(const char* msg)
|
void CoreLogger::log(const char* msg, LogLevel level)
|
||||||
{
|
{
|
||||||
_currentLogger(msg);
|
_currentLogger(msg, level);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // ix
|
void CoreLogger::debug(const std::string& msg)
|
||||||
|
{
|
||||||
|
_currentLogger(msg.c_str(), LogLevel::Debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreLogger::info(const std::string& msg)
|
||||||
|
{
|
||||||
|
_currentLogger(msg.c_str(), LogLevel::Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreLogger::warn(const std::string& msg)
|
||||||
|
{
|
||||||
|
_currentLogger(msg.c_str(), LogLevel::Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreLogger::error(const std::string& msg)
|
||||||
|
{
|
||||||
|
_currentLogger(msg.c_str(), LogLevel::Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreLogger::critical(const std::string& msg)
|
||||||
|
{
|
||||||
|
_currentLogger(msg.c_str(), LogLevel::Critical);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ix
|
||||||
|
@@ -1,15 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* IXCoreLogger.h
|
||||||
|
* Author: Thomas Wells, Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class IXCoreLogger
|
enum class LogLevel
|
||||||
|
{
|
||||||
|
Debug = 0,
|
||||||
|
Info = 1,
|
||||||
|
Warning = 2,
|
||||||
|
Error = 3,
|
||||||
|
Critical = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
class CoreLogger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using LogFunc = std::function<void(const char*)>;
|
using LogFunc = std::function<void(const char*, LogLevel level)>;
|
||||||
static void Log(const char* msg);
|
|
||||||
|
|
||||||
static void setLogFunction(LogFunc& func) { _currentLogger = func; }
|
static void log(const char* msg, LogLevel level = LogLevel::Debug);
|
||||||
|
|
||||||
|
static void debug(const std::string& msg);
|
||||||
|
static void info(const std::string& msg);
|
||||||
|
static void warn(const std::string& msg);
|
||||||
|
static void error(const std::string& msg);
|
||||||
|
static void critical(const std::string& msg);
|
||||||
|
|
||||||
|
static void setLogFunction(LogFunc& func)
|
||||||
|
{
|
||||||
|
_currentLogger = func;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static LogFunc _currentLogger;
|
static LogFunc _currentLogger;
|
||||||
|
@@ -29,10 +29,9 @@
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
static const std::string base64_chars =
|
static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
"abcdefghijklmnopqrstuvwxyz"
|
||||||
"abcdefghijklmnopqrstuvwxyz"
|
"0123456789+/";
|
||||||
"0123456789+/";
|
|
||||||
|
|
||||||
std::string base64_encode(const std::string& data, size_t len)
|
std::string base64_encode(const std::string& data, size_t len)
|
||||||
{
|
{
|
||||||
@@ -50,26 +49,26 @@ namespace ix
|
|||||||
unsigned char char_array_3[3];
|
unsigned char char_array_3[3];
|
||||||
unsigned char char_array_4[4];
|
unsigned char char_array_4[4];
|
||||||
|
|
||||||
while(len--)
|
while (len--)
|
||||||
{
|
{
|
||||||
char_array_3[i++] = *(bytes_to_encode++);
|
char_array_3[i++] = *(bytes_to_encode++);
|
||||||
if(i == 3)
|
if (i == 3)
|
||||||
{
|
{
|
||||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||||
|
|
||||||
for(i = 0; (i <4) ; i++)
|
for (i = 0; (i < 4); i++)
|
||||||
ret += base64_chars[char_array_4[i]];
|
ret += base64_chars[char_array_4[i]];
|
||||||
|
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(i)
|
if (i)
|
||||||
{
|
{
|
||||||
for(j = i; j < 3; j++)
|
for (j = i; j < 3; j++)
|
||||||
char_array_3[j] = '\0';
|
char_array_3[j] = '\0';
|
||||||
|
|
||||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||||
@@ -77,12 +76,11 @@ namespace ix
|
|||||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||||
|
|
||||||
for(j = 0; (j < i + 1); j++)
|
for (j = 0; (j < i + 1); j++)
|
||||||
ret += base64_chars[char_array_4[j]];
|
ret += base64_chars[char_array_4[j]];
|
||||||
|
|
||||||
while((i++ < 3))
|
while ((i++ < 3))
|
||||||
ret += '=';
|
ret += '=';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@@ -95,7 +93,7 @@ namespace ix
|
|||||||
|
|
||||||
std::string base64_decode(const std::string& encoded_string)
|
std::string base64_decode(const std::string& encoded_string)
|
||||||
{
|
{
|
||||||
int in_len = (int)encoded_string.size();
|
int in_len = (int) encoded_string.size();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
int j = 0;
|
int j = 0;
|
||||||
int in_ = 0;
|
int in_ = 0;
|
||||||
@@ -103,40 +101,42 @@ namespace ix
|
|||||||
std::string ret;
|
std::string ret;
|
||||||
ret.reserve(((in_len + 3) / 4) * 3);
|
ret.reserve(((in_len + 3) / 4) * 3);
|
||||||
|
|
||||||
while(in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_]))
|
while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_]))
|
||||||
{
|
{
|
||||||
char_array_4[i++] = encoded_string[in_]; in_++;
|
char_array_4[i++] = encoded_string[in_];
|
||||||
if(i ==4)
|
in_++;
|
||||||
|
if (i == 4)
|
||||||
{
|
{
|
||||||
for(i = 0; i <4; i++)
|
for (i = 0; i < 4; i++)
|
||||||
char_array_4[i] = base64_chars.find(char_array_4[i]);
|
char_array_4[i] = base64_chars.find(char_array_4[i]);
|
||||||
|
|
||||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||||
|
|
||||||
for(i = 0; (i < 3); i++)
|
for (i = 0; (i < 3); i++)
|
||||||
ret += char_array_3[i];
|
ret += char_array_3[i];
|
||||||
|
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(i)
|
if (i)
|
||||||
{
|
{
|
||||||
for(j = i; j <4; j++)
|
for (j = i; j < 4; j++)
|
||||||
char_array_4[j] = 0;
|
char_array_4[j] = 0;
|
||||||
|
|
||||||
for(j = 0; j <4; j++)
|
for (j = 0; j < 4; j++)
|
||||||
char_array_4[j] = base64_chars.find(char_array_4[j]);
|
char_array_4[j] = base64_chars.find(char_array_4[j]);
|
||||||
|
|
||||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||||
|
|
||||||
for(j = 0; (j < i - 1); j++) ret += char_array_3[j];
|
for (j = 0; (j < i - 1); j++)
|
||||||
|
ret += char_array_3[j];
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
} // namespace ix
|
||||||
|
@@ -5,16 +5,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "IXHMac.h"
|
#include "IXHMac.h"
|
||||||
|
|
||||||
#include "IXBase64.h"
|
#include "IXBase64.h"
|
||||||
|
|
||||||
#if defined(IXCRYPTO_USE_MBED_TLS)
|
#if defined(IXCRYPTO_USE_MBED_TLS)
|
||||||
# include <mbedtls/md.h>
|
#include <mbedtls/md.h>
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
# include <CommonCrypto/CommonHMAC.h>
|
#include <CommonCrypto/CommonHMAC.h>
|
||||||
#elif defined(IXCRYPTO_USE_OPEN_SSL)
|
#elif defined(IXCRYPTO_USE_OPEN_SSL)
|
||||||
# include <openssl/hmac.h>
|
#include <openssl/hmac.h>
|
||||||
#else
|
#else
|
||||||
# include <assert.h>
|
#include <assert.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
@@ -26,19 +27,21 @@ namespace ix
|
|||||||
|
|
||||||
#if defined(IXCRYPTO_USE_MBED_TLS)
|
#if defined(IXCRYPTO_USE_MBED_TLS)
|
||||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_MD5),
|
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_MD5),
|
||||||
(unsigned char *) key.c_str(), key.size(),
|
(unsigned char*) key.c_str(),
|
||||||
(unsigned char *) data.c_str(), data.size(),
|
key.size(),
|
||||||
(unsigned char *) &hash);
|
(unsigned char*) data.c_str(),
|
||||||
|
data.size(),
|
||||||
|
(unsigned char*) &hash);
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
CCHmac(kCCHmacAlgMD5,
|
CCHmac(kCCHmacAlgMD5, key.c_str(), key.size(), data.c_str(), data.size(), &hash);
|
||||||
key.c_str(), key.size(),
|
|
||||||
data.c_str(), data.size(),
|
|
||||||
&hash);
|
|
||||||
#elif defined(IXCRYPTO_USE_OPEN_SSL)
|
#elif defined(IXCRYPTO_USE_OPEN_SSL)
|
||||||
HMAC(EVP_md5(),
|
HMAC(EVP_md5(),
|
||||||
key.c_str(), (int) key.size(),
|
key.c_str(),
|
||||||
(unsigned char *) data.c_str(), (int) data.size(),
|
(int) key.size(),
|
||||||
(unsigned char *) hash, nullptr);
|
(unsigned char*) data.c_str(),
|
||||||
|
(int) data.size(),
|
||||||
|
(unsigned char*) hash,
|
||||||
|
nullptr);
|
||||||
#else
|
#else
|
||||||
assert(false && "hmac not implemented on this platform");
|
assert(false && "hmac not implemented on this platform");
|
||||||
#endif
|
#endif
|
||||||
@@ -47,4 +50,4 @@ namespace ix
|
|||||||
|
|
||||||
return base64_encode(hashString, (uint32_t) hashString.size());
|
return base64_encode(hashString, (uint32_t) hashString.size());
|
||||||
}
|
}
|
||||||
}
|
} // namespace ix
|
||||||
|
@@ -19,4 +19,4 @@ namespace ix
|
|||||||
|
|
||||||
return hashAddress;
|
return hashAddress;
|
||||||
}
|
}
|
||||||
}
|
} // namespace ix
|
||||||
|
@@ -16,23 +16,23 @@
|
|||||||
|
|
||||||
#include "IXUuid.h"
|
#include "IXUuid.h"
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
#include <string>
|
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class Uuid
|
class Uuid
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Uuid();
|
Uuid();
|
||||||
std::string toString() const;
|
std::string toString() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint64_t _ab;
|
uint64_t _ab;
|
||||||
uint64_t _cd;
|
uint64_t _cd;
|
||||||
};
|
};
|
||||||
|
|
||||||
Uuid::Uuid()
|
Uuid::Uuid()
|
||||||
@@ -60,7 +60,7 @@ namespace ix
|
|||||||
ss << std::setw(8) << (a);
|
ss << std::setw(8) << (a);
|
||||||
ss << std::setw(4) << (b >> 16);
|
ss << std::setw(4) << (b >> 16);
|
||||||
ss << std::setw(4) << (b & 0xFFFF);
|
ss << std::setw(4) << (b & 0xFFFF);
|
||||||
ss << std::setw(4) << (c >> 16 );
|
ss << std::setw(4) << (c >> 16);
|
||||||
ss << std::setw(4) << (c & 0xFFFF);
|
ss << std::setw(4) << (c & 0xFFFF);
|
||||||
ss << std::setw(8) << d;
|
ss << std::setw(8) << d;
|
||||||
|
|
||||||
@@ -72,4 +72,4 @@ namespace ix
|
|||||||
Uuid id;
|
Uuid id;
|
||||||
return id.toString();
|
return id.toString();
|
||||||
}
|
}
|
||||||
}
|
} // namespace ix
|
||||||
|
@@ -7,12 +7,12 @@
|
|||||||
#include "IXSentryClient.h"
|
#include "IXSentryClient.h"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <iostream>
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <iostream>
|
||||||
|
#include <ixcore/utils/IXCoreLogger.h>
|
||||||
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||||
#include <ixwebsocket/IXWebSocketVersion.h>
|
#include <ixwebsocket/IXWebSocketVersion.h>
|
||||||
#include <ixcore/utils/IXCoreLogger.h>
|
#include <sstream>
|
||||||
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
@@ -64,7 +64,8 @@ namespace ix
|
|||||||
std::string SentryClient::computeAuthHeader()
|
std::string SentryClient::computeAuthHeader()
|
||||||
{
|
{
|
||||||
std::string securityHeader("Sentry sentry_version=5");
|
std::string securityHeader("Sentry sentry_version=5");
|
||||||
securityHeader += ",sentry_client=ws/1.0.0";
|
securityHeader += ",sentry_client=ws/";
|
||||||
|
securityHeader += std::string(IX_WEBSOCKET_VERSION);
|
||||||
securityHeader += ",sentry_timestamp=" + std::to_string(SentryClient::getTimestamp());
|
securityHeader += ",sentry_timestamp=" + std::to_string(SentryClient::getTimestamp());
|
||||||
securityHeader += ",sentry_key=" + _publicKey;
|
securityHeader += ",sentry_key=" + _publicKey;
|
||||||
securityHeader += ",sentry_secret=" + _secretKey;
|
securityHeader += ",sentry_secret=" + _secretKey;
|
||||||
@@ -233,7 +234,7 @@ namespace ix
|
|||||||
args->transferTimeout = 5 * 60;
|
args->transferTimeout = 5 * 60;
|
||||||
args->followRedirects = true;
|
args->followRedirects = true;
|
||||||
args->verbose = verbose;
|
args->verbose = verbose;
|
||||||
args->logger = [](const std::string& msg) { ix::IXCoreLogger::Log(msg.c_str()); };
|
args->logger = [](const std::string& msg) { CoreLogger::log(msg.c_str()); };
|
||||||
|
|
||||||
std::string body = computePayload(msg);
|
std::string body = computePayload(msg);
|
||||||
HttpResponsePtr response = _httpClient->post(_url, body, args);
|
HttpResponsePtr response = _httpClient->post(_url, body, args);
|
||||||
@@ -245,24 +246,21 @@ namespace ix
|
|||||||
std::string SentryClient::computeUrl(const std::string& project, const std::string& key)
|
std::string SentryClient::computeUrl(const std::string& project, const std::string& key)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "https://sentry.io/api/"
|
ss << "https://sentry.io/api/" << project << "/minidump?sentry_key=" << key;
|
||||||
<< project
|
|
||||||
<< "/minidump?sentry_key="
|
|
||||||
<< key;
|
|
||||||
|
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// curl -v -X POST -F upload_file_minidump=@ws/crash.dmp 'https://sentry.io/api/123456/minidump?sentry_key=12344567890'
|
// curl -v -X POST -F upload_file_minidump=@ws/crash.dmp
|
||||||
|
// 'https://sentry.io/api/123456/minidump?sentry_key=12344567890'
|
||||||
//
|
//
|
||||||
void SentryClient::uploadMinidump(
|
void SentryClient::uploadMinidump(const std::string& sentryMetadata,
|
||||||
const std::string& sentryMetadata,
|
const std::string& minidumpBytes,
|
||||||
const std::string& minidumpBytes,
|
const std::string& project,
|
||||||
const std::string& project,
|
const std::string& key,
|
||||||
const std::string& key,
|
bool verbose,
|
||||||
bool verbose,
|
const OnResponseCallback& onResponseCallback)
|
||||||
const OnResponseCallback& onResponseCallback)
|
|
||||||
{
|
{
|
||||||
std::string multipartBoundary = _httpClient->generateMultipartBoundary();
|
std::string multipartBoundary = _httpClient->generateMultipartBoundary();
|
||||||
|
|
||||||
@@ -273,7 +271,7 @@ namespace ix
|
|||||||
args->followRedirects = true;
|
args->followRedirects = true;
|
||||||
args->verbose = verbose;
|
args->verbose = verbose;
|
||||||
args->multipartBoundary = multipartBoundary;
|
args->multipartBoundary = multipartBoundary;
|
||||||
args->logger = [](const std::string& msg) { ix::IXCoreLogger::Log(msg.c_str()); };
|
args->logger = [](const std::string& msg) { CoreLogger::log(msg.c_str()); };
|
||||||
|
|
||||||
HttpFormDataParameters httpFormDataParameters;
|
HttpFormDataParameters httpFormDataParameters;
|
||||||
httpFormDataParameters["upload_file_minidump"] = minidumpBytes;
|
httpFormDataParameters["upload_file_minidump"] = minidumpBytes;
|
||||||
@@ -282,7 +280,27 @@ namespace ix
|
|||||||
httpParameters["sentry"] = sentryMetadata;
|
httpParameters["sentry"] = sentryMetadata;
|
||||||
|
|
||||||
args->url = computeUrl(project, key);
|
args->url = computeUrl(project, key);
|
||||||
args->body = _httpClient->serializeHttpFormDataParameters(multipartBoundary, httpFormDataParameters, httpParameters);
|
args->body = _httpClient->serializeHttpFormDataParameters(
|
||||||
|
multipartBoundary, httpFormDataParameters, httpParameters);
|
||||||
|
|
||||||
|
_httpClient->performRequest(args, onResponseCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SentryClient::uploadPayload(const Json::Value& payload,
|
||||||
|
bool verbose,
|
||||||
|
const OnResponseCallback& onResponseCallback)
|
||||||
|
{
|
||||||
|
auto args = _httpClient->createRequest();
|
||||||
|
args->extraHeaders["X-Sentry-Auth"] = SentryClient::computeAuthHeader();
|
||||||
|
args->verb = HttpClient::kPost;
|
||||||
|
args->connectTimeout = 60;
|
||||||
|
args->transferTimeout = 5 * 60;
|
||||||
|
args->followRedirects = true;
|
||||||
|
args->verbose = verbose;
|
||||||
|
args->logger = [](const std::string& msg) { CoreLogger::log(msg.c_str()); };
|
||||||
|
|
||||||
|
args->url = _url;
|
||||||
|
args->body = _jsonWriter.write(payload);
|
||||||
|
|
||||||
_httpClient->performRequest(args, onResponseCallback);
|
_httpClient->performRequest(args, onResponseCallback);
|
||||||
}
|
}
|
||||||
|
@@ -10,8 +10,8 @@
|
|||||||
#include <ixwebsocket/IXHttpClient.h>
|
#include <ixwebsocket/IXHttpClient.h>
|
||||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||||
#include <json/json.h>
|
#include <json/json.h>
|
||||||
#include <regex>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
@@ -28,13 +28,16 @@ namespace ix
|
|||||||
// Mostly for testing
|
// Mostly for testing
|
||||||
void setTLSOptions(const SocketTLSOptions& tlsOptions);
|
void setTLSOptions(const SocketTLSOptions& tlsOptions);
|
||||||
|
|
||||||
void uploadMinidump(
|
void uploadMinidump(const std::string& sentryMetadata,
|
||||||
const std::string& sentryMetadata,
|
const std::string& minidumpBytes,
|
||||||
const std::string& minidumpBytes,
|
const std::string& project,
|
||||||
const std::string& project,
|
const std::string& key,
|
||||||
const std::string& key,
|
bool verbose,
|
||||||
bool verbose,
|
const OnResponseCallback& onResponseCallback);
|
||||||
const OnResponseCallback& onResponseCallback);
|
|
||||||
|
void uploadPayload(const Json::Value& payload,
|
||||||
|
bool verbose,
|
||||||
|
const OnResponseCallback& onResponseCallback);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int64_t getTimestamp();
|
int64_t getTimestamp();
|
||||||
|
@@ -6,10 +6,10 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
|
||||||
|
|
||||||
namespace snake
|
namespace snake
|
||||||
{
|
{
|
||||||
|
@@ -28,10 +28,7 @@ namespace ix
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
CancellationRequest cancellationRequest = []() -> bool
|
CancellationRequest cancellationRequest = []() -> bool { return false; };
|
||||||
{
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
return _socket->connect(hostname, port, errMsg, cancellationRequest);
|
return _socket->connect(hostname, port, errMsg, cancellationRequest);
|
||||||
@@ -252,9 +249,8 @@ namespace ix
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string RedisClient::prepareXaddCommand(
|
std::string RedisClient::prepareXaddCommand(const std::string& stream,
|
||||||
const std::string& stream,
|
const std::string& message)
|
||||||
const std::string& message)
|
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "*5\r\n";
|
ss << "*5\r\n";
|
||||||
@@ -328,7 +324,9 @@ namespace ix
|
|||||||
return streamId;
|
return streamId;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RedisClient::sendCommand(const std::string& commands, int commandsCount, std::string& errMsg)
|
bool RedisClient::sendCommand(const std::string& commands,
|
||||||
|
int commandsCount,
|
||||||
|
std::string& errMsg)
|
||||||
{
|
{
|
||||||
bool sent = _socket->writeBytes(commands, nullptr);
|
bool sent = _socket->writeBytes(commands, nullptr);
|
||||||
if (!sent)
|
if (!sent)
|
||||||
|
@@ -8,11 +8,10 @@
|
|||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <ixwebsocket/IXSocket.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <ixwebsocket/IXSocket.h>
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class RedisClient
|
class RedisClient
|
||||||
@@ -39,14 +38,11 @@ namespace ix
|
|||||||
const OnRedisSubscribeCallback& callback);
|
const OnRedisSubscribeCallback& callback);
|
||||||
|
|
||||||
// XADD
|
// XADD
|
||||||
std::string xadd(
|
std::string xadd(const std::string& channel,
|
||||||
const std::string& channel,
|
const std::string& message,
|
||||||
const std::string& message,
|
std::string& errMsg);
|
||||||
std::string& errMsg);
|
|
||||||
|
|
||||||
std::string prepareXaddCommand(
|
std::string prepareXaddCommand(const std::string& stream, const std::string& message);
|
||||||
const std::string& stream,
|
|
||||||
const std::string& message);
|
|
||||||
|
|
||||||
std::string readXaddReply(std::string& errMsg);
|
std::string readXaddReply(std::string& errMsg);
|
||||||
|
|
||||||
|
@@ -6,17 +6,18 @@
|
|||||||
|
|
||||||
#include "IXRedisServer.h"
|
#include "IXRedisServer.h"
|
||||||
|
|
||||||
#include <ixwebsocket/IXNetSystem.h>
|
|
||||||
#include <ixwebsocket/IXSocketConnect.h>
|
|
||||||
#include <ixwebsocket/IXSocket.h>
|
|
||||||
#include <ixwebsocket/IXCancellationRequest.h>
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <ixwebsocket/IXCancellationRequest.h>
|
||||||
|
#include <ixwebsocket/IXNetSystem.h>
|
||||||
|
#include <ixwebsocket/IXSocket.h>
|
||||||
|
#include <ixwebsocket/IXSocketConnect.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
RedisServer::RedisServer(int port, const std::string& host, int backlog, size_t maxConnections, int addressFamily)
|
RedisServer::RedisServer(
|
||||||
|
int port, const std::string& host, int backlog, size_t maxConnections, int addressFamily)
|
||||||
: SocketServer(port, host, backlog, maxConnections, addressFamily)
|
: SocketServer(port, host, backlog, maxConnections, addressFamily)
|
||||||
, _connectedClientsCount(0)
|
, _connectedClientsCount(0)
|
||||||
, _stopHandlingConnections(false)
|
, _stopHandlingConnections(false)
|
||||||
@@ -114,8 +115,7 @@ namespace ix
|
|||||||
for (auto it : _subscribers)
|
for (auto it : _subscribers)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Subscription id: " << it.first
|
ss << "Subscription id: " << it.first << " #subscribers: " << it.second.size();
|
||||||
<< " #subscribers: " << it.second.size();
|
|
||||||
|
|
||||||
logInfo(ss.str());
|
logInfo(ss.str());
|
||||||
}
|
}
|
||||||
@@ -126,8 +126,7 @@ namespace ix
|
|||||||
return _connectedClientsCount;
|
return _connectedClientsCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RedisServer::startsWith(const std::string& str,
|
bool RedisServer::startsWith(const std::string& str, const std::string& start)
|
||||||
const std::string& start)
|
|
||||||
{
|
{
|
||||||
return str.compare(0, start.length(), start) == 0;
|
return str.compare(0, start.length(), start) == 0;
|
||||||
}
|
}
|
||||||
@@ -144,9 +143,8 @@ namespace ix
|
|||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RedisServer::parseRequest(
|
bool RedisServer::parseRequest(std::unique_ptr<Socket>& socket,
|
||||||
std::unique_ptr<Socket>& socket,
|
std::vector<std::string>& tokens)
|
||||||
std::vector<std::string>& tokens)
|
|
||||||
{
|
{
|
||||||
// Parse first line
|
// Parse first line
|
||||||
auto cb = makeCancellationRequestWithTimeout(30, _stopHandlingConnections);
|
auto cb = makeCancellationRequestWithTimeout(30, _stopHandlingConnections);
|
||||||
@@ -190,9 +188,8 @@ namespace ix
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RedisServer::handleCommand(
|
bool RedisServer::handleCommand(std::unique_ptr<Socket>& socket,
|
||||||
std::unique_ptr<Socket>& socket,
|
const std::vector<std::string>& tokens)
|
||||||
const std::vector<std::string>& tokens)
|
|
||||||
{
|
{
|
||||||
if (tokens.size() != 1) return false;
|
if (tokens.size() != 1) return false;
|
||||||
|
|
||||||
@@ -207,31 +204,30 @@ namespace ix
|
|||||||
//
|
//
|
||||||
ss << "*6\r\n";
|
ss << "*6\r\n";
|
||||||
ss << writeString("publish"); // 1
|
ss << writeString("publish"); // 1
|
||||||
ss << ":3\r\n"; // 2
|
ss << ":3\r\n"; // 2
|
||||||
ss << "*0\r\n"; // 3
|
ss << "*0\r\n"; // 3
|
||||||
ss << ":1\r\n"; // 4
|
ss << ":1\r\n"; // 4
|
||||||
ss << ":2\r\n"; // 5
|
ss << ":2\r\n"; // 5
|
||||||
ss << ":1\r\n"; // 6
|
ss << ":1\r\n"; // 6
|
||||||
|
|
||||||
//
|
//
|
||||||
// subscribe
|
// subscribe
|
||||||
//
|
//
|
||||||
ss << "*6\r\n";
|
ss << "*6\r\n";
|
||||||
ss << writeString("subscribe"); // 1
|
ss << writeString("subscribe"); // 1
|
||||||
ss << ":2\r\n"; // 2
|
ss << ":2\r\n"; // 2
|
||||||
ss << "*0\r\n"; // 3
|
ss << "*0\r\n"; // 3
|
||||||
ss << ":1\r\n"; // 4
|
ss << ":1\r\n"; // 4
|
||||||
ss << ":1\r\n"; // 5
|
ss << ":1\r\n"; // 5
|
||||||
ss << ":1\r\n"; // 6
|
ss << ":1\r\n"; // 6
|
||||||
|
|
||||||
socket->writeBytes(ss.str(), cb);
|
socket->writeBytes(ss.str(), cb);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RedisServer::handleSubscribe(
|
bool RedisServer::handleSubscribe(std::unique_ptr<Socket>& socket,
|
||||||
std::unique_ptr<Socket>& socket,
|
const std::vector<std::string>& tokens)
|
||||||
const std::vector<std::string>& tokens)
|
|
||||||
{
|
{
|
||||||
if (tokens.size() != 2) return false;
|
if (tokens.size() != 2) return false;
|
||||||
|
|
||||||
@@ -250,9 +246,8 @@ namespace ix
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RedisServer::handlePublish(
|
bool RedisServer::handlePublish(std::unique_ptr<Socket>& socket,
|
||||||
std::unique_ptr<Socket>& socket,
|
const std::vector<std::string>& tokens)
|
||||||
const std::vector<std::string>& tokens)
|
|
||||||
{
|
{
|
||||||
if (tokens.size() != 3) return false;
|
if (tokens.size() != 3) return false;
|
||||||
|
|
||||||
@@ -281,9 +276,7 @@ namespace ix
|
|||||||
|
|
||||||
// return the number of clients that received the message.
|
// return the number of clients that received the message.
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << ":"
|
ss << ":" << std::to_string(subscribers.size()) << "\r\n";
|
||||||
<< std::to_string(subscribers.size())
|
|
||||||
<< "\r\n";
|
|
||||||
socket->writeBytes(ss.str(), cb);
|
socket->writeBytes(ss.str(), cb);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@@ -6,13 +6,13 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IXSocketServer.h"
|
|
||||||
#include "IXSocket.h"
|
#include "IXSocket.h"
|
||||||
|
#include "IXSocketServer.h"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <map>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <utility> // pair
|
#include <utility> // pair
|
||||||
@@ -50,18 +50,14 @@ namespace ix
|
|||||||
bool startsWith(const std::string& str, const std::string& start);
|
bool startsWith(const std::string& str, const std::string& start);
|
||||||
std::string writeString(const std::string& str);
|
std::string writeString(const std::string& str);
|
||||||
|
|
||||||
bool parseRequest(
|
bool parseRequest(std::unique_ptr<Socket>& socket, std::vector<std::string>& tokens);
|
||||||
std::unique_ptr<Socket>& socket,
|
|
||||||
std::vector<std::string>& tokens);
|
|
||||||
|
|
||||||
bool handlePublish(std::unique_ptr<Socket>& socket,
|
bool handlePublish(std::unique_ptr<Socket>& socket, const std::vector<std::string>& tokens);
|
||||||
const std::vector<std::string>& tokens);
|
|
||||||
|
|
||||||
bool handleSubscribe(std::unique_ptr<Socket>& socket,
|
bool handleSubscribe(std::unique_ptr<Socket>& socket,
|
||||||
const std::vector<std::string>& tokens);
|
const std::vector<std::string>& tokens);
|
||||||
|
|
||||||
bool handleCommand(std::unique_ptr<Socket>& socket,
|
bool handleCommand(std::unique_ptr<Socket>& socket, const std::vector<std::string>& tokens);
|
||||||
const std::vector<std::string>& tokens);
|
|
||||||
|
|
||||||
void cleanupSubscribers(std::unique_ptr<Socket>& socket);
|
void cleanupSubscribers(std::unique_ptr<Socket>& socket);
|
||||||
};
|
};
|
||||||
|
@@ -10,9 +10,9 @@
|
|||||||
#include "IXSnakeConnectionState.h"
|
#include "IXSnakeConnectionState.h"
|
||||||
#include "nlohmann/json.hpp"
|
#include "nlohmann/json.hpp"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <ixcore/utils/IXCoreLogger.h>
|
||||||
#include <ixcrypto/IXHMac.h>
|
#include <ixcrypto/IXHMac.h>
|
||||||
#include <ixwebsocket/IXWebSocket.h>
|
#include <ixwebsocket/IXWebSocket.h>
|
||||||
#include <ixcore/utils/IXCoreLogger.h>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
namespace snake
|
namespace snake
|
||||||
@@ -189,7 +189,8 @@ namespace snake
|
|||||||
nlohmann::json response = {
|
nlohmann::json response = {
|
||||||
{"action", "rtm/subscription/data"},
|
{"action", "rtm/subscription/data"},
|
||||||
{"id", id++},
|
{"id", id++},
|
||||||
{"body", {{"subscription_id", subscriptionId}, {"position", "0-0"}, {"messages", {msg}}}}};
|
{"body",
|
||||||
|
{{"subscription_id", subscriptionId}, {"position", "0-0"}, {"messages", {msg}}}}};
|
||||||
|
|
||||||
ws->sendText(response.dump());
|
ws->sendText(response.dump());
|
||||||
};
|
};
|
||||||
@@ -197,7 +198,7 @@ namespace snake
|
|||||||
auto responseCallback = [ws, pdu, &subscriptionId](const std::string& redisResponse) {
|
auto responseCallback = [ws, pdu, &subscriptionId](const std::string& redisResponse) {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Redis Response: " << redisResponse << "...";
|
ss << "Redis Response: " << redisResponse << "...";
|
||||||
ix::IXCoreLogger::Log(ss.str().c_str());
|
ix::CoreLogger::log(ss.str().c_str());
|
||||||
|
|
||||||
// Success
|
// Success
|
||||||
nlohmann::json response = {{"action", "rtm/subscribe/ok"},
|
nlohmann::json response = {{"action", "rtm/subscribe/ok"},
|
||||||
@@ -209,7 +210,7 @@ namespace snake
|
|||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Subscribing to " << appChannel << "...";
|
ss << "Subscribing to " << appChannel << "...";
|
||||||
ix::IXCoreLogger::Log(ss.str().c_str());
|
ix::CoreLogger::log(ss.str().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!redisClient.subscribe(appChannel, responseCallback, callback))
|
if (!redisClient.subscribe(appChannel, responseCallback, callback))
|
||||||
@@ -251,7 +252,21 @@ namespace snake
|
|||||||
const AppConfig& appConfig,
|
const AppConfig& appConfig,
|
||||||
const std::string& str)
|
const std::string& str)
|
||||||
{
|
{
|
||||||
auto pdu = nlohmann::json::parse(str);
|
nlohmann::json pdu;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
pdu = nlohmann::json::parse(str);
|
||||||
|
}
|
||||||
|
catch (const nlohmann::json::parse_error& e)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "malformed json pdu: " << e.what() << " -> " << str << "";
|
||||||
|
|
||||||
|
nlohmann::json response = {{"body", {{"error", "invalid_json"}, {"reason", ss.str()}}}};
|
||||||
|
ws->sendText(response.dump());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto action = pdu["action"];
|
auto action = pdu["action"];
|
||||||
|
|
||||||
if (action == "auth/handshake")
|
if (action == "auth/handshake")
|
||||||
|
@@ -10,8 +10,8 @@
|
|||||||
#include "IXSnakeConnectionState.h"
|
#include "IXSnakeConnectionState.h"
|
||||||
#include "IXSnakeProtocol.h"
|
#include "IXSnakeProtocol.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
|
||||||
#include <ixcore/utils/IXCoreLogger.h>
|
#include <ixcore/utils/IXCoreLogger.h>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
|
||||||
namespace snake
|
namespace snake
|
||||||
@@ -29,7 +29,7 @@ namespace snake
|
|||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Listening on " << appConfig.hostname << ":" << appConfig.port;
|
ss << "Listening on " << appConfig.hostname << ":" << appConfig.port;
|
||||||
ix::IXCoreLogger::Log(ss.str().c_str());
|
ix::CoreLogger::log(ss.str().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -67,6 +67,7 @@ namespace snake
|
|||||||
webSocket->setOnMessageCallback(
|
webSocket->setOnMessageCallback(
|
||||||
[this, webSocket, state](const ix::WebSocketMessagePtr& msg) {
|
[this, webSocket, state](const ix::WebSocketMessagePtr& msg) {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
|
ix::LogLevel logLevel = ix::LogLevel::Debug;
|
||||||
if (msg->type == ix::WebSocketMessageType::Open)
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
{
|
{
|
||||||
ss << "New connection" << std::endl;
|
ss << "New connection" << std::endl;
|
||||||
@@ -86,6 +87,7 @@ namespace snake
|
|||||||
_appConfig.redisPort))
|
_appConfig.redisPort))
|
||||||
{
|
{
|
||||||
ss << "Cannot connect to redis host" << std::endl;
|
ss << "Cannot connect to redis host" << std::endl;
|
||||||
|
logLevel = ix::LogLevel::Error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||||
@@ -101,6 +103,7 @@ namespace snake
|
|||||||
ss << "#retries: " << msg->errorInfo.retries << std::endl;
|
ss << "#retries: " << msg->errorInfo.retries << std::endl;
|
||||||
ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl;
|
ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl;
|
||||||
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
|
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
|
||||||
|
logLevel = ix::LogLevel::Error;
|
||||||
}
|
}
|
||||||
else if (msg->type == ix::WebSocketMessageType::Fragment)
|
else if (msg->type == ix::WebSocketMessageType::Fragment)
|
||||||
{
|
{
|
||||||
@@ -112,7 +115,7 @@ namespace snake
|
|||||||
processCobraMessage(state, webSocket, _appConfig, msg->str);
|
processCobraMessage(state, webSocket, _appConfig, msg->str);
|
||||||
}
|
}
|
||||||
|
|
||||||
ix::IXCoreLogger::Log(ss.str().c_str());
|
ix::CoreLogger::log(ss.str().c_str(), logLevel);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -78,12 +78,12 @@ namespace ix
|
|||||||
WebSocketHttpHeaders extraHeaders;
|
WebSocketHttpHeaders extraHeaders;
|
||||||
std::string body;
|
std::string body;
|
||||||
std::string multipartBoundary;
|
std::string multipartBoundary;
|
||||||
int connectTimeout;
|
int connectTimeout = 60;
|
||||||
int transferTimeout;
|
int transferTimeout = 1800;
|
||||||
bool followRedirects;
|
bool followRedirects = true;
|
||||||
int maxRedirects;
|
int maxRedirects = 5;
|
||||||
bool verbose;
|
bool verbose = false;
|
||||||
bool compress;
|
bool compress = true;
|
||||||
Logger logger;
|
Logger logger;
|
||||||
OnProgressCallback onProgressCallback;
|
OnProgressCallback onProgressCallback;
|
||||||
};
|
};
|
||||||
|
@@ -1,115 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSelectInterruptEventFd.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
//
|
|
||||||
// On Linux we use eventd to wake up select.
|
|
||||||
//
|
|
||||||
|
|
||||||
//
|
|
||||||
// Linux/Android has a special type of virtual files. select(2) will react
|
|
||||||
// when reading/writing to those files, unlike closing sockets.
|
|
||||||
//
|
|
||||||
// https://linux.die.net/man/2/eventfd
|
|
||||||
// http://www.sourcexr.com/articles/2013/10/26/lightweight-inter-process-signaling-with-eventfd
|
|
||||||
//
|
|
||||||
// eventfd was added in Linux kernel 2.x, and our oldest Android (Kitkat 4.4)
|
|
||||||
// is on Kernel 3.x
|
|
||||||
//
|
|
||||||
// cf Android/Kernel table here
|
|
||||||
// https://android.stackexchange.com/questions/51651/which-android-runs-which-linux-kernel
|
|
||||||
//
|
|
||||||
// On macOS we use UNIX pipes to wake up select.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "IXSelectInterruptEventFd.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <sstream>
|
|
||||||
#include <string.h> // for strerror
|
|
||||||
#include <sys/eventfd.h>
|
|
||||||
#include <unistd.h> // for write
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
SelectInterruptEventFd::SelectInterruptEventFd()
|
|
||||||
{
|
|
||||||
_eventfd = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectInterruptEventFd::~SelectInterruptEventFd()
|
|
||||||
{
|
|
||||||
::close(_eventfd);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SelectInterruptEventFd::init(std::string& errorMsg)
|
|
||||||
{
|
|
||||||
// calling init twice is a programming error
|
|
||||||
assert(_eventfd == -1);
|
|
||||||
|
|
||||||
_eventfd = eventfd(0, 0);
|
|
||||||
if (_eventfd < 0)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "SelectInterruptEventFd::init() failed in eventfd()"
|
|
||||||
<< " : " << strerror(errno);
|
|
||||||
errorMsg = ss.str();
|
|
||||||
|
|
||||||
_eventfd = -1;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fcntl(_eventfd, F_SETFL, O_NONBLOCK) == -1)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "SelectInterruptEventFd::init() failed in fcntl() call"
|
|
||||||
<< " : " << strerror(errno);
|
|
||||||
errorMsg = ss.str();
|
|
||||||
|
|
||||||
_eventfd = -1;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SelectInterruptEventFd::notify(uint64_t value)
|
|
||||||
{
|
|
||||||
int fd = _eventfd;
|
|
||||||
|
|
||||||
if (fd == -1) return false;
|
|
||||||
|
|
||||||
// we should write 8 bytes for an uint64_t
|
|
||||||
return write(fd, &value, sizeof(value)) == 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: return max uint64_t for errors ?
|
|
||||||
uint64_t SelectInterruptEventFd::read()
|
|
||||||
{
|
|
||||||
int fd = _eventfd;
|
|
||||||
|
|
||||||
uint64_t value = 0;
|
|
||||||
::read(fd, &value, sizeof(value));
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SelectInterruptEventFd::clear()
|
|
||||||
{
|
|
||||||
if (_eventfd == -1) return false;
|
|
||||||
|
|
||||||
// 0 is a special value ; select will not wake up
|
|
||||||
uint64_t value = 0;
|
|
||||||
|
|
||||||
// we should write 8 bytes for an uint64_t
|
|
||||||
return write(_eventfd, &value, sizeof(value)) == 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
int SelectInterruptEventFd::getFd() const
|
|
||||||
{
|
|
||||||
return _eventfd;
|
|
||||||
}
|
|
||||||
} // namespace ix
|
|
@@ -1,31 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSelectInterruptEventFd.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "IXSelectInterrupt.h"
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class SelectInterruptEventFd final : public SelectInterrupt
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
SelectInterruptEventFd();
|
|
||||||
virtual ~SelectInterruptEventFd();
|
|
||||||
|
|
||||||
bool init(std::string& errorMsg) final;
|
|
||||||
|
|
||||||
bool notify(uint64_t value) final;
|
|
||||||
bool clear() final;
|
|
||||||
uint64_t read() final;
|
|
||||||
int getFd() const final;
|
|
||||||
|
|
||||||
private:
|
|
||||||
int _eventfd;
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
@@ -376,7 +376,8 @@ namespace ix
|
|||||||
{
|
{
|
||||||
if (isCancellationRequested && isCancellationRequested())
|
if (isCancellationRequested && isCancellationRequested())
|
||||||
{
|
{
|
||||||
return std::make_pair(false, std::string());
|
const std::string errorMsg("Cancellation Requested");
|
||||||
|
return std::make_pair(false, errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t size = std::min(kChunkSize, length - output.size());
|
size_t size = std::min(kChunkSize, length - output.size());
|
||||||
@@ -388,7 +389,8 @@ namespace ix
|
|||||||
}
|
}
|
||||||
else if (ret <= 0 && !Socket::isWaitNeeded())
|
else if (ret <= 0 && !Socket::isWaitNeeded())
|
||||||
{
|
{
|
||||||
return std::make_pair(false, std::string());
|
const std::string errorMsg("Recv Error");
|
||||||
|
return std::make_pair(false, errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onProgressCallback) onProgressCallback((int) output.size(), (int) length);
|
if (onProgressCallback) onProgressCallback((int) output.size(), (int) length);
|
||||||
@@ -397,7 +399,8 @@ namespace ix
|
|||||||
// This way we are not busy looping
|
// This way we are not busy looping
|
||||||
if (isReadyToRead(1) == PollResultType::Error)
|
if (isReadyToRead(1) == PollResultType::Error)
|
||||||
{
|
{
|
||||||
return std::make_pair(false, std::string());
|
const std::string errorMsg("Poll Error");
|
||||||
|
return std::make_pair(false, errorMsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,10 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* IXSocketAppleSSL.cpp
|
* IXSocketAppleSSL.cpp
|
||||||
* Author: Benjamin Sergeant
|
* Author: Benjamin Sergeant
|
||||||
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
|
||||||
*
|
*
|
||||||
* Adapted from Satori SDK Apple SSL code.
|
* Adapted from Satori SDK Apple SSL code.
|
||||||
*/
|
*/
|
||||||
|
#ifdef IXWEBSOCKET_USE_SECURE_TRANSPORT
|
||||||
|
|
||||||
#include "IXSocketAppleSSL.h"
|
#include "IXSocketAppleSSL.h"
|
||||||
|
|
||||||
#include "IXSocketConnect.h"
|
#include "IXSocketConnect.h"
|
||||||
@@ -307,3 +309,5 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
|
||||||
|
#endif // IXWEBSOCKET_USE_SECURE_TRANSPORT
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
/*
|
/*
|
||||||
* IXSocketAppleSSL.h
|
* IXSocketAppleSSL.h
|
||||||
* Author: Benjamin Sergeant
|
* Author: Benjamin Sergeant
|
||||||
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
#ifdef IXWEBSOCKET_USE_SECURE_TRANSPORT
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
@@ -47,3 +48,5 @@ namespace ix
|
|||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
|
||||||
|
#endif // IXWEBSOCKET_USE_SECURE_TRANSPORT
|
||||||
|
@@ -1,12 +1,13 @@
|
|||||||
/*
|
/*
|
||||||
* IXSocketMbedTLS.cpp
|
* IXSocketMbedTLS.cpp
|
||||||
* Author: Benjamin Sergeant
|
* Author: Benjamin Sergeant, Max Weisel
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
|
||||||
*
|
*
|
||||||
* Some code taken from
|
* Some code taken from
|
||||||
* https://github.com/rottor12/WsClientLib/blob/master/lib/src/WsClientLib.cpp
|
* https://github.com/rottor12/WsClientLib/blob/master/lib/src/WsClientLib.cpp
|
||||||
* and mini_client.c example from mbedtls
|
* and mini_client.c example from mbedtls
|
||||||
*/
|
*/
|
||||||
|
#ifdef IXWEBSOCKET_USE_MBED_TLS
|
||||||
|
|
||||||
#include "IXSocketMbedTLS.h"
|
#include "IXSocketMbedTLS.h"
|
||||||
|
|
||||||
@@ -103,10 +104,26 @@ namespace ix
|
|||||||
{
|
{
|
||||||
; // FIXME
|
; // FIXME
|
||||||
}
|
}
|
||||||
else if (mbedtls_x509_crt_parse_file(&_cacert, _tlsOptions.caFile.c_str()) < 0)
|
else
|
||||||
{
|
{
|
||||||
errMsg = "Cannot parse CA file '" + _tlsOptions.caFile + "'";
|
if (_tlsOptions.isUsingInMemoryCAs())
|
||||||
return false;
|
{
|
||||||
|
const char* buffer = _tlsOptions.caFile.c_str();
|
||||||
|
size_t bufferSize =
|
||||||
|
_tlsOptions.caFile.size() + 1; // Needs to include null terminating
|
||||||
|
// character otherwise mbedtls will fail.
|
||||||
|
if (mbedtls_x509_crt_parse(
|
||||||
|
&_cacert, (const unsigned char*) buffer, bufferSize) < 0)
|
||||||
|
{
|
||||||
|
errMsg = "Cannot parse CA from memory.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mbedtls_x509_crt_parse_file(&_cacert, _tlsOptions.caFile.c_str()) < 0)
|
||||||
|
{
|
||||||
|
errMsg = "Cannot parse CA file '" + _tlsOptions.caFile + "'";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mbedtls_ssl_conf_ca_chain(&_conf, &_cacert, NULL);
|
mbedtls_ssl_conf_ca_chain(&_conf, &_cacert, NULL);
|
||||||
@@ -280,3 +297,5 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
|
||||||
|
#endif // IXWEBSOCKET_USE_MBED_TLS
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
/*
|
/*
|
||||||
* IXSocketMbedTLS.h
|
* IXSocketMbedTLS.h
|
||||||
* Author: Benjamin Sergeant
|
* Author: Benjamin Sergeant
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
#ifdef IXWEBSOCKET_USE_MBED_TLS
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
@@ -54,3 +55,5 @@ namespace ix
|
|||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
|
||||||
|
#endif // IXWEBSOCKET_USE_MBED_TLS
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
* IXSocketOpenSSL.cpp
|
* IXSocketOpenSSL.cpp
|
||||||
* Author: Benjamin Sergeant, Matt DeBoer
|
* Author: Benjamin Sergeant, Matt DeBoer, Max Weisel
|
||||||
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
|
||||||
*
|
*
|
||||||
* Adapted from Satori SDK OpenSSL code.
|
* Adapted from Satori SDK OpenSSL code.
|
||||||
*/
|
*/
|
||||||
|
#ifdef IXWEBSOCKET_USE_OPEN_SSL
|
||||||
|
|
||||||
#include "IXSocketOpenSSL.h"
|
#include "IXSocketOpenSSL.h"
|
||||||
|
|
||||||
@@ -194,6 +195,66 @@ namespace ix
|
|||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SocketOpenSSL::openSSLAddCARootsFromString(const std::string roots)
|
||||||
|
{
|
||||||
|
// Create certificate store
|
||||||
|
X509_STORE* certificate_store = SSL_CTX_get_cert_store(_ssl_context);
|
||||||
|
if (certificate_store == nullptr) return false;
|
||||||
|
|
||||||
|
// Configure to allow intermediate certs
|
||||||
|
X509_STORE_set_flags(certificate_store,
|
||||||
|
X509_V_FLAG_TRUSTED_FIRST | X509_V_FLAG_PARTIAL_CHAIN);
|
||||||
|
|
||||||
|
// Create a new buffer and populate it with the roots
|
||||||
|
BIO* buffer = BIO_new_mem_buf((void*) roots.c_str(), static_cast<int>(roots.length()));
|
||||||
|
if (buffer == nullptr) return false;
|
||||||
|
|
||||||
|
// Read each root in the buffer and add to the certificate store
|
||||||
|
bool success = true;
|
||||||
|
size_t number_of_roots = 0;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// Read the next root in the buffer
|
||||||
|
X509* root = PEM_read_bio_X509_AUX(buffer, nullptr, nullptr, (void*) "");
|
||||||
|
if (root == nullptr)
|
||||||
|
{
|
||||||
|
// No more certs left in the buffer, we're done.
|
||||||
|
ERR_clear_error();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try adding the root to the certificate store
|
||||||
|
ERR_clear_error();
|
||||||
|
if (!X509_STORE_add_cert(certificate_store, root))
|
||||||
|
{
|
||||||
|
// Failed to add. If the error is unrelated to the x509 lib or the cert already
|
||||||
|
// exists, we're safe to continue.
|
||||||
|
unsigned long error = ERR_get_error();
|
||||||
|
if (ERR_GET_LIB(error) != ERR_LIB_X509 ||
|
||||||
|
ERR_GET_REASON(error) != X509_R_CERT_ALREADY_IN_HASH_TABLE)
|
||||||
|
{
|
||||||
|
// Failed. Clean up and bail.
|
||||||
|
success = false;
|
||||||
|
X509_free(root);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up and loop
|
||||||
|
X509_free(root);
|
||||||
|
number_of_roots++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up buffer
|
||||||
|
BIO_free(buffer);
|
||||||
|
|
||||||
|
// Make sure we loaded at least one certificate.
|
||||||
|
if (number_of_roots == 0) success = false;
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether a hostname matches a pattern
|
* Check whether a hostname matches a pattern
|
||||||
*/
|
*/
|
||||||
@@ -402,20 +463,32 @@ namespace ix
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else if (SSL_CTX_load_verify_locations(
|
else
|
||||||
_ssl_context, _tlsOptions.caFile.c_str(), NULL) != 1)
|
|
||||||
{
|
{
|
||||||
auto sslErr = ERR_get_error();
|
if (_tlsOptions.isUsingInMemoryCAs())
|
||||||
errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" + _tlsOptions.caFile +
|
{
|
||||||
"\") failed: ";
|
// Load from memory
|
||||||
errMsg += ERR_error_string(sslErr, nullptr);
|
openSSLAddCARootsFromString(_tlsOptions.caFile);
|
||||||
return false;
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
|
if (SSL_CTX_load_verify_locations(
|
||||||
|
_ssl_context, _tlsOptions.caFile.c_str(), NULL) != 1)
|
||||||
|
{
|
||||||
|
auto sslErr = ERR_get_error();
|
||||||
|
errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" +
|
||||||
|
_tlsOptions.caFile + "\") failed: ";
|
||||||
|
errMsg += ERR_error_string(sslErr, nullptr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
SSL_CTX_set_verify(_ssl_context,
|
SSL_CTX_set_verify(
|
||||||
SSL_VERIFY_PEER,
|
_ssl_context, SSL_VERIFY_PEER, [](int preverify, X509_STORE_CTX*) -> int {
|
||||||
[](int preverify, X509_STORE_CTX*) -> int { return preverify; });
|
return preverify;
|
||||||
SSL_CTX_set_verify_depth(_ssl_context, 4);
|
});
|
||||||
|
SSL_CTX_set_verify_depth(_ssl_context, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -525,26 +598,35 @@ namespace ix
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const char* root_ca_file = _tlsOptions.caFile.c_str();
|
if (_tlsOptions.isUsingInMemoryCAs())
|
||||||
STACK_OF(X509_NAME) * rootCAs;
|
|
||||||
rootCAs = SSL_load_client_CA_file(root_ca_file);
|
|
||||||
if (rootCAs == NULL)
|
|
||||||
{
|
{
|
||||||
auto sslErr = ERR_get_error();
|
// Load from memory
|
||||||
errMsg = "OpenSSL failed - SSL_load_client_CA_file('" + _tlsOptions.caFile +
|
openSSLAddCARootsFromString(_tlsOptions.caFile);
|
||||||
"') failed: ";
|
|
||||||
errMsg += ERR_error_string(sslErr, nullptr);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SSL_CTX_set_client_CA_list(_ssl_context, rootCAs);
|
const char* root_ca_file = _tlsOptions.caFile.c_str();
|
||||||
if (SSL_CTX_load_verify_locations(_ssl_context, root_ca_file, nullptr) != 1)
|
STACK_OF(X509_NAME) * rootCAs;
|
||||||
|
rootCAs = SSL_load_client_CA_file(root_ca_file);
|
||||||
|
if (rootCAs == NULL)
|
||||||
{
|
{
|
||||||
auto sslErr = ERR_get_error();
|
auto sslErr = ERR_get_error();
|
||||||
errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" +
|
errMsg = "OpenSSL failed - SSL_load_client_CA_file('" +
|
||||||
_tlsOptions.caFile + "\") failed: ";
|
_tlsOptions.caFile + "') failed: ";
|
||||||
errMsg += ERR_error_string(sslErr, nullptr);
|
errMsg += ERR_error_string(sslErr, nullptr);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SSL_CTX_set_client_CA_list(_ssl_context, rootCAs);
|
||||||
|
if (SSL_CTX_load_verify_locations(
|
||||||
|
_ssl_context, root_ca_file, nullptr) != 1)
|
||||||
|
{
|
||||||
|
auto sslErr = ERR_get_error();
|
||||||
|
errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" +
|
||||||
|
_tlsOptions.caFile + "\") failed: ";
|
||||||
|
errMsg += ERR_error_string(sslErr, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -731,3 +813,5 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
|
||||||
|
#endif // IXWEBSOCKET_USE_OPEN_SSL
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
/*
|
/*
|
||||||
* IXSocketOpenSSL.h
|
* IXSocketOpenSSL.h
|
||||||
* Author: Benjamin Sergeant, Matt DeBoer
|
* Author: Benjamin Sergeant, Matt DeBoer
|
||||||
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
#ifdef IXWEBSOCKET_USE_OPEN_SSL
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
@@ -39,6 +40,7 @@ namespace ix
|
|||||||
void openSSLInitialize();
|
void openSSLInitialize();
|
||||||
std::string getSSLError(int ret);
|
std::string getSSLError(int ret);
|
||||||
SSL_CTX* openSSLCreateContext(std::string& errMsg);
|
SSL_CTX* openSSLCreateContext(std::string& errMsg);
|
||||||
|
bool openSSLAddCARootsFromString(const std::string roots);
|
||||||
bool openSSLClientHandshake(const std::string& hostname,
|
bool openSSLClientHandshake(const std::string& hostname,
|
||||||
std::string& errMsg,
|
std::string& errMsg,
|
||||||
const CancellationRequest& isCancellationRequested);
|
const CancellationRequest& isCancellationRequested);
|
||||||
@@ -59,3 +61,5 @@ namespace ix
|
|||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
|
||||||
|
#endif // IXWEBSOCKET_USE_OPEN_SSL
|
||||||
|
@@ -15,6 +15,7 @@ namespace ix
|
|||||||
const char* kTLSCAFileUseSystemDefaults = "SYSTEM";
|
const char* kTLSCAFileUseSystemDefaults = "SYSTEM";
|
||||||
const char* kTLSCAFileDisableVerify = "NONE";
|
const char* kTLSCAFileDisableVerify = "NONE";
|
||||||
const char* kTLSCiphersUseDefault = "DEFAULT";
|
const char* kTLSCiphersUseDefault = "DEFAULT";
|
||||||
|
const char* kTLSInMemoryMarker = "-----BEGIN CERTIFICATE-----";
|
||||||
|
|
||||||
bool SocketTLSOptions::isValid() const
|
bool SocketTLSOptions::isValid() const
|
||||||
{
|
{
|
||||||
@@ -58,6 +59,11 @@ namespace ix
|
|||||||
return caFile == kTLSCAFileUseSystemDefaults;
|
return caFile == kTLSCAFileUseSystemDefaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SocketTLSOptions::isUsingInMemoryCAs() const
|
||||||
|
{
|
||||||
|
return caFile.find(kTLSInMemoryMarker) != std::string::npos;
|
||||||
|
}
|
||||||
|
|
||||||
bool SocketTLSOptions::isPeerVerifyDisabled() const
|
bool SocketTLSOptions::isPeerVerifyDisabled() const
|
||||||
{
|
{
|
||||||
return caFile == kTLSCAFileDisableVerify;
|
return caFile == kTLSCAFileDisableVerify;
|
||||||
|
@@ -37,6 +37,8 @@ namespace ix
|
|||||||
|
|
||||||
bool isUsingSystemDefaults() const;
|
bool isUsingSystemDefaults() const;
|
||||||
|
|
||||||
|
bool isUsingInMemoryCAs() const;
|
||||||
|
|
||||||
bool isPeerVerifyDisabled() const;
|
bool isPeerVerifyDisabled() const;
|
||||||
|
|
||||||
bool isUsingDefaultCiphers() const;
|
bool isUsingDefaultCiphers() const;
|
||||||
|
@@ -1,4 +1,29 @@
|
|||||||
/*
|
/*
|
||||||
|
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
|
||||||
|
* https://github.com/corporateshark/LUrlParser
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
* IXUrlParser.cpp
|
* IXUrlParser.cpp
|
||||||
* Author: Benjamin Sergeant
|
* Author: Benjamin Sergeant
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
@@ -6,7 +31,308 @@
|
|||||||
|
|
||||||
#include "IXUrlParser.h"
|
#include "IXUrlParser.h"
|
||||||
|
|
||||||
#include "LUrlParser.h"
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
enum LUrlParserError
|
||||||
|
{
|
||||||
|
LUrlParserError_Ok = 0,
|
||||||
|
LUrlParserError_Uninitialized = 1,
|
||||||
|
LUrlParserError_NoUrlCharacter = 2,
|
||||||
|
LUrlParserError_InvalidSchemeName = 3,
|
||||||
|
LUrlParserError_NoDoubleSlash = 4,
|
||||||
|
LUrlParserError_NoAtSign = 5,
|
||||||
|
LUrlParserError_UnexpectedEndOfLine = 6,
|
||||||
|
LUrlParserError_NoSlash = 7,
|
||||||
|
};
|
||||||
|
|
||||||
|
class clParseURL
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUrlParserError m_ErrorCode;
|
||||||
|
std::string m_Scheme;
|
||||||
|
std::string m_Host;
|
||||||
|
std::string m_Port;
|
||||||
|
std::string m_Path;
|
||||||
|
std::string m_Query;
|
||||||
|
std::string m_Fragment;
|
||||||
|
std::string m_UserName;
|
||||||
|
std::string m_Password;
|
||||||
|
|
||||||
|
clParseURL()
|
||||||
|
: m_ErrorCode(LUrlParserError_Uninitialized)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// return 'true' if the parsing was successful
|
||||||
|
bool IsValid() const
|
||||||
|
{
|
||||||
|
return m_ErrorCode == LUrlParserError_Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// helper to convert the port number to int, return 'true' if the port is valid (within the
|
||||||
|
/// 0..65535 range)
|
||||||
|
bool GetPort(int* OutPort) const;
|
||||||
|
|
||||||
|
/// parse the URL
|
||||||
|
static clParseURL ParseURL(const std::string& URL);
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit clParseURL(LUrlParserError ErrorCode)
|
||||||
|
: m_ErrorCode(ErrorCode)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool IsSchemeValid(const std::string& SchemeName)
|
||||||
|
{
|
||||||
|
for (auto c : SchemeName)
|
||||||
|
{
|
||||||
|
if (!isalpha(c) && c != '+' && c != '-' && c != '.') return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool clParseURL::GetPort(int* OutPort) const
|
||||||
|
{
|
||||||
|
if (!IsValid())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Port = atoi(m_Port.c_str());
|
||||||
|
|
||||||
|
if (Port <= 0 || Port > 65535)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OutPort)
|
||||||
|
{
|
||||||
|
*OutPort = Port;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// based on RFC 1738 and RFC 3986
|
||||||
|
clParseURL clParseURL::ParseURL(const std::string& URL)
|
||||||
|
{
|
||||||
|
clParseURL Result;
|
||||||
|
|
||||||
|
const char* CurrentString = URL.c_str();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* <scheme>:<scheme-specific-part>
|
||||||
|
* <scheme> := [a-z\+\-\.]+
|
||||||
|
* For resiliency, programs interpreting URLs should treat upper case letters as
|
||||||
|
*equivalent to lower case in scheme names
|
||||||
|
*/
|
||||||
|
|
||||||
|
// try to read scheme
|
||||||
|
{
|
||||||
|
const char* LocalString = strchr(CurrentString, ':');
|
||||||
|
|
||||||
|
if (!LocalString)
|
||||||
|
{
|
||||||
|
return clParseURL(LUrlParserError_NoUrlCharacter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// save the scheme name
|
||||||
|
Result.m_Scheme = std::string(CurrentString, LocalString - CurrentString);
|
||||||
|
|
||||||
|
if (!IsSchemeValid(Result.m_Scheme))
|
||||||
|
{
|
||||||
|
return clParseURL(LUrlParserError_InvalidSchemeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// scheme should be lowercase
|
||||||
|
std::transform(
|
||||||
|
Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower);
|
||||||
|
|
||||||
|
// skip ':'
|
||||||
|
CurrentString = LocalString + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* //<user>:<password>@<host>:<port>/<url-path>
|
||||||
|
* any ":", "@" and "/" must be normalized
|
||||||
|
*/
|
||||||
|
|
||||||
|
// skip "//"
|
||||||
|
if (*CurrentString++ != '/') return clParseURL(LUrlParserError_NoDoubleSlash);
|
||||||
|
if (*CurrentString++ != '/') return clParseURL(LUrlParserError_NoDoubleSlash);
|
||||||
|
|
||||||
|
// check if the user name and password are specified
|
||||||
|
bool bHasUserName = false;
|
||||||
|
|
||||||
|
const char* LocalString = CurrentString;
|
||||||
|
|
||||||
|
while (*LocalString)
|
||||||
|
{
|
||||||
|
if (*LocalString == '@')
|
||||||
|
{
|
||||||
|
// user name and password are specified
|
||||||
|
bHasUserName = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (*LocalString == '/')
|
||||||
|
{
|
||||||
|
// end of <host>:<port> specification
|
||||||
|
bHasUserName = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalString++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// user name and password
|
||||||
|
LocalString = CurrentString;
|
||||||
|
|
||||||
|
if (bHasUserName)
|
||||||
|
{
|
||||||
|
// read user name
|
||||||
|
while (*LocalString && *LocalString != ':' && *LocalString != '@')
|
||||||
|
LocalString++;
|
||||||
|
|
||||||
|
Result.m_UserName = std::string(CurrentString, LocalString - CurrentString);
|
||||||
|
|
||||||
|
// proceed with the current pointer
|
||||||
|
CurrentString = LocalString;
|
||||||
|
|
||||||
|
if (*CurrentString == ':')
|
||||||
|
{
|
||||||
|
// skip ':'
|
||||||
|
CurrentString++;
|
||||||
|
|
||||||
|
// read password
|
||||||
|
LocalString = CurrentString;
|
||||||
|
|
||||||
|
while (*LocalString && *LocalString != '@')
|
||||||
|
LocalString++;
|
||||||
|
|
||||||
|
Result.m_Password = std::string(CurrentString, LocalString - CurrentString);
|
||||||
|
|
||||||
|
CurrentString = LocalString;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip '@'
|
||||||
|
if (*CurrentString != '@')
|
||||||
|
{
|
||||||
|
return clParseURL(LUrlParserError_NoAtSign);
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentString++;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bHasBracket = (*CurrentString == '[');
|
||||||
|
|
||||||
|
// go ahead, read the host name
|
||||||
|
LocalString = CurrentString;
|
||||||
|
|
||||||
|
while (*LocalString)
|
||||||
|
{
|
||||||
|
if (bHasBracket && *LocalString == ']')
|
||||||
|
{
|
||||||
|
// end of IPv6 address
|
||||||
|
LocalString++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (!bHasBracket && (*LocalString == ':' || *LocalString == '/'))
|
||||||
|
{
|
||||||
|
// port number is specified
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalString++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.m_Host = std::string(CurrentString, LocalString - CurrentString);
|
||||||
|
|
||||||
|
CurrentString = LocalString;
|
||||||
|
|
||||||
|
// is port number specified?
|
||||||
|
if (*CurrentString == ':')
|
||||||
|
{
|
||||||
|
CurrentString++;
|
||||||
|
|
||||||
|
// read port number
|
||||||
|
LocalString = CurrentString;
|
||||||
|
|
||||||
|
while (*LocalString && *LocalString != '/')
|
||||||
|
LocalString++;
|
||||||
|
|
||||||
|
Result.m_Port = std::string(CurrentString, LocalString - CurrentString);
|
||||||
|
|
||||||
|
CurrentString = LocalString;
|
||||||
|
}
|
||||||
|
|
||||||
|
// end of string
|
||||||
|
if (!*CurrentString)
|
||||||
|
{
|
||||||
|
Result.m_ErrorCode = LUrlParserError_Ok;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip '/'
|
||||||
|
if (*CurrentString != '/')
|
||||||
|
{
|
||||||
|
return clParseURL(LUrlParserError_NoSlash);
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentString++;
|
||||||
|
|
||||||
|
// parse the path
|
||||||
|
LocalString = CurrentString;
|
||||||
|
|
||||||
|
while (*LocalString && *LocalString != '#' && *LocalString != '?')
|
||||||
|
LocalString++;
|
||||||
|
|
||||||
|
Result.m_Path = std::string(CurrentString, LocalString - CurrentString);
|
||||||
|
|
||||||
|
CurrentString = LocalString;
|
||||||
|
|
||||||
|
// check for query
|
||||||
|
if (*CurrentString == '?')
|
||||||
|
{
|
||||||
|
// skip '?'
|
||||||
|
CurrentString++;
|
||||||
|
|
||||||
|
// read query
|
||||||
|
LocalString = CurrentString;
|
||||||
|
|
||||||
|
while (*LocalString && *LocalString != '#')
|
||||||
|
LocalString++;
|
||||||
|
|
||||||
|
Result.m_Query = std::string(CurrentString, LocalString - CurrentString);
|
||||||
|
|
||||||
|
CurrentString = LocalString;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for fragment
|
||||||
|
if (*CurrentString == '#')
|
||||||
|
{
|
||||||
|
// skip '#'
|
||||||
|
CurrentString++;
|
||||||
|
|
||||||
|
// read fragment
|
||||||
|
LocalString = CurrentString;
|
||||||
|
|
||||||
|
while (*LocalString)
|
||||||
|
LocalString++;
|
||||||
|
|
||||||
|
Result.m_Fragment = std::string(CurrentString, LocalString - CurrentString);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.m_ErrorCode = LUrlParserError_Ok;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
@@ -17,7 +343,7 @@ namespace ix
|
|||||||
std::string& query,
|
std::string& query,
|
||||||
int& port)
|
int& port)
|
||||||
{
|
{
|
||||||
LUrlParser::clParseURL res = LUrlParser::clParseURL::ParseURL(url);
|
clParseURL res = clParseURL::ParseURL(url);
|
||||||
|
|
||||||
if (!res.IsValid())
|
if (!res.IsValid())
|
||||||
{
|
{
|
||||||
|
@@ -34,7 +34,7 @@ namespace ix
|
|||||||
_ws.setOnCloseCallback(
|
_ws.setOnCloseCallback(
|
||||||
[this](uint16_t code, const std::string& reason, size_t wireSize, bool remote) {
|
[this](uint16_t code, const std::string& reason, size_t wireSize, bool remote) {
|
||||||
_onMessageCallback(
|
_onMessageCallback(
|
||||||
std::make_shared<WebSocketMessage>(WebSocketMessageType::Close,
|
std::make_unique<WebSocketMessage>(WebSocketMessageType::Close,
|
||||||
"",
|
"",
|
||||||
wireSize,
|
wireSize,
|
||||||
WebSocketErrorInfo(),
|
WebSocketErrorInfo(),
|
||||||
@@ -193,7 +193,7 @@ namespace ix
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onMessageCallback(std::make_shared<WebSocketMessage>(
|
_onMessageCallback(std::make_unique<WebSocketMessage>(
|
||||||
WebSocketMessageType::Open,
|
WebSocketMessageType::Open,
|
||||||
"",
|
"",
|
||||||
0,
|
0,
|
||||||
@@ -225,7 +225,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
_onMessageCallback(
|
_onMessageCallback(
|
||||||
std::make_shared<WebSocketMessage>(WebSocketMessageType::Open,
|
std::make_unique<WebSocketMessage>(WebSocketMessageType::Open,
|
||||||
"",
|
"",
|
||||||
0,
|
0,
|
||||||
WebSocketErrorInfo(),
|
WebSocketErrorInfo(),
|
||||||
@@ -310,7 +310,7 @@ namespace ix
|
|||||||
connectErr.reason = status.errorStr;
|
connectErr.reason = status.errorStr;
|
||||||
connectErr.http_status = status.http_status;
|
connectErr.http_status = status.http_status;
|
||||||
|
|
||||||
_onMessageCallback(std::make_shared<WebSocketMessage>(WebSocketMessageType::Error,
|
_onMessageCallback(std::make_unique<WebSocketMessage>(WebSocketMessageType::Error,
|
||||||
"",
|
"",
|
||||||
0,
|
0,
|
||||||
connectErr,
|
connectErr,
|
||||||
@@ -386,7 +386,7 @@ namespace ix
|
|||||||
|
|
||||||
bool binary = messageKind == WebSocketTransport::MessageKind::MSG_BINARY;
|
bool binary = messageKind == WebSocketTransport::MessageKind::MSG_BINARY;
|
||||||
|
|
||||||
_onMessageCallback(std::make_shared<WebSocketMessage>(webSocketMessageType,
|
_onMessageCallback(std::make_unique<WebSocketMessage>(webSocketMessageType,
|
||||||
msg,
|
msg,
|
||||||
wireSize,
|
wireSize,
|
||||||
webSocketErrorInfo,
|
webSocketErrorInfo,
|
||||||
|
@@ -6,6 +6,9 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
struct WebSocketCloseInfo
|
struct WebSocketCloseInfo
|
||||||
|
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
|
@@ -10,7 +10,7 @@
|
|||||||
#include "IXSocketConnect.h"
|
#include "IXSocketConnect.h"
|
||||||
#include "IXUrlParser.h"
|
#include "IXUrlParser.h"
|
||||||
#include "IXUserAgent.h"
|
#include "IXUserAgent.h"
|
||||||
#include "libwshandshake.hpp"
|
#include "IXWebSocketHandshakeKeyGen.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <random>
|
#include <random>
|
||||||
@@ -133,7 +133,7 @@ namespace ix
|
|||||||
|
|
||||||
for (auto& it : extraHeaders)
|
for (auto& it : extraHeaders)
|
||||||
{
|
{
|
||||||
ss << it.first << ":" << it.second << "\r\n";
|
ss << it.first << ": " << it.second << "\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_enablePerMessageDeflate)
|
if (_enablePerMessageDeflate)
|
||||||
|
171
ixwebsocket/IXWebSocketHandshakeKeyGen.h
Normal file
171
ixwebsocket/IXWebSocketHandshakeKeyGen.h
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
// Copyright (c) 2016 Alex Hultman and contributors
|
||||||
|
|
||||||
|
// This software is provided 'as-is', without any express or implied
|
||||||
|
// warranty. In no event will the authors be held liable for any damages
|
||||||
|
// arising from the use of this software.
|
||||||
|
|
||||||
|
// Permission is granted to anyone to use this software for any purpose,
|
||||||
|
// including commercial applications, and to alter it and redistribute it
|
||||||
|
// freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
// 1. The origin of this software must not be misrepresented; you must not
|
||||||
|
// claim that you wrote the original software. If you use this software
|
||||||
|
// in a product, an acknowledgement in the product documentation would be
|
||||||
|
// appreciated but is not required.
|
||||||
|
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
// misrepresented as being the original software.
|
||||||
|
// 3. This notice may not be removed or altered from any source distribution.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class WebSocketHandshakeKeyGen
|
||||||
|
{
|
||||||
|
template<int N, typename T>
|
||||||
|
struct static_for
|
||||||
|
{
|
||||||
|
void operator()(uint32_t* a, uint32_t* b)
|
||||||
|
{
|
||||||
|
static_for<N - 1, T>()(a, b);
|
||||||
|
T::template f<N - 1>(a, b);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct static_for<0, T>
|
||||||
|
{
|
||||||
|
void operator()(uint32_t* /*a*/, uint32_t* /*hash*/)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<int state>
|
||||||
|
struct Sha1Loop
|
||||||
|
{
|
||||||
|
static inline uint32_t rol(uint32_t value, size_t bits)
|
||||||
|
{
|
||||||
|
return (value << bits) | (value >> (32 - bits));
|
||||||
|
}
|
||||||
|
static inline uint32_t blk(uint32_t b[16], size_t i)
|
||||||
|
{
|
||||||
|
return rol(b[(i + 13) & 15] ^ b[(i + 8) & 15] ^ b[(i + 2) & 15] ^ b[i], 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int i>
|
||||||
|
static inline void f(uint32_t* a, uint32_t* b)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
a[i % 5] +=
|
||||||
|
((a[(3 + i) % 5] & (a[(2 + i) % 5] ^ a[(1 + i) % 5])) ^ a[(1 + i) % 5]) +
|
||||||
|
b[i] + 0x5a827999 + rol(a[(4 + i) % 5], 5);
|
||||||
|
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
b[i] = blk(b, i);
|
||||||
|
a[(1 + i) % 5] +=
|
||||||
|
((a[(4 + i) % 5] & (a[(3 + i) % 5] ^ a[(2 + i) % 5])) ^ a[(2 + i) % 5]) +
|
||||||
|
b[i] + 0x5a827999 + rol(a[(5 + i) % 5], 5);
|
||||||
|
a[(4 + i) % 5] = rol(a[(4 + i) % 5], 30);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
b[(i + 4) % 16] = blk(b, (i + 4) % 16);
|
||||||
|
a[i % 5] += (a[(3 + i) % 5] ^ a[(2 + i) % 5] ^ a[(1 + i) % 5]) +
|
||||||
|
b[(i + 4) % 16] + 0x6ed9eba1 + rol(a[(4 + i) % 5], 5);
|
||||||
|
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
b[(i + 8) % 16] = blk(b, (i + 8) % 16);
|
||||||
|
a[i % 5] += (((a[(3 + i) % 5] | a[(2 + i) % 5]) & a[(1 + i) % 5]) |
|
||||||
|
(a[(3 + i) % 5] & a[(2 + i) % 5])) +
|
||||||
|
b[(i + 8) % 16] + 0x8f1bbcdc + rol(a[(4 + i) % 5], 5);
|
||||||
|
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
b[(i + 12) % 16] = blk(b, (i + 12) % 16);
|
||||||
|
a[i % 5] += (a[(3 + i) % 5] ^ a[(2 + i) % 5] ^ a[(1 + i) % 5]) +
|
||||||
|
b[(i + 12) % 16] + 0xca62c1d6 + rol(a[(4 + i) % 5], 5);
|
||||||
|
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
|
||||||
|
break;
|
||||||
|
case 6: b[i] += a[4 - i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline void sha1(uint32_t hash[5], uint32_t b[16])
|
||||||
|
{
|
||||||
|
uint32_t a[5] = {hash[4], hash[3], hash[2], hash[1], hash[0]};
|
||||||
|
static_for<16, Sha1Loop<1>>()(a, b);
|
||||||
|
static_for<4, Sha1Loop<2>>()(a, b);
|
||||||
|
static_for<20, Sha1Loop<3>>()(a, b);
|
||||||
|
static_for<20, Sha1Loop<4>>()(a, b);
|
||||||
|
static_for<20, Sha1Loop<5>>()(a, b);
|
||||||
|
static_for<5, Sha1Loop<6>>()(a, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void base64(unsigned char* src, char* dst)
|
||||||
|
{
|
||||||
|
const char* b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
for (int i = 0; i < 18; i += 3)
|
||||||
|
{
|
||||||
|
*dst++ = b64[(src[i] >> 2) & 63];
|
||||||
|
*dst++ = b64[((src[i] & 3) << 4) | ((src[i + 1] & 240) >> 4)];
|
||||||
|
*dst++ = b64[((src[i + 1] & 15) << 2) | ((src[i + 2] & 192) >> 6)];
|
||||||
|
*dst++ = b64[src[i + 2] & 63];
|
||||||
|
}
|
||||||
|
*dst++ = b64[(src[18] >> 2) & 63];
|
||||||
|
*dst++ = b64[((src[18] & 3) << 4) | ((src[19] & 240) >> 4)];
|
||||||
|
*dst++ = b64[((src[19] & 15) << 2)];
|
||||||
|
*dst++ = '=';
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
static inline void generate(const std::string& inputStr, char output[28])
|
||||||
|
{
|
||||||
|
char input[25] = {};
|
||||||
|
strncpy(input, inputStr.c_str(), 25 - 1);
|
||||||
|
input[25 - 1] = '\0';
|
||||||
|
|
||||||
|
uint32_t b_output[5] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0};
|
||||||
|
uint32_t b_input[16] = {0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0x32353845,
|
||||||
|
0x41464135,
|
||||||
|
0x2d453931,
|
||||||
|
0x342d3437,
|
||||||
|
0x44412d39,
|
||||||
|
0x3543412d,
|
||||||
|
0x43354142,
|
||||||
|
0x30444338,
|
||||||
|
0x35423131,
|
||||||
|
0x80000000};
|
||||||
|
|
||||||
|
for (int i = 0; i < 6; i++)
|
||||||
|
{
|
||||||
|
b_input[i] = (input[4 * i + 3] & 0xff) | (input[4 * i + 2] & 0xff) << 8 |
|
||||||
|
(input[4 * i + 1] & 0xff) << 16 | (input[4 * i + 0] & 0xff) << 24;
|
||||||
|
}
|
||||||
|
sha1(b_output, b_input);
|
||||||
|
uint32_t last_b[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 480};
|
||||||
|
sha1(b_output, last_b);
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
uint32_t tmp = b_output[i];
|
||||||
|
char* bytes = (char*) &b_output[i];
|
||||||
|
bytes[3] = tmp & 0xff;
|
||||||
|
bytes[2] = (tmp >> 8) & 0xff;
|
||||||
|
bytes[1] = (tmp >> 16) & 0xff;
|
||||||
|
bytes[0] = (tmp >> 24) & 0xff;
|
||||||
|
}
|
||||||
|
base64((unsigned char*) b_output, output);
|
||||||
|
}
|
||||||
|
};
|
@@ -19,7 +19,7 @@ namespace ix
|
|||||||
struct WebSocketMessage
|
struct WebSocketMessage
|
||||||
{
|
{
|
||||||
WebSocketMessageType type;
|
WebSocketMessageType type;
|
||||||
std::string str;
|
const std::string& str;
|
||||||
size_t wireSize;
|
size_t wireSize;
|
||||||
WebSocketErrorInfo errorInfo;
|
WebSocketErrorInfo errorInfo;
|
||||||
WebSocketOpenInfo openInfo;
|
WebSocketOpenInfo openInfo;
|
||||||
@@ -34,7 +34,7 @@ namespace ix
|
|||||||
WebSocketCloseInfo c,
|
WebSocketCloseInfo c,
|
||||||
bool b = false)
|
bool b = false)
|
||||||
: type(t)
|
: type(t)
|
||||||
, str(std::move(s))
|
, str(s)
|
||||||
, wireSize(w)
|
, wireSize(w)
|
||||||
, errorInfo(e)
|
, errorInfo(e)
|
||||||
, openInfo(o)
|
, openInfo(o)
|
||||||
@@ -45,5 +45,5 @@ namespace ix
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using WebSocketMessagePtr = std::shared_ptr<WebSocketMessage>;
|
using WebSocketMessagePtr = std::unique_ptr<WebSocketMessage>;
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@@ -1,86 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocketMessageQueue.cpp
|
|
||||||
* Author: Korchynskyi Dmytro
|
|
||||||
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXWebSocketMessageQueue.h"
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
WebSocketMessageQueue::WebSocketMessageQueue(WebSocket* websocket)
|
|
||||||
{
|
|
||||||
bindWebsocket(websocket);
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketMessageQueue::~WebSocketMessageQueue()
|
|
||||||
{
|
|
||||||
if (!_messages.empty())
|
|
||||||
{
|
|
||||||
// not handled all messages
|
|
||||||
}
|
|
||||||
|
|
||||||
bindWebsocket(nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketMessageQueue::bindWebsocket(WebSocket* websocket)
|
|
||||||
{
|
|
||||||
if (_websocket == websocket) return;
|
|
||||||
|
|
||||||
// unbind old
|
|
||||||
if (_websocket)
|
|
||||||
{
|
|
||||||
// set dummy callback just to avoid crash
|
|
||||||
_websocket->setOnMessageCallback([](const WebSocketMessagePtr&) {});
|
|
||||||
}
|
|
||||||
|
|
||||||
_websocket = websocket;
|
|
||||||
|
|
||||||
// bind new
|
|
||||||
if (_websocket)
|
|
||||||
{
|
|
||||||
_websocket->setOnMessageCallback([this](const WebSocketMessagePtr& msg) {
|
|
||||||
std::lock_guard<std::mutex> lock(_messagesMutex);
|
|
||||||
_messages.emplace_back(std::move(msg));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketMessageQueue::setOnMessageCallback(const OnMessageCallback& callback)
|
|
||||||
{
|
|
||||||
_onMessageUserCallback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketMessageQueue::setOnMessageCallback(OnMessageCallback&& callback)
|
|
||||||
{
|
|
||||||
_onMessageUserCallback = std::move(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketMessagePtr WebSocketMessageQueue::popMessage()
|
|
||||||
{
|
|
||||||
WebSocketMessagePtr message;
|
|
||||||
std::lock_guard<std::mutex> lock(_messagesMutex);
|
|
||||||
|
|
||||||
if (!_messages.empty())
|
|
||||||
{
|
|
||||||
message = std::move(_messages.front());
|
|
||||||
_messages.pop_front();
|
|
||||||
}
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketMessageQueue::poll(int count)
|
|
||||||
{
|
|
||||||
if (!_onMessageUserCallback) return;
|
|
||||||
|
|
||||||
WebSocketMessagePtr message;
|
|
||||||
|
|
||||||
while (count > 0 && (message = popMessage()))
|
|
||||||
{
|
|
||||||
_onMessageUserCallback(message);
|
|
||||||
--count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ix
|
|
@@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocketMessageQueue.h
|
|
||||||
* Author: Korchynskyi Dmytro
|
|
||||||
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "IXWebSocket.h"
|
|
||||||
#include <list>
|
|
||||||
#include <memory>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// A helper class to dispatch websocket message callbacks in your thread.
|
|
||||||
//
|
|
||||||
class WebSocketMessageQueue
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
WebSocketMessageQueue(WebSocket* websocket = nullptr);
|
|
||||||
~WebSocketMessageQueue();
|
|
||||||
|
|
||||||
void bindWebsocket(WebSocket* websocket);
|
|
||||||
|
|
||||||
void setOnMessageCallback(const OnMessageCallback& callback);
|
|
||||||
void setOnMessageCallback(OnMessageCallback&& callback);
|
|
||||||
|
|
||||||
void poll(int count = 512);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
WebSocketMessagePtr popMessage();
|
|
||||||
|
|
||||||
private:
|
|
||||||
WebSocket* _websocket = nullptr;
|
|
||||||
OnMessageCallback _onMessageUserCallback;
|
|
||||||
std::mutex _messagesMutex;
|
|
||||||
std::list<WebSocketMessagePtr> _messages;
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
@@ -6,6 +6,10 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "IXWebSocketHttpHeaders.h"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
struct WebSocketOpenInfo
|
struct WebSocketOpenInfo
|
||||||
|
@@ -87,6 +87,9 @@ namespace ix
|
|||||||
//
|
//
|
||||||
size_t output;
|
size_t output;
|
||||||
|
|
||||||
|
// Clear output
|
||||||
|
out.clear();
|
||||||
|
|
||||||
if (in.empty())
|
if (in.empty())
|
||||||
{
|
{
|
||||||
// See issue #167
|
// See issue #167
|
||||||
@@ -174,6 +177,9 @@ namespace ix
|
|||||||
_inflateState.avail_in = (uInt) inFixed.size();
|
_inflateState.avail_in = (uInt) inFixed.size();
|
||||||
_inflateState.next_in = (unsigned char*) (const_cast<char*>(inFixed.data()));
|
_inflateState.next_in = (unsigned char*) (const_cast<char*>(inFixed.data()));
|
||||||
|
|
||||||
|
// Clear output
|
||||||
|
out.clear();
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
_inflateState.avail_out = (uInt) _compressBufferSize;
|
_inflateState.avail_out = (uInt) _compressBufferSize;
|
||||||
|
@@ -62,7 +62,7 @@ namespace ix
|
|||||||
WebSocketTransport::WebSocketTransport()
|
WebSocketTransport::WebSocketTransport()
|
||||||
: _useMask(true)
|
: _useMask(true)
|
||||||
, _blockingSend(false)
|
, _blockingSend(false)
|
||||||
, _compressedMessage(false)
|
, _receivedMessageCompressed(false)
|
||||||
, _readyState(ReadyState::CLOSED)
|
, _readyState(ReadyState::CLOSED)
|
||||||
, _closeCode(WebSocketCloseConstants::kInternalErrorCode)
|
, _closeCode(WebSocketCloseConstants::kInternalErrorCode)
|
||||||
, _closeReason(WebSocketCloseConstants::kInternalErrorMessage)
|
, _closeReason(WebSocketCloseConstants::kInternalErrorMessage)
|
||||||
@@ -74,6 +74,7 @@ namespace ix
|
|||||||
, _enablePong(kDefaultEnablePong)
|
, _enablePong(kDefaultEnablePong)
|
||||||
, _pingIntervalSecs(kDefaultPingIntervalSecs)
|
, _pingIntervalSecs(kDefaultPingIntervalSecs)
|
||||||
, _pongReceived(false)
|
, _pongReceived(false)
|
||||||
|
, _pingCount(0)
|
||||||
, _lastSendPingTimePoint(std::chrono::steady_clock::now())
|
, _lastSendPingTimePoint(std::chrono::steady_clock::now())
|
||||||
{
|
{
|
||||||
_readbuf.resize(kChunkSize);
|
_readbuf.resize(kChunkSize);
|
||||||
@@ -221,7 +222,8 @@ namespace ix
|
|||||||
{
|
{
|
||||||
_pongReceived = false;
|
_pongReceived = false;
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << kPingMessage << "::" << _pingIntervalSecs << "s";
|
ss << kPingMessage << "::" << _pingIntervalSecs << "s"
|
||||||
|
<< "::" << _pingCount++;
|
||||||
return sendPing(ss.str());
|
return sendPing(ss.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,7 +495,7 @@ namespace ix
|
|||||||
? MessageKind::MSG_TEXT
|
? MessageKind::MSG_TEXT
|
||||||
: MessageKind::MSG_BINARY;
|
: MessageKind::MSG_BINARY;
|
||||||
|
|
||||||
_compressedMessage = _enablePerMessageDeflate && ws.rsv1;
|
_receivedMessageCompressed = _enablePerMessageDeflate && ws.rsv1;
|
||||||
|
|
||||||
// Continuation message needs to follow a non-fin TEXT or BINARY message
|
// Continuation message needs to follow a non-fin TEXT or BINARY message
|
||||||
if (!_chunks.empty())
|
if (!_chunks.empty())
|
||||||
@@ -515,10 +517,12 @@ namespace ix
|
|||||||
//
|
//
|
||||||
if (ws.fin && _chunks.empty())
|
if (ws.fin && _chunks.empty())
|
||||||
{
|
{
|
||||||
emitMessage(
|
emitMessage(_fragmentedMessageKind,
|
||||||
_fragmentedMessageKind, frameData, _compressedMessage, onMessageCallback);
|
frameData,
|
||||||
|
_receivedMessageCompressed,
|
||||||
|
onMessageCallback);
|
||||||
|
|
||||||
_compressedMessage = false;
|
_receivedMessageCompressed = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -535,11 +539,11 @@ namespace ix
|
|||||||
{
|
{
|
||||||
emitMessage(_fragmentedMessageKind,
|
emitMessage(_fragmentedMessageKind,
|
||||||
getMergedChunks(),
|
getMergedChunks(),
|
||||||
_compressedMessage,
|
_receivedMessageCompressed,
|
||||||
onMessageCallback);
|
onMessageCallback);
|
||||||
|
|
||||||
_chunks.clear();
|
_chunks.clear();
|
||||||
_compressedMessage = false;
|
_receivedMessageCompressed = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -712,17 +716,16 @@ namespace ix
|
|||||||
// When the RSV1 bit is 1 it means the message is compressed
|
// When the RSV1 bit is 1 it means the message is compressed
|
||||||
if (compressedMessage && messageKind != MessageKind::FRAGMENT)
|
if (compressedMessage && messageKind != MessageKind::FRAGMENT)
|
||||||
{
|
{
|
||||||
std::string decompressedMessage;
|
bool success = _perMessageDeflate->decompress(message, _decompressedMessage);
|
||||||
bool success = _perMessageDeflate->decompress(message, decompressedMessage);
|
|
||||||
|
|
||||||
if (messageKind == MessageKind::MSG_TEXT && !validateUtf8(decompressedMessage))
|
if (messageKind == MessageKind::MSG_TEXT && !validateUtf8(_decompressedMessage))
|
||||||
{
|
{
|
||||||
close(WebSocketCloseConstants::kInvalidFramePayloadData,
|
close(WebSocketCloseConstants::kInvalidFramePayloadData,
|
||||||
WebSocketCloseConstants::kInvalidFramePayloadDataMessage);
|
WebSocketCloseConstants::kInvalidFramePayloadDataMessage);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
onMessageCallback(decompressedMessage, wireSize, !success, messageKind);
|
onMessageCallback(_decompressedMessage, wireSize, !success, messageKind);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -759,7 +762,6 @@ namespace ix
|
|||||||
|
|
||||||
size_t payloadSize = message.size();
|
size_t payloadSize = message.size();
|
||||||
size_t wireSize = message.size();
|
size_t wireSize = message.size();
|
||||||
std::string compressedMessage;
|
|
||||||
bool compressionError = false;
|
bool compressionError = false;
|
||||||
|
|
||||||
std::string::const_iterator message_begin = message.begin();
|
std::string::const_iterator message_begin = message.begin();
|
||||||
@@ -767,7 +769,7 @@ namespace ix
|
|||||||
|
|
||||||
if (compress)
|
if (compress)
|
||||||
{
|
{
|
||||||
if (!_perMessageDeflate->compress(message, compressedMessage))
|
if (!_perMessageDeflate->compress(message, _compressedMessage))
|
||||||
{
|
{
|
||||||
bool success = false;
|
bool success = false;
|
||||||
compressionError = true;
|
compressionError = true;
|
||||||
@@ -776,10 +778,10 @@ namespace ix
|
|||||||
return WebSocketSendInfo(success, compressionError, payloadSize, wireSize);
|
return WebSocketSendInfo(success, compressionError, payloadSize, wireSize);
|
||||||
}
|
}
|
||||||
compressionError = false;
|
compressionError = false;
|
||||||
wireSize = compressedMessage.size();
|
wireSize = _compressedMessage.size();
|
||||||
|
|
||||||
message_begin = compressedMessage.begin();
|
message_begin = _compressedMessage.begin();
|
||||||
message_end = compressedMessage.end();
|
message_end = _compressedMessage.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@@ -165,7 +165,7 @@ namespace ix
|
|||||||
MessageKind _fragmentedMessageKind;
|
MessageKind _fragmentedMessageKind;
|
||||||
|
|
||||||
// Ditto for whether a message is compressed
|
// Ditto for whether a message is compressed
|
||||||
bool _compressedMessage;
|
bool _receivedMessageCompressed;
|
||||||
|
|
||||||
// Fragments are 32K long
|
// Fragments are 32K long
|
||||||
static constexpr size_t kChunkSize = 1 << 15;
|
static constexpr size_t kChunkSize = 1 << 15;
|
||||||
@@ -189,6 +189,9 @@ namespace ix
|
|||||||
WebSocketPerMessageDeflateOptions _perMessageDeflateOptions;
|
WebSocketPerMessageDeflateOptions _perMessageDeflateOptions;
|
||||||
std::atomic<bool> _enablePerMessageDeflate;
|
std::atomic<bool> _enablePerMessageDeflate;
|
||||||
|
|
||||||
|
std::string _decompressedMessage;
|
||||||
|
std::string _compressedMessage;
|
||||||
|
|
||||||
// Used to control TLS connection behavior
|
// Used to control TLS connection behavior
|
||||||
SocketTLSOptions _socketTLSOptions;
|
SocketTLSOptions _socketTLSOptions;
|
||||||
|
|
||||||
@@ -209,6 +212,7 @@ namespace ix
|
|||||||
|
|
||||||
static const int kDefaultPingIntervalSecs;
|
static const int kDefaultPingIntervalSecs;
|
||||||
static const std::string kPingMessage;
|
static const std::string kPingMessage;
|
||||||
|
std::atomic<uint64_t> _pingCount;
|
||||||
|
|
||||||
// We record when ping are being sent so that we can know when to send the next one
|
// We record when ping are being sent so that we can know when to send the next one
|
||||||
mutable std::mutex _lastSendPingTimePointMutex;
|
mutable std::mutex _lastSendPingTimePointMutex;
|
||||||
|
@@ -6,4 +6,4 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define IX_WEBSOCKET_VERSION "9.2.1"
|
#define IX_WEBSOCKET_VERSION "9.5.2"
|
||||||
|
@@ -1,280 +0,0 @@
|
|||||||
/*
|
|
||||||
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
|
|
||||||
* https://github.com/corporateshark/LUrlParser
|
|
||||||
*
|
|
||||||
* The MIT License (MIT)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "LUrlParser.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstring>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
// check if the scheme name is valid
|
|
||||||
static bool IsSchemeValid(const std::string& SchemeName)
|
|
||||||
{
|
|
||||||
for (auto c : SchemeName)
|
|
||||||
{
|
|
||||||
if (!isalpha(c) && c != '+' && c != '-' && c != '.') return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LUrlParser::clParseURL::GetPort(int* OutPort) const
|
|
||||||
{
|
|
||||||
if (!IsValid())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Port = atoi(m_Port.c_str());
|
|
||||||
|
|
||||||
if (Port <= 0 || Port > 65535)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (OutPort)
|
|
||||||
{
|
|
||||||
*OutPort = Port;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// based on RFC 1738 and RFC 3986
|
|
||||||
LUrlParser::clParseURL LUrlParser::clParseURL::ParseURL(const std::string& URL)
|
|
||||||
{
|
|
||||||
LUrlParser::clParseURL Result;
|
|
||||||
|
|
||||||
const char* CurrentString = URL.c_str();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* <scheme>:<scheme-specific-part>
|
|
||||||
* <scheme> := [a-z\+\-\.]+
|
|
||||||
* For resiliency, programs interpreting URLs should treat upper case letters as equivalent to
|
|
||||||
*lower case in scheme names
|
|
||||||
*/
|
|
||||||
|
|
||||||
// try to read scheme
|
|
||||||
{
|
|
||||||
const char* LocalString = strchr(CurrentString, ':');
|
|
||||||
|
|
||||||
if (!LocalString)
|
|
||||||
{
|
|
||||||
return clParseURL(LUrlParserError_NoUrlCharacter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// save the scheme name
|
|
||||||
Result.m_Scheme = std::string(CurrentString, LocalString - CurrentString);
|
|
||||||
|
|
||||||
if (!IsSchemeValid(Result.m_Scheme))
|
|
||||||
{
|
|
||||||
return clParseURL(LUrlParserError_InvalidSchemeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// scheme should be lowercase
|
|
||||||
std::transform(
|
|
||||||
Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower);
|
|
||||||
|
|
||||||
// skip ':'
|
|
||||||
CurrentString = LocalString + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* //<user>:<password>@<host>:<port>/<url-path>
|
|
||||||
* any ":", "@" and "/" must be normalized
|
|
||||||
*/
|
|
||||||
|
|
||||||
// skip "//"
|
|
||||||
if (*CurrentString++ != '/') return clParseURL(LUrlParserError_NoDoubleSlash);
|
|
||||||
if (*CurrentString++ != '/') return clParseURL(LUrlParserError_NoDoubleSlash);
|
|
||||||
|
|
||||||
// check if the user name and password are specified
|
|
||||||
bool bHasUserName = false;
|
|
||||||
|
|
||||||
const char* LocalString = CurrentString;
|
|
||||||
|
|
||||||
while (*LocalString)
|
|
||||||
{
|
|
||||||
if (*LocalString == '@')
|
|
||||||
{
|
|
||||||
// user name and password are specified
|
|
||||||
bHasUserName = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (*LocalString == '/')
|
|
||||||
{
|
|
||||||
// end of <host>:<port> specification
|
|
||||||
bHasUserName = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
LocalString++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// user name and password
|
|
||||||
LocalString = CurrentString;
|
|
||||||
|
|
||||||
if (bHasUserName)
|
|
||||||
{
|
|
||||||
// read user name
|
|
||||||
while (*LocalString && *LocalString != ':' && *LocalString != '@')
|
|
||||||
LocalString++;
|
|
||||||
|
|
||||||
Result.m_UserName = std::string(CurrentString, LocalString - CurrentString);
|
|
||||||
|
|
||||||
// proceed with the current pointer
|
|
||||||
CurrentString = LocalString;
|
|
||||||
|
|
||||||
if (*CurrentString == ':')
|
|
||||||
{
|
|
||||||
// skip ':'
|
|
||||||
CurrentString++;
|
|
||||||
|
|
||||||
// read password
|
|
||||||
LocalString = CurrentString;
|
|
||||||
|
|
||||||
while (*LocalString && *LocalString != '@')
|
|
||||||
LocalString++;
|
|
||||||
|
|
||||||
Result.m_Password = std::string(CurrentString, LocalString - CurrentString);
|
|
||||||
|
|
||||||
CurrentString = LocalString;
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip '@'
|
|
||||||
if (*CurrentString != '@')
|
|
||||||
{
|
|
||||||
return clParseURL(LUrlParserError_NoAtSign);
|
|
||||||
}
|
|
||||||
|
|
||||||
CurrentString++;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool bHasBracket = (*CurrentString == '[');
|
|
||||||
|
|
||||||
// go ahead, read the host name
|
|
||||||
LocalString = CurrentString;
|
|
||||||
|
|
||||||
while (*LocalString)
|
|
||||||
{
|
|
||||||
if (bHasBracket && *LocalString == ']')
|
|
||||||
{
|
|
||||||
// end of IPv6 address
|
|
||||||
LocalString++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (!bHasBracket && (*LocalString == ':' || *LocalString == '/'))
|
|
||||||
{
|
|
||||||
// port number is specified
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
LocalString++;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result.m_Host = std::string(CurrentString, LocalString - CurrentString);
|
|
||||||
|
|
||||||
CurrentString = LocalString;
|
|
||||||
|
|
||||||
// is port number specified?
|
|
||||||
if (*CurrentString == ':')
|
|
||||||
{
|
|
||||||
CurrentString++;
|
|
||||||
|
|
||||||
// read port number
|
|
||||||
LocalString = CurrentString;
|
|
||||||
|
|
||||||
while (*LocalString && *LocalString != '/')
|
|
||||||
LocalString++;
|
|
||||||
|
|
||||||
Result.m_Port = std::string(CurrentString, LocalString - CurrentString);
|
|
||||||
|
|
||||||
CurrentString = LocalString;
|
|
||||||
}
|
|
||||||
|
|
||||||
// end of string
|
|
||||||
if (!*CurrentString)
|
|
||||||
{
|
|
||||||
Result.m_ErrorCode = LUrlParserError_Ok;
|
|
||||||
|
|
||||||
return Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip '/'
|
|
||||||
if (*CurrentString != '/')
|
|
||||||
{
|
|
||||||
return clParseURL(LUrlParserError_NoSlash);
|
|
||||||
}
|
|
||||||
|
|
||||||
CurrentString++;
|
|
||||||
|
|
||||||
// parse the path
|
|
||||||
LocalString = CurrentString;
|
|
||||||
|
|
||||||
while (*LocalString && *LocalString != '#' && *LocalString != '?')
|
|
||||||
LocalString++;
|
|
||||||
|
|
||||||
Result.m_Path = std::string(CurrentString, LocalString - CurrentString);
|
|
||||||
|
|
||||||
CurrentString = LocalString;
|
|
||||||
|
|
||||||
// check for query
|
|
||||||
if (*CurrentString == '?')
|
|
||||||
{
|
|
||||||
// skip '?'
|
|
||||||
CurrentString++;
|
|
||||||
|
|
||||||
// read query
|
|
||||||
LocalString = CurrentString;
|
|
||||||
|
|
||||||
while (*LocalString && *LocalString != '#')
|
|
||||||
LocalString++;
|
|
||||||
|
|
||||||
Result.m_Query = std::string(CurrentString, LocalString - CurrentString);
|
|
||||||
|
|
||||||
CurrentString = LocalString;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for fragment
|
|
||||||
if (*CurrentString == '#')
|
|
||||||
{
|
|
||||||
// skip '#'
|
|
||||||
CurrentString++;
|
|
||||||
|
|
||||||
// read fragment
|
|
||||||
LocalString = CurrentString;
|
|
||||||
|
|
||||||
while (*LocalString)
|
|
||||||
LocalString++;
|
|
||||||
|
|
||||||
Result.m_Fragment = std::string(CurrentString, LocalString - CurrentString);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result.m_ErrorCode = LUrlParserError_Ok;
|
|
||||||
|
|
||||||
return Result;
|
|
||||||
}
|
|
@@ -1,84 +0,0 @@
|
|||||||
/*
|
|
||||||
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
|
|
||||||
* https://github.com/corporateshark/LUrlParser
|
|
||||||
*
|
|
||||||
* The MIT License (MIT)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace LUrlParser
|
|
||||||
{
|
|
||||||
enum LUrlParserError
|
|
||||||
{
|
|
||||||
LUrlParserError_Ok = 0,
|
|
||||||
LUrlParserError_Uninitialized = 1,
|
|
||||||
LUrlParserError_NoUrlCharacter = 2,
|
|
||||||
LUrlParserError_InvalidSchemeName = 3,
|
|
||||||
LUrlParserError_NoDoubleSlash = 4,
|
|
||||||
LUrlParserError_NoAtSign = 5,
|
|
||||||
LUrlParserError_UnexpectedEndOfLine = 6,
|
|
||||||
LUrlParserError_NoSlash = 7,
|
|
||||||
};
|
|
||||||
|
|
||||||
class clParseURL
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
LUrlParserError m_ErrorCode;
|
|
||||||
std::string m_Scheme;
|
|
||||||
std::string m_Host;
|
|
||||||
std::string m_Port;
|
|
||||||
std::string m_Path;
|
|
||||||
std::string m_Query;
|
|
||||||
std::string m_Fragment;
|
|
||||||
std::string m_UserName;
|
|
||||||
std::string m_Password;
|
|
||||||
|
|
||||||
clParseURL()
|
|
||||||
: m_ErrorCode(LUrlParserError_Uninitialized)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// return 'true' if the parsing was successful
|
|
||||||
bool IsValid() const
|
|
||||||
{
|
|
||||||
return m_ErrorCode == LUrlParserError_Ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// helper to convert the port number to int, return 'true' if the port is valid (within the
|
|
||||||
/// 0..65535 range)
|
|
||||||
bool GetPort(int* OutPort) const;
|
|
||||||
|
|
||||||
/// parse the URL
|
|
||||||
static clParseURL ParseURL(const std::string& URL);
|
|
||||||
|
|
||||||
private:
|
|
||||||
explicit clParseURL(LUrlParserError ErrorCode)
|
|
||||||
: m_ErrorCode(ErrorCode)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace LUrlParser
|
|
@@ -1,135 +0,0 @@
|
|||||||
// Copyright (c) 2016 Alex Hultman and contributors
|
|
||||||
|
|
||||||
// This software is provided 'as-is', without any express or implied
|
|
||||||
// warranty. In no event will the authors be held liable for any damages
|
|
||||||
// arising from the use of this software.
|
|
||||||
|
|
||||||
// Permission is granted to anyone to use this software for any purpose,
|
|
||||||
// including commercial applications, and to alter it and redistribute it
|
|
||||||
// freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
// 1. The origin of this software must not be misrepresented; you must not
|
|
||||||
// claim that you wrote the original software. If you use this software
|
|
||||||
// in a product, an acknowledgement in the product documentation would be
|
|
||||||
// appreciated but is not required.
|
|
||||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
|
||||||
// misrepresented as being the original software.
|
|
||||||
// 3. This notice may not be removed or altered from any source distribution.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <string>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
class WebSocketHandshakeKeyGen {
|
|
||||||
template <int N, typename T>
|
|
||||||
struct static_for {
|
|
||||||
void operator()(uint32_t *a, uint32_t *b) {
|
|
||||||
static_for<N - 1, T>()(a, b);
|
|
||||||
T::template f<N - 1>(a, b);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct static_for<0, T> {
|
|
||||||
void operator()(uint32_t * /*a*/, uint32_t * /*hash*/) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <int state>
|
|
||||||
struct Sha1Loop {
|
|
||||||
static inline uint32_t rol(uint32_t value, size_t bits) {return (value << bits) | (value >> (32 - bits));}
|
|
||||||
static inline uint32_t blk(uint32_t b[16], size_t i) {
|
|
||||||
return rol(b[(i + 13) & 15] ^ b[(i + 8) & 15] ^ b[(i + 2) & 15] ^ b[i], 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <int i>
|
|
||||||
static inline void f(uint32_t *a, uint32_t *b) {
|
|
||||||
switch (state) {
|
|
||||||
case 1:
|
|
||||||
a[i % 5] += ((a[(3 + i) % 5] & (a[(2 + i) % 5] ^ a[(1 + i) % 5])) ^ a[(1 + i) % 5]) + b[i] + 0x5a827999 + rol(a[(4 + i) % 5], 5);
|
|
||||||
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
b[i] = blk(b, i);
|
|
||||||
a[(1 + i) % 5] += ((a[(4 + i) % 5] & (a[(3 + i) % 5] ^ a[(2 + i) % 5])) ^ a[(2 + i) % 5]) + b[i] + 0x5a827999 + rol(a[(5 + i) % 5], 5);
|
|
||||||
a[(4 + i) % 5] = rol(a[(4 + i) % 5], 30);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
b[(i + 4) % 16] = blk(b, (i + 4) % 16);
|
|
||||||
a[i % 5] += (a[(3 + i) % 5] ^ a[(2 + i) % 5] ^ a[(1 + i) % 5]) + b[(i + 4) % 16] + 0x6ed9eba1 + rol(a[(4 + i) % 5], 5);
|
|
||||||
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
b[(i + 8) % 16] = blk(b, (i + 8) % 16);
|
|
||||||
a[i % 5] += (((a[(3 + i) % 5] | a[(2 + i) % 5]) & a[(1 + i) % 5]) | (a[(3 + i) % 5] & a[(2 + i) % 5])) + b[(i + 8) % 16] + 0x8f1bbcdc + rol(a[(4 + i) % 5], 5);
|
|
||||||
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
b[(i + 12) % 16] = blk(b, (i + 12) % 16);
|
|
||||||
a[i % 5] += (a[(3 + i) % 5] ^ a[(2 + i) % 5] ^ a[(1 + i) % 5]) + b[(i + 12) % 16] + 0xca62c1d6 + rol(a[(4 + i) % 5], 5);
|
|
||||||
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
b[i] += a[4 - i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline void sha1(uint32_t hash[5], uint32_t b[16]) {
|
|
||||||
uint32_t a[5] = {hash[4], hash[3], hash[2], hash[1], hash[0]};
|
|
||||||
static_for<16, Sha1Loop<1>>()(a, b);
|
|
||||||
static_for<4, Sha1Loop<2>>()(a, b);
|
|
||||||
static_for<20, Sha1Loop<3>>()(a, b);
|
|
||||||
static_for<20, Sha1Loop<4>>()(a, b);
|
|
||||||
static_for<20, Sha1Loop<5>>()(a, b);
|
|
||||||
static_for<5, Sha1Loop<6>>()(a, hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void base64(unsigned char *src, char *dst) {
|
|
||||||
const char *b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
||||||
for (int i = 0; i < 18; i += 3) {
|
|
||||||
*dst++ = b64[(src[i] >> 2) & 63];
|
|
||||||
*dst++ = b64[((src[i] & 3) << 4) | ((src[i + 1] & 240) >> 4)];
|
|
||||||
*dst++ = b64[((src[i + 1] & 15) << 2) | ((src[i + 2] & 192) >> 6)];
|
|
||||||
*dst++ = b64[src[i + 2] & 63];
|
|
||||||
}
|
|
||||||
*dst++ = b64[(src[18] >> 2) & 63];
|
|
||||||
*dst++ = b64[((src[18] & 3) << 4) | ((src[19] & 240) >> 4)];
|
|
||||||
*dst++ = b64[((src[19] & 15) << 2)];
|
|
||||||
*dst++ = '=';
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
static inline void generate(const std::string& inputStr, char output[28]) {
|
|
||||||
|
|
||||||
char input[25] = {};
|
|
||||||
strncpy(input, inputStr.c_str(), 25 - 1);
|
|
||||||
input[25 - 1] = '\0';
|
|
||||||
|
|
||||||
uint32_t b_output[5] = {
|
|
||||||
0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0
|
|
||||||
};
|
|
||||||
uint32_t b_input[16] = {
|
|
||||||
0, 0, 0, 0, 0, 0, 0x32353845, 0x41464135, 0x2d453931, 0x342d3437, 0x44412d39,
|
|
||||||
0x3543412d, 0x43354142, 0x30444338, 0x35423131, 0x80000000
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int i = 0; i < 6; i++) {
|
|
||||||
b_input[i] = (input[4 * i + 3] & 0xff) | (input[4 * i + 2] & 0xff) << 8 | (input[4 * i + 1] & 0xff) << 16 | (input[4 * i + 0] & 0xff) << 24;
|
|
||||||
}
|
|
||||||
sha1(b_output, b_input);
|
|
||||||
uint32_t last_b[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 480};
|
|
||||||
sha1(b_output, last_b);
|
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
uint32_t tmp = b_output[i];
|
|
||||||
char *bytes = (char *) &b_output[i];
|
|
||||||
bytes[3] = tmp & 0xff;
|
|
||||||
bytes[2] = (tmp >> 8) & 0xff;
|
|
||||||
bytes[1] = (tmp >> 16) & 0xff;
|
|
||||||
bytes[0] = (tmp >> 24) & 0xff;
|
|
||||||
}
|
|
||||||
base64((unsigned char *) b_output, output);
|
|
||||||
}
|
|
||||||
};
|
|
30
luarocks/CMakeLists.txt
Normal file
30
luarocks/CMakeLists.txt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#
|
||||||
|
# Author: Benjamin Sergeant
|
||||||
|
# Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||||
|
#
|
||||||
|
|
||||||
|
cmake_minimum_required (VERSION 3.4.1)
|
||||||
|
project (luarocks)
|
||||||
|
|
||||||
|
# There's -Weverything too for clang
|
||||||
|
if (NOT WIN32)
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set (CMAKE_CXX_STANDARD 14)
|
||||||
|
|
||||||
|
option(USE_TLS "Add TLS support" ON)
|
||||||
|
|
||||||
|
include_directories(luarocks .)
|
||||||
|
|
||||||
|
add_executable(luarocks main.cpp)
|
||||||
|
|
||||||
|
include(FindLua)
|
||||||
|
find_package(lua REQUIRED)
|
||||||
|
target_link_libraries(luarocks ${LUA_LIBRARIES})
|
||||||
|
target_include_directories(luarocks PRIVATE ${LUA_INCLUDE_DIR})
|
||||||
|
|
||||||
|
# library with the most dependencies come first
|
||||||
|
target_link_libraries(luarocks ixwebsocket)
|
||||||
|
|
||||||
|
install(TARGETS luarocks RUNTIME DESTINATION bin)
|
163
luarocks/LuaWebSocket.h
Normal file
163
luarocks/LuaWebSocket.h
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
* LuaWebSocket.h
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#include "lua.h"
|
||||||
|
#include "lauxlib.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "luawrapper.hpp"
|
||||||
|
#include <ixwebsocket/IXWebSocket.h>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#include <queue>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
class LuaWebSocket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LuaWebSocket()
|
||||||
|
{
|
||||||
|
_webSocket.setOnMessageCallback([this](const WebSocketMessagePtr& msg) {
|
||||||
|
if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_queueMutex);
|
||||||
|
_queue.push(msg->str);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void setUrl(const std::string& url) { _webSocket.setUrl(url); }
|
||||||
|
const std::string& getUrl() { return _webSocket.getUrl(); }
|
||||||
|
void start() { _webSocket.start(); }
|
||||||
|
void send(const std::string& msg) { _webSocket.send(msg); }
|
||||||
|
|
||||||
|
const std::string& getMessage()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_queueMutex);
|
||||||
|
return _queue.front();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasMessage()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_queueMutex);
|
||||||
|
return !_queue.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void popMessage()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_queueMutex);
|
||||||
|
_queue.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
WebSocket _webSocket;
|
||||||
|
|
||||||
|
std::queue<std::string> _queue;
|
||||||
|
std::mutex _queueMutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
LuaWebSocket* WebSocket_new(lua_State* /*L*/)
|
||||||
|
{
|
||||||
|
LuaWebSocket* webSocket = new LuaWebSocket();
|
||||||
|
return webSocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WebSocket_getUrl(lua_State* L)
|
||||||
|
{
|
||||||
|
LuaWebSocket* luaWebSocket = luaW_check<LuaWebSocket>(L, 1);
|
||||||
|
lua_pushstring(L, luaWebSocket->getUrl().c_str());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WebSocket_setUrl(lua_State* L)
|
||||||
|
{
|
||||||
|
LuaWebSocket* luaWebSocket = luaW_check<LuaWebSocket>(L, 1);
|
||||||
|
const char* url = luaL_checkstring(L, 2);
|
||||||
|
luaWebSocket->setUrl(url);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WebSocket_start(lua_State* L)
|
||||||
|
{
|
||||||
|
LuaWebSocket* luaWebSocket = luaW_check<LuaWebSocket>(L, 1);
|
||||||
|
luaWebSocket->start();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WebSocket_send(lua_State* L)
|
||||||
|
{
|
||||||
|
LuaWebSocket* luaWebSocket = luaW_check<LuaWebSocket>(L, 1);
|
||||||
|
const char* msg = luaL_checkstring(L, 2);
|
||||||
|
luaWebSocket->send(msg);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WebSocket_getMessage(lua_State* L)
|
||||||
|
{
|
||||||
|
LuaWebSocket* luaWebSocket = luaW_check<LuaWebSocket>(L, 1);
|
||||||
|
lua_pushstring(L, luaWebSocket->getMessage().c_str());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WebSocket_hasMessage(lua_State* L)
|
||||||
|
{
|
||||||
|
LuaWebSocket* luaWebSocket = luaW_check<LuaWebSocket>(L, 1);
|
||||||
|
lua_pushboolean(L, luaWebSocket->hasMessage());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WebSocket_popMessage(lua_State* L)
|
||||||
|
{
|
||||||
|
LuaWebSocket* luaWebSocket = luaW_check<LuaWebSocket>(L, 1);
|
||||||
|
luaWebSocket->popMessage();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: This should be a static method, or be part of a different module
|
||||||
|
int WebSocket_sleep(lua_State* L)
|
||||||
|
{
|
||||||
|
// LuaWebSocket* luaWebSocket = luaW_check<LuaWebSocket>(L, 1);
|
||||||
|
auto duration = luaL_checkinteger(L, 2);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(duration));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static luaL_Reg WebSocket_table[] = {
|
||||||
|
{ NULL, NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
static luaL_Reg WebSocket_metatable[] = {
|
||||||
|
{ "getUrl", WebSocket_getUrl },
|
||||||
|
{ "setUrl", WebSocket_setUrl },
|
||||||
|
{ "start", WebSocket_start },
|
||||||
|
{ "send", WebSocket_send },
|
||||||
|
{ "getMessage", WebSocket_getMessage },
|
||||||
|
{ "hasMessage", WebSocket_hasMessage },
|
||||||
|
{ "popMessage", WebSocket_popMessage },
|
||||||
|
{ "sleep", WebSocket_sleep },
|
||||||
|
{ NULL, NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
int luaopen_WebSocket(lua_State* L)
|
||||||
|
{
|
||||||
|
luaW_register<LuaWebSocket>(L,
|
||||||
|
"WebSocket",
|
||||||
|
WebSocket_table,
|
||||||
|
WebSocket_metatable,
|
||||||
|
WebSocket_new
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
56
luarocks/Player.hpp
Normal file
56
luarocks/Player.hpp
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#ifndef PLAYER_HPP
|
||||||
|
#define PLAYER_HPP
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
class Player
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Player(const char* name, unsigned int health) :
|
||||||
|
m_Name(name),
|
||||||
|
m_Health(health)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void info()
|
||||||
|
{
|
||||||
|
std::cout << m_Name << " have " << m_Health << " HP" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void say(const char* text)
|
||||||
|
{
|
||||||
|
std::cout << m_Name << ": " << text << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void heal(Player* target)
|
||||||
|
{
|
||||||
|
target->setHealth(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* getName()
|
||||||
|
{
|
||||||
|
return m_Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int getHealth()
|
||||||
|
{
|
||||||
|
return m_Health;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool setHealth(unsigned int health)
|
||||||
|
{
|
||||||
|
if (health >= 0 && health <= 100)
|
||||||
|
{
|
||||||
|
m_Health = health;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const char* m_Name;
|
||||||
|
unsigned int m_Health;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PLAYER_HPP
|
7
luarocks/README.md
Normal file
7
luarocks/README.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Wrapper based on https://github.com/LuaxY/cpp-lua
|
||||||
|
|
||||||
|
Examples to build C++
|
||||||
|
https://github.com/siffiejoe/lua-fltk4lua/blob/master/fltk4lua-scm-0.rockspec
|
||||||
|
https://github.com/lua4web/refser/blob/master/rockspecs/refser-0.2-1.rockspec
|
||||||
|
|
||||||
|
|
30
luarocks/echo_client.lua
Normal file
30
luarocks/echo_client.lua
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
--
|
||||||
|
-- make luarocks && (cd luarocks ; ../build/luarocks/luarocks)
|
||||||
|
--
|
||||||
|
-- ... git push test with ssh key
|
||||||
|
--
|
||||||
|
local webSocket = WebSocket.new()
|
||||||
|
|
||||||
|
webSocket:setUrl("ws://localhost:8008")
|
||||||
|
print("Url: " .. webSocket:getUrl())
|
||||||
|
|
||||||
|
-- Start the background thread
|
||||||
|
webSocket:start()
|
||||||
|
|
||||||
|
local i = 0
|
||||||
|
|
||||||
|
while true
|
||||||
|
do
|
||||||
|
print("Sending message...")
|
||||||
|
webSocket:send("msg_" .. tostring(i));
|
||||||
|
i = i + 1
|
||||||
|
|
||||||
|
print("Waiting 1s...")
|
||||||
|
webSocket:sleep(1000)
|
||||||
|
|
||||||
|
if webSocket:hasMessage() then
|
||||||
|
local msg = webSocket:getMessage()
|
||||||
|
print("Received: " .. msg)
|
||||||
|
webSocket:popMessage()
|
||||||
|
end
|
||||||
|
end
|
15
luarocks/functions.hpp
Normal file
15
luarocks/functions.hpp
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* functions.hpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int lua_info(lua_State* /*L*/)
|
||||||
|
{
|
||||||
|
std::cout << "C++ Lua v0.1" << std::endl << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
709
luarocks/luawrapper.hpp
Normal file
709
luarocks/luawrapper.hpp
Normal file
@@ -0,0 +1,709 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2013 Alexander Ames
|
||||||
|
* Alexander.Ames@gmail.com
|
||||||
|
* See Copyright Notice at the end of this file
|
||||||
|
*/
|
||||||
|
|
||||||
|
// API Summary:
|
||||||
|
//
|
||||||
|
// LuaWrapper is a library designed to help bridge the gab between Lua and
|
||||||
|
// C++. It is designed to be small (a single header file), simple, fast,
|
||||||
|
// and typesafe. It has no external dependencies, and does not need to be
|
||||||
|
// precompiled; the header can simply be dropped into a project and used
|
||||||
|
// immediately. It even supports class inheritance to a certain degree. Objects
|
||||||
|
// can be created in either Lua or C++, and passed back and forth.
|
||||||
|
//
|
||||||
|
// The main functions of interest are the following:
|
||||||
|
// luaW_is<T>
|
||||||
|
// luaW_to<T>
|
||||||
|
// luaW_check<T>
|
||||||
|
// luaW_push<T>
|
||||||
|
// luaW_register<T>
|
||||||
|
// luaW_setfuncs<T>
|
||||||
|
// luaW_extend<T, U>
|
||||||
|
// luaW_hold<T>
|
||||||
|
// luaW_release<T>
|
||||||
|
//
|
||||||
|
// These functions allow you to manipulate arbitrary classes just like you
|
||||||
|
// would the primitive types (e.g. numbers or strings). If you are familiar
|
||||||
|
// with the normal Lua API the behavior of these functions should be very
|
||||||
|
// intuative.
|
||||||
|
//
|
||||||
|
// For more information see the README and the comments below
|
||||||
|
|
||||||
|
#ifndef LUA_WRAPPER_H_
|
||||||
|
#define LUA_WRAPPER_H_
|
||||||
|
|
||||||
|
// If you are linking against Lua compiled in C++, define LUAW_NO_EXTERN_C
|
||||||
|
#ifndef LUAW_NO_EXTERN_C
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif // LUAW_NO_EXTERN_C
|
||||||
|
|
||||||
|
#include "lua.h"
|
||||||
|
#include "lauxlib.h"
|
||||||
|
|
||||||
|
#ifndef LUAW_NO_EXTERN_C
|
||||||
|
}
|
||||||
|
#endif // LUAW_NO_EXTERN_C
|
||||||
|
|
||||||
|
#define LUAW_POSTCTOR_KEY "__postctor"
|
||||||
|
#define LUAW_EXTENDS_KEY "__extends"
|
||||||
|
#define LUAW_STORAGE_KEY "storage"
|
||||||
|
#define LUAW_CACHE_KEY "cache"
|
||||||
|
#define LUAW_CACHE_METATABLE_KEY "cachemetatable"
|
||||||
|
#define LUAW_HOLDS_KEY "holds"
|
||||||
|
#define LUAW_WRAPPER_KEY "LuaWrapper"
|
||||||
|
|
||||||
|
// A simple utility function to adjust a given index
|
||||||
|
// Useful for when a parameter index needs to be adjusted
|
||||||
|
// after pushing or popping things off the stack
|
||||||
|
inline int luaW_correctindex(lua_State* /*L*/, int index, int correction)
|
||||||
|
{
|
||||||
|
return index < 0 ? index - correction : index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are the default allocator and deallocator. If you would prefer an
|
||||||
|
// alternative option, you may select a different function when registering
|
||||||
|
// your class.
|
||||||
|
template <typename T>
|
||||||
|
T* luaW_defaultallocator(lua_State*)
|
||||||
|
{
|
||||||
|
return new T();
|
||||||
|
}
|
||||||
|
template <typename T>
|
||||||
|
void luaW_defaultdeallocator(lua_State*, T* obj)
|
||||||
|
{
|
||||||
|
delete obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The identifier function is responsible for pushing a value unique to each
|
||||||
|
// object on to the stack. Most of the time, this can simply be the address
|
||||||
|
// of the pointer, but sometimes that is not adaquate. For example, if you
|
||||||
|
// are using shared_ptr you would need to push the address of the object the
|
||||||
|
// shared_ptr represents, rather than the address of the shared_ptr itself.
|
||||||
|
template <typename T>
|
||||||
|
void luaW_defaultidentifier(lua_State* L, T* obj)
|
||||||
|
{
|
||||||
|
lua_pushlightuserdata(L, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This class is what is used by LuaWrapper to contain the userdata. data
|
||||||
|
// stores a pointer to the object itself, and cast is used to cast toward the
|
||||||
|
// base class if there is one and it is necessary. Rather than use RTTI and
|
||||||
|
// typid to compare types, I use the clever trick of using the cast to compare
|
||||||
|
// types. Because there is at most one cast per type, I can use it to identify
|
||||||
|
// when and object is the type I want. This is only used internally.
|
||||||
|
struct luaW_Userdata
|
||||||
|
{
|
||||||
|
luaW_Userdata(void* vptr = NULL, luaW_Userdata (*udcast)(const luaW_Userdata&) = NULL)
|
||||||
|
: data(vptr), cast(udcast) {}
|
||||||
|
void* data;
|
||||||
|
luaW_Userdata (*cast)(const luaW_Userdata&);
|
||||||
|
};
|
||||||
|
|
||||||
|
// This class cannot actually to be instantiated. It is used only hold the
|
||||||
|
// table name and other information.
|
||||||
|
template <typename T>
|
||||||
|
class LuaWrapper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static const char* classname;
|
||||||
|
static void (*identifier)(lua_State*, T*);
|
||||||
|
static T* (*allocator)(lua_State*);
|
||||||
|
static void (*deallocator)(lua_State*, T*);
|
||||||
|
static luaW_Userdata (*cast)(const luaW_Userdata&);
|
||||||
|
private:
|
||||||
|
LuaWrapper();
|
||||||
|
};
|
||||||
|
template <typename T> const char* LuaWrapper<T>::classname;
|
||||||
|
template <typename T> void (*LuaWrapper<T>::identifier)(lua_State*, T*);
|
||||||
|
template <typename T> T* (*LuaWrapper<T>::allocator)(lua_State*);
|
||||||
|
template <typename T> void (*LuaWrapper<T>::deallocator)(lua_State*, T*);
|
||||||
|
template <typename T> luaW_Userdata (*LuaWrapper<T>::cast)(const luaW_Userdata&);
|
||||||
|
|
||||||
|
// Cast from an object of type T to an object of type U. This template
|
||||||
|
// function is instantiated by calling luaW_extend<T, U>(L). This is only used
|
||||||
|
// internally.
|
||||||
|
template <typename T, typename U>
|
||||||
|
luaW_Userdata luaW_cast(const luaW_Userdata& obj)
|
||||||
|
{
|
||||||
|
return luaW_Userdata(static_cast<U*>(static_cast<T*>(obj.data)), LuaWrapper<U>::cast);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U>
|
||||||
|
void luaW_identify(lua_State* L, T* obj)
|
||||||
|
{
|
||||||
|
LuaWrapper<U>::identifier(L, static_cast<U*>(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline void luaW_wrapperfield(lua_State* L, const char* field)
|
||||||
|
{
|
||||||
|
lua_getfield(L, LUA_REGISTRYINDEX, LUAW_WRAPPER_KEY); // ... LuaWrapper
|
||||||
|
lua_getfield(L, -1, field); // ... LuaWrapper LuaWrapper.field
|
||||||
|
lua_getfield(L, -1, LuaWrapper<T>::classname); // ... LuaWrapper LuaWrapper.field LuaWrapper.field.class
|
||||||
|
lua_replace(L, -3); // ... LuaWrapper.field.class LuaWrapper.field
|
||||||
|
lua_pop(L, 1); // ... LuaWrapper.field.class
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analogous to lua_is(boolean|string|*)
|
||||||
|
//
|
||||||
|
// Returns 1 if the value at the given acceptable index is of type T (or if
|
||||||
|
// strict is false, convertable to type T) and 0 otherwise.
|
||||||
|
template <typename T>
|
||||||
|
bool luaW_is(lua_State *L, int index, bool strict = false)
|
||||||
|
{
|
||||||
|
bool equal = false;// lua_isnil(L, index);
|
||||||
|
if (!equal && lua_isuserdata(L, index) && lua_getmetatable(L, index))
|
||||||
|
{
|
||||||
|
// ... ud ... udmt
|
||||||
|
luaL_getmetatable(L, LuaWrapper<T>::classname); // ... ud ... udmt Tmt
|
||||||
|
equal = lua_rawequal(L, -1, -2) != 0;
|
||||||
|
if (!equal && !strict)
|
||||||
|
{
|
||||||
|
lua_getfield(L, -2, LUAW_EXTENDS_KEY); // ... ud ... udmt Tmt udmt.extends
|
||||||
|
for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1))
|
||||||
|
{
|
||||||
|
// ... ud ... udmt Tmt udmt.extends k v
|
||||||
|
equal = lua_rawequal(L, -1, -4) != 0;
|
||||||
|
if (equal)
|
||||||
|
{
|
||||||
|
lua_pop(L, 2); // ... ud ... udmt Tmt udmt.extends
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lua_pop(L, 1); // ... ud ... udmt Tmt
|
||||||
|
}
|
||||||
|
lua_pop(L, 2); // ... ud ...
|
||||||
|
}
|
||||||
|
return equal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analogous to lua_to(boolean|string|*)
|
||||||
|
//
|
||||||
|
// Converts the given acceptable index to a T*. That value must be of (or
|
||||||
|
// convertable to) type T; otherwise, returns NULL.
|
||||||
|
template <typename T>
|
||||||
|
T* luaW_to(lua_State* L, int index, bool strict = false)
|
||||||
|
{
|
||||||
|
if (luaW_is<T>(L, index, strict))
|
||||||
|
{
|
||||||
|
luaW_Userdata* pud = static_cast<luaW_Userdata*>(lua_touserdata(L, index));
|
||||||
|
luaW_Userdata ud;
|
||||||
|
while (!strict && LuaWrapper<T>::cast != pud->cast)
|
||||||
|
{
|
||||||
|
ud = pud->cast(*pud);
|
||||||
|
pud = &ud;
|
||||||
|
}
|
||||||
|
return static_cast<T*>(pud->data);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analogous to luaL_check(boolean|string|*)
|
||||||
|
//
|
||||||
|
// Converts the given acceptable index to a T*. That value must be of (or
|
||||||
|
// convertable to) type T; otherwise, an error is raised.
|
||||||
|
template <typename T>
|
||||||
|
T* luaW_check(lua_State* L, int index, bool strict = false)
|
||||||
|
{
|
||||||
|
T* obj = NULL;
|
||||||
|
if (luaW_is<T>(L, index, strict))
|
||||||
|
{
|
||||||
|
luaW_Userdata* pud = (luaW_Userdata*)lua_touserdata(L, index);
|
||||||
|
luaW_Userdata ud;
|
||||||
|
while (!strict && LuaWrapper<T>::cast != pud->cast)
|
||||||
|
{
|
||||||
|
ud = pud->cast(*pud);
|
||||||
|
pud = &ud;
|
||||||
|
}
|
||||||
|
obj = (T*)pud->data;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char *msg = lua_pushfstring(L, "%s expected, got %s", LuaWrapper<T>::classname, luaL_typename(L, index));
|
||||||
|
luaL_argerror(L, index, msg);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T* luaW_opt(lua_State* L, int index, T* fallback = NULL, bool strict = false)
|
||||||
|
{
|
||||||
|
if (lua_isnil(L, index))
|
||||||
|
return fallback;
|
||||||
|
else
|
||||||
|
return luaW_check<T>(L, index, strict);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analogous to lua_push(boolean|string|*)
|
||||||
|
//
|
||||||
|
// Pushes a userdata of type T onto the stack. If this object already exists in
|
||||||
|
// the Lua environment, it will assign the existing storage table to it.
|
||||||
|
// Otherwise, a new storage table will be created for it.
|
||||||
|
template <typename T>
|
||||||
|
void luaW_push(lua_State* L, T* obj)
|
||||||
|
{
|
||||||
|
if (obj)
|
||||||
|
{
|
||||||
|
LuaWrapper<T>::identifier(L, obj); // ... id
|
||||||
|
luaW_wrapperfield<T>(L, LUAW_CACHE_KEY); // ... id cache
|
||||||
|
lua_pushvalue(L, -2); // ... id cache id
|
||||||
|
lua_gettable(L, -2); // ... id cache obj
|
||||||
|
if (lua_isnil(L, -1))
|
||||||
|
{
|
||||||
|
// Create the new luaW_userdata and place it in the cache
|
||||||
|
lua_pop(L, 1); // ... id cache
|
||||||
|
lua_insert(L, -2); // ... cache id
|
||||||
|
luaW_Userdata* ud = static_cast<luaW_Userdata*>(lua_newuserdata(L, sizeof(luaW_Userdata))); // ... cache id obj
|
||||||
|
ud->data = obj;
|
||||||
|
ud->cast = LuaWrapper<T>::cast;
|
||||||
|
lua_pushvalue(L, -1); // ... cache id obj obj
|
||||||
|
lua_insert(L, -4); // ... obj cache id obj
|
||||||
|
lua_settable(L, -3); // ... obj cache
|
||||||
|
|
||||||
|
luaL_getmetatable(L, LuaWrapper<T>::classname); // ... obj cache mt
|
||||||
|
lua_setmetatable(L, -3); // ... obj cache
|
||||||
|
|
||||||
|
lua_pop(L, 1); // ... obj
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lua_replace(L, -3); // ... obj cache
|
||||||
|
lua_pop(L, 1); // ... obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lua_pushnil(L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instructs LuaWrapper that it owns the userdata, and can manage its memory.
|
||||||
|
// When all references to the object are removed, Lua is free to garbage
|
||||||
|
// collect it and delete the object.
|
||||||
|
//
|
||||||
|
// Returns true if luaW_hold took hold of the object, and false if it was
|
||||||
|
// already held
|
||||||
|
template <typename T>
|
||||||
|
bool luaW_hold(lua_State* L, T* obj)
|
||||||
|
{
|
||||||
|
luaW_wrapperfield<T>(L, LUAW_HOLDS_KEY); // ... holds
|
||||||
|
LuaWrapper<T>::identifier(L, obj); // ... holds id
|
||||||
|
lua_pushvalue(L, -1); // ... holds id id
|
||||||
|
lua_gettable(L, -3); // ... holds id hold
|
||||||
|
// If it's not held, hold it
|
||||||
|
if (!lua_toboolean(L, -1))
|
||||||
|
{
|
||||||
|
// Apply hold boolean
|
||||||
|
lua_pop(L, 1); // ... holds id
|
||||||
|
lua_pushboolean(L, true); // ... holds id true
|
||||||
|
lua_settable(L, -3); // ... holds
|
||||||
|
lua_pop(L, 1); // ...
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
lua_pop(L, 3); // ...
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Releases LuaWrapper's hold on an object. This allows the user to remove
|
||||||
|
// all references to an object in Lua and ensure that Lua will not attempt to
|
||||||
|
// garbage collect it.
|
||||||
|
//
|
||||||
|
// This function takes the index of the identifier for an object rather than
|
||||||
|
// the object itself. This is because needs to be able to run after the object
|
||||||
|
// has already been deallocated. A wrapper is provided for when it is more
|
||||||
|
// convenient to pass in the object directly.
|
||||||
|
template <typename T>
|
||||||
|
void luaW_release(lua_State* L, int index)
|
||||||
|
{
|
||||||
|
luaW_wrapperfield<T>(L, LUAW_HOLDS_KEY); // ... id ... holds
|
||||||
|
lua_pushvalue(L, luaW_correctindex(L, index, 1)); // ... id ... holds id
|
||||||
|
lua_pushnil(L); // ... id ... holds id nil
|
||||||
|
lua_settable(L, -3); // ... id ... holds
|
||||||
|
lua_pop(L, 1); // ... id ...
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void luaW_release(lua_State* L, T* obj)
|
||||||
|
{
|
||||||
|
LuaWrapper<T>::identifier(L, obj); // ... id
|
||||||
|
luaW_release<T>(L, -1); // ... id
|
||||||
|
lua_pop(L, 1); // ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is called from Lua, not C++
|
||||||
|
//
|
||||||
|
// Calls the lua post-constructor (LUAW_POSTCTOR_KEY or "__postctor") on a
|
||||||
|
// userdata. Assumes the userdata is on top of the stack, and numargs arguments
|
||||||
|
// are below it. This runs the LUAW_POSTCTOR_KEY function on T's metatable,
|
||||||
|
// using the object as the first argument and whatever else is below it as
|
||||||
|
// the rest of the arguments This exists to allow types to adjust values in
|
||||||
|
// thier storage table, which can not be created until after the constructor is
|
||||||
|
// called.
|
||||||
|
template <typename T>
|
||||||
|
void luaW_postconstructor(lua_State* L, int numargs)
|
||||||
|
{
|
||||||
|
// ... args... ud
|
||||||
|
lua_getfield(L, -1, LUAW_POSTCTOR_KEY); // ... args... ud ud.__postctor
|
||||||
|
if (lua_type(L, -1) == LUA_TFUNCTION)
|
||||||
|
{
|
||||||
|
lua_pushvalue(L, -2); // ... args... ud ud.__postctor ud
|
||||||
|
lua_insert(L, -3 - numargs); // ... ud args... ud ud.__postctor
|
||||||
|
lua_insert(L, -3 - numargs); // ... ud.__postctor ud args... ud
|
||||||
|
lua_insert(L, -3 - numargs); // ... ud ud.__postctor ud args...
|
||||||
|
lua_call(L, numargs + 1, 0); // ... ud
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lua_pop(L, 1); // ... ud
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is generally called from Lua, not C++
|
||||||
|
//
|
||||||
|
// Creates an object of type T using the constructor and subsequently calls the
|
||||||
|
// post-constructor on it.
|
||||||
|
template <typename T>
|
||||||
|
inline int luaW_new(lua_State* L, int args)
|
||||||
|
{
|
||||||
|
T* obj = LuaWrapper<T>::allocator(L);
|
||||||
|
luaW_push<T>(L, obj);
|
||||||
|
luaW_hold<T>(L, obj);
|
||||||
|
luaW_postconstructor<T>(L, args);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
int luaW_new(lua_State* L)
|
||||||
|
{
|
||||||
|
return luaW_new<T>(L, lua_gettop(L));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is called from Lua, not C++
|
||||||
|
//
|
||||||
|
// The default metamethod to call when indexing into lua userdata representing
|
||||||
|
// an object of type T. This will first check the userdata's environment table
|
||||||
|
// and if it's not found there it will check the metatable. This is done so
|
||||||
|
// individual userdata can be treated as a table, and can hold thier own
|
||||||
|
// values.
|
||||||
|
template <typename T>
|
||||||
|
int luaW_index(lua_State* L)
|
||||||
|
{
|
||||||
|
// obj key
|
||||||
|
T* obj = luaW_to<T>(L, 1);
|
||||||
|
luaW_wrapperfield<T>(L, LUAW_STORAGE_KEY); // obj key storage
|
||||||
|
LuaWrapper<T>::identifier(L, obj); // obj key storage id
|
||||||
|
lua_gettable(L, -2); // obj key storage store
|
||||||
|
|
||||||
|
// Check if storage table exists
|
||||||
|
if (!lua_isnil(L, -1))
|
||||||
|
{
|
||||||
|
lua_pushvalue(L, -3); // obj key storage store key
|
||||||
|
lua_gettable(L, -2); // obj key storage store store[k]
|
||||||
|
}
|
||||||
|
|
||||||
|
// If either there is no storage table or the key wasn't found
|
||||||
|
// then fall back to the metatable
|
||||||
|
if (lua_isnil(L, -1))
|
||||||
|
{
|
||||||
|
lua_settop(L, 2); // obj key
|
||||||
|
lua_getmetatable(L, -2); // obj key mt
|
||||||
|
lua_pushvalue(L, -2); // obj key mt k
|
||||||
|
lua_gettable(L, -2); // obj key mt mt[k]
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is called from Lua, not C++
|
||||||
|
//
|
||||||
|
// The default metamethod to call when creating a new index on lua userdata
|
||||||
|
// representing an object of type T. This will index into the the userdata's
|
||||||
|
// environment table that it keeps for personal storage. This is done so
|
||||||
|
// individual userdata can be treated as a table, and can hold thier own
|
||||||
|
// values.
|
||||||
|
template <typename T>
|
||||||
|
int luaW_newindex(lua_State* L)
|
||||||
|
{
|
||||||
|
// obj key value
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
luaW_wrapperfield<T>(L, LUAW_STORAGE_KEY); // obj key value storage
|
||||||
|
LuaWrapper<T>::identifier(L, obj); // obj key value storage id
|
||||||
|
lua_pushvalue(L, -1); // obj key value storage id id
|
||||||
|
lua_gettable(L, -3); // obj key value storage id store
|
||||||
|
|
||||||
|
// Add the storage table if there isn't one already
|
||||||
|
if (lua_isnil(L, -1))
|
||||||
|
{
|
||||||
|
lua_pop(L, 1); // obj key value storage id
|
||||||
|
lua_newtable(L); // obj key value storage id store
|
||||||
|
lua_pushvalue(L, -1); // obj key value storage id store store
|
||||||
|
lua_insert(L, -3); // obj key value storage store id store
|
||||||
|
lua_settable(L, -4); // obj key value storage store
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushvalue(L, 2); // obj key value ... store key
|
||||||
|
lua_pushvalue(L, 3); // obj key value ... store key value
|
||||||
|
lua_settable(L, -3); // obj key value ... store
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is called from Lua, not C++
|
||||||
|
//
|
||||||
|
// The __gc metamethod handles cleaning up userdata. The userdata's reference
|
||||||
|
// count is decremented and if this is the final reference to the userdata its
|
||||||
|
// environment table is nil'd and pointer deleted with the destructor callback.
|
||||||
|
template <typename T>
|
||||||
|
int luaW_gc(lua_State* L)
|
||||||
|
{
|
||||||
|
// obj
|
||||||
|
T* obj = luaW_to<T>(L, 1);
|
||||||
|
LuaWrapper<T>::identifier(L, obj); // obj key value storage id
|
||||||
|
luaW_wrapperfield<T>(L, LUAW_HOLDS_KEY); // obj id counts count holds
|
||||||
|
lua_pushvalue(L, 2); // obj id counts count holds id
|
||||||
|
lua_gettable(L, -2); // obj id counts count holds hold
|
||||||
|
if (lua_toboolean(L, -1) && LuaWrapper<T>::deallocator)
|
||||||
|
{
|
||||||
|
LuaWrapper<T>::deallocator(L, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
luaW_wrapperfield<T>(L, LUAW_STORAGE_KEY); // obj id counts count holds hold storage
|
||||||
|
lua_pushvalue(L, 2); // obj id counts count holds hold storage id
|
||||||
|
lua_pushnil(L); // obj id counts count holds hold storage id nil
|
||||||
|
lua_settable(L, -3); // obj id counts count holds hold storage
|
||||||
|
|
||||||
|
luaW_release<T>(L, 2);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thakes two tables and registers them with Lua to the table on the top of the
|
||||||
|
// stack.
|
||||||
|
//
|
||||||
|
// This function is only called from LuaWrapper internally.
|
||||||
|
inline void luaW_registerfuncs(lua_State* L, const luaL_Reg defaulttable[], const luaL_Reg table[])
|
||||||
|
{
|
||||||
|
// ... T
|
||||||
|
#if LUA_VERSION_NUM > 501
|
||||||
|
if (defaulttable)
|
||||||
|
luaL_setfuncs(L, defaulttable, 0); // ... T
|
||||||
|
if (table)
|
||||||
|
luaL_setfuncs(L, table, 0); // ... T
|
||||||
|
#else
|
||||||
|
if (defaulttable)
|
||||||
|
luaL_register(L, NULL, defaulttable); // ... T
|
||||||
|
if (table)
|
||||||
|
luaL_register(L, NULL, table); // ... T
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initializes the LuaWrapper tables used to track internal state.
|
||||||
|
//
|
||||||
|
// This function is only called from LuaWrapper internally.
|
||||||
|
inline void luaW_initialize(lua_State* L)
|
||||||
|
{
|
||||||
|
// Ensure that the LuaWrapper table is set up
|
||||||
|
lua_getfield(L, LUA_REGISTRYINDEX, LUAW_WRAPPER_KEY); // ... LuaWrapper
|
||||||
|
if (lua_isnil(L, -1))
|
||||||
|
{
|
||||||
|
lua_newtable(L); // ... nil {}
|
||||||
|
lua_pushvalue(L, -1); // ... nil {} {}
|
||||||
|
lua_setfield(L, LUA_REGISTRYINDEX, LUAW_WRAPPER_KEY); // ... nil LuaWrapper
|
||||||
|
|
||||||
|
// Create a storage table
|
||||||
|
lua_newtable(L); // ... LuaWrapper nil {}
|
||||||
|
lua_setfield(L, -2, LUAW_STORAGE_KEY); // ... nil LuaWrapper
|
||||||
|
|
||||||
|
// Create a holds table
|
||||||
|
lua_newtable(L); // ... LuaWrapper {}
|
||||||
|
lua_setfield(L, -2, LUAW_HOLDS_KEY); // ... nil LuaWrapper
|
||||||
|
|
||||||
|
// Create a cache table, with weak values so that the userdata will not
|
||||||
|
// be ref counted
|
||||||
|
lua_newtable(L); // ... nil LuaWrapper {}
|
||||||
|
lua_setfield(L, -2, LUAW_CACHE_KEY); // ... nil LuaWrapper
|
||||||
|
|
||||||
|
lua_newtable(L); // ... nil LuaWrapper {}
|
||||||
|
lua_pushstring(L, "v"); // ... nil LuaWrapper {} "v"
|
||||||
|
lua_setfield(L, -2, "__mode"); // ... nil LuaWrapper {}
|
||||||
|
lua_setfield(L, -2, LUAW_CACHE_METATABLE_KEY); // ... nil LuaWrapper
|
||||||
|
|
||||||
|
lua_pop(L, 1); // ... nil
|
||||||
|
}
|
||||||
|
lua_pop(L, 1); // ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run luaW_register or luaW_setfuncs to create a table and metatable for your
|
||||||
|
// class. These functions create a table with filled with the function from
|
||||||
|
// the table argument in addition to the functions new and build (This is
|
||||||
|
// generally for things you think of as static methods in C++). The given
|
||||||
|
// metatable argument becomes a metatable for each object of your class. These
|
||||||
|
// can be thought of as member functions or methods.
|
||||||
|
//
|
||||||
|
// You may also supply constructors and destructors for classes that do not
|
||||||
|
// have a default constructor or that require special set up or tear down. You
|
||||||
|
// may specify NULL as the constructor, which means that you will not be able
|
||||||
|
// to call the new function on your class table. You will need to manually push
|
||||||
|
// objects from C++. By default, the default constructor is used to create
|
||||||
|
// objects and a simple call to delete is used to destroy them.
|
||||||
|
//
|
||||||
|
// By default LuaWrapper uses the address of C++ object to identify unique
|
||||||
|
// objects. In some cases this is not desired, such as in the case of
|
||||||
|
// shared_ptrs. Two shared_ptrs may themselves have unique locations in memory
|
||||||
|
// but still represent the same object. For cases like that, you may specify an
|
||||||
|
// identifier function which is responsible for pushing a key representing your
|
||||||
|
// object on to the stack.
|
||||||
|
//
|
||||||
|
// luaW_register will set table as the new value of the global of the given
|
||||||
|
// name. luaW_setfuncs is identical to luaW_register, but it does not set the
|
||||||
|
// table globally. As with luaL_register and luaL_setfuncs, both funcstions
|
||||||
|
// leave the new table on the top of the stack.
|
||||||
|
template <typename T>
|
||||||
|
void luaW_setfuncs(lua_State* L, const char* classname, const luaL_Reg* table, const luaL_Reg* metatable, T* (*allocator)(lua_State*) = luaW_defaultallocator<T>, void (*deallocator)(lua_State*, T*) = luaW_defaultdeallocator<T>, void (*identifier)(lua_State*, T*) = luaW_defaultidentifier<T>)
|
||||||
|
{
|
||||||
|
luaW_initialize(L);
|
||||||
|
|
||||||
|
LuaWrapper<T>::classname = classname;
|
||||||
|
LuaWrapper<T>::identifier = identifier;
|
||||||
|
LuaWrapper<T>::allocator = allocator;
|
||||||
|
LuaWrapper<T>::deallocator = deallocator;
|
||||||
|
|
||||||
|
const luaL_Reg defaulttable[] =
|
||||||
|
{
|
||||||
|
{ "new", luaW_new<T> },
|
||||||
|
{ NULL, NULL }
|
||||||
|
};
|
||||||
|
const luaL_Reg defaultmetatable[] =
|
||||||
|
{
|
||||||
|
{ "__index", luaW_index<T> },
|
||||||
|
{ "__newindex", luaW_newindex<T> },
|
||||||
|
{ "__gc", luaW_gc<T> },
|
||||||
|
{ NULL, NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set up per-type tables
|
||||||
|
lua_getfield(L, LUA_REGISTRYINDEX, LUAW_WRAPPER_KEY); // ... LuaWrapper
|
||||||
|
|
||||||
|
lua_getfield(L, -1, LUAW_STORAGE_KEY); // ... LuaWrapper LuaWrapper.storage
|
||||||
|
lua_newtable(L); // ... LuaWrapper LuaWrapper.storage {}
|
||||||
|
lua_setfield(L, -2, LuaWrapper<T>::classname); // ... LuaWrapper LuaWrapper.storage
|
||||||
|
lua_pop(L, 1); // ... LuaWrapper
|
||||||
|
|
||||||
|
lua_getfield(L, -1, LUAW_HOLDS_KEY); // ... LuaWrapper LuaWrapper.holds
|
||||||
|
lua_newtable(L); // ... LuaWrapper LuaWrapper.holds {}
|
||||||
|
lua_setfield(L, -2, LuaWrapper<T>::classname); // ... LuaWrapper LuaWrapper.holds
|
||||||
|
lua_pop(L, 1); // ... LuaWrapper
|
||||||
|
|
||||||
|
lua_getfield(L, -1, LUAW_CACHE_KEY); // ... LuaWrapper LuaWrapper.cache
|
||||||
|
lua_newtable(L); // ... LuaWrapper LuaWrapper.cache {}
|
||||||
|
luaW_wrapperfield<T>(L, LUAW_CACHE_METATABLE_KEY); // ... LuaWrapper LuaWrapper.cache {} cmt
|
||||||
|
lua_setmetatable(L, -2); // ... LuaWrapper LuaWrapper.cache {}
|
||||||
|
lua_setfield(L, -2, LuaWrapper<T>::classname); // ... LuaWrapper LuaWrapper.cache
|
||||||
|
|
||||||
|
lua_pop(L, 2); // ...
|
||||||
|
|
||||||
|
// Open table
|
||||||
|
lua_newtable(L); // ... T
|
||||||
|
luaW_registerfuncs(L, allocator ? defaulttable : NULL, table); // ... T
|
||||||
|
|
||||||
|
// Open metatable, set up extends table
|
||||||
|
luaL_newmetatable(L, classname); // ... T mt
|
||||||
|
lua_newtable(L); // ... T mt {}
|
||||||
|
lua_setfield(L, -2, LUAW_EXTENDS_KEY); // ... T mt
|
||||||
|
luaW_registerfuncs(L, defaultmetatable, metatable); // ... T mt
|
||||||
|
lua_setfield(L, -2, "metatable"); // ... T
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void luaW_register(lua_State* L, const char* classname, const luaL_Reg* table, const luaL_Reg* metatable, T* (*allocator)(lua_State*) = luaW_defaultallocator<T>, void (*deallocator)(lua_State*, T*) = luaW_defaultdeallocator<T>, void (*identifier)(lua_State*, T*) = luaW_defaultidentifier<T>)
|
||||||
|
{
|
||||||
|
luaW_setfuncs(L, classname, table, metatable, allocator, deallocator, identifier); // ... T
|
||||||
|
lua_pushvalue(L, -1); // ... T T
|
||||||
|
lua_setglobal(L, classname); // ... T
|
||||||
|
}
|
||||||
|
|
||||||
|
// luaW_extend is used to declare that class T inherits from class U. All
|
||||||
|
// functions in the base class will be available to the derived class (except
|
||||||
|
// when they share a function name, in which case the derived class's function
|
||||||
|
// wins). This also allows luaW_to<T> to cast your object apropriately, as
|
||||||
|
// casts straight through a void pointer do not work.
|
||||||
|
template <typename T, typename U>
|
||||||
|
void luaW_extend(lua_State* L)
|
||||||
|
{
|
||||||
|
if(!LuaWrapper<T>::classname)
|
||||||
|
luaL_error(L, "attempting to call extend on a type that has not been registered");
|
||||||
|
|
||||||
|
if(!LuaWrapper<U>::classname)
|
||||||
|
luaL_error(L, "attempting to extend %s by a type that has not been registered", LuaWrapper<T>::classname);
|
||||||
|
|
||||||
|
LuaWrapper<T>::cast = luaW_cast<T, U>;
|
||||||
|
LuaWrapper<T>::identifier = luaW_identify<T, U>;
|
||||||
|
|
||||||
|
luaL_getmetatable(L, LuaWrapper<T>::classname); // mt
|
||||||
|
luaL_getmetatable(L, LuaWrapper<U>::classname); // mt emt
|
||||||
|
|
||||||
|
// Point T's metatable __index at U's metatable for inheritance
|
||||||
|
lua_newtable(L); // mt emt {}
|
||||||
|
lua_pushvalue(L, -2); // mt emt {} emt
|
||||||
|
lua_setfield(L, -2, "__index"); // mt emt {}
|
||||||
|
lua_setmetatable(L, -3); // mt emt
|
||||||
|
|
||||||
|
// Set up per-type tables to point at parent type
|
||||||
|
lua_getfield(L, LUA_REGISTRYINDEX, LUAW_WRAPPER_KEY); // ... LuaWrapper
|
||||||
|
|
||||||
|
lua_getfield(L, -1, LUAW_STORAGE_KEY); // ... LuaWrapper LuaWrapper.storage
|
||||||
|
lua_getfield(L, -1, LuaWrapper<U>::classname); // ... LuaWrapper LuaWrapper.storage U
|
||||||
|
lua_setfield(L, -2, LuaWrapper<T>::classname); // ... LuaWrapper LuaWrapper.storage
|
||||||
|
lua_pop(L, 1); // ... LuaWrapper
|
||||||
|
|
||||||
|
lua_getfield(L, -1, LUAW_HOLDS_KEY); // ... LuaWrapper LuaWrapper.holds
|
||||||
|
lua_getfield(L, -1, LuaWrapper<U>::classname); // ... LuaWrapper LuaWrapper.holds U
|
||||||
|
lua_setfield(L, -2, LuaWrapper<T>::classname); // ... LuaWrapper LuaWrapper.holds
|
||||||
|
lua_pop(L, 1); // ... LuaWrapper
|
||||||
|
|
||||||
|
lua_getfield(L, -1, LUAW_CACHE_KEY); // ... LuaWrapper LuaWrapper.cache
|
||||||
|
lua_getfield(L, -1, LuaWrapper<U>::classname); // ... LuaWrapper LuaWrapper.cache U
|
||||||
|
lua_setfield(L, -2, LuaWrapper<T>::classname); // ... LuaWrapper LuaWrapper.cache
|
||||||
|
|
||||||
|
lua_pop(L, 2); // ...
|
||||||
|
|
||||||
|
// Make a list of all types that inherit from U, for type checking
|
||||||
|
lua_getfield(L, -2, LUAW_EXTENDS_KEY); // mt emt mt.extends
|
||||||
|
lua_pushvalue(L, -2); // mt emt mt.extends emt
|
||||||
|
lua_setfield(L, -2, LuaWrapper<U>::classname); // mt emt mt.extends
|
||||||
|
lua_getfield(L, -2, LUAW_EXTENDS_KEY); // mt emt mt.extends emt.extends
|
||||||
|
for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1))
|
||||||
|
{
|
||||||
|
// mt emt mt.extends emt.extends k v
|
||||||
|
lua_pushvalue(L, -2); // mt emt mt.extends emt.extends k v k
|
||||||
|
lua_pushvalue(L, -2); // mt emt mt.extends emt.extends k v k
|
||||||
|
lua_rawset(L, -6); // mt emt mt.extends emt.extends k v
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pop(L, 4); // mt emt
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2013 Alexander Ames
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to
|
||||||
|
* deal in the Software without restriction, including without limitation the
|
||||||
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
* IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#endif // LUA_WRAPPER_H_
|
714
luarocks/luawrapperutil.hpp
Normal file
714
luarocks/luawrapperutil.hpp
Normal file
@@ -0,0 +1,714 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2013 Alexander Ames
|
||||||
|
* Alexander.Ames@gmail.com
|
||||||
|
* See Copyright Notice at the end of this file
|
||||||
|
*/
|
||||||
|
|
||||||
|
// API Summary:
|
||||||
|
//
|
||||||
|
// This is a set of utility functions that add to the functionalit of
|
||||||
|
// LuaWrapper. Over time I found myself repeating a few patterns, such as
|
||||||
|
// writing many trvial getter and setter functions, and wanting pass ownership
|
||||||
|
// of one object to another and have the Lua script properly handle that case.
|
||||||
|
//
|
||||||
|
// This file contains the additional functions that I've added but that do
|
||||||
|
// not really belong in the core API.
|
||||||
|
|
||||||
|
#ifndef LUAWRAPPERUTILS_HPP_
|
||||||
|
#define LUAWRAPPERUTILS_HPP_
|
||||||
|
|
||||||
|
#include "luawrapper.hpp"
|
||||||
|
|
||||||
|
#ifndef LUAW_NO_CXX11
|
||||||
|
#include <type_traits>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef LUAW_STD
|
||||||
|
#define LUAW_STD std
|
||||||
|
#endif
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// A set of templated luaL_check and lua_push functions for use in the getters
|
||||||
|
// and setters below
|
||||||
|
//
|
||||||
|
// It is often useful to override luaU_check, luaU_to and/or luaU_push to
|
||||||
|
// operate on your own simple types rather than register your type with
|
||||||
|
// LuaWrapper, especially with small objects.
|
||||||
|
//
|
||||||
|
|
||||||
|
template<typename U, typename = void>
|
||||||
|
struct luaU_Impl
|
||||||
|
{
|
||||||
|
static U luaU_check(lua_State* L, int index);
|
||||||
|
static U luaU_to (lua_State* L, int index);
|
||||||
|
static void luaU_push (lua_State* L, const U& value);
|
||||||
|
static void luaU_push (lua_State* L, U& value);
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename U> U luaU_check(lua_State* L, int index) { return luaU_Impl<U>::luaU_check(L, index); }
|
||||||
|
template<typename U> U luaU_to (lua_State* L, int index) { return luaU_Impl<U>::luaU_to (L, index); }
|
||||||
|
template<typename U> void luaU_push (lua_State* L, const U& value) { luaU_Impl<U>::luaU_push (L, value); }
|
||||||
|
template<typename U> void luaU_push (lua_State* L, U& value) { luaU_Impl<U>::luaU_push (L, value); }
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// This is slightly different than the previous three functions in that you
|
||||||
|
// shouldn't need to write your own version of it, since it uses luaU_check
|
||||||
|
// automatically.
|
||||||
|
//
|
||||||
|
template<typename U>
|
||||||
|
U luaU_opt(lua_State* L, int index, const U& fallback = U())
|
||||||
|
{
|
||||||
|
if (lua_isnil(L, index))
|
||||||
|
return fallback;
|
||||||
|
else
|
||||||
|
return luaU_Impl<U>::luaU_check(L, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct luaU_Impl<bool>
|
||||||
|
{
|
||||||
|
static bool luaU_check(lua_State* L, int index) { return lua_toboolean (L, index) != 0; }
|
||||||
|
static bool luaU_to (lua_State* L, int index) { return lua_toboolean (L, index) != 0; }
|
||||||
|
static void luaU_push (lua_State* L, const bool& value) { lua_pushboolean(L, value); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct luaU_Impl<const char*>
|
||||||
|
{
|
||||||
|
static const char* luaU_check(lua_State* L, int index) { return luaL_checkstring(L, index); }
|
||||||
|
static const char* luaU_to (lua_State* L, int index) { return lua_tostring (L, index); }
|
||||||
|
static void luaU_push (lua_State* L, const char* const& value) { lua_pushstring (L, value); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct luaU_Impl<unsigned int>
|
||||||
|
{
|
||||||
|
static unsigned int luaU_check(lua_State* L, int index) { return static_cast<unsigned int>(luaL_checkinteger(L, index)); }
|
||||||
|
static unsigned int luaU_to (lua_State* L, int index) { return static_cast<unsigned int>(lua_tointeger (L, index)); }
|
||||||
|
static void luaU_push (lua_State* L, const unsigned int& value) { lua_pushinteger (L, value); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct luaU_Impl<int>
|
||||||
|
{
|
||||||
|
static int luaU_check(lua_State* L, int index) { return static_cast<int>(luaL_checkinteger(L, index)); }
|
||||||
|
static int luaU_to (lua_State* L, int index) { return static_cast<int>(lua_tointeger (L, index)); }
|
||||||
|
static void luaU_push (lua_State* L, const int& value) { lua_pushinteger (L, value); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct luaU_Impl<unsigned char>
|
||||||
|
{
|
||||||
|
static unsigned char luaU_check(lua_State* L, int index) { return static_cast<unsigned char>(luaL_checkinteger(L, index)); }
|
||||||
|
static unsigned char luaU_to (lua_State* L, int index) { return static_cast<unsigned char>(lua_tointeger (L, index)); }
|
||||||
|
static void luaU_push (lua_State* L, const unsigned char& value) { lua_pushinteger (L, value); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct luaU_Impl<char>
|
||||||
|
{
|
||||||
|
static char luaU_check(lua_State* L, int index) { return static_cast<char>(luaL_checkinteger(L, index)); }
|
||||||
|
static char luaU_to (lua_State* L, int index) { return static_cast<char>(lua_tointeger (L, index)); }
|
||||||
|
static void luaU_push (lua_State* L, const char& value) { lua_pushinteger (L, value); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct luaU_Impl<float>
|
||||||
|
{
|
||||||
|
static float luaU_check(lua_State* L, int index) { return static_cast<float>(luaL_checknumber(L, index)); }
|
||||||
|
static float luaU_to (lua_State* L, int index) { return static_cast<float>(lua_tonumber (L, index)); }
|
||||||
|
static void luaU_push (lua_State* L, const float& value) { lua_pushnumber (L, value); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct luaU_Impl<double>
|
||||||
|
{
|
||||||
|
static double luaU_check(lua_State* L, int index) { return static_cast<double>(luaL_checknumber(L, index)); }
|
||||||
|
static double luaU_to (lua_State* L, int index) { return static_cast<double>(lua_tonumber (L, index)); }
|
||||||
|
static void luaU_push (lua_State* L, const double& value) { lua_pushnumber (L, value); }
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifndef LUAW_NO_CXX11
|
||||||
|
template<typename T>
|
||||||
|
struct luaU_Impl<T, typename LUAW_STD::enable_if<LUAW_STD::is_enum<T>::value>::type>
|
||||||
|
{
|
||||||
|
static T luaU_check(lua_State* L, int index) { return static_cast<T>(luaL_checkinteger (L, index)); }
|
||||||
|
static T luaU_to (lua_State* L, int index) { return static_cast<T>(lua_tointeger (L, index)); }
|
||||||
|
static void luaU_push (lua_State* L, const T& value) { lua_pushnumber(L, static_cast<int>(value )); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct luaU_Impl<T*, typename LUAW_STD::enable_if<LUAW_STD::is_class<T>::value>::type>
|
||||||
|
{
|
||||||
|
static T* luaU_check( lua_State* L, int index) { return luaW_check<T>(L, index); }
|
||||||
|
static T* luaU_to ( lua_State* L, int index) { return luaW_to <T>(L, index); }
|
||||||
|
static void luaU_push ( lua_State* L, T*& value) { luaW_push <T>(L, value); }
|
||||||
|
static void luaU_push ( lua_State* L, T* value) { luaW_push <T>(L, value); }
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// These are just some functions I've always felt should exist
|
||||||
|
//
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
inline U luaU_getfield(lua_State* L, int index, const char* field)
|
||||||
|
{
|
||||||
|
#ifndef LUAW_NO_CXX11
|
||||||
|
static_assert(!std::is_same<U, const char*>::value,
|
||||||
|
"luaU_getfield is not safe to use on const char*'s. (The string will be popped from the stack.)");
|
||||||
|
#endif
|
||||||
|
lua_getfield(L, index, field);
|
||||||
|
U val = luaU_to<U>(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
inline U luaU_checkfield(lua_State* L, int index, const char* field)
|
||||||
|
{
|
||||||
|
#ifndef LUAW_NO_CXX11
|
||||||
|
static_assert(!std::is_same<U, const char*>::value,
|
||||||
|
"luaU_checkfield is not safe to use on const char*'s. (The string will be popped from the stack.)");
|
||||||
|
#endif
|
||||||
|
lua_getfield(L, index, field);
|
||||||
|
U val = luaU_check<U>(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
inline void luaU_setfield(lua_State* L, int index, const char* field, U val)
|
||||||
|
{
|
||||||
|
luaU_push<U>(L, val);
|
||||||
|
lua_setfield(L, luaW_correctindex(L, index, 1), field);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// A set of trivial getter and setter templates. These templates are designed
|
||||||
|
// to call trivial getters or setters.
|
||||||
|
//
|
||||||
|
// There are four forms:
|
||||||
|
// 1. Getting or setting a public member variable that is of a primitive type
|
||||||
|
// 2. Getting or setting a public member variable that is a pointer to an
|
||||||
|
// object
|
||||||
|
// 3. Getting or setting a private member variable that is of a primitive type
|
||||||
|
// through a getter or setter
|
||||||
|
// 3. Getting or setting a private member variable that is is a pointer to an
|
||||||
|
// object through a getter or setter
|
||||||
|
//
|
||||||
|
// The interface to all of them is the same however. In addition to plain
|
||||||
|
// getter and setter functions, there is a getset which does both - if an
|
||||||
|
// argument is supplied it attempts to set the value, and in either case it
|
||||||
|
// returns the value. In your lua table declaration in C++ rather than write
|
||||||
|
// individiual wrappers for each getter and setter you may do the following:
|
||||||
|
//
|
||||||
|
// static luaL_reg Foo_metatable[] =
|
||||||
|
// {
|
||||||
|
// { "GetBar", luaU_get<Foo, bool, &Widget::GetBar> },
|
||||||
|
// { "SetBar", luaU_set<Foo, bool, &Widget::SetBar> },
|
||||||
|
// { "Bar", luaU_getset<Foo, bool, &Widget::GetBar, &Widget::SetBar> },
|
||||||
|
// { NULL, NULL }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Getters and setters must have one of the following signatures:
|
||||||
|
// void T::Setter(U value);
|
||||||
|
// void T::Setter(U* value);
|
||||||
|
// void T::Setter(const U& value);
|
||||||
|
// U Getter() const;
|
||||||
|
// U* Getter() const;
|
||||||
|
//
|
||||||
|
// In this example, the variable Foo::bar is private and so it is accessed
|
||||||
|
// through getter and setter functions. If Foo::bar were public, it could be
|
||||||
|
// accessed directly, like so:
|
||||||
|
//
|
||||||
|
// static luaL_reg Foo_metatable[] =
|
||||||
|
// {
|
||||||
|
// { "GetBar", luaU_get<Foo, bool, &Widget::bar> },
|
||||||
|
// { "SetBar", luaU_set<Foo, bool, &Widget::bar> },
|
||||||
|
// { "Bar", luaU_getset<Foo, bool, &Widget::bar> },
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// In a Lua script, you can now use foo:GetBar(), foo:SetBar() and foo:Bar()
|
||||||
|
//
|
||||||
|
|
||||||
|
template <typename T, typename U, U T::*Member>
|
||||||
|
int luaU_get(lua_State* L)
|
||||||
|
{
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
luaU_push<U>(L, obj->*Member);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U, U* T::*Member>
|
||||||
|
int luaU_get(lua_State* L)
|
||||||
|
{
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
luaW_push<U>(L, obj->*Member);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U, U (T::*Getter)() const>
|
||||||
|
int luaU_get(lua_State* L)
|
||||||
|
{
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
luaU_push<U>(L, (obj->*Getter)());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U, const U& (T::*Getter)() const>
|
||||||
|
int luaU_get(lua_State* L)
|
||||||
|
{
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
luaU_push<U>(L, (obj->*Getter)());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U, U* (T::*Getter)() const>
|
||||||
|
int luaU_get(lua_State* L)
|
||||||
|
{
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
luaW_push<U>(L, (obj->*Getter)());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U, U T::*Member>
|
||||||
|
int luaU_set(lua_State* L)
|
||||||
|
{
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
if (obj)
|
||||||
|
obj->*Member = luaU_check<U>(L, 2);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U, U* T::*Member>
|
||||||
|
int luaU_set(lua_State* L)
|
||||||
|
{
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
if (obj)
|
||||||
|
{
|
||||||
|
U* member = luaW_opt<U>(L, 2);
|
||||||
|
obj->*Member = member;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U, const U* T::*Member>
|
||||||
|
int luaU_set(lua_State* L)
|
||||||
|
{
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
if (obj)
|
||||||
|
{
|
||||||
|
U* member = luaW_opt<U>(L, 2);
|
||||||
|
obj->*Member = member;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U, const U* T::*Member>
|
||||||
|
int luaU_setandrelease(lua_State* L)
|
||||||
|
{
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
if (obj)
|
||||||
|
{
|
||||||
|
U* member = luaW_opt<U>(L, 2);
|
||||||
|
obj->*Member = member;
|
||||||
|
if (member)
|
||||||
|
luaW_release<U>(L, member);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U, void (T::*Setter)(U)>
|
||||||
|
int luaU_set(lua_State* L)
|
||||||
|
{
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
if (obj)
|
||||||
|
(obj->*Setter)(luaU_check<U>(L, 2));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U, void (T::*Setter)(const U&)>
|
||||||
|
int luaU_set(lua_State* L)
|
||||||
|
{
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
if (obj)
|
||||||
|
(obj->*Setter)(luaU_check<U>(L, 2));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U, void (T::*Setter)(U*)>
|
||||||
|
int luaU_set(lua_State* L)
|
||||||
|
{
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
if (obj)
|
||||||
|
{
|
||||||
|
U* member = luaW_opt<U>(L, 2);
|
||||||
|
(obj->*Setter)(member);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U, void (T::*Setter)(U*)>
|
||||||
|
int luaU_setandrelease(lua_State* L)
|
||||||
|
{
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
if (obj)
|
||||||
|
{
|
||||||
|
U* member = luaW_opt<U>(L, 2);
|
||||||
|
(obj->*Setter)(member);
|
||||||
|
if (member)
|
||||||
|
luaW_release<U>(L, member);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U, U T::*Member>
|
||||||
|
int luaU_getset(lua_State* L)
|
||||||
|
{
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
if (obj && lua_gettop(L) >= 2)
|
||||||
|
{
|
||||||
|
obj->*Member = luaU_check<U>(L, 2);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
luaU_push<U>(L, obj->*Member);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U, U* T::*Member>
|
||||||
|
int luaU_getset(lua_State* L)
|
||||||
|
{
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
if (obj && lua_gettop(L) >= 2)
|
||||||
|
{
|
||||||
|
U* member = luaW_opt<U>(L, 2);
|
||||||
|
obj->*Member = member;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
luaW_push<U>(L, obj->*Member);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U, U* T::*Member>
|
||||||
|
int luaU_getsetandrelease(lua_State* L)
|
||||||
|
{
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
if (obj && lua_gettop(L) >= 2)
|
||||||
|
{
|
||||||
|
U* member = luaW_opt<U>(L, 2);
|
||||||
|
obj->*Member = member;
|
||||||
|
if (member)
|
||||||
|
luaW_release<U>(L, member);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
luaW_push<U>(L, obj->*Member);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U, U (T::*Getter)() const, void (T::*Setter)(U)>
|
||||||
|
int luaU_getset(lua_State* L)
|
||||||
|
{
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
if (obj && lua_gettop(L) >= 2)
|
||||||
|
{
|
||||||
|
(obj->*Setter)(luaU_check<U>(L, 2));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
luaU_push<U>(L, (obj->*Getter)());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U, U (T::*Getter)() const, void (T::*Setter)(const U&)>
|
||||||
|
int luaU_getset(lua_State* L)
|
||||||
|
{
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
if (obj && lua_gettop(L) >= 2)
|
||||||
|
{
|
||||||
|
(obj->*Setter)(luaU_check<U>(L, 2));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
luaU_push<U>(L, (obj->*Getter)());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U, const U& (T::*Getter)() const, void (T::*Setter)(const U&)>
|
||||||
|
int luaU_getset(lua_State* L)
|
||||||
|
{
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
if (obj && lua_gettop(L) >= 2)
|
||||||
|
{
|
||||||
|
(obj->*Setter)(luaU_check<U>(L, 2));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
luaU_push<U>(L, (obj->*Getter)());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U, U* (T::*Getter)() const, void (T::*Setter)(U*)>
|
||||||
|
int luaU_getset(lua_State* L)
|
||||||
|
{
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
if (obj && lua_gettop(L) >= 2)
|
||||||
|
{
|
||||||
|
U* member = luaW_opt<U>(L, 2);
|
||||||
|
(obj->*Setter)(member);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
luaW_push<U>(L, (obj->*Getter)());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U, U* (T::*Getter)() const, void (T::*Setter)(U*)>
|
||||||
|
int luaU_getsetandrelease(lua_State* L)
|
||||||
|
{
|
||||||
|
T* obj = luaW_check<T>(L, 1);
|
||||||
|
if (obj && lua_gettop(L) >= 2)
|
||||||
|
{
|
||||||
|
U* member = luaW_opt<U>(L, 2);
|
||||||
|
(obj->*Setter)(member);
|
||||||
|
if (member)
|
||||||
|
luaW_release<U>(L, member);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
luaW_push<U>(L, (obj->*Getter)());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(_WIN32) && !defined(LUAW_NO_CXX11)
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// luaU_func is a special macro that expands into a simple function wrapper.
|
||||||
|
// Unlike the getter setters above, you merely need to name the function you
|
||||||
|
// would like to wrap.
|
||||||
|
//
|
||||||
|
// For example,
|
||||||
|
//
|
||||||
|
// struct Foo
|
||||||
|
// {
|
||||||
|
// int DoSomething(int, const char*);
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// static luaL_reg Foo_metatable[] =
|
||||||
|
// {
|
||||||
|
// { "DoSomething", luaU_func(&Foo::DoSomething) },
|
||||||
|
// { NULL, NULL }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// This macro will expand based on the function signature of Foo::DoSomething
|
||||||
|
// In this example, it would expand into the following wrapper:
|
||||||
|
//
|
||||||
|
// luaU_push(luaW_check<T>(L, 1)->DoSomething(luaU_check<int>(L, 2), luaU_check<const char*>(L, 3)));
|
||||||
|
// return 1;
|
||||||
|
//
|
||||||
|
// In this example there is only one member function called DoSomething. In some
|
||||||
|
// cases there may be multiple overrides for a function. For those cases, an
|
||||||
|
// additional macro has been provided, luaU_funcsig, which takes the entire
|
||||||
|
// function signature. The arguments to the macro reflect the order they would
|
||||||
|
// appear in the function signature: return type, type name, function name, and
|
||||||
|
// finally a list of all the argument types. For example:
|
||||||
|
//
|
||||||
|
// struct Foo
|
||||||
|
// {
|
||||||
|
// int DoSomething (const char*);
|
||||||
|
// int DoSomething (const char*, int);
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// const struct luaL_Reg Foo_metatable[] =
|
||||||
|
// {
|
||||||
|
// {"DoSomething1", luaU_funcsig(int, Foo, DoSomething, const char*)) },
|
||||||
|
// {"DoSomething1", luaU_funcsig(int, Foo, DoSomething, const char*, int)) },
|
||||||
|
// { NULL, NULL }
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// These macros and it's underlying templates are somewhat experimental and some
|
||||||
|
// refinements are probably needed. There are cases where it does not
|
||||||
|
// currently work and I expect some changes can be made to refine its behavior.
|
||||||
|
//
|
||||||
|
|
||||||
|
#define luaU_func(memberfunc) &luaU_FuncWrapper<decltype(memberfunc),memberfunc>::call
|
||||||
|
#define luaU_funcsig(returntype, type, funcname, ...) luaU_func(static_cast<returntype (type::*)(__VA_ARGS__)>(&type::funcname))
|
||||||
|
|
||||||
|
template<int... ints> struct luaU_IntPack { };
|
||||||
|
template<int start, int count, int... tail> struct luaU_MakeIntRangeType { typedef typename luaU_MakeIntRangeType<start, count-1, start+count-1, tail...>::type type; };
|
||||||
|
template<int start, int... tail> struct luaU_MakeIntRangeType<start, 0, tail...> { typedef luaU_IntPack<tail...> type; };
|
||||||
|
template<int start, int count> inline typename luaU_MakeIntRangeType<start, count>::type luaU_makeIntRange() { return typename luaU_MakeIntRangeType<start, count>::type(); }
|
||||||
|
template<class MemFunPtrType, MemFunPtrType MemberFunc> struct luaU_FuncWrapper;
|
||||||
|
|
||||||
|
template<class T, class ReturnType, class... Args, ReturnType(T::*MemberFunc)(Args...)>
|
||||||
|
struct luaU_FuncWrapper<ReturnType (T::*)(Args...), MemberFunc>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static int call(lua_State* L)
|
||||||
|
{
|
||||||
|
return callImpl(L, luaU_makeIntRange<2,sizeof...(Args)>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<int... indices>
|
||||||
|
static int callImpl(lua_State* L, luaU_IntPack<indices...>)
|
||||||
|
{
|
||||||
|
luaU_push<ReturnType>(L, (luaW_check<T>(L, 1)->*MemberFunc)(luaU_check<Args>(L, indices)...));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Calls the copy constructor for an object of type T.
|
||||||
|
// Arguments may be passed in, in case they're needed for the postconstructor
|
||||||
|
//
|
||||||
|
// e.g.
|
||||||
|
//
|
||||||
|
// C++:
|
||||||
|
// luaL_reg Foo_Metatable[] =
|
||||||
|
// {
|
||||||
|
// { "clone", luaU_clone<Foo> },
|
||||||
|
// { NULL, NULL }
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// Lua:
|
||||||
|
// foo = Foo.new()
|
||||||
|
// foo2 = foo:clone()
|
||||||
|
//
|
||||||
|
template <typename T>
|
||||||
|
int luaU_clone(lua_State* L)
|
||||||
|
{
|
||||||
|
// obj ...
|
||||||
|
T* obj = new T(*luaW_check<T>(L, 1));
|
||||||
|
lua_remove(L, 1); // ...
|
||||||
|
int numargs = lua_gettop(L);
|
||||||
|
luaW_push<T>(L, obj); // ... clone
|
||||||
|
luaW_hold<T>(L, obj);
|
||||||
|
luaW_postconstructor<T>(L, numargs);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// luaU_build is intended to be used to initialize many values by passing in a
|
||||||
|
// table. They keys of the table are used as function names, and values are
|
||||||
|
// used as arguments to the function. This is intended to be used on functions
|
||||||
|
// that are simple setters.
|
||||||
|
//
|
||||||
|
// For example, if luaU_build is set as the post constructor, you can
|
||||||
|
// initialize an object as so:
|
||||||
|
//
|
||||||
|
// f = Foo.new
|
||||||
|
// {
|
||||||
|
// X = 10;
|
||||||
|
// Y = 20;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// After the object is constructed, luaU_build will do the equivalent of
|
||||||
|
// calling f:X(10) and f:Y(20).
|
||||||
|
//
|
||||||
|
template <typename T>
|
||||||
|
int luaU_build(lua_State* L)
|
||||||
|
{
|
||||||
|
// obj {}
|
||||||
|
lua_insert(L, -2); // {} obj
|
||||||
|
if (lua_type(L, 1) == LUA_TTABLE)
|
||||||
|
{
|
||||||
|
for (lua_pushnil(L); lua_next(L, 1); lua_pop(L, 1))
|
||||||
|
{
|
||||||
|
// {} obj k v
|
||||||
|
lua_pushvalue(L, -2); // {} obj k v k
|
||||||
|
lua_gettable(L, -4); // {} obj k v ud[k]
|
||||||
|
lua_pushvalue(L, -4); // {} obj k v ud[k] ud
|
||||||
|
lua_pushvalue(L, -3); // {} obj k v ud[k] ud v
|
||||||
|
lua_call(L, 2, 0); // {} obj k v
|
||||||
|
}
|
||||||
|
// {} ud
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Takes the object of type T at the top of the stack and stores it in on a
|
||||||
|
// table with the name storagetable, on the table at the specified index.
|
||||||
|
//
|
||||||
|
// You may manually call luaW_hold and luaW_release to handle pointer
|
||||||
|
// ownership, but it is often easier to simply store a Lua userdata on a table
|
||||||
|
// that is owned by its parent. This ensures that your object will not be
|
||||||
|
// prematurely freed, and that it can only be destoryed after its parent.
|
||||||
|
//
|
||||||
|
// e.g.
|
||||||
|
//
|
||||||
|
// Foo* parent = luaW_check<Foo>(L, 1);
|
||||||
|
// Bar* child = luaW_check<Bar>(L, 2);
|
||||||
|
// if (parent && child)
|
||||||
|
// {
|
||||||
|
// luaU_store<Bar>(L, 1, "Children");
|
||||||
|
// parent->AddChild(child);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
template <typename T>
|
||||||
|
void luaU_store(lua_State* L, int index, const char* storagetable, const char* key = NULL)
|
||||||
|
{
|
||||||
|
// ... store ... obj
|
||||||
|
lua_getfield(L, index, storagetable); // ... store ... obj store.storagetable
|
||||||
|
if (key)
|
||||||
|
lua_pushstring(L, key); // ... store ... obj store.storagetable key
|
||||||
|
else
|
||||||
|
LuaWrapper<T>::identifier(L, luaW_to<T>(L, -2)); // ... store ... obj store.storagetable key
|
||||||
|
lua_pushvalue(L, -3); // ... store ... obj store.storagetable key obj
|
||||||
|
lua_settable(L, -3); // ... store ... obj store.storagetable
|
||||||
|
lua_pop(L, 1); // ... store ... obj
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2011 Alexander Ames
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to
|
||||||
|
* deal in the Software without restriction, including without limitation the
|
||||||
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
* IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#endif // LUAWRAPPERUTILS_HPP_
|
64
luarocks/main.cpp
Normal file
64
luarocks/main.cpp
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* main.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#include "lua.h"
|
||||||
|
#include "lualib.h"
|
||||||
|
#include "lauxlib.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "functions.hpp"
|
||||||
|
#include "LuaWebSocket.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
lua_State* L = luaL_newstate();
|
||||||
|
luaL_openlibs(L);
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
lua_register(L, "info", lua_info);
|
||||||
|
|
||||||
|
// Objets
|
||||||
|
ix::luaopen_WebSocket(L);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Simple version does little error handling
|
||||||
|
// luaL_dofile(L, "echo_client.lua");
|
||||||
|
//
|
||||||
|
std::string luaFile("echo_client.lua");
|
||||||
|
int loadStatus = luaL_loadfile(L, luaFile.c_str());
|
||||||
|
if (loadStatus)
|
||||||
|
{
|
||||||
|
std::cerr << "Error loading " << luaFile << std::endl;
|
||||||
|
std::cerr << lua_tostring(L, -1) << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cout << "loaded " << luaFile << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Capture lua errors
|
||||||
|
//
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lua_call(L, 0, 0);
|
||||||
|
lua_close(L);
|
||||||
|
}
|
||||||
|
catch (const std::runtime_error& ex)
|
||||||
|
{
|
||||||
|
lua_close(L);
|
||||||
|
std::cerr << ex.what() << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
12
makefile
12
makefile
@@ -20,7 +20,7 @@ install: brew
|
|||||||
# Release, Debug, MinSizeRel, RelWithDebInfo are the build types
|
# Release, Debug, MinSizeRel, RelWithDebInfo are the build types
|
||||||
#
|
#
|
||||||
brew:
|
brew:
|
||||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; make -j 4 install)
|
mkdir -p build && (cd build ; cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; make -j 4 install)
|
||||||
|
|
||||||
# Docker default target. We've add problem with OpenSSL and TLS 1.3 (on the
|
# Docker default target. We've add problem with OpenSSL and TLS 1.3 (on the
|
||||||
# server side ?) and I can't work-around it easily, so we're using mbedtls on
|
# server side ?) and I can't work-around it easily, so we're using mbedtls on
|
||||||
@@ -116,10 +116,12 @@ test_ubsan:
|
|||||||
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
||||||
(cd test ; python2.7 run.py -r)
|
(cd test ; python2.7 run.py -r)
|
||||||
|
|
||||||
test_asan:
|
test_asan: build_test_asan
|
||||||
|
(cd test ; python2.7 run.py -r)
|
||||||
|
|
||||||
|
build_test_asan:
|
||||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableAddressSanitizer YES)
|
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableAddressSanitizer YES)
|
||||||
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
||||||
(cd test ; python2.7 run.py -r)
|
|
||||||
|
|
||||||
test_tsan_openssl:
|
test_tsan_openssl:
|
||||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 -DUSE_OPEN_SSL=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableThreadSanitizer YES)
|
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 -DUSE_OPEN_SSL=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableThreadSanitizer YES)
|
||||||
@@ -157,6 +159,9 @@ test_no_ssl:
|
|||||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TEST=1 .. ; make -j 4)
|
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TEST=1 .. ; make -j 4)
|
||||||
(cd test ; python2.7 run.py -r)
|
(cd test ; python2.7 run.py -r)
|
||||||
|
|
||||||
|
luarocks:
|
||||||
|
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_LUAROCKS=1 .. ; ninja)
|
||||||
|
|
||||||
ws_test: ws
|
ws_test: ws
|
||||||
(cd ws ; env DEBUG=1 PATH=../ws/build:$$PATH bash test_ws.sh)
|
(cd ws ; env DEBUG=1 PATH=../ws/build:$$PATH bash test_ws.sh)
|
||||||
|
|
||||||
@@ -183,3 +188,4 @@ doc:
|
|||||||
.PHONY: test
|
.PHONY: test
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
.PHONY: ws
|
.PHONY: ws
|
||||||
|
.PHONY: luarocks
|
||||||
|
@@ -66,13 +66,7 @@ if (UNIX)
|
|||||||
IXCobraMetricsPublisherTest.cpp
|
IXCobraMetricsPublisherTest.cpp
|
||||||
IXCobraToSentryBotTest.cpp
|
IXCobraToSentryBotTest.cpp
|
||||||
IXCobraToStatsdBotTest.cpp
|
IXCobraToStatsdBotTest.cpp
|
||||||
)
|
IXCobraToStdoutBotTest.cpp
|
||||||
endif()
|
|
||||||
|
|
||||||
# Some unittest fail for dubious reason on Ubuntu Xenial with TSAN
|
|
||||||
if (MAC OR WIN32)
|
|
||||||
list(APPEND SOURCES
|
|
||||||
IXWebSocketMessageQTest.cpp
|
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
@@ -180,44 +180,40 @@ namespace
|
|||||||
_conn.configure(_cobraConfig);
|
_conn.configure(_cobraConfig);
|
||||||
_conn.connect();
|
_conn.connect();
|
||||||
|
|
||||||
_conn.setEventCallback([this, channel](ix::CobraConnectionEventType eventType,
|
_conn.setEventCallback([this, channel](const CobraEventPtr& event) {
|
||||||
const std::string& errMsg,
|
if (event->type == ix::CobraEventType::Open)
|
||||||
const ix::WebSocketHttpHeaders& headers,
|
|
||||||
const std::string& subscriptionId,
|
|
||||||
CobraConnection::MsgId msgId) {
|
|
||||||
if (eventType == ix::CobraConnection_EventType_Open)
|
|
||||||
{
|
{
|
||||||
log("Subscriber connected: " + _user);
|
log("Subscriber connected: " + _user);
|
||||||
for (auto&& it : headers)
|
for (auto&& it : event->headers)
|
||||||
{
|
{
|
||||||
log("Headers " + it.first + " " + it.second);
|
log("Headers " + it.first + " " + it.second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (eventType == ix::CobraConnection_EventType_Authenticated)
|
else if (event->type == ix::CobraEventType::Authenticated)
|
||||||
{
|
{
|
||||||
log("Subscriber authenticated: " + _user);
|
log("Subscriber authenticated: " + _user);
|
||||||
subscribe(channel);
|
subscribe(channel);
|
||||||
}
|
}
|
||||||
else if (eventType == ix::CobraConnection_EventType_Error)
|
else if (event->type == ix::CobraEventType::Error)
|
||||||
{
|
{
|
||||||
log(errMsg + _user);
|
log(event->errMsg + _user);
|
||||||
}
|
}
|
||||||
else if (eventType == ix::CobraConnection_EventType_Closed)
|
else if (event->type == ix::CobraEventType::Closed)
|
||||||
{
|
{
|
||||||
log("Connection closed: " + _user);
|
log("Connection closed: " + _user);
|
||||||
}
|
}
|
||||||
else if (eventType == ix::CobraConnection_EventType_Subscribed)
|
else if (event->type == ix::CobraEventType::Subscribed)
|
||||||
{
|
{
|
||||||
log("Subscription ok: " + _user + " subscription_id " + subscriptionId);
|
log("Subscription ok: " + _user + " subscription_id " + event->subscriptionId);
|
||||||
_connectedAndSubscribed = true;
|
_connectedAndSubscribed = true;
|
||||||
}
|
}
|
||||||
else if (eventType == ix::CobraConnection_EventType_UnSubscribed)
|
else if (event->type == ix::CobraEventType::UnSubscribed)
|
||||||
{
|
{
|
||||||
log("Unsubscription ok: " + _user + " subscription_id " + subscriptionId);
|
log("Unsubscription ok: " + _user + " subscription_id " + event->subscriptionId);
|
||||||
}
|
}
|
||||||
else if (eventType == ix::CobraConnection_EventType_Published)
|
else if (event->type == ix::CobraEventType::Published)
|
||||||
{
|
{
|
||||||
TLogger() << "Subscriber: published message acked: " << msgId;
|
TLogger() << "Subscriber: published message acked: " << event->msgId;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -248,11 +244,7 @@ namespace
|
|||||||
ix::msleep(50);
|
ix::msleep(50);
|
||||||
_conn.disconnect();
|
_conn.disconnect();
|
||||||
|
|
||||||
_conn.setEventCallback([](ix::CobraConnectionEventType /*eventType*/,
|
_conn.setEventCallback([](const CobraEventPtr& /*event*/) {});
|
||||||
const std::string& /*errMsg*/,
|
|
||||||
const ix::WebSocketHttpHeaders& /*headers*/,
|
|
||||||
const std::string& /*subscriptionId*/,
|
|
||||||
CobraConnection::MsgId /*msgId*/) { ; });
|
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@@ -54,24 +54,24 @@ namespace
|
|||||||
conn.configure(config);
|
conn.configure(config);
|
||||||
conn.connect();
|
conn.connect();
|
||||||
|
|
||||||
conn.setEventCallback([&conn, &channel](ix::CobraConnectionEventType eventType,
|
conn.setEventCallback([&conn, &channel](const CobraEventPtr& event) {
|
||||||
const std::string& errMsg,
|
if (event->type == ix::CobraEventType::Open)
|
||||||
const ix::WebSocketHttpHeaders& headers,
|
|
||||||
const std::string& subscriptionId,
|
|
||||||
CobraConnection::MsgId msgId) {
|
|
||||||
if (eventType == ix::CobraConnection_EventType_Open)
|
|
||||||
{
|
{
|
||||||
TLogger() << "Subscriber connected:";
|
TLogger() << "Subscriber connected:";
|
||||||
for (auto&& it : headers)
|
for (auto&& it : event->headers)
|
||||||
{
|
{
|
||||||
log("Headers " + it.first + " " + it.second);
|
log("Headers " + it.first + " " + it.second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (eventType == ix::CobraConnection_EventType_Error)
|
else if (event->type == ix::CobraEventType::Closed)
|
||||||
{
|
{
|
||||||
TLogger() << "Subscriber error:" << errMsg;
|
TLogger() << "Subscriber closed:" << event->errMsg;
|
||||||
}
|
}
|
||||||
else if (eventType == ix::CobraConnection_EventType_Authenticated)
|
else if (event->type == ix::CobraEventType::Error)
|
||||||
|
{
|
||||||
|
TLogger() << "Subscriber error:" << event->errMsg;
|
||||||
|
}
|
||||||
|
else if (event->type == ix::CobraEventType::Authenticated)
|
||||||
{
|
{
|
||||||
log("Subscriber authenticated");
|
log("Subscriber authenticated");
|
||||||
std::string filter;
|
std::string filter;
|
||||||
@@ -92,29 +92,29 @@ namespace
|
|||||||
gMessageCount++;
|
gMessageCount++;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (eventType == ix::CobraConnection_EventType_Subscribed)
|
else if (event->type == ix::CobraEventType::Subscribed)
|
||||||
{
|
{
|
||||||
TLogger() << "Subscriber: subscribed to channel " << subscriptionId;
|
TLogger() << "Subscriber: subscribed to channel " << event->subscriptionId;
|
||||||
if (subscriptionId == channel)
|
if (event->subscriptionId == channel)
|
||||||
{
|
{
|
||||||
gSubscriberConnectedAndSubscribed = true;
|
gSubscriberConnectedAndSubscribed = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TLogger() << "Subscriber: unexpected channel " << subscriptionId;
|
TLogger() << "Subscriber: unexpected channel " << event->subscriptionId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (eventType == ix::CobraConnection_EventType_UnSubscribed)
|
else if (event->type == ix::CobraEventType::UnSubscribed)
|
||||||
{
|
{
|
||||||
TLogger() << "Subscriber: ununexpected from channel " << subscriptionId;
|
TLogger() << "Subscriber: ununexpected from channel " << event->subscriptionId;
|
||||||
if (subscriptionId != channel)
|
if (event->subscriptionId != channel)
|
||||||
{
|
{
|
||||||
TLogger() << "Subscriber: unexpected channel " << subscriptionId;
|
TLogger() << "Subscriber: unexpected channel " << event->subscriptionId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (eventType == ix::CobraConnection_EventType_Published)
|
else if (event->type == ix::CobraEventType::Published)
|
||||||
{
|
{
|
||||||
TLogger() << "Subscriber: published message acked: " << msgId;
|
TLogger() << "Subscriber: published message acked: " << event->msgId;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -141,7 +141,6 @@ TEST_CASE("Cobra_to_sentry_bot", "[cobra_bots]")
|
|||||||
std::string filter;
|
std::string filter;
|
||||||
std::string position("$");
|
std::string position("$");
|
||||||
bool verbose = true;
|
bool verbose = true;
|
||||||
bool strict = true;
|
|
||||||
size_t maxQueueSize = 10;
|
size_t maxQueueSize = 10;
|
||||||
bool enableHeartbeat = false;
|
bool enableHeartbeat = false;
|
||||||
|
|
||||||
@@ -161,16 +160,15 @@ TEST_CASE("Cobra_to_sentry_bot", "[cobra_bots]")
|
|||||||
// Only run the bot for 3 seconds
|
// Only run the bot for 3 seconds
|
||||||
int runtime = 3;
|
int runtime = 3;
|
||||||
|
|
||||||
int sentCount = cobra_to_sentry_bot(config,
|
int64_t sentCount = cobra_to_sentry_bot(config,
|
||||||
channel,
|
channel,
|
||||||
filter,
|
filter,
|
||||||
position,
|
position,
|
||||||
sentryClient,
|
sentryClient,
|
||||||
verbose,
|
verbose,
|
||||||
strict,
|
maxQueueSize,
|
||||||
maxQueueSize,
|
enableHeartbeat,
|
||||||
enableHeartbeat,
|
runtime);
|
||||||
runtime);
|
|
||||||
//
|
//
|
||||||
// We want at least 2 messages to be sent
|
// We want at least 2 messages to be sent
|
||||||
//
|
//
|
||||||
|
@@ -114,18 +114,18 @@ TEST_CASE("Cobra_to_statsd_bot", "[cobra_bots]")
|
|||||||
std::string gauge;
|
std::string gauge;
|
||||||
std::string timer;
|
std::string timer;
|
||||||
|
|
||||||
int sentCount = ix::cobra_to_statsd_bot(config,
|
int64_t sentCount = ix::cobra_to_statsd_bot(config,
|
||||||
channel,
|
channel,
|
||||||
filter,
|
filter,
|
||||||
position,
|
position,
|
||||||
statsdClient,
|
statsdClient,
|
||||||
fields,
|
fields,
|
||||||
gauge,
|
gauge,
|
||||||
timer,
|
timer,
|
||||||
verbose,
|
verbose,
|
||||||
maxQueueSize,
|
maxQueueSize,
|
||||||
enableHeartbeat,
|
enableHeartbeat,
|
||||||
runtime);
|
runtime);
|
||||||
//
|
//
|
||||||
// We want at least 2 messages to be sent
|
// We want at least 2 messages to be sent
|
||||||
//
|
//
|
||||||
|
127
test/IXCobraToStdoutBotTest.cpp
Normal file
127
test/IXCobraToStdoutBotTest.cpp
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* IXCobraToStdoutTest.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2020 Machine Zone. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IXTest.h"
|
||||||
|
#include "catch.hpp"
|
||||||
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
|
#include <ixbots/IXCobraToStdoutBot.h>
|
||||||
|
#include <ixcobra/IXCobraConnection.h>
|
||||||
|
#include <ixcobra/IXCobraMetricsPublisher.h>
|
||||||
|
#include <ixcrypto/IXUuid.h>
|
||||||
|
#include <ixsentry/IXSentryClient.h>
|
||||||
|
#include <ixsnake/IXRedisServer.h>
|
||||||
|
#include <ixsnake/IXSnakeServer.h>
|
||||||
|
#include <ixwebsocket/IXHttpServer.h>
|
||||||
|
#include <ixwebsocket/IXUserAgent.h>
|
||||||
|
|
||||||
|
using namespace ix;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
void runPublisher(const ix::CobraConfig& config, const std::string& channel)
|
||||||
|
{
|
||||||
|
ix::CobraMetricsPublisher cobraMetricsPublisher;
|
||||||
|
cobraMetricsPublisher.configure(config, channel);
|
||||||
|
cobraMetricsPublisher.setSession(uuid4());
|
||||||
|
cobraMetricsPublisher.enable(true);
|
||||||
|
|
||||||
|
Json::Value msg;
|
||||||
|
msg["fps"] = 60;
|
||||||
|
|
||||||
|
cobraMetricsPublisher.setGenericAttributes("game", "ody");
|
||||||
|
|
||||||
|
// Wait a bit
|
||||||
|
ix::msleep(500);
|
||||||
|
|
||||||
|
// publish some messages
|
||||||
|
cobraMetricsPublisher.push("sms_metric_A_id", msg); // (msg #1)
|
||||||
|
cobraMetricsPublisher.push("sms_metric_B_id", msg); // (msg #2)
|
||||||
|
ix::msleep(500);
|
||||||
|
|
||||||
|
cobraMetricsPublisher.push("sms_metric_A_id", msg); // (msg #3)
|
||||||
|
cobraMetricsPublisher.push("sms_metric_D_id", msg); // (msg #4)
|
||||||
|
ix::msleep(500);
|
||||||
|
|
||||||
|
cobraMetricsPublisher.push("sms_metric_A_id", msg); // (msg #4)
|
||||||
|
cobraMetricsPublisher.push("sms_metric_F_id", msg); // (msg #5)
|
||||||
|
ix::msleep(500);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("Cobra_to_stdout_bot", "[cobra_bots]")
|
||||||
|
{
|
||||||
|
SECTION("Exchange and count sent/received messages.")
|
||||||
|
{
|
||||||
|
int port = getFreePort();
|
||||||
|
snake::AppConfig appConfig = makeSnakeServerConfig(port, true);
|
||||||
|
|
||||||
|
// Start a redis server
|
||||||
|
ix::RedisServer redisServer(appConfig.redisPort);
|
||||||
|
auto res = redisServer.listen();
|
||||||
|
REQUIRE(res.first);
|
||||||
|
redisServer.start();
|
||||||
|
|
||||||
|
// Start a snake server
|
||||||
|
snake::SnakeServer snakeServer(appConfig);
|
||||||
|
snakeServer.run();
|
||||||
|
|
||||||
|
// Run the bot for a small amount of time
|
||||||
|
std::string channel = ix::generateSessionId();
|
||||||
|
std::string appkey("FC2F10139A2BAc53BB72D9db967b024f");
|
||||||
|
std::string role = "_sub";
|
||||||
|
std::string secret = "66B1dA3ED5fA074EB5AE84Dd8CE3b5ba";
|
||||||
|
std::string endpoint = makeCobraEndpoint(port, true);
|
||||||
|
|
||||||
|
ix::CobraConfig config;
|
||||||
|
config.endpoint = endpoint;
|
||||||
|
config.appkey = appkey;
|
||||||
|
config.rolename = role;
|
||||||
|
config.rolesecret = secret;
|
||||||
|
config.socketTLSOptions = makeClientTLSOptions();
|
||||||
|
|
||||||
|
std::thread publisherThread(runPublisher, config, channel);
|
||||||
|
|
||||||
|
std::string filter;
|
||||||
|
std::string position("$");
|
||||||
|
bool verbose = true;
|
||||||
|
bool quiet = false;
|
||||||
|
size_t maxQueueSize = 10;
|
||||||
|
bool enableHeartbeat = false;
|
||||||
|
|
||||||
|
// Only run the bot for 3 seconds
|
||||||
|
int runtime = 3;
|
||||||
|
|
||||||
|
// We could try to capture the output ... not sure how.
|
||||||
|
bool fluentd = true;
|
||||||
|
|
||||||
|
int64_t sentCount = ix::cobra_to_stdout_bot(config,
|
||||||
|
channel,
|
||||||
|
filter,
|
||||||
|
position,
|
||||||
|
fluentd,
|
||||||
|
quiet,
|
||||||
|
verbose,
|
||||||
|
maxQueueSize,
|
||||||
|
enableHeartbeat,
|
||||||
|
runtime);
|
||||||
|
//
|
||||||
|
// We want at least 2 messages to be sent
|
||||||
|
//
|
||||||
|
REQUIRE(sentCount >= 2);
|
||||||
|
|
||||||
|
// Give us 1s for all messages to be received
|
||||||
|
ix::msleep(1000);
|
||||||
|
|
||||||
|
spdlog::info("Stopping snake server...");
|
||||||
|
snakeServer.stop();
|
||||||
|
|
||||||
|
spdlog::info("Stopping redis server...");
|
||||||
|
redisServer.stop();
|
||||||
|
|
||||||
|
publisherThread.join();
|
||||||
|
}
|
||||||
|
}
|
@@ -17,8 +17,11 @@
|
|||||||
#include <ixwebsocket/IXSelectInterruptFactory.h>
|
#include <ixwebsocket/IXSelectInterruptFactory.h>
|
||||||
#include <ixwebsocket/IXSetThreadName.h>
|
#include <ixwebsocket/IXSetThreadName.h>
|
||||||
#include <ixwebsocket/IXSocket.h>
|
#include <ixwebsocket/IXSocket.h>
|
||||||
|
#include <ixwebsocket/IXSocketAppleSSL.h>
|
||||||
#include <ixwebsocket/IXSocketConnect.h>
|
#include <ixwebsocket/IXSocketConnect.h>
|
||||||
#include <ixwebsocket/IXSocketFactory.h>
|
#include <ixwebsocket/IXSocketFactory.h>
|
||||||
|
#include <ixwebsocket/IXSocketMbedTLS.h>
|
||||||
|
#include <ixwebsocket/IXSocketOpenSSL.h>
|
||||||
#include <ixwebsocket/IXSocketServer.h>
|
#include <ixwebsocket/IXSocketServer.h>
|
||||||
#include <ixwebsocket/IXUrlParser.h>
|
#include <ixwebsocket/IXUrlParser.h>
|
||||||
#include <ixwebsocket/IXWebSocket.h>
|
#include <ixwebsocket/IXWebSocket.h>
|
||||||
@@ -26,9 +29,9 @@
|
|||||||
#include <ixwebsocket/IXWebSocketCloseInfo.h>
|
#include <ixwebsocket/IXWebSocketCloseInfo.h>
|
||||||
#include <ixwebsocket/IXWebSocketErrorInfo.h>
|
#include <ixwebsocket/IXWebSocketErrorInfo.h>
|
||||||
#include <ixwebsocket/IXWebSocketHandshake.h>
|
#include <ixwebsocket/IXWebSocketHandshake.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketHandshakeKeyGen.h>
|
||||||
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||||
#include <ixwebsocket/IXWebSocketMessage.h>
|
#include <ixwebsocket/IXWebSocketMessage.h>
|
||||||
#include <ixwebsocket/IXWebSocketMessageQueue.h>
|
|
||||||
#include <ixwebsocket/IXWebSocketMessageType.h>
|
#include <ixwebsocket/IXWebSocketMessageType.h>
|
||||||
#include <ixwebsocket/IXWebSocketOpenInfo.h>
|
#include <ixwebsocket/IXWebSocketOpenInfo.h>
|
||||||
#include <ixwebsocket/IXWebSocketPerMessageDeflate.h>
|
#include <ixwebsocket/IXWebSocketPerMessageDeflate.h>
|
||||||
@@ -37,8 +40,6 @@
|
|||||||
#include <ixwebsocket/IXWebSocketSendInfo.h>
|
#include <ixwebsocket/IXWebSocketSendInfo.h>
|
||||||
#include <ixwebsocket/IXWebSocketServer.h>
|
#include <ixwebsocket/IXWebSocketServer.h>
|
||||||
#include <ixwebsocket/IXWebSocketTransport.h>
|
#include <ixwebsocket/IXWebSocketTransport.h>
|
||||||
#include <ixwebsocket/LUrlParser.h>
|
|
||||||
#include <ixwebsocket/libwshandshake.hpp>
|
|
||||||
|
|
||||||
using namespace ix;
|
using namespace ix;
|
||||||
|
|
||||||
|
@@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
#include "IXTest.h"
|
#include "IXTest.h"
|
||||||
#include "catch.hpp"
|
#include "catch.hpp"
|
||||||
#include <iostream>
|
|
||||||
#include "msgpack11.hpp"
|
#include "msgpack11.hpp"
|
||||||
|
#include <iostream>
|
||||||
#include <ixwebsocket/IXSocket.h>
|
#include <ixwebsocket/IXSocket.h>
|
||||||
#include <ixwebsocket/IXSocketFactory.h>
|
#include <ixwebsocket/IXSocketFactory.h>
|
||||||
#include <ixwebsocket/IXWebSocket.h>
|
#include <ixwebsocket/IXWebSocket.h>
|
||||||
@@ -130,7 +130,8 @@ namespace
|
|||||||
}
|
}
|
||||||
else if (msg->type == ix::WebSocketMessageType::Error)
|
else if (msg->type == ix::WebSocketMessageType::Error)
|
||||||
{
|
{
|
||||||
ss << "websocket_broadcast_client: " << _user << " Error ! " << msg->errorInfo.reason;
|
ss << "websocket_broadcast_client: " << _user << " Error ! "
|
||||||
|
<< msg->errorInfo.reason;
|
||||||
log(ss.str());
|
log(ss.str());
|
||||||
}
|
}
|
||||||
else if (msg->type == ix::WebSocketMessageType::Ping)
|
else if (msg->type == ix::WebSocketMessageType::Ping)
|
||||||
@@ -234,7 +235,7 @@ namespace
|
|||||||
server.start();
|
server.start();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} // namespace ix
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("Websocket_broadcast_server", "[websocket_server]")
|
TEST_CASE("Websocket_broadcast_server", "[websocket_server]")
|
||||||
{
|
{
|
||||||
@@ -247,7 +248,7 @@ TEST_CASE("Websocket_broadcast_server", "[websocket_server]")
|
|||||||
|
|
||||||
std::string session = ix::generateSessionId();
|
std::string session = ix::generateSessionId();
|
||||||
std::vector<std::shared_ptr<WebSocketChat>> chatClients;
|
std::vector<std::shared_ptr<WebSocketChat>> chatClients;
|
||||||
for (int i = 0 ; i < 10; ++i)
|
for (int i = 0; i < 10; ++i)
|
||||||
{
|
{
|
||||||
std::string user("user_" + std::to_string(i));
|
std::string user("user_" + std::to_string(i));
|
||||||
chatClients.push_back(std::make_shared<WebSocketChat>(user, session, port));
|
chatClients.push_back(std::make_shared<WebSocketChat>(user, session, port));
|
||||||
@@ -259,7 +260,7 @@ TEST_CASE("Websocket_broadcast_server", "[websocket_server]")
|
|||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
bool allReady = true;
|
bool allReady = true;
|
||||||
for (size_t i = 0 ; i < chatClients.size(); ++i)
|
for (size_t i = 0; i < chatClients.size(); ++i)
|
||||||
{
|
{
|
||||||
allReady &= chatClients[i]->isReady();
|
allReady &= chatClients[i]->isReady();
|
||||||
}
|
}
|
||||||
@@ -269,7 +270,7 @@ TEST_CASE("Websocket_broadcast_server", "[websocket_server]")
|
|||||||
|
|
||||||
for (int j = 0; j < 1000; j++)
|
for (int j = 0; j < 1000; j++)
|
||||||
{
|
{
|
||||||
for (size_t i = 0 ; i < chatClients.size(); ++i)
|
for (size_t i = 0; i < chatClients.size(); ++i)
|
||||||
{
|
{
|
||||||
chatClients[i]->sendMessage("hello world");
|
chatClients[i]->sendMessage("hello world");
|
||||||
}
|
}
|
||||||
@@ -291,7 +292,7 @@ TEST_CASE("Websocket_broadcast_server", "[websocket_server]")
|
|||||||
|
|
||||||
// Stop all clients
|
// Stop all clients
|
||||||
size_t messageCount = chatClients.size() * 50;
|
size_t messageCount = chatClients.size() * 50;
|
||||||
for (size_t i = 0 ; i < chatClients.size(); ++i)
|
for (size_t i = 0; i < chatClients.size(); ++i)
|
||||||
{
|
{
|
||||||
REQUIRE(chatClients[i]->getReceivedMessagesCount() >= messageCount);
|
REQUIRE(chatClients[i]->getReceivedMessagesCount() >= messageCount);
|
||||||
chatClients[i]->stop();
|
chatClients[i]->stop();
|
||||||
|
@@ -1,178 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocketMessageQTest.cpp
|
|
||||||
* Author: Korchynskyi Dmytro
|
|
||||||
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXTest.h"
|
|
||||||
#include "catch.hpp"
|
|
||||||
#include <ixwebsocket/IXWebSocket.h>
|
|
||||||
#include <ixwebsocket/IXWebSocketMessageQueue.h>
|
|
||||||
#include <ixwebsocket/IXWebSocketServer.h>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
using namespace ix;
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
bool startServer(ix::WebSocketServer& server)
|
|
||||||
{
|
|
||||||
server.setOnConnectionCallback([&server](std::shared_ptr<ix::WebSocket> webSocket,
|
|
||||||
std::shared_ptr<ConnectionState> connectionState) {
|
|
||||||
webSocket->setOnMessageCallback(
|
|
||||||
[connectionState, &server](const WebSocketMessagePtr& msg) {
|
|
||||||
if (msg->type == ix::WebSocketMessageType::Open)
|
|
||||||
{
|
|
||||||
TLogger() << "New connection";
|
|
||||||
connectionState->computeId();
|
|
||||||
TLogger() << "id: " << connectionState->getId();
|
|
||||||
TLogger() << "Uri: " << msg->openInfo.uri;
|
|
||||||
TLogger() << "Headers:";
|
|
||||||
for (auto&& it : msg->openInfo.headers)
|
|
||||||
{
|
|
||||||
TLogger() << it.first << ": " << it.second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
|
||||||
{
|
|
||||||
TLogger() << "Closed connection";
|
|
||||||
}
|
|
||||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
|
||||||
{
|
|
||||||
TLogger() << "Message received: " << msg->str;
|
|
||||||
|
|
||||||
for (auto&& client : server.getClients())
|
|
||||||
{
|
|
||||||
client->send(msg->str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
auto res = server.listen();
|
|
||||||
if (!res.first)
|
|
||||||
{
|
|
||||||
TLogger() << res.second;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
server.start();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
class MsgQTestClient
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
MsgQTestClient()
|
|
||||||
{
|
|
||||||
msgQ.bindWebsocket(&ws);
|
|
||||||
|
|
||||||
msgQ.setOnMessageCallback([this](const WebSocketMessagePtr& msg) {
|
|
||||||
REQUIRE(mainThreadId == std::this_thread::get_id());
|
|
||||||
|
|
||||||
std::stringstream ss;
|
|
||||||
if (msg->type == WebSocketMessageType::Open)
|
|
||||||
{
|
|
||||||
log("client connected");
|
|
||||||
sendNextMessage();
|
|
||||||
}
|
|
||||||
else if (msg->type == WebSocketMessageType::Close)
|
|
||||||
{
|
|
||||||
log("client disconnected");
|
|
||||||
}
|
|
||||||
else if (msg->type == WebSocketMessageType::Error)
|
|
||||||
{
|
|
||||||
ss << "Error ! " << msg->errorInfo.reason;
|
|
||||||
log(ss.str());
|
|
||||||
testDone = true;
|
|
||||||
}
|
|
||||||
else if (msg->type == WebSocketMessageType::Pong)
|
|
||||||
{
|
|
||||||
ss << "Received pong message " << msg->str;
|
|
||||||
log(ss.str());
|
|
||||||
}
|
|
||||||
else if (msg->type == WebSocketMessageType::Ping)
|
|
||||||
{
|
|
||||||
ss << "Received ping message " << msg->str;
|
|
||||||
log(ss.str());
|
|
||||||
}
|
|
||||||
else if (msg->type == WebSocketMessageType::Message)
|
|
||||||
{
|
|
||||||
REQUIRE(msg->str.compare("Hey dude!") == 0);
|
|
||||||
++receivedCount;
|
|
||||||
ss << "Received message " << msg->str;
|
|
||||||
log(ss.str());
|
|
||||||
sendNextMessage();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ss << "Invalid WebSocketMessageType";
|
|
||||||
log(ss.str());
|
|
||||||
testDone = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendNextMessage()
|
|
||||||
{
|
|
||||||
if (receivedCount >= 3)
|
|
||||||
{
|
|
||||||
testDone = true;
|
|
||||||
succeeded = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto info = ws.sendText("Hey dude!");
|
|
||||||
if (info.success)
|
|
||||||
log("sent message");
|
|
||||||
else
|
|
||||||
log("send failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void run(const std::string& url)
|
|
||||||
{
|
|
||||||
mainThreadId = std::this_thread::get_id();
|
|
||||||
testDone = false;
|
|
||||||
receivedCount = 0;
|
|
||||||
|
|
||||||
ws.setUrl(url);
|
|
||||||
ws.start();
|
|
||||||
|
|
||||||
while (!testDone)
|
|
||||||
{
|
|
||||||
msgQ.poll();
|
|
||||||
msleep(50);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isSucceeded() const
|
|
||||||
{
|
|
||||||
return succeeded;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
WebSocket ws;
|
|
||||||
WebSocketMessageQueue msgQ;
|
|
||||||
bool testDone = false;
|
|
||||||
uint32_t receivedCount = 0;
|
|
||||||
std::thread::id mainThreadId;
|
|
||||||
bool succeeded = false;
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("Websocket_message_queue", "[websocket_message_q]")
|
|
||||||
{
|
|
||||||
SECTION("Send several messages")
|
|
||||||
{
|
|
||||||
int port = getFreePort();
|
|
||||||
WebSocketServer server(port);
|
|
||||||
REQUIRE(startServer(server));
|
|
||||||
|
|
||||||
MsgQTestClient testClient;
|
|
||||||
testClient.run("ws://127.0.0.1:" + std::to_string(port));
|
|
||||||
REQUIRE(testClient.isSucceeded());
|
|
||||||
|
|
||||||
server.stop();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -14,8 +14,41 @@ int main(int argc, char* argv[])
|
|||||||
{
|
{
|
||||||
ix::initNetSystem();
|
ix::initNetSystem();
|
||||||
|
|
||||||
ix::IXCoreLogger::LogFunc logFunc = [](const char* msg) { spdlog::info(msg); };
|
ix::CoreLogger::LogFunc logFunc = [](const char* msg, ix::LogLevel level) {
|
||||||
ix::IXCoreLogger::setLogFunction(logFunc);
|
switch (level)
|
||||||
|
{
|
||||||
|
case ix::LogLevel::Debug:
|
||||||
|
{
|
||||||
|
spdlog::debug(msg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ix::LogLevel::Info:
|
||||||
|
{
|
||||||
|
spdlog::info(msg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ix::LogLevel::Warning:
|
||||||
|
{
|
||||||
|
spdlog::warn(msg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ix::LogLevel::Error:
|
||||||
|
{
|
||||||
|
spdlog::error(msg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ix::LogLevel::Critical:
|
||||||
|
{
|
||||||
|
spdlog::critical(msg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ix::CoreLogger::setLogFunction(logFunc);
|
||||||
|
|
||||||
int result = Catch::Session().run(argc, argv);
|
int result = Catch::Session().run(argc, argv);
|
||||||
|
|
||||||
|
6
third_party/.clang-format
vendored
6
third_party/.clang-format
vendored
@@ -1,4 +1,2 @@
|
|||||||
{
|
DisableFormat: true
|
||||||
"DisableFormat": true,
|
SortIncludes: false
|
||||||
"SortIncludes": false
|
|
||||||
}
|
|
||||||
|
41
third_party/mbedtls/.github/issue_template.md
vendored
41
third_party/mbedtls/.github/issue_template.md
vendored
@@ -1,41 +0,0 @@
|
|||||||
Note: This is just a template, so feel free to use/remove the unnecessary things
|
|
||||||
|
|
||||||
### Description
|
|
||||||
- Type: Bug | Enhancement\Feature Request | Question
|
|
||||||
- Priority: Blocker | Major | Minor
|
|
||||||
|
|
||||||
---------------------------------------------------------------
|
|
||||||
## Bug
|
|
||||||
|
|
||||||
**OS**
|
|
||||||
Mbed OS|linux|windows|
|
|
||||||
|
|
||||||
**mbed TLS build:**
|
|
||||||
Version: x.x.x or git commit id
|
|
||||||
OS version: x.x.x
|
|
||||||
Configuration: please attach config.h file where possible
|
|
||||||
Compiler and options (if you used a pre-built binary, please indicate how you obtained it):
|
|
||||||
Additional environment information:
|
|
||||||
|
|
||||||
**Peer device TLS stack and version**
|
|
||||||
OpenSSL|GnuTls|Chrome|NSS(Firefox)|SecureChannel (IIS/Internet Explorer/Edge)|Other
|
|
||||||
Version:
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
|
|
||||||
**Actual behavior**
|
|
||||||
|
|
||||||
**Steps to reproduce**
|
|
||||||
|
|
||||||
----------------------------------------------------------------
|
|
||||||
## Enhancement\Feature Request
|
|
||||||
|
|
||||||
**Justification - why does the library need this feature?**
|
|
||||||
|
|
||||||
**Suggested enhancement**
|
|
||||||
|
|
||||||
-----------------------------------------------------------------
|
|
||||||
|
|
||||||
## Question
|
|
||||||
|
|
||||||
**Please first check for answers in the [Mbed TLS knowledge Base](https://tls.mbed.org/kb), and preferably file an issue in the [Mbed TLS support forum](https://forums.mbed.com/c/mbed-tls)**
|
|
@@ -1,39 +0,0 @@
|
|||||||
Notes:
|
|
||||||
* Pull requests cannot be accepted until:
|
|
||||||
- The submitter has [accepted the online agreement here with a click through](https://developer.mbed.org/contributor_agreement/)
|
|
||||||
or for companies or those that do not wish to create an mbed account, a slightly different agreement can be found [here](https://www.mbed.com/en/about-mbed/contributor-license-agreements/)
|
|
||||||
- The PR follows the [mbed TLS coding standards](https://tls.mbed.org/kb/development/mbedtls-coding-standards)
|
|
||||||
* This is just a template, so feel free to use/remove the unnecessary things
|
|
||||||
## Description
|
|
||||||
A few sentences describing the overall goals of the pull request's commits.
|
|
||||||
|
|
||||||
|
|
||||||
## Status
|
|
||||||
**READY/IN DEVELOPMENT/HOLD**
|
|
||||||
|
|
||||||
## Requires Backporting
|
|
||||||
When there is a bug fix, it should be backported to all maintained and supported branches.
|
|
||||||
Changes do not have to be backported if:
|
|
||||||
- This PR is a new feature\enhancement
|
|
||||||
- This PR contains changes in the API. If this is true, and there is a need for the fix to be backported, the fix should be handled differently in the legacy branch
|
|
||||||
|
|
||||||
Yes | NO
|
|
||||||
Which branch?
|
|
||||||
|
|
||||||
## Migrations
|
|
||||||
If there is any API change, what's the incentive and logic for it.
|
|
||||||
|
|
||||||
YES | NO
|
|
||||||
|
|
||||||
## Additional comments
|
|
||||||
Any additional information that could be of interest
|
|
||||||
|
|
||||||
## Todos
|
|
||||||
- [ ] Tests
|
|
||||||
- [ ] Documentation
|
|
||||||
- [ ] Changelog updated
|
|
||||||
- [ ] Backported
|
|
||||||
|
|
||||||
|
|
||||||
## Steps to test or reproduce
|
|
||||||
Outline the steps to test or reproduce the PR here.
|
|
43
third_party/mbedtls/.gitignore
vendored
43
third_party/mbedtls/.gitignore
vendored
@@ -1,43 +0,0 @@
|
|||||||
# Random seed file created by test scripts and sample programs
|
|
||||||
seedfile
|
|
||||||
|
|
||||||
# CMake build artifacts:
|
|
||||||
CMakeCache.txt
|
|
||||||
CMakeFiles
|
|
||||||
CTestTestfile.cmake
|
|
||||||
cmake_install.cmake
|
|
||||||
Testing
|
|
||||||
# CMake generates *.dir/ folders for in-tree builds (used by MSVC projects), ignore all of those:
|
|
||||||
*.dir/
|
|
||||||
# MSVC files generated by CMake:
|
|
||||||
/*.sln
|
|
||||||
/*.vcxproj
|
|
||||||
/*.filters
|
|
||||||
|
|
||||||
# Test coverage build artifacts:
|
|
||||||
Coverage
|
|
||||||
*.gcno
|
|
||||||
*.gcda
|
|
||||||
|
|
||||||
# generated by scripts/memory.sh
|
|
||||||
massif-*
|
|
||||||
|
|
||||||
# MSVC build artifacts:
|
|
||||||
*.exe
|
|
||||||
*.pdb
|
|
||||||
*.ilk
|
|
||||||
*.lib
|
|
||||||
|
|
||||||
# Python build artifacts:
|
|
||||||
*.pyc
|
|
||||||
|
|
||||||
# Generated documentation:
|
|
||||||
/apidoc
|
|
||||||
|
|
||||||
# Editor navigation files:
|
|
||||||
/GPATH
|
|
||||||
/GRTAGS
|
|
||||||
/GSYMS
|
|
||||||
/GTAGS
|
|
||||||
/TAGS
|
|
||||||
/tags
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user