Compare commits
	
		
			23 Commits
		
	
	
		
			v11.0.4
			...
			feature/ci
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 84163ad19e | ||
|  | 5e5b7c34ca | ||
|  | 9bb1de64a4 | ||
|  | df9889947d | ||
|  | 6ede8c85b0 | ||
|  | 2663f605f4 | ||
|  | 1ea3bc3666 | ||
|  | fe92ad205d | ||
|  | e4a1ac80c2 | ||
|  | e9dc7f7aed | ||
|  | cd82eed4ec | ||
|  | fabc07d598 | ||
|  | b89621fa78 | ||
|  | 049d1eec63 | ||
|  | 6122154f74 | ||
|  | 0b7919834a | ||
|  | 6035dd4c11 | ||
|  | 1d0432c8c5 | ||
|  | 461a645704 | ||
|  | 93ad709dfd | ||
|  | 2fac4bd9ef | ||
|  | f566fb457b | ||
|  | 75e9c84388 | 
| @@ -2,3 +2,4 @@ build | |||||||
| CMakeCache.txt | CMakeCache.txt | ||||||
| ws/CMakeCache.txt | ws/CMakeCache.txt | ||||||
| test/build | test/build | ||||||
|  | makefile | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.github/workflows/unittest_linux.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/unittest_linux.yml
									
									
									
									
										vendored
									
									
								
							| @@ -2,7 +2,7 @@ name: linux | |||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
|     paths-ignore: |     paths-ignore: | ||||||
|     - 'docs/**' |     - './**' | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   linux: |   linux: | ||||||
| @@ -11,4 +11,4 @@ jobs: | |||||||
|     - uses: actions/checkout@v1 |     - uses: actions/checkout@v1 | ||||||
|     - uses: seanmiddleditch/gha-setup-ninja@master |     - uses: seanmiddleditch/gha-setup-ninja@master | ||||||
|     - name: make test |     - name: make test | ||||||
|       run: make test |       run: make -f makefile.dev test | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.github/workflows/unittest_linux_asan.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/unittest_linux_asan.yml
									
									
									
									
										vendored
									
									
								
							| @@ -2,7 +2,7 @@ name: linux_asan | |||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
|     paths-ignore: |     paths-ignore: | ||||||
|     - 'docs/**' |     - './**' | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   linux: |   linux: | ||||||
| @@ -11,4 +11,4 @@ jobs: | |||||||
|     - uses: actions/checkout@v1 |     - uses: actions/checkout@v1 | ||||||
|     - uses: seanmiddleditch/gha-setup-ninja@master |     - uses: seanmiddleditch/gha-setup-ninja@master | ||||||
|     - name: make test_asan |     - name: make test_asan | ||||||
|       run: make test_asan |       run: make -f makefile.dev test_asan | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ name: mac_tsan_mbedtls | |||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
|     paths-ignore: |     paths-ignore: | ||||||
|     - 'docs/**' |     - './**' | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   mac_tsan_mbedtls: |   mac_tsan_mbedtls: | ||||||
| @@ -13,4 +13,4 @@ jobs: | |||||||
|     - name: install mbedtls |     - name: install mbedtls | ||||||
|       run: brew install mbedtls |       run: brew install mbedtls | ||||||
|     - name: make test |     - name: make test | ||||||
|       run: make test_tsan_mbedtls |       run: make -f makefile.dev test_tsan_mbedtls | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ name: mac_tsan_openssl | |||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
|     paths-ignore: |     paths-ignore: | ||||||
|     - 'docs/**' |     - './**' | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   mac_tsan_openssl: |   mac_tsan_openssl: | ||||||
| @@ -13,4 +13,4 @@ jobs: | |||||||
|     - name: install openssl |     - name: install openssl | ||||||
|       run: brew install openssl@1.1 |       run: brew install openssl@1.1 | ||||||
|     - name: make test |     - name: make test | ||||||
|       run: make test_tsan_openssl |       run: make -f makefile.dev test_tsan_openssl | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ name: mac_tsan_sectransport | |||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
|     paths-ignore: |     paths-ignore: | ||||||
|     - 'docs/**' |     - './**' | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   mac_tsan_sectransport: |   mac_tsan_sectransport: | ||||||
| @@ -11,4 +11,4 @@ jobs: | |||||||
|     - uses: actions/checkout@v1 |     - uses: actions/checkout@v1 | ||||||
|     - uses: seanmiddleditch/gha-setup-ninja@master |     - uses: seanmiddleditch/gha-setup-ninja@master | ||||||
|     - name: make test_tsan_sectransport |     - name: make test_tsan_sectransport | ||||||
|       run: make test_tsan_sectransport |       run: make -f makefile.dev test_tsan_sectransport | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								.github/workflows/unittest_uwp.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/workflows/unittest_uwp.yml
									
									
									
									
										vendored
									
									
								
							| @@ -2,7 +2,7 @@ name: uwp | |||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
|     paths-ignore: |     paths-ignore: | ||||||
|     - 'docs/**' |     - './**' | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   uwp: |   uwp: | ||||||
| @@ -10,11 +10,17 @@ jobs: | |||||||
|     steps: |     steps: | ||||||
|     - uses: actions/checkout@v1 |     - uses: actions/checkout@v1 | ||||||
|     - uses: seanmiddleditch/gha-setup-vsdevenv@master |     - uses: seanmiddleditch/gha-setup-vsdevenv@master | ||||||
|  |     - uses: seanmiddleditch/gha-setup-ninja@master | ||||||
|     - run: | |     - run: | | ||||||
|         mkdir build |         mkdir build | ||||||
|         cd build |         cd build | ||||||
|         cmake -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION="10.0" -DCMAKE_CXX_COMPILER=cl.exe -DUSE_TEST=1 -DUSE_ZLIB=0 .. |         cmake -GNinja -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION="10.0" -DCMAKE_CXX_COMPILER=cl.exe -DCMAKE_C_COMPILER=cl.exe -DUSE_TEST=1 -DUSE_ZLIB=0 .. | ||||||
|     - run: cmake --build build |     - run: | | ||||||
|  |         cd build | ||||||
|  |         ninja | ||||||
|  |     - run: | | ||||||
|  |         cd build | ||||||
|  |         ninja test | ||||||
|  |  | ||||||
| # | # | ||||||
| #   Windows with OpenSSL is working but disabled as it takes 13 minutes (10 for openssl) to build with vcpkg | #   Windows with OpenSSL is working but disabled as it takes 13 minutes (10 for openssl) to build with vcpkg | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								.github/workflows/unittest_windows.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/unittest_windows.yml
									
									
									
									
										vendored
									
									
								
							| @@ -10,11 +10,17 @@ jobs: | |||||||
|     steps: |     steps: | ||||||
|     - uses: actions/checkout@v1 |     - uses: actions/checkout@v1 | ||||||
|     - uses: seanmiddleditch/gha-setup-vsdevenv@master |     - uses: seanmiddleditch/gha-setup-vsdevenv@master | ||||||
|  |     - uses: seanmiddleditch/gha-setup-ninja@master | ||||||
|     - run: | |     - run: | | ||||||
|         mkdir build |         mkdir build | ||||||
|         cd build |         cd build | ||||||
|         cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_WS=1 -DUSE_TEST=1 -DUSE_ZLIB=0 .. |         cmake -GNinja -DCMAKE_CXX_COMPILER=cl.exe -DCMAKE_C_COMPILER=cl.exe -DUSE_WS=1 -DUSE_TEST=1 -DUSE_ZLIB=0 .. | ||||||
|     - run: cmake --build build |     - run: | | ||||||
|  |         cd build | ||||||
|  |         ninja | ||||||
|  |     - run: | | ||||||
|  |         cd build | ||||||
|  |         ninja test | ||||||
|  |  | ||||||
| #- run: ../build/test/ixwebsocket_unittest.exe | #- run: ../build/test/ixwebsocket_unittest.exe | ||||||
| # working-directory: test | # working-directory: test | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -6,3 +6,4 @@ site/ | |||||||
| ws/.certs/ | ws/.certs/ | ||||||
| ws/.srl | ws/.srl | ||||||
| ixhttpd | ixhttpd | ||||||
|  | makefile | ||||||
|   | |||||||
| @@ -1,19 +0,0 @@ | |||||||
| # Find package structure taken from libcurl |  | ||||||
|  |  | ||||||
| include(FindPackageHandleStandardArgs) |  | ||||||
|  |  | ||||||
| find_path(JSONCPP_INCLUDE_DIRS json/json.h) |  | ||||||
| find_library(JSONCPP_LIBRARY jsoncpp) |  | ||||||
|  |  | ||||||
| find_package_handle_standard_args(JsonCpp |  | ||||||
|     FOUND_VAR |  | ||||||
|       JSONCPP_FOUND |  | ||||||
|     REQUIRED_VARS |  | ||||||
|       JSONCPP_LIBRARY |  | ||||||
|       JSONCPP_INCLUDE_DIRS |  | ||||||
|     FAIL_MESSAGE |  | ||||||
|       "Could NOT find jsoncpp" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| set(JSONCPP_INCLUDE_DIRS ${JSONCPP_INCLUDE_DIRS}) |  | ||||||
| set(JSONCPP_LIBRARIES ${JSONCPP_LIBRARY}) |  | ||||||
| @@ -48,6 +48,7 @@ set( IXWEBSOCKET_SOURCES | |||||||
|     ixwebsocket/IXStrCaseCompare.cpp |     ixwebsocket/IXStrCaseCompare.cpp | ||||||
|     ixwebsocket/IXUdpSocket.cpp |     ixwebsocket/IXUdpSocket.cpp | ||||||
|     ixwebsocket/IXUrlParser.cpp |     ixwebsocket/IXUrlParser.cpp | ||||||
|  |     ixwebsocket/IXUuid.cpp | ||||||
|     ixwebsocket/IXUserAgent.cpp |     ixwebsocket/IXUserAgent.cpp | ||||||
|     ixwebsocket/IXWebSocket.cpp |     ixwebsocket/IXWebSocket.cpp | ||||||
|     ixwebsocket/IXWebSocketCloseConstants.cpp |     ixwebsocket/IXWebSocketCloseConstants.cpp | ||||||
| @@ -86,6 +87,7 @@ set( IXWEBSOCKET_HEADERS | |||||||
|     ixwebsocket/IXStrCaseCompare.h |     ixwebsocket/IXStrCaseCompare.h | ||||||
|     ixwebsocket/IXUdpSocket.h |     ixwebsocket/IXUdpSocket.h | ||||||
|     ixwebsocket/IXUrlParser.h |     ixwebsocket/IXUrlParser.h | ||||||
|  |     ixwebsocket/IXUuid.h | ||||||
|     ixwebsocket/IXUtf8Validator.h |     ixwebsocket/IXUtf8Validator.h | ||||||
|     ixwebsocket/IXUserAgent.h |     ixwebsocket/IXUserAgent.h | ||||||
|     ixwebsocket/IXWebSocket.h |     ixwebsocket/IXWebSocket.h | ||||||
| @@ -255,14 +257,6 @@ install(EXPORT ixwebsocket | |||||||
|         DESTINATION lib/cmake/ixwebsocket) |         DESTINATION lib/cmake/ixwebsocket) | ||||||
|  |  | ||||||
| if (USE_WS OR USE_TEST) | if (USE_WS OR USE_TEST) | ||||||
|   add_subdirectory(ixcore) |  | ||||||
|   add_subdirectory(ixcrypto) |  | ||||||
|   add_subdirectory(ixcobra) |  | ||||||
|   add_subdirectory(ixredis) |  | ||||||
|   add_subdirectory(ixsnake) |  | ||||||
|   add_subdirectory(ixsentry) |  | ||||||
|   add_subdirectory(ixbots) |  | ||||||
|  |  | ||||||
|   include(FetchContent) |   include(FetchContent) | ||||||
|   FetchContent_Declare(spdlog |   FetchContent_Declare(spdlog | ||||||
|       GIT_REPOSITORY "https://github.com/gabime/spdlog" |       GIT_REPOSITORY "https://github.com/gabime/spdlog" | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								README.md
									
									
									
									
									
								
							| @@ -88,6 +88,19 @@ If your company or project is using this library, feel free to open an issue or | |||||||
| - [DisCPP](https://github.com/DisCPP/DisCPP), a simple but feature rich Discord API wrapper | - [DisCPP](https://github.com/DisCPP/DisCPP), a simple but feature rich Discord API wrapper | ||||||
| - [discord.cpp](https://github.com/luccanunes/discord.cpp), a discord library for making bots | - [discord.cpp](https://github.com/luccanunes/discord.cpp), a discord library for making bots | ||||||
|  |  | ||||||
|  | ## Alternative libraries | ||||||
|  |  | ||||||
|  | There are plenty of great websocket libraries out there, which might work for you. Here are a couple of serious ones. | ||||||
|  |  | ||||||
|  | * [websocketpp](https://github.com/zaphoyd/websocketpp) - C++ | ||||||
|  | * [beast](https://github.com/boostorg/beast) - C++ | ||||||
|  | * [libwebsockets](https://libwebsockets.org/) - C | ||||||
|  | * [µWebSockets](https://github.com/uNetworking/uWebSockets) - C | ||||||
|  |  | ||||||
|  | [uvweb](https://github.com/bsergean/uvweb) is a library written by the IXWebSocket author which is built on top of [uvw](https://github.com/skypjack/uvw), which is a C++ wrapper for [libuv](https://libuv.org/). It has more dependencies and does not support SSL at this point, but it can be used to open multiple connections within a single OS thread thanks to libuv. | ||||||
|  |  | ||||||
|  | To check the performance of a websocket library, you can look at the [autoroute](https://github.com/bsergean/autoroute) project. | ||||||
|  |  | ||||||
| ## Continuous Integration | ## Continuous Integration | ||||||
|  |  | ||||||
| | OS                | TLS               | Sanitizer         | Status            | | | OS                | TLS               | Sanitizer         | Status            | | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ COPY --chown=app:app . /opt | |||||||
| WORKDIR /opt | WORKDIR /opt | ||||||
|  |  | ||||||
| USER app | USER app | ||||||
| RUN make ws_mbedtls_install && \ | RUN make -f makefile.dev ws_mbedtls_install && \ | ||||||
|     sh tools/trim_repo_for_docker.sh |     sh tools/trim_repo_for_docker.sh | ||||||
|  |  | ||||||
| FROM alpine:3.12 as runtime | FROM alpine:3.12 as runtime | ||||||
|   | |||||||
| @@ -2,6 +2,24 @@ | |||||||
|  |  | ||||||
| All changes to this project will be documented in this file. | All changes to this project will be documented in this file. | ||||||
|  |  | ||||||
|  | ## [11.0.8] - 2020-12-25 | ||||||
|  |  | ||||||
|  | (ws) trim ws dependencies no more ixcrypto and ixcore deps | ||||||
|  |  | ||||||
|  | ## [11.0.7] - 2020-12-25 | ||||||
|  |  | ||||||
|  | (ws) trim ws dependencies, only depends on ixcrypto and ixcore | ||||||
|  |  | ||||||
|  | ## [11.0.6] - 2020-12-22 | ||||||
|  |  | ||||||
|  | (build) rename makefile to makefile.dev to ease cmake BuildExternal (fix #261) | ||||||
|  |  | ||||||
|  | ## [11.0.5] - 2020-12-17 | ||||||
|  |  | ||||||
|  | (ws) Implement simple header based websocket authorization technique to reject | ||||||
|  | client which do not supply a certain header ("Authorization") with a special | ||||||
|  | value (see doc). | ||||||
|  |  | ||||||
| ## [11.0.4] - 2020-11-16 | ## [11.0.4] - 2020-11-16 | ||||||
|  |  | ||||||
| (ixwebsocket) Handle EINTR return code in ix::poll and IXSelectInterrupt | (ixwebsocket) Handle EINTR return code in ix::poll and IXSelectInterrupt | ||||||
|   | |||||||
| @@ -1,81 +0,0 @@ | |||||||
| ## General |  | ||||||
|  |  | ||||||
| [cobra](https://github.com/machinezone/cobra) is a real time messaging server. The `ws` utility can run a cobra server (named snake), and has client to publish and subscribe to a cobra server. |  | ||||||
|  |  | ||||||
| Bring up 3 terminals and run a server, a publisher and a subscriber in each one. As you publish data you should see it being received by the subscriber. You can run `redis-cli MONITOR` too to see how redis is being used. |  | ||||||
|  |  | ||||||
| ### Server |  | ||||||
|  |  | ||||||
| You will need to have a redis server running locally. To run the server: |  | ||||||
|  |  | ||||||
| ```bash |  | ||||||
| $ cd <ixwebsocket-top-level-folder>/ixsnake/ixsnake |  | ||||||
| $ ws snake |  | ||||||
| { |  | ||||||
|   "apps": { |  | ||||||
|     "FC2F10139A2BAc53BB72D9db967b024f": { |  | ||||||
|       "roles": { |  | ||||||
|         "_sub": { |  | ||||||
|           "secret": "66B1dA3ED5fA074EB5AE84Dd8CE3b5ba" |  | ||||||
|         }, |  | ||||||
|         "_pub": { |  | ||||||
|           "secret": "1c04DB8fFe76A4EeFE3E318C72d771db" |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| redis host: 127.0.0.1 |  | ||||||
| redis password: |  | ||||||
| redis port: 6379 |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### Publisher |  | ||||||
|  |  | ||||||
| ```bash |  | ||||||
| $ cd <ixwebsocket-top-level-folder>/ws |  | ||||||
| $ ws cobra_publish --appkey FC2F10139A2BAc53BB72D9db967b024f --endpoint ws://127.0.0.1:8008 --rolename _pub --rolesecret 1c04DB8fFe76A4EeFE3E318C72d771db test_channel cobraMetricsSample.json |  | ||||||
| [2019-11-27 09:06:12.980] [info] Publisher connected |  | ||||||
| [2019-11-27 09:06:12.980] [info] Connection: Upgrade |  | ||||||
| [2019-11-27 09:06:12.980] [info] Sec-WebSocket-Accept: zTtQKMKbvwjdivURplYXwCVUCWM= |  | ||||||
| [2019-11-27 09:06:12.980] [info] Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15 |  | ||||||
| [2019-11-27 09:06:12.980] [info] Server: ixwebsocket/7.4.0 macos ssl/DarwinSSL zlib 1.2.11 |  | ||||||
| [2019-11-27 09:06:12.980] [info] Upgrade: websocket |  | ||||||
| [2019-11-27 09:06:12.982] [info] Publisher authenticated |  | ||||||
| [2019-11-27 09:06:12.982] [info] Published msg 3 |  | ||||||
| [2019-11-27 09:06:12.982] [info] Published message id 3 acked |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### Subscriber |  | ||||||
|  |  | ||||||
| ```bash |  | ||||||
| $ ws cobra_subscribe --appkey FC2F10139A2BAc53BB72D9db967b024f --endpoint ws://127.0.0.1:8008 --rolename _pub --rolesecret 1c04DB8fFe76A4EeFE3E318C72d771db test_channel |  | ||||||
| #messages 0 msg/s 0 |  | ||||||
| [2019-11-27 09:07:39.341] [info] Subscriber connected |  | ||||||
| [2019-11-27 09:07:39.341] [info] Connection: Upgrade |  | ||||||
| [2019-11-27 09:07:39.341] [info] Sec-WebSocket-Accept: 9vkQWofz49qMCUlTSptCCwHWm+Q= |  | ||||||
| [2019-11-27 09:07:39.341] [info] Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15 |  | ||||||
| [2019-11-27 09:07:39.341] [info] Server: ixwebsocket/7.4.0 macos ssl/DarwinSSL zlib 1.2.11 |  | ||||||
| [2019-11-27 09:07:39.341] [info] Upgrade: websocket |  | ||||||
| [2019-11-27 09:07:39.342] [info] Subscriber authenticated |  | ||||||
| [2019-11-27 09:07:39.345] [info] Subscriber: subscribed to channel test_channel |  | ||||||
| #messages 0 msg/s 0 |  | ||||||
| #messages 0 msg/s 0 |  | ||||||
| #messages 0 msg/s 0 |  | ||||||
| {"baz":123,"foo":"bar"} |  | ||||||
|  |  | ||||||
| #messages 1 msg/s 1 |  | ||||||
| #messages 1 msg/s 0 |  | ||||||
| #messages 1 msg/s 0 |  | ||||||
| {"baz":123,"foo":"bar"} |  | ||||||
|  |  | ||||||
| {"baz":123,"foo":"bar"} |  | ||||||
|  |  | ||||||
| #messages 3 msg/s 2 |  | ||||||
| #messages 3 msg/s 0 |  | ||||||
| {"baz":123,"foo":"bar"} |  | ||||||
|  |  | ||||||
| #messages 4 msg/s 1 |  | ||||||
| ^C |  | ||||||
| ``` |  | ||||||
							
								
								
									
										199
									
								
								docs/ws.md
									
									
									
									
									
								
							
							
						
						
									
										199
									
								
								docs/ws.md
									
									
									
									
									
								
							| @@ -19,13 +19,6 @@ Subcommands: | |||||||
|   broadcast_server            Broadcasting server |   broadcast_server            Broadcasting server | ||||||
|   ping                        Ping pong |   ping                        Ping pong | ||||||
|   curl                        HTTP Client |   curl                        HTTP Client | ||||||
|   redis_publish               Redis publisher |  | ||||||
|   redis_subscribe             Redis subscriber |  | ||||||
|   cobra_subscribe             Cobra subscriber |  | ||||||
|   cobra_publish               Cobra publisher |  | ||||||
|   cobra_to_statsd             Cobra to statsd |  | ||||||
|   cobra_to_sentry             Cobra to sentry |  | ||||||
|   snake                       Snake server |  | ||||||
|   httpd                       HTTP server |   httpd                       HTTP server | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| @@ -195,6 +188,63 @@ Server: Python/3.7 websockets/8.0.2 | |||||||
| Upgrade: websocket | Upgrade: websocket | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | It is possible to pass custom HTTP header when doing the connection handshake, | ||||||
|  | the remote server might process them to implement a simple authorization | ||||||
|  | scheme. | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | src$ ws connect -H Authorization:supersecret ws://localhost:8008 | ||||||
|  | Type Ctrl-D to exit prompt... | ||||||
|  | [2020-12-17 22:35:08.732] [info] Authorization: supersecret | ||||||
|  | Connecting to url: ws://localhost:8008 | ||||||
|  | > [2020-12-17 22:35:08.736] [info] ws_connect: connected | ||||||
|  | [2020-12-17 22:35:08.736] [info] Uri: / | ||||||
|  | [2020-12-17 22:35:08.736] [info] Headers: | ||||||
|  | [2020-12-17 22:35:08.736] [info] Connection: Upgrade | ||||||
|  | [2020-12-17 22:35:08.736] [info] Sec-WebSocket-Accept: 2yaTFcdwn8KL6IzSMj2u6Le7KTg= | ||||||
|  | [2020-12-17 22:35:08.736] [info] Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15 | ||||||
|  | [2020-12-17 22:35:08.736] [info] Server: ixwebsocket/11.0.4 macos ssl/SecureTransport zlib 1.2.11 | ||||||
|  | [2020-12-17 22:35:08.736] [info] Upgrade: websocket | ||||||
|  | [2020-12-17 22:35:08.736] [info] Received 25 bytes | ||||||
|  | ws_connect: received message: Authorization suceeded! | ||||||
|  | [2020-12-17 22:35:08.736] [info] Received pong ixwebsocket::heartbeat::30s::0 | ||||||
|  | hello | ||||||
|  | > [2020-12-17 22:35:25.157] [info] Received 7 bytes | ||||||
|  | ws_connect: received message: hello | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | If the wrong header is passed in, the server would close the connection with a custom close code (>4000, and <4999). | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | [2020-12-17 22:39:37.044] [info] Upgrade: websocket | ||||||
|  | ws_connect: connection closed: code 4001 reason Permission denied | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## echo server | ||||||
|  |  | ||||||
|  | The ws echo server will respond what the client just sent him. If we use the | ||||||
|  | simple --http_authorization_header we can enforce that client need to pass a | ||||||
|  | special value in the Authorization header to connect. | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | $ ws echo_server --http_authorization_header supersecret | ||||||
|  | [2020-12-17 22:35:06.192] [info] Listening on 127.0.0.1:8008 | ||||||
|  | [2020-12-17 22:35:08.735] [info] New connection | ||||||
|  | [2020-12-17 22:35:08.735] [info] remote ip: 127.0.0.1 | ||||||
|  | [2020-12-17 22:35:08.735] [info] id: 0 | ||||||
|  | [2020-12-17 22:35:08.735] [info] Uri: / | ||||||
|  | [2020-12-17 22:35:08.735] [info] Headers: | ||||||
|  | [2020-12-17 22:35:08.735] [info] Authorization: supersecret | ||||||
|  | [2020-12-17 22:35:08.735] [info] Connection: Upgrade | ||||||
|  | [2020-12-17 22:35:08.735] [info] Host: localhost:8008 | ||||||
|  | [2020-12-17 22:35:08.735] [info] Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15 | ||||||
|  | [2020-12-17 22:35:08.735] [info] Sec-WebSocket-Key: eFF2Gf25dC7eC15Ab1135G== | ||||||
|  | [2020-12-17 22:35:08.735] [info] Sec-WebSocket-Version: 13 | ||||||
|  | [2020-12-17 22:35:08.735] [info] Upgrade: websocket | ||||||
|  | [2020-12-17 22:35:08.735] [info] User-Agent: ixwebsocket/11.0.4 macos ssl/SecureTransport zlib 1.2.11 | ||||||
|  | [2020-12-17 22:35:25.157] [info] Received 7 bytes | ||||||
|  | ``` | ||||||
|  |  | ||||||
| ## Websocket proxy | ## Websocket proxy | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
| @@ -208,13 +258,9 @@ You can also use a more complex setup if you want to redirect to different webso | |||||||
|  |  | ||||||
| A JSON config file is used to express that mapping ; here connecting to echo.jeanserge.com will proxy the client to ws://localhost:8008 on the local machine (which actually runs ws echo_server), while connecting to bavarde.jeanserge.com will proxy the client to ws://localhost:5678 where a cobra python server is running. As a side note you will need a wildcard SSL certificate if you want to have SSL enabled on that machine. | A JSON config file is used to express that mapping ; here connecting to echo.jeanserge.com will proxy the client to ws://localhost:8008 on the local machine (which actually runs ws echo_server), while connecting to bavarde.jeanserge.com will proxy the client to ws://localhost:5678 where a cobra python server is running. As a side note you will need a wildcard SSL certificate if you want to have SSL enabled on that machine. | ||||||
|  |  | ||||||
| ```json | ``` | ||||||
| { | echo.jeanserge.com=ws://localhost:8008 | ||||||
|         "remote_urls": { | bavarde.jeanserge.com=ws://localhost:5678 | ||||||
|                 "echo.jeanserge.com": "ws://localhost:8008", |  | ||||||
|                 "bavarde.jeanserge.com": "ws://localhost:5678" |  | ||||||
|         } |  | ||||||
| } |  | ||||||
| ``` | ``` | ||||||
| The --config_path option is required to instruct ws proxy_server to read that file. | The --config_path option is required to instruct ws proxy_server to read that file. | ||||||
|  |  | ||||||
| @@ -260,128 +306,3 @@ Options: | |||||||
|   --connect-timeout INT       Connection timeout |   --connect-timeout INT       Connection timeout | ||||||
|   --transfer-timeout INT      Transfer timeout |   --transfer-timeout INT      Transfer timeout | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ## Cobra client and server |  | ||||||
|  |  | ||||||
| [cobra](https://github.com/machinezone/cobra) is a real time messenging server. ws has several sub-command to interact with cobra. There is also a minimal cobra compatible server named snake available. |  | ||||||
|  |  | ||||||
| Below are examples on running a snake server and clients with TLS enabled (the server only works with the OpenSSL and the Mbed TLS backend for now). |  | ||||||
|  |  | ||||||
| First, generate certificates. |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| $ cd /path/to/IXWebSocket |  | ||||||
| $ cd ixsnake/ixsnake |  | ||||||
| $ bash ../../ws/generate_certs.sh |  | ||||||
| Generating RSA private key, 2048 bit long modulus |  | ||||||
| .....+++ |  | ||||||
| .................+++ |  | ||||||
| e is 65537 (0x10001) |  | ||||||
| generated ./.certs/trusted-ca-key.pem |  | ||||||
| generated ./.certs/trusted-ca-crt.pem |  | ||||||
| Generating RSA private key, 2048 bit long modulus |  | ||||||
| ..+++ |  | ||||||
| .......................................+++ |  | ||||||
| e is 65537 (0x10001) |  | ||||||
| generated ./.certs/trusted-server-key.pem |  | ||||||
| Signature ok |  | ||||||
| subject=/O=machinezone/O=IXWebSocket/CN=trusted-server |  | ||||||
| Getting CA Private Key |  | ||||||
| generated ./.certs/trusted-server-crt.pem |  | ||||||
| Generating RSA private key, 2048 bit long modulus |  | ||||||
| ...................................+++ |  | ||||||
| ..................................................+++ |  | ||||||
| e is 65537 (0x10001) |  | ||||||
| generated ./.certs/trusted-client-key.pem |  | ||||||
| Signature ok |  | ||||||
| subject=/O=machinezone/O=IXWebSocket/CN=trusted-client |  | ||||||
| Getting CA Private Key |  | ||||||
| generated ./.certs/trusted-client-crt.pem |  | ||||||
| Generating RSA private key, 2048 bit long modulus |  | ||||||
| ..............+++ |  | ||||||
| .......................................+++ |  | ||||||
| e is 65537 (0x10001) |  | ||||||
| generated ./.certs/untrusted-ca-key.pem |  | ||||||
| generated ./.certs/untrusted-ca-crt.pem |  | ||||||
| Generating RSA private key, 2048 bit long modulus |  | ||||||
| ..........+++ |  | ||||||
| ................................................+++ |  | ||||||
| e is 65537 (0x10001) |  | ||||||
| generated ./.certs/untrusted-client-key.pem |  | ||||||
| Signature ok |  | ||||||
| subject=/O=machinezone/O=IXWebSocket/CN=untrusted-client |  | ||||||
| Getting CA Private Key |  | ||||||
| generated ./.certs/untrusted-client-crt.pem |  | ||||||
| Generating RSA private key, 2048 bit long modulus |  | ||||||
| .....................................................................................+++ |  | ||||||
| ...........+++ |  | ||||||
| e is 65537 (0x10001) |  | ||||||
| generated ./.certs/selfsigned-client-key.pem |  | ||||||
| Signature ok |  | ||||||
| subject=/O=machinezone/O=IXWebSocket/CN=selfsigned-client |  | ||||||
| Getting Private key |  | ||||||
| generated ./.certs/selfsigned-client-crt.pem |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| Now run the snake server. |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| $ export certs=.certs |  | ||||||
| $ ws snake --tls --port 8765 --cert-file ${certs}/trusted-server-crt.pem --key-file ${certs}/trusted-server-key.pem --ca-file ${certs}/trusted-ca-crt.pem |  | ||||||
| { |  | ||||||
|   "apps": { |  | ||||||
|     "FC2F10139A2BAc53BB72D9db967b024f": { |  | ||||||
|       "roles": { |  | ||||||
|         "_sub": { |  | ||||||
|           "secret": "66B1dA3ED5fA074EB5AE84Dd8CE3b5ba" |  | ||||||
|         }, |  | ||||||
|         "_pub": { |  | ||||||
|           "secret": "1c04DB8fFe76A4EeFE3E318C72d771db" |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| redis host: 127.0.0.1 |  | ||||||
| redis password: |  | ||||||
| redis port: 6379 |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| As a new connection comes in, such output should be printed |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| [2019-12-19 20:27:19.724] [info] New connection |  | ||||||
| id: 0 |  | ||||||
| Uri: /v2?appkey=_health |  | ||||||
| Headers: |  | ||||||
| Connection: Upgrade |  | ||||||
| Host: 127.0.0.1:8765 |  | ||||||
| Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15 |  | ||||||
| Sec-WebSocket-Key: d747B0fE61Db73f7Eh47c0== |  | ||||||
| Sec-WebSocket-Protocol: json |  | ||||||
| Sec-WebSocket-Version: 13 |  | ||||||
| Upgrade: websocket |  | ||||||
| User-Agent: ixwebsocket/7.5.8 macos ssl/OpenSSL OpenSSL 1.0.2q  20 Nov 2018 zlib 1.2.11 |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| To connect and publish a message, do: |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| $ export certs=.certs |  | ||||||
| $ cd /path/to/ws/folder |  | ||||||
| $ ls cobraMetricsSample.json |  | ||||||
| cobraMetricsSample.json |  | ||||||
| $ ws cobra_publish --endpoint wss://127.0.0.1:8765 --appkey FC2F10139A2BAc53BB72D9db967b024f --rolename _pub --rolesecret 1c04DB8fFe76A4EeFE3E318C72d771db --channel foo --cert-file ${certs}/trusted-client-crt.pem --key-file ${certs}/trusted-client-key.pem --ca-file ${certs}/trusted-ca-crt.pem cobraMetricsSample.json |  | ||||||
| [2019-12-19 20:46:42.656] [info] Publisher connected |  | ||||||
| [2019-12-19 20:46:42.657] [info] Connection: Upgrade |  | ||||||
| [2019-12-19 20:46:42.657] [info] Sec-WebSocket-Accept: rs99IFThoBrhSg+k8G4ixH9yaq4= |  | ||||||
| [2019-12-19 20:46:42.657] [info] Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15 |  | ||||||
| [2019-12-19 20:46:42.657] [info] Server: ixwebsocket/7.5.8 macos ssl/OpenSSL OpenSSL 1.0.2q  20 Nov 2018 zlib 1.2.11 |  | ||||||
| [2019-12-19 20:46:42.657] [info] Upgrade: websocket |  | ||||||
| [2019-12-19 20:46:42.658] [info] Publisher authenticated |  | ||||||
| [2019-12-19 20:46:42.658] [info] Published msg 3 |  | ||||||
| [2019-12-19 20:46:42.659] [info] Published message id 3 acked |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| To use OpenSSL on macOS, compile with `make ws_openssl`. First you will have to install OpenSSL libraries, which can be done with Homebrew. Use `make ws_mbedtls` accordingly to use MbedTLS. |  | ||||||
|   | |||||||
| @@ -1,59 +0,0 @@ | |||||||
| # |  | ||||||
| # Author: Benjamin Sergeant |  | ||||||
| # Copyright (c) 2019 Machine Zone, Inc. All rights reserved. |  | ||||||
| # |  | ||||||
|  |  | ||||||
| set (IXBOTS_SOURCES |  | ||||||
|     ixbots/IXCobraBot.cpp |  | ||||||
|     ixbots/IXCobraToCobraBot.cpp |  | ||||||
|     ixbots/IXCobraToSentryBot.cpp |  | ||||||
|     ixbots/IXCobraToStatsdBot.cpp |  | ||||||
|     ixbots/IXCobraToStdoutBot.cpp |  | ||||||
|     ixbots/IXCobraMetricsToRedisBot.cpp |  | ||||||
|     ixbots/IXCobraToPythonBot.cpp |  | ||||||
|     ixbots/IXStatsdClient.cpp |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| set (IXBOTS_HEADERS |  | ||||||
|     ixbots/IXCobraBot.h |  | ||||||
|     ixbots/IXCobraBotConfig.h |  | ||||||
|     ixbots/IXCobraToCobraBot.h |  | ||||||
|     ixbots/IXCobraToSentryBot.h |  | ||||||
|     ixbots/IXCobraToStatsdBot.h |  | ||||||
|     ixbots/IXCobraToStdoutBot.h |  | ||||||
|     ixbots/IXCobraMetricsToRedisBot.h |  | ||||||
|     ixbots/IXCobraToPythonBot.h |  | ||||||
|     ixbots/IXStatsdClient.h |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| add_library(ixbots STATIC |  | ||||||
|     ${IXBOTS_SOURCES} |  | ||||||
|     ${IXBOTS_HEADERS} |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| find_package(JsonCpp) |  | ||||||
| if (NOT JSONCPP_FOUND) |  | ||||||
|   set(JSONCPP_INCLUDE_DIRS ../third_party/jsoncpp) |  | ||||||
| endif() |  | ||||||
|  |  | ||||||
| if (USE_PYTHON) |  | ||||||
|   target_compile_definitions(ixbots PUBLIC IXBOTS_USE_PYTHON) |  | ||||||
|   find_package(Python COMPONENTS Development) |  | ||||||
| endif() |  | ||||||
|  |  | ||||||
| set(IXBOTS_INCLUDE_DIRS |  | ||||||
|     . |  | ||||||
|     .. |  | ||||||
|     ../ixcore |  | ||||||
|     ../ixwebsocket |  | ||||||
|     ../ixcobra |  | ||||||
|     ../ixredis |  | ||||||
|     ../ixsentry |  | ||||||
|     ${JSONCPP_INCLUDE_DIRS} |  | ||||||
|     ${SPDLOG_INCLUDE_DIRS}) |  | ||||||
|  |  | ||||||
| if (USE_PYTHON) |  | ||||||
|   set(IXBOTS_INCLUDE_DIRS ${IXBOTS_INCLUDE_DIRS} ${Python_INCLUDE_DIRS}) |  | ||||||
| endif() |  | ||||||
|  |  | ||||||
| target_include_directories( ixbots PUBLIC ${IXBOTS_INCLUDE_DIRS} ) |  | ||||||
| @@ -1,326 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraBot.cpp |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2020 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXCobraBot.h" |  | ||||||
|  |  | ||||||
| #include <ixcobra/IXCobraConnection.h> |  | ||||||
| #include <ixcore/utils/IXCoreLogger.h> |  | ||||||
| #include <ixwebsocket/IXSetThreadName.h> |  | ||||||
|  |  | ||||||
| #include <algorithm> |  | ||||||
| #include <chrono> |  | ||||||
| #include <sstream> |  | ||||||
| #include <thread> |  | ||||||
| #include <vector> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     int64_t CobraBot::run(const CobraBotConfig& botConfig) |  | ||||||
|     { |  | ||||||
|         auto config = botConfig.cobraConfig; |  | ||||||
|         auto channel = botConfig.channel; |  | ||||||
|         auto filter = botConfig.filter; |  | ||||||
|         auto position = botConfig.position; |  | ||||||
|         auto enableHeartbeat = botConfig.enableHeartbeat; |  | ||||||
|         auto heartBeatTimeout = botConfig.heartBeatTimeout; |  | ||||||
|         auto runtime = botConfig.runtime; |  | ||||||
|         auto maxEventsPerMinute = botConfig.maxEventsPerMinute; |  | ||||||
|         auto limitReceivedEvents = botConfig.limitReceivedEvents; |  | ||||||
|         auto batchSize = botConfig.batchSize; |  | ||||||
|  |  | ||||||
|         config.headers["X-Cobra-Channel"] = channel; |  | ||||||
|  |  | ||||||
|         ix::CobraConnection conn; |  | ||||||
|         conn.configure(config); |  | ||||||
|         conn.connect(); |  | ||||||
|  |  | ||||||
|         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<int> receivedCountPerMinutes(0); |  | ||||||
|         std::atomic<bool> stop(false); |  | ||||||
|         std::atomic<bool> throttled(false); |  | ||||||
|         std::atomic<bool> fatalCobraError(false); |  | ||||||
|         std::atomic<bool> stalledConnection(false); |  | ||||||
|         int minuteCounter = 0; |  | ||||||
|  |  | ||||||
|         auto timer = [&sentCount, |  | ||||||
|                       &receivedCount, |  | ||||||
|                       &sentCountTotal, |  | ||||||
|                       &receivedCountTotal, |  | ||||||
|                       &sentCountPerSecs, |  | ||||||
|                       &receivedCountPerSecs, |  | ||||||
|                       &receivedCountPerMinutes, |  | ||||||
|                       &minuteCounter, |  | ||||||
|                       &conn, |  | ||||||
|                       &stop] { |  | ||||||
|             setThreadName("Bot progress"); |  | ||||||
|             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; |  | ||||||
|  |  | ||||||
|                 if (conn.isAuthenticated()) |  | ||||||
|                 { |  | ||||||
|                     CoreLogger::info(ss.str()); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 receivedCountPerSecs = receivedCount - receivedCountTotal; |  | ||||||
|                 sentCountPerSecs = sentCount - sentCountTotal; |  | ||||||
|  |  | ||||||
|                 receivedCountTotal += receivedCountPerSecs; |  | ||||||
|                 sentCountTotal += sentCountPerSecs; |  | ||||||
|  |  | ||||||
|                 auto duration = std::chrono::seconds(1); |  | ||||||
|                 std::this_thread::sleep_for(duration); |  | ||||||
|  |  | ||||||
|                 if (minuteCounter++ == 60) |  | ||||||
|                 { |  | ||||||
|                     receivedCountPerMinutes = 0; |  | ||||||
|                     minuteCounter = 0; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             CoreLogger::info("timer thread done"); |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         std::thread t1(timer); |  | ||||||
|  |  | ||||||
|         auto heartbeat = [&sentCount, |  | ||||||
|                           &receivedCount, |  | ||||||
|                           &stop, |  | ||||||
|                           &enableHeartbeat, |  | ||||||
|                           &heartBeatTimeout, |  | ||||||
|                           &stalledConnection] |  | ||||||
|         { |  | ||||||
|             setThreadName("Bot heartbeat"); |  | ||||||
|             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) |  | ||||||
|                 { |  | ||||||
|                     ss.str(""); |  | ||||||
|                     ss << "no messages received or sent for " |  | ||||||
|                        << heartBeatTimeout << " seconds, reconnecting"; |  | ||||||
|  |  | ||||||
|                     CoreLogger::warn(ss.str()); |  | ||||||
|                     stalledConnection = true; |  | ||||||
|                 } |  | ||||||
|                 state = currentState; |  | ||||||
|  |  | ||||||
|                 auto duration = std::chrono::seconds(heartBeatTimeout); |  | ||||||
|                 std::this_thread::sleep_for(duration); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             CoreLogger::info("heartbeat thread done"); |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         std::thread t2(heartbeat); |  | ||||||
|  |  | ||||||
|         std::string subscriptionPosition(position); |  | ||||||
|  |  | ||||||
|         conn.setEventCallback([this, |  | ||||||
|                                &conn, |  | ||||||
|                                &channel, |  | ||||||
|                                &filter, |  | ||||||
|                                &subscriptionPosition, |  | ||||||
|                                &throttled, |  | ||||||
|                                &receivedCount, |  | ||||||
|                                &receivedCountPerMinutes, |  | ||||||
|                                maxEventsPerMinute, |  | ||||||
|                                limitReceivedEvents, |  | ||||||
|                                batchSize, |  | ||||||
|                                &fatalCobraError, |  | ||||||
|                                &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::Handshake) |  | ||||||
|             { |  | ||||||
|                 CoreLogger::info("Subscriber: Cobra handshake connection id: " + event->connectionId); |  | ||||||
|             } |  | ||||||
|             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, batchSize, |  | ||||||
|                     [&sentCount, &receivedCountPerMinutes, |  | ||||||
|                      maxEventsPerMinute, limitReceivedEvents, |  | ||||||
|                      &throttled, &receivedCount, |  | ||||||
|                      &subscriptionPosition, &fatalCobraError, |  | ||||||
|                      this](const Json::Value& msg, const std::string& position) { |  | ||||||
|                         subscriptionPosition = position; |  | ||||||
|                         ++receivedCount; |  | ||||||
|  |  | ||||||
|                         ++receivedCountPerMinutes; |  | ||||||
|                         if (limitReceivedEvents) |  | ||||||
|                         { |  | ||||||
|                             if (receivedCountPerMinutes > maxEventsPerMinute) |  | ||||||
|                             { |  | ||||||
|                                 return; |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|  |  | ||||||
|                         // If we cannot send to sentry fast enough, drop the message |  | ||||||
|                         if (throttled) |  | ||||||
|                         { |  | ||||||
|                             return; |  | ||||||
|                         } |  | ||||||
|  |  | ||||||
|                         _onBotMessageCallback( |  | ||||||
|                             msg, position, throttled, |  | ||||||
|                             fatalCobraError, sentCount); |  | ||||||
|                     }); |  | ||||||
|             } |  | ||||||
|             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; |  | ||||||
|  |  | ||||||
|                 if (stalledConnection) |  | ||||||
|                 { |  | ||||||
|                     conn.disconnect(); |  | ||||||
|                     conn.connect(); |  | ||||||
|                     stalledConnection = false; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         // 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; |  | ||||||
|  |  | ||||||
|                 if (stalledConnection) |  | ||||||
|                 { |  | ||||||
|                     conn.disconnect(); |  | ||||||
|                     conn.connect(); |  | ||||||
|                     stalledConnection = false; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // |  | ||||||
|         // Cleanup. |  | ||||||
|         // join all the bg threads and stop them. |  | ||||||
|         // |  | ||||||
|         conn.disconnect(); |  | ||||||
|         stop = true; |  | ||||||
|  |  | ||||||
|         // progress thread |  | ||||||
|         t1.join(); |  | ||||||
|  |  | ||||||
|         // heartbeat thread |  | ||||||
|         if (t2.joinable()) t2.join(); |  | ||||||
|  |  | ||||||
|         return fatalCobraError ? -1 : (int64_t) sentCount; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraBot::setOnBotMessageCallback(const OnBotMessageCallback& callback) |  | ||||||
|     { |  | ||||||
|         _onBotMessageCallback = callback; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::string CobraBot::getDeviceIdentifier(const Json::Value& msg) |  | ||||||
|     { |  | ||||||
|         std::string deviceId("na"); |  | ||||||
|  |  | ||||||
|         auto osName = msg["device"]["os_name"]; |  | ||||||
|         if (osName == "Android") |  | ||||||
|         { |  | ||||||
|             deviceId = msg["device"]["model"].asString(); |  | ||||||
|         } |  | ||||||
|         else if (osName == "iOS") |  | ||||||
|         { |  | ||||||
|             deviceId = msg["device"]["hardware_model"].asString(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return deviceId; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,36 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraBot.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2020 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include <atomic> |  | ||||||
| #include <functional> |  | ||||||
| #include "IXCobraBotConfig.h" |  | ||||||
| #include <json/json.h> |  | ||||||
| #include <stddef.h> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     using OnBotMessageCallback = std::function<void(const Json::Value&, |  | ||||||
|                                                     const std::string&, |  | ||||||
|                                                     std::atomic<bool>&, |  | ||||||
|                                                     std::atomic<bool>&, |  | ||||||
|                                                     std::atomic<uint64_t>&)>; |  | ||||||
|  |  | ||||||
|     class CobraBot |  | ||||||
|     { |  | ||||||
|     public: |  | ||||||
|         CobraBot() = default; |  | ||||||
|  |  | ||||||
|         int64_t run(const CobraBotConfig& botConfig); |  | ||||||
|         void setOnBotMessageCallback(const OnBotMessageCallback& callback); |  | ||||||
|  |  | ||||||
|         std::string getDeviceIdentifier(const Json::Value& msg); |  | ||||||
|  |  | ||||||
|     private: |  | ||||||
|         OnBotMessageCallback _onBotMessageCallback; |  | ||||||
|     }; |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,32 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraBotConfig.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2020 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include <string> |  | ||||||
| #include <limits> |  | ||||||
| #include <ixcobra/IXCobraConfig.h> |  | ||||||
|  |  | ||||||
| #ifdef max |  | ||||||
| #undef max |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     struct CobraBotConfig |  | ||||||
|     { |  | ||||||
|         CobraConfig cobraConfig; |  | ||||||
|         std::string channel; |  | ||||||
|         std::string filter; |  | ||||||
|         std::string position = std::string("$"); |  | ||||||
|         bool enableHeartbeat = true; |  | ||||||
|         int heartBeatTimeout = 60; |  | ||||||
|         int runtime = -1; |  | ||||||
|         int maxEventsPerMinute = std::numeric_limits<int>::max(); |  | ||||||
|         bool limitReceivedEvents = false; |  | ||||||
|         int batchSize = 1; |  | ||||||
|     }; |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,149 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraMetricsToRedisBot.cpp |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2020 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXCobraMetricsToRedisBot.h" |  | ||||||
|  |  | ||||||
| #include "IXCobraBot.h" |  | ||||||
| #include "IXStatsdClient.h" |  | ||||||
| #include <chrono> |  | ||||||
| #include <ixcobra/IXCobraConnection.h> |  | ||||||
| #include <ixcore/utils/IXCoreLogger.h> |  | ||||||
| #include <sstream> |  | ||||||
| #include <vector> |  | ||||||
| #include <algorithm> |  | ||||||
| #include <map> |  | ||||||
| #include <cctype> |  | ||||||
|  |  | ||||||
|  |  | ||||||
| namespace |  | ||||||
| { |  | ||||||
|     std::string removeSpaces(const std::string& str) |  | ||||||
|     { |  | ||||||
|         std::string out(str); |  | ||||||
|         out.erase( |  | ||||||
|             std::remove_if(out.begin(), out.end(), [](unsigned char x) { return std::isspace(x); }), |  | ||||||
|             out.end()); |  | ||||||
|  |  | ||||||
|         return out; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     bool processPerfMetricsEventSlowFrames(const Json::Value& msg, |  | ||||||
|                                            RedisClient& redisClient, |  | ||||||
|                                            const std::string& deviceId) |  | ||||||
|     { |  | ||||||
|         auto frameRateHistogramCounts = msg["data"]["FrameRateHistogramCounts"]; |  | ||||||
|  |  | ||||||
|         int slowFrames = 0; |  | ||||||
|         slowFrames += frameRateHistogramCounts[4].asInt(); |  | ||||||
|         slowFrames += frameRateHistogramCounts[5].asInt(); |  | ||||||
|         slowFrames += frameRateHistogramCounts[6].asInt(); |  | ||||||
|         slowFrames += frameRateHistogramCounts[7].asInt(); |  | ||||||
|  |  | ||||||
|         // |  | ||||||
|         // XADD without a device id |  | ||||||
|         // |  | ||||||
|         std::stringstream ss; |  | ||||||
|         ss << msg["id"].asString() << "_slow_frames" << "." |  | ||||||
|            << msg["device"]["game"].asString() << "." |  | ||||||
|            << msg["device"]["os_name"].asString() << "." |  | ||||||
|            << removeSpaces(msg["data"]["Tag"].asString()); |  | ||||||
|  |  | ||||||
|         int maxLen; |  | ||||||
|         maxLen = 100000; |  | ||||||
|         std::string id = ss.str(); |  | ||||||
|         std::string errMsg; |  | ||||||
|         if (redisClient.xadd(id, std::to_string(slowFrames), maxLen, errMsg).empty()) |  | ||||||
|         { |  | ||||||
|             CoreLogger::info(std::string("redis XADD error: ") + errMsg); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // |  | ||||||
|         // XADD with a device id |  | ||||||
|         // |  | ||||||
|         ss.str(""); // reset the stringstream |  | ||||||
|         ss << msg["id"].asString() << "_slow_frames_by_device" << "." |  | ||||||
|            << deviceId << "." |  | ||||||
|            << msg["device"]["game"].asString() << "." |  | ||||||
|            << msg["device"]["os_name"].asString() << "." |  | ||||||
|            << removeSpaces(msg["data"]["Tag"].asString()); |  | ||||||
|  |  | ||||||
|         id = ss.str(); |  | ||||||
|         maxLen = 1000; |  | ||||||
|         if (redisClient.xadd(id, std::to_string(slowFrames), maxLen, errMsg).empty()) |  | ||||||
|         { |  | ||||||
|             CoreLogger::info(std::string("redis XADD error: ") + errMsg); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // |  | ||||||
|         // Add device to the device zset, and increment the score |  | ||||||
|         // so that we know which devices are used more than others |  | ||||||
|         // ZINCRBY myzset 1 one |  | ||||||
|         // |  | ||||||
|         ss.str(""); // reset the stringstream |  | ||||||
|         ss << msg["id"].asString() << "_slow_frames_devices" << "." |  | ||||||
|            << msg["device"]["game"].asString(); |  | ||||||
|  |  | ||||||
|         id = ss.str(); |  | ||||||
|         std::vector<std::string> args = { |  | ||||||
|             "ZINCRBY", id, "1", deviceId |  | ||||||
|         }; |  | ||||||
|         auto response = redisClient.send(args, errMsg); |  | ||||||
|         if (response.first == RespType::Error) |  | ||||||
|         { |  | ||||||
|             CoreLogger::info(std::string("redis ZINCRBY error: ") + errMsg); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     int64_t cobra_metrics_to_redis_bot(const ix::CobraBotConfig& config, |  | ||||||
|                                        RedisClient& redisClient, |  | ||||||
|                                        bool verbose) |  | ||||||
|     { |  | ||||||
|         CobraBot bot; |  | ||||||
|  |  | ||||||
|         bot.setOnBotMessageCallback( |  | ||||||
|             [&redisClient, &verbose, &bot] |  | ||||||
|              (const Json::Value& msg, |  | ||||||
|               const std::string& /*position*/, |  | ||||||
|               std::atomic<bool>& /*throttled*/, |  | ||||||
|               std::atomic<bool>& /*fatalCobraError*/, |  | ||||||
|               std::atomic<uint64_t>& sentCount) -> void { |  | ||||||
|             if (msg["device"].isNull()) |  | ||||||
|             { |  | ||||||
|                 CoreLogger::info("no device entry, skipping event"); |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (msg["id"].isNull()) |  | ||||||
|             { |  | ||||||
|                 CoreLogger::info("no id entry, skipping event"); |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // |  | ||||||
|             // Display full message with |  | ||||||
|             if (verbose) |  | ||||||
|             { |  | ||||||
|                 CoreLogger::info(msg.toStyledString()); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             bool success = false; |  | ||||||
|             if (msg["id"].asString() == "engine_performance_metrics_id") |  | ||||||
|             { |  | ||||||
|                 auto deviceId = bot.getDeviceIdentifier(msg); |  | ||||||
|                 success = processPerfMetricsEventSlowFrames(msg, redisClient, deviceId); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (success) sentCount++; |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         return bot.run(config); |  | ||||||
|     } |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,20 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraMetricsToRedisBot.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2020 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include <cstdint> |  | ||||||
| #include <ixredis/IXRedisClient.h> |  | ||||||
| #include "IXCobraBotConfig.h" |  | ||||||
| #include <stddef.h> |  | ||||||
| #include <string> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     int64_t cobra_metrics_to_redis_bot(const ix::CobraBotConfig& config, |  | ||||||
|                                        RedisClient& redisClient, |  | ||||||
|                                        bool verbose); |  | ||||||
| } // namespace ix |  | ||||||
|  |  | ||||||
| @@ -1,45 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraToCobraBot.cpp |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2020 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXCobraToCobraBot.h" |  | ||||||
|  |  | ||||||
| #include "IXCobraBot.h" |  | ||||||
| #include <ixcobra/IXCobraMetricsPublisher.h> |  | ||||||
| #include <sstream> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     int64_t cobra_to_cobra_bot(const ix::CobraBotConfig& cobraBotConfig, |  | ||||||
|                                const std::string& republishChannel, |  | ||||||
|                                const std::string& publisherRolename, |  | ||||||
|                                const std::string& publisherRolesecret) |  | ||||||
|     { |  | ||||||
|         CobraBot bot; |  | ||||||
|  |  | ||||||
|         CobraMetricsPublisher cobraMetricsPublisher; |  | ||||||
|         CobraConfig cobraPublisherConfig = cobraBotConfig.cobraConfig; |  | ||||||
|         cobraPublisherConfig.rolename = publisherRolename; |  | ||||||
|         cobraPublisherConfig.rolesecret = publisherRolesecret; |  | ||||||
|         cobraPublisherConfig.headers["X-Cobra-Republish-Channel"] = republishChannel; |  | ||||||
|  |  | ||||||
|         cobraMetricsPublisher.configure(cobraPublisherConfig, republishChannel); |  | ||||||
|  |  | ||||||
|         bot.setOnBotMessageCallback( |  | ||||||
|             [&republishChannel, &cobraMetricsPublisher](const Json::Value& msg, |  | ||||||
|                                                         const std::string& /*position*/, |  | ||||||
|                                                         std::atomic<bool>& /*throttled*/, |  | ||||||
|                                                         std::atomic<bool>& /*fatalCobraError*/, |  | ||||||
|                                                         std::atomic<uint64_t>& sentCount) -> void { |  | ||||||
|                 Json::Value msgWithNoId(msg); |  | ||||||
|                 msgWithNoId.removeMember("id"); |  | ||||||
|              |  | ||||||
|                 cobraMetricsPublisher.push(republishChannel, msg); |  | ||||||
|                 sentCount++; |  | ||||||
|             }); |  | ||||||
|  |  | ||||||
|         return bot.run(cobraBotConfig); |  | ||||||
|     } |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,20 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraToCobraBot.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2020 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include <cstdint> |  | ||||||
| #include <ixbots/IXStatsdClient.h> |  | ||||||
| #include "IXCobraBotConfig.h" |  | ||||||
| #include <stddef.h> |  | ||||||
| #include <string> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     int64_t cobra_to_cobra_bot(const ix::CobraBotConfig& config, |  | ||||||
|                                const std::string& republishChannel, |  | ||||||
|                                const std::string& publisherRolename, |  | ||||||
|                                const std::string& publisherRolesecret); |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,329 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraToPythonBot.cpp |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2020 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXCobraToPythonBot.h" |  | ||||||
|  |  | ||||||
| #include "IXCobraBot.h" |  | ||||||
| #include "IXStatsdClient.h" |  | ||||||
| #include <chrono> |  | ||||||
| #include <ixcobra/IXCobraConnection.h> |  | ||||||
| #include <ixcore/utils/IXCoreLogger.h> |  | ||||||
| #include <sstream> |  | ||||||
| #include <vector> |  | ||||||
| #include <algorithm> |  | ||||||
| #include <map> |  | ||||||
| #include <cctype> |  | ||||||
|  |  | ||||||
| // |  | ||||||
| // I cannot get Windows to easily build on CI (github action) so support |  | ||||||
| // is disabled for now. It should be a simple fix  |  | ||||||
| // (linking error about missing debug build) |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #ifdef IXBOTS_USE_PYTHON |  | ||||||
| #define PY_SSIZE_T_CLEAN |  | ||||||
| #include <Python.h> |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #ifdef IXBOTS_USE_PYTHON |  | ||||||
| namespace |  | ||||||
| { |  | ||||||
|     // |  | ||||||
|     // This function is unused at this point. It produce a correct output, |  | ||||||
|     // but triggers memory leaks when called repeateadly, as I cannot figure out how to |  | ||||||
|     // make the reference counting Python functions to work properly (Py_DECREF and friends) |  | ||||||
|     // |  | ||||||
|     PyObject* jsonToPythonObject(const Json::Value& val) |  | ||||||
|     { |  | ||||||
|         switch(val.type()) |  | ||||||
|         { |  | ||||||
|             case Json::nullValue: |  | ||||||
|             { |  | ||||||
|                 return Py_None; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             case Json::intValue: |  | ||||||
|             { |  | ||||||
|                 return PyLong_FromLong(val.asInt64()); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             case Json::uintValue: |  | ||||||
|             { |  | ||||||
|                 return PyLong_FromLong(val.asUInt64()); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             case Json::realValue: |  | ||||||
|             { |  | ||||||
|                 return PyFloat_FromDouble(val.asDouble()); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             case Json::stringValue: |  | ||||||
|             { |  | ||||||
|                 return PyUnicode_FromString(val.asCString()); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             case Json::booleanValue: |  | ||||||
|             { |  | ||||||
|                 return val.asBool() ? Py_True : Py_False; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             case Json::arrayValue: |  | ||||||
|             { |  | ||||||
|                 PyObject* list = PyList_New(val.size()); |  | ||||||
|                 Py_ssize_t i = 0; |  | ||||||
|                 for (auto&& it = val.begin(); it != val.end(); ++it) |  | ||||||
|                 { |  | ||||||
|                     PyList_SetItem(list, i++, jsonToPythonObject(*it)); |  | ||||||
|                 } |  | ||||||
|                 return list; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             case Json::objectValue: |  | ||||||
|             { |  | ||||||
|                 PyObject* dict = PyDict_New(); |  | ||||||
|                 for (auto&& it = val.begin(); it != val.end(); ++it) |  | ||||||
|                 { |  | ||||||
|                     PyObject* key = jsonToPythonObject(it.key()); |  | ||||||
|                     PyObject* value = jsonToPythonObject(*it); |  | ||||||
|  |  | ||||||
|                     PyDict_SetItem(dict, key, value); |  | ||||||
|                 } |  | ||||||
|                 return dict; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     int64_t cobra_to_python_bot(const ix::CobraBotConfig& config, |  | ||||||
|                                 StatsdClient& statsdClient, |  | ||||||
|                                 const std::string& moduleName) |  | ||||||
|     { |  | ||||||
| #ifndef IXBOTS_USE_PYTHON |  | ||||||
|         CoreLogger::error("Command is disabled. " |  | ||||||
|                           "Needs to be configured with USE_PYTHON=1"); |  | ||||||
|         return -1; |  | ||||||
| #else |  | ||||||
|         CobraBot bot; |  | ||||||
|         Py_InitializeEx(0); // 0 arg so that we do not install signal handlers  |  | ||||||
|                             // which prevent us from using Ctrl-C |  | ||||||
|  |  | ||||||
|         PyObject* pyModuleName = PyUnicode_DecodeFSDefault(moduleName.c_str()); |  | ||||||
|  |  | ||||||
|         if (pyModuleName == nullptr) |  | ||||||
|         { |  | ||||||
|             CoreLogger::error("Python error: Cannot decode file system path"); |  | ||||||
|             PyErr_Print(); |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Import module |  | ||||||
|         PyObject* pyModule = PyImport_Import(pyModuleName); |  | ||||||
|         Py_DECREF(pyModuleName); |  | ||||||
|         if (pyModule == nullptr) |  | ||||||
|         { |  | ||||||
|             CoreLogger::error("Python error: Cannot import module."); |  | ||||||
|             CoreLogger::error("Module name cannot countain dash characters."); |  | ||||||
|             CoreLogger::error("Is PYTHONPATH set correctly ?"); |  | ||||||
|             PyErr_Print(); |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // module main funtion name is named 'run' |  | ||||||
|         const std::string entryPoint("run"); |  | ||||||
|         PyObject* pyFunc = PyObject_GetAttrString(pyModule, entryPoint.c_str()); |  | ||||||
|  |  | ||||||
|         if (!pyFunc) |  | ||||||
|         { |  | ||||||
|             CoreLogger::error("run symbol is missing from module."); |  | ||||||
|             PyErr_Print(); |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (!PyCallable_Check(pyFunc)) |  | ||||||
|         { |  | ||||||
|             CoreLogger::error("run symbol is not a function."); |  | ||||||
|             PyErr_Print(); |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         bot.setOnBotMessageCallback( |  | ||||||
|             [&statsdClient, pyFunc] |  | ||||||
|                 (const Json::Value& msg, |  | ||||||
|                  const std::string& /*position*/, |  | ||||||
|                  std::atomic<bool>& /*throttled*/, |  | ||||||
|                  std::atomic<bool>& fatalCobraError, |  | ||||||
|                  std::atomic<uint64_t>& sentCount) -> void { |  | ||||||
|             // |  | ||||||
|             // Invoke python script here. First build function parameters, a tuple |  | ||||||
|             // |  | ||||||
|             const int kVersion = 1; // We can bump this and let the interface evolve |  | ||||||
|  |  | ||||||
|             PyObject *pyArgs = PyTuple_New(2); |  | ||||||
|             PyTuple_SetItem(pyArgs, 0, PyLong_FromLong(kVersion)); // First argument |  | ||||||
|  |  | ||||||
|             // |  | ||||||
|             // It would be better to create a Python object (a dictionary)  |  | ||||||
|             // from the json msg, but it is simpler to serialize it to a string |  | ||||||
|             // and decode it on the Python side of the fence |  | ||||||
|             // |  | ||||||
|             PyObject* pySerializedJson = PyUnicode_FromString(msg.toStyledString().c_str()); |  | ||||||
|             PyTuple_SetItem(pyArgs, 1, pySerializedJson); // Second argument |  | ||||||
|  |  | ||||||
|             // Invoke the python routine |  | ||||||
|             PyObject* pyList = PyObject_CallObject(pyFunc, pyArgs); |  | ||||||
|  |  | ||||||
|             // Error calling the function |  | ||||||
|             if (pyList == nullptr) |  | ||||||
|             { |  | ||||||
|                 fatalCobraError = true; |  | ||||||
|                 CoreLogger::error("run() function call failed. Input msg: "); |  | ||||||
|                 auto serializedMsg = msg.toStyledString(); |  | ||||||
|                 CoreLogger::error(serializedMsg); |  | ||||||
|                 PyErr_Print(); |  | ||||||
|                 CoreLogger::error("================"); |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // Invalid return type |  | ||||||
|             if (!PyList_Check(pyList)) |  | ||||||
|             { |  | ||||||
|                 fatalCobraError = true; |  | ||||||
|                 CoreLogger::error("run() return type should be a list"); |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // The result is a list of dict containing sufficient info  |  | ||||||
|             // to send messages to statsd |  | ||||||
|             auto listSize = PyList_Size(pyList); |  | ||||||
|              |  | ||||||
|             for (Py_ssize_t i = 0 ; i < listSize; ++i) |  | ||||||
|             { |  | ||||||
|                 PyObject* dict = PyList_GetItem(pyList, i); |  | ||||||
|  |  | ||||||
|                 // Make sure this is a dict |  | ||||||
|                 if (!PyDict_Check(dict)) |  | ||||||
|                 { |  | ||||||
|                     fatalCobraError = true; |  | ||||||
|                     CoreLogger::error("list element is not a dict"); |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // |  | ||||||
|                 // Retrieve object kind |  | ||||||
|                 // |  | ||||||
|                 PyObject* pyKind = PyDict_GetItemString(dict, "kind"); |  | ||||||
|                 if (!PyUnicode_Check(pyKind)) |  | ||||||
|                 { |  | ||||||
|                     fatalCobraError = true; |  | ||||||
|                     CoreLogger::error("kind entry is not a string"); |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
|                 std::string kind(PyUnicode_AsUTF8(pyKind)); |  | ||||||
|  |  | ||||||
|                 bool counter = false; |  | ||||||
|                 bool gauge = false; |  | ||||||
|                 bool timing = false; |  | ||||||
|  |  | ||||||
|                 if (kind == "counter") |  | ||||||
|                 { |  | ||||||
|                     counter = true; |  | ||||||
|                 } |  | ||||||
|                 else if (kind == "gauge") |  | ||||||
|                 { |  | ||||||
|                     gauge = true; |  | ||||||
|                 } |  | ||||||
|                 else if (kind == "timing") |  | ||||||
|                 { |  | ||||||
|                     timing = true; |  | ||||||
|                 } |  | ||||||
|                 else |  | ||||||
|                 { |  | ||||||
|                     fatalCobraError = true; |  | ||||||
|                     CoreLogger::error(std::string("invalid kind entry: ") + kind + |  | ||||||
|                                       ". Supported ones are counter, gauge, timing"); |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // |  | ||||||
|                 // Retrieve object key |  | ||||||
|                 // |  | ||||||
|                 PyObject* pyKey = PyDict_GetItemString(dict, "key"); |  | ||||||
|                 if (!PyUnicode_Check(pyKey)) |  | ||||||
|                 { |  | ||||||
|                     fatalCobraError = true; |  | ||||||
|                     CoreLogger::error("key entry is not a string"); |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
|                 std::string key(PyUnicode_AsUTF8(pyKey)); |  | ||||||
|  |  | ||||||
|                 // |  | ||||||
|                 // Retrieve object value and send data to statsd |  | ||||||
|                 // |  | ||||||
|                 PyObject* pyValue = PyDict_GetItemString(dict, "value"); |  | ||||||
|  |  | ||||||
|                 // Send data to statsd |  | ||||||
|                 if (PyFloat_Check(pyValue)) |  | ||||||
|                 { |  | ||||||
|                     double value = PyFloat_AsDouble(pyValue); |  | ||||||
|  |  | ||||||
|                     if (counter) |  | ||||||
|                     { |  | ||||||
|                         statsdClient.count(key, value); |  | ||||||
|                     } |  | ||||||
|                     else if (gauge) |  | ||||||
|                     { |  | ||||||
|                         statsdClient.gauge(key, value); |  | ||||||
|                     } |  | ||||||
|                     else if (timing) |  | ||||||
|                     { |  | ||||||
|                         statsdClient.timing(key, value); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 else if (PyLong_Check(pyValue)) |  | ||||||
|                 { |  | ||||||
|                     long value = PyLong_AsLong(pyValue); |  | ||||||
|  |  | ||||||
|                     if (counter) |  | ||||||
|                     { |  | ||||||
|                         statsdClient.count(key, value); |  | ||||||
|                     } |  | ||||||
|                     else if (gauge) |  | ||||||
|                     { |  | ||||||
|                         statsdClient.gauge(key, value); |  | ||||||
|                     } |  | ||||||
|                     else if (timing) |  | ||||||
|                     { |  | ||||||
|                         statsdClient.timing(key, value); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 else |  | ||||||
|                 { |  | ||||||
|                     fatalCobraError = true; |  | ||||||
|                     CoreLogger::error("value entry is neither an int or a float"); |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 sentCount++; // should we update this for each statsd object sent ? |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             Py_DECREF(pyArgs); |  | ||||||
|             Py_DECREF(pyList); |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         bool status = bot.run(config); |  | ||||||
|  |  | ||||||
|         // Cleanup - we should do something similar in all exit case ... |  | ||||||
|         Py_DECREF(pyFunc); |  | ||||||
|         Py_DECREF(pyModule); |  | ||||||
|         Py_FinalizeEx(); |  | ||||||
|  |  | ||||||
|         return status; |  | ||||||
| #endif |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,19 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraMetricsToStatsdBot.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2020 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include <cstdint> |  | ||||||
| #include <ixbots/IXStatsdClient.h> |  | ||||||
| #include "IXCobraBotConfig.h" |  | ||||||
| #include <stddef.h> |  | ||||||
| #include <string> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     int64_t cobra_to_python_bot(const ix::CobraBotConfig& config, |  | ||||||
|                                 StatsdClient& statsdClient, |  | ||||||
|                                 const std::string& moduleName); |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,76 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraToSentryBot.cpp |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXCobraToSentryBot.h" |  | ||||||
|  |  | ||||||
| #include "IXCobraBot.h" |  | ||||||
| #include <ixcobra/IXCobraConnection.h> |  | ||||||
| #include <ixcore/utils/IXCoreLogger.h> |  | ||||||
|  |  | ||||||
| #include <chrono> |  | ||||||
| #include <sstream> |  | ||||||
| #include <vector> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     int64_t cobra_to_sentry_bot(const CobraBotConfig& config, |  | ||||||
|                                 SentryClient& sentryClient, |  | ||||||
|                                 bool verbose) |  | ||||||
|     { |  | ||||||
|         CobraBot bot; |  | ||||||
|         bot.setOnBotMessageCallback([&sentryClient, &verbose](const Json::Value& msg, |  | ||||||
|                                                     const std::string& /*position*/, |  | ||||||
|                                                     std::atomic<bool>& throttled, |  | ||||||
|                                                     std::atomic<bool>& /*fatalCobraError*/, |  | ||||||
|                                                     std::atomic<uint64_t>& sentCount) -> void { |  | ||||||
|             sentryClient.send(msg, verbose, |  | ||||||
|                 [&sentCount, &throttled](const HttpResponsePtr& response) { |  | ||||||
|                 if (!response) |  | ||||||
|                 { |  | ||||||
|                     CoreLogger::warn("Null HTTP Response"); |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 if (response->statusCode == 200) |  | ||||||
|                 { |  | ||||||
|                     sentCount++; |  | ||||||
|                 } |  | ||||||
|                 else |  | ||||||
|                 { |  | ||||||
|                     CoreLogger::error("Error sending data to sentry: " + std::to_string(response->statusCode)); |  | ||||||
|                     CoreLogger::error("Response: " + response->body); |  | ||||||
|  |  | ||||||
|                     // 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 bot.run(config); |  | ||||||
|     } |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,18 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraToSentryBot.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include <cstdint> |  | ||||||
| #include "IXCobraBotConfig.h" |  | ||||||
| #include <ixsentry/IXSentryClient.h> |  | ||||||
| #include <string> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     int64_t cobra_to_sentry_bot(const CobraBotConfig& config, |  | ||||||
|                                 SentryClient& sentryClient, |  | ||||||
|                                 bool verbose); |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,143 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraToStatsdBot.cpp |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXCobraToStatsdBot.h" |  | ||||||
|  |  | ||||||
| #include "IXCobraBot.h" |  | ||||||
| #include "IXStatsdClient.h" |  | ||||||
| #include <chrono> |  | ||||||
| #include <ixcobra/IXCobraConnection.h> |  | ||||||
| #include <ixcore/utils/IXCoreLogger.h> |  | ||||||
| #include <sstream> |  | ||||||
| #include <vector> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     // fields are command line argument that can be specified multiple times |  | ||||||
|     std::vector<std::string> parseFields(const std::string& fields) |  | ||||||
|     { |  | ||||||
|         std::vector<std::string> tokens; |  | ||||||
|  |  | ||||||
|         // Split by \n |  | ||||||
|         std::string token; |  | ||||||
|         std::stringstream tokenStream(fields); |  | ||||||
|  |  | ||||||
|         while (std::getline(tokenStream, token)) |  | ||||||
|         { |  | ||||||
|             tokens.push_back(token); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return tokens; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // |  | ||||||
|     // Extract an attribute from a Json Value. |  | ||||||
|     // extractAttr("foo.bar", {"foo": {"bar": "baz"}}) => baz |  | ||||||
|     // |  | ||||||
|     Json::Value extractAttr(const std::string& attr, const Json::Value& jsonValue) |  | ||||||
|     { |  | ||||||
|         // Split by . |  | ||||||
|         std::string token; |  | ||||||
|         std::stringstream tokenStream(attr); |  | ||||||
|  |  | ||||||
|         Json::Value val(jsonValue); |  | ||||||
|  |  | ||||||
|         while (std::getline(tokenStream, token, '.')) |  | ||||||
|         { |  | ||||||
|             val = val[token]; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return val; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     int64_t cobra_to_statsd_bot(const ix::CobraBotConfig& config, |  | ||||||
|                                 StatsdClient& statsdClient, |  | ||||||
|                                 const std::string& fields, |  | ||||||
|                                 const std::string& gauge, |  | ||||||
|                                 const std::string& timer, |  | ||||||
|                                 bool verbose) |  | ||||||
|     { |  | ||||||
|         auto tokens = parseFields(fields); |  | ||||||
|  |  | ||||||
|         CobraBot bot; |  | ||||||
|         bot.setOnBotMessageCallback( |  | ||||||
|             [&statsdClient, &tokens, &gauge, &timer, &verbose](const Json::Value& msg, |  | ||||||
|                                                      const std::string& /*position*/, |  | ||||||
|                                                      std::atomic<bool>& /*throttled*/, |  | ||||||
|                                                      std::atomic<bool>& fatalCobraError, |  | ||||||
|                                                      std::atomic<uint64_t>& sentCount) -> void { |  | ||||||
|                 std::string id; |  | ||||||
|                 size_t idx = 0; |  | ||||||
|                 for (auto&& attr : tokens) |  | ||||||
|                 { |  | ||||||
|                     auto val = extractAttr(attr, msg); |  | ||||||
|                     id += val.asString(); |  | ||||||
|  |  | ||||||
|                     // We add a dot separator unless we are processing the last token |  | ||||||
|                     if (idx++ != tokens.size() - 1) |  | ||||||
|                     { |  | ||||||
|                         id += "."; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 if (gauge.empty() && timer.empty()) |  | ||||||
|                 { |  | ||||||
|                     statsdClient.count(id, 1); |  | ||||||
|                 } |  | ||||||
|                 else |  | ||||||
|                 { |  | ||||||
|                     std::string attrName = (!gauge.empty()) ? gauge : timer; |  | ||||||
|                     auto val = extractAttr(attrName, msg); |  | ||||||
|                     size_t x; |  | ||||||
|  |  | ||||||
|                     if (val.isInt()) |  | ||||||
|                     { |  | ||||||
|                         x = (size_t) val.asInt(); |  | ||||||
|                     } |  | ||||||
|                     else if (val.isInt64()) |  | ||||||
|                     { |  | ||||||
|                         x = (size_t) val.asInt64(); |  | ||||||
|                     } |  | ||||||
|                     else if (val.isUInt()) |  | ||||||
|                     { |  | ||||||
|                         x = (size_t) val.asUInt(); |  | ||||||
|                     } |  | ||||||
|                     else if (val.isUInt64()) |  | ||||||
|                     { |  | ||||||
|                         x = (size_t) val.asUInt64(); |  | ||||||
|                     } |  | ||||||
|                     else if (val.isDouble()) |  | ||||||
|                     { |  | ||||||
|                         x = (size_t) val.asUInt64(); |  | ||||||
|                     } |  | ||||||
|                     else |  | ||||||
|                     { |  | ||||||
|                         CoreLogger::error("Gauge " + gauge + " is not a numeric type"); |  | ||||||
|                         fatalCobraError = true; |  | ||||||
|                         return; |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     if (verbose) |  | ||||||
|                     { |  | ||||||
|                         CoreLogger::info(id + " - " + attrName + " -> " + std::to_string(x)); |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     if (!gauge.empty()) |  | ||||||
|                     { |  | ||||||
|                         statsdClient.gauge(id, x); |  | ||||||
|                     } |  | ||||||
|                     else |  | ||||||
|                     { |  | ||||||
|                         statsdClient.timing(id, x); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 sentCount++; |  | ||||||
|             }); |  | ||||||
|  |  | ||||||
|         return bot.run(config); |  | ||||||
|     } |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,22 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraToStatsdBot.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include <cstdint> |  | ||||||
| #include <ixbots/IXStatsdClient.h> |  | ||||||
| #include "IXCobraBotConfig.h" |  | ||||||
| #include <stddef.h> |  | ||||||
| #include <string> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     int64_t cobra_to_statsd_bot(const ix::CobraBotConfig& config, |  | ||||||
|                                 StatsdClient& statsdClient, |  | ||||||
|                                 const std::string& fields, |  | ||||||
|                                 const std::string& gauge, |  | ||||||
|                                 const std::string& timer, |  | ||||||
|                                 bool verbose); |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,88 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraToStdoutBot.cpp |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXCobraToStdoutBot.h" |  | ||||||
|  |  | ||||||
| #include "IXCobraBot.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 ix::CobraBotConfig& config, |  | ||||||
|                                 bool fluentd, |  | ||||||
|                                 bool quiet) |  | ||||||
|     { |  | ||||||
|         CobraBot bot; |  | ||||||
|         auto jsonWriter = makeStreamWriter(); |  | ||||||
|  |  | ||||||
|         bot.setOnBotMessageCallback( |  | ||||||
|             [&fluentd, &quiet, &jsonWriter](const Json::Value& msg, |  | ||||||
|                                             const std::string& position, |  | ||||||
|                                             std::atomic<bool>& /*throttled*/, |  | ||||||
|                                             std::atomic<bool>& /*fatalCobraError*/, |  | ||||||
|                                             std::atomic<uint64_t>& sentCount) -> void { |  | ||||||
|                 if (!quiet) |  | ||||||
|                 { |  | ||||||
|                     writeToStdout(fluentd, jsonWriter, msg, position); |  | ||||||
|                 } |  | ||||||
|                 sentCount++; |  | ||||||
|             }); |  | ||||||
|  |  | ||||||
|         return bot.run(config); |  | ||||||
|     } |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,18 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraToStdoutBot.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include <cstdint> |  | ||||||
| #include "IXCobraBotConfig.h" |  | ||||||
| #include <stddef.h> |  | ||||||
| #include <string> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     int64_t cobra_to_stdout_bot(const ix::CobraBotConfig& config, |  | ||||||
|                                 bool fluentd, |  | ||||||
|                                 bool quiet); |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,161 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright (c) 2014, Rex |  | ||||||
|  * All rights reserved. |  | ||||||
|  * |  | ||||||
|  * Redistribution and use in source and binary forms, with or without |  | ||||||
|  * modification, are permitted provided that the following conditions are met: |  | ||||||
|  * |  | ||||||
|  * * Redistributions of source code must retain the above copyright notice, this |  | ||||||
|  *   list of conditions and the following disclaimer. |  | ||||||
|  * |  | ||||||
|  * * Redistributions in binary form must reproduce the above copyright notice, |  | ||||||
|  *   this list of conditions and the following disclaimer in the documentation |  | ||||||
|  *   and/or other materials provided with the distribution. |  | ||||||
|  * |  | ||||||
|  * * Neither the name of the {organization} nor the names of its |  | ||||||
|  *   contributors may be used to endorse or promote products derived from |  | ||||||
|  *   this software without specific prior written permission. |  | ||||||
|  * |  | ||||||
|  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |  | ||||||
|  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |  | ||||||
|  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |  | ||||||
|  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |  | ||||||
|  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |  | ||||||
|  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |  | ||||||
|  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |  | ||||||
|  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |  | ||||||
|  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |  | ||||||
|  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| /* |  | ||||||
|  *  IXStatsdClient.cpp |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2020 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| // Adapted from statsd-client-cpp |  | ||||||
| // test with netcat as a server: `nc -ul 8125` |  | ||||||
|  |  | ||||||
| #include "IXStatsdClient.h" |  | ||||||
|  |  | ||||||
| #include <ixwebsocket/IXNetSystem.h> |  | ||||||
| #include <ixwebsocket/IXSetThreadName.h> |  | ||||||
| #include <ixcore/utils/IXCoreLogger.h> |  | ||||||
| #include <sstream> |  | ||||||
| #include <stdlib.h> |  | ||||||
| #include <string.h> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     StatsdClient::StatsdClient(const std::string& host, |  | ||||||
|                                int port, |  | ||||||
|                                const std::string& prefix, |  | ||||||
|                                bool verbose) |  | ||||||
|         : _host(host) |  | ||||||
|         , _port(port) |  | ||||||
|         , _prefix(prefix) |  | ||||||
|         , _stop(false) |  | ||||||
|         , _verbose(verbose) |  | ||||||
|     { |  | ||||||
|         _thread = std::thread([this] { |  | ||||||
|             setThreadName("Statsd"); |  | ||||||
|  |  | ||||||
|             while (!_stop) |  | ||||||
|             { |  | ||||||
|                 flushQueue(); |  | ||||||
|                 std::this_thread::sleep_for(std::chrono::seconds(1)); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     StatsdClient::~StatsdClient() |  | ||||||
|     { |  | ||||||
|         _stop = true; |  | ||||||
|         if (_thread.joinable()) _thread.join(); |  | ||||||
|  |  | ||||||
|         _socket.close(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool StatsdClient::init(std::string& errMsg) |  | ||||||
|     { |  | ||||||
|         return _socket.init(_host, _port, errMsg); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /* will change the original string */ |  | ||||||
|     void StatsdClient::cleanup(std::string& key) |  | ||||||
|     { |  | ||||||
|         size_t pos = key.find_first_of(":|@"); |  | ||||||
|         while (pos != std::string::npos) |  | ||||||
|         { |  | ||||||
|             key[pos] = '_'; |  | ||||||
|             pos = key.find_first_of(":|@"); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     int StatsdClient::dec(const std::string& key) |  | ||||||
|     { |  | ||||||
|         return count(key, -1); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     int StatsdClient::inc(const std::string& key) |  | ||||||
|     { |  | ||||||
|         return count(key, 1); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     int StatsdClient::count(const std::string& key, size_t value) |  | ||||||
|     { |  | ||||||
|         return send(key, value, "c"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     int StatsdClient::gauge(const std::string& key, size_t value) |  | ||||||
|     { |  | ||||||
|         return send(key, value, "g"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     int StatsdClient::timing(const std::string& key, size_t ms) |  | ||||||
|     { |  | ||||||
|         return send(key, ms, "ms"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     int StatsdClient::send(std::string key, size_t value, const std::string& type) |  | ||||||
|     { |  | ||||||
|         cleanup(key); |  | ||||||
|  |  | ||||||
|         std::stringstream ss; |  | ||||||
|         ss << _prefix << "." << key << ":" << value << "|" << type; |  | ||||||
|  |  | ||||||
|         if (_verbose) |  | ||||||
|         { |  | ||||||
|             CoreLogger::info(ss.str()); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         enqueue(ss.str() + "\n"); |  | ||||||
|         return 0; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void StatsdClient::enqueue(const std::string& message) |  | ||||||
|     { |  | ||||||
|         std::lock_guard<std::mutex> lock(_mutex); |  | ||||||
|         _queue.push_back(message); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void StatsdClient::flushQueue() |  | ||||||
|     { |  | ||||||
|         std::lock_guard<std::mutex> lock(_mutex); |  | ||||||
|  |  | ||||||
|         while (!_queue.empty()) |  | ||||||
|         { |  | ||||||
|             auto message = _queue.front(); |  | ||||||
|             auto ret = _socket.sendto(message); |  | ||||||
|             if (ret == -1) |  | ||||||
|             { |  | ||||||
|                 CoreLogger::error(std::string("statsd error: ") + strerror(UdpSocket::getErrno())); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // we always dequeue regardless of the ability to send the message |  | ||||||
|             // so that we keep our queue size under control |  | ||||||
|             _queue.pop_front(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } // end namespace ix |  | ||||||
| @@ -1,59 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXStatsdClient.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2020 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include <atomic> |  | ||||||
| #include <deque> |  | ||||||
| #include <ixwebsocket/IXUdpSocket.h> |  | ||||||
| #include <mutex> |  | ||||||
| #include <string> |  | ||||||
| #include <thread> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     class StatsdClient |  | ||||||
|     { |  | ||||||
|     public: |  | ||||||
|         StatsdClient(const std::string& host = "127.0.0.1", |  | ||||||
|                      int port = 8125, |  | ||||||
|                      const std::string& prefix = "", |  | ||||||
|                      bool verbose = false); |  | ||||||
|         ~StatsdClient(); |  | ||||||
|  |  | ||||||
|         bool init(std::string& errMsg); |  | ||||||
|         int inc(const std::string& key); |  | ||||||
|         int dec(const std::string& key); |  | ||||||
|         int count(const std::string& key, size_t value); |  | ||||||
|         int gauge(const std::string& key, size_t value); |  | ||||||
|         int timing(const std::string& key, size_t ms); |  | ||||||
|  |  | ||||||
|     private: |  | ||||||
|         void enqueue(const std::string& message); |  | ||||||
|  |  | ||||||
|         /* (Low Level Api) manually send a message |  | ||||||
|          * type = "c", "g" or "ms" |  | ||||||
|          */ |  | ||||||
|         int send(std::string key, size_t value, const std::string& type); |  | ||||||
|  |  | ||||||
|         void cleanup(std::string& key); |  | ||||||
|         void flushQueue(); |  | ||||||
|  |  | ||||||
|         UdpSocket _socket; |  | ||||||
|  |  | ||||||
|         std::string _host; |  | ||||||
|         int _port; |  | ||||||
|         std::string _prefix; |  | ||||||
|  |  | ||||||
|         std::atomic<bool> _stop; |  | ||||||
|         std::thread _thread; |  | ||||||
|         std::mutex _mutex; // for the queue |  | ||||||
|  |  | ||||||
|         std::deque<std::string> _queue; |  | ||||||
|         bool _verbose; |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
| } // end namespace ix |  | ||||||
| @@ -1,37 +0,0 @@ | |||||||
| # |  | ||||||
| # Author: Benjamin Sergeant |  | ||||||
| # Copyright (c) 2019 Machine Zone, Inc. All rights reserved. |  | ||||||
| # |  | ||||||
|  |  | ||||||
| set (IXCOBRA_SOURCES |  | ||||||
|     ixcobra/IXCobraConnection.cpp |  | ||||||
|     ixcobra/IXCobraMetricsThreadedPublisher.cpp |  | ||||||
|     ixcobra/IXCobraMetricsPublisher.cpp |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| set (IXCOBRA_HEADERS |  | ||||||
|     ixcobra/IXCobraConnection.h |  | ||||||
|     ixcobra/IXCobraMetricsThreadedPublisher.h |  | ||||||
|     ixcobra/IXCobraMetricsPublisher.h |  | ||||||
|     ixcobra/IXCobraConfig.h |  | ||||||
|     ixcobra/IXCobraEventType.h |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| add_library(ixcobra STATIC |  | ||||||
|     ${IXCOBRA_SOURCES} |  | ||||||
|     ${IXCOBRA_HEADERS} |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| find_package(JsonCpp) |  | ||||||
| if (NOT JSONCPP_FOUND) |  | ||||||
|   set(JSONCPP_INCLUDE_DIRS ../third_party/jsoncpp) |  | ||||||
| endif() |  | ||||||
|  |  | ||||||
| set(IXCOBRA_INCLUDE_DIRS |  | ||||||
|     . |  | ||||||
|     .. |  | ||||||
|     ../ixcore |  | ||||||
|     ../ixcrypto |  | ||||||
|     ${JSONCPP_INCLUDE_DIRS}) |  | ||||||
|  |  | ||||||
| target_include_directories( ixcobra PUBLIC ${IXCOBRA_INCLUDE_DIRS} ) |  | ||||||
| @@ -1,37 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraConfig.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2020 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include <ixwebsocket/IXSocketTLSOptions.h> |  | ||||||
| #include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h> |  | ||||||
| #include <ixwebsocket/IXWebSocketHttpHeaders.h> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     struct CobraConfig |  | ||||||
|     { |  | ||||||
|         std::string appkey; |  | ||||||
|         std::string endpoint; |  | ||||||
|         std::string rolename; |  | ||||||
|         std::string rolesecret; |  | ||||||
|         WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions; |  | ||||||
|         SocketTLSOptions socketTLSOptions; |  | ||||||
|         WebSocketHttpHeaders headers; |  | ||||||
|  |  | ||||||
|         CobraConfig(const std::string& a = std::string(), |  | ||||||
|                     const std::string& e = std::string(), |  | ||||||
|                     const std::string& r = std::string(), |  | ||||||
|                     const std::string& s = std::string()) |  | ||||||
|             : appkey(a) |  | ||||||
|             , endpoint(e) |  | ||||||
|             , rolename(r) |  | ||||||
|             , rolesecret(s) |  | ||||||
|         { |  | ||||||
|             ; |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,713 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraConnection.cpp |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2017-2018 Machine Zone. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXCobraConnection.h" |  | ||||||
|  |  | ||||||
| #include <algorithm> |  | ||||||
| #include <cassert> |  | ||||||
| #include <cmath> |  | ||||||
| #include <cstring> |  | ||||||
| #include <iostream> |  | ||||||
| #include <ixcrypto/IXHMac.h> |  | ||||||
| #include <ixwebsocket/IXSocketTLSOptions.h> |  | ||||||
| #include <ixwebsocket/IXWebSocket.h> |  | ||||||
| #include <ixwebsocket/IXUniquePtr.h> |  | ||||||
| #include <sstream> |  | ||||||
| #include <stdexcept> |  | ||||||
|  |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     TrafficTrackerCallback CobraConnection::_trafficTrackerCallback = nullptr; |  | ||||||
|     PublishTrackerCallback CobraConnection::_publishTrackerCallback = nullptr; |  | ||||||
|     constexpr size_t CobraConnection::kQueueMaxSize; |  | ||||||
|     constexpr CobraConnection::MsgId CobraConnection::kInvalidMsgId; |  | ||||||
|     constexpr int CobraConnection::kPingIntervalSecs; |  | ||||||
|  |  | ||||||
|     CobraConnection::CobraConnection() |  | ||||||
|         : _webSocket(new WebSocket()) |  | ||||||
|         , _publishMode(CobraConnection_PublishMode_Immediate) |  | ||||||
|         , _authenticated(false) |  | ||||||
|         , _eventCallback(nullptr) |  | ||||||
|         , _id(1) |  | ||||||
|     { |  | ||||||
|         _pdu["action"] = "rtm/publish"; |  | ||||||
|  |  | ||||||
|         _webSocket->addSubProtocol("json"); |  | ||||||
|         initWebSocketOnMessageCallback(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     CobraConnection::~CobraConnection() |  | ||||||
|     { |  | ||||||
|         disconnect(); |  | ||||||
|         setEventCallback(nullptr); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraConnection::setTrafficTrackerCallback(const TrafficTrackerCallback& callback) |  | ||||||
|     { |  | ||||||
|         _trafficTrackerCallback = callback; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraConnection::resetTrafficTrackerCallback() |  | ||||||
|     { |  | ||||||
|         setTrafficTrackerCallback(nullptr); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraConnection::invokeTrafficTrackerCallback(size_t size, bool incoming) |  | ||||||
|     { |  | ||||||
|         if (_trafficTrackerCallback) |  | ||||||
|         { |  | ||||||
|             _trafficTrackerCallback(size, incoming); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraConnection::setPublishTrackerCallback(const PublishTrackerCallback& callback) |  | ||||||
|     { |  | ||||||
|         _publishTrackerCallback = callback; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraConnection::resetPublishTrackerCallback() |  | ||||||
|     { |  | ||||||
|         setPublishTrackerCallback(nullptr); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraConnection::invokePublishTrackerCallback(bool sent, bool acked) |  | ||||||
|     { |  | ||||||
|         if (_publishTrackerCallback) |  | ||||||
|         { |  | ||||||
|             _publishTrackerCallback(sent, acked); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraConnection::setEventCallback(const EventCallback& eventCallback) |  | ||||||
|     { |  | ||||||
|         std::lock_guard<std::mutex> lock(_eventCallbackMutex); |  | ||||||
|         _eventCallback = eventCallback; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraConnection::invokeEventCallback(ix::CobraEventType eventType, |  | ||||||
|                                               const std::string& errorMsg, |  | ||||||
|                                               const WebSocketHttpHeaders& headers, |  | ||||||
|                                               const std::string& subscriptionId, |  | ||||||
|                                               CobraConnection::MsgId msgId, |  | ||||||
|                                               const std::string& connectionId) |  | ||||||
|     { |  | ||||||
|         std::lock_guard<std::mutex> lock(_eventCallbackMutex); |  | ||||||
|         if (_eventCallback) |  | ||||||
|         { |  | ||||||
|             _eventCallback( |  | ||||||
|                 ix::make_unique<CobraEvent>(eventType, errorMsg, headers, subscriptionId, msgId, connectionId)); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraConnection::invokeErrorCallback(const std::string& errorMsg, |  | ||||||
|                                               const std::string& serializedPdu) |  | ||||||
|     { |  | ||||||
|         std::stringstream ss; |  | ||||||
|         ss << errorMsg << " : received pdu => " << serializedPdu; |  | ||||||
|         invokeEventCallback(ix::CobraEventType::Error, ss.str()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraConnection::disconnect() |  | ||||||
|     { |  | ||||||
|         auto subscriptionIds = getSubscriptionsIds(); |  | ||||||
|         for (auto&& subscriptionId : subscriptionIds) |  | ||||||
|         { |  | ||||||
|             unsubscribe(subscriptionId); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         _authenticated = false; |  | ||||||
|         _webSocket->stop(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraConnection::initWebSocketOnMessageCallback() |  | ||||||
|     { |  | ||||||
|         _webSocket->setOnMessageCallback([this](const ix::WebSocketMessagePtr& msg) { |  | ||||||
|             CobraConnection::invokeTrafficTrackerCallback(msg->wireSize, true); |  | ||||||
|  |  | ||||||
|             std::stringstream ss; |  | ||||||
|             if (msg->type == ix::WebSocketMessageType::Open) |  | ||||||
|             { |  | ||||||
|                 invokeEventCallback(ix::CobraEventType::Open, std::string(), msg->openInfo.headers); |  | ||||||
|                 sendHandshakeMessage(); |  | ||||||
|             } |  | ||||||
|             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::CobraEventType::Closed, ss.str()); |  | ||||||
|             } |  | ||||||
|             else if (msg->type == ix::WebSocketMessageType::Message) |  | ||||||
|             { |  | ||||||
|                 Json::Value data; |  | ||||||
|                 Json::Reader reader; |  | ||||||
|                 if (!reader.parse(msg->str, 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::CobraEventType::HandshakeError, msg->str); |  | ||||||
|                 } |  | ||||||
|                 else if (action == "auth/authenticate/ok") |  | ||||||
|                 { |  | ||||||
|                     _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); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraConnection::setPublishMode(CobraConnectionPublishMode publishMode) |  | ||||||
|     { |  | ||||||
|         _publishMode = publishMode; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     CobraConnectionPublishMode CobraConnection::getPublishMode() |  | ||||||
|     { |  | ||||||
|         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, |  | ||||||
|         const WebSocketHttpHeaders& headers) |  | ||||||
|     { |  | ||||||
|         _roleName = rolename; |  | ||||||
|         _roleSecret = rolesecret; |  | ||||||
|  |  | ||||||
|         std::stringstream ss; |  | ||||||
|         ss << endpoint; |  | ||||||
|         ss << "/v2?appkey="; |  | ||||||
|         ss << appkey; |  | ||||||
|  |  | ||||||
|         std::string url = ss.str(); |  | ||||||
|         _webSocket->setUrl(url); |  | ||||||
|         _webSocket->setPerMessageDeflateOptions(webSocketPerMessageDeflateOptions); |  | ||||||
|         _webSocket->setTLSOptions(socketTLSOptions); |  | ||||||
|         _webSocket->setExtraHeaders(headers); |  | ||||||
|  |  | ||||||
|         // Send a websocket ping every N seconds (N = 30) now |  | ||||||
|         // This should keep the connection open and prevent some load balancers such as |  | ||||||
|         // the Amazon one from shutting it down |  | ||||||
|         _webSocket->setPingInterval(kPingIntervalSecs); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraConnection::configure(const ix::CobraConfig& config) |  | ||||||
|     { |  | ||||||
|         configure(config.appkey, |  | ||||||
|                   config.endpoint, |  | ||||||
|                   config.rolename, |  | ||||||
|                   config.rolesecret, |  | ||||||
|                   config.webSocketPerMessageDeflateOptions, |  | ||||||
|                   config.socketTLSOptions, |  | ||||||
|                   config.headers); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // |  | ||||||
|     // Handshake message schema. |  | ||||||
|     // |  | ||||||
|     // handshake = { |  | ||||||
|     //     "action": "auth/handshake", |  | ||||||
|     //     "body": { |  | ||||||
|     //         "data": { |  | ||||||
|     //             "role": role |  | ||||||
|     //         }, |  | ||||||
|     //         "method": "role_secret" |  | ||||||
|     //     }, |  | ||||||
|     // } |  | ||||||
|     // |  | ||||||
|     // |  | ||||||
|     bool CobraConnection::sendHandshakeMessage() |  | ||||||
|     { |  | ||||||
|         Json::Value data; |  | ||||||
|         data["role"] = _roleName; |  | ||||||
|  |  | ||||||
|         Json::Value body; |  | ||||||
|         body["data"] = data; |  | ||||||
|         body["method"] = "role_secret"; |  | ||||||
|  |  | ||||||
|         Json::Value pdu; |  | ||||||
|         pdu["action"] = "auth/handshake"; |  | ||||||
|         pdu["body"] = body; |  | ||||||
|         pdu["id"] = Json::UInt64(_id++); |  | ||||||
|  |  | ||||||
|         std::string serializedJson = serializeJson(pdu); |  | ||||||
|         CobraConnection::invokeTrafficTrackerCallback(serializedJson.size(), false); |  | ||||||
|  |  | ||||||
|         return _webSocket->send(serializedJson).success; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // |  | ||||||
|     // Extract the nonce from the handshake response |  | ||||||
|     // use it to compute a hash during authentication |  | ||||||
|     // |  | ||||||
|     // { |  | ||||||
|     //     "action": "auth/handshake/ok", |  | ||||||
|     //     "body": { |  | ||||||
|     //         "data": { |  | ||||||
|     //             "nonce": "MTI0Njg4NTAyMjYxMzgxMzgzMg==", |  | ||||||
|     //             "version": "0.0.24" |  | ||||||
|     //         } |  | ||||||
|     //     } |  | ||||||
|     // } |  | ||||||
|     // |  | ||||||
|     bool CobraConnection::handleHandshakeResponse(const Json::Value& pdu) |  | ||||||
|     { |  | ||||||
|         if (!pdu.isObject()) return false; |  | ||||||
|  |  | ||||||
|         if (!pdu.isMember("body")) return false; |  | ||||||
|         Json::Value body = pdu["body"]; |  | ||||||
|  |  | ||||||
|         if (!body.isMember("data")) return false; |  | ||||||
|         Json::Value data = body["data"]; |  | ||||||
|  |  | ||||||
|         if (!data.isMember("nonce")) return false; |  | ||||||
|         Json::Value nonce = data["nonce"]; |  | ||||||
|  |  | ||||||
|         if (!nonce.isString()) return false; |  | ||||||
|  |  | ||||||
|         if (!data.isMember("connection_id")) return false; |  | ||||||
|         Json::Value connectionId = data["connection_id"]; |  | ||||||
|  |  | ||||||
|         if (!connectionId.isString()) return false; |  | ||||||
|  |  | ||||||
|         invokeEventCallback(ix::CobraEventType::Handshake, |  | ||||||
|                             std::string(), |  | ||||||
|                             WebSocketHttpHeaders(), |  | ||||||
|                             std::string(), |  | ||||||
|                             0, |  | ||||||
|                             connectionId.asString()); |  | ||||||
|  |  | ||||||
|         return sendAuthMessage(nonce.asString()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // |  | ||||||
|     // Authenticate message schema. |  | ||||||
|     // |  | ||||||
|     // challenge = { |  | ||||||
|     //     "action": "auth/authenticate", |  | ||||||
|     //     "body": { |  | ||||||
|     //         "method": "role_secret", |  | ||||||
|     //         "credentials": { |  | ||||||
|     //             "hash": computeHash(secret, nonce) |  | ||||||
|     //         } |  | ||||||
|     //     }, |  | ||||||
|     // } |  | ||||||
|     // |  | ||||||
|     bool CobraConnection::sendAuthMessage(const std::string& nonce) |  | ||||||
|     { |  | ||||||
|         Json::Value credentials; |  | ||||||
|         credentials["hash"] = hmac(nonce, _roleSecret); |  | ||||||
|  |  | ||||||
|         Json::Value body; |  | ||||||
|         body["credentials"] = credentials; |  | ||||||
|         body["method"] = "role_secret"; |  | ||||||
|  |  | ||||||
|         Json::Value pdu; |  | ||||||
|         pdu["action"] = "auth/authenticate"; |  | ||||||
|         pdu["body"] = body; |  | ||||||
|         pdu["id"] = Json::UInt64(_id++); |  | ||||||
|  |  | ||||||
|         std::string serializedJson = serializeJson(pdu); |  | ||||||
|         CobraConnection::invokeTrafficTrackerCallback(serializedJson.size(), false); |  | ||||||
|  |  | ||||||
|         return _webSocket->send(serializedJson).success; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool CobraConnection::handleSubscriptionResponse(const Json::Value& pdu) |  | ||||||
|     { |  | ||||||
|         if (!pdu.isObject()) return false; |  | ||||||
|  |  | ||||||
|         if (!pdu.isMember("body")) return false; |  | ||||||
|         Json::Value body = pdu["body"]; |  | ||||||
|  |  | ||||||
|         if (!body.isMember("subscription_id")) return false; |  | ||||||
|         Json::Value subscriptionId = body["subscription_id"]; |  | ||||||
|  |  | ||||||
|         if (!subscriptionId.isString()) return false; |  | ||||||
|  |  | ||||||
|         invokeEventCallback(ix::CobraEventType::Subscribed, |  | ||||||
|                             std::string(), |  | ||||||
|                             WebSocketHttpHeaders(), |  | ||||||
|                             subscriptionId.asString()); |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool CobraConnection::handleUnsubscriptionResponse(const Json::Value& pdu) |  | ||||||
|     { |  | ||||||
|         if (!pdu.isObject()) return false; |  | ||||||
|  |  | ||||||
|         if (!pdu.isMember("body")) return false; |  | ||||||
|         Json::Value body = pdu["body"]; |  | ||||||
|  |  | ||||||
|         if (!body.isMember("subscription_id")) return false; |  | ||||||
|         Json::Value subscriptionId = body["subscription_id"]; |  | ||||||
|  |  | ||||||
|         if (!subscriptionId.isString()) return false; |  | ||||||
|  |  | ||||||
|         invokeEventCallback(ix::CobraEventType::UnSubscribed, |  | ||||||
|                             std::string(), |  | ||||||
|                             WebSocketHttpHeaders(), |  | ||||||
|                             subscriptionId.asString()); |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool CobraConnection::handleSubscriptionData(const Json::Value& pdu) |  | ||||||
|     { |  | ||||||
|         if (!pdu.isObject()) return false; |  | ||||||
|  |  | ||||||
|         if (!pdu.isMember("body")) return false; |  | ||||||
|         Json::Value body = pdu["body"]; |  | ||||||
|  |  | ||||||
|         // Identify subscription_id, so that we can find |  | ||||||
|         // which callback to execute |  | ||||||
|         if (!body.isMember("subscription_id")) return false; |  | ||||||
|         Json::Value subscriptionId = body["subscription_id"]; |  | ||||||
|  |  | ||||||
|         std::lock_guard<std::mutex> lock(_cbsMutex); |  | ||||||
|         auto cb = _cbs.find(subscriptionId.asString()); |  | ||||||
|         if (cb == _cbs.end()) return false; // cannot find callback |  | ||||||
|  |  | ||||||
|         // Extract messages now |  | ||||||
|         if (!body.isMember("messages")) return false; |  | ||||||
|         Json::Value messages = body["messages"]; |  | ||||||
|  |  | ||||||
|         if (!body.isMember("position")) return false; |  | ||||||
|         std::string position = body["position"].asString(); |  | ||||||
|  |  | ||||||
|         for (auto&& msg : messages) |  | ||||||
|         { |  | ||||||
|             cb->second(msg, position); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool CobraConnection::handlePublishResponse(const Json::Value& pdu) |  | ||||||
|     { |  | ||||||
|         if (!pdu.isObject()) return false; |  | ||||||
|  |  | ||||||
|         if (!pdu.isMember("id")) return false; |  | ||||||
|         Json::Value id = pdu["id"]; |  | ||||||
|  |  | ||||||
|         if (!id.isUInt64()) return false; |  | ||||||
|  |  | ||||||
|         uint64_t msgId = id.asUInt64(); |  | ||||||
|  |  | ||||||
|         invokeEventCallback(ix::CobraEventType::Published, |  | ||||||
|                             std::string(), |  | ||||||
|                             WebSocketHttpHeaders(), |  | ||||||
|                             std::string(), |  | ||||||
|                             msgId); |  | ||||||
|  |  | ||||||
|         invokePublishTrackerCallback(false, true); |  | ||||||
|  |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool CobraConnection::connect() |  | ||||||
|     { |  | ||||||
|         _webSocket->start(); |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool CobraConnection::isConnected() const |  | ||||||
|     { |  | ||||||
|         return _webSocket->getReadyState() == ix::ReadyState::Open; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool CobraConnection::isAuthenticated() const |  | ||||||
|     { |  | ||||||
|         return isConnected() && _authenticated; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::string CobraConnection::serializeJson(const Json::Value& value) |  | ||||||
|     { |  | ||||||
|         std::lock_guard<std::mutex> lock(_jsonWriterMutex); |  | ||||||
|         return _jsonWriter.write(value); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::pair<CobraConnection::MsgId, std::string> CobraConnection::prePublish( |  | ||||||
|         const Json::Value& channels, const Json::Value& msg, bool addToQueue) |  | ||||||
|     { |  | ||||||
|         std::lock_guard<std::mutex> lock(_prePublishMutex); |  | ||||||
|  |  | ||||||
|         invokePublishTrackerCallback(true, false); |  | ||||||
|  |  | ||||||
|         CobraConnection::MsgId msgId = _id; |  | ||||||
|  |  | ||||||
|         _body["channels"] = channels; |  | ||||||
|         _body["message"] = msg; |  | ||||||
|         _pdu["body"] = _body; |  | ||||||
|         _pdu["id"] = Json::UInt64(_id++); |  | ||||||
|  |  | ||||||
|         std::string serializedJson = serializeJson(_pdu); |  | ||||||
|  |  | ||||||
|         if (addToQueue) |  | ||||||
|         { |  | ||||||
|             enqueue(serializedJson); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return std::make_pair(msgId, serializedJson); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool CobraConnection::publishNext() |  | ||||||
|     { |  | ||||||
|         std::lock_guard<std::mutex> lock(_queueMutex); |  | ||||||
|  |  | ||||||
|         if (_messageQueue.empty()) return true; |  | ||||||
|  |  | ||||||
|         auto&& msg = _messageQueue.back(); |  | ||||||
|         if (!_authenticated || !publishMessage(msg)) |  | ||||||
|         { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|         _messageQueue.pop_back(); |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // |  | ||||||
|     // publish is not thread safe as we are trying to reuse some Json objects. |  | ||||||
|     // |  | ||||||
|     CobraConnection::MsgId CobraConnection::publish(const Json::Value& channels, |  | ||||||
|                                                     const Json::Value& msg) |  | ||||||
|     { |  | ||||||
|         auto p = prePublish(channels, msg, false); |  | ||||||
|         auto msgId = p.first; |  | ||||||
|         auto serializedJson = p.second; |  | ||||||
|  |  | ||||||
|         // |  | ||||||
|         // 1. When we use batch mode, we just enqueue and will do the flush explicitely |  | ||||||
|         // 2. When we aren't authenticated yet to the cobra server, we need to enqueue |  | ||||||
|         //    and retry later |  | ||||||
|         // 3. If the network connection was droped (WebSocket::send will return false), |  | ||||||
|         //    it means the message won't be sent so we need to enqueue as well. |  | ||||||
|         // |  | ||||||
|         // The order of the conditionals is important. |  | ||||||
|         // |  | ||||||
|         if (_publishMode == CobraConnection_PublishMode_Batch || !_authenticated || |  | ||||||
|             !publishMessage(serializedJson)) |  | ||||||
|         { |  | ||||||
|             enqueue(serializedJson); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return msgId; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraConnection::subscribe(const std::string& channel, |  | ||||||
|                                     const std::string& filter, |  | ||||||
|                                     const std::string& position, |  | ||||||
|                                     int batchSize, |  | ||||||
|                                     SubscriptionCallback cb) |  | ||||||
|     { |  | ||||||
|         // Create and send a subscribe pdu |  | ||||||
|         Json::Value body; |  | ||||||
|         body["channel"] = channel; |  | ||||||
|         body["batch_size"] = batchSize; |  | ||||||
|  |  | ||||||
|         if (!filter.empty()) |  | ||||||
|         { |  | ||||||
|             body["filter"] = filter; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (!position.empty()) |  | ||||||
|         { |  | ||||||
|             body["position"] = position; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Json::Value pdu; |  | ||||||
|         pdu["action"] = "rtm/subscribe"; |  | ||||||
|         pdu["body"] = body; |  | ||||||
|         pdu["id"] = Json::UInt64(_id++); |  | ||||||
|  |  | ||||||
|         _webSocket->send(pdu.toStyledString()); |  | ||||||
|  |  | ||||||
|         // Set the callback |  | ||||||
|         std::lock_guard<std::mutex> lock(_cbsMutex); |  | ||||||
|         _cbs[channel] = cb; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraConnection::unsubscribe(const std::string& channel) |  | ||||||
|     { |  | ||||||
|         { |  | ||||||
|             std::lock_guard<std::mutex> lock(_cbsMutex); |  | ||||||
|             auto cb = _cbs.find(channel); |  | ||||||
|             if (cb == _cbs.end()) return; |  | ||||||
|  |  | ||||||
|             _cbs.erase(cb); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Create and send an unsubscribe pdu |  | ||||||
|         Json::Value body; |  | ||||||
|         body["subscription_id"] = channel; |  | ||||||
|  |  | ||||||
|         Json::Value pdu; |  | ||||||
|         pdu["action"] = "rtm/unsubscribe"; |  | ||||||
|         pdu["body"] = body; |  | ||||||
|         pdu["id"] = Json::UInt64(_id++); |  | ||||||
|  |  | ||||||
|         _webSocket->send(pdu.toStyledString()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::vector<std::string> CobraConnection::getSubscriptionsIds() |  | ||||||
|     { |  | ||||||
|         std::vector<std::string> subscriptionIds; |  | ||||||
|         std::lock_guard<std::mutex> lock(_cbsMutex); |  | ||||||
|  |  | ||||||
|         for (auto&& it : _cbs) |  | ||||||
|         { |  | ||||||
|             subscriptionIds.push_back(it.first); |  | ||||||
|         } |  | ||||||
|         return subscriptionIds; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // |  | ||||||
|     // Enqueue strategy drops old messages when we are at full capacity |  | ||||||
|     // |  | ||||||
|     // If we want to keep only 3 items max in the queue: |  | ||||||
|     // |  | ||||||
|     // enqueue(A) -> [A] |  | ||||||
|     // enqueue(B) -> [B, A] |  | ||||||
|     // enqueue(C) -> [C, B, A] |  | ||||||
|     // enqueue(D) -> [D, C, B] -- now we drop A, the oldest message, |  | ||||||
|     //                         -- and keep the 'fresh ones' |  | ||||||
|     // |  | ||||||
|     void CobraConnection::enqueue(const std::string& msg) |  | ||||||
|     { |  | ||||||
|         std::lock_guard<std::mutex> lock(_queueMutex); |  | ||||||
|  |  | ||||||
|         if (_messageQueue.size() == CobraConnection::kQueueMaxSize) |  | ||||||
|         { |  | ||||||
|             _messageQueue.pop_back(); |  | ||||||
|         } |  | ||||||
|         _messageQueue.push_front(msg); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // |  | ||||||
|     // We process messages back (oldest) to front (newest) to respect ordering |  | ||||||
|     // when sending them. If we fail to send something, we put it back in the queue |  | ||||||
|     // at the end we picked it up originally (at the end). |  | ||||||
|     // |  | ||||||
|     bool CobraConnection::flushQueue() |  | ||||||
|     { |  | ||||||
|         while (!isQueueEmpty()) |  | ||||||
|         { |  | ||||||
|             bool ok = publishNext(); |  | ||||||
|             if (!ok) return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool CobraConnection::isQueueEmpty() |  | ||||||
|     { |  | ||||||
|         std::lock_guard<std::mutex> lock(_queueMutex); |  | ||||||
|         return _messageQueue.empty(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool CobraConnection::publishMessage(const std::string& serializedJson) |  | ||||||
|     { |  | ||||||
|         auto webSocketSendInfo = _webSocket->send(serializedJson); |  | ||||||
|         CobraConnection::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false); |  | ||||||
|         return webSocketSendInfo.success; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraConnection::suspend() |  | ||||||
|     { |  | ||||||
|         disconnect(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraConnection::resume() |  | ||||||
|     { |  | ||||||
|         connect(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,224 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraConnection.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2017-2018 Machine Zone. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #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> |  | ||||||
|  |  | ||||||
| #ifdef max |  | ||||||
| #undef max |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     class WebSocket; |  | ||||||
|     struct SocketTLSOptions; |  | ||||||
|  |  | ||||||
|     enum CobraConnectionPublishMode |  | ||||||
|     { |  | ||||||
|         CobraConnection_PublishMode_Immediate = 0, |  | ||||||
|         CobraConnection_PublishMode_Batch = 1 |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     using SubscriptionCallback = std::function<void(const Json::Value&, const std::string&)>; |  | ||||||
|     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)>; |  | ||||||
|  |  | ||||||
|     class CobraConnection |  | ||||||
|     { |  | ||||||
|     public: |  | ||||||
|         using MsgId = uint64_t; |  | ||||||
|  |  | ||||||
|         CobraConnection(); |  | ||||||
|         ~CobraConnection(); |  | ||||||
|  |  | ||||||
|         /// Configuration / set keys, etc... |  | ||||||
|         /// All input data but the channel name is encrypted with rc4 |  | ||||||
|         void configure(const std::string& appkey, |  | ||||||
|                        const std::string& endpoint, |  | ||||||
|                        const std::string& rolename, |  | ||||||
|                        const std::string& rolesecret, |  | ||||||
|                        const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions, |  | ||||||
|                        const SocketTLSOptions& socketTLSOptions, |  | ||||||
|                        const WebSocketHttpHeaders& headers); |  | ||||||
|  |  | ||||||
|         void configure(const ix::CobraConfig& config); |  | ||||||
|  |  | ||||||
|         /// Set the traffic tracker callback |  | ||||||
|         static void setTrafficTrackerCallback(const TrafficTrackerCallback& callback); |  | ||||||
|  |  | ||||||
|         /// Reset the traffic tracker callback to an no-op one. |  | ||||||
|         static void resetTrafficTrackerCallback(); |  | ||||||
|  |  | ||||||
|         /// Set the publish tracker callback |  | ||||||
|         static void setPublishTrackerCallback(const PublishTrackerCallback& callback); |  | ||||||
|  |  | ||||||
|         /// Reset the publish tracker callback to an no-op one. |  | ||||||
|         static void resetPublishTrackerCallback(); |  | ||||||
|  |  | ||||||
|         /// Set the closed callback |  | ||||||
|         void setEventCallback(const EventCallback& eventCallback); |  | ||||||
|  |  | ||||||
|         /// Start the worker thread, used for background publishing |  | ||||||
|         void start(); |  | ||||||
|  |  | ||||||
|         /// Publish a message to a channel |  | ||||||
|         /// |  | ||||||
|         /// No-op if the connection is not established |  | ||||||
|         MsgId publish(const Json::Value& channels, const Json::Value& msg); |  | ||||||
|  |  | ||||||
|         // Subscribe to a channel, and execute a callback when an incoming |  | ||||||
|         // message arrives. |  | ||||||
|         void subscribe(const std::string& channel, |  | ||||||
|                        const std::string& filter = std::string(), |  | ||||||
|                        const std::string& position = std::string(), |  | ||||||
|                        int batchSize = 1, |  | ||||||
|                        SubscriptionCallback cb = nullptr); |  | ||||||
|  |  | ||||||
|         /// Unsubscribe from a channel |  | ||||||
|         void unsubscribe(const std::string& channel); |  | ||||||
|  |  | ||||||
|         /// Close the connection |  | ||||||
|         void disconnect(); |  | ||||||
|  |  | ||||||
|         /// Connect to Cobra and authenticate the connection |  | ||||||
|         bool connect(); |  | ||||||
|  |  | ||||||
|         /// Returns true only if we're connected |  | ||||||
|         bool isConnected() const; |  | ||||||
|  |  | ||||||
|         /// Returns true only if we're authenticated |  | ||||||
|         bool isAuthenticated() const; |  | ||||||
|  |  | ||||||
|         /// Flush the publish queue |  | ||||||
|         bool flushQueue(); |  | ||||||
|  |  | ||||||
|         /// Set the publish mode |  | ||||||
|         void setPublishMode(CobraConnectionPublishMode publishMode); |  | ||||||
|  |  | ||||||
|         /// Query the publish mode |  | ||||||
|         CobraConnectionPublishMode getPublishMode(); |  | ||||||
|  |  | ||||||
|         /// Lifecycle management. Free resources when backgrounding |  | ||||||
|         void suspend(); |  | ||||||
|         void resume(); |  | ||||||
|  |  | ||||||
|         /// 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); |  | ||||||
|  |  | ||||||
|         /// Attempt to send next message from the internal queue |  | ||||||
|         bool publishNext(); |  | ||||||
|  |  | ||||||
|         // An invalid message id, signifying an error. |  | ||||||
|         static constexpr MsgId kInvalidMsgId = 0; |  | ||||||
|  |  | ||||||
|     private: |  | ||||||
|         bool sendHandshakeMessage(); |  | ||||||
|         bool handleHandshakeResponse(const Json::Value& data); |  | ||||||
|         bool sendAuthMessage(const std::string& nonce); |  | ||||||
|         bool handleSubscriptionData(const Json::Value& pdu); |  | ||||||
|         bool handleSubscriptionResponse(const Json::Value& pdu); |  | ||||||
|         bool handleUnsubscriptionResponse(const Json::Value& pdu); |  | ||||||
|         bool handlePublishResponse(const Json::Value& pdu); |  | ||||||
|  |  | ||||||
|         void initWebSocketOnMessageCallback(); |  | ||||||
|  |  | ||||||
|         bool publishMessage(const std::string& serializedJson); |  | ||||||
|         void enqueue(const std::string& msg); |  | ||||||
|         std::string serializeJson(const Json::Value& pdu); |  | ||||||
|  |  | ||||||
|         /// Invoke the traffic tracker callback |  | ||||||
|         static void invokeTrafficTrackerCallback(size_t size, bool incoming); |  | ||||||
|  |  | ||||||
|         /// Invoke the publish tracker callback |  | ||||||
|         static void invokePublishTrackerCallback(bool sent, bool acked); |  | ||||||
|  |  | ||||||
|         /// Invoke event callbacks |  | ||||||
|         void invokeEventCallback(CobraEventType eventType, |  | ||||||
|                                  const std::string& errorMsg = std::string(), |  | ||||||
|                                  const WebSocketHttpHeaders& headers = WebSocketHttpHeaders(), |  | ||||||
|                                  const std::string& subscriptionId = std::string(), |  | ||||||
|                                  uint64_t msgId = std::numeric_limits<uint64_t>::max(), |  | ||||||
|                                  const std::string& connectionId = std::string()); |  | ||||||
|  |  | ||||||
|         void invokeErrorCallback(const std::string& errorMsg, const std::string& serializedPdu); |  | ||||||
|  |  | ||||||
|         /// Tells whether the internal queue is empty or not |  | ||||||
|         bool isQueueEmpty(); |  | ||||||
|  |  | ||||||
|         /// Retrieve all subscriptions ids |  | ||||||
|         std::vector<std::string> getSubscriptionsIds(); |  | ||||||
|  |  | ||||||
|         /// |  | ||||||
|         /// Member variables |  | ||||||
|         /// |  | ||||||
|         std::unique_ptr<WebSocket> _webSocket; |  | ||||||
|  |  | ||||||
|         /// Configuration data |  | ||||||
|         std::string _roleName; |  | ||||||
|         std::string _roleSecret; |  | ||||||
|         std::atomic<CobraConnectionPublishMode> _publishMode; |  | ||||||
|  |  | ||||||
|         // Can be set on control+background thread, protecting with an atomic |  | ||||||
|         std::atomic<bool> _authenticated; |  | ||||||
|  |  | ||||||
|         // Keep some objects around |  | ||||||
|         Json::Value _body; |  | ||||||
|         Json::Value _pdu; |  | ||||||
|         Json::FastWriter _jsonWriter; |  | ||||||
|         mutable std::mutex _jsonWriterMutex; |  | ||||||
|         std::mutex _prePublishMutex; |  | ||||||
|  |  | ||||||
|         /// Traffic tracker callback |  | ||||||
|         static TrafficTrackerCallback _trafficTrackerCallback; |  | ||||||
|  |  | ||||||
|         /// Publish tracker callback |  | ||||||
|         static PublishTrackerCallback _publishTrackerCallback; |  | ||||||
|  |  | ||||||
|         /// Cobra events callbacks |  | ||||||
|         EventCallback _eventCallback; |  | ||||||
|         mutable std::mutex _eventCallbackMutex; |  | ||||||
|  |  | ||||||
|         /// Subscription callbacks, only one per channel |  | ||||||
|         std::unordered_map<std::string, SubscriptionCallback> _cbs; |  | ||||||
|         mutable std::mutex _cbsMutex; |  | ||||||
|  |  | ||||||
|         // Message Queue can be touched on control+background thread, |  | ||||||
|         // protecting with a mutex. |  | ||||||
|         // |  | ||||||
|         // Message queue is used when there are problems sending messages so |  | ||||||
|         // that sending can be retried later. |  | ||||||
|         std::deque<std::string> _messageQueue; |  | ||||||
|         mutable std::mutex _queueMutex; |  | ||||||
|  |  | ||||||
|         // Cap the queue size (100 elems so far -> ~100k) |  | ||||||
|         static constexpr size_t kQueueMaxSize = 256; |  | ||||||
|  |  | ||||||
|         // Each pdu sent should have an incremental unique id |  | ||||||
|         std::atomic<uint64_t> _id; |  | ||||||
|  |  | ||||||
|         // Frequency at which we send a websocket ping to the backing cobra connection |  | ||||||
|         static constexpr int kPingIntervalSecs = 30; |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,44 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  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 |  | ||||||
|         const std::string& connectionId; |  | ||||||
|  |  | ||||||
|         CobraEvent(ix::CobraEventType t, |  | ||||||
|                    const std::string& e, |  | ||||||
|                    const ix::WebSocketHttpHeaders& h, |  | ||||||
|                    const std::string& s, |  | ||||||
|                    uint64_t m, |  | ||||||
|                    const std::string& c) |  | ||||||
|             : type(t) |  | ||||||
|             , errMsg(e) |  | ||||||
|             , headers(h) |  | ||||||
|             , subscriptionId(s) |  | ||||||
|             , msgId(m) |  | ||||||
|             , connectionId(c) |  | ||||||
|         { |  | ||||||
|             ; |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     using CobraEventPtr = std::unique_ptr<CobraEvent>; |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,26 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  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, |  | ||||||
|         Handshake = 11 |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| @@ -1,232 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraMetricsPublisher.cpp |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2017 Machine Zone. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXCobraMetricsPublisher.h" |  | ||||||
|  |  | ||||||
| #include <algorithm> |  | ||||||
| #include <ixwebsocket/IXSocketTLSOptions.h> |  | ||||||
| #include <stdexcept> |  | ||||||
|  |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     const int CobraMetricsPublisher::kVersion = 1; |  | ||||||
|     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() |  | ||||||
|     { |  | ||||||
|         ; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     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); |  | ||||||
|         _cobra_metrics_theaded_publisher.start(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     Json::Value& CobraMetricsPublisher::getGenericAttributes() |  | ||||||
|     { |  | ||||||
|         std::lock_guard<std::mutex> lock(_device_mutex); |  | ||||||
|         return _device; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraMetricsPublisher::setGenericAttributes(const std::string& attrName, |  | ||||||
|                                                      const Json::Value& value) |  | ||||||
|     { |  | ||||||
|         std::lock_guard<std::mutex> lock(_device_mutex); |  | ||||||
|         _device[attrName] = value; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraMetricsPublisher::enable(bool enabled) |  | ||||||
|     { |  | ||||||
|         _enabled = enabled; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraMetricsPublisher::setBlacklist(const std::vector<std::string>& blacklist) |  | ||||||
|     { |  | ||||||
|         _blacklist = blacklist; |  | ||||||
|         std::sort(_blacklist.begin(), _blacklist.end()); |  | ||||||
|  |  | ||||||
|         // publish our blacklist |  | ||||||
|         Json::Value data; |  | ||||||
|         Json::Value metrics; |  | ||||||
|         for (auto&& metric : blacklist) |  | ||||||
|         { |  | ||||||
|             metrics.append(metric); |  | ||||||
|         } |  | ||||||
|         data["blacklist"] = metrics; |  | ||||||
|         push(kSetBlacklistId, data); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool CobraMetricsPublisher::isMetricBlacklisted(const std::string& id) const |  | ||||||
|     { |  | ||||||
|         return std::binary_search(_blacklist.begin(), _blacklist.end(), id); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraMetricsPublisher::setRateControl( |  | ||||||
|         const std::unordered_map<std::string, int>& rate_control) |  | ||||||
|     { |  | ||||||
|         for (auto&& it : rate_control) |  | ||||||
|         { |  | ||||||
|             if (it.second >= 0) |  | ||||||
|             { |  | ||||||
|                 _rate_control[it.first] = it.second; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // publish our rate_control |  | ||||||
|         Json::Value data; |  | ||||||
|         Json::Value metrics; |  | ||||||
|         for (auto&& it : _rate_control) |  | ||||||
|         { |  | ||||||
|             metrics[it.first] = it.second; |  | ||||||
|         } |  | ||||||
|         data["rate_control"] = metrics; |  | ||||||
|         push(kSetRateControlId, data); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool CobraMetricsPublisher::isAboveMaxUpdateRate(const std::string& id) const |  | ||||||
|     { |  | ||||||
|         // Is this metrics rate controlled ? |  | ||||||
|         auto rate_control_it = _rate_control.find(id); |  | ||||||
|         if (rate_control_it == _rate_control.end()) return false; |  | ||||||
|  |  | ||||||
|         // Was this metrics already sent ? |  | ||||||
|         std::lock_guard<std::mutex> lock(_last_update_mutex); |  | ||||||
|         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; |  | ||||||
|  |  | ||||||
|         return timeDeltaFromLastSend < std::chrono::seconds(rate_control_it->second); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraMetricsPublisher::setLastUpdate(const std::string& id) |  | ||||||
|     { |  | ||||||
|         std::lock_guard<std::mutex> lock(_last_update_mutex); |  | ||||||
|         _last_update[id] = std::chrono::steady_clock::now(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     uint64_t CobraMetricsPublisher::getMillisecondsSinceEpoch() const |  | ||||||
|     { |  | ||||||
|         auto now = std::chrono::system_clock::now(); |  | ||||||
|         auto ms = |  | ||||||
|             std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count(); |  | ||||||
|  |  | ||||||
|         return ms; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     CobraConnection::MsgId CobraMetricsPublisher::push(const std::string& id, |  | ||||||
|                                                        const std::string& data, |  | ||||||
|                                                        bool shouldPushTest) |  | ||||||
|     { |  | ||||||
|         if (!_enabled) return CobraConnection::kInvalidMsgId; |  | ||||||
|  |  | ||||||
|         Json::Value root; |  | ||||||
|         Json::Reader reader; |  | ||||||
|         if (!reader.parse(data, root)) return CobraConnection::kInvalidMsgId; |  | ||||||
|  |  | ||||||
|         return push(id, root, shouldPushTest); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     CobraConnection::MsgId CobraMetricsPublisher::push(const std::string& id, |  | ||||||
|                                                        const CobraMetricsPublisher::Message& data) |  | ||||||
|     { |  | ||||||
|         if (!_enabled) return CobraConnection::kInvalidMsgId; |  | ||||||
|  |  | ||||||
|         Json::Value root; |  | ||||||
|         for (auto it : data) |  | ||||||
|         { |  | ||||||
|             root[it.first] = it.second; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return push(id, root); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool CobraMetricsPublisher::shouldPush(const std::string& id) const |  | ||||||
|     { |  | ||||||
|         if (!_enabled) return false; |  | ||||||
|         if (isMetricBlacklisted(id)) return false; |  | ||||||
|         if (isAboveMaxUpdateRate(id)) return false; |  | ||||||
|  |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     CobraConnection::MsgId CobraMetricsPublisher::push(const std::string& id, |  | ||||||
|                                                        const Json::Value& data, |  | ||||||
|                                                        bool shouldPushTest) |  | ||||||
|     { |  | ||||||
|         if (shouldPushTest && !shouldPush(id)) return CobraConnection::kInvalidMsgId; |  | ||||||
|  |  | ||||||
|         setLastUpdate(id); |  | ||||||
|  |  | ||||||
|         Json::Value msg; |  | ||||||
|         msg["id"] = id; |  | ||||||
|         msg["data"] = data; |  | ||||||
|         msg["session"] = _session; |  | ||||||
|         msg["version"] = kVersion; |  | ||||||
|         msg["timestamp"] = Json::UInt64(getMillisecondsSinceEpoch()); |  | ||||||
|  |  | ||||||
|         { |  | ||||||
|             std::lock_guard<std::mutex> lock(_device_mutex); |  | ||||||
|             msg["device"] = _device; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         { |  | ||||||
|             // |  | ||||||
|             // Bump a counter for each id |  | ||||||
|             // This is used to make sure that we are not |  | ||||||
|             // dropping messages, by checking that all the ids is the list of |  | ||||||
|             // all natural numbers until the last value sent (0, 1, 2, ..., N) |  | ||||||
|             // |  | ||||||
|             std::lock_guard<std::mutex> lock(_device_mutex); |  | ||||||
|             auto it = _counters.emplace(id, 0); |  | ||||||
|             msg["per_id_counter"] = it.first->second; |  | ||||||
|             it.first->second += 1; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Now actually enqueue the task |  | ||||||
|         return _cobra_metrics_theaded_publisher.push(msg); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraMetricsPublisher::setPublishMode(CobraConnectionPublishMode publishMode) |  | ||||||
|     { |  | ||||||
|         _cobra_metrics_theaded_publisher.setPublishMode(publishMode); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool CobraMetricsPublisher::flushQueue() |  | ||||||
|     { |  | ||||||
|         return _cobra_metrics_theaded_publisher.flushQueue(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraMetricsPublisher::suspend() |  | ||||||
|     { |  | ||||||
|         _cobra_metrics_theaded_publisher.suspend(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraMetricsPublisher::resume() |  | ||||||
|     { |  | ||||||
|         _cobra_metrics_theaded_publisher.resume(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool CobraMetricsPublisher::isConnected() const |  | ||||||
|     { |  | ||||||
|         return _cobra_metrics_theaded_publisher.isConnected(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool CobraMetricsPublisher::isAuthenticated() const |  | ||||||
|     { |  | ||||||
|         return _cobra_metrics_theaded_publisher.isAuthenticated(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,175 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraMetricsPublisher.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2017 Machine Zone. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "IXCobraMetricsThreadedPublisher.h" |  | ||||||
| #include <atomic> |  | ||||||
| #include <chrono> |  | ||||||
| #include <json/json.h> |  | ||||||
| #include <string> |  | ||||||
| #include <unordered_map> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     struct SocketTLSOptions; |  | ||||||
|  |  | ||||||
|     class CobraMetricsPublisher |  | ||||||
|     { |  | ||||||
|     public: |  | ||||||
|         CobraMetricsPublisher(); |  | ||||||
|         ~CobraMetricsPublisher(); |  | ||||||
|  |  | ||||||
|         /// Thread safety notes: |  | ||||||
|         /// |  | ||||||
|         /// 1. _enabled, _blacklist and _rate_control read/writes are not protected by a mutex |  | ||||||
|         /// to make shouldPush as fast as possible. _enabled default to false. |  | ||||||
|         /// |  | ||||||
|         /// The code that set those is ran only once at init, and |  | ||||||
|         /// the last value to be set is _enabled, which is also the first value checked in |  | ||||||
|         /// shouldPush, so there shouldn't be any race condition. |  | ||||||
|         /// |  | ||||||
|         /// 2. The queue of messages is thread safe, so multiple metrics can be safely pushed on |  | ||||||
|         /// multiple threads |  | ||||||
|         /// |  | ||||||
|         /// 3. Access to _last_update is protected as it needs to be read/write. |  | ||||||
|         /// |  | ||||||
|  |  | ||||||
|         /// Configuration / set keys, etc... |  | ||||||
|         /// All input data but the channel name is encrypted with rc4 |  | ||||||
|         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 |  | ||||||
|         void setBlacklist(const std::vector<std::string>& blacklist); |  | ||||||
|  |  | ||||||
|         /// Set the maximum rate at which a metrics can be sent. Unit is seconds |  | ||||||
|         /// if rate_control = { 'foo_id': 60 }, |  | ||||||
|         /// the foo_id metric cannot be pushed more than once every 60 seconds |  | ||||||
|         void setRateControl(const std::unordered_map<std::string, int>& rate_control); |  | ||||||
|  |  | ||||||
|         /// Configuration / enable/disable |  | ||||||
|         void enable(bool enabled); |  | ||||||
|  |  | ||||||
|         /// Simple interface, list of key value pairs where typeof(key) == typeof(value) == string |  | ||||||
|         typedef std::unordered_map<std::string, std::string> Message; |  | ||||||
|         CobraConnection::MsgId push( |  | ||||||
|             const std::string& id, |  | ||||||
|             const CobraMetricsPublisher::Message& data = CobraMetricsPublisher::Message()); |  | ||||||
|  |  | ||||||
|         /// Richer interface using json, which supports types (bool, int, float) and hierarchies of |  | ||||||
|         /// elements |  | ||||||
|         /// |  | ||||||
|         /// The shouldPushTest argument should be set to false, and used in combination with the |  | ||||||
|         /// 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); |  | ||||||
|  |  | ||||||
|         /// Interface used by lua. msg is a json encoded string. |  | ||||||
|         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: |  | ||||||
|         /// |  | ||||||
|         /// 1. the metrics system should be enabled |  | ||||||
|         /// 2. the metrics shouldn't be black-listed |  | ||||||
|         /// 3. the metrics shouldn't have reached its rate control limit at this |  | ||||||
|         /// "sampling"/"calling" time |  | ||||||
|         bool shouldPush(const std::string& id) const; |  | ||||||
|  |  | ||||||
|         /// Get generic information json object |  | ||||||
|         Json::Value& getGenericAttributes(); |  | ||||||
|  |  | ||||||
|         /// Set generic information values |  | ||||||
|         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; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /// Get the unique id used to identify the current session |  | ||||||
|         const std::string& getSession() const |  | ||||||
|         { |  | ||||||
|             return _session; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /// Return the number of milliseconds since the epoch (~1970) |  | ||||||
|         uint64_t getMillisecondsSinceEpoch() const; |  | ||||||
|  |  | ||||||
|         /// Set satori connection publish mode |  | ||||||
|         void setPublishMode(CobraConnectionPublishMode publishMode); |  | ||||||
|  |  | ||||||
|         /// Flush the publish queue |  | ||||||
|         bool flushQueue(); |  | ||||||
|  |  | ||||||
|         /// Lifecycle management. Free resources when backgrounding |  | ||||||
|         void suspend(); |  | ||||||
|         void resume(); |  | ||||||
|  |  | ||||||
|         /// Tells whether the socket connection is opened |  | ||||||
|         bool isConnected() const; |  | ||||||
|  |  | ||||||
|         /// Returns true only if we're authenticated |  | ||||||
|         bool isAuthenticated() const; |  | ||||||
|  |  | ||||||
|     private: |  | ||||||
|         /// Lookup an id in our metrics to see whether it is blacklisted |  | ||||||
|         /// Complexity is logarithmic |  | ||||||
|         bool isMetricBlacklisted(const std::string& id) const; |  | ||||||
|  |  | ||||||
|         /// Tells whether we should drop a metrics or not as part of an enqueuing |  | ||||||
|         /// because it exceed the max update rate (it is sent too often) |  | ||||||
|         bool isAboveMaxUpdateRate(const std::string& id) const; |  | ||||||
|  |  | ||||||
|         /// Record when a metric was last sent. Used for rate control |  | ||||||
|         void setLastUpdate(const std::string& id); |  | ||||||
|  |  | ||||||
|         /// |  | ||||||
|         /// Member variables |  | ||||||
|         /// |  | ||||||
|  |  | ||||||
|         CobraMetricsThreadedPublisher _cobra_metrics_theaded_publisher; |  | ||||||
|  |  | ||||||
|         /// A boolean to enable or disable this system |  | ||||||
|         /// push becomes a no-op when _enabled is false |  | ||||||
|         std::atomic<bool> _enabled; |  | ||||||
|  |  | ||||||
|         /// A uuid used to uniquely identify a session |  | ||||||
|         std::string _session; |  | ||||||
|  |  | ||||||
|         /// The _device json blob is populated once when configuring this system |  | ||||||
|         /// It record generic metadata about the client, run (version, device model, etc...) |  | ||||||
|         Json::Value _device; |  | ||||||
|         mutable std::mutex _device_mutex; // protect access to _device |  | ||||||
|  |  | ||||||
|         /// Metrics control (black list + rate control) |  | ||||||
|         std::vector<std::string> _blacklist; |  | ||||||
|         std::unordered_map<std::string, int> _rate_control; |  | ||||||
|         std::unordered_map<std::string, std::chrono::time_point<std::chrono::steady_clock>> |  | ||||||
|             _last_update; |  | ||||||
|         mutable std::mutex _last_update_mutex; // protect access to _last_update |  | ||||||
|  |  | ||||||
|         /// Bump a counter for each metric type |  | ||||||
|         std::unordered_map<std::string, int> _counters; |  | ||||||
|         mutable std::mutex _counters_mutex; // protect access to _counters |  | ||||||
|  |  | ||||||
|         // const strings for internal ids |  | ||||||
|         static const std::string kSetRateControlId; |  | ||||||
|         static const std::string kSetBlacklistId; |  | ||||||
|  |  | ||||||
|         /// Our protocol version. Can be used by subscribers who would want to be backward |  | ||||||
|         /// compatible if we change the way we arrange data |  | ||||||
|         static const int kVersion; |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,240 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraMetricsThreadedPublisher.cpp |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2017 Machine Zone. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXCobraMetricsThreadedPublisher.h" |  | ||||||
|  |  | ||||||
| #include <algorithm> |  | ||||||
| #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) |  | ||||||
|     { |  | ||||||
|         _cobra_connection.setEventCallback([](const CobraEventPtr& event) { |  | ||||||
|             std::stringstream ss; |  | ||||||
|             ix::LogLevel logLevel = LogLevel::Info; |  | ||||||
|  |  | ||||||
|             if (event->type == ix::CobraEventType::Open) |  | ||||||
|             { |  | ||||||
|                 ss << "Handshake headers" << std::endl; |  | ||||||
|  |  | ||||||
|                 for (auto&& it : event->headers) |  | ||||||
|                 { |  | ||||||
|                     ss << it.first << ": " << it.second << std::endl; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             else if (event->type == ix::CobraEventType::Handshake) |  | ||||||
|             { |  | ||||||
|                 ss << "Cobra handshake connection id: " << event->connectionId; |  | ||||||
|             } |  | ||||||
|             else if (event->type == ix::CobraEventType::Authenticated) |  | ||||||
|             { |  | ||||||
|                 ss << "Authenticated"; |  | ||||||
|             } |  | ||||||
|             else if (event->type == ix::CobraEventType::Error) |  | ||||||
|             { |  | ||||||
|                 ss << "Error: " << event->errMsg; |  | ||||||
|                 logLevel = ix::LogLevel::Error; |  | ||||||
|             } |  | ||||||
|             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"; |  | ||||||
|                 logLevel = ix::LogLevel::Debug; |  | ||||||
|             } |  | ||||||
|             else if (event->type == ix::CobraEventType::Pong) |  | ||||||
|             { |  | ||||||
|                 ss << "Received websocket pong"; |  | ||||||
|             } |  | ||||||
|             else if (event->type == ix::CobraEventType::HandshakeError) |  | ||||||
|             { |  | ||||||
|                 ss << "Handshake error: " << event->errMsg; |  | ||||||
|                 logLevel = ix::LogLevel::Error; |  | ||||||
|             } |  | ||||||
|             else if (event->type == ix::CobraEventType::AuthenticationError) |  | ||||||
|             { |  | ||||||
|                 ss << "Authentication error: " << event->errMsg; |  | ||||||
|                 logLevel = ix::LogLevel::Error; |  | ||||||
|             } |  | ||||||
|             else if (event->type == ix::CobraEventType::SubscriptionError) |  | ||||||
|             { |  | ||||||
|                 ss << "Subscription error: " << event->errMsg; |  | ||||||
|                 logLevel = ix::LogLevel::Error; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             CoreLogger::log(ss.str().c_str(), logLevel); |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     CobraMetricsThreadedPublisher::~CobraMetricsThreadedPublisher() |  | ||||||
|     { |  | ||||||
|         // The background thread won't be joinable if it was never |  | ||||||
|         // started by calling CobraMetricsThreadedPublisher::start |  | ||||||
|         if (!_thread.joinable()) return; |  | ||||||
|  |  | ||||||
|         _stop = true; |  | ||||||
|         _condition.notify_one(); |  | ||||||
|         _thread.join(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraMetricsThreadedPublisher::start() |  | ||||||
|     { |  | ||||||
|         if (_thread.joinable()) return; // we've already been started |  | ||||||
|  |  | ||||||
|         _thread = std::thread(&CobraMetricsThreadedPublisher::run, this); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraMetricsThreadedPublisher::configure(const CobraConfig& config, |  | ||||||
|                                                   const std::string& channel) |  | ||||||
|     { |  | ||||||
|         CoreLogger::log(config.socketTLSOptions.getDescription().c_str()); |  | ||||||
|  |  | ||||||
|         _channel = channel; |  | ||||||
|         _cobra_connection.configure(config); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraMetricsThreadedPublisher::pushMessage(MessageKind messageKind) |  | ||||||
|     { |  | ||||||
|         { |  | ||||||
|             std::unique_lock<std::mutex> lock(_queue_mutex); |  | ||||||
|             _queue.push(messageKind); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // wake up one thread |  | ||||||
|         _condition.notify_one(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraMetricsThreadedPublisher::setPublishMode(CobraConnectionPublishMode publishMode) |  | ||||||
|     { |  | ||||||
|         _cobra_connection.setPublishMode(publishMode); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool CobraMetricsThreadedPublisher::flushQueue() |  | ||||||
|     { |  | ||||||
|         return _cobra_connection.flushQueue(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraMetricsThreadedPublisher::run() |  | ||||||
|     { |  | ||||||
|         setThreadName("CobraMetricsPublisher"); |  | ||||||
|  |  | ||||||
|         _cobra_connection.connect(); |  | ||||||
|  |  | ||||||
|         while (true) |  | ||||||
|         { |  | ||||||
|             Json::Value msg; |  | ||||||
|             MessageKind messageKind; |  | ||||||
|  |  | ||||||
|             { |  | ||||||
|                 std::unique_lock<std::mutex> lock(_queue_mutex); |  | ||||||
|  |  | ||||||
|                 while (!_stop && _queue.empty()) |  | ||||||
|                 { |  | ||||||
|                     _condition.wait(lock); |  | ||||||
|                 } |  | ||||||
|                 if (_stop) |  | ||||||
|                 { |  | ||||||
|                     _cobra_connection.disconnect(); |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 messageKind = _queue.front(); |  | ||||||
|                 _queue.pop(); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             switch (messageKind) |  | ||||||
|             { |  | ||||||
|                 case MessageKind::Suspend: |  | ||||||
|                 { |  | ||||||
|                     _cobra_connection.suspend(); |  | ||||||
|                     continue; |  | ||||||
|                 }; |  | ||||||
|                 break; |  | ||||||
|  |  | ||||||
|                 case MessageKind::Resume: |  | ||||||
|                 { |  | ||||||
|                     _cobra_connection.resume(); |  | ||||||
|                     continue; |  | ||||||
|                 }; |  | ||||||
|                 break; |  | ||||||
|  |  | ||||||
|                 case MessageKind::Message: |  | ||||||
|                 { |  | ||||||
|                     if (_cobra_connection.getPublishMode() == CobraConnection_PublishMode_Immediate) |  | ||||||
|                     { |  | ||||||
|                         _cobra_connection.publishNext(); |  | ||||||
|                     } |  | ||||||
|                 }; |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     CobraConnection::MsgId CobraMetricsThreadedPublisher::push(const Json::Value& msg) |  | ||||||
|     { |  | ||||||
|         static const std::string messageIdKey("id"); |  | ||||||
|  |  | ||||||
|         // |  | ||||||
|         // Publish to multiple channels. This let the consumer side |  | ||||||
|         // easily subscribe to all message of a certain type, without having |  | ||||||
|         // to do manipulations on the messages on the server side. |  | ||||||
|         // |  | ||||||
|         Json::Value channels; |  | ||||||
|  |  | ||||||
|         channels.append(_channel); |  | ||||||
|         if (msg.isMember(messageIdKey)) |  | ||||||
|         { |  | ||||||
|             channels.append(msg[messageIdKey]); |  | ||||||
|         } |  | ||||||
|         auto res = _cobra_connection.prePublish(channels, msg, true); |  | ||||||
|         auto msgId = res.first; |  | ||||||
|  |  | ||||||
|         pushMessage(MessageKind::Message); |  | ||||||
|  |  | ||||||
|         return msgId; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraMetricsThreadedPublisher::suspend() |  | ||||||
|     { |  | ||||||
|         pushMessage(MessageKind::Suspend); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraMetricsThreadedPublisher::resume() |  | ||||||
|     { |  | ||||||
|         pushMessage(MessageKind::Resume); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool CobraMetricsThreadedPublisher::isConnected() const |  | ||||||
|     { |  | ||||||
|         return _cobra_connection.isConnected(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool CobraMetricsThreadedPublisher::isAuthenticated() const |  | ||||||
|     { |  | ||||||
|         return _cobra_connection.isAuthenticated(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,101 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraMetricsThreadedPublisher.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2017 Machine Zone. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "IXCobraConnection.h" |  | ||||||
| #include <atomic> |  | ||||||
| #include <condition_variable> |  | ||||||
| #include <json/json.h> |  | ||||||
| #include <map> |  | ||||||
| #include <mutex> |  | ||||||
| #include <queue> |  | ||||||
| #include <string> |  | ||||||
| #include <thread> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     struct SocketTLSOptions; |  | ||||||
|  |  | ||||||
|     class CobraMetricsThreadedPublisher |  | ||||||
|     { |  | ||||||
|     public: |  | ||||||
|         CobraMetricsThreadedPublisher(); |  | ||||||
|         ~CobraMetricsThreadedPublisher(); |  | ||||||
|  |  | ||||||
|         /// Configuration / set keys, etc... |  | ||||||
|         void configure(const CobraConfig& config, const std::string& channel); |  | ||||||
|  |  | ||||||
|         /// Start the worker thread, used for background publishing |  | ||||||
|         void start(); |  | ||||||
|  |  | ||||||
|         /// Push a msg to our queue of messages to be published to cobra on the background |  | ||||||
|         //  thread. Main user right now is the Cobra Metrics System |  | ||||||
|         CobraConnection::MsgId push(const Json::Value& msg); |  | ||||||
|  |  | ||||||
|         /// Set cobra connection publish mode |  | ||||||
|         void setPublishMode(CobraConnectionPublishMode publishMode); |  | ||||||
|  |  | ||||||
|         /// Flush the publish queue |  | ||||||
|         bool flushQueue(); |  | ||||||
|  |  | ||||||
|         /// Lifecycle management. Free resources when backgrounding |  | ||||||
|         void suspend(); |  | ||||||
|         void resume(); |  | ||||||
|  |  | ||||||
|         /// Tells whether the socket connection is opened |  | ||||||
|         bool isConnected() const; |  | ||||||
|  |  | ||||||
|         /// Returns true only if we're authenticated |  | ||||||
|         bool isAuthenticated() const; |  | ||||||
|  |  | ||||||
|     private: |  | ||||||
|         enum class MessageKind |  | ||||||
|         { |  | ||||||
|             Message = 0, |  | ||||||
|             Suspend = 1, |  | ||||||
|             Resume = 2 |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         /// Push a message to be processed by the background thread |  | ||||||
|         void pushMessage(MessageKind messageKind); |  | ||||||
|  |  | ||||||
|         /// Get a wait time which is increasing exponentially based on the number of retries |  | ||||||
|         uint64_t getWaitTimeExp(int retry_count); |  | ||||||
|  |  | ||||||
|         /// Debugging routine to print the connection parameters to the console |  | ||||||
|         void printInfo(); |  | ||||||
|  |  | ||||||
|         /// Publish a message to satory |  | ||||||
|         /// Will retry multiple times (3) if a problem occurs. |  | ||||||
|         /// |  | ||||||
|         /// Right now, only called on the publish worker thread. |  | ||||||
|         void safePublish(const Json::Value& msg); |  | ||||||
|  |  | ||||||
|         /// The worker thread "daemon" method. That method never returns unless _stop is set to true |  | ||||||
|         void run(); |  | ||||||
|  |  | ||||||
|         /// Our connection to cobra. |  | ||||||
|         CobraConnection _cobra_connection; |  | ||||||
|  |  | ||||||
|         /// The channel we are publishing to |  | ||||||
|         std::string _channel; |  | ||||||
|  |  | ||||||
|         /// Internal data structures used to publish to cobra |  | ||||||
|         /// Pending messages are stored into a queue, which is protected by a mutex |  | ||||||
|         /// We used a condition variable to prevent the worker thread from busy polling |  | ||||||
|         /// So we notify the condition variable when an incoming message arrives to signal |  | ||||||
|         /// that it should wake up and take care of publishing it to cobra |  | ||||||
|         /// To shutdown the worker thread one has to set the _stop boolean to true. |  | ||||||
|         /// This is done in the destructor |  | ||||||
|         std::queue<MessageKind> _queue; |  | ||||||
|         mutable std::mutex _queue_mutex; |  | ||||||
|         std::condition_variable _condition; |  | ||||||
|         std::atomic<bool> _stop; |  | ||||||
|         std::thread _thread; |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| Client code to publish to a real time analytic system, described in [https://bsergean.github.io/redis_conf_2019/slides.html#1](link). |  | ||||||
| @@ -1,19 +0,0 @@ | |||||||
| # |  | ||||||
| # Author: Benjamin Sergeant |  | ||||||
| # Copyright (c) 2019 Machine Zone, Inc. All rights reserved. |  | ||||||
| # |  | ||||||
|  |  | ||||||
| set (IXCORE_SOURCES |  | ||||||
|     ixcore/utils/IXCoreLogger.cpp |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| set (IXCORE_HEADERS |  | ||||||
|     ixcore/utils/IXCoreLogger.h |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| add_library(ixcore STATIC |  | ||||||
|     ${IXCORE_SOURCES} |  | ||||||
|     ${IXCORE_HEADERS} |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| target_include_directories( ixcore PUBLIC . ) |  | ||||||
| @@ -1,44 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  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 a no-op logger |  | ||||||
|     CoreLogger::LogFunc CoreLogger::_currentLogger = [](const char*, LogLevel) {}; |  | ||||||
|  |  | ||||||
|     void CoreLogger::log(const char* msg, LogLevel level) |  | ||||||
|     { |  | ||||||
|         _currentLogger(msg, level); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     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,44 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  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 |  | ||||||
| { |  | ||||||
|     enum class LogLevel |  | ||||||
|     { |  | ||||||
|         Debug = 0, |  | ||||||
|         Info = 1, |  | ||||||
|         Warning = 2, |  | ||||||
|         Error = 3, |  | ||||||
|         Critical = 4 |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     class CoreLogger |  | ||||||
|     { |  | ||||||
|     public: |  | ||||||
|         using LogFunc = std::function<void(const char*, LogLevel level)>; |  | ||||||
|  |  | ||||||
|         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; |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,47 +0,0 @@ | |||||||
| # |  | ||||||
| # Author: Benjamin Sergeant |  | ||||||
| # Copyright (c) 2019 Machine Zone, Inc. All rights reserved. |  | ||||||
| # |  | ||||||
| set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../CMake;${CMAKE_MODULE_PATH}") |  | ||||||
|  |  | ||||||
| set (IXCRYPTO_SOURCES |  | ||||||
|     ixcrypto/IXHMac.cpp |  | ||||||
|     ixcrypto/IXBase64.cpp |  | ||||||
|     ixcrypto/IXUuid.cpp |  | ||||||
|     ixcrypto/IXHash.cpp |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| set (IXCRYPTO_HEADERS |  | ||||||
|     ixcrypto/IXHMac.h |  | ||||||
|     ixcrypto/IXBase64.h |  | ||||||
|     ixcrypto/IXUuid.h |  | ||||||
|     ixcrypto/IXHash.h |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| add_library(ixcrypto STATIC |  | ||||||
|     ${IXCRYPTO_SOURCES} |  | ||||||
|     ${IXCRYPTO_HEADERS} |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| set(IXCRYPTO_INCLUDE_DIRS |  | ||||||
|     . |  | ||||||
|     ../ixcore) |  | ||||||
|  |  | ||||||
| target_include_directories( ixcrypto PUBLIC ${IXCRYPTO_INCLUDE_DIRS} ) |  | ||||||
|  |  | ||||||
| # hmac computation needs a crypto library |  | ||||||
|  |  | ||||||
| target_compile_definitions(ixcrypto PUBLIC IXCRYPTO_USE_TLS) |  | ||||||
| if (USE_MBED_TLS) |  | ||||||
|     find_package(MbedTLS REQUIRED) |  | ||||||
|     target_include_directories(ixcrypto PUBLIC ${MBEDTLS_INCLUDE_DIRS}) |  | ||||||
|     target_link_libraries(ixcrypto ${MBEDTLS_LIBRARIES}) |  | ||||||
|     target_compile_definitions(ixcrypto PUBLIC IXCRYPTO_USE_MBED_TLS) |  | ||||||
| elseif (USE_OPEN_SSL) |  | ||||||
|     find_package(OpenSSL REQUIRED) |  | ||||||
|     add_definitions(${OPENSSL_DEFINITIONS}) |  | ||||||
|     message(STATUS "OpenSSL: " ${OPENSSL_VERSION}) |  | ||||||
|     include_directories(${OPENSSL_INCLUDE_DIR}) |  | ||||||
|     target_link_libraries(ixcrypto ${OPENSSL_LIBRARIES}) |  | ||||||
|     target_compile_definitions(ixcrypto PUBLIC IXCRYPTO_USE_OPEN_SSL) |  | ||||||
| endif() |  | ||||||
| @@ -1,142 +0,0 @@ | |||||||
| /* |  | ||||||
|  base64.cpp and base64.h |  | ||||||
|  |  | ||||||
|  Copyright (C) 2004-2008 René Nyffenegger |  | ||||||
|  |  | ||||||
|  This source code is provided 'as-is', without any express or implied |  | ||||||
|  warranty. In no event will the author 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 source code must not be misrepresented; you must not |  | ||||||
|  claim that you wrote the original source code. If you use this source code |  | ||||||
|  in a product, an acknowledgment 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 source code. |  | ||||||
|  |  | ||||||
|  3. This notice may not be removed or altered from any source distribution. |  | ||||||
|  |  | ||||||
|  René Nyffenegger rene.nyffenegger@adp-gmbh.ch |  | ||||||
|  |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXBase64.h" |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |  | ||||||
|                                             "abcdefghijklmnopqrstuvwxyz" |  | ||||||
|                                             "0123456789+/"; |  | ||||||
|  |  | ||||||
|     std::string base64_encode(const std::string& data, size_t len) |  | ||||||
|     { |  | ||||||
|         const char* bytes_to_encode = data.c_str(); |  | ||||||
|         return base64_encode(bytes_to_encode, len); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::string base64_encode(const char* bytes_to_encode, size_t len) |  | ||||||
|     { |  | ||||||
|         std::string ret; |  | ||||||
|         ret.reserve(((len + 2) / 3) * 4); |  | ||||||
|  |  | ||||||
|         int i = 0; |  | ||||||
|         int j = 0; |  | ||||||
|         unsigned char char_array_3[3]; |  | ||||||
|         unsigned char char_array_4[4]; |  | ||||||
|  |  | ||||||
|         while (len--) |  | ||||||
|         { |  | ||||||
|             char_array_3[i++] = *(bytes_to_encode++); |  | ||||||
|             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++) |  | ||||||
|                     ret += base64_chars[char_array_4[i]]; |  | ||||||
|  |  | ||||||
|                 i = 0; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (i) |  | ||||||
|         { |  | ||||||
|             for (j = i; j < 3; j++) |  | ||||||
|                 char_array_3[j] = '\0'; |  | ||||||
|  |  | ||||||
|             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 (j = 0; (j < i + 1); j++) |  | ||||||
|                 ret += base64_chars[char_array_4[j]]; |  | ||||||
|  |  | ||||||
|             while ((i++ < 3)) |  | ||||||
|                 ret += '='; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return ret; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     static inline bool is_base64(unsigned char c) |  | ||||||
|     { |  | ||||||
|         return (isalnum(c) || (c == '+') || (c == '/')); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::string base64_decode(const std::string& encoded_string) |  | ||||||
|     { |  | ||||||
|         int in_len = (int) encoded_string.size(); |  | ||||||
|         int i = 0; |  | ||||||
|         int j = 0; |  | ||||||
|         int in_ = 0; |  | ||||||
|         unsigned char char_array_4[4], char_array_3[3]; |  | ||||||
|         std::string ret; |  | ||||||
|         ret.reserve(((in_len + 3) / 4) * 3); |  | ||||||
|  |  | ||||||
|         while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) |  | ||||||
|         { |  | ||||||
|             char_array_4[i++] = encoded_string[in_]; |  | ||||||
|             in_++; |  | ||||||
|             if (i == 4) |  | ||||||
|             { |  | ||||||
|                 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++) |  | ||||||
|                     ret += char_array_3[i]; |  | ||||||
|  |  | ||||||
|                 i = 0; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (i) |  | ||||||
|         { |  | ||||||
|             for (j = i; j < 4; j++) |  | ||||||
|                 char_array_4[j] = 0; |  | ||||||
|  |  | ||||||
|             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]; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return ret; |  | ||||||
|     } |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,16 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  base64.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2018 Machine Zone. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include <string> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     std::string base64_encode(const std::string& data, size_t len); |  | ||||||
|     std::string base64_encode(const char* data, size_t len); |  | ||||||
|     std::string base64_decode(const std::string& encoded_string); |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,53 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXHMac.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2018 Machine Zone. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXHMac.h" |  | ||||||
|  |  | ||||||
| #include "IXBase64.h" |  | ||||||
|  |  | ||||||
| #if defined(IXCRYPTO_USE_MBED_TLS) |  | ||||||
| #include <mbedtls/md.h> |  | ||||||
| #elif defined(__APPLE__) |  | ||||||
| #include <CommonCrypto/CommonHMAC.h> |  | ||||||
| #elif defined(IXCRYPTO_USE_OPEN_SSL) |  | ||||||
| #include <openssl/hmac.h> |  | ||||||
| #else |  | ||||||
| #include <assert.h> |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     std::string hmac(const std::string& data, const std::string& key) |  | ||||||
|     { |  | ||||||
|         constexpr size_t hashSize = 16; |  | ||||||
|         unsigned char hash[hashSize]; |  | ||||||
|  |  | ||||||
| #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); |  | ||||||
| #elif defined(__APPLE__) |  | ||||||
|         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); |  | ||||||
| #else |  | ||||||
|         assert(false && "hmac not implemented on this platform"); |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|         std::string hashString(reinterpret_cast<char*>(hash), hashSize); |  | ||||||
|  |  | ||||||
|         return base64_encode(hashString, (uint32_t) hashString.size()); |  | ||||||
|     } |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,14 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXHMac.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2018 Machine Zone. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include <string> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     std::string hmac(const std::string& data, const std::string& key); |  | ||||||
| } |  | ||||||
| @@ -1,34 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXHash.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2018 Machine Zone. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXHash.h" |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     uint64_t djb2Hash(const std::vector<uint8_t>& data) |  | ||||||
|     { |  | ||||||
|         uint64_t hashAddress = 5381; |  | ||||||
|  |  | ||||||
|         for (auto&& c : data) |  | ||||||
|         { |  | ||||||
|             hashAddress = ((hashAddress << 5) + hashAddress) + c; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return hashAddress; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     uint64_t djb2HashStr(const std::string& data) |  | ||||||
|     { |  | ||||||
|         uint64_t hashAddress = 5381; |  | ||||||
|  |  | ||||||
|         for (size_t i = 0; i < data.size(); ++i) |  | ||||||
|         { |  | ||||||
|             hashAddress = ((hashAddress << 5) + hashAddress) + data[i]; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return hashAddress; |  | ||||||
|     } |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,17 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXHash.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2018 Machine Zone. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include <cstdint> |  | ||||||
| #include <vector> |  | ||||||
| #include <string> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     uint64_t djb2Hash(const std::vector<uint8_t>& data); |  | ||||||
|     uint64_t djb2HashStr(const std::string& data); |  | ||||||
| } |  | ||||||
| @@ -1,27 +0,0 @@ | |||||||
| # |  | ||||||
| # Author: Benjamin Sergeant |  | ||||||
| # Copyright (c) 2020 Machine Zone, Inc. All rights reserved. |  | ||||||
| # |  | ||||||
|  |  | ||||||
| set (IXREDIS_SOURCES |  | ||||||
|     ixredis/IXRedisClient.cpp |  | ||||||
|     ixredis/IXRedisServer.cpp |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| set (IXREDIS_HEADERS |  | ||||||
|     ixredis/IXRedisClient.h |  | ||||||
|     ixredis/IXRedisServer.h |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| add_library(ixredis STATIC |  | ||||||
|     ${IXREDIS_SOURCES} |  | ||||||
|     ${IXREDIS_HEADERS} |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| set(IXREDIS_INCLUDE_DIRS |  | ||||||
|     . |  | ||||||
|     .. |  | ||||||
|     ../ixcore |  | ||||||
|     ../ixwebsocket) |  | ||||||
|  |  | ||||||
| target_include_directories( ixredis PUBLIC ${IXREDIS_INCLUDE_DIRS} ) |  | ||||||
| @@ -1,457 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXRedisClient.cpp |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXRedisClient.h" |  | ||||||
|  |  | ||||||
| #include <cstring> |  | ||||||
| #include <iomanip> |  | ||||||
| #include <iostream> |  | ||||||
| #include <ixwebsocket/IXSocket.h> |  | ||||||
| #include <ixwebsocket/IXSocketFactory.h> |  | ||||||
| #include <ixwebsocket/IXSocketTLSOptions.h> |  | ||||||
| #include <sstream> |  | ||||||
| #include <vector> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     bool RedisClient::connect(const std::string& hostname, int port) |  | ||||||
|     { |  | ||||||
|         bool tls = false; |  | ||||||
|         std::string errorMsg; |  | ||||||
|         SocketTLSOptions tlsOptions; |  | ||||||
|         _socket = createSocket(tls, -1, errorMsg, tlsOptions); |  | ||||||
|  |  | ||||||
|         if (!_socket) |  | ||||||
|         { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         CancellationRequest cancellationRequest = []() -> bool { return false; }; |  | ||||||
|  |  | ||||||
|         std::string errMsg; |  | ||||||
|         return _socket->connect(hostname, port, errMsg, cancellationRequest); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RedisClient::stop() |  | ||||||
|     { |  | ||||||
|         _stop = true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool RedisClient::auth(const std::string& password, std::string& response) |  | ||||||
|     { |  | ||||||
|         response.clear(); |  | ||||||
|  |  | ||||||
|         if (!_socket) return false; |  | ||||||
|  |  | ||||||
|         std::stringstream ss; |  | ||||||
|         ss << "AUTH "; |  | ||||||
|         ss << password; |  | ||||||
|         ss << "\r\n"; |  | ||||||
|  |  | ||||||
|         bool sent = _socket->writeBytes(ss.str(), nullptr); |  | ||||||
|         if (!sent) |  | ||||||
|         { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         auto pollResult = _socket->isReadyToRead(-1); |  | ||||||
|         if (pollResult == PollResultType::Error) |  | ||||||
|         { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         auto lineResult = _socket->readLine(nullptr); |  | ||||||
|         auto lineValid = lineResult.first; |  | ||||||
|         auto line = lineResult.second; |  | ||||||
|  |  | ||||||
|         response = line; |  | ||||||
|         return lineValid; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::string RedisClient::writeString(const std::string& str) |  | ||||||
|     { |  | ||||||
|         std::stringstream ss; |  | ||||||
|         ss << "$"; |  | ||||||
|         ss << str.size(); |  | ||||||
|         ss << "\r\n"; |  | ||||||
|         ss << str; |  | ||||||
|         ss << "\r\n"; |  | ||||||
|  |  | ||||||
|         return ss.str(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool RedisClient::publish(const std::string& channel, |  | ||||||
|                               const std::string& message, |  | ||||||
|                               std::string& errMsg) |  | ||||||
|     { |  | ||||||
|         errMsg.clear(); |  | ||||||
|  |  | ||||||
|         if (!_socket) |  | ||||||
|         { |  | ||||||
|             errMsg = "socket is not initialized"; |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         std::stringstream ss; |  | ||||||
|         ss << "*3\r\n"; |  | ||||||
|         ss << writeString("PUBLISH"); |  | ||||||
|         ss << writeString(channel); |  | ||||||
|         ss << writeString(message); |  | ||||||
|  |  | ||||||
|         bool sent = _socket->writeBytes(ss.str(), nullptr); |  | ||||||
|         if (!sent) |  | ||||||
|         { |  | ||||||
|             errMsg = "Cannot write bytes to socket"; |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         auto pollResult = _socket->isReadyToRead(-1); |  | ||||||
|         if (pollResult == PollResultType::Error) |  | ||||||
|         { |  | ||||||
|             errMsg = "Error while polling for result"; |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         auto lineResult = _socket->readLine(nullptr); |  | ||||||
|         auto lineValid = lineResult.first; |  | ||||||
|         auto line = lineResult.second; |  | ||||||
|  |  | ||||||
|         // A successful response starts with a : |  | ||||||
|         if (line.empty() || line[0] != ':') |  | ||||||
|         { |  | ||||||
|             errMsg = line; |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return lineValid; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // |  | ||||||
|     // FIXME: we assume that redis never return errors... |  | ||||||
|     // |  | ||||||
|     bool RedisClient::subscribe(const std::string& channel, |  | ||||||
|                                 const OnRedisSubscribeResponseCallback& responseCallback, |  | ||||||
|                                 const OnRedisSubscribeCallback& callback) |  | ||||||
|     { |  | ||||||
|         _stop = false; |  | ||||||
|  |  | ||||||
|         if (!_socket) return false; |  | ||||||
|  |  | ||||||
|         std::stringstream ss; |  | ||||||
|         ss << "*2\r\n"; |  | ||||||
|         ss << writeString("SUBSCRIBE"); |  | ||||||
|         ss << writeString(channel); |  | ||||||
|  |  | ||||||
|         bool sent = _socket->writeBytes(ss.str(), nullptr); |  | ||||||
|         if (!sent) |  | ||||||
|         { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Wait 1s for the response |  | ||||||
|         auto pollResult = _socket->isReadyToRead(-1); |  | ||||||
|         if (pollResult == PollResultType::Error) |  | ||||||
|         { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // build the response as a single string |  | ||||||
|         std::stringstream oss; |  | ||||||
|  |  | ||||||
|         // Read the first line of the response |  | ||||||
|         auto lineResult = _socket->readLine(nullptr); |  | ||||||
|         auto lineValid = lineResult.first; |  | ||||||
|         auto line = lineResult.second; |  | ||||||
|         oss << line; |  | ||||||
|  |  | ||||||
|         if (!lineValid) return false; |  | ||||||
|  |  | ||||||
|         // There are 5 items for the subscribe reply |  | ||||||
|         for (int i = 0; i < 5; ++i) |  | ||||||
|         { |  | ||||||
|             auto lineResult = _socket->readLine(nullptr); |  | ||||||
|             auto lineValid = lineResult.first; |  | ||||||
|             auto line = lineResult.second; |  | ||||||
|             oss << line; |  | ||||||
|  |  | ||||||
|             if (!lineValid) return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         responseCallback(oss.str()); |  | ||||||
|  |  | ||||||
|         // Wait indefinitely for new messages |  | ||||||
|         while (true) |  | ||||||
|         { |  | ||||||
|             if (_stop) break; |  | ||||||
|  |  | ||||||
|             // Wait until something is ready to read |  | ||||||
|             int timeoutMs = 10; |  | ||||||
|             auto pollResult = _socket->isReadyToRead(timeoutMs); |  | ||||||
|             if (pollResult == PollResultType::Error) |  | ||||||
|             { |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (pollResult == PollResultType::Timeout) |  | ||||||
|             { |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // The first line of the response describe the return type, |  | ||||||
|             // => *3 (an array of 3 elements) |  | ||||||
|             auto lineResult = _socket->readLine(nullptr); |  | ||||||
|             auto lineValid = lineResult.first; |  | ||||||
|             auto line = lineResult.second; |  | ||||||
|  |  | ||||||
|             if (!lineValid) return false; |  | ||||||
|  |  | ||||||
|             int arraySize; |  | ||||||
|             { |  | ||||||
|                 std::stringstream ss; |  | ||||||
|                 ss << line.substr(1, line.size() - 1); |  | ||||||
|                 ss >> arraySize; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // There are 6 items for each received message |  | ||||||
|             for (int i = 0; i < arraySize; ++i) |  | ||||||
|             { |  | ||||||
|                 auto lineResult = _socket->readLine(nullptr); |  | ||||||
|                 auto lineValid = lineResult.first; |  | ||||||
|                 auto line = lineResult.second; |  | ||||||
|  |  | ||||||
|                 if (!lineValid) return false; |  | ||||||
|  |  | ||||||
|                 // Messages are string, which start with a string size |  | ||||||
|                 // => $7 (7 bytes) |  | ||||||
|                 int stringSize; |  | ||||||
|                 std::stringstream ss; |  | ||||||
|                 ss << line.substr(1, line.size() - 1); |  | ||||||
|                 ss >> stringSize; |  | ||||||
|  |  | ||||||
|                 auto readResult = _socket->readBytes(stringSize, nullptr, nullptr); |  | ||||||
|                 if (!readResult.first) return false; |  | ||||||
|  |  | ||||||
|                 if (i == 2) |  | ||||||
|                 { |  | ||||||
|                     // The message is the 3rd element. |  | ||||||
|                     callback(readResult.second); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // read last 2 bytes (\r\n) |  | ||||||
|                 char c; |  | ||||||
|                 _socket->readByte(&c, nullptr); |  | ||||||
|                 _socket->readByte(&c, nullptr); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::string RedisClient::prepareXaddCommand(const std::string& stream, |  | ||||||
|                                                 const std::string& message, |  | ||||||
|                                                 int maxLen) |  | ||||||
|     { |  | ||||||
|         std::stringstream ss; |  | ||||||
|         ss << "*8\r\n"; |  | ||||||
|         ss << writeString("XADD"); |  | ||||||
|         ss << writeString(stream); |  | ||||||
|         ss << writeString("MAXLEN"); |  | ||||||
|         ss << writeString("~"); |  | ||||||
|         ss << writeString(std::to_string(maxLen)); |  | ||||||
|         ss << writeString("*"); |  | ||||||
|         ss << writeString("field"); |  | ||||||
|         ss << writeString(message); |  | ||||||
|  |  | ||||||
|         return ss.str(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::string RedisClient::xadd(const std::string& stream, |  | ||||||
|                                   const std::string& message, |  | ||||||
|                                   int maxLen, |  | ||||||
|                                   std::string& errMsg) |  | ||||||
|     { |  | ||||||
|         errMsg.clear(); |  | ||||||
|  |  | ||||||
|         if (!_socket) |  | ||||||
|         { |  | ||||||
|             errMsg = "socket is not initialized"; |  | ||||||
|             return std::string(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         std::string command = prepareXaddCommand(stream, message, maxLen); |  | ||||||
|  |  | ||||||
|         bool sent = _socket->writeBytes(command, nullptr); |  | ||||||
|         if (!sent) |  | ||||||
|         { |  | ||||||
|             errMsg = "Cannot write bytes to socket"; |  | ||||||
|             return std::string(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return readXaddReply(errMsg); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::string RedisClient::readXaddReply(std::string& errMsg) |  | ||||||
|     { |  | ||||||
|         // Read result |  | ||||||
|         auto pollResult = _socket->isReadyToRead(-1); |  | ||||||
|         if (pollResult == PollResultType::Error) |  | ||||||
|         { |  | ||||||
|             errMsg = "Error while polling for result"; |  | ||||||
|             return std::string(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // First line is the string length |  | ||||||
|         auto lineResult = _socket->readLine(nullptr); |  | ||||||
|         auto lineValid = lineResult.first; |  | ||||||
|         auto line = lineResult.second; |  | ||||||
|  |  | ||||||
|         if (!lineValid) |  | ||||||
|         { |  | ||||||
|             errMsg = "Error while polling for result"; |  | ||||||
|             return std::string(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         int stringSize; |  | ||||||
|         { |  | ||||||
|             std::stringstream ss; |  | ||||||
|             ss << line.substr(1, line.size() - 1); |  | ||||||
|             ss >> stringSize; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Read the result, which is the stream id computed by the redis server |  | ||||||
|         lineResult = _socket->readLine(nullptr); |  | ||||||
|         lineValid = lineResult.first; |  | ||||||
|         line = lineResult.second; |  | ||||||
|  |  | ||||||
|         std::string streamId = line.substr(0, stringSize - 1); |  | ||||||
|         return streamId; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool RedisClient::sendCommand(const std::string& commands, |  | ||||||
|                                   int commandsCount, |  | ||||||
|                                   std::string& errMsg) |  | ||||||
|     { |  | ||||||
|         bool sent = _socket->writeBytes(commands, nullptr); |  | ||||||
|         if (!sent) |  | ||||||
|         { |  | ||||||
|             errMsg = "Cannot write bytes to socket"; |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         bool success = true; |  | ||||||
|  |  | ||||||
|         for (int i = 0; i < commandsCount; ++i) |  | ||||||
|         { |  | ||||||
|             auto reply = readXaddReply(errMsg); |  | ||||||
|             if (reply == std::string()) |  | ||||||
|             { |  | ||||||
|                 success = false; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return success; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::pair<RespType, std::string> RedisClient::send( |  | ||||||
|         const std::vector<std::string>& args, |  | ||||||
|         std::string& errMsg) |  | ||||||
|     { |  | ||||||
|         std::stringstream ss; |  | ||||||
|         ss << "*"; |  | ||||||
|         ss << std::to_string(args.size()); |  | ||||||
|         ss << "\r\n"; |  | ||||||
|  |  | ||||||
|         for (auto&& arg : args) |  | ||||||
|         { |  | ||||||
|             ss << writeString(arg); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         bool sent = _socket->writeBytes(ss.str(), nullptr); |  | ||||||
|         if (!sent) |  | ||||||
|         { |  | ||||||
|             errMsg = "Cannot write bytes to socket"; |  | ||||||
|             return std::make_pair(RespType::Error, ""); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return readResponse(errMsg); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::pair<RespType, std::string> RedisClient::readResponse(std::string& errMsg) |  | ||||||
|     { |  | ||||||
|         // Read result |  | ||||||
|         auto pollResult = _socket->isReadyToRead(-1); |  | ||||||
|         if (pollResult == PollResultType::Error) |  | ||||||
|         { |  | ||||||
|             errMsg = "Error while polling for result"; |  | ||||||
|             return std::make_pair(RespType::Error, ""); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // First line is the string length |  | ||||||
|         auto lineResult = _socket->readLine(nullptr); |  | ||||||
|         auto lineValid = lineResult.first; |  | ||||||
|         auto line = lineResult.second; |  | ||||||
|  |  | ||||||
|         if (!lineValid) |  | ||||||
|         { |  | ||||||
|             errMsg = "Error while polling for result"; |  | ||||||
|             return std::make_pair(RespType::Error, ""); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         std::string response; |  | ||||||
|  |  | ||||||
|         if (line[0] == '+') // Simple string |  | ||||||
|         { |  | ||||||
|             std::stringstream ss; |  | ||||||
|             response = line.substr(1, line.size() - 3); |  | ||||||
|             return std::make_pair(RespType::String, response); |  | ||||||
|         } |  | ||||||
|         else if (line[0] == '-') // Errors |  | ||||||
|         { |  | ||||||
|             std::stringstream ss; |  | ||||||
|             response = line.substr(1, line.size() - 3); |  | ||||||
|             return std::make_pair(RespType::Error, response); |  | ||||||
|         } |  | ||||||
|         else if (line[0] == ':') // Integers |  | ||||||
|         { |  | ||||||
|             std::stringstream ss; |  | ||||||
|             response = line.substr(1, line.size() - 3); |  | ||||||
|             return std::make_pair(RespType::Integer, response); |  | ||||||
|         } |  | ||||||
|         else if (line[0] == '$') // Bulk strings |  | ||||||
|         { |  | ||||||
|             int stringSize; |  | ||||||
|             { |  | ||||||
|                 std::stringstream ss; |  | ||||||
|                 ss << line.substr(1, line.size() - 1); |  | ||||||
|                 ss >> stringSize; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // Read the result, which is the stream id computed by the redis server |  | ||||||
|             lineResult = _socket->readLine(nullptr); |  | ||||||
|             lineValid = lineResult.first; |  | ||||||
|             line = lineResult.second; |  | ||||||
|  |  | ||||||
|             std::string str = line.substr(0, stringSize); |  | ||||||
|             return std::make_pair(RespType::String, str); |  | ||||||
|         } |  | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             errMsg = "Unhandled response type"; |  | ||||||
|             return std::make_pair(RespType::Unknown, std::string()); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::string RedisClient::getRespTypeDescription(RespType respType) |  | ||||||
|     { |  | ||||||
|         switch (respType) |  | ||||||
|         { |  | ||||||
|             case RespType::Integer: return "integer"; |  | ||||||
|             case RespType::Error: return "error"; |  | ||||||
|             case RespType::String: return "string"; |  | ||||||
|             default: return "unknown"; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,77 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXRedisClient.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include <atomic> |  | ||||||
| #include <functional> |  | ||||||
| #include <memory> |  | ||||||
| #include <string> |  | ||||||
| #include <vector> |  | ||||||
| #include <ixwebsocket/IXSocket.h> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     enum class RespType : int |  | ||||||
|     { |  | ||||||
|         String = 0, |  | ||||||
|         Error = 1, |  | ||||||
|         Integer = 2, |  | ||||||
|         Unknown = 3 |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     class RedisClient |  | ||||||
|     { |  | ||||||
|     public: |  | ||||||
|         using OnRedisSubscribeResponseCallback = std::function<void(const std::string&)>; |  | ||||||
|         using OnRedisSubscribeCallback = std::function<void(const std::string&)>; |  | ||||||
|  |  | ||||||
|         RedisClient() |  | ||||||
|             : _stop(false) |  | ||||||
|         { |  | ||||||
|         } |  | ||||||
|         ~RedisClient() = default; |  | ||||||
|  |  | ||||||
|         bool connect(const std::string& hostname, int port); |  | ||||||
|  |  | ||||||
|         bool auth(const std::string& password, std::string& response); |  | ||||||
|  |  | ||||||
|         // Publish / Subscribe |  | ||||||
|         bool publish(const std::string& channel, const std::string& message, std::string& errMsg); |  | ||||||
|  |  | ||||||
|         bool subscribe(const std::string& channel, |  | ||||||
|                        const OnRedisSubscribeResponseCallback& responseCallback, |  | ||||||
|                        const OnRedisSubscribeCallback& callback); |  | ||||||
|  |  | ||||||
|         // XADD |  | ||||||
|         std::string xadd(const std::string& channel, |  | ||||||
|                          const std::string& message, |  | ||||||
|                          int maxLen, |  | ||||||
|                          std::string& errMsg); |  | ||||||
|         std::string prepareXaddCommand(const std::string& stream, |  | ||||||
|                                        const std::string& message, |  | ||||||
|                                        int maxLen); |  | ||||||
|         std::string readXaddReply(std::string& errMsg); |  | ||||||
|         bool sendCommand( |  | ||||||
|             const std::string& commands, int commandsCount, std::string& errMsg); |  | ||||||
|  |  | ||||||
|         // Arbitrary commands |  | ||||||
|         std::pair<RespType, std::string> send( |  | ||||||
|             const std::vector<std::string>& args, |  | ||||||
|             std::string& errMsg); |  | ||||||
|         std::pair<RespType, std::string> readResponse(std::string& errMsg); |  | ||||||
|  |  | ||||||
|         std::string getRespTypeDescription(RespType respType); |  | ||||||
|  |  | ||||||
|         void stop(); |  | ||||||
|  |  | ||||||
|     private: |  | ||||||
|         std::string writeString(const std::string& str); |  | ||||||
|  |  | ||||||
|         std::unique_ptr<Socket> _socket; |  | ||||||
|         std::atomic<bool> _stop; |  | ||||||
|     }; |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,287 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXRedisServer.cpp |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXRedisServer.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) |  | ||||||
|         : SocketServer(port, host, backlog, maxConnections, addressFamily) |  | ||||||
|         , _connectedClientsCount(0) |  | ||||||
|         , _stopHandlingConnections(false) |  | ||||||
|     { |  | ||||||
|         ; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     RedisServer::~RedisServer() |  | ||||||
|     { |  | ||||||
|         stop(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RedisServer::stop() |  | ||||||
|     { |  | ||||||
|         stopAcceptingConnections(); |  | ||||||
|  |  | ||||||
|         _stopHandlingConnections = true; |  | ||||||
|         while (_connectedClientsCount != 0) |  | ||||||
|         { |  | ||||||
|             std::this_thread::sleep_for(std::chrono::milliseconds(10)); |  | ||||||
|         } |  | ||||||
|         _stopHandlingConnections = false; |  | ||||||
|  |  | ||||||
|         SocketServer::stop(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RedisServer::handleConnection(std::unique_ptr<Socket> socket, |  | ||||||
|                                        std::shared_ptr<ConnectionState> connectionState) |  | ||||||
|     { |  | ||||||
|         logInfo("New connection from remote ip " + connectionState->getRemoteIp()); |  | ||||||
|  |  | ||||||
|         _connectedClientsCount++; |  | ||||||
|  |  | ||||||
|         while (!_stopHandlingConnections) |  | ||||||
|         { |  | ||||||
|             std::vector<std::string> tokens; |  | ||||||
|             if (!parseRequest(socket, tokens)) |  | ||||||
|             { |  | ||||||
|                 if (_stopHandlingConnections) |  | ||||||
|                 { |  | ||||||
|                     logError("Cancellation requested"); |  | ||||||
|                 } |  | ||||||
|                 else |  | ||||||
|                 { |  | ||||||
|                     logError("Error parsing request"); |  | ||||||
|                 } |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             bool success = false; |  | ||||||
|  |  | ||||||
|             // publish |  | ||||||
|             if (tokens[0] == "COMMAND") |  | ||||||
|             { |  | ||||||
|                 success = handleCommand(socket, tokens); |  | ||||||
|             } |  | ||||||
|             else if (tokens[0] == "PUBLISH") |  | ||||||
|             { |  | ||||||
|                 success = handlePublish(socket, tokens); |  | ||||||
|             } |  | ||||||
|             else if (tokens[0] == "SUBSCRIBE") |  | ||||||
|             { |  | ||||||
|                 success = handleSubscribe(socket, tokens); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (!success) |  | ||||||
|             { |  | ||||||
|                 if (_stopHandlingConnections) |  | ||||||
|                 { |  | ||||||
|                     logError("Cancellation requested"); |  | ||||||
|                 } |  | ||||||
|                 else |  | ||||||
|                 { |  | ||||||
|                     logError("Error processing request for command: " + tokens[0]); |  | ||||||
|                 } |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         cleanupSubscribers(socket); |  | ||||||
|  |  | ||||||
|         logInfo("Connection closed for connection id " + connectionState->getId()); |  | ||||||
|         connectionState->setTerminated(); |  | ||||||
|  |  | ||||||
|         _connectedClientsCount--; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RedisServer::cleanupSubscribers(std::unique_ptr<Socket>& socket) |  | ||||||
|     { |  | ||||||
|         std::lock_guard<std::mutex> lock(_mutex); |  | ||||||
|  |  | ||||||
|         for (auto&& it : _subscribers) |  | ||||||
|         { |  | ||||||
|             it.second.erase(socket.get()); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         for (auto&& it : _subscribers) |  | ||||||
|         { |  | ||||||
|             std::stringstream ss; |  | ||||||
|             ss << "Subscription id: " << it.first << " #subscribers: " << it.second.size(); |  | ||||||
|  |  | ||||||
|             logInfo(ss.str()); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     size_t RedisServer::getConnectedClientsCount() |  | ||||||
|     { |  | ||||||
|         return _connectedClientsCount; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool RedisServer::startsWith(const std::string& str, const std::string& start) |  | ||||||
|     { |  | ||||||
|         return str.compare(0, start.length(), start) == 0; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::string RedisServer::writeString(const std::string& str) |  | ||||||
|     { |  | ||||||
|         std::stringstream ss; |  | ||||||
|         ss << "$"; |  | ||||||
|         ss << str.size(); |  | ||||||
|         ss << "\r\n"; |  | ||||||
|         ss << str; |  | ||||||
|         ss << "\r\n"; |  | ||||||
|  |  | ||||||
|         return ss.str(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool RedisServer::parseRequest(std::unique_ptr<Socket>& socket, |  | ||||||
|                                    std::vector<std::string>& tokens) |  | ||||||
|     { |  | ||||||
|         // Parse first line |  | ||||||
|         auto cb = makeCancellationRequestWithTimeout(30, _stopHandlingConnections); |  | ||||||
|         auto lineResult = socket->readLine(cb); |  | ||||||
|         auto lineValid = lineResult.first; |  | ||||||
|         auto line = lineResult.second; |  | ||||||
|  |  | ||||||
|         if (!lineValid) return false; |  | ||||||
|  |  | ||||||
|         std::string str = line.substr(1); |  | ||||||
|         std::stringstream ss; |  | ||||||
|         ss << str; |  | ||||||
|         int count; |  | ||||||
|         ss >> count; |  | ||||||
|  |  | ||||||
|         for (int i = 0; i < count; ++i) |  | ||||||
|         { |  | ||||||
|             auto lineResult = socket->readLine(cb); |  | ||||||
|             auto lineValid = lineResult.first; |  | ||||||
|             auto line = lineResult.second; |  | ||||||
|  |  | ||||||
|             if (!lineValid) return false; |  | ||||||
|  |  | ||||||
|             int stringSize; |  | ||||||
|             std::stringstream ss; |  | ||||||
|             ss << line.substr(1, line.size() - 1); |  | ||||||
|             ss >> stringSize; |  | ||||||
|  |  | ||||||
|             auto readResult = socket->readBytes(stringSize, nullptr, nullptr); |  | ||||||
|  |  | ||||||
|             if (!readResult.first) return false; |  | ||||||
|  |  | ||||||
|             // read last 2 bytes (\r\n) |  | ||||||
|             char c; |  | ||||||
|             socket->readByte(&c, nullptr); |  | ||||||
|             socket->readByte(&c, nullptr); |  | ||||||
|  |  | ||||||
|             tokens.push_back(readResult.second); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool RedisServer::handleCommand(std::unique_ptr<Socket>& socket, |  | ||||||
|                                     const std::vector<std::string>& tokens) |  | ||||||
|     { |  | ||||||
|         if (tokens.size() != 1) return false; |  | ||||||
|  |  | ||||||
|         auto cb = makeCancellationRequestWithTimeout(30, _stopHandlingConnections); |  | ||||||
|         std::stringstream ss; |  | ||||||
|  |  | ||||||
|         // return 2 nested arrays |  | ||||||
|         ss << "*2\r\n"; |  | ||||||
|  |  | ||||||
|         // |  | ||||||
|         // publish |  | ||||||
|         // |  | ||||||
|         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 |  | ||||||
|  |  | ||||||
|         // |  | ||||||
|         // 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 |  | ||||||
|  |  | ||||||
|         socket->writeBytes(ss.str(), cb); |  | ||||||
|  |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool RedisServer::handleSubscribe(std::unique_ptr<Socket>& socket, |  | ||||||
|                                       const std::vector<std::string>& tokens) |  | ||||||
|     { |  | ||||||
|         if (tokens.size() != 2) return false; |  | ||||||
|  |  | ||||||
|         auto cb = makeCancellationRequestWithTimeout(30, _stopHandlingConnections); |  | ||||||
|         std::string channel = tokens[1]; |  | ||||||
|  |  | ||||||
|         // Respond |  | ||||||
|         socket->writeBytes("*3\r\n", cb); |  | ||||||
|         socket->writeBytes(writeString("subscribe"), cb); |  | ||||||
|         socket->writeBytes(writeString(channel), cb); |  | ||||||
|         socket->writeBytes(":1\r\n", cb); |  | ||||||
|  |  | ||||||
|         std::lock_guard<std::mutex> lock(_mutex); |  | ||||||
|         _subscribers[channel].insert(socket.get()); |  | ||||||
|  |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool RedisServer::handlePublish(std::unique_ptr<Socket>& socket, |  | ||||||
|                                     const std::vector<std::string>& tokens) |  | ||||||
|     { |  | ||||||
|         if (tokens.size() != 3) return false; |  | ||||||
|  |  | ||||||
|         auto cb = makeCancellationRequestWithTimeout(30, _stopHandlingConnections); |  | ||||||
|         std::string channel = tokens[1]; |  | ||||||
|         std::string data = tokens[2]; |  | ||||||
|  |  | ||||||
|         // now dispatch the message to subscribers (write custom method) |  | ||||||
|         std::lock_guard<std::mutex> lock(_mutex); |  | ||||||
|         auto it = _subscribers.find(channel); |  | ||||||
|         if (it == _subscribers.end()) |  | ||||||
|         { |  | ||||||
|             // return the number of clients that received the message, 0 in that case |  | ||||||
|             socket->writeBytes(":0\r\n", cb); |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         auto subscribers = it->second; |  | ||||||
|         for (auto jt : subscribers) |  | ||||||
|         { |  | ||||||
|             jt->writeBytes("*3\r\n", cb); |  | ||||||
|             jt->writeBytes(writeString("message"), cb); |  | ||||||
|             jt->writeBytes(writeString(channel), cb); |  | ||||||
|             jt->writeBytes(writeString(data), cb); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // return the number of clients that received the message. |  | ||||||
|         std::stringstream ss; |  | ||||||
|         ss << ":" << std::to_string(subscribers.size()) << "\r\n"; |  | ||||||
|         socket->writeBytes(ss.str(), cb); |  | ||||||
|  |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,65 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXRedisServer.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2018 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include <ixwebsocket/IXSocket.h> |  | ||||||
| #include <ixwebsocket/IXSocketServer.h> |  | ||||||
| #include <functional> |  | ||||||
| #include <map> |  | ||||||
| #include <memory> |  | ||||||
| #include <mutex> |  | ||||||
| #include <set> |  | ||||||
| #include <string> |  | ||||||
| #include <thread> |  | ||||||
| #include <utility> // pair |  | ||||||
| #include <vector> // pair |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     class RedisServer final : public SocketServer |  | ||||||
|     { |  | ||||||
|     public: |  | ||||||
|         RedisServer(int port = SocketServer::kDefaultPort, |  | ||||||
|                     const std::string& host = SocketServer::kDefaultHost, |  | ||||||
|                     int backlog = SocketServer::kDefaultTcpBacklog, |  | ||||||
|                     size_t maxConnections = SocketServer::kDefaultMaxConnections, |  | ||||||
|                     int addressFamily = SocketServer::kDefaultAddressFamily); |  | ||||||
|         virtual ~RedisServer(); |  | ||||||
|         virtual void stop() final; |  | ||||||
|  |  | ||||||
|     private: |  | ||||||
|         // Member variables |  | ||||||
|         std::atomic<int> _connectedClientsCount; |  | ||||||
|  |  | ||||||
|         // Subscribers |  | ||||||
|         // We could store connection states in there, to add better debugging |  | ||||||
|         // since a connection state has a readable ID |  | ||||||
|         std::map<std::string, std::set<Socket*>> _subscribers; |  | ||||||
|         std::mutex _mutex; |  | ||||||
|  |  | ||||||
|         std::atomic<bool> _stopHandlingConnections; |  | ||||||
|  |  | ||||||
|         // Methods |  | ||||||
|         virtual void handleConnection(std::unique_ptr<Socket>, |  | ||||||
|                                       std::shared_ptr<ConnectionState> connectionState) final; |  | ||||||
|         virtual size_t getConnectedClientsCount() final; |  | ||||||
|  |  | ||||||
|         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 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); |  | ||||||
|  |  | ||||||
|         void cleanupSubscribers(std::unique_ptr<Socket>& socket); |  | ||||||
|     }; |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,44 +0,0 @@ | |||||||
| # |  | ||||||
| # Author: Benjamin Sergeant |  | ||||||
| # Copyright (c) 2019 Machine Zone, Inc. All rights reserved. |  | ||||||
| # |  | ||||||
|  |  | ||||||
| set (IXSENTRY_SOURCES |  | ||||||
|     ixsentry/IXSentryClient.cpp |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| set (IXSENTRY_HEADERS |  | ||||||
|     ixsentry/IXSentryClient.h |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| add_library(ixsentry STATIC |  | ||||||
|   ${IXSENTRY_SOURCES} |  | ||||||
|   ${IXSENTRY_HEADERS} |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| # |  | ||||||
| # Using try_compile or other techniques to detect std::regex |  | ||||||
| # availability is hard, so resorting to an ugly compiler and compiler |  | ||||||
| # version check. |  | ||||||
| # |  | ||||||
| if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") |  | ||||||
|   if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.9.0") |  | ||||||
|   else() |  | ||||||
|     target_compile_definitions( ixsentry PUBLIC HAVE_STD_REGEX=1 ) |  | ||||||
|   endif() |  | ||||||
| else() |  | ||||||
|   target_compile_definitions( ixsentry PUBLIC HAVE_STD_REGEX=1 ) |  | ||||||
| endif() |  | ||||||
|  |  | ||||||
| find_package(JsonCpp) |  | ||||||
| if (NOT JSONCPP_FOUND) |  | ||||||
|   set(JSONCPP_INCLUDE_DIRS ../third_party/jsoncpp) |  | ||||||
| endif() |  | ||||||
|  |  | ||||||
| set(IXSENTRY_INCLUDE_DIRS |  | ||||||
|     . |  | ||||||
|     .. |  | ||||||
|     ../ixcore |  | ||||||
|     ${JSONCPP_INCLUDE_DIRS}) |  | ||||||
|  |  | ||||||
| target_include_directories( ixsentry PUBLIC ${IXSENTRY_INCLUDE_DIRS} ) |  | ||||||
| @@ -1,316 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXSentryClient.cpp |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2019 Machine Zone. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXSentryClient.h" |  | ||||||
|  |  | ||||||
| #include <chrono> |  | ||||||
| #include <fstream> |  | ||||||
| #include <iostream> |  | ||||||
| #include <ixcore/utils/IXCoreLogger.h> |  | ||||||
| #include <ixwebsocket/IXWebSocketHttpHeaders.h> |  | ||||||
| #include <ixwebsocket/IXWebSocketVersion.h> |  | ||||||
| #include <sstream> |  | ||||||
|  |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     SentryClient::SentryClient(const std::string& dsn) |  | ||||||
|         : _dsn(dsn) |  | ||||||
|         , _validDsn(false) |  | ||||||
| #ifdef HAVE_STD_REGEX |  | ||||||
|         , _luaFrameRegex("\t([^/]+):([0-9]+): in function ['<]([^/]+)['>]") |  | ||||||
| #endif |  | ||||||
|         , _httpClient(std::make_shared<HttpClient>(true)) |  | ||||||
|     { |  | ||||||
| #ifdef HAVE_STD_REGEX |  | ||||||
|         const std::regex dsnRegex("(http[s]?)://([^:]+):([^@]+)@([^/]+)/([0-9]+)"); |  | ||||||
|         std::smatch group; |  | ||||||
|  |  | ||||||
|         if (std::regex_match(dsn, group, dsnRegex) && group.size() == 6) |  | ||||||
|         { |  | ||||||
|             _validDsn = true; |  | ||||||
|  |  | ||||||
|             const auto scheme = group.str(1); |  | ||||||
|             const auto host = group.str(4); |  | ||||||
|             const auto project_id = group.str(5); |  | ||||||
|             _url = scheme + "://" + host + "/api/" + project_id + "/store/"; |  | ||||||
|  |  | ||||||
|             _publicKey = group.str(2); |  | ||||||
|             _secretKey = group.str(3); |  | ||||||
|         } |  | ||||||
| #endif |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void SentryClient::setTLSOptions(const SocketTLSOptions& tlsOptions) |  | ||||||
|     { |  | ||||||
|         _httpClient->setTLSOptions(tlsOptions); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     int64_t SentryClient::getTimestamp() |  | ||||||
|     { |  | ||||||
|         const auto tp = std::chrono::system_clock::now(); |  | ||||||
|         const auto dur = tp.time_since_epoch(); |  | ||||||
|         return std::chrono::duration_cast<std::chrono::seconds>(dur).count(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::string SentryClient::getIso8601() |  | ||||||
|     { |  | ||||||
|         std::time_t now; |  | ||||||
|         std::time(&now); |  | ||||||
|         char buf[sizeof("2011-10-08T07:07:09Z")]; |  | ||||||
|         std::strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", std::gmtime(&now)); |  | ||||||
|         return buf; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::string SentryClient::computeAuthHeader() |  | ||||||
|     { |  | ||||||
|         std::string securityHeader("Sentry sentry_version=5"); |  | ||||||
|         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; |  | ||||||
|  |  | ||||||
|         return securityHeader; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     Json::Value SentryClient::parseLuaStackTrace(const std::string& stack) |  | ||||||
|     { |  | ||||||
|         Json::Value frames; |  | ||||||
|  |  | ||||||
| #ifdef HAVE_STD_REGEX |  | ||||||
|         // Split by lines |  | ||||||
|         std::string line; |  | ||||||
|         std::stringstream tokenStream(stack); |  | ||||||
|  |  | ||||||
|         std::smatch group; |  | ||||||
|  |  | ||||||
|         while (std::getline(tokenStream, line)) |  | ||||||
|         { |  | ||||||
|             //	MapScene.lua:2169: in function 'singleCB' |  | ||||||
|             if (std::regex_match(line, group, _luaFrameRegex)) |  | ||||||
|             { |  | ||||||
|                 const auto fileName = group.str(1); |  | ||||||
|                 const auto linenoStr = group.str(2); |  | ||||||
|                 const auto function = group.str(3); |  | ||||||
|  |  | ||||||
|                 std::stringstream ss; |  | ||||||
|                 ss << linenoStr; |  | ||||||
|                 uint64_t lineno; |  | ||||||
|                 ss >> lineno; |  | ||||||
|  |  | ||||||
|                 Json::Value frame; |  | ||||||
|                 frame["lineno"] = Json::UInt64(lineno); |  | ||||||
|                 frame["filename"] = fileName; |  | ||||||
|                 frame["function"] = function; |  | ||||||
|  |  | ||||||
|                 frames.append(frame); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         std::reverse(frames.begin(), frames.end()); |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|         return frames; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::string parseExceptionName(const std::string& stack) |  | ||||||
|     { |  | ||||||
|         // Split by lines |  | ||||||
|         std::string line; |  | ||||||
|         std::stringstream tokenStream(stack); |  | ||||||
|  |  | ||||||
|         // Extract the first line |  | ||||||
|         std::getline(tokenStream, line); |  | ||||||
|  |  | ||||||
|         return line; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::string SentryClient::computePayload(const Json::Value& msg) |  | ||||||
|     { |  | ||||||
|         Json::Value payload; |  | ||||||
|  |  | ||||||
|         // |  | ||||||
|         // "tags": [ |  | ||||||
|         //   [ |  | ||||||
|         //     "a", |  | ||||||
|         //     "b" |  | ||||||
|         //   ], |  | ||||||
|         //  ] |  | ||||||
|         // |  | ||||||
|         Json::Value tags(Json::arrayValue); |  | ||||||
|  |  | ||||||
|         payload["platform"] = "python"; |  | ||||||
|         payload["sdk"]["name"] = "ws"; |  | ||||||
|         payload["sdk"]["version"] = IX_WEBSOCKET_VERSION; |  | ||||||
|         payload["timestamp"] = SentryClient::getIso8601(); |  | ||||||
|  |  | ||||||
|         bool isNoisyTypes = msg["id"].asString() == "game_noisytypes_id"; |  | ||||||
|  |  | ||||||
|         std::string stackTraceFieldName = isNoisyTypes ? "traceback" : "stack"; |  | ||||||
|         std::string stack; |  | ||||||
|         std::string message; |  | ||||||
|  |  | ||||||
|         if (isNoisyTypes) |  | ||||||
|         { |  | ||||||
|             stack = msg["data"][stackTraceFieldName].asString(); |  | ||||||
|             message = parseExceptionName(stack); |  | ||||||
|         } |  | ||||||
|         else // logging |  | ||||||
|         { |  | ||||||
|             if (msg["data"].isMember("info")) |  | ||||||
|             { |  | ||||||
|                 stack = msg["data"]["info"][stackTraceFieldName].asString(); |  | ||||||
|                 message = msg["data"]["info"]["message"].asString(); |  | ||||||
|  |  | ||||||
|                 if (msg["data"].isMember("tags")) |  | ||||||
|                 { |  | ||||||
|                     auto members = msg["data"]["tags"].getMemberNames(); |  | ||||||
|  |  | ||||||
|                     for (auto member : members) |  | ||||||
|                     { |  | ||||||
|                         Json::Value tag; |  | ||||||
|                         tag.append(member); |  | ||||||
|                         tag.append(msg["data"]["tags"][member]); |  | ||||||
|                         tags.append(tag); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 if (msg["data"]["info"].isMember("level_str")) |  | ||||||
|                 { |  | ||||||
|                     // https://docs.sentry.io/enriching-error-data/context/?platform=python#setting-the-level |  | ||||||
|                     std::string level = msg["data"]["info"]["level_str"].asString(); |  | ||||||
|                     if (level == "critical") |  | ||||||
|                     { |  | ||||||
|                         level = "fatal"; |  | ||||||
|                     } |  | ||||||
|                     payload["level"] = level; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 stack = msg["data"][stackTraceFieldName].asString(); |  | ||||||
|                 message = msg["data"]["message"].asString(); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Json::Value exception; |  | ||||||
|         exception["stacktrace"]["frames"] = parseLuaStackTrace(stack); |  | ||||||
|         exception["value"] = message; |  | ||||||
|  |  | ||||||
|         payload["exception"].append(exception); |  | ||||||
|  |  | ||||||
|         Json::Value extra; |  | ||||||
|         extra["cobra_event"] = msg; |  | ||||||
|  |  | ||||||
|         // Builtin tags |  | ||||||
|         Json::Value gameTag; |  | ||||||
|         gameTag.append("game"); |  | ||||||
|         gameTag.append(msg["device"]["game"]); |  | ||||||
|         tags.append(gameTag); |  | ||||||
|  |  | ||||||
|         Json::Value userIdTag; |  | ||||||
|         userIdTag.append("userid"); |  | ||||||
|         userIdTag.append(msg["device"]["user_id"]); |  | ||||||
|         tags.append(userIdTag); |  | ||||||
|  |  | ||||||
|         Json::Value environmentTag; |  | ||||||
|         environmentTag.append("environment"); |  | ||||||
|         environmentTag.append(msg["device"]["environment"]); |  | ||||||
|         tags.append(environmentTag); |  | ||||||
|  |  | ||||||
|         Json::Value clientVersionTag; |  | ||||||
|         clientVersionTag.append("client_version"); |  | ||||||
|         clientVersionTag.append(msg["device"]["app_version"]); |  | ||||||
|         tags.append(clientVersionTag); |  | ||||||
|  |  | ||||||
|         payload["tags"] = tags; |  | ||||||
|  |  | ||||||
|         return _jsonWriter.write(payload); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void SentryClient::send( |  | ||||||
|         const Json::Value& msg, |  | ||||||
|         bool verbose, |  | ||||||
|         const OnResponseCallback& onResponseCallback) |  | ||||||
|     { |  | ||||||
|         auto args = _httpClient->createRequest(); |  | ||||||
|         args->url = _url; |  | ||||||
|         args->verb = HttpClient::kPost; |  | ||||||
|         args->extraHeaders["X-Sentry-Auth"] = SentryClient::computeAuthHeader(); |  | ||||||
|         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->body = computePayload(msg); |  | ||||||
|  |  | ||||||
|         _httpClient->performRequest(args, onResponseCallback); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // https://sentry.io/api/12345/minidump?sentry_key=abcdefgh"); |  | ||||||
|     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; |  | ||||||
|  |  | ||||||
|         return ss.str(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // |  | ||||||
|     // 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) |  | ||||||
|     { |  | ||||||
|         std::string multipartBoundary = _httpClient->generateMultipartBoundary(); |  | ||||||
|  |  | ||||||
|         auto args = _httpClient->createRequest(); |  | ||||||
|         args->verb = HttpClient::kPost; |  | ||||||
|         args->connectTimeout = 60; |  | ||||||
|         args->transferTimeout = 5 * 60; |  | ||||||
|         args->followRedirects = true; |  | ||||||
|         args->verbose = verbose; |  | ||||||
|         args->multipartBoundary = multipartBoundary; |  | ||||||
|         args->logger = [](const std::string& msg) { CoreLogger::log(msg.c_str()); }; |  | ||||||
|  |  | ||||||
|         HttpFormDataParameters httpFormDataParameters; |  | ||||||
|         httpFormDataParameters["upload_file_minidump"] = minidumpBytes; |  | ||||||
|  |  | ||||||
|         HttpParameters httpParameters; |  | ||||||
|         httpParameters["sentry"] = sentryMetadata; |  | ||||||
|  |  | ||||||
|         args->url = computeUrl(project, key); |  | ||||||
|         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); |  | ||||||
|     } |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,74 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXSentryClient.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2019 Machine Zone. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include <algorithm> |  | ||||||
| #include <ixwebsocket/IXHttpClient.h> |  | ||||||
| #include <ixwebsocket/IXSocketTLSOptions.h> |  | ||||||
| #include <json/json.h> |  | ||||||
| #include <memory> |  | ||||||
| #ifdef HAVE_STD_REGEX |  | ||||||
| #include <regex> |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     class SentryClient |  | ||||||
|     { |  | ||||||
|     public: |  | ||||||
|         SentryClient(const std::string& dsn); |  | ||||||
|         ~SentryClient() = default; |  | ||||||
|  |  | ||||||
|         void send(const Json::Value& msg, |  | ||||||
|                   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); |  | ||||||
|  |  | ||||||
|         Json::Value parseLuaStackTrace(const std::string& stack); |  | ||||||
|  |  | ||||||
|         // Mostly for testing |  | ||||||
|         void setTLSOptions(const SocketTLSOptions& tlsOptions); |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     private: |  | ||||||
|         int64_t getTimestamp(); |  | ||||||
|         std::string computeAuthHeader(); |  | ||||||
|         std::string getIso8601(); |  | ||||||
|         std::string computePayload(const Json::Value& msg); |  | ||||||
|  |  | ||||||
|         std::string computeUrl(const std::string& project, const std::string& key); |  | ||||||
|  |  | ||||||
|         void displayReponse(HttpResponsePtr response); |  | ||||||
|  |  | ||||||
|         std::string _dsn; |  | ||||||
|         bool _validDsn; |  | ||||||
|         std::string _url; |  | ||||||
|  |  | ||||||
|         // Used for authentication with a header |  | ||||||
|         std::string _publicKey; |  | ||||||
|         std::string _secretKey; |  | ||||||
|  |  | ||||||
|         Json::FastWriter _jsonWriter; |  | ||||||
|  |  | ||||||
| #ifdef HAVE_STD_REGEX |  | ||||||
|         std::regex _luaFrameRegex; |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|         std::shared_ptr<HttpClient> _httpClient; |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
| } // namespace ix |  | ||||||
| @@ -1,34 +0,0 @@ | |||||||
| # |  | ||||||
| # Author: Benjamin Sergeant |  | ||||||
| # Copyright (c) 2019 Machine Zone, Inc. All rights reserved. |  | ||||||
| # |  | ||||||
|  |  | ||||||
| set (IXSNAKE_SOURCES |  | ||||||
|     ixsnake/IXSnakeServer.cpp |  | ||||||
|     ixsnake/IXSnakeProtocol.cpp |  | ||||||
|     ixsnake/IXAppConfig.cpp |  | ||||||
|     ixsnake/IXStreamSql.cpp |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| set (IXSNAKE_HEADERS |  | ||||||
|     ixsnake/IXSnakeServer.h |  | ||||||
|     ixsnake/IXSnakeProtocol.h |  | ||||||
|     ixsnake/IXAppConfig.h |  | ||||||
|     ixsnake/IXStreamSql.h |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| add_library(ixsnake STATIC |  | ||||||
|     ${IXSNAKE_SOURCES} |  | ||||||
|     ${IXSNAKE_HEADERS} |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| set(IXSNAKE_INCLUDE_DIRS |  | ||||||
|     . |  | ||||||
|     .. |  | ||||||
|     ../ixcore |  | ||||||
|     ../ixcrypto |  | ||||||
|     ../ixwebsocket |  | ||||||
|     ../ixredis |  | ||||||
|     ../third_party) |  | ||||||
|  |  | ||||||
| target_include_directories( ixsnake PUBLIC ${IXSNAKE_INCLUDE_DIRS} ) |  | ||||||
| @@ -1,54 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXSnakeProtocol.cpp |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXAppConfig.h" |  | ||||||
|  |  | ||||||
| #include "IXSnakeProtocol.h" |  | ||||||
| #include <iostream> |  | ||||||
| #include <ixcrypto/IXUuid.h> |  | ||||||
|  |  | ||||||
| namespace snake |  | ||||||
| { |  | ||||||
|     bool isAppKeyValid(const AppConfig& appConfig, std::string appkey) |  | ||||||
|     { |  | ||||||
|         return appConfig.apps.count(appkey) != 0; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::string getRoleSecret(const AppConfig& appConfig, std::string appkey, std::string role) |  | ||||||
|     { |  | ||||||
|         if (!isAppKeyValid(appConfig, appkey)) |  | ||||||
|         { |  | ||||||
|             std::cerr << "Missing appkey " << appkey << std::endl; |  | ||||||
|             return std::string(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         auto roles = appConfig.apps[appkey]["roles"]; |  | ||||||
|         if (roles.count(role) == 0) |  | ||||||
|         { |  | ||||||
|             std::cerr << "Missing role " << role << std::endl; |  | ||||||
|             return std::string(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         auto channel = roles[role]["secret"]; |  | ||||||
|         return channel; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::string generateNonce() |  | ||||||
|     { |  | ||||||
|         return ix::uuid4(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void dumpConfig(const AppConfig& appConfig) |  | ||||||
|     { |  | ||||||
|         for (auto&& host : appConfig.redisHosts) |  | ||||||
|         { |  | ||||||
|             std::cout << "redis host: " << host << std::endl; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         std::cout << "redis password: " << appConfig.redisPassword << std::endl; |  | ||||||
|         std::cout << "redis port: " << appConfig.redisPort << std::endl; |  | ||||||
|     } |  | ||||||
| } // namespace snake |  | ||||||
| @@ -1,48 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXAppConfig.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include <ixwebsocket/IXSocketTLSOptions.h> |  | ||||||
| #include <nlohmann/json.hpp> |  | ||||||
| #include <string> |  | ||||||
| #include <vector> |  | ||||||
|  |  | ||||||
| namespace snake |  | ||||||
| { |  | ||||||
|     struct AppConfig |  | ||||||
|     { |  | ||||||
|         // Server |  | ||||||
|         std::string hostname; |  | ||||||
|         int port; |  | ||||||
|  |  | ||||||
|         // Redis |  | ||||||
|         std::vector<std::string> redisHosts; |  | ||||||
|         int redisPort; |  | ||||||
|         std::string redisPassword; |  | ||||||
|  |  | ||||||
|         // AppKeys |  | ||||||
|         nlohmann::json apps; |  | ||||||
|  |  | ||||||
|         // TLS options |  | ||||||
|         ix::SocketTLSOptions socketTLSOptions; |  | ||||||
|  |  | ||||||
|         // Misc |  | ||||||
|         bool verbose; |  | ||||||
|         bool disablePong; |  | ||||||
|  |  | ||||||
|         // If non empty, every published message gets republished to a given channel |  | ||||||
|         std::string republishChannel; |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     bool isAppKeyValid(const AppConfig& appConfig, std::string appkey); |  | ||||||
|  |  | ||||||
|     std::string getRoleSecret(const AppConfig& appConfig, std::string appkey, std::string role); |  | ||||||
|  |  | ||||||
|     std::string generateNonce(); |  | ||||||
|  |  | ||||||
|     void dumpConfig(const AppConfig& appConfig); |  | ||||||
| } // namespace snake |  | ||||||
| @@ -1,86 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXSnakeConnectionState.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include <ixredis/IXRedisClient.h> |  | ||||||
| #include <thread> |  | ||||||
| #include <ixwebsocket/IXConnectionState.h> |  | ||||||
| #include <string> |  | ||||||
| #include "IXStreamSql.h" |  | ||||||
|  |  | ||||||
| namespace snake |  | ||||||
| { |  | ||||||
|     class SnakeConnectionState : public ix::ConnectionState |  | ||||||
|     { |  | ||||||
|     public: |  | ||||||
|         virtual ~SnakeConnectionState() |  | ||||||
|         { |  | ||||||
|             stopSubScriptionThread(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         std::string getNonce() |  | ||||||
|         { |  | ||||||
|             return _nonce; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         void setNonce(const std::string& nonce) |  | ||||||
|         { |  | ||||||
|             _nonce = nonce; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         std::string appkey() |  | ||||||
|         { |  | ||||||
|             return _appkey; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         void setAppkey(const std::string& appkey) |  | ||||||
|         { |  | ||||||
|             _appkey = appkey; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         std::string role() |  | ||||||
|         { |  | ||||||
|             return _role; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         void setRole(const std::string& role) |  | ||||||
|         { |  | ||||||
|             _role = role; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         ix::RedisClient& redisClient() |  | ||||||
|         { |  | ||||||
|             return _redisClient; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         void stopSubScriptionThread() |  | ||||||
|         { |  | ||||||
|             if (subscriptionThread.joinable()) |  | ||||||
|             { |  | ||||||
|                 subscriptionRedisClient.stop(); |  | ||||||
|                 subscriptionThread.join(); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // We could make those accessible through methods |  | ||||||
|         std::thread subscriptionThread; |  | ||||||
|         std::string appChannel; |  | ||||||
|         std::string subscriptionId; |  | ||||||
|         uint64_t id; |  | ||||||
|         std::unique_ptr<StreamSql> streamSql; |  | ||||||
|         ix::RedisClient subscriptionRedisClient; |  | ||||||
|         ix::RedisClient::OnRedisSubscribeResponseCallback onRedisSubscribeResponseCallback; |  | ||||||
|         ix::RedisClient::OnRedisSubscribeCallback onRedisSubscribeCallback; |  | ||||||
|  |  | ||||||
|     private: |  | ||||||
|         std::string _nonce; |  | ||||||
|         std::string _role; |  | ||||||
|         std::string _appkey; |  | ||||||
|  |  | ||||||
|         ix::RedisClient _redisClient; |  | ||||||
|     }; |  | ||||||
| } // namespace snake |  | ||||||
| @@ -1,320 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXSnakeProtocol.cpp |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXSnakeProtocol.h" |  | ||||||
|  |  | ||||||
| #include "IXAppConfig.h" |  | ||||||
| #include "IXSnakeConnectionState.h" |  | ||||||
| #include "nlohmann/json.hpp" |  | ||||||
| #include <iostream> |  | ||||||
| #include <ixcore/utils/IXCoreLogger.h> |  | ||||||
| #include <ixcrypto/IXHMac.h> |  | ||||||
| #include <ixwebsocket/IXWebSocket.h> |  | ||||||
| #include <ixwebsocket/IXUniquePtr.h> |  | ||||||
| #include <sstream> |  | ||||||
|  |  | ||||||
| namespace snake |  | ||||||
| { |  | ||||||
|     void handleError(const std::string& action, |  | ||||||
|                      ix::WebSocket& ws, |  | ||||||
|                      uint64_t pduId, |  | ||||||
|                      const std::string& errMsg) |  | ||||||
|     { |  | ||||||
|         std::string actionError(action); |  | ||||||
|         actionError += "/error"; |  | ||||||
|  |  | ||||||
|         nlohmann::json response = { |  | ||||||
|             {"action", actionError}, {"id", pduId}, {"body", {{"reason", errMsg}}}}; |  | ||||||
|         ws.sendText(response.dump()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void handleHandshake(std::shared_ptr<SnakeConnectionState> state, |  | ||||||
|                          ix::WebSocket& ws, |  | ||||||
|                          const nlohmann::json& pdu, |  | ||||||
|                          uint64_t pduId) |  | ||||||
|     { |  | ||||||
|         std::string role = pdu["body"]["data"]["role"]; |  | ||||||
|  |  | ||||||
|         state->setNonce(generateNonce()); |  | ||||||
|         state->setRole(role); |  | ||||||
|  |  | ||||||
|         nlohmann::json response = { |  | ||||||
|             {"action", "auth/handshake/ok"}, |  | ||||||
|             {"id", pduId}, |  | ||||||
|             {"body", |  | ||||||
|              { |  | ||||||
|                  {"data", {{"nonce", state->getNonce()}, {"connection_id", state->getId()}}}, |  | ||||||
|              }}}; |  | ||||||
|  |  | ||||||
|         auto serializedResponse = response.dump(); |  | ||||||
|  |  | ||||||
|         ws.sendText(serializedResponse); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void handleAuth(std::shared_ptr<SnakeConnectionState> state, |  | ||||||
|                     ix::WebSocket& ws, |  | ||||||
|                     const AppConfig& appConfig, |  | ||||||
|                     const nlohmann::json& pdu, |  | ||||||
|                     uint64_t pduId) |  | ||||||
|     { |  | ||||||
|         auto secret = getRoleSecret(appConfig, state->appkey(), state->role()); |  | ||||||
|  |  | ||||||
|         if (secret.empty()) |  | ||||||
|         { |  | ||||||
|             nlohmann::json response = { |  | ||||||
|                 {"action", "auth/authenticate/error"}, |  | ||||||
|                 {"id", pduId}, |  | ||||||
|                 {"body", {{"error", "authentication_failed"}, {"reason", "invalid secret"}}}}; |  | ||||||
|             ws.sendText(response.dump()); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         auto nonce = state->getNonce(); |  | ||||||
|         auto serverHash = ix::hmac(nonce, secret); |  | ||||||
|         std::string clientHash = pdu["body"]["credentials"]["hash"]; |  | ||||||
|  |  | ||||||
|         if (serverHash != clientHash) |  | ||||||
|         { |  | ||||||
|             nlohmann::json response = { |  | ||||||
|                 {"action", "auth/authenticate/error"}, |  | ||||||
|                 {"id", pdu.value("id", 1)}, |  | ||||||
|                 {"body", {{"error", "authentication_failed"}, {"reason", "invalid hash"}}}}; |  | ||||||
|             ws.sendText(response.dump()); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         nlohmann::json response = { |  | ||||||
|             {"action", "auth/authenticate/ok"}, {"id", pdu.value("id", 1)}, {"body", {}}}; |  | ||||||
|  |  | ||||||
|         ws.sendText(response.dump()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void handlePublish(std::shared_ptr<SnakeConnectionState> state, |  | ||||||
|                        ix::WebSocket& ws, |  | ||||||
|                        const AppConfig& appConfig, |  | ||||||
|                        const nlohmann::json& pdu, |  | ||||||
|                        uint64_t pduId) |  | ||||||
|     { |  | ||||||
|         std::vector<std::string> channels; |  | ||||||
|  |  | ||||||
|         auto body = pdu["body"]; |  | ||||||
|         if (body.find("channels") != body.end()) |  | ||||||
|         { |  | ||||||
|             for (auto&& channel : body["channels"]) |  | ||||||
|             { |  | ||||||
|                 channels.push_back(channel); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         else if (body.find("channel") != body.end()) |  | ||||||
|         { |  | ||||||
|             channels.push_back(body["channel"]); |  | ||||||
|         } |  | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             std::stringstream ss; |  | ||||||
|             ss << "Missing channels or channel field in publish data"; |  | ||||||
|             handleError("rtm/publish", ws, pduId, ss.str()); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // add an extra channel if the config has one specified |  | ||||||
|         if (!appConfig.republishChannel.empty()) |  | ||||||
|         { |  | ||||||
|             channels.push_back(appConfig.republishChannel); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         for (auto&& channel : channels) |  | ||||||
|         { |  | ||||||
|             std::stringstream ss; |  | ||||||
|             ss << state->appkey() << "::" << channel; |  | ||||||
|  |  | ||||||
|             std::string errMsg; |  | ||||||
|             if (!state->redisClient().publish(ss.str(), pdu.dump(), errMsg)) |  | ||||||
|             { |  | ||||||
|                 std::stringstream ss; |  | ||||||
|                 ss << "Cannot publish to redis host " << errMsg; |  | ||||||
|                 handleError("rtm/publish", ws, pduId, ss.str()); |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         nlohmann::json response = { |  | ||||||
|             {"action", "rtm/publish/ok"}, {"id", pdu.value("id", 1)}, {"body", {}}}; |  | ||||||
|  |  | ||||||
|         ws.sendText(response.dump()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // |  | ||||||
|     // FIXME: this is not cancellable. We should be able to cancel the redis subscription |  | ||||||
|     // |  | ||||||
|     void handleSubscribe(std::shared_ptr<SnakeConnectionState> state, |  | ||||||
|                          ix::WebSocket& ws, |  | ||||||
|                          const AppConfig& appConfig, |  | ||||||
|                          const nlohmann::json& pdu, |  | ||||||
|                          uint64_t pduId) |  | ||||||
|     { |  | ||||||
|         std::string channel = pdu["body"]["channel"]; |  | ||||||
|         state->subscriptionId = channel; |  | ||||||
|  |  | ||||||
|         std::stringstream ss; |  | ||||||
|         ss << state->appkey() << "::" << channel; |  | ||||||
|  |  | ||||||
|         state->appChannel = ss.str(); |  | ||||||
|  |  | ||||||
|         ix::RedisClient& redisClient = state->subscriptionRedisClient; |  | ||||||
|         int port = appConfig.redisPort; |  | ||||||
|  |  | ||||||
|         auto urls = appConfig.redisHosts; |  | ||||||
|         std::string hostname(urls[0]); |  | ||||||
|  |  | ||||||
|         // Connect to redis first |  | ||||||
|         if (!redisClient.connect(hostname, port)) |  | ||||||
|         { |  | ||||||
|             std::stringstream ss; |  | ||||||
|             ss << "Cannot connect to redis host " << hostname << ":" << port; |  | ||||||
|             handleError("rtm/subscribe", ws, pduId, ss.str()); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Now authenticate, if needed |  | ||||||
|         if (!appConfig.redisPassword.empty()) |  | ||||||
|         { |  | ||||||
|             std::string authResponse; |  | ||||||
|             if (!redisClient.auth(appConfig.redisPassword, authResponse)) |  | ||||||
|             { |  | ||||||
|                 std::stringstream ss; |  | ||||||
|                 ss << "Cannot authenticated to redis"; |  | ||||||
|                 handleError("rtm/subscribe", ws, pduId, ss.str()); |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         std::string filterStr; |  | ||||||
|         if (pdu["body"].find("filter") != pdu["body"].end()) |  | ||||||
|         { |  | ||||||
|             std::string filterStr = pdu["body"]["filter"]; |  | ||||||
|         } |  | ||||||
|         state->streamSql = ix::make_unique<StreamSql>(filterStr); |  | ||||||
|         state->id = 0; |  | ||||||
|         state->onRedisSubscribeCallback = [&ws, state](const std::string& messageStr) { |  | ||||||
|             auto msg = nlohmann::json::parse(messageStr); |  | ||||||
|  |  | ||||||
|             msg = msg["body"]["message"]; |  | ||||||
|  |  | ||||||
|             if (state->streamSql->valid() && !state->streamSql->match(msg)) |  | ||||||
|             { |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             nlohmann::json response = { |  | ||||||
|                 {"action", "rtm/subscription/data"}, |  | ||||||
|                 {"id", state->id++}, |  | ||||||
|                 {"body", |  | ||||||
|                  {{"subscription_id", state->subscriptionId}, {"position", "0-0"}, {"messages", {msg}}}}}; |  | ||||||
|  |  | ||||||
|             ws.sendText(response.dump()); |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         state->onRedisSubscribeResponseCallback = [&ws, state, pduId](const std::string& redisResponse) { |  | ||||||
|             std::stringstream ss; |  | ||||||
|             ss << "Redis Response: " << redisResponse << "..."; |  | ||||||
|             ix::CoreLogger::log(ss.str().c_str()); |  | ||||||
|  |  | ||||||
|             // Success |  | ||||||
|             nlohmann::json response = {{"action", "rtm/subscribe/ok"}, |  | ||||||
|                                        {"id", pduId}, |  | ||||||
|                                        {"body", {{"subscription_id", state->subscriptionId}}}}; |  | ||||||
|             ws.sendText(response.dump()); |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         { |  | ||||||
|             std::stringstream ss; |  | ||||||
|             ss << "Subscribing to " << state->appChannel << "..."; |  | ||||||
|             ix::CoreLogger::log(ss.str().c_str()); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         auto subscription = [&redisClient, state, &ws, pduId] |  | ||||||
|         { |  | ||||||
|             if (!redisClient.subscribe(state->appChannel,  |  | ||||||
|                                        state->onRedisSubscribeResponseCallback, |  | ||||||
|                                        state->onRedisSubscribeCallback)) |  | ||||||
|             { |  | ||||||
|                 std::stringstream ss; |  | ||||||
|                 ss << "Error subscribing to channel " << state->appChannel; |  | ||||||
|                 handleError("rtm/subscribe", ws, pduId, ss.str()); |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         state->subscriptionThread = std::thread(subscription); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void handleUnSubscribe(std::shared_ptr<SnakeConnectionState> state, |  | ||||||
|                            ix::WebSocket& ws, |  | ||||||
|                            const nlohmann::json& pdu, |  | ||||||
|                            uint64_t pduId) |  | ||||||
|     { |  | ||||||
|         // extract subscription_id |  | ||||||
|         auto body = pdu["body"]; |  | ||||||
|         auto subscriptionId = body["subscription_id"]; |  | ||||||
|  |  | ||||||
|         state->stopSubScriptionThread(); |  | ||||||
|  |  | ||||||
|         nlohmann::json response = {{"action", "rtm/unsubscribe/ok"}, |  | ||||||
|                                    {"id", pduId}, |  | ||||||
|                                    {"body", {{"subscription_id", subscriptionId}}}}; |  | ||||||
|         ws.sendText(response.dump()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void processCobraMessage(std::shared_ptr<SnakeConnectionState> state, |  | ||||||
|                              ix::WebSocket& ws, |  | ||||||
|                              const AppConfig& appConfig, |  | ||||||
|                              const std::string& 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"]; |  | ||||||
|         uint64_t pduId = pdu.value("id", 1); |  | ||||||
|  |  | ||||||
|         if (action == "auth/handshake") |  | ||||||
|         { |  | ||||||
|             handleHandshake(state, ws, pdu, pduId); |  | ||||||
|         } |  | ||||||
|         else if (action == "auth/authenticate") |  | ||||||
|         { |  | ||||||
|             handleAuth(state, ws, appConfig, pdu, pduId); |  | ||||||
|         } |  | ||||||
|         else if (action == "rtm/publish") |  | ||||||
|         { |  | ||||||
|             handlePublish(state, ws, appConfig, pdu, pduId); |  | ||||||
|         } |  | ||||||
|         else if (action == "rtm/subscribe") |  | ||||||
|         { |  | ||||||
|             handleSubscribe(state, ws, appConfig, pdu, pduId); |  | ||||||
|         } |  | ||||||
|         else if (action == "rtm/unsubscribe") |  | ||||||
|         { |  | ||||||
|             handleUnSubscribe(state, ws, pdu, pduId); |  | ||||||
|         } |  | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             std::cerr << "Unhandled action: " << action << std::endl; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } // namespace snake |  | ||||||
| @@ -1,26 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXSnakeProtocol.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include <memory> |  | ||||||
| #include <string> |  | ||||||
|  |  | ||||||
| namespace ix |  | ||||||
| { |  | ||||||
|     class WebSocket; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| namespace snake |  | ||||||
| { |  | ||||||
|     class SnakeConnectionState; |  | ||||||
|     struct AppConfig; |  | ||||||
|  |  | ||||||
|     void processCobraMessage(std::shared_ptr<SnakeConnectionState> state, |  | ||||||
|                              ix::WebSocket& ws, |  | ||||||
|                              const AppConfig& appConfig, |  | ||||||
|                              const std::string& str); |  | ||||||
| } // namespace snake |  | ||||||
| @@ -1,147 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXSnakeServer.cpp |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXSnakeServer.h" |  | ||||||
|  |  | ||||||
| #include "IXAppConfig.h" |  | ||||||
| #include "IXSnakeConnectionState.h" |  | ||||||
| #include "IXSnakeProtocol.h" |  | ||||||
| #include <iostream> |  | ||||||
| #include <ixcore/utils/IXCoreLogger.h> |  | ||||||
| #include <sstream> |  | ||||||
|  |  | ||||||
|  |  | ||||||
| namespace snake |  | ||||||
| { |  | ||||||
|     SnakeServer::SnakeServer(const AppConfig& appConfig) |  | ||||||
|         : _appConfig(appConfig) |  | ||||||
|         , _server(appConfig.port, appConfig.hostname) |  | ||||||
|     { |  | ||||||
|         _server.setTLSOptions(appConfig.socketTLSOptions); |  | ||||||
|  |  | ||||||
|         if (appConfig.disablePong) |  | ||||||
|         { |  | ||||||
|             _server.disablePong(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         std::stringstream ss; |  | ||||||
|         ss << "Listening on " << appConfig.hostname << ":" << appConfig.port; |  | ||||||
|         ix::CoreLogger::log(ss.str().c_str()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // |  | ||||||
|     // Parse appkey from this uri. Won't work if multiple args are present in the uri |  | ||||||
|     // Uri: /v2?appkey=FC2F10139A2BAc53BB72D9db967b024f |  | ||||||
|     // |  | ||||||
|     std::string SnakeServer::parseAppKey(const std::string& path) |  | ||||||
|     { |  | ||||||
|         std::string::size_type idx; |  | ||||||
|  |  | ||||||
|         idx = path.rfind('='); |  | ||||||
|         if (idx != std::string::npos) |  | ||||||
|         { |  | ||||||
|             std::string appkey = path.substr(idx + 1); |  | ||||||
|             return appkey; |  | ||||||
|         } |  | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             return std::string(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool SnakeServer::run() |  | ||||||
|     { |  | ||||||
|         auto factory = []() -> std::shared_ptr<ix::ConnectionState> { |  | ||||||
|             return std::make_shared<SnakeConnectionState>(); |  | ||||||
|         }; |  | ||||||
|         _server.setConnectionStateFactory(factory); |  | ||||||
|  |  | ||||||
|         _server.setOnClientMessageCallback( |  | ||||||
|             [this](std::shared_ptr<ix::ConnectionState> connectionState, |  | ||||||
|                    ix::WebSocket& webSocket, |  | ||||||
|                    const ix::WebSocketMessagePtr& msg) { |  | ||||||
|                 auto state = std::dynamic_pointer_cast<SnakeConnectionState>(connectionState); |  | ||||||
|                 auto remoteIp = connectionState->getRemoteIp(); |  | ||||||
|              |  | ||||||
|                 std::stringstream ss; |  | ||||||
|                 ss << "[" << state->getId() << "] "; |  | ||||||
|  |  | ||||||
|                 ix::LogLevel logLevel = ix::LogLevel::Debug; |  | ||||||
|                 if (msg->type == ix::WebSocketMessageType::Open) |  | ||||||
|                 { |  | ||||||
|                     ss << "New connection" << std::endl; |  | ||||||
|                     ss << "remote ip: " << remoteIp << std::endl; |  | ||||||
|                     ss << "id: " << state->getId() << std::endl; |  | ||||||
|                     ss << "Uri: " << msg->openInfo.uri << std::endl; |  | ||||||
|                     ss << "Headers:" << std::endl; |  | ||||||
|                     for (auto it : msg->openInfo.headers) |  | ||||||
|                     { |  | ||||||
|                         ss << it.first << ": " << it.second << std::endl; |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     std::string appkey = parseAppKey(msg->openInfo.uri); |  | ||||||
|                     state->setAppkey(appkey); |  | ||||||
|  |  | ||||||
|                     // Connect to redis first |  | ||||||
|                     if (!state->redisClient().connect(_appConfig.redisHosts[0], |  | ||||||
|                                                       _appConfig.redisPort)) |  | ||||||
|                     { |  | ||||||
|                         ss << "Cannot connect to redis host" << std::endl; |  | ||||||
|                         logLevel = ix::LogLevel::Error; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 else if (msg->type == ix::WebSocketMessageType::Close) |  | ||||||
|                 { |  | ||||||
|                     ss << "Closed connection" |  | ||||||
|                        << " code " << msg->closeInfo.code << " reason " |  | ||||||
|                        << msg->closeInfo.reason << std::endl; |  | ||||||
|                 } |  | ||||||
|                 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; |  | ||||||
|                     logLevel = ix::LogLevel::Error; |  | ||||||
|                 } |  | ||||||
|                 else if (msg->type == ix::WebSocketMessageType::Fragment) |  | ||||||
|                 { |  | ||||||
|                     ss << "Received message fragment" << std::endl; |  | ||||||
|                 } |  | ||||||
|                 else if (msg->type == ix::WebSocketMessageType::Message) |  | ||||||
|                 { |  | ||||||
|                     ss << "Received " << msg->wireSize << " bytes" << " " << msg->str << std::endl; |  | ||||||
|                     processCobraMessage(state, webSocket, _appConfig, msg->str); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 ix::CoreLogger::log(ss.str().c_str(), logLevel); |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         auto res = _server.listen(); |  | ||||||
|         if (!res.first) |  | ||||||
|         { |  | ||||||
|             std::cerr << res.second << std::endl; |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         _server.start(); |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void SnakeServer::runForever() |  | ||||||
|     { |  | ||||||
|         if (run()) |  | ||||||
|         { |  | ||||||
|             _server.wait(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void SnakeServer::stop() |  | ||||||
|     { |  | ||||||
|         _server.stop(); |  | ||||||
|     } |  | ||||||
| } // namespace snake |  | ||||||
| @@ -1,31 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXSnakeServer.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "IXAppConfig.h" |  | ||||||
| #include <ixwebsocket/IXWebSocketServer.h> |  | ||||||
| #include <string> |  | ||||||
|  |  | ||||||
| namespace snake |  | ||||||
| { |  | ||||||
|     class SnakeServer |  | ||||||
|     { |  | ||||||
|     public: |  | ||||||
|         SnakeServer(const AppConfig& appConfig); |  | ||||||
|         ~SnakeServer() = default; |  | ||||||
|  |  | ||||||
|         bool run(); |  | ||||||
|         void runForever(); |  | ||||||
|         void stop(); |  | ||||||
|  |  | ||||||
|     private: |  | ||||||
|         std::string parseAppKey(const std::string& path); |  | ||||||
|  |  | ||||||
|         AppConfig _appConfig; |  | ||||||
|         ix::WebSocketServer _server; |  | ||||||
|     }; |  | ||||||
| } // namespace snake |  | ||||||
| @@ -1,63 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXStreamSql.cpp |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2020 Machine Zone, Inc. All rights reserved. |  | ||||||
|  * |  | ||||||
|  *  Super simple hacked up version of a stream sql expression, |  | ||||||
|  *  that only supports non nested field evaluation |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXStreamSql.h" |  | ||||||
| #include <sstream> |  | ||||||
| #include <iostream> |  | ||||||
|  |  | ||||||
| namespace snake |  | ||||||
| { |  | ||||||
|     StreamSql::StreamSql(const std::string& sqlFilter) |  | ||||||
|         : _valid(false) |  | ||||||
|     { |  | ||||||
|         std::string token; |  | ||||||
|         std::stringstream tokenStream(sqlFilter); |  | ||||||
|         std::vector<std::string> tokens; |  | ||||||
|  |  | ||||||
|         // Split by ' ' |  | ||||||
|         while (std::getline(tokenStream, token, ' ')) |  | ||||||
|         { |  | ||||||
|             tokens.push_back(token); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         _valid = tokens.size() == 8; |  | ||||||
|         if (!_valid) return; |  | ||||||
|  |  | ||||||
|         _field = tokens[5]; |  | ||||||
|         _operator = tokens[6]; |  | ||||||
|         _value = tokens[7]; |  | ||||||
|  |  | ||||||
|         // remove single quotes |  | ||||||
|         _value = _value.substr(1, _value.size() - 2); |  | ||||||
|  |  | ||||||
|         if (_operator == "LIKE") |  | ||||||
|         { |  | ||||||
|             _value = _value.substr(1, _value.size() - 2); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool StreamSql::valid() const |  | ||||||
|     { |  | ||||||
|         return _valid; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool StreamSql::match(const nlohmann::json& msg) |  | ||||||
|     { |  | ||||||
|         if (!_valid) return false; |  | ||||||
|  |  | ||||||
|         if (msg.find(_field) == msg.end()) |  | ||||||
|         { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         std::string value = msg[_field]; |  | ||||||
|         return value == _value; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } // namespace snake |  | ||||||
| @@ -1,29 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXStreamSql.h |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2020 Machine Zone, Inc. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include <string> |  | ||||||
| #include "nlohmann/json.hpp" |  | ||||||
|  |  | ||||||
| namespace snake |  | ||||||
| { |  | ||||||
|     class StreamSql |  | ||||||
|     { |  | ||||||
|     public: |  | ||||||
|         StreamSql(const std::string& sqlFilter = std::string()); |  | ||||||
|         ~StreamSql() = default; |  | ||||||
|  |  | ||||||
|         bool match(const nlohmann::json& msg); |  | ||||||
|         bool valid() const; |  | ||||||
|  |  | ||||||
|     private: |  | ||||||
|         std::string _field; |  | ||||||
|         std::string _operator; |  | ||||||
|         std::string _value; |  | ||||||
|         bool _valid; |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| @@ -1,14 +0,0 @@ | |||||||
| { |  | ||||||
|   "apps": { |  | ||||||
|     "FC2F10139A2BAc53BB72D9db967b024f": { |  | ||||||
|       "roles": { |  | ||||||
|         "_sub": { |  | ||||||
|           "secret": "66B1dA3ED5fA074EB5AE84Dd8CE3b5ba" |  | ||||||
|         }, |  | ||||||
|         "_pub": { |  | ||||||
|           "secret": "1c04DB8fFe76A4EeFE3E318C72d771db" |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -31,7 +31,7 @@ namespace ix | |||||||
|  |  | ||||||
|     int getAnyFreePort() |     int getAnyFreePort() | ||||||
|     { |     { | ||||||
|         int sockfd; |         socket_t sockfd; | ||||||
|         if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) |         if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) | ||||||
|         { |         { | ||||||
|             return getAnyFreePortRandom(); |             return getAnyFreePortRandom(); | ||||||
|   | |||||||
| @@ -45,7 +45,7 @@ namespace ix | |||||||
|     int poll(struct pollfd* fds, nfds_t nfds, int timeout) |     int poll(struct pollfd* fds, nfds_t nfds, int timeout) | ||||||
|     { |     { | ||||||
| #ifdef _WIN32 | #ifdef _WIN32 | ||||||
|         int maxfd = 0; |         socket_t maxfd = 0; | ||||||
|         fd_set readfds, writefds, errorfds; |         fd_set readfds, writefds, errorfds; | ||||||
|         FD_ZERO(&readfds); |         FD_ZERO(&readfds); | ||||||
|         FD_ZERO(&writefds); |         FD_ZERO(&writefds); | ||||||
|   | |||||||
| @@ -34,6 +34,12 @@ typedef unsigned long int nfds_t; | |||||||
|  |  | ||||||
| namespace ix | namespace ix | ||||||
| { | { | ||||||
|  | #ifdef _WIN32 | ||||||
|  |     typedef SOCKET socket_t; | ||||||
|  | #else | ||||||
|  |     typedef int socket_t; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|     bool initNetSystem(); |     bool initNetSystem(); | ||||||
|     bool uninitNetSystem(); |     bool uninitNetSystem(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -35,7 +35,7 @@ namespace ix | |||||||
|     { |     { | ||||||
|         errMsg = "no error"; |         errMsg = "no error"; | ||||||
|  |  | ||||||
|         int fd = socket(address->ai_family, address->ai_socktype, address->ai_protocol); |         socket_t fd = socket(address->ai_family, address->ai_socktype, address->ai_protocol); | ||||||
|         if (fd < 0) |         if (fd < 0) | ||||||
|         { |         { | ||||||
|             errMsg = "Cannot create a socket"; |             errMsg = "Cannot create a socket"; | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "IXConnectionState.h" | #include "IXConnectionState.h" | ||||||
|  | #include "IXNetSystem.h" | ||||||
| #include "IXSelectInterrupt.h" | #include "IXSelectInterrupt.h" | ||||||
| #include "IXSocketTLSOptions.h" | #include "IXSocketTLSOptions.h" | ||||||
| #include <atomic> | #include <atomic> | ||||||
| @@ -75,7 +76,7 @@ namespace ix | |||||||
|         int _addressFamily; |         int _addressFamily; | ||||||
|  |  | ||||||
|         // socket for accepting connections |         // socket for accepting connections | ||||||
|         int _serverFd; |         socket_t _serverFd; | ||||||
|  |  | ||||||
|         std::atomic<bool> _stop; |         std::atomic<bool> _stop; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,7 +12,6 @@ | |||||||
| #include "IXWebSocketOpenInfo.h" | #include "IXWebSocketOpenInfo.h" | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <string> | #include <string> | ||||||
| #include <thread> |  | ||||||
|  |  | ||||||
| namespace ix | namespace ix | ||||||
| { | { | ||||||
|   | |||||||
| @@ -146,7 +146,7 @@ namespace ix | |||||||
|  |  | ||||||
|         // Contains all messages that were fetched in the last socket read. |         // Contains all messages that were fetched in the last socket read. | ||||||
|         // This could be a mix of control messages (Close, Ping, etc...) and |         // This could be a mix of control messages (Close, Ping, etc...) and | ||||||
|         // data messages. That buffer |         // data messages. That buffer is resized | ||||||
|         std::vector<uint8_t> _rxbuf; |         std::vector<uint8_t> _rxbuf; | ||||||
|  |  | ||||||
|         // Contains all messages that are waiting to be sent |         // Contains all messages that are waiting to be sent | ||||||
|   | |||||||
| @@ -6,4 +6,4 @@ | |||||||
|  |  | ||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #define IX_WEBSOCKET_VERSION "11.0.4" | #define IX_WEBSOCKET_VERSION "11.0.8" | ||||||
|   | |||||||
| @@ -22,31 +22,31 @@ install: brew | |||||||
| # Default rule does not use python as that requires first time users to have Python3 installed | # Default rule does not use python as that requires first time users to have Python3 installed | ||||||
| # | # | ||||||
| brew: | brew: | ||||||
| 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; ninja install) | 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; ninja install) | ||||||
| 
 | 
 | ||||||
| # Docker default target. We've had problems with OpenSSL and TLS 1.3 (on the | # Docker default target. We've had problems with OpenSSL and TLS 1.3 (on the | ||||||
| # server side ?) and I can't work-around it easily, so we're using mbedtls on | # server side ?) and I can't work-around it easily, so we're using mbedtls on | ||||||
| # Linux for the SSL backend, which works great. | # Linux for the SSL backend, which works great. | ||||||
| ws_mbedtls_install: | ws_mbedtls_install: | ||||||
| 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_ZLIB=OFF -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; ninja install) | 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_ZLIB=OFF -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; ninja install) | ||||||
| 
 | 
 | ||||||
| ws: | ws: | ||||||
| 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install) | 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install) | ||||||
| 
 | 
 | ||||||
| ws_unity: | ws_unity: | ||||||
| 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install) | 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install) | ||||||
| 
 | 
 | ||||||
| ws_install: | ws_install: | ||||||
| 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 .. -DUSE_TEST=0 && ninja install) | 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. -DUSE_TEST=0 && ninja install) | ||||||
| 
 | 
 | ||||||
| ws_install_release: | ws_install_release: | ||||||
| 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 .. -DUSE_TEST=0 && ninja install) | 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. -DUSE_TEST=0 && ninja install) | ||||||
| 
 | 
 | ||||||
| ws_openssl_install: | ws_openssl_install: | ||||||
| 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_OPEN_SSL=1 .. ; ninja install) | 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_OPEN_SSL=1 .. ; ninja install) | ||||||
| 
 | 
 | ||||||
| ws_mbedtls: | ws_mbedtls: | ||||||
| 	mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; make -j 4) | 	mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; make -j 4) | ||||||
| 
 | 
 | ||||||
| ws_no_ssl: | ws_no_ssl: | ||||||
| 	mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_WS=1 .. ; make -j 4) | 	mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_WS=1 .. ; make -j 4) | ||||||
| @@ -111,27 +111,27 @@ test_server: | |||||||
| 	(cd test && npm i ws && node broadcast-server.js) | 	(cd test && npm i ws && node broadcast-server.js) | ||||||
| 
 | 
 | ||||||
| test: | test: | ||||||
| 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_TEST=1 ..) | 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 ..) | ||||||
| 	(cd build ; ninja) | 	(cd build ; ninja) | ||||||
| 	(cd build ; ninja test) | 	(cd build ; ninja test) | ||||||
| 
 | 
 | ||||||
| test_asan: | test_asan: | ||||||
| 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer") | 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer") | ||||||
| 	(cd build ; ninja) | 	(cd build ; ninja) | ||||||
| 	(cd build ; ctest -V .) | 	(cd build ; ctest -V .) | ||||||
| 
 | 
 | ||||||
| test_tsan_mbedtls: | test_tsan_mbedtls: | ||||||
| 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_MBED_TLS=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer") | 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_MBED_TLS=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer") | ||||||
| 	(cd build ; ninja) | 	(cd build ; ninja) | ||||||
| 	(cd build ; ninja test) | 	(cd build ; ninja test) | ||||||
| 
 | 
 | ||||||
| test_tsan_openssl: | test_tsan_openssl: | ||||||
| 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_OPENS_SSL=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer") | 	mkdir -p build && (cd build ; cmake -GNinja --DCMAKE_UNITY_BUILD=ON DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_OPENS_SSL=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer") | ||||||
| 	(cd build ; ninja) | 	(cd build ; ninja) | ||||||
| 	(cd build ; ninja test) | 	(cd build ; ninja test) | ||||||
| 
 | 
 | ||||||
| test_tsan_sectransport: | test_tsan_sectransport: | ||||||
| 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_OPENS_SSL=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer") | 	mkdir -p build && (cd build ; cmake -GNinja --DCMAKE_UNITY_BUILD=ON DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_OPENS_SSL=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer") | ||||||
| 	(cd build ; ninja) | 	(cd build ; ninja) | ||||||
| 	(cd build ; ninja test) | 	(cd build ; ninja test) | ||||||
| 
 | 
 | ||||||
| @@ -17,16 +17,11 @@ set (TEST_TARGET_NAMES | |||||||
|   IXWebSocketTestConnectionDisconnection |   IXWebSocketTestConnectionDisconnection | ||||||
|   IXUrlParserTest |   IXUrlParserTest | ||||||
|   IXHttpClientTest |   IXHttpClientTest | ||||||
|   IXHttpServerTest |  | ||||||
|   IXUnityBuildsTest |   IXUnityBuildsTest | ||||||
|   IXHttpTest |   IXHttpTest | ||||||
|   IXDNSLookupTest |   IXDNSLookupTest | ||||||
|   IXWebSocketSubProtocolTest |   IXWebSocketSubProtocolTest | ||||||
|   IXSentryClientTest |   # IXWebSocketBroadcastTest ## FIXME was depending on cobra / take a broadcast server from ws | ||||||
|   IXWebSocketChatTest |  | ||||||
|   IXWebSocketBroadcastTest |  | ||||||
|   IXWebSocketPerMessageDeflateCompressorTest |  | ||||||
|   IXStreamSqlTest |  | ||||||
|   IXStrCaseCompareTest |   IXStrCaseCompareTest | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -35,11 +30,17 @@ set (TEST_TARGET_NAMES | |||||||
| if (UNIX) | if (UNIX) | ||||||
|   list(APPEND TEST_TARGET_NAMES |   list(APPEND TEST_TARGET_NAMES | ||||||
|     IXWebSocketCloseTest |     IXWebSocketCloseTest | ||||||
|     IXCobraChatTest |  | ||||||
|     IXCobraMetricsPublisherTest |     # Fail on Windows in CI probably because the pathing is wrong and | ||||||
|     IXCobraToSentryBotTest |     # some resource files cannot be found | ||||||
|     IXCobraToStatsdBotTest |     IXHttpServerTest | ||||||
|     IXCobraToStdoutBotTest |     IXWebSocketChatTest | ||||||
|  |   ) | ||||||
|  | endif() | ||||||
|  |  | ||||||
|  | if (USE_ZLIB) | ||||||
|  |   list(APPEND TEST_TARGET_NAMES | ||||||
|  |     IXWebSocketPerMessageDeflateCompressorTest | ||||||
|   ) |   ) | ||||||
| endif() | endif() | ||||||
|  |  | ||||||
| @@ -52,22 +53,6 @@ endif() | |||||||
|  |  | ||||||
| # Disable tests for now that are failing or not reliable | # Disable tests for now that are failing or not reliable | ||||||
|  |  | ||||||
| find_package(JsonCpp) |  | ||||||
| if (NOT JSONCPP_FOUND) |  | ||||||
|   set(JSONCPP_SOURCES ../third_party/jsoncpp/jsoncpp.cpp) |  | ||||||
| endif() |  | ||||||
|  |  | ||||||
| if (USE_PYTHON) |  | ||||||
|   find_package(Python COMPONENTS Development) |  | ||||||
|   if (NOT Python_FOUND) |  | ||||||
|     message(FATAL_ERROR "Python3 not found") |  | ||||||
|   endif() |  | ||||||
|   message("Python_FOUND:${Python_FOUND}") |  | ||||||
|   message("Python_VERSION:${Python_VERSION}") |  | ||||||
|   message("Python_Development_FOUND:${Python_Development_FOUND}") |  | ||||||
|   message("Python_LIBRARIES:${Python_LIBRARIES}") |  | ||||||
| endif() |  | ||||||
|  |  | ||||||
| add_library(ixwebsocket_test) | add_library(ixwebsocket_test) | ||||||
| target_sources(ixwebsocket_test PRIVATE  | target_sources(ixwebsocket_test PRIVATE  | ||||||
|     ${JSONCPP_SOURCES} |     ${JSONCPP_SOURCES} | ||||||
| @@ -80,10 +65,7 @@ target_include_directories(ixwebsocket_test PRIVATE | |||||||
|   ${PROJECT_SOURCE_DIR}/Catch2/single_include |   ${PROJECT_SOURCE_DIR}/Catch2/single_include | ||||||
|   ../third_party |   ../third_party | ||||||
| ) | ) | ||||||
| target_link_libraries(ixwebsocket_test ixsnake) |  | ||||||
| target_link_libraries(ixwebsocket_test ixcobra) |  | ||||||
| target_link_libraries(ixwebsocket_test ixwebsocket) | target_link_libraries(ixwebsocket_test ixwebsocket) | ||||||
| target_link_libraries(ixwebsocket_test ixcrypto) |  | ||||||
| target_link_libraries(ixwebsocket_test spdlog) | target_link_libraries(ixwebsocket_test spdlog) | ||||||
|  |  | ||||||
| foreach(TEST_TARGET_NAME ${TEST_TARGET_NAMES}) | foreach(TEST_TARGET_NAME ${TEST_TARGET_NAMES}) | ||||||
| @@ -99,35 +81,15 @@ foreach(TEST_TARGET_NAME ${TEST_TARGET_NAMES}) | |||||||
|  |  | ||||||
|   target_compile_definitions(${TEST_TARGET_NAME} PRIVATE SPDLOG_COMPILED_LIB=1) |   target_compile_definitions(${TEST_TARGET_NAME} PRIVATE SPDLOG_COMPILED_LIB=1) | ||||||
|  |  | ||||||
|   if (NOT JSONCPP_FOUND) |  | ||||||
|     target_include_directories(${TEST_TARGET_NAME} PRIVATE ../third_party/jsoncpp) |  | ||||||
|   endif() |  | ||||||
|  |  | ||||||
|   if (APPLE AND USE_TLS) |   if (APPLE AND USE_TLS) | ||||||
|     target_link_libraries(${TEST_TARGET_NAME} "-framework foundation" "-framework security") |     target_link_libraries(${TEST_TARGET_NAME} "-framework foundation" "-framework security") | ||||||
|   endif() |   endif() | ||||||
|  |  | ||||||
|   # library with the most dependencies come first |   # library with the most dependencies come first | ||||||
|   target_link_libraries(${TEST_TARGET_NAME} ixwebsocket_test) |   target_link_libraries(${TEST_TARGET_NAME} ixwebsocket_test) | ||||||
|   target_link_libraries(${TEST_TARGET_NAME} ixbots) |  | ||||||
|   target_link_libraries(${TEST_TARGET_NAME} ixsnake) |  | ||||||
|   target_link_libraries(${TEST_TARGET_NAME} ixcobra) |  | ||||||
|   target_link_libraries(${TEST_TARGET_NAME} ixsentry) |  | ||||||
|  |  | ||||||
|   if (JSONCPP_FOUND) |  | ||||||
|     target_include_directories(${TEST_TARGET_NAME} PRIVATE ${JSONCPP_INCLUDE_DIRS}) |  | ||||||
|     target_link_libraries(${TEST_TARGET_NAME} ${JSONCPP_LIBRARIES}) |  | ||||||
|   endif() |  | ||||||
|  |  | ||||||
|   target_link_libraries(${TEST_TARGET_NAME} ixredis) |  | ||||||
|   target_link_libraries(${TEST_TARGET_NAME} ixwebsocket) |   target_link_libraries(${TEST_TARGET_NAME} ixwebsocket) | ||||||
|   target_link_libraries(${TEST_TARGET_NAME} ixcrypto) |  | ||||||
|   target_link_libraries(${TEST_TARGET_NAME} ixcore) |  | ||||||
|  |  | ||||||
|   target_link_libraries(${TEST_TARGET_NAME} spdlog) |   target_link_libraries(${TEST_TARGET_NAME} spdlog) | ||||||
|   if (USE_PYTHON) |  | ||||||
|     target_link_libraries(${TEST_TARGET_NAME} ${Python_LIBRARIES}) |  | ||||||
|   endif() |  | ||||||
|  |  | ||||||
|   add_test(NAME ${TEST_TARGET_NAME} |   add_test(NAME ${TEST_TARGET_NAME} | ||||||
|            COMMAND ${TEST_TARGET_NAME} |            COMMAND ${TEST_TARGET_NAME} | ||||||
|   | |||||||
| @@ -1,351 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  cmd_satori_chat.cpp |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2017 Machine Zone. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXTest.h" |  | ||||||
| #include "catch.hpp" |  | ||||||
| #include <chrono> |  | ||||||
| #include <iostream> |  | ||||||
| #include <ixcobra/IXCobraConnection.h> |  | ||||||
| #include <ixcrypto/IXUuid.h> |  | ||||||
| #include <ixredis/IXRedisServer.h> |  | ||||||
| #include <ixsnake/IXSnakeServer.h> |  | ||||||
|  |  | ||||||
| using namespace ix; |  | ||||||
|  |  | ||||||
| namespace |  | ||||||
| { |  | ||||||
|     std::atomic<size_t> incomingBytes(0); |  | ||||||
|     std::atomic<size_t> outgoingBytes(0); |  | ||||||
|  |  | ||||||
|     void setupTrafficTrackerCallback() |  | ||||||
|     { |  | ||||||
|         ix::CobraConnection::setTrafficTrackerCallback([](size_t size, bool incoming) { |  | ||||||
|             if (incoming) |  | ||||||
|             { |  | ||||||
|                 incomingBytes += size; |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 outgoingBytes += size; |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     class CobraChat |  | ||||||
|     { |  | ||||||
|     public: |  | ||||||
|         CobraChat(const std::string& user, |  | ||||||
|                   const std::string& session, |  | ||||||
|                   const ix::CobraConfig& config); |  | ||||||
|  |  | ||||||
|         void subscribe(const std::string& channel); |  | ||||||
|         void start(); |  | ||||||
|         void stop(); |  | ||||||
|         void run(); |  | ||||||
|         bool isReady() const; |  | ||||||
|  |  | ||||||
|         void sendMessage(const std::string& text); |  | ||||||
|         size_t getReceivedMessagesCount() const; |  | ||||||
|  |  | ||||||
|         bool hasPendingMessages() const; |  | ||||||
|         Json::Value popMessage(); |  | ||||||
|  |  | ||||||
|     private: |  | ||||||
|         std::string _user; |  | ||||||
|         std::string _session; |  | ||||||
|         ix::CobraConfig _cobraConfig; |  | ||||||
|  |  | ||||||
|         std::queue<Json::Value> _publish_queue; |  | ||||||
|         mutable std::mutex _queue_mutex; |  | ||||||
|  |  | ||||||
|         std::thread _thread; |  | ||||||
|         std::atomic<bool> _stop; |  | ||||||
|  |  | ||||||
|         ix::CobraConnection _conn; |  | ||||||
|         std::atomic<bool> _connectedAndSubscribed; |  | ||||||
|  |  | ||||||
|         std::queue<Json::Value> _receivedQueue; |  | ||||||
|  |  | ||||||
|         std::mutex _logMutex; |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     CobraChat::CobraChat(const std::string& user, |  | ||||||
|                          const std::string& session, |  | ||||||
|                          const ix::CobraConfig& config) |  | ||||||
|         : _user(user) |  | ||||||
|         , _session(session) |  | ||||||
|         , _cobraConfig(config) |  | ||||||
|         , _stop(false) |  | ||||||
|         , _connectedAndSubscribed(false) |  | ||||||
|     { |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraChat::start() |  | ||||||
|     { |  | ||||||
|         _thread = std::thread(&CobraChat::run, this); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraChat::stop() |  | ||||||
|     { |  | ||||||
|         _stop = true; |  | ||||||
|         _thread.join(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool CobraChat::isReady() const |  | ||||||
|     { |  | ||||||
|         return _connectedAndSubscribed; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     size_t CobraChat::getReceivedMessagesCount() const |  | ||||||
|     { |  | ||||||
|         return _receivedQueue.size(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool CobraChat::hasPendingMessages() const |  | ||||||
|     { |  | ||||||
|         std::unique_lock<std::mutex> lock(_queue_mutex); |  | ||||||
|         return !_publish_queue.empty(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     Json::Value CobraChat::popMessage() |  | ||||||
|     { |  | ||||||
|         std::unique_lock<std::mutex> lock(_queue_mutex); |  | ||||||
|         auto msg = _publish_queue.front(); |  | ||||||
|         _publish_queue.pop(); |  | ||||||
|         return msg; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // |  | ||||||
|     // Callback to handle received messages, that are printed on the console |  | ||||||
|     // |  | ||||||
|     void CobraChat::subscribe(const std::string& channel) |  | ||||||
|     { |  | ||||||
|         std::string filter; |  | ||||||
|         std::string position("$"); |  | ||||||
|         int batchSize = 1; |  | ||||||
|  |  | ||||||
|         _conn.subscribe(channel, |  | ||||||
|                         filter, |  | ||||||
|                         position, |  | ||||||
|                         batchSize, |  | ||||||
|                         [this](const Json::Value& msg, const std::string& /*position*/) { |  | ||||||
|                             spdlog::info("receive {}", msg.toStyledString()); |  | ||||||
|  |  | ||||||
|                             if (!msg.isObject()) return; |  | ||||||
|                             if (!msg.isMember("user")) return; |  | ||||||
|                             if (!msg.isMember("text")) return; |  | ||||||
|                             if (!msg.isMember("session")) return; |  | ||||||
|  |  | ||||||
|                             std::string msg_user = msg["user"].asString(); |  | ||||||
|                             std::string msg_text = msg["text"].asString(); |  | ||||||
|                             std::string msg_session = msg["session"].asString(); |  | ||||||
|  |  | ||||||
|                             // We are not interested in messages |  | ||||||
|                             // from a different session. |  | ||||||
|                             if (msg_session != _session) return; |  | ||||||
|  |  | ||||||
|                             // We are not interested in our own messages |  | ||||||
|                             if (msg_user == _user) return; |  | ||||||
|  |  | ||||||
|                             _receivedQueue.push(msg); |  | ||||||
|  |  | ||||||
|                             std::stringstream ss; |  | ||||||
|                             ss << std::endl |  | ||||||
|                                << msg_user << " > " << msg_text << std::endl |  | ||||||
|                                << _user << " > "; |  | ||||||
|                             log(ss.str()); |  | ||||||
|                         }); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void CobraChat::sendMessage(const std::string& text) |  | ||||||
|     { |  | ||||||
|         Json::Value msg; |  | ||||||
|         msg["user"] = _user; |  | ||||||
|         msg["session"] = _session; |  | ||||||
|         msg["text"] = text; |  | ||||||
|  |  | ||||||
|         std::unique_lock<std::mutex> lock(_queue_mutex); |  | ||||||
|         _publish_queue.push(msg); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // |  | ||||||
|     // Do satori communication on a background thread, where we can have |  | ||||||
|     // something like an event loop that publish, poll and receive data |  | ||||||
|     // |  | ||||||
|     void CobraChat::run() |  | ||||||
|     { |  | ||||||
|         std::string channel = _session; |  | ||||||
|  |  | ||||||
|         _conn.configure(_cobraConfig); |  | ||||||
|         _conn.connect(); |  | ||||||
|  |  | ||||||
|         _conn.setEventCallback([this, channel](const CobraEventPtr& event) { |  | ||||||
|             if (event->type == ix::CobraEventType::Open) |  | ||||||
|             { |  | ||||||
|                 log("Subscriber connected: " + _user); |  | ||||||
|                 for (auto&& it : event->headers) |  | ||||||
|                 { |  | ||||||
|                     log("Headers " + it.first + " " + it.second); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             else if (event->type == ix::CobraEventType::Authenticated) |  | ||||||
|             { |  | ||||||
|                 log("Subscriber authenticated: " + _user); |  | ||||||
|                 subscribe(channel); |  | ||||||
|             } |  | ||||||
|             else if (event->type == ix::CobraEventType::Error) |  | ||||||
|             { |  | ||||||
|                 log(event->errMsg + _user); |  | ||||||
|             } |  | ||||||
|             else if (event->type == ix::CobraEventType::Closed) |  | ||||||
|             { |  | ||||||
|                 log("Connection closed: " + _user); |  | ||||||
|             } |  | ||||||
|             else if (event->type == ix::CobraEventType::Subscribed) |  | ||||||
|             { |  | ||||||
|                 log("Subscription ok: " + _user + " subscription_id " + event->subscriptionId); |  | ||||||
|                 _connectedAndSubscribed = true; |  | ||||||
|             } |  | ||||||
|             else if (event->type == ix::CobraEventType::UnSubscribed) |  | ||||||
|             { |  | ||||||
|                 log("Unsubscription ok: " + _user + " subscription_id " + event->subscriptionId); |  | ||||||
|             } |  | ||||||
|             else if (event->type == ix::CobraEventType::Published) |  | ||||||
|             { |  | ||||||
|                 TLogger() << "Subscriber: published message acked: " << event->msgId; |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         while (!_stop) |  | ||||||
|         { |  | ||||||
|             { |  | ||||||
|                 while (hasPendingMessages()) |  | ||||||
|                 { |  | ||||||
|                     auto msg = popMessage(); |  | ||||||
|  |  | ||||||
|                     std::string text = msg["text"].asString(); |  | ||||||
|  |  | ||||||
|                     std::stringstream ss; |  | ||||||
|                     ss << "Sending msg [" << text << "]"; |  | ||||||
|                     log(ss.str()); |  | ||||||
|  |  | ||||||
|                     Json::Value channels; |  | ||||||
|                     channels.append(channel); |  | ||||||
|                     _conn.publish(channels, msg); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             ix::msleep(50); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         _conn.unsubscribe(channel); |  | ||||||
|  |  | ||||||
|         ix::msleep(50); |  | ||||||
|         _conn.disconnect(); |  | ||||||
|  |  | ||||||
|         _conn.setEventCallback([](const CobraEventPtr& /*event*/) {}); |  | ||||||
|     } |  | ||||||
| } // namespace |  | ||||||
|  |  | ||||||
| TEST_CASE("Cobra_chat", "[cobra_chat]") |  | ||||||
| { |  | ||||||
|     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(); |  | ||||||
|  |  | ||||||
|         int timeout; |  | ||||||
|         setupTrafficTrackerCallback(); |  | ||||||
|  |  | ||||||
|         std::string session = 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(); |  | ||||||
|  |  | ||||||
|         CobraChat chatA("jean", session, config); |  | ||||||
|         CobraChat chatB("paul", session, config); |  | ||||||
|  |  | ||||||
|         chatA.start(); |  | ||||||
|         chatB.start(); |  | ||||||
|  |  | ||||||
|         // Wait for all chat instance to be ready |  | ||||||
|         timeout = 10 * 1000; // 10s |  | ||||||
|         while (true) |  | ||||||
|         { |  | ||||||
|             if (chatA.isReady() && chatB.isReady()) break; |  | ||||||
|             ix::msleep(10); |  | ||||||
|  |  | ||||||
|             timeout -= 10; |  | ||||||
|             if (timeout <= 0) |  | ||||||
|             { |  | ||||||
|                 snakeServer.stop(); |  | ||||||
|                 redisServer.stop(); |  | ||||||
|                 REQUIRE(false); // timeout |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Add a bit of extra time, for the subscription to be active |  | ||||||
|         ix::msleep(1000); |  | ||||||
|  |  | ||||||
|         chatA.sendMessage("from A1"); |  | ||||||
|         chatA.sendMessage("from A2"); |  | ||||||
|         chatA.sendMessage("from A3"); |  | ||||||
|  |  | ||||||
|         chatB.sendMessage("from B1"); |  | ||||||
|         chatB.sendMessage("from B2"); |  | ||||||
|  |  | ||||||
|         // 1. Wait for all messages to be sent |  | ||||||
|         timeout = 10 * 1000; // 10s |  | ||||||
|         while (chatA.hasPendingMessages() || chatB.hasPendingMessages()) |  | ||||||
|         { |  | ||||||
|             ix::msleep(10); |  | ||||||
|  |  | ||||||
|             timeout -= 10; |  | ||||||
|             if (timeout <= 0) |  | ||||||
|             { |  | ||||||
|                 snakeServer.stop(); |  | ||||||
|                 redisServer.stop(); |  | ||||||
|                 REQUIRE(false); // timeout |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Give us 1s for all messages to be received |  | ||||||
|         ix::msleep(1000); |  | ||||||
|  |  | ||||||
|         chatA.stop(); |  | ||||||
|         chatB.stop(); |  | ||||||
|  |  | ||||||
|         REQUIRE(chatA.getReceivedMessagesCount() == 2); |  | ||||||
|         REQUIRE(chatB.getReceivedMessagesCount() == 3); |  | ||||||
|  |  | ||||||
|         spdlog::info("Incoming bytes {}", incomingBytes); |  | ||||||
|         spdlog::info("Outgoing bytes {}", outgoingBytes); |  | ||||||
|  |  | ||||||
|         spdlog::info("Stopping snake server..."); |  | ||||||
|         snakeServer.stop(); |  | ||||||
|  |  | ||||||
|         spdlog::info("Stopping redis server..."); |  | ||||||
|         redisServer.stop(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,303 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2018 Machine Zone. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXTest.h" |  | ||||||
| #include "catch.hpp" |  | ||||||
| #include <iostream> |  | ||||||
| #include <ixcobra/IXCobraMetricsPublisher.h> |  | ||||||
| #include <ixcrypto/IXUuid.h> |  | ||||||
| #include <ixredis/IXRedisServer.h> |  | ||||||
| #include <ixsnake/IXSnakeServer.h> |  | ||||||
| #include <set> |  | ||||||
|  |  | ||||||
| using namespace ix; |  | ||||||
|  |  | ||||||
| namespace |  | ||||||
| { |  | ||||||
|     std::atomic<size_t> incomingBytes(0); |  | ||||||
|     std::atomic<size_t> outgoingBytes(0); |  | ||||||
|  |  | ||||||
|     void setupTrafficTrackerCallback() |  | ||||||
|     { |  | ||||||
|         ix::CobraConnection::setTrafficTrackerCallback([](size_t size, bool incoming) { |  | ||||||
|             if (incoming) |  | ||||||
|             { |  | ||||||
|                 incomingBytes += size; |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 outgoingBytes += size; |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::atomic<bool> gStop; |  | ||||||
|     std::atomic<bool> gSubscriberConnectedAndSubscribed; |  | ||||||
|     std::atomic<size_t> gUniqueMessageIdsCount; |  | ||||||
|     std::atomic<int> gMessageCount; |  | ||||||
|  |  | ||||||
|     std::set<std::string> gIds; |  | ||||||
|     std::mutex gProtectIds; // std::set is no thread-safe, so protect access with this mutex. |  | ||||||
|  |  | ||||||
|     // |  | ||||||
|     // Background thread subscribe to the channel and validates what was sent |  | ||||||
|     // |  | ||||||
|     void startSubscriber(const ix::CobraConfig& config, const std::string& channel) |  | ||||||
|     { |  | ||||||
|         gSubscriberConnectedAndSubscribed = false; |  | ||||||
|         gUniqueMessageIdsCount = 0; |  | ||||||
|         gMessageCount = 0; |  | ||||||
|  |  | ||||||
|         ix::CobraConnection conn; |  | ||||||
|         conn.configure(config); |  | ||||||
|         conn.connect(); |  | ||||||
|  |  | ||||||
|         conn.setEventCallback([&conn, &channel](const CobraEventPtr& event) { |  | ||||||
|             if (event->type == ix::CobraEventType::Open) |  | ||||||
|             { |  | ||||||
|                 TLogger() << "Subscriber connected:"; |  | ||||||
|                 for (auto&& it : event->headers) |  | ||||||
|                 { |  | ||||||
|                     log("Headers " + it.first + " " + it.second); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             else if (event->type == ix::CobraEventType::Closed) |  | ||||||
|             { |  | ||||||
|                 TLogger() << "Subscriber closed:" << event->errMsg; |  | ||||||
|             } |  | ||||||
|             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; |  | ||||||
|                 std::string position("$"); |  | ||||||
|                 int batchSize = 1; |  | ||||||
|  |  | ||||||
|                 conn.subscribe(channel, |  | ||||||
|                                filter, |  | ||||||
|                                position, |  | ||||||
|                                batchSize, |  | ||||||
|                                [](const Json::Value& msg, const std::string& /*position*/) { |  | ||||||
|                                    log(msg.toStyledString()); |  | ||||||
|  |  | ||||||
|                                    std::string id = msg["id"].asString(); |  | ||||||
|                                    { |  | ||||||
|                                        std::lock_guard<std::mutex> guard(gProtectIds); |  | ||||||
|                                        gIds.insert(id); |  | ||||||
|                                    } |  | ||||||
|  |  | ||||||
|                                    gMessageCount++; |  | ||||||
|                                }); |  | ||||||
|             } |  | ||||||
|             else if (event->type == ix::CobraEventType::Subscribed) |  | ||||||
|             { |  | ||||||
|                 TLogger() << "Subscriber: subscribed to channel " << event->subscriptionId; |  | ||||||
|                 if (event->subscriptionId == channel) |  | ||||||
|                 { |  | ||||||
|                     gSubscriberConnectedAndSubscribed = true; |  | ||||||
|                 } |  | ||||||
|                 else |  | ||||||
|                 { |  | ||||||
|                     TLogger() << "Subscriber: unexpected channel " << event->subscriptionId; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             else if (event->type == ix::CobraEventType::UnSubscribed) |  | ||||||
|             { |  | ||||||
|                 TLogger() << "Subscriber: unsubscribed from channel " << event->subscriptionId; |  | ||||||
|                 if (event->subscriptionId != channel) |  | ||||||
|                 { |  | ||||||
|                     TLogger() << "Subscriber: unexpected channel " << event->subscriptionId; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             else if (event->type == ix::CobraEventType::Published) |  | ||||||
|             { |  | ||||||
|                 TLogger() << "Subscriber: published message acked: " << event->msgId; |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         while (!gStop) |  | ||||||
|         { |  | ||||||
|             std::chrono::duration<double, std::milli> duration(10); |  | ||||||
|             std::this_thread::sleep_for(duration); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         conn.unsubscribe(channel); |  | ||||||
|         conn.disconnect(); |  | ||||||
|  |  | ||||||
|         gUniqueMessageIdsCount = gIds.size(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // publish 100 messages, during roughly 100ms |  | ||||||
|     // this is used to test thread safety of CobraMetricsPublisher::push |  | ||||||
|     void runAdditionalPublisher(ix::CobraMetricsPublisher* cobraMetricsPublisher) |  | ||||||
|     { |  | ||||||
|         Json::Value data; |  | ||||||
|         data["foo"] = "bar"; |  | ||||||
|  |  | ||||||
|         for (int i = 0; i < 100; ++i) |  | ||||||
|         { |  | ||||||
|             cobraMetricsPublisher->push("sms_metric_F_id", data); |  | ||||||
|             ix::msleep(1); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } // namespace |  | ||||||
|  |  | ||||||
| TEST_CASE("Cobra_Metrics_Publisher", "[cobra]") |  | ||||||
| { |  | ||||||
|     int port = getFreePort(); |  | ||||||
|     bool preferTLS = true; |  | ||||||
|     snake::AppConfig appConfig = makeSnakeServerConfig(port, preferTLS); |  | ||||||
|  |  | ||||||
|     // 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(); |  | ||||||
|  |  | ||||||
|     setupTrafficTrackerCallback(); |  | ||||||
|  |  | ||||||
|     std::string channel = ix::generateSessionId(); |  | ||||||
|     std::string endpoint = makeCobraEndpoint(port, preferTLS); |  | ||||||
|     std::string appkey("FC2F10139A2BAc53BB72D9db967b024f"); |  | ||||||
|     std::string role = "_sub"; |  | ||||||
|     std::string secret = "66B1dA3ED5fA074EB5AE84Dd8CE3b5ba"; |  | ||||||
|  |  | ||||||
|     ix::CobraConfig config; |  | ||||||
|     config.endpoint = endpoint; |  | ||||||
|     config.appkey = appkey; |  | ||||||
|     config.rolename = role; |  | ||||||
|     config.rolesecret = secret; |  | ||||||
|     config.socketTLSOptions = makeClientTLSOptions(); |  | ||||||
|  |  | ||||||
|     gStop = false; |  | ||||||
|     std::thread subscriberThread(&startSubscriber, config, channel); |  | ||||||
|  |  | ||||||
|     int timeout = 10 * 1000; // 10s |  | ||||||
|  |  | ||||||
|     // Wait until the subscriber is ready (authenticated + subscription successful) |  | ||||||
|     while (!gSubscriberConnectedAndSubscribed) |  | ||||||
|     { |  | ||||||
|         std::chrono::duration<double, std::milli> duration(10); |  | ||||||
|         std::this_thread::sleep_for(duration); |  | ||||||
|  |  | ||||||
|         timeout -= 10; |  | ||||||
|         if (timeout <= 0) |  | ||||||
|         { |  | ||||||
|             snakeServer.stop(); |  | ||||||
|             redisServer.stop(); |  | ||||||
|             REQUIRE(false); // timeout |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     ix::CobraMetricsPublisher cobraMetricsPublisher; |  | ||||||
|     cobraMetricsPublisher.configure(config, channel); |  | ||||||
|     cobraMetricsPublisher.setSession(uuid4()); |  | ||||||
|     cobraMetricsPublisher.enable(true); |  | ||||||
|  |  | ||||||
|     Json::Value data; |  | ||||||
|     data["foo"] = "bar"; |  | ||||||
|  |  | ||||||
|     // (1) Publish without restrictions |  | ||||||
|     cobraMetricsPublisher.push("sms_metric_A_id", data); // (msg #1) |  | ||||||
|     cobraMetricsPublisher.push("sms_metric_B_id", data); // (msg #2) |  | ||||||
|  |  | ||||||
|     // (2) Restrict what is sent using a blacklist |  | ||||||
|     // Add one entry to the blacklist |  | ||||||
|     // (will send msg #3) |  | ||||||
|     cobraMetricsPublisher.setBlacklist({ |  | ||||||
|         "sms_metric_B_id" // this id will not be sent |  | ||||||
|     }); |  | ||||||
|     // (msg #4) |  | ||||||
|     cobraMetricsPublisher.push("sms_metric_A_id", data); |  | ||||||
|     // ... |  | ||||||
|     cobraMetricsPublisher.push("sms_metric_B_id", data); // this won't be sent |  | ||||||
|  |  | ||||||
|     // Reset the blacklist |  | ||||||
|     // (msg #5) |  | ||||||
|     cobraMetricsPublisher.setBlacklist({}); // 4. |  | ||||||
|  |  | ||||||
|     // (3) Restrict what is sent using rate control |  | ||||||
|  |  | ||||||
|     // (msg #6) |  | ||||||
|     cobraMetricsPublisher.setRateControl({ |  | ||||||
|         {"sms_metric_C_id", 1}, // published once per minute (60 seconds) max |  | ||||||
|     }); |  | ||||||
|     // (msg #7) |  | ||||||
|     cobraMetricsPublisher.push("sms_metric_C_id", data); |  | ||||||
|     cobraMetricsPublisher.push("sms_metric_C_id", data); // this won't be sent |  | ||||||
|  |  | ||||||
|     ix::msleep(1400); |  | ||||||
|  |  | ||||||
|     // (msg #8) |  | ||||||
|     cobraMetricsPublisher.push("sms_metric_C_id", data); // now this will be sent |  | ||||||
|  |  | ||||||
|     ix::msleep(600); // wait a bit so that the last message is sent and can be received |  | ||||||
|  |  | ||||||
|     log("Testing suspend/resume now, which will disconnect the cobraMetricsPublisher."); |  | ||||||
|  |  | ||||||
|     // Test suspend + resume |  | ||||||
|     for (int i = 0; i < 3; ++i) |  | ||||||
|     { |  | ||||||
|         cobraMetricsPublisher.suspend(); |  | ||||||
|         ix::msleep(500); |  | ||||||
|         REQUIRE(!cobraMetricsPublisher.isConnected()); // Check that we are not connected anymore |  | ||||||
|  |  | ||||||
|         cobraMetricsPublisher.push("sms_metric_D_id", data); // will not be sent this time |  | ||||||
|  |  | ||||||
|         cobraMetricsPublisher.resume(); |  | ||||||
|         ix::msleep(2000);                             // give cobra 2s to connect |  | ||||||
|         REQUIRE(cobraMetricsPublisher.isConnected()); // Check that we are connected now |  | ||||||
|  |  | ||||||
|         cobraMetricsPublisher.push("sms_metric_E_id", data); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     ix::msleep(500); |  | ||||||
|  |  | ||||||
|     // Test multi-threaded publish |  | ||||||
|     std::thread bgPublisher1(&runAdditionalPublisher, &cobraMetricsPublisher); |  | ||||||
|     std::thread bgPublisher2(&runAdditionalPublisher, &cobraMetricsPublisher); |  | ||||||
|     std::thread bgPublisher3(&runAdditionalPublisher, &cobraMetricsPublisher); |  | ||||||
|     std::thread bgPublisher4(&runAdditionalPublisher, &cobraMetricsPublisher); |  | ||||||
|     std::thread bgPublisher5(&runAdditionalPublisher, &cobraMetricsPublisher); |  | ||||||
|  |  | ||||||
|     bgPublisher1.join(); |  | ||||||
|     bgPublisher2.join(); |  | ||||||
|     bgPublisher3.join(); |  | ||||||
|     bgPublisher4.join(); |  | ||||||
|     bgPublisher5.join(); |  | ||||||
|  |  | ||||||
|     // Now stop the thread |  | ||||||
|     gStop = true; |  | ||||||
|     subscriberThread.join(); |  | ||||||
|  |  | ||||||
|     // |  | ||||||
|     // Validate that we received all message kinds, and the correct number of messages |  | ||||||
|     // |  | ||||||
|     CHECK(gIds.count("sms_metric_A_id") == 1); |  | ||||||
|     CHECK(gIds.count("sms_metric_B_id") == 1); |  | ||||||
|     CHECK(gIds.count("sms_metric_C_id") == 1); |  | ||||||
|     CHECK(gIds.count("sms_metric_D_id") == 1); |  | ||||||
|     CHECK(gIds.count("sms_metric_E_id") == 1); |  | ||||||
|     CHECK(gIds.count("sms_metric_F_id") == 1); |  | ||||||
|     CHECK(gIds.count("sms_set_rate_control_id") == 1); |  | ||||||
|     CHECK(gIds.count("sms_set_blacklist_id") == 1); |  | ||||||
|  |  | ||||||
|     spdlog::info("Incoming bytes {}", incomingBytes); |  | ||||||
|     spdlog::info("Outgoing bytes {}", outgoingBytes); |  | ||||||
|  |  | ||||||
|     spdlog::info("Stopping snake server..."); |  | ||||||
|     snakeServer.stop(); |  | ||||||
|  |  | ||||||
|     spdlog::info("Stopping redis server..."); |  | ||||||
|     redisServer.stop(); |  | ||||||
| } |  | ||||||
| @@ -1,154 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraToSentryTest.cpp |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2020 Machine Zone. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXTest.h" |  | ||||||
| #include "catch.hpp" |  | ||||||
| #include <chrono> |  | ||||||
| #include <iostream> |  | ||||||
| #include <ixbots/IXCobraToSentryBot.h> |  | ||||||
| #include <ixcobra/IXCobraConnection.h> |  | ||||||
| #include <ixcobra/IXCobraMetricsPublisher.h> |  | ||||||
| #include <ixcrypto/IXUuid.h> |  | ||||||
| #include <ixredis/IXRedisServer.h> |  | ||||||
| #include <ixsentry/IXSentryClient.h> |  | ||||||
| #include <ixsnake/IXSnakeServer.h> |  | ||||||
| #include <ixwebsocket/IXHttpServer.h> |  | ||||||
| #include <ixwebsocket/IXUserAgent.h> |  | ||||||
|  |  | ||||||
| using namespace ix; |  | ||||||
|  |  | ||||||
| namespace |  | ||||||
| { |  | ||||||
|     std::atomic<size_t> incomingBytes(0); |  | ||||||
|     std::atomic<size_t> outgoingBytes(0); |  | ||||||
|  |  | ||||||
|     void setupTrafficTrackerCallback() |  | ||||||
|     { |  | ||||||
|         ix::CobraConnection::setTrafficTrackerCallback([](size_t size, bool incoming) { |  | ||||||
|             if (incoming) |  | ||||||
|             { |  | ||||||
|                 incomingBytes += size; |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 outgoingBytes += size; |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| } // namespace |  | ||||||
|  |  | ||||||
| TEST_CASE("Cobra_to_sentry_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(); |  | ||||||
|  |  | ||||||
|         // Start a fake sentry http server |  | ||||||
|         SocketTLSOptions tlsOptionsServer = makeServerTLSOptions(true); |  | ||||||
|  |  | ||||||
|         int sentryPort = getFreePort(); |  | ||||||
|         ix::HttpServer sentryServer(sentryPort, "127.0.0.1"); |  | ||||||
|         sentryServer.setTLSOptions(tlsOptionsServer); |  | ||||||
|  |  | ||||||
|         sentryServer.setOnConnectionCallback( |  | ||||||
|             [](HttpRequestPtr request, |  | ||||||
|                std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr { |  | ||||||
|                 WebSocketHttpHeaders headers; |  | ||||||
|                 headers["Server"] = userAgent(); |  | ||||||
|  |  | ||||||
|                 // Log request |  | ||||||
|                 std::stringstream ss; |  | ||||||
|                 ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort() |  | ||||||
|                    << " " << request->method << " " << request->headers["User-Agent"] << " " |  | ||||||
|                    << request->uri; |  | ||||||
|  |  | ||||||
|                 if (request->method == "POST") |  | ||||||
|                 { |  | ||||||
|                     return std::make_shared<HttpResponse>( |  | ||||||
|                         200, "OK", HttpErrorCode::Ok, headers, std::string()); |  | ||||||
|                 } |  | ||||||
|                 else |  | ||||||
|                 { |  | ||||||
|                     return std::make_shared<HttpResponse>( |  | ||||||
|                         405, "OK", HttpErrorCode::Invalid, headers, std::string("Invalid method")); |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|  |  | ||||||
|         res = sentryServer.listen(); |  | ||||||
|         REQUIRE(res.first); |  | ||||||
|         sentryServer.start(); |  | ||||||
|  |  | ||||||
|         setupTrafficTrackerCallback(); |  | ||||||
|  |  | ||||||
|         // 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); |  | ||||||
|  |  | ||||||
|         ix::CobraBotConfig cobraBotConfig; |  | ||||||
|         cobraBotConfig.cobraConfig = config; |  | ||||||
|         cobraBotConfig.channel = channel; |  | ||||||
|         cobraBotConfig.runtime = 3; // Only run the bot for 3 seconds |  | ||||||
|         cobraBotConfig.enableHeartbeat = false; |  | ||||||
|         bool verbose = true; |  | ||||||
|  |  | ||||||
|         // FIXME: try to get this working with https instead of http |  | ||||||
|         //        to regress the TLS 1.3 OpenSSL bug |  | ||||||
|         //        -> https://github.com/openssl/openssl/issues/7967 |  | ||||||
|         // https://xxxxx:yyyyyy@sentry.io/1234567 |  | ||||||
|         std::stringstream oss; |  | ||||||
|         oss << getHttpScheme() << "xxxxxxx:yyyyyyy@localhost:" << sentryPort << "/1234567"; |  | ||||||
|         std::string dsn = oss.str(); |  | ||||||
|  |  | ||||||
|         SocketTLSOptions tlsOptionsClient = makeClientTLSOptions(); |  | ||||||
|  |  | ||||||
|         SentryClient sentryClient(dsn); |  | ||||||
|         sentryClient.setTLSOptions(tlsOptionsClient); |  | ||||||
|  |  | ||||||
|         int64_t sentCount = cobra_to_sentry_bot(cobraBotConfig, sentryClient, verbose); |  | ||||||
|         // |  | ||||||
|         // 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("Incoming bytes {}", incomingBytes); |  | ||||||
|         spdlog::info("Outgoing bytes {}", outgoingBytes); |  | ||||||
|  |  | ||||||
|         spdlog::info("Stopping snake server..."); |  | ||||||
|         snakeServer.stop(); |  | ||||||
|  |  | ||||||
|         spdlog::info("Stopping redis server..."); |  | ||||||
|         redisServer.stop(); |  | ||||||
|  |  | ||||||
|         publisherThread.join(); |  | ||||||
|         sentryServer.stop(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,101 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  IXCobraToStatsdTest.cpp |  | ||||||
|  *  Author: Benjamin Sergeant |  | ||||||
|  *  Copyright (c) 2020 Machine Zone. All rights reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "IXTest.h" |  | ||||||
| #include "catch.hpp" |  | ||||||
| #include <chrono> |  | ||||||
| #include <iostream> |  | ||||||
| #include <ixbots/IXCobraToStatsdBot.h> |  | ||||||
| #include <ixcobra/IXCobraConnection.h> |  | ||||||
| #include <ixcobra/IXCobraMetricsPublisher.h> |  | ||||||
| #include <ixcrypto/IXUuid.h> |  | ||||||
| #include <ixredis/IXRedisServer.h> |  | ||||||
| #include <ixsentry/IXSentryClient.h> |  | ||||||
| #include <ixsnake/IXSnakeServer.h> |  | ||||||
| #include <ixwebsocket/IXHttpServer.h> |  | ||||||
| #include <ixwebsocket/IXUserAgent.h> |  | ||||||
|  |  | ||||||
| using namespace ix; |  | ||||||
|  |  | ||||||
| TEST_CASE("Cobra_to_statsd_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(); |  | ||||||
|  |  | ||||||
|         // Start a fake statsd server (ultimately) |  | ||||||
|  |  | ||||||
|         // 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); |  | ||||||
|  |  | ||||||
|         ix::CobraBotConfig cobraBotConfig; |  | ||||||
|         cobraBotConfig.cobraConfig = config; |  | ||||||
|         cobraBotConfig.channel = channel; |  | ||||||
|         cobraBotConfig.runtime = 3; // Only run the bot for 3 seconds |  | ||||||
|         cobraBotConfig.enableHeartbeat = false; |  | ||||||
|  |  | ||||||
|         std::string hostname("127.0.0.1"); |  | ||||||
|         // std::string hostname("www.google.com"); |  | ||||||
|         int statsdPort = 8125; |  | ||||||
|         std::string prefix("ix.test"); |  | ||||||
|         StatsdClient statsdClient(hostname, statsdPort, prefix); |  | ||||||
|  |  | ||||||
|         std::string errMsg; |  | ||||||
|         bool initialized = statsdClient.init(errMsg); |  | ||||||
|         if (!initialized) |  | ||||||
|         { |  | ||||||
|             spdlog::error(errMsg); |  | ||||||
|         } |  | ||||||
|         REQUIRE(initialized); |  | ||||||
|  |  | ||||||
|         std::string fields("device.game\ndevice.os_name"); |  | ||||||
|         std::string gauge; |  | ||||||
|         std::string timer; |  | ||||||
|         bool verbose = true; |  | ||||||
|  |  | ||||||
|         int64_t sentCount = |  | ||||||
|             ix::cobra_to_statsd_bot(cobraBotConfig, statsdClient, fields, gauge, timer, verbose); |  | ||||||
|         // |  | ||||||
|         // 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(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,85 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  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 <ixcrypto/IXUuid.h> |  | ||||||
| #include <ixredis/IXRedisServer.h> |  | ||||||
| #include <ixsentry/IXSentryClient.h> |  | ||||||
| #include <ixsnake/IXSnakeServer.h> |  | ||||||
| #include <ixwebsocket/IXHttpServer.h> |  | ||||||
| #include <ixwebsocket/IXUserAgent.h> |  | ||||||
|  |  | ||||||
| using namespace ix; |  | ||||||
|  |  | ||||||
| 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); |  | ||||||
|  |  | ||||||
|         ix::CobraBotConfig cobraBotConfig; |  | ||||||
|         cobraBotConfig.cobraConfig = config; |  | ||||||
|         cobraBotConfig.channel = channel; |  | ||||||
|         cobraBotConfig.runtime = 3; // Only run the bot for 3 seconds |  | ||||||
|         cobraBotConfig.enableHeartbeat = false; |  | ||||||
|         bool quiet = false; |  | ||||||
|  |  | ||||||
|         cobraBotConfig.filter = |  | ||||||
|             std::string("select * from `") + channel + "` where id = 'sms_metric_A_id'"; |  | ||||||
|  |  | ||||||
|         // We could try to capture the output ... not sure how. |  | ||||||
|         bool fluentd = true; |  | ||||||
|  |  | ||||||
|         int64_t sentCount = ix::cobra_to_stdout_bot(cobraBotConfig, fluentd, quiet); |  | ||||||
|         // |  | ||||||
|         // 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(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -10,9 +10,8 @@ | |||||||
| #include <fstream> | #include <fstream> | ||||||
| #include <iomanip> | #include <iomanip> | ||||||
| #include <iostream> | #include <iostream> | ||||||
| #include <ixcobra/IXCobraMetricsPublisher.h> |  | ||||||
| #include <ixcrypto/IXUuid.h> |  | ||||||
| #include <ixwebsocket/IXNetSystem.h> | #include <ixwebsocket/IXNetSystem.h> | ||||||
|  | #include <ixwebsocket/IXUuid.h> | ||||||
| #include <ixwebsocket/IXWebSocket.h> | #include <ixwebsocket/IXWebSocket.h> | ||||||
| #include <mutex> | #include <mutex> | ||||||
| #include <random> | #include <random> | ||||||
| @@ -205,73 +204,4 @@ namespace ix | |||||||
| #endif | #endif | ||||||
|         return scheme; |         return scheme; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     snake::AppConfig makeSnakeServerConfig(int port, bool preferTLS) |  | ||||||
|     { |  | ||||||
|         snake::AppConfig appConfig; |  | ||||||
|         appConfig.port = port; |  | ||||||
|         appConfig.hostname = "127.0.0.1"; |  | ||||||
|         appConfig.verbose = true; |  | ||||||
|         appConfig.redisPort = getFreePort(); |  | ||||||
|         appConfig.redisPassword = ""; |  | ||||||
|         appConfig.redisHosts.push_back("localhost"); // only one host supported now |  | ||||||
|         appConfig.socketTLSOptions = makeServerTLSOptions(preferTLS); |  | ||||||
|  |  | ||||||
|         std::string appsConfigPath("appsConfig.json"); |  | ||||||
|  |  | ||||||
|         // Parse config file |  | ||||||
|         auto str = readAsString(appsConfigPath); |  | ||||||
|         if (str.empty()) |  | ||||||
|         { |  | ||||||
|             std::cout << "Cannot read content of " << appsConfigPath << std::endl; |  | ||||||
|             return appConfig; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         std::cout << str << std::endl; |  | ||||||
|         auto apps = nlohmann::json::parse(str); |  | ||||||
|         appConfig.apps = apps["apps"]; |  | ||||||
|  |  | ||||||
|         // Display config on the terminal for debugging |  | ||||||
|         dumpConfig(appConfig); |  | ||||||
|  |  | ||||||
|         return appConfig; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::string makeCobraEndpoint(int port, bool preferTLS) |  | ||||||
|     { |  | ||||||
|         std::stringstream ss; |  | ||||||
|         ss << getWsScheme(preferTLS) << "localhost:" << port; |  | ||||||
|         std::string endpoint = ss.str(); |  | ||||||
|  |  | ||||||
|         return endpoint; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     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 ix | } // namespace ix | ||||||
|   | |||||||
| @@ -7,8 +7,6 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include <iostream> | #include <iostream> | ||||||
| #include <ixcobra/IXCobraConfig.h> |  | ||||||
| #include <ixsnake/IXAppConfig.h> |  | ||||||
| #include <ixwebsocket/IXGetFreePort.h> | #include <ixwebsocket/IXGetFreePort.h> | ||||||
| #include <ixwebsocket/IXSocketTLSOptions.h> | #include <ixwebsocket/IXSocketTLSOptions.h> | ||||||
| #include <ixwebsocket/IXWebSocketServer.h> | #include <ixwebsocket/IXWebSocketServer.h> | ||||||
| @@ -52,14 +50,8 @@ namespace ix | |||||||
|  |  | ||||||
|     bool startWebSocketEchoServer(ix::WebSocketServer& server); |     bool startWebSocketEchoServer(ix::WebSocketServer& server); | ||||||
|  |  | ||||||
|     snake::AppConfig makeSnakeServerConfig(int port, bool preferTLS); |  | ||||||
|  |  | ||||||
|     SocketTLSOptions makeClientTLSOptions(); |     SocketTLSOptions makeClientTLSOptions(); | ||||||
|     SocketTLSOptions makeServerTLSOptions(bool preferTLS); |     SocketTLSOptions makeServerTLSOptions(bool preferTLS); | ||||||
|     std::string getHttpScheme(); |     std::string getHttpScheme(); | ||||||
|     std::string getWsScheme(bool preferTLS); |     std::string getWsScheme(bool preferTLS); | ||||||
|  |  | ||||||
|     std::string makeCobraEndpoint(int port, bool preferTLS); |  | ||||||
|  |  | ||||||
|     void runPublisher(const ix::CobraConfig& config, const std::string& channel); |  | ||||||
| } // namespace ix | } // namespace ix | ||||||
|   | |||||||
| @@ -87,12 +87,10 @@ namespace | |||||||
|  |  | ||||||
|     void WebSocketBroadcastChat::start() |     void WebSocketBroadcastChat::start() | ||||||
|     { |     { | ||||||
|  |         // | ||||||
|  |         // Which server ?? | ||||||
|  |         // | ||||||
|         std::string url; |         std::string url; | ||||||
|         { |  | ||||||
|             bool preferTLS = true; |  | ||||||
|             url = makeCobraEndpoint(_port, preferTLS); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         _webSocket.setUrl(url); |         _webSocket.setUrl(url); | ||||||
|  |  | ||||||
|         std::stringstream ss; |         std::stringstream ss; | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ | |||||||
|  |  | ||||||
| #define CATCH_CONFIG_RUNNER | #define CATCH_CONFIG_RUNNER | ||||||
| #include "catch.hpp" | #include "catch.hpp" | ||||||
| #include <ixcore/utils/IXCoreLogger.h> |  | ||||||
| #include <ixwebsocket/IXNetSystem.h> | #include <ixwebsocket/IXNetSystem.h> | ||||||
| #include <spdlog/spdlog.h> | #include <spdlog/spdlog.h> | ||||||
|  |  | ||||||
| @@ -21,42 +20,6 @@ int main(int argc, char* argv[]) | |||||||
| #ifndef _WIN32 | #ifndef _WIN32 | ||||||
|     signal(SIGPIPE, SIG_IGN); |     signal(SIGPIPE, SIG_IGN); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|     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); |  | ||||||
|     spdlog::set_level(spdlog::level::debug); |     spdlog::set_level(spdlog::level::debug); | ||||||
|  |  | ||||||
|     int result = Catch::Session().run(argc, argv); |     int result = Catch::Session().run(argc, argv); | ||||||
|   | |||||||
							
								
								
									
										316
									
								
								third_party/jsoncpp/json/json-forwards.h
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										316
									
								
								third_party/jsoncpp/json/json-forwards.h
									
									
									
									
										vendored
									
									
								
							| @@ -1,316 +0,0 @@ | |||||||
| /// Json-cpp amalgamated forward header (http://jsoncpp.sourceforge.net/). |  | ||||||
| /// It is intended to be used with #include "json/json-forwards.h" |  | ||||||
| /// This header provides forward declaration for all JsonCpp types. |  | ||||||
|  |  | ||||||
| // ////////////////////////////////////////////////////////////////////// |  | ||||||
| // Beginning of content of file: LICENSE |  | ||||||
| // ////////////////////////////////////////////////////////////////////// |  | ||||||
|  |  | ||||||
| /* |  | ||||||
| The JsonCpp library's source code, including accompanying documentation, |  | ||||||
| tests and demonstration applications, are licensed under the following |  | ||||||
| conditions... |  | ||||||
|  |  | ||||||
| Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all |  | ||||||
| jurisdictions which recognize such a disclaimer. In such jurisdictions, |  | ||||||
| this software is released into the Public Domain. |  | ||||||
|  |  | ||||||
| In jurisdictions which do not recognize Public Domain property (e.g. Germany as of |  | ||||||
| 2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and |  | ||||||
| The JsonCpp Authors, and is released under the terms of the MIT License (see below). |  | ||||||
|  |  | ||||||
| In jurisdictions which recognize Public Domain property, the user of this |  | ||||||
| software may choose to accept it either as 1) Public Domain, 2) under the |  | ||||||
| conditions of the MIT License (see below), or 3) under the terms of dual |  | ||||||
| Public Domain/MIT License conditions described here, as they choose. |  | ||||||
|  |  | ||||||
| The MIT License is about as close to Public Domain as a license can get, and is |  | ||||||
| described in clear, concise terms at: |  | ||||||
|  |  | ||||||
|    http://en.wikipedia.org/wiki/MIT_License |  | ||||||
|  |  | ||||||
| The full text of the MIT License follows: |  | ||||||
|  |  | ||||||
| ======================================================================== |  | ||||||
| Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors |  | ||||||
|  |  | ||||||
| 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. |  | ||||||
| ======================================================================== |  | ||||||
| (END LICENSE TEXT) |  | ||||||
|  |  | ||||||
| The MIT license is compatible with both the GPL and commercial |  | ||||||
| software, affording one all of the rights of Public Domain with the |  | ||||||
| minor nuisance of being required to keep the above copyright notice |  | ||||||
| and license text in the source code. Note also that by accepting the |  | ||||||
| Public Domain "license" you can re-license your copy using whatever |  | ||||||
| license you like. |  | ||||||
|  |  | ||||||
| */ |  | ||||||
|  |  | ||||||
| // ////////////////////////////////////////////////////////////////////// |  | ||||||
| // End of content of file: LICENSE |  | ||||||
| // ////////////////////////////////////////////////////////////////////// |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| #ifndef JSON_FORWARD_AMALGAMATED_H_INCLUDED |  | ||||||
| # define JSON_FORWARD_AMALGAMATED_H_INCLUDED |  | ||||||
| /// If defined, indicates that the source file is amalgamated |  | ||||||
| /// to prevent private header inclusion. |  | ||||||
| #define JSON_IS_AMALGAMATION |  | ||||||
|  |  | ||||||
| // ////////////////////////////////////////////////////////////////////// |  | ||||||
| // Beginning of content of file: include/json/config.h |  | ||||||
| // ////////////////////////////////////////////////////////////////////// |  | ||||||
|  |  | ||||||
| // Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors |  | ||||||
| // Distributed under MIT license, or public domain if desired and |  | ||||||
| // recognized in your jurisdiction. |  | ||||||
| // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE |  | ||||||
|  |  | ||||||
| #ifndef JSON_CONFIG_H_INCLUDED |  | ||||||
| #define JSON_CONFIG_H_INCLUDED |  | ||||||
| #include <cstddef> |  | ||||||
| #include <cstdint> |  | ||||||
| #include <istream> |  | ||||||
| #include <memory> |  | ||||||
| #include <ostream> |  | ||||||
| #include <sstream> |  | ||||||
| #include <string> |  | ||||||
| #include <type_traits> |  | ||||||
|  |  | ||||||
| // If non-zero, the library uses exceptions to report bad input instead of C |  | ||||||
| // assertion macros. The default is to use exceptions. |  | ||||||
| #ifndef JSON_USE_EXCEPTION |  | ||||||
| #define JSON_USE_EXCEPTION 1 |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| // Temporary, tracked for removal with issue #982. |  | ||||||
| #ifndef JSON_USE_NULLREF |  | ||||||
| #define JSON_USE_NULLREF 1 |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| /// If defined, indicates that the source file is amalgamated |  | ||||||
| /// to prevent private header inclusion. |  | ||||||
| /// Remarks: it is automatically defined in the generated amalgamated header. |  | ||||||
| // #define JSON_IS_AMALGAMATION |  | ||||||
|  |  | ||||||
| // Export macros for DLL visibility |  | ||||||
| #if defined(JSON_DLL_BUILD) |  | ||||||
| #if defined(_MSC_VER) || defined(__MINGW32__) |  | ||||||
| #define JSON_API __declspec(dllexport) |  | ||||||
| #define JSONCPP_DISABLE_DLL_INTERFACE_WARNING |  | ||||||
| #elif defined(__GNUC__) || defined(__clang__) |  | ||||||
| #define JSON_API __attribute__((visibility("default"))) |  | ||||||
| #endif // if defined(_MSC_VER) |  | ||||||
|  |  | ||||||
| #elif defined(JSON_DLL) |  | ||||||
| #if defined(_MSC_VER) || defined(__MINGW32__) |  | ||||||
| #define JSON_API __declspec(dllimport) |  | ||||||
| #define JSONCPP_DISABLE_DLL_INTERFACE_WARNING |  | ||||||
| #endif // if defined(_MSC_VER) |  | ||||||
| #endif // ifdef JSON_DLL_BUILD |  | ||||||
|  |  | ||||||
| #if !defined(JSON_API) |  | ||||||
| #define JSON_API |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #if defined(_MSC_VER) && _MSC_VER < 1800 |  | ||||||
| #error                                                                         \ |  | ||||||
|     "ERROR:  Visual Studio 12 (2013) with _MSC_VER=1800 is the oldest supported compiler with sufficient C++11 capabilities" |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #if defined(_MSC_VER) && _MSC_VER < 1900 |  | ||||||
| // As recommended at |  | ||||||
| // https://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010 |  | ||||||
| extern JSON_API int msvc_pre1900_c99_snprintf(char* outBuf, size_t size, |  | ||||||
|                                               const char* format, ...); |  | ||||||
| #define jsoncpp_snprintf msvc_pre1900_c99_snprintf |  | ||||||
| #else |  | ||||||
| #define jsoncpp_snprintf std::snprintf |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| // If JSON_NO_INT64 is defined, then Json only support C++ "int" type for |  | ||||||
| // integer |  | ||||||
| // Storages, and 64 bits integer support is disabled. |  | ||||||
| // #define JSON_NO_INT64 1 |  | ||||||
|  |  | ||||||
| // JSONCPP_OVERRIDE is maintained for backwards compatibility of external tools. |  | ||||||
| // C++11 should be used directly in JSONCPP. |  | ||||||
| #define JSONCPP_OVERRIDE override |  | ||||||
|  |  | ||||||
| #if __cplusplus >= 201103L |  | ||||||
| #define JSONCPP_NOEXCEPT noexcept |  | ||||||
| #define JSONCPP_OP_EXPLICIT explicit |  | ||||||
| #elif defined(_MSC_VER) && _MSC_VER < 1900 |  | ||||||
| #define JSONCPP_NOEXCEPT throw() |  | ||||||
| #define JSONCPP_OP_EXPLICIT explicit |  | ||||||
| #elif defined(_MSC_VER) && _MSC_VER >= 1900 |  | ||||||
| #define JSONCPP_NOEXCEPT noexcept |  | ||||||
| #define JSONCPP_OP_EXPLICIT explicit |  | ||||||
| #else |  | ||||||
| #define JSONCPP_NOEXCEPT throw() |  | ||||||
| #define JSONCPP_OP_EXPLICIT |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #ifdef __clang__ |  | ||||||
| #if __has_extension(attribute_deprecated_with_message) |  | ||||||
| #define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) |  | ||||||
| #endif |  | ||||||
| #elif defined(__GNUC__) // not clang (gcc comes later since clang emulates gcc) |  | ||||||
| #if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) |  | ||||||
| #define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) |  | ||||||
| #elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) |  | ||||||
| #define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__)) |  | ||||||
| #endif                  // GNUC version |  | ||||||
| #elif defined(_MSC_VER) // MSVC (after clang because clang on Windows emulates |  | ||||||
|                         // MSVC) |  | ||||||
| #define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) |  | ||||||
| #endif // __clang__ || __GNUC__ || _MSC_VER |  | ||||||
|  |  | ||||||
| #if !defined(JSONCPP_DEPRECATED) |  | ||||||
| #define JSONCPP_DEPRECATED(message) |  | ||||||
| #endif // if !defined(JSONCPP_DEPRECATED) |  | ||||||
|  |  | ||||||
| #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ >= 6)) |  | ||||||
| #define JSON_USE_INT64_DOUBLE_CONVERSION 1 |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #if !defined(JSON_IS_AMALGAMATION) |  | ||||||
|  |  | ||||||
| #include "allocator.h" |  | ||||||
| #include "version.h" |  | ||||||
|  |  | ||||||
| #endif // if !defined(JSON_IS_AMALGAMATION) |  | ||||||
|  |  | ||||||
| namespace Json { |  | ||||||
| using Int = int; |  | ||||||
| using UInt = unsigned int; |  | ||||||
| #if defined(JSON_NO_INT64) |  | ||||||
| using LargestInt = int; |  | ||||||
| using LargestUInt = unsigned int; |  | ||||||
| #undef JSON_HAS_INT64 |  | ||||||
| #else                 // if defined(JSON_NO_INT64) |  | ||||||
| // For Microsoft Visual use specific types as long long is not supported |  | ||||||
| #if defined(_MSC_VER) // Microsoft Visual Studio |  | ||||||
| using Int64 = __int64; |  | ||||||
| using UInt64 = unsigned __int64; |  | ||||||
| #else                 // if defined(_MSC_VER) // Other platforms, use long long |  | ||||||
| using Int64 = int64_t; |  | ||||||
| using UInt64 = uint64_t; |  | ||||||
| #endif                // if defined(_MSC_VER) |  | ||||||
| using LargestInt = Int64; |  | ||||||
| using LargestUInt = UInt64; |  | ||||||
| #define JSON_HAS_INT64 |  | ||||||
| #endif // if defined(JSON_NO_INT64) |  | ||||||
|  |  | ||||||
| template <typename T> |  | ||||||
| using Allocator = |  | ||||||
|     typename std::conditional<JSONCPP_USING_SECURE_MEMORY, SecureAllocator<T>, |  | ||||||
|                               std::allocator<T>>::type; |  | ||||||
| using String = std::basic_string<char, std::char_traits<char>, Allocator<char>>; |  | ||||||
| using IStringStream = |  | ||||||
|     std::basic_istringstream<String::value_type, String::traits_type, |  | ||||||
|                              String::allocator_type>; |  | ||||||
| using OStringStream = |  | ||||||
|     std::basic_ostringstream<String::value_type, String::traits_type, |  | ||||||
|                              String::allocator_type>; |  | ||||||
| using IStream = std::istream; |  | ||||||
| using OStream = std::ostream; |  | ||||||
| } // namespace Json |  | ||||||
|  |  | ||||||
| // Legacy names (formerly macros). |  | ||||||
| using JSONCPP_STRING = Json::String; |  | ||||||
| using JSONCPP_ISTRINGSTREAM = Json::IStringStream; |  | ||||||
| using JSONCPP_OSTRINGSTREAM = Json::OStringStream; |  | ||||||
| using JSONCPP_ISTREAM = Json::IStream; |  | ||||||
| using JSONCPP_OSTREAM = Json::OStream; |  | ||||||
|  |  | ||||||
| #endif // JSON_CONFIG_H_INCLUDED |  | ||||||
|  |  | ||||||
| // ////////////////////////////////////////////////////////////////////// |  | ||||||
| // End of content of file: include/json/config.h |  | ||||||
| // ////////////////////////////////////////////////////////////////////// |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| // ////////////////////////////////////////////////////////////////////// |  | ||||||
| // Beginning of content of file: include/json/forwards.h |  | ||||||
| // ////////////////////////////////////////////////////////////////////// |  | ||||||
|  |  | ||||||
| // Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors |  | ||||||
| // Distributed under MIT license, or public domain if desired and |  | ||||||
| // recognized in your jurisdiction. |  | ||||||
| // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE |  | ||||||
|  |  | ||||||
| #ifndef JSON_FORWARDS_H_INCLUDED |  | ||||||
| #define JSON_FORWARDS_H_INCLUDED |  | ||||||
|  |  | ||||||
| #if !defined(JSON_IS_AMALGAMATION) |  | ||||||
| #include "config.h" |  | ||||||
| #endif // if !defined(JSON_IS_AMALGAMATION) |  | ||||||
|  |  | ||||||
| namespace Json { |  | ||||||
|  |  | ||||||
| // writer.h |  | ||||||
| class StreamWriter; |  | ||||||
| class StreamWriterBuilder; |  | ||||||
| class Writer; |  | ||||||
| class FastWriter; |  | ||||||
| class StyledWriter; |  | ||||||
| class StyledStreamWriter; |  | ||||||
|  |  | ||||||
| // reader.h |  | ||||||
| class Reader; |  | ||||||
| class CharReader; |  | ||||||
| class CharReaderBuilder; |  | ||||||
|  |  | ||||||
| // json_features.h |  | ||||||
| class Features; |  | ||||||
|  |  | ||||||
| // value.h |  | ||||||
| using ArrayIndex = unsigned int; |  | ||||||
| class StaticString; |  | ||||||
| class Path; |  | ||||||
| class PathArgument; |  | ||||||
| class Value; |  | ||||||
| class ValueIteratorBase; |  | ||||||
| class ValueIterator; |  | ||||||
| class ValueConstIterator; |  | ||||||
|  |  | ||||||
| } // namespace Json |  | ||||||
|  |  | ||||||
| #endif // JSON_FORWARDS_H_INCLUDED |  | ||||||
|  |  | ||||||
| // ////////////////////////////////////////////////////////////////////// |  | ||||||
| // End of content of file: include/json/forwards.h |  | ||||||
| // ////////////////////////////////////////////////////////////////////// |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| #endif //ifndef JSON_FORWARD_AMALGAMATED_H_INCLUDED |  | ||||||
							
								
								
									
										2354
									
								
								third_party/jsoncpp/json/json.h
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2354
									
								
								third_party/jsoncpp/json/json.h
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										5304
									
								
								third_party/jsoncpp/jsoncpp.cpp
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5304
									
								
								third_party/jsoncpp/jsoncpp.cpp
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										25447
									
								
								third_party/nlohmann/json.hpp
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										25447
									
								
								third_party/nlohmann/json.hpp
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										3
									
								
								third_party/sanitizers-cmake/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								third_party/sanitizers-cmake/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +0,0 @@ | |||||||
| # out-of-source build top-level folders. |  | ||||||
| build/ |  | ||||||
| _build/ |  | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user