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 |
16
.github/workflows/ccpp.yml
vendored
16
.github/workflows/ccpp.yml
vendored
@ -19,15 +19,6 @@ jobs:
|
||||
- name: 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:
|
||||
runs-on: macOS-latest
|
||||
steps:
|
||||
@ -37,18 +28,15 @@ jobs:
|
||||
- name: make test
|
||||
run: make test_tsan_mbedtls
|
||||
|
||||
windows_mbedtls:
|
||||
windows_openssl:
|
||||
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 ..
|
||||
cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_WS=1 -DUSE_TEST=1 ..
|
||||
- run: cmake --build build
|
||||
|
||||
# Running the unittest does not work, the binary cannot be found
|
||||
|
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:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.3.0
|
||||
rev: v2.5.0
|
||||
hooks:
|
||||
- id: check-yaml
|
||||
- id: end-of-file-fixer
|
||||
- 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_library(JSONCPP_LIBRARY jsoncpp)
|
||||
|
||||
find_package_handle_standard_args(JSONCPP
|
||||
find_package_handle_standard_args(JsonCpp
|
||||
FOUND_VAR
|
||||
JSONCPP_FOUND
|
||||
REQUIRED_VARS
|
||||
|
111
CMakeLists.txt
111
CMakeLists.txt
@ -44,13 +44,11 @@ set( IXWEBSOCKET_SOURCES
|
||||
ixwebsocket/IXWebSocketCloseConstants.cpp
|
||||
ixwebsocket/IXWebSocketHandshake.cpp
|
||||
ixwebsocket/IXWebSocketHttpHeaders.cpp
|
||||
ixwebsocket/IXWebSocketMessageQueue.cpp
|
||||
ixwebsocket/IXWebSocketPerMessageDeflate.cpp
|
||||
ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp
|
||||
ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp
|
||||
ixwebsocket/IXWebSocketServer.cpp
|
||||
ixwebsocket/IXWebSocketTransport.cpp
|
||||
ixwebsocket/LUrlParser.cpp
|
||||
)
|
||||
|
||||
set( IXWEBSOCKET_HEADERS
|
||||
@ -81,10 +79,10 @@ set( IXWEBSOCKET_HEADERS
|
||||
ixwebsocket/IXWebSocketCloseInfo.h
|
||||
ixwebsocket/IXWebSocketErrorInfo.h
|
||||
ixwebsocket/IXWebSocketHandshake.h
|
||||
ixwebsocket/IXWebSocketHandshakeKeyGen.h
|
||||
ixwebsocket/IXWebSocketHttpHeaders.h
|
||||
ixwebsocket/IXWebSocketInitResult.h
|
||||
ixwebsocket/IXWebSocketMessage.h
|
||||
ixwebsocket/IXWebSocketMessageQueue.h
|
||||
ixwebsocket/IXWebSocketMessageType.h
|
||||
ixwebsocket/IXWebSocketOpenInfo.h
|
||||
ixwebsocket/IXWebSocketPerMessageDeflate.h
|
||||
@ -94,8 +92,6 @@ set( IXWEBSOCKET_HEADERS
|
||||
ixwebsocket/IXWebSocketServer.h
|
||||
ixwebsocket/IXWebSocketTransport.h
|
||||
ixwebsocket/IXWebSocketVersion.h
|
||||
ixwebsocket/LUrlParser.h
|
||||
ixwebsocket/libwshandshake.hpp
|
||||
)
|
||||
|
||||
if (UNIX)
|
||||
@ -113,31 +109,33 @@ elseif (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/freebsd/IXSetThreadName_freebsd.cpp)
|
||||
else()
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/linux/IXSetThreadName_linux.cpp)
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptEventFd.cpp)
|
||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSelectInterruptEventFd.h)
|
||||
endif()
|
||||
|
||||
option(USE_TLS "Enable TLS support" FALSE)
|
||||
|
||||
if (USE_TLS)
|
||||
option(USE_MBED_TLS "Use Mbed TLS" OFF)
|
||||
option(USE_OPEN_SSL "Use OpenSSL" OFF)
|
||||
|
||||
# default to mbedtls on windows if nothing is configured
|
||||
if (WIN32 AND NOT USE_OPEN_SSL AND NOT USE_MBED_TLS)
|
||||
option(USE_MBED_TLS "Use Mbed TLS" ON)
|
||||
# default to securetranport on Apple if nothing is configured
|
||||
if (APPLE)
|
||||
if (NOT USE_MBED_TLS AND NOT USE_OPEN_SSL) # unless we want something else
|
||||
set(USE_SECURE_TRANSPORT ON)
|
||||
endif()
|
||||
else() # default to OpenSSL on all other platforms
|
||||
if (NOT USE_MBED_TLS) # Unless mbedtls is requested
|
||||
set(USE_OPEN_SSL ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (USE_MBED_TLS)
|
||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketMbedTLS.h)
|
||||
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_SOURCES ixwebsocket/IXSocketAppleSSL.cpp)
|
||||
else()
|
||||
set(USE_OPEN_SSL ON)
|
||||
elseif (USE_OPEN_SSL)
|
||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketOpenSSL.h)
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketOpenSSL.cpp)
|
||||
else()
|
||||
message(FATAL_ERROR "TLS Configuration error: unknown backend")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@ -152,55 +150,45 @@ if (USE_TLS)
|
||||
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_MBED_TLS)
|
||||
elseif (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()
|
||||
|
||||
if (APPLE AND USE_TLS AND NOT USE_MBED_TLS AND NOT USE_OPEN_SSL)
|
||||
target_link_libraries(ixwebsocket "-framework foundation" "-framework security")
|
||||
endif()
|
||||
if (USE_TLS)
|
||||
if (USE_OPEN_SSL)
|
||||
message(STATUS "TLS configured to use openssl")
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(ixwebsocket wsock32 ws2_32 shlwapi)
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||
endif()
|
||||
# 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 (UNIX)
|
||||
find_package(Threads)
|
||||
target_link_libraries(ixwebsocket ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
# This OPENSSL_FOUND check is to help find a cmake manually configured OpenSSL
|
||||
if (NOT OPENSSL_FOUND)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
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)
|
||||
target_include_directories(ixwebsocket PUBLIC ${MBEDTLS_INCLUDE_DIRS})
|
||||
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()
|
||||
|
||||
# This ZLIB_FOUND check is to help find a cmake manually configured zlib
|
||||
if (NOT ZLIB_FOUND)
|
||||
find_package(ZLIB)
|
||||
endif()
|
||||
@ -213,6 +201,21 @@ else()
|
||||
target_link_libraries(ixwebsocket zlibstatic)
|
||||
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
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
@ -248,3 +251,7 @@ if (USE_WS OR USE_TEST)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (USE_LUAROCKS)
|
||||
add_subdirectory(luarocks)
|
||||
endif()
|
||||
|
@ -1,6 +1,74 @@
|
||||
# Changelog
|
||||
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
|
||||
|
||||
(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:
|
||||
|
||||
* `-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_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:
|
||||
|
||||
@ -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.
|
||||
|
||||
```
|
||||
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.
|
||||
|
@ -38,8 +38,7 @@ The regression test is running after each commit on github actions for multiple
|
||||
|
||||
## 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`.
|
||||
* There is no convenient way to embed a ca cert.
|
||||
* 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`.
|
||||
* 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.
|
||||
|
||||
|
@ -4,7 +4,7 @@ Notes on how we can update the different packages for ixwebsocket.
|
||||
|
||||
Visit the [releases](https://github.com/machinezone/IXWebSocket/releases) page on Github. A tag must have been made first.
|
||||
|
||||
Download the latest entry.
|
||||
Download the latest entry.
|
||||
|
||||
```
|
||||
$ cd /tmp
|
||||
|
@ -447,7 +447,7 @@ Additional TLS options can be configured by passing a `ix::SocketTLSOptions` ins
|
||||
webSocket.setTLSOptions({
|
||||
.certFile = "path/to/cert/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
|
||||
});
|
||||
```
|
||||
@ -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.
|
||||
- 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.
|
||||
- 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.
|
||||
|
||||
|
@ -4,15 +4,19 @@
|
||||
#
|
||||
|
||||
set (IXBOTS_SOURCES
|
||||
ixbots/IXCobraBot.cpp
|
||||
ixbots/IXCobraToSentryBot.cpp
|
||||
ixbots/IXCobraToStatsdBot.cpp
|
||||
ixbots/IXCobraToStdoutBot.cpp
|
||||
ixbots/IXQueueManager.cpp
|
||||
ixbots/IXStatsdClient.cpp
|
||||
)
|
||||
|
||||
set (IXBOTS_HEADERS
|
||||
ixbots/IXCobraBot.h
|
||||
ixbots/IXCobraToSentryBot.h
|
||||
ixbots/IXCobraToStatsdBot.h
|
||||
ixbots/IXCobraToStdoutBot.h
|
||||
ixbots/IXQueueManager.h
|
||||
ixbots/IXStatsdClient.h
|
||||
)
|
||||
@ -27,11 +31,6 @@ if (NOT JSONCPP_FOUND)
|
||||
set(JSONCPP_INCLUDE_DIRS ../third_party/jsoncpp)
|
||||
endif()
|
||||
|
||||
find_package(SpdLog)
|
||||
if (NOT SPDLOG_FOUND)
|
||||
set(SPDLOG_INCLUDE_DIRS ../third_party/spdlog/include)
|
||||
endif()
|
||||
|
||||
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 "IXCobraBot.h"
|
||||
#include "IXQueueManager.h"
|
||||
#include <ixcobra/IXCobraConnection.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <ixcobra/IXCobraConnection.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
int cobra_to_sentry_bot(const CobraConfig& config,
|
||||
const std::string& channel,
|
||||
const std::string& filter,
|
||||
const std::string& position,
|
||||
SentryClient& sentryClient,
|
||||
bool verbose,
|
||||
bool strict,
|
||||
size_t maxQueueSize,
|
||||
bool enableHeartbeat,
|
||||
int runtime)
|
||||
int64_t cobra_to_sentry_bot(const CobraConfig& config,
|
||||
const std::string& channel,
|
||||
const std::string& filter,
|
||||
const std::string& position,
|
||||
SentryClient& sentryClient,
|
||||
bool verbose,
|
||||
size_t maxQueueSize,
|
||||
bool enableHeartbeat,
|
||||
int runtime)
|
||||
{
|
||||
ix::CobraConnection conn;
|
||||
conn.configure(config);
|
||||
conn.connect();
|
||||
CobraBot bot;
|
||||
bot.setOnBotMessageCallback([&sentryClient](const Json::Value& msg,
|
||||
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;
|
||||
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)
|
||||
if (!response)
|
||||
{
|
||||
spdlog::info("messages received {} sent {}", receivedCount, sentCount);
|
||||
|
||||
auto duration = std::chrono::seconds(1);
|
||||
std::this_thread::sleep_for(duration);
|
||||
CoreLogger::warn("Null HTTP Response");
|
||||
return false;
|
||||
}
|
||||
|
||||
spdlog::info("timer thread done");
|
||||
};
|
||||
|
||||
std::thread t1(timer);
|
||||
|
||||
auto heartbeat = [&sentCount, &receivedCount, &stop, &enableHeartbeat] {
|
||||
std::string state("na");
|
||||
|
||||
if (!enableHeartbeat) return;
|
||||
|
||||
while (!stop)
|
||||
if (verbose)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "messages received " << receivedCount;
|
||||
ss << "messages sent " << sentCount;
|
||||
|
||||
std::string currentState = ss.str();
|
||||
|
||||
if (currentState == state)
|
||||
for (auto it : response->headers)
|
||||
{
|
||||
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 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;
|
||||
CoreLogger::info(it.first + ": " + it.second);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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)
|
||||
CoreLogger::info("Status: " + std::to_string(response->statusCode));
|
||||
if (response->errorCode != HttpErrorCode::Ok)
|
||||
{
|
||||
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
|
||||
if (throttled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bool success = response->statusCode == 200;
|
||||
|
||||
++receivedCount;
|
||||
queueManager.add(msg);
|
||||
});
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Subscribed)
|
||||
if (!success)
|
||||
{
|
||||
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;
|
||||
CoreLogger::error("Error sending data to sentry: " + std::to_string(response->statusCode));
|
||||
CoreLogger::error("Body: " + ret.second);
|
||||
CoreLogger::error("Response: " + response->payload);
|
||||
|
||||
// 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;
|
||||
CoreLogger::warn("Error parsing Retry-After header. "
|
||||
"Using " + retryAfter + " for the sleep duration");
|
||||
}
|
||||
|
||||
CoreLogger::warn("Error 429 - Too Many Requests. ws will sleep "
|
||||
"and retry after " + retryAfter + " seconds");
|
||||
|
||||
throttled = true;
|
||||
auto duration = std::chrono::seconds(seconds);
|
||||
std::this_thread::sleep_for(duration);
|
||||
throttled = false;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
});
|
||||
|
||||
// Run forever
|
||||
if (runtime == -1)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
auto duration = std::chrono::seconds(1);
|
||||
std::this_thread::sleep_for(duration);
|
||||
bool useQueue = true;
|
||||
|
||||
if (strict && errorSending) break;
|
||||
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 (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;
|
||||
return bot.run(config,
|
||||
channel,
|
||||
filter,
|
||||
position,
|
||||
verbose,
|
||||
maxQueueSize,
|
||||
useQueue,
|
||||
enableHeartbeat,
|
||||
runtime);
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -5,20 +5,20 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <ixcobra/IXCobraConfig.h>
|
||||
#include <ixsentry/IXSentryClient.h>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
int cobra_to_sentry_bot(const CobraConfig& config,
|
||||
const std::string& channel,
|
||||
const std::string& filter,
|
||||
const std::string& position,
|
||||
SentryClient& sentryClient,
|
||||
bool verbose,
|
||||
bool strict,
|
||||
size_t maxQueueSize,
|
||||
bool enableHeartbeat,
|
||||
int runtime);
|
||||
int64_t cobra_to_sentry_bot(const CobraConfig& config,
|
||||
const std::string& channel,
|
||||
const std::string& filter,
|
||||
const std::string& position,
|
||||
SentryClient& sentryClient,
|
||||
bool verbose,
|
||||
size_t maxQueueSize,
|
||||
bool enableHeartbeat,
|
||||
int runtime);
|
||||
} // namespace ix
|
||||
|
@ -5,16 +5,14 @@
|
||||
*/
|
||||
|
||||
#include "IXCobraToStatsdBot.h"
|
||||
|
||||
#include "IXCobraBot.h"
|
||||
#include "IXQueueManager.h"
|
||||
#include "IXStatsdClient.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <ixcobra/IXCobraConnection.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace ix
|
||||
@ -56,18 +54,18 @@ namespace ix
|
||||
return val;
|
||||
}
|
||||
|
||||
int cobra_to_statsd_bot(const ix::CobraConfig& config,
|
||||
const std::string& channel,
|
||||
const std::string& filter,
|
||||
const std::string& position,
|
||||
StatsdClient& statsdClient,
|
||||
const std::string& fields,
|
||||
const std::string& gauge,
|
||||
const std::string& timer,
|
||||
bool verbose,
|
||||
size_t maxQueueSize,
|
||||
bool enableHeartbeat,
|
||||
int runtime)
|
||||
int64_t cobra_to_statsd_bot(const ix::CobraConfig& config,
|
||||
const std::string& channel,
|
||||
const std::string& filter,
|
||||
const std::string& position,
|
||||
StatsdClient& statsdClient,
|
||||
const std::string& fields,
|
||||
const std::string& gauge,
|
||||
const std::string& timer,
|
||||
bool verbose,
|
||||
size_t maxQueueSize,
|
||||
bool enableHeartbeat,
|
||||
int runtime)
|
||||
{
|
||||
ix::CobraConnection conn;
|
||||
conn.configure(config);
|
||||
@ -75,65 +73,13 @@ namespace ix
|
||||
|
||||
auto tokens = parseFields(fields);
|
||||
|
||||
Json::FastWriter jsonWriter;
|
||||
std::atomic<uint64_t> sentCount(0);
|
||||
std::atomic<uint64_t> receivedCount(0);
|
||||
std::atomic<bool> stop(false);
|
||||
std::atomic<bool> fatalCobraError(false);
|
||||
|
||||
QueueManager queueManager(maxQueueSize);
|
||||
|
||||
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;
|
||||
|
||||
CobraBot bot;
|
||||
bot.setOnBotMessageCallback(
|
||||
[&statsdClient, &tokens, &gauge, &timer](const Json::Value& msg,
|
||||
const std::string& /*position*/,
|
||||
const bool verbose,
|
||||
std::atomic<bool>& /*throttled*/,
|
||||
std::atomic<bool>& fatalCobraError) -> bool {
|
||||
std::string id;
|
||||
for (auto&& attr : tokens)
|
||||
{
|
||||
@ -174,14 +120,14 @@ namespace ix
|
||||
}
|
||||
else
|
||||
{
|
||||
spdlog::error("Gauge {} is not a numberic type", gauge);
|
||||
CoreLogger::error("Gauge " + gauge + " is not a numeric type");
|
||||
fatalCobraError = true;
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
spdlog::info("{} - {} -> {}", id, attrName, x);
|
||||
CoreLogger::info(id + " - " + attrName + " -> " + std::to_string(x));
|
||||
}
|
||||
|
||||
if (!gauge.empty())
|
||||
@ -194,127 +140,19 @@ namespace ix
|
||||
}
|
||||
}
|
||||
|
||||
sentCount += 1;
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// Run forever
|
||||
if (runtime == -1)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
auto duration = std::chrono::seconds(1);
|
||||
std::this_thread::sleep_for(duration);
|
||||
bool useQueue = true;
|
||||
|
||||
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();
|
||||
|
||||
// statsd sender thread
|
||||
t3.join();
|
||||
|
||||
return fatalCobraError ? -1 : (int) sentCount;
|
||||
return bot.run(config,
|
||||
channel,
|
||||
filter,
|
||||
position,
|
||||
verbose,
|
||||
maxQueueSize,
|
||||
useQueue,
|
||||
enableHeartbeat,
|
||||
runtime);
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -5,23 +5,24 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <ixcobra/IXCobraConfig.h>
|
||||
#include <cstdint>
|
||||
#include <ixbots/IXStatsdClient.h>
|
||||
#include <string>
|
||||
#include <ixcobra/IXCobraConfig.h>
|
||||
#include <stddef.h>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
int cobra_to_statsd_bot(const ix::CobraConfig& config,
|
||||
const std::string& channel,
|
||||
const std::string& filter,
|
||||
const std::string& position,
|
||||
StatsdClient& statsdClient,
|
||||
const std::string& fields,
|
||||
const std::string& gauge,
|
||||
const std::string& timer,
|
||||
bool verbose,
|
||||
size_t maxQueueSize,
|
||||
bool enableHeartbeat,
|
||||
int runtime);
|
||||
int64_t cobra_to_statsd_bot(const ix::CobraConfig& config,
|
||||
const std::string& channel,
|
||||
const std::string& filter,
|
||||
const std::string& position,
|
||||
StatsdClient& statsdClient,
|
||||
const std::string& fields,
|
||||
const std::string& gauge,
|
||||
const std::string& timer,
|
||||
bool verbose,
|
||||
size_t maxQueueSize,
|
||||
bool enableHeartbeat,
|
||||
int runtime);
|
||||
} // 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 <vector>
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
Json::Value QueueManager::pop()
|
||||
std::pair<Json::Value, std::string> QueueManager::pop()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
|
||||
if (_queues.empty())
|
||||
{
|
||||
Json::Value val;
|
||||
return val;
|
||||
return std::make_pair(val, std::string());
|
||||
}
|
||||
|
||||
std::vector<std::string> games;
|
||||
@ -35,7 +36,7 @@ namespace ix
|
||||
if (_queues[game].empty())
|
||||
{
|
||||
Json::Value val;
|
||||
return val;
|
||||
return std::make_pair(val, std::string());
|
||||
}
|
||||
|
||||
auto msg = _queues[game].front();
|
||||
@ -43,7 +44,7 @@ namespace ix
|
||||
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);
|
||||
|
||||
@ -59,8 +60,8 @@ namespace ix
|
||||
// in queuing too many events.
|
||||
if (_queues[game].size() < _maxQueueSize)
|
||||
{
|
||||
_queues[game].push(msg);
|
||||
_queues[game].push(std::make_pair(msg, position));
|
||||
_condition.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -6,12 +6,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <json/json.h>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <queue>
|
||||
#include <json/json.h>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <stddef.h>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
@ -23,13 +23,13 @@ namespace ix
|
||||
{
|
||||
}
|
||||
|
||||
Json::Value pop();
|
||||
void add(Json::Value msg);
|
||||
std::pair<Json::Value, std::string> pop();
|
||||
void add(const Json::Value& msg, const std::string& position);
|
||||
|
||||
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::condition_variable _condition;
|
||||
size_t _maxQueueSize;
|
||||
};
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -39,24 +39,21 @@
|
||||
|
||||
#include "IXStatsdClient.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <ixwebsocket/IXNetSystem.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <iostream>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
StatsdClient::StatsdClient(const std::string& host,
|
||||
int port,
|
||||
const std::string& prefix)
|
||||
: _host(host)
|
||||
, _port(port)
|
||||
, _prefix(prefix)
|
||||
, _stop(false)
|
||||
StatsdClient::StatsdClient(const std::string& host, int port, const std::string& prefix)
|
||||
: _host(host)
|
||||
, _port(port)
|
||||
, _prefix(prefix)
|
||||
, _stop(false)
|
||||
{
|
||||
_thread = std::thread([this]
|
||||
{
|
||||
_thread = std::thread([this] {
|
||||
while (!_stop)
|
||||
{
|
||||
flushQueue();
|
||||
@ -119,8 +116,8 @@ namespace ix
|
||||
cleanup(key);
|
||||
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf), "%s%s:%zd|%s\n",
|
||||
_prefix.c_str(), key.c_str(), value, type.c_str());
|
||||
snprintf(
|
||||
buf, sizeof(buf), "%s%s:%zd|%s\n", _prefix.c_str(), key.c_str(), value, type.c_str());
|
||||
|
||||
enqueue(buf);
|
||||
return 0;
|
||||
@ -142,9 +139,7 @@ namespace ix
|
||||
auto ret = _socket.sendto(message);
|
||||
if (ret != 0)
|
||||
{
|
||||
std::cerr << "error: "
|
||||
<< strerror(UdpSocket::getErrno())
|
||||
<< std::endl;
|
||||
std::cerr << "error: " << strerror(UdpSocket::getErrno()) << std::endl;
|
||||
}
|
||||
_queue.pop_front();
|
||||
}
|
||||
|
@ -6,21 +6,20 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <ixwebsocket/IXUdpSocket.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class StatsdClient
|
||||
{
|
||||
public:
|
||||
StatsdClient(const std::string& host="127.0.0.1",
|
||||
int port=8125,
|
||||
StatsdClient(const std::string& host = "127.0.0.1",
|
||||
int port = 8125,
|
||||
const std::string& prefix = "");
|
||||
~StatsdClient();
|
||||
|
||||
|
@ -14,6 +14,7 @@ set (IXCOBRA_HEADERS
|
||||
ixcobra/IXCobraMetricsThreadedPublisher.h
|
||||
ixcobra/IXCobraMetricsPublisher.h
|
||||
ixcobra/IXCobraConfig.h
|
||||
ixcobra/IXCobraEventType.h
|
||||
)
|
||||
|
||||
add_library(ixcobra STATIC
|
||||
|
@ -6,8 +6,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
|
@ -5,17 +5,17 @@
|
||||
*/
|
||||
|
||||
#include "IXCobraConnection.h"
|
||||
#include <ixcrypto/IXHMac.h>
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <ixcrypto/IXHMac.h>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
|
||||
namespace ix
|
||||
@ -26,12 +26,12 @@ namespace ix
|
||||
constexpr CobraConnection::MsgId CobraConnection::kInvalidMsgId;
|
||||
constexpr int CobraConnection::kPingIntervalSecs;
|
||||
|
||||
CobraConnection::CobraConnection() :
|
||||
_webSocket(new WebSocket()),
|
||||
_publishMode(CobraConnection_PublishMode_Immediate),
|
||||
_authenticated(false),
|
||||
_eventCallback(nullptr),
|
||||
_id(1)
|
||||
CobraConnection::CobraConnection()
|
||||
: _webSocket(new WebSocket())
|
||||
, _publishMode(CobraConnection_PublishMode_Immediate)
|
||||
, _authenticated(false)
|
||||
, _eventCallback(nullptr)
|
||||
, _id(1)
|
||||
{
|
||||
_pdu["action"] = "rtm/publish";
|
||||
|
||||
@ -87,7 +87,7 @@ namespace ix
|
||||
_eventCallback = eventCallback;
|
||||
}
|
||||
|
||||
void CobraConnection::invokeEventCallback(ix::CobraConnectionEventType eventType,
|
||||
void CobraConnection::invokeEventCallback(ix::CobraEventType eventType,
|
||||
const std::string& errorMsg,
|
||||
const WebSocketHttpHeaders& headers,
|
||||
const std::string& subscriptionId,
|
||||
@ -96,7 +96,8 @@ namespace ix
|
||||
std::lock_guard<std::mutex> lock(_eventCallbackMutex);
|
||||
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;
|
||||
ss << errorMsg << " : received pdu => " << serializedPdu;
|
||||
invokeEventCallback(ix::CobraConnection_EventType_Error, ss.str());
|
||||
invokeEventCallback(ix::CobraEventType::Error, ss.str());
|
||||
}
|
||||
|
||||
void CobraConnection::disconnect()
|
||||
@ -116,126 +117,119 @@ namespace ix
|
||||
|
||||
void CobraConnection::initWebSocketOnMessageCallback()
|
||||
{
|
||||
_webSocket->setOnMessageCallback(
|
||||
[this](const ix::WebSocketMessagePtr& msg)
|
||||
_webSocket->setOnMessageCallback([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;
|
||||
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,
|
||||
std::string(),
|
||||
msg->openInfo.headers);
|
||||
sendHandshakeMessage();
|
||||
invokeErrorCallback("Invalid json", msg->str);
|
||||
return;
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
_authenticated = false;
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "Close code " << msg->closeInfo.code;
|
||||
ss << " reason " << msg->closeInfo.reason;
|
||||
invokeEventCallback(ix::CobraConnection_EventType_Closed,
|
||||
ss.str());
|
||||
if (!data.isMember("action"))
|
||||
{
|
||||
invokeErrorCallback("Missing action", msg->str);
|
||||
return;
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||
|
||||
auto action = data["action"].asString();
|
||||
|
||||
if (action == "auth/handshake/ok")
|
||||
{
|
||||
Json::Value data;
|
||||
Json::Reader reader;
|
||||
if (!reader.parse(msg->str, data))
|
||||
if (!handleHandshakeResponse(data))
|
||||
{
|
||||
invokeErrorCallback("Invalid json", msg->str);
|
||||
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,
|
||||
invokeErrorCallback("Error extracting nonce from handshake response",
|
||||
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;
|
||||
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());
|
||||
invokeEventCallback(ix::CobraEventType::HandshakeError, msg->str);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
void CobraConnection::configure(const std::string& appkey,
|
||||
const std::string& endpoint,
|
||||
const std::string& rolename,
|
||||
const std::string& rolesecret,
|
||||
const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions,
|
||||
const SocketTLSOptions& socketTLSOptions)
|
||||
void CobraConnection::configure(
|
||||
const std::string& appkey,
|
||||
const std::string& endpoint,
|
||||
const std::string& rolename,
|
||||
const std::string& rolesecret,
|
||||
const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions,
|
||||
const SocketTLSOptions& socketTLSOptions)
|
||||
{
|
||||
_roleName = rolename;
|
||||
_roleSecret = rolesecret;
|
||||
@ -396,8 +391,9 @@ namespace ix
|
||||
|
||||
if (!subscriptionId.isString()) return false;
|
||||
|
||||
invokeEventCallback(ix::CobraConnection_EventType_Subscribed,
|
||||
std::string(), WebSocketHttpHeaders(),
|
||||
invokeEventCallback(ix::CobraEventType::Subscribed,
|
||||
std::string(),
|
||||
WebSocketHttpHeaders(),
|
||||
subscriptionId.asString());
|
||||
return true;
|
||||
}
|
||||
@ -414,8 +410,9 @@ namespace ix
|
||||
|
||||
if (!subscriptionId.isString()) return false;
|
||||
|
||||
invokeEventCallback(ix::CobraConnection_EventType_UnSubscribed,
|
||||
std::string(), WebSocketHttpHeaders(),
|
||||
invokeEventCallback(ix::CobraEventType::UnSubscribed,
|
||||
std::string(),
|
||||
WebSocketHttpHeaders(),
|
||||
subscriptionId.asString());
|
||||
return true;
|
||||
}
|
||||
@ -462,9 +459,11 @@ namespace ix
|
||||
|
||||
uint64_t msgId = id.asUInt64();
|
||||
|
||||
invokeEventCallback(ix::CobraConnection_EventType_Published,
|
||||
std::string(), WebSocketHttpHeaders(),
|
||||
std::string(), msgId);
|
||||
invokeEventCallback(ix::CobraEventType::Published,
|
||||
std::string(),
|
||||
WebSocketHttpHeaders(),
|
||||
std::string(),
|
||||
msgId);
|
||||
|
||||
invokePublishTrackerCallback(false, true);
|
||||
|
||||
@ -494,9 +493,7 @@ namespace ix
|
||||
}
|
||||
|
||||
std::pair<CobraConnection::MsgId, std::string> CobraConnection::prePublish(
|
||||
const Json::Value& channels,
|
||||
const Json::Value& msg,
|
||||
bool addToQueue)
|
||||
const Json::Value& channels, const Json::Value& msg, bool addToQueue)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_prePublishMutex);
|
||||
|
||||
@ -662,8 +659,7 @@ namespace ix
|
||||
bool CobraConnection::publishMessage(const std::string& serializedJson)
|
||||
{
|
||||
auto webSocketSendInfo = _webSocket->send(serializedJson);
|
||||
CobraConnection::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize,
|
||||
false);
|
||||
CobraConnection::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false);
|
||||
return webSocketSendInfo.success;
|
||||
}
|
||||
|
||||
|
@ -6,18 +6,19 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IXCobraConfig.h"
|
||||
#include "IXCobraEvent.h"
|
||||
#include "IXCobraEventType.h"
|
||||
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
||||
#include <json/json.h>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <limits>
|
||||
|
||||
#include "IXCobraConfig.h"
|
||||
|
||||
#ifdef max
|
||||
#undef max
|
||||
@ -28,21 +29,6 @@ namespace ix
|
||||
class WebSocket;
|
||||
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
|
||||
{
|
||||
CobraConnection_PublishMode_Immediate = 0,
|
||||
@ -50,11 +36,7 @@ namespace ix
|
||||
};
|
||||
|
||||
using SubscriptionCallback = std::function<void(const Json::Value&, const std::string&)>;
|
||||
using EventCallback = std::function<void(CobraConnectionEventType,
|
||||
const std::string&,
|
||||
const WebSocketHttpHeaders&,
|
||||
const std::string&,
|
||||
uint64_t msgId)>;
|
||||
using EventCallback = std::function<void(const CobraEventPtr&)>;
|
||||
|
||||
using TrafficTrackerCallback = std::function<void(size_t size, bool incoming)>;
|
||||
using PublishTrackerCallback = std::function<void(bool sent, bool acked)>;
|
||||
@ -138,10 +120,9 @@ namespace ix
|
||||
|
||||
/// Prepare a message for transmission
|
||||
/// (update the pdu, compute a msgId, serialize json to a string)
|
||||
std::pair<CobraConnection::MsgId, std::string> prePublish(
|
||||
const Json::Value& channels,
|
||||
const Json::Value& msg,
|
||||
bool addToQueue);
|
||||
std::pair<CobraConnection::MsgId, std::string> prePublish(const Json::Value& channels,
|
||||
const Json::Value& msg,
|
||||
bool addToQueue);
|
||||
|
||||
/// Attempt to send next message from the internal queue
|
||||
bool publishNext();
|
||||
@ -171,7 +152,7 @@ namespace ix
|
||||
static void invokePublishTrackerCallback(bool sent, bool acked);
|
||||
|
||||
/// Invoke event callbacks
|
||||
void invokeEventCallback(CobraConnectionEventType eventType,
|
||||
void invokeEventCallback(CobraEventType eventType,
|
||||
const std::string& errorMsg = std::string(),
|
||||
const WebSocketHttpHeaders& headers = WebSocketHttpHeaders(),
|
||||
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 <ixwebsocket/IXSocketTLSOptions.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
#include <stdexcept>
|
||||
|
||||
|
||||
@ -17,8 +17,8 @@ namespace ix
|
||||
const std::string CobraMetricsPublisher::kSetRateControlId = "sms_set_rate_control_id";
|
||||
const std::string CobraMetricsPublisher::kSetBlacklistId = "sms_set_blacklist_id";
|
||||
|
||||
CobraMetricsPublisher::CobraMetricsPublisher() :
|
||||
_enabled(true)
|
||||
CobraMetricsPublisher::CobraMetricsPublisher()
|
||||
: _enabled(true)
|
||||
{
|
||||
}
|
||||
|
||||
@ -27,8 +27,7 @@ namespace ix
|
||||
;
|
||||
}
|
||||
|
||||
void CobraMetricsPublisher::configure(const CobraConfig& config,
|
||||
const std::string& channel)
|
||||
void CobraMetricsPublisher::configure(const CobraConfig& config, const std::string& channel)
|
||||
{
|
||||
// Configure the satori connection and start its publish background thread
|
||||
_cobra_metrics_theaded_publisher.configure(config, channel);
|
||||
@ -42,7 +41,7 @@ namespace ix
|
||||
}
|
||||
|
||||
void CobraMetricsPublisher::setGenericAttributes(const std::string& attrName,
|
||||
const Json::Value& value)
|
||||
const Json::Value& value)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_device_mutex);
|
||||
_device[attrName] = value;
|
||||
@ -107,8 +106,7 @@ namespace ix
|
||||
auto last_update = _last_update.find(id);
|
||||
if (last_update == _last_update.end()) return false;
|
||||
|
||||
auto timeDeltaFromLastSend =
|
||||
std::chrono::steady_clock::now() - last_update->second;
|
||||
auto timeDeltaFromLastSend = std::chrono::steady_clock::now() - last_update->second;
|
||||
|
||||
return timeDeltaFromLastSend < std::chrono::seconds(rate_control_it->second);
|
||||
}
|
||||
@ -123,8 +121,7 @@ namespace ix
|
||||
{
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto ms =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now.time_since_epoch()).count();
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
|
||||
|
||||
return ms;
|
||||
}
|
||||
@ -165,10 +162,9 @@ namespace ix
|
||||
return true;
|
||||
}
|
||||
|
||||
CobraConnection::MsgId CobraMetricsPublisher::push(
|
||||
const std::string& id,
|
||||
const Json::Value& data,
|
||||
bool shouldPushTest)
|
||||
CobraConnection::MsgId CobraMetricsPublisher::push(const std::string& id,
|
||||
const Json::Value& data,
|
||||
bool shouldPushTest)
|
||||
{
|
||||
if (shouldPushTest && !shouldPush(id)) return CobraConnection::kInvalidMsgId;
|
||||
|
||||
|
@ -40,8 +40,7 @@ namespace ix
|
||||
|
||||
/// Configuration / set keys, etc...
|
||||
/// All input data but the channel name is encrypted with rc4
|
||||
void configure(const CobraConfig& config,
|
||||
const std::string& channel);
|
||||
void configure(const CobraConfig& config, const std::string& channel);
|
||||
|
||||
/// Setter for the list of blacklisted metrics ids.
|
||||
/// 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
|
||||
/// 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.
|
||||
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.
|
||||
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.
|
||||
/// 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);
|
||||
|
||||
/// 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
|
||||
const std::string& getSession() const { return _session; }
|
||||
const std::string& getSession() const
|
||||
{
|
||||
return _session;
|
||||
}
|
||||
|
||||
/// Return the number of milliseconds since the epoch (~1970)
|
||||
uint64_t getMillisecondsSinceEpoch() const;
|
||||
|
@ -5,72 +5,77 @@
|
||||
*/
|
||||
|
||||
#include "IXCobraMetricsThreadedPublisher.h"
|
||||
#include <ixwebsocket/IXSetThreadName.h>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
#include <ixwebsocket/IXSetThreadName.h>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
|
||||
namespace ix
|
||||
{
|
||||
CobraMetricsThreadedPublisher::CobraMetricsThreadedPublisher() :
|
||||
_stop(false)
|
||||
CobraMetricsThreadedPublisher::CobraMetricsThreadedPublisher()
|
||||
: _stop(false)
|
||||
{
|
||||
_cobra_connection.setEventCallback(
|
||||
[]
|
||||
(ix::CobraConnectionEventType eventType,
|
||||
const std::string& errMsg,
|
||||
const ix::WebSocketHttpHeaders& headers,
|
||||
const std::string& subscriptionId,
|
||||
CobraConnection::MsgId msgId)
|
||||
_cobra_connection.setEventCallback([](const CobraEventPtr& event) {
|
||||
std::stringstream ss;
|
||||
|
||||
if (event->type == ix::CobraEventType::Open)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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());
|
||||
CoreLogger::log(ss.str().c_str());
|
||||
});
|
||||
}
|
||||
|
||||
@ -95,11 +100,10 @@ namespace ix
|
||||
void CobraMetricsThreadedPublisher::configure(const CobraConfig& config,
|
||||
const std::string& channel)
|
||||
{
|
||||
ix::IXCoreLogger::Log(config.socketTLSOptions.getDescription().c_str());
|
||||
CoreLogger::log(config.socketTLSOptions.getDescription().c_str());
|
||||
|
||||
_channel = channel;
|
||||
_cobra_connection.configure(config);
|
||||
|
||||
}
|
||||
|
||||
void CobraMetricsThreadedPublisher::pushMessage(MessageKind messageKind)
|
||||
@ -157,13 +161,15 @@ namespace ix
|
||||
{
|
||||
_cobra_connection.suspend();
|
||||
continue;
|
||||
}; break;
|
||||
};
|
||||
break;
|
||||
|
||||
case MessageKind::Resume:
|
||||
{
|
||||
_cobra_connection.resume();
|
||||
continue;
|
||||
}; break;
|
||||
};
|
||||
break;
|
||||
|
||||
case MessageKind::Message:
|
||||
{
|
||||
@ -171,7 +177,8 @@ namespace ix
|
||||
{
|
||||
_cobra_connection.publishNext();
|
||||
}
|
||||
}; break;
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,8 +27,7 @@ namespace ix
|
||||
~CobraMetricsThreadedPublisher();
|
||||
|
||||
/// Configuration / set keys, etc...
|
||||
void configure(const CobraConfig& config,
|
||||
const std::string& channel);
|
||||
void configure(const CobraConfig& config, const std::string& channel);
|
||||
|
||||
/// Start the worker thread, used for background publishing
|
||||
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
|
||||
{
|
||||
// Default do nothing logger
|
||||
IXCoreLogger::LogFunc IXCoreLogger::_currentLogger = [](const char* /*msg*/){};
|
||||
// Default do a no-op logger
|
||||
CoreLogger::LogFunc CoreLogger::_currentLogger = [](const char*, LogLevel) {};
|
||||
|
||||
void IXCoreLogger::Log(const char* msg)
|
||||
{
|
||||
_currentLogger(msg);
|
||||
}
|
||||
void CoreLogger::log(const char* msg, LogLevel level)
|
||||
{
|
||||
_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
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class IXCoreLogger
|
||||
enum class LogLevel
|
||||
{
|
||||
Debug = 0,
|
||||
Info = 1,
|
||||
Warning = 2,
|
||||
Error = 3,
|
||||
Critical = 4
|
||||
};
|
||||
|
||||
class CoreLogger
|
||||
{
|
||||
public:
|
||||
using LogFunc = std::function<void(const char*)>;
|
||||
static void Log(const char* msg);
|
||||
using LogFunc = std::function<void(const char*, LogLevel level)>;
|
||||
|
||||
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:
|
||||
static LogFunc _currentLogger;
|
||||
|
@ -29,10 +29,9 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
static const std::string base64_chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
|
||||
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_4[4];
|
||||
|
||||
while(len--)
|
||||
while (len--)
|
||||
{
|
||||
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[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[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]];
|
||||
|
||||
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_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[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]];
|
||||
|
||||
while((i++ < 3))
|
||||
while ((i++ < 3))
|
||||
ret += '=';
|
||||
|
||||
}
|
||||
|
||||
return ret;
|
||||
@ -95,7 +93,7 @@ namespace ix
|
||||
|
||||
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 j = 0;
|
||||
int in_ = 0;
|
||||
@ -103,40 +101,42 @@ namespace ix
|
||||
std::string ret;
|
||||
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_++;
|
||||
if(i ==4)
|
||||
char_array_4[i++] = encoded_string[in_];
|
||||
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_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[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];
|
||||
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(i)
|
||||
if (i)
|
||||
{
|
||||
for(j = i; j <4; j++)
|
||||
for (j = i; j < 4; j++)
|
||||
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_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[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;
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -5,16 +5,17 @@
|
||||
*/
|
||||
|
||||
#include "IXHMac.h"
|
||||
|
||||
#include "IXBase64.h"
|
||||
|
||||
#if defined(IXCRYPTO_USE_MBED_TLS)
|
||||
# include <mbedtls/md.h>
|
||||
#include <mbedtls/md.h>
|
||||
#elif defined(__APPLE__)
|
||||
# include <CommonCrypto/CommonHMAC.h>
|
||||
#include <CommonCrypto/CommonHMAC.h>
|
||||
#elif defined(IXCRYPTO_USE_OPEN_SSL)
|
||||
# include <openssl/hmac.h>
|
||||
#include <openssl/hmac.h>
|
||||
#else
|
||||
# include <assert.h>
|
||||
#include <assert.h>
|
||||
#endif
|
||||
|
||||
namespace ix
|
||||
@ -26,19 +27,21 @@ namespace ix
|
||||
|
||||
#if defined(IXCRYPTO_USE_MBED_TLS)
|
||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_MD5),
|
||||
(unsigned char *) key.c_str(), key.size(),
|
||||
(unsigned char *) data.c_str(), data.size(),
|
||||
(unsigned char *) &hash);
|
||||
(unsigned char*) key.c_str(),
|
||||
key.size(),
|
||||
(unsigned char*) data.c_str(),
|
||||
data.size(),
|
||||
(unsigned char*) &hash);
|
||||
#elif defined(__APPLE__)
|
||||
CCHmac(kCCHmacAlgMD5,
|
||||
key.c_str(), key.size(),
|
||||
data.c_str(), data.size(),
|
||||
&hash);
|
||||
CCHmac(kCCHmacAlgMD5, key.c_str(), key.size(), data.c_str(), data.size(), &hash);
|
||||
#elif defined(IXCRYPTO_USE_OPEN_SSL)
|
||||
HMAC(EVP_md5(),
|
||||
key.c_str(), (int) key.size(),
|
||||
(unsigned char *) data.c_str(), (int) data.size(),
|
||||
(unsigned char *) hash, nullptr);
|
||||
key.c_str(),
|
||||
(int) key.size(),
|
||||
(unsigned char*) data.c_str(),
|
||||
(int) data.size(),
|
||||
(unsigned char*) hash,
|
||||
nullptr);
|
||||
#else
|
||||
assert(false && "hmac not implemented on this platform");
|
||||
#endif
|
||||
@ -47,4 +50,4 @@ namespace ix
|
||||
|
||||
return base64_encode(hashString, (uint32_t) hashString.size());
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -19,4 +19,4 @@ namespace ix
|
||||
|
||||
return hashAddress;
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -16,23 +16,23 @@
|
||||
|
||||
#include "IXUuid.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <iomanip>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class Uuid
|
||||
{
|
||||
public:
|
||||
Uuid();
|
||||
std::string toString() const;
|
||||
public:
|
||||
Uuid();
|
||||
std::string toString() const;
|
||||
|
||||
private:
|
||||
uint64_t _ab;
|
||||
uint64_t _cd;
|
||||
private:
|
||||
uint64_t _ab;
|
||||
uint64_t _cd;
|
||||
};
|
||||
|
||||
Uuid::Uuid()
|
||||
@ -60,7 +60,7 @@ namespace ix
|
||||
ss << std::setw(8) << (a);
|
||||
ss << std::setw(4) << (b >> 16);
|
||||
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(8) << d;
|
||||
|
||||
@ -72,4 +72,4 @@ namespace ix
|
||||
Uuid id;
|
||||
return id.toString();
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -7,12 +7,12 @@
|
||||
#include "IXSentryClient.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||
#include <ixwebsocket/IXWebSocketVersion.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
namespace ix
|
||||
@ -64,7 +64,8 @@ namespace ix
|
||||
std::string SentryClient::computeAuthHeader()
|
||||
{
|
||||
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_key=" + _publicKey;
|
||||
securityHeader += ",sentry_secret=" + _secretKey;
|
||||
@ -233,7 +234,7 @@ namespace ix
|
||||
args->transferTimeout = 5 * 60;
|
||||
args->followRedirects = true;
|
||||
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);
|
||||
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::stringstream ss;
|
||||
ss << "https://sentry.io/api/"
|
||||
<< project
|
||||
<< "/minidump?sentry_key="
|
||||
<< key;
|
||||
ss << "https://sentry.io/api/" << project << "/minidump?sentry_key=" << key;
|
||||
|
||||
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(
|
||||
const std::string& sentryMetadata,
|
||||
const std::string& minidumpBytes,
|
||||
const std::string& project,
|
||||
const std::string& key,
|
||||
bool verbose,
|
||||
const OnResponseCallback& onResponseCallback)
|
||||
void SentryClient::uploadMinidump(const std::string& sentryMetadata,
|
||||
const std::string& minidumpBytes,
|
||||
const std::string& project,
|
||||
const std::string& key,
|
||||
bool verbose,
|
||||
const OnResponseCallback& onResponseCallback)
|
||||
{
|
||||
std::string multipartBoundary = _httpClient->generateMultipartBoundary();
|
||||
|
||||
@ -273,7 +271,7 @@ namespace ix
|
||||
args->followRedirects = true;
|
||||
args->verbose = verbose;
|
||||
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["upload_file_minidump"] = minidumpBytes;
|
||||
@ -282,7 +280,27 @@ namespace ix
|
||||
httpParameters["sentry"] = sentryMetadata;
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -10,8 +10,8 @@
|
||||
#include <ixwebsocket/IXHttpClient.h>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
#include <json/json.h>
|
||||
#include <regex>
|
||||
#include <memory>
|
||||
#include <regex>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
@ -28,13 +28,16 @@ namespace ix
|
||||
// Mostly for testing
|
||||
void setTLSOptions(const SocketTLSOptions& tlsOptions);
|
||||
|
||||
void uploadMinidump(
|
||||
const std::string& sentryMetadata,
|
||||
const std::string& minidumpBytes,
|
||||
const std::string& project,
|
||||
const std::string& key,
|
||||
bool verbose,
|
||||
const OnResponseCallback& onResponseCallback);
|
||||
void uploadMinidump(const std::string& sentryMetadata,
|
||||
const std::string& minidumpBytes,
|
||||
const std::string& project,
|
||||
const std::string& key,
|
||||
bool verbose,
|
||||
const OnResponseCallback& onResponseCallback);
|
||||
|
||||
void uploadPayload(const Json::Value& payload,
|
||||
bool verbose,
|
||||
const OnResponseCallback& onResponseCallback);
|
||||
|
||||
private:
|
||||
int64_t getTimestamp();
|
||||
|
@ -6,10 +6,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
|
||||
namespace snake
|
||||
{
|
||||
|
@ -28,10 +28,7 @@ namespace ix
|
||||
return false;
|
||||
}
|
||||
|
||||
CancellationRequest cancellationRequest = []() -> bool
|
||||
{
|
||||
return false;
|
||||
};
|
||||
CancellationRequest cancellationRequest = []() -> bool { return false; };
|
||||
|
||||
std::string errMsg;
|
||||
return _socket->connect(hostname, port, errMsg, cancellationRequest);
|
||||
@ -252,9 +249,8 @@ namespace ix
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string RedisClient::prepareXaddCommand(
|
||||
const std::string& stream,
|
||||
const std::string& message)
|
||||
std::string RedisClient::prepareXaddCommand(const std::string& stream,
|
||||
const std::string& message)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "*5\r\n";
|
||||
@ -328,7 +324,9 @@ namespace ix
|
||||
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);
|
||||
if (!sent)
|
||||
|
@ -8,11 +8,10 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class RedisClient
|
||||
@ -39,14 +38,11 @@ namespace ix
|
||||
const OnRedisSubscribeCallback& callback);
|
||||
|
||||
// XADD
|
||||
std::string xadd(
|
||||
const std::string& channel,
|
||||
const std::string& message,
|
||||
std::string& errMsg);
|
||||
std::string xadd(const std::string& channel,
|
||||
const std::string& message,
|
||||
std::string& errMsg);
|
||||
|
||||
std::string prepareXaddCommand(
|
||||
const std::string& stream,
|
||||
const std::string& message);
|
||||
std::string prepareXaddCommand(const std::string& stream, const std::string& message);
|
||||
|
||||
std::string readXaddReply(std::string& errMsg);
|
||||
|
||||
|
@ -6,17 +6,18 @@
|
||||
|
||||
#include "IXRedisServer.h"
|
||||
|
||||
#include <ixwebsocket/IXNetSystem.h>
|
||||
#include <ixwebsocket/IXSocketConnect.h>
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
#include <ixwebsocket/IXCancellationRequest.h>
|
||||
#include <fstream>
|
||||
#include <ixwebsocket/IXCancellationRequest.h>
|
||||
#include <ixwebsocket/IXNetSystem.h>
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
#include <ixwebsocket/IXSocketConnect.h>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
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)
|
||||
, _connectedClientsCount(0)
|
||||
, _stopHandlingConnections(false)
|
||||
@ -114,8 +115,7 @@ namespace ix
|
||||
for (auto it : _subscribers)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Subscription id: " << it.first
|
||||
<< " #subscribers: " << it.second.size();
|
||||
ss << "Subscription id: " << it.first << " #subscribers: " << it.second.size();
|
||||
|
||||
logInfo(ss.str());
|
||||
}
|
||||
@ -126,8 +126,7 @@ namespace ix
|
||||
return _connectedClientsCount;
|
||||
}
|
||||
|
||||
bool RedisServer::startsWith(const std::string& str,
|
||||
const std::string& start)
|
||||
bool RedisServer::startsWith(const std::string& str, const std::string& start)
|
||||
{
|
||||
return str.compare(0, start.length(), start) == 0;
|
||||
}
|
||||
@ -144,9 +143,8 @@ namespace ix
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool RedisServer::parseRequest(
|
||||
std::unique_ptr<Socket>& socket,
|
||||
std::vector<std::string>& tokens)
|
||||
bool RedisServer::parseRequest(std::unique_ptr<Socket>& socket,
|
||||
std::vector<std::string>& tokens)
|
||||
{
|
||||
// Parse first line
|
||||
auto cb = makeCancellationRequestWithTimeout(30, _stopHandlingConnections);
|
||||
@ -190,9 +188,8 @@ namespace ix
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RedisServer::handleCommand(
|
||||
std::unique_ptr<Socket>& socket,
|
||||
const std::vector<std::string>& tokens)
|
||||
bool RedisServer::handleCommand(std::unique_ptr<Socket>& socket,
|
||||
const std::vector<std::string>& tokens)
|
||||
{
|
||||
if (tokens.size() != 1) return false;
|
||||
|
||||
@ -207,31 +204,30 @@ namespace ix
|
||||
//
|
||||
ss << "*6\r\n";
|
||||
ss << writeString("publish"); // 1
|
||||
ss << ":3\r\n"; // 2
|
||||
ss << "*0\r\n"; // 3
|
||||
ss << ":1\r\n"; // 4
|
||||
ss << ":2\r\n"; // 5
|
||||
ss << ":1\r\n"; // 6
|
||||
ss << ":3\r\n"; // 2
|
||||
ss << "*0\r\n"; // 3
|
||||
ss << ":1\r\n"; // 4
|
||||
ss << ":2\r\n"; // 5
|
||||
ss << ":1\r\n"; // 6
|
||||
|
||||
//
|
||||
// subscribe
|
||||
//
|
||||
ss << "*6\r\n";
|
||||
ss << writeString("subscribe"); // 1
|
||||
ss << ":2\r\n"; // 2
|
||||
ss << "*0\r\n"; // 3
|
||||
ss << ":1\r\n"; // 4
|
||||
ss << ":1\r\n"; // 5
|
||||
ss << ":1\r\n"; // 6
|
||||
ss << ":2\r\n"; // 2
|
||||
ss << "*0\r\n"; // 3
|
||||
ss << ":1\r\n"; // 4
|
||||
ss << ":1\r\n"; // 5
|
||||
ss << ":1\r\n"; // 6
|
||||
|
||||
socket->writeBytes(ss.str(), cb);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RedisServer::handleSubscribe(
|
||||
std::unique_ptr<Socket>& socket,
|
||||
const std::vector<std::string>& tokens)
|
||||
bool RedisServer::handleSubscribe(std::unique_ptr<Socket>& socket,
|
||||
const std::vector<std::string>& tokens)
|
||||
{
|
||||
if (tokens.size() != 2) return false;
|
||||
|
||||
@ -250,9 +246,8 @@ namespace ix
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RedisServer::handlePublish(
|
||||
std::unique_ptr<Socket>& socket,
|
||||
const std::vector<std::string>& tokens)
|
||||
bool RedisServer::handlePublish(std::unique_ptr<Socket>& socket,
|
||||
const std::vector<std::string>& tokens)
|
||||
{
|
||||
if (tokens.size() != 3) return false;
|
||||
|
||||
@ -281,9 +276,7 @@ namespace ix
|
||||
|
||||
// return the number of clients that received the message.
|
||||
std::stringstream ss;
|
||||
ss << ":"
|
||||
<< std::to_string(subscribers.size())
|
||||
<< "\r\n";
|
||||
ss << ":" << std::to_string(subscribers.size()) << "\r\n";
|
||||
socket->writeBytes(ss.str(), cb);
|
||||
|
||||
return true;
|
||||
|
@ -6,13 +6,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IXSocketServer.h"
|
||||
#include "IXSocket.h"
|
||||
#include "IXSocketServer.h"
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility> // pair
|
||||
@ -50,18 +50,14 @@ namespace ix
|
||||
bool startsWith(const std::string& str, const std::string& start);
|
||||
std::string writeString(const std::string& str);
|
||||
|
||||
bool parseRequest(
|
||||
std::unique_ptr<Socket>& socket,
|
||||
std::vector<std::string>& tokens);
|
||||
bool parseRequest(std::unique_ptr<Socket>& socket, std::vector<std::string>& tokens);
|
||||
|
||||
bool handlePublish(std::unique_ptr<Socket>& socket,
|
||||
const std::vector<std::string>& tokens);
|
||||
bool handlePublish(std::unique_ptr<Socket>& socket, const std::vector<std::string>& tokens);
|
||||
|
||||
bool handleSubscribe(std::unique_ptr<Socket>& socket,
|
||||
const std::vector<std::string>& tokens);
|
||||
|
||||
bool handleCommand(std::unique_ptr<Socket>& socket,
|
||||
const std::vector<std::string>& tokens);
|
||||
bool handleCommand(std::unique_ptr<Socket>& socket, const std::vector<std::string>& tokens);
|
||||
|
||||
void cleanupSubscribers(std::unique_ptr<Socket>& socket);
|
||||
};
|
||||
|
@ -10,9 +10,9 @@
|
||||
#include "IXSnakeConnectionState.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
#include <iostream>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
#include <ixcrypto/IXHMac.h>
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
#include <sstream>
|
||||
|
||||
namespace snake
|
||||
@ -189,7 +189,8 @@ namespace snake
|
||||
nlohmann::json response = {
|
||||
{"action", "rtm/subscription/data"},
|
||||
{"id", id++},
|
||||
{"body", {{"subscription_id", subscriptionId}, {"position", "0-0"}, {"messages", {msg}}}}};
|
||||
{"body",
|
||||
{{"subscription_id", subscriptionId}, {"position", "0-0"}, {"messages", {msg}}}}};
|
||||
|
||||
ws->sendText(response.dump());
|
||||
};
|
||||
@ -197,7 +198,7 @@ namespace snake
|
||||
auto responseCallback = [ws, pdu, &subscriptionId](const std::string& redisResponse) {
|
||||
std::stringstream ss;
|
||||
ss << "Redis Response: " << redisResponse << "...";
|
||||
ix::IXCoreLogger::Log(ss.str().c_str());
|
||||
ix::CoreLogger::log(ss.str().c_str());
|
||||
|
||||
// Success
|
||||
nlohmann::json response = {{"action", "rtm/subscribe/ok"},
|
||||
@ -209,7 +210,7 @@ namespace snake
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Subscribing to " << appChannel << "...";
|
||||
ix::IXCoreLogger::Log(ss.str().c_str());
|
||||
ix::CoreLogger::log(ss.str().c_str());
|
||||
}
|
||||
|
||||
if (!redisClient.subscribe(appChannel, responseCallback, callback))
|
||||
@ -251,7 +252,21 @@ namespace snake
|
||||
const AppConfig& appConfig,
|
||||
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"];
|
||||
|
||||
if (action == "auth/handshake")
|
||||
|
@ -10,8 +10,8 @@
|
||||
#include "IXSnakeConnectionState.h"
|
||||
#include "IXSnakeProtocol.h"
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
namespace snake
|
||||
@ -29,7 +29,7 @@ namespace snake
|
||||
|
||||
std::stringstream ss;
|
||||
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(
|
||||
[this, webSocket, state](const ix::WebSocketMessagePtr& msg) {
|
||||
std::stringstream ss;
|
||||
ix::LogLevel logLevel = ix::LogLevel::Debug;
|
||||
if (msg->type == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
ss << "New connection" << std::endl;
|
||||
@ -86,6 +87,7 @@ namespace snake
|
||||
_appConfig.redisPort))
|
||||
{
|
||||
ss << "Cannot connect to redis host" << std::endl;
|
||||
logLevel = ix::LogLevel::Error;
|
||||
}
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||
@ -101,6 +103,7 @@ namespace snake
|
||||
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;
|
||||
logLevel = ix::LogLevel::Error;
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Fragment)
|
||||
{
|
||||
@ -112,7 +115,7 @@ namespace snake
|
||||
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;
|
||||
std::string body;
|
||||
std::string multipartBoundary;
|
||||
int connectTimeout;
|
||||
int transferTimeout;
|
||||
bool followRedirects;
|
||||
int maxRedirects;
|
||||
bool verbose;
|
||||
bool compress;
|
||||
int connectTimeout = 60;
|
||||
int transferTimeout = 1800;
|
||||
bool followRedirects = true;
|
||||
int maxRedirects = 5;
|
||||
bool verbose = false;
|
||||
bool compress = true;
|
||||
Logger logger;
|
||||
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())
|
||||
{
|
||||
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());
|
||||
@ -388,7 +389,8 @@ namespace ix
|
||||
}
|
||||
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);
|
||||
@ -397,7 +399,8 @@ namespace ix
|
||||
// This way we are not busy looping
|
||||
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
|
||||
* 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.
|
||||
*/
|
||||
#ifdef IXWEBSOCKET_USE_SECURE_TRANSPORT
|
||||
|
||||
#include "IXSocketAppleSSL.h"
|
||||
|
||||
#include "IXSocketConnect.h"
|
||||
@ -307,3 +309,5 @@ namespace ix
|
||||
}
|
||||
|
||||
} // namespace ix
|
||||
|
||||
#endif // IXWEBSOCKET_USE_SECURE_TRANSPORT
|
||||
|
@ -1,8 +1,9 @@
|
||||
/*
|
||||
* IXSocketAppleSSL.h
|
||||
* 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
|
||||
|
||||
@ -47,3 +48,5 @@ namespace ix
|
||||
};
|
||||
|
||||
} // namespace ix
|
||||
|
||||
#endif // IXWEBSOCKET_USE_SECURE_TRANSPORT
|
||||
|
@ -1,12 +1,13 @@
|
||||
/*
|
||||
* IXSocketMbedTLS.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
* Author: Benjamin Sergeant, Max Weisel
|
||||
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
|
||||
*
|
||||
* Some code taken from
|
||||
* https://github.com/rottor12/WsClientLib/blob/master/lib/src/WsClientLib.cpp
|
||||
* and mini_client.c example from mbedtls
|
||||
*/
|
||||
#ifdef IXWEBSOCKET_USE_MBED_TLS
|
||||
|
||||
#include "IXSocketMbedTLS.h"
|
||||
|
||||
@ -103,10 +104,26 @@ namespace ix
|
||||
{
|
||||
; // FIXME
|
||||
}
|
||||
else if (mbedtls_x509_crt_parse_file(&_cacert, _tlsOptions.caFile.c_str()) < 0)
|
||||
else
|
||||
{
|
||||
errMsg = "Cannot parse CA file '" + _tlsOptions.caFile + "'";
|
||||
return false;
|
||||
if (_tlsOptions.isUsingInMemoryCAs())
|
||||
{
|
||||
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);
|
||||
@ -280,3 +297,5 @@ namespace ix
|
||||
}
|
||||
|
||||
} // namespace ix
|
||||
|
||||
#endif // IXWEBSOCKET_USE_MBED_TLS
|
||||
|
@ -1,8 +1,9 @@
|
||||
/*
|
||||
* IXSocketMbedTLS.h
|
||||
* 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
|
||||
|
||||
@ -54,3 +55,5 @@ namespace ix
|
||||
};
|
||||
|
||||
} // namespace ix
|
||||
|
||||
#endif // IXWEBSOCKET_USE_MBED_TLS
|
||||
|
@ -1,10 +1,11 @@
|
||||
/*
|
||||
* IXSocketOpenSSL.cpp
|
||||
* Author: Benjamin Sergeant, Matt DeBoer
|
||||
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
||||
* Author: Benjamin Sergeant, Matt DeBoer, Max Weisel
|
||||
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
|
||||
*
|
||||
* Adapted from Satori SDK OpenSSL code.
|
||||
*/
|
||||
#ifdef IXWEBSOCKET_USE_OPEN_SSL
|
||||
|
||||
#include "IXSocketOpenSSL.h"
|
||||
|
||||
@ -194,6 +195,66 @@ namespace ix
|
||||
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
|
||||
*/
|
||||
@ -402,20 +463,32 @@ namespace ix
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (SSL_CTX_load_verify_locations(
|
||||
_ssl_context, _tlsOptions.caFile.c_str(), NULL) != 1)
|
||||
else
|
||||
{
|
||||
auto sslErr = ERR_get_error();
|
||||
errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" + _tlsOptions.caFile +
|
||||
"\") failed: ";
|
||||
errMsg += ERR_error_string(sslErr, nullptr);
|
||||
return false;
|
||||
}
|
||||
if (_tlsOptions.isUsingInMemoryCAs())
|
||||
{
|
||||
// Load from memory
|
||||
openSSLAddCARootsFromString(_tlsOptions.caFile);
|
||||
}
|
||||
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_VERIFY_PEER,
|
||||
[](int preverify, X509_STORE_CTX*) -> int { return preverify; });
|
||||
SSL_CTX_set_verify_depth(_ssl_context, 4);
|
||||
SSL_CTX_set_verify(
|
||||
_ssl_context, SSL_VERIFY_PEER, [](int preverify, X509_STORE_CTX*) -> int {
|
||||
return preverify;
|
||||
});
|
||||
SSL_CTX_set_verify_depth(_ssl_context, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -525,26 +598,35 @@ namespace ix
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* root_ca_file = _tlsOptions.caFile.c_str();
|
||||
STACK_OF(X509_NAME) * rootCAs;
|
||||
rootCAs = SSL_load_client_CA_file(root_ca_file);
|
||||
if (rootCAs == NULL)
|
||||
if (_tlsOptions.isUsingInMemoryCAs())
|
||||
{
|
||||
auto sslErr = ERR_get_error();
|
||||
errMsg = "OpenSSL failed - SSL_load_client_CA_file('" + _tlsOptions.caFile +
|
||||
"') failed: ";
|
||||
errMsg += ERR_error_string(sslErr, nullptr);
|
||||
// Load from memory
|
||||
openSSLAddCARootsFromString(_tlsOptions.caFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
SSL_CTX_set_client_CA_list(_ssl_context, rootCAs);
|
||||
if (SSL_CTX_load_verify_locations(_ssl_context, root_ca_file, nullptr) != 1)
|
||||
const char* root_ca_file = _tlsOptions.caFile.c_str();
|
||||
STACK_OF(X509_NAME) * rootCAs;
|
||||
rootCAs = SSL_load_client_CA_file(root_ca_file);
|
||||
if (rootCAs == NULL)
|
||||
{
|
||||
auto sslErr = ERR_get_error();
|
||||
errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" +
|
||||
_tlsOptions.caFile + "\") failed: ";
|
||||
errMsg = "OpenSSL failed - SSL_load_client_CA_file('" +
|
||||
_tlsOptions.caFile + "') failed: ";
|
||||
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
|
||||
|
||||
#endif // IXWEBSOCKET_USE_OPEN_SSL
|
||||
|
@ -1,8 +1,9 @@
|
||||
/*
|
||||
* IXSocketOpenSSL.h
|
||||
* 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
|
||||
|
||||
@ -39,6 +40,7 @@ namespace ix
|
||||
void openSSLInitialize();
|
||||
std::string getSSLError(int ret);
|
||||
SSL_CTX* openSSLCreateContext(std::string& errMsg);
|
||||
bool openSSLAddCARootsFromString(const std::string roots);
|
||||
bool openSSLClientHandshake(const std::string& hostname,
|
||||
std::string& errMsg,
|
||||
const CancellationRequest& isCancellationRequested);
|
||||
@ -59,3 +61,5 @@ namespace ix
|
||||
};
|
||||
|
||||
} // namespace ix
|
||||
|
||||
#endif // IXWEBSOCKET_USE_OPEN_SSL
|
||||
|
@ -15,6 +15,7 @@ namespace ix
|
||||
const char* kTLSCAFileUseSystemDefaults = "SYSTEM";
|
||||
const char* kTLSCAFileDisableVerify = "NONE";
|
||||
const char* kTLSCiphersUseDefault = "DEFAULT";
|
||||
const char* kTLSInMemoryMarker = "-----BEGIN CERTIFICATE-----";
|
||||
|
||||
bool SocketTLSOptions::isValid() const
|
||||
{
|
||||
@ -58,6 +59,11 @@ namespace ix
|
||||
return caFile == kTLSCAFileUseSystemDefaults;
|
||||
}
|
||||
|
||||
bool SocketTLSOptions::isUsingInMemoryCAs() const
|
||||
{
|
||||
return caFile.find(kTLSInMemoryMarker) != std::string::npos;
|
||||
}
|
||||
|
||||
bool SocketTLSOptions::isPeerVerifyDisabled() const
|
||||
{
|
||||
return caFile == kTLSCAFileDisableVerify;
|
||||
|
@ -37,6 +37,8 @@ namespace ix
|
||||
|
||||
bool isUsingSystemDefaults() const;
|
||||
|
||||
bool isUsingInMemoryCAs() const;
|
||||
|
||||
bool isPeerVerifyDisabled() 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
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
@ -6,7 +31,308 @@
|
||||
|
||||
#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
|
||||
{
|
||||
@ -17,7 +343,7 @@ namespace ix
|
||||
std::string& query,
|
||||
int& port)
|
||||
{
|
||||
LUrlParser::clParseURL res = LUrlParser::clParseURL::ParseURL(url);
|
||||
clParseURL res = clParseURL::ParseURL(url);
|
||||
|
||||
if (!res.IsValid())
|
||||
{
|
||||
|
@ -34,7 +34,7 @@ namespace ix
|
||||
_ws.setOnCloseCallback(
|
||||
[this](uint16_t code, const std::string& reason, size_t wireSize, bool remote) {
|
||||
_onMessageCallback(
|
||||
std::make_shared<WebSocketMessage>(WebSocketMessageType::Close,
|
||||
std::make_unique<WebSocketMessage>(WebSocketMessageType::Close,
|
||||
"",
|
||||
wireSize,
|
||||
WebSocketErrorInfo(),
|
||||
@ -193,7 +193,7 @@ namespace ix
|
||||
return status;
|
||||
}
|
||||
|
||||
_onMessageCallback(std::make_shared<WebSocketMessage>(
|
||||
_onMessageCallback(std::make_unique<WebSocketMessage>(
|
||||
WebSocketMessageType::Open,
|
||||
"",
|
||||
0,
|
||||
@ -225,7 +225,7 @@ namespace ix
|
||||
}
|
||||
|
||||
_onMessageCallback(
|
||||
std::make_shared<WebSocketMessage>(WebSocketMessageType::Open,
|
||||
std::make_unique<WebSocketMessage>(WebSocketMessageType::Open,
|
||||
"",
|
||||
0,
|
||||
WebSocketErrorInfo(),
|
||||
@ -310,7 +310,7 @@ namespace ix
|
||||
connectErr.reason = status.errorStr;
|
||||
connectErr.http_status = status.http_status;
|
||||
|
||||
_onMessageCallback(std::make_shared<WebSocketMessage>(WebSocketMessageType::Error,
|
||||
_onMessageCallback(std::make_unique<WebSocketMessage>(WebSocketMessageType::Error,
|
||||
"",
|
||||
0,
|
||||
connectErr,
|
||||
@ -386,7 +386,7 @@ namespace ix
|
||||
|
||||
bool binary = messageKind == WebSocketTransport::MessageKind::MSG_BINARY;
|
||||
|
||||
_onMessageCallback(std::make_shared<WebSocketMessage>(webSocketMessageType,
|
||||
_onMessageCallback(std::make_unique<WebSocketMessage>(webSocketMessageType,
|
||||
msg,
|
||||
wireSize,
|
||||
webSocketErrorInfo,
|
||||
|
@ -6,6 +6,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
struct WebSocketCloseInfo
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
|
@ -10,7 +10,7 @@
|
||||
#include "IXSocketConnect.h"
|
||||
#include "IXUrlParser.h"
|
||||
#include "IXUserAgent.h"
|
||||
#include "libwshandshake.hpp"
|
||||
#include "IXWebSocketHandshakeKeyGen.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
@ -133,7 +133,7 @@ namespace ix
|
||||
|
||||
for (auto& it : extraHeaders)
|
||||
{
|
||||
ss << it.first << ":" << it.second << "\r\n";
|
||||
ss << it.first << ": " << it.second << "\r\n";
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
WebSocketMessageType type;
|
||||
std::string str;
|
||||
const std::string& str;
|
||||
size_t wireSize;
|
||||
WebSocketErrorInfo errorInfo;
|
||||
WebSocketOpenInfo openInfo;
|
||||
@ -34,7 +34,7 @@ namespace ix
|
||||
WebSocketCloseInfo c,
|
||||
bool b = false)
|
||||
: type(t)
|
||||
, str(std::move(s))
|
||||
, str(s)
|
||||
, wireSize(w)
|
||||
, errorInfo(e)
|
||||
, openInfo(o)
|
||||
@ -45,5 +45,5 @@ namespace ix
|
||||
}
|
||||
};
|
||||
|
||||
using WebSocketMessagePtr = std::shared_ptr<WebSocketMessage>;
|
||||
using WebSocketMessagePtr = std::unique_ptr<WebSocketMessage>;
|
||||
} // 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
|
||||
|
||||
#include "IXWebSocketHttpHeaders.h"
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
struct WebSocketOpenInfo
|
||||
|
@ -87,6 +87,9 @@ namespace ix
|
||||
//
|
||||
size_t output;
|
||||
|
||||
// Clear output
|
||||
out.clear();
|
||||
|
||||
if (in.empty())
|
||||
{
|
||||
// See issue #167
|
||||
@ -174,6 +177,9 @@ namespace ix
|
||||
_inflateState.avail_in = (uInt) inFixed.size();
|
||||
_inflateState.next_in = (unsigned char*) (const_cast<char*>(inFixed.data()));
|
||||
|
||||
// Clear output
|
||||
out.clear();
|
||||
|
||||
do
|
||||
{
|
||||
_inflateState.avail_out = (uInt) _compressBufferSize;
|
||||
|
@ -62,7 +62,7 @@ namespace ix
|
||||
WebSocketTransport::WebSocketTransport()
|
||||
: _useMask(true)
|
||||
, _blockingSend(false)
|
||||
, _compressedMessage(false)
|
||||
, _receivedMessageCompressed(false)
|
||||
, _readyState(ReadyState::CLOSED)
|
||||
, _closeCode(WebSocketCloseConstants::kInternalErrorCode)
|
||||
, _closeReason(WebSocketCloseConstants::kInternalErrorMessage)
|
||||
@ -74,6 +74,7 @@ namespace ix
|
||||
, _enablePong(kDefaultEnablePong)
|
||||
, _pingIntervalSecs(kDefaultPingIntervalSecs)
|
||||
, _pongReceived(false)
|
||||
, _pingCount(0)
|
||||
, _lastSendPingTimePoint(std::chrono::steady_clock::now())
|
||||
{
|
||||
_readbuf.resize(kChunkSize);
|
||||
@ -221,7 +222,8 @@ namespace ix
|
||||
{
|
||||
_pongReceived = false;
|
||||
std::stringstream ss;
|
||||
ss << kPingMessage << "::" << _pingIntervalSecs << "s";
|
||||
ss << kPingMessage << "::" << _pingIntervalSecs << "s"
|
||||
<< "::" << _pingCount++;
|
||||
return sendPing(ss.str());
|
||||
}
|
||||
|
||||
@ -493,7 +495,7 @@ namespace ix
|
||||
? MessageKind::MSG_TEXT
|
||||
: MessageKind::MSG_BINARY;
|
||||
|
||||
_compressedMessage = _enablePerMessageDeflate && ws.rsv1;
|
||||
_receivedMessageCompressed = _enablePerMessageDeflate && ws.rsv1;
|
||||
|
||||
// Continuation message needs to follow a non-fin TEXT or BINARY message
|
||||
if (!_chunks.empty())
|
||||
@ -515,10 +517,12 @@ namespace ix
|
||||
//
|
||||
if (ws.fin && _chunks.empty())
|
||||
{
|
||||
emitMessage(
|
||||
_fragmentedMessageKind, frameData, _compressedMessage, onMessageCallback);
|
||||
emitMessage(_fragmentedMessageKind,
|
||||
frameData,
|
||||
_receivedMessageCompressed,
|
||||
onMessageCallback);
|
||||
|
||||
_compressedMessage = false;
|
||||
_receivedMessageCompressed = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -535,11 +539,11 @@ namespace ix
|
||||
{
|
||||
emitMessage(_fragmentedMessageKind,
|
||||
getMergedChunks(),
|
||||
_compressedMessage,
|
||||
_receivedMessageCompressed,
|
||||
onMessageCallback);
|
||||
|
||||
_chunks.clear();
|
||||
_compressedMessage = false;
|
||||
_receivedMessageCompressed = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -712,17 +716,16 @@ namespace ix
|
||||
// When the RSV1 bit is 1 it means the message is compressed
|
||||
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,
|
||||
WebSocketCloseConstants::kInvalidFramePayloadDataMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
onMessageCallback(decompressedMessage, wireSize, !success, messageKind);
|
||||
onMessageCallback(_decompressedMessage, wireSize, !success, messageKind);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -759,7 +762,6 @@ namespace ix
|
||||
|
||||
size_t payloadSize = message.size();
|
||||
size_t wireSize = message.size();
|
||||
std::string compressedMessage;
|
||||
bool compressionError = false;
|
||||
|
||||
std::string::const_iterator message_begin = message.begin();
|
||||
@ -767,7 +769,7 @@ namespace ix
|
||||
|
||||
if (compress)
|
||||
{
|
||||
if (!_perMessageDeflate->compress(message, compressedMessage))
|
||||
if (!_perMessageDeflate->compress(message, _compressedMessage))
|
||||
{
|
||||
bool success = false;
|
||||
compressionError = true;
|
||||
@ -776,10 +778,10 @@ namespace ix
|
||||
return WebSocketSendInfo(success, compressionError, payloadSize, wireSize);
|
||||
}
|
||||
compressionError = false;
|
||||
wireSize = compressedMessage.size();
|
||||
wireSize = _compressedMessage.size();
|
||||
|
||||
message_begin = compressedMessage.begin();
|
||||
message_end = compressedMessage.end();
|
||||
message_begin = _compressedMessage.begin();
|
||||
message_end = _compressedMessage.end();
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -165,7 +165,7 @@ namespace ix
|
||||
MessageKind _fragmentedMessageKind;
|
||||
|
||||
// Ditto for whether a message is compressed
|
||||
bool _compressedMessage;
|
||||
bool _receivedMessageCompressed;
|
||||
|
||||
// Fragments are 32K long
|
||||
static constexpr size_t kChunkSize = 1 << 15;
|
||||
@ -189,6 +189,9 @@ namespace ix
|
||||
WebSocketPerMessageDeflateOptions _perMessageDeflateOptions;
|
||||
std::atomic<bool> _enablePerMessageDeflate;
|
||||
|
||||
std::string _decompressedMessage;
|
||||
std::string _compressedMessage;
|
||||
|
||||
// Used to control TLS connection behavior
|
||||
SocketTLSOptions _socketTLSOptions;
|
||||
|
||||
@ -209,6 +212,7 @@ namespace ix
|
||||
|
||||
static const int kDefaultPingIntervalSecs;
|
||||
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
|
||||
mutable std::mutex _lastSendPingTimePointMutex;
|
||||
|
@ -6,4 +6,4 @@
|
||||
|
||||
#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
|
||||
#
|
||||
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
|
||||
# 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 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)
|
||||
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
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)
|
||||
@ -157,6 +159,9 @@ test_no_ssl:
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TEST=1 .. ; make -j 4)
|
||||
(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
|
||||
(cd ws ; env DEBUG=1 PATH=../ws/build:$$PATH bash test_ws.sh)
|
||||
|
||||
@ -183,3 +188,4 @@ doc:
|
||||
.PHONY: test
|
||||
.PHONY: build
|
||||
.PHONY: ws
|
||||
.PHONY: luarocks
|
||||
|
@ -66,13 +66,7 @@ if (UNIX)
|
||||
IXCobraMetricsPublisherTest.cpp
|
||||
IXCobraToSentryBotTest.cpp
|
||||
IXCobraToStatsdBotTest.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
# Some unittest fail for dubious reason on Ubuntu Xenial with TSAN
|
||||
if (MAC OR WIN32)
|
||||
list(APPEND SOURCES
|
||||
IXWebSocketMessageQTest.cpp
|
||||
IXCobraToStdoutBotTest.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
|
@ -180,44 +180,40 @@ namespace
|
||||
_conn.configure(_cobraConfig);
|
||||
_conn.connect();
|
||||
|
||||
_conn.setEventCallback([this, channel](ix::CobraConnectionEventType eventType,
|
||||
const std::string& errMsg,
|
||||
const ix::WebSocketHttpHeaders& headers,
|
||||
const std::string& subscriptionId,
|
||||
CobraConnection::MsgId msgId) {
|
||||
if (eventType == ix::CobraConnection_EventType_Open)
|
||||
_conn.setEventCallback([this, channel](const CobraEventPtr& event) {
|
||||
if (event->type == ix::CobraEventType::Open)
|
||||
{
|
||||
log("Subscriber connected: " + _user);
|
||||
for (auto&& it : headers)
|
||||
for (auto&& it : event->headers)
|
||||
{
|
||||
log("Headers " + it.first + " " + it.second);
|
||||
}
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Authenticated)
|
||||
else if (event->type == ix::CobraEventType::Authenticated)
|
||||
{
|
||||
log("Subscriber authenticated: " + _user);
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
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);
|
||||
_conn.disconnect();
|
||||
|
||||
_conn.setEventCallback([](ix::CobraConnectionEventType /*eventType*/,
|
||||
const std::string& /*errMsg*/,
|
||||
const ix::WebSocketHttpHeaders& /*headers*/,
|
||||
const std::string& /*subscriptionId*/,
|
||||
CobraConnection::MsgId /*msgId*/) { ; });
|
||||
_conn.setEventCallback([](const CobraEventPtr& /*event*/) {});
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -54,24 +54,24 @@ namespace
|
||||
conn.configure(config);
|
||||
conn.connect();
|
||||
|
||||
conn.setEventCallback([&conn, &channel](ix::CobraConnectionEventType eventType,
|
||||
const std::string& errMsg,
|
||||
const ix::WebSocketHttpHeaders& headers,
|
||||
const std::string& subscriptionId,
|
||||
CobraConnection::MsgId msgId) {
|
||||
if (eventType == ix::CobraConnection_EventType_Open)
|
||||
conn.setEventCallback([&conn, &channel](const CobraEventPtr& event) {
|
||||
if (event->type == ix::CobraEventType::Open)
|
||||
{
|
||||
TLogger() << "Subscriber connected:";
|
||||
for (auto&& it : headers)
|
||||
for (auto&& it : event->headers)
|
||||
{
|
||||
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");
|
||||
std::string filter;
|
||||
@ -92,29 +92,29 @@ namespace
|
||||
gMessageCount++;
|
||||
});
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Subscribed)
|
||||
else if (event->type == ix::CobraEventType::Subscribed)
|
||||
{
|
||||
TLogger() << "Subscriber: subscribed to channel " << subscriptionId;
|
||||
if (subscriptionId == channel)
|
||||
TLogger() << "Subscriber: subscribed to channel " << event->subscriptionId;
|
||||
if (event->subscriptionId == channel)
|
||||
{
|
||||
gSubscriberConnectedAndSubscribed = true;
|
||||
}
|
||||
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;
|
||||
if (subscriptionId != channel)
|
||||
TLogger() << "Subscriber: ununexpected from channel " << event->subscriptionId;
|
||||
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 position("$");
|
||||
bool verbose = true;
|
||||
bool strict = true;
|
||||
size_t maxQueueSize = 10;
|
||||
bool enableHeartbeat = false;
|
||||
|
||||
@ -161,16 +160,15 @@ TEST_CASE("Cobra_to_sentry_bot", "[cobra_bots]")
|
||||
// Only run the bot for 3 seconds
|
||||
int runtime = 3;
|
||||
|
||||
int sentCount = cobra_to_sentry_bot(config,
|
||||
channel,
|
||||
filter,
|
||||
position,
|
||||
sentryClient,
|
||||
verbose,
|
||||
strict,
|
||||
maxQueueSize,
|
||||
enableHeartbeat,
|
||||
runtime);
|
||||
int64_t sentCount = cobra_to_sentry_bot(config,
|
||||
channel,
|
||||
filter,
|
||||
position,
|
||||
sentryClient,
|
||||
verbose,
|
||||
maxQueueSize,
|
||||
enableHeartbeat,
|
||||
runtime);
|
||||
//
|
||||
// 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 timer;
|
||||
|
||||
int sentCount = ix::cobra_to_statsd_bot(config,
|
||||
channel,
|
||||
filter,
|
||||
position,
|
||||
statsdClient,
|
||||
fields,
|
||||
gauge,
|
||||
timer,
|
||||
verbose,
|
||||
maxQueueSize,
|
||||
enableHeartbeat,
|
||||
runtime);
|
||||
int64_t sentCount = ix::cobra_to_statsd_bot(config,
|
||||
channel,
|
||||
filter,
|
||||
position,
|
||||
statsdClient,
|
||||
fields,
|
||||
gauge,
|
||||
timer,
|
||||
verbose,
|
||||
maxQueueSize,
|
||||
enableHeartbeat,
|
||||
runtime);
|
||||
//
|
||||
// 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/IXSetThreadName.h>
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
#include <ixwebsocket/IXSocketAppleSSL.h>
|
||||
#include <ixwebsocket/IXSocketConnect.h>
|
||||
#include <ixwebsocket/IXSocketFactory.h>
|
||||
#include <ixwebsocket/IXSocketMbedTLS.h>
|
||||
#include <ixwebsocket/IXSocketOpenSSL.h>
|
||||
#include <ixwebsocket/IXSocketServer.h>
|
||||
#include <ixwebsocket/IXUrlParser.h>
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
@ -26,9 +29,9 @@
|
||||
#include <ixwebsocket/IXWebSocketCloseInfo.h>
|
||||
#include <ixwebsocket/IXWebSocketErrorInfo.h>
|
||||
#include <ixwebsocket/IXWebSocketHandshake.h>
|
||||
#include <ixwebsocket/IXWebSocketHandshakeKeyGen.h>
|
||||
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||
#include <ixwebsocket/IXWebSocketMessage.h>
|
||||
#include <ixwebsocket/IXWebSocketMessageQueue.h>
|
||||
#include <ixwebsocket/IXWebSocketMessageType.h>
|
||||
#include <ixwebsocket/IXWebSocketOpenInfo.h>
|
||||
#include <ixwebsocket/IXWebSocketPerMessageDeflate.h>
|
||||
@ -37,8 +40,6 @@
|
||||
#include <ixwebsocket/IXWebSocketSendInfo.h>
|
||||
#include <ixwebsocket/IXWebSocketServer.h>
|
||||
#include <ixwebsocket/IXWebSocketTransport.h>
|
||||
#include <ixwebsocket/LUrlParser.h>
|
||||
#include <ixwebsocket/libwshandshake.hpp>
|
||||
|
||||
using namespace ix;
|
||||
|
||||
|
@ -6,8 +6,8 @@
|
||||
|
||||
#include "IXTest.h"
|
||||
#include "catch.hpp"
|
||||
#include <iostream>
|
||||
#include "msgpack11.hpp"
|
||||
#include <iostream>
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
#include <ixwebsocket/IXSocketFactory.h>
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
@ -130,7 +130,8 @@ namespace
|
||||
}
|
||||
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());
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Ping)
|
||||
@ -234,7 +235,7 @@ namespace
|
||||
server.start();
|
||||
return true;
|
||||
}
|
||||
} // namespace ix
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Websocket_broadcast_server", "[websocket_server]")
|
||||
{
|
||||
@ -247,7 +248,7 @@ TEST_CASE("Websocket_broadcast_server", "[websocket_server]")
|
||||
|
||||
std::string session = ix::generateSessionId();
|
||||
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));
|
||||
chatClients.push_back(std::make_shared<WebSocketChat>(user, session, port));
|
||||
@ -259,7 +260,7 @@ TEST_CASE("Websocket_broadcast_server", "[websocket_server]")
|
||||
while (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();
|
||||
}
|
||||
@ -269,7 +270,7 @@ TEST_CASE("Websocket_broadcast_server", "[websocket_server]")
|
||||
|
||||
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");
|
||||
}
|
||||
@ -291,7 +292,7 @@ TEST_CASE("Websocket_broadcast_server", "[websocket_server]")
|
||||
|
||||
// Stop all clients
|
||||
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);
|
||||
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::IXCoreLogger::LogFunc logFunc = [](const char* msg) { spdlog::info(msg); };
|
||||
ix::IXCoreLogger::setLogFunction(logFunc);
|
||||
ix::CoreLogger::LogFunc logFunc = [](const char* msg, ix::LogLevel level) {
|
||||
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);
|
||||
|
||||
|
6
third_party/.clang-format
vendored
6
third_party/.clang-format
vendored
@ -1,4 +1,2 @@
|
||||
{
|
||||
"DisableFormat": true,
|
||||
"SortIncludes": false
|
||||
}
|
||||
DisableFormat: true
|
||||
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.
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user