Compare commits
	
		
			29 Commits
		
	
	
		
			v11.0.2
			...
			bug/docker
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					e31b913280 | ||
| 
						 | 
					39cc0ed32f | ||
| 
						 | 
					22c3a7264e | ||
| 
						 | 
					ee5a2eb46e | ||
| 
						 | 
					f6e34e4b34 | ||
| 
						 | 
					d0359a1764 | ||
| 
						 | 
					8910ebcc3c | ||
| 
						 | 
					1ea3bc3666 | ||
| 
						 | 
					fe92ad205d | ||
| 
						 | 
					e4a1ac80c2 | ||
| 
						 | 
					e9dc7f7aed | ||
| 
						 | 
					cd82eed4ec | ||
| 
						 | 
					fabc07d598 | ||
| 
						 | 
					b89621fa78 | ||
| 
						 | 
					049d1eec63 | ||
| 
						 | 
					6122154f74 | ||
| 
						 | 
					0b7919834a | ||
| 
						 | 
					6035dd4c11 | ||
| 
						 | 
					1d0432c8c5 | ||
| 
						 | 
					461a645704 | ||
| 
						 | 
					93ad709dfd | ||
| 
						 | 
					2fac4bd9ef | ||
| 
						 | 
					f566fb457b | ||
| 
						 | 
					75e9c84388 | ||
| 
						 | 
					223cd41b3c | ||
| 
						 | 
					60aeaec734 | ||
| 
						 | 
					fcf114e2b2 | ||
| 
						 | 
					ea32c0e1ec | ||
| 
						 | 
					866670a906 | 
@@ -2,3 +2,4 @@ build
 | 
				
			|||||||
CMakeCache.txt
 | 
					CMakeCache.txt
 | 
				
			||||||
ws/CMakeCache.txt
 | 
					ws/CMakeCache.txt
 | 
				
			||||||
test/build
 | 
					test/build
 | 
				
			||||||
 | 
					makefile
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/unittest_linux.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/unittest_linux.yml
									
									
									
									
										vendored
									
									
								
							@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/unittest_linux_asan.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/unittest_linux_asan.yml
									
									
									
									
										vendored
									
									
								
							@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								.github/workflows/unittest_uwp.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/unittest_uwp.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_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})
 | 
					 | 
				
			||||||
@@ -17,7 +17,7 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
 | 
				
			|||||||
endif()
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (UNIX)
 | 
					if (UNIX)
 | 
				
			||||||
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
 | 
					  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -gz")
 | 
				
			||||||
endif()
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
 | 
					if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
 | 
				
			||||||
@@ -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"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										19
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								README.md
									
									
									
									
									
								
							@@ -77,6 +77,8 @@ IXWebSocket is actively being developed, check out the [changelog](https://machi
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
IXWebSocket client code is autobahn compliant beginning with the 6.0.0 version. See the current [test results](https://bsergean.github.io/autobahn/reports/clients/index.html). Some tests are still failing in the server code.
 | 
					IXWebSocket client code is autobahn compliant beginning with the 6.0.0 version. See the current [test results](https://bsergean.github.io/autobahn/reports/clients/index.html). Some tests are still failing in the server code.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Starting with the 11.0.8 release, IXWebSocket should be fully C++11 compatible.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Users
 | 
					## Users
 | 
				
			||||||
 | 
					
 | 
				
			||||||
If your company or project is using this library, feel free to open an issue or PR to amend this list.
 | 
					If your company or project is using this library, feel free to open an issue or PR to amend this list.
 | 
				
			||||||
@@ -88,6 +90,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            |
 | 
				
			||||||
@@ -100,6 +115,10 @@ If your company or project is using this library, feel free to open an issue or
 | 
				
			|||||||
| UWP               | Disabled          | None              | [![Build2][6]][0] |
 | 
					| UWP               | Disabled          | None              | [![Build2][6]][0] |
 | 
				
			||||||
| Linux             | OpenSSL           | Address Sanitizer | [![Build2][7]][0] |
 | 
					| Linux             | OpenSSL           | Address Sanitizer | [![Build2][7]][0] |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* ASAN fails on Linux because of a known problem, we need a 
 | 
				
			||||||
 | 
					* Some tests are disabled on Windows/UWP because of a pathing problem
 | 
				
			||||||
 | 
					* TLS and ZLIB are disabled on Windows/UWP because enabling make the CI run takes a lot of time, for setting up vcpkg.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[0]: https://github.com/machinezone/IXWebSocket
 | 
					[0]: https://github.com/machinezone/IXWebSocket
 | 
				
			||||||
[1]: https://github.com/machinezone/IXWebSocket/workflows/linux/badge.svg
 | 
					[1]: https://github.com/machinezone/IXWebSocket/workflows/linux/badge.svg
 | 
				
			||||||
[2]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_sectransport/badge.svg
 | 
					[2]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_sectransport/badge.svg
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										7
									
								
								build.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								build.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					#!/bin/sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rm -rf build
 | 
				
			||||||
 | 
					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=OFF .. 
 | 
				
			||||||
 | 
					ninja
 | 
				
			||||||
@@ -1,39 +1,25 @@
 | 
				
			|||||||
FROM alpine:3.12 as build
 | 
					# Build time
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Build with
 | 
				
			||||||
 | 
					# docker build --ssh default -t ws .
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# focal == ubuntu 2020.04
 | 
				
			||||||
 | 
					# groovy == ubuntu 2020.10
 | 
				
			||||||
 | 
					FROM ubuntu:groovy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN apk add --no-cache \
 | 
					RUN apt update
 | 
				
			||||||
    gcc g++ musl-dev linux-headers \
 | 
					 | 
				
			||||||
    cmake mbedtls-dev make zlib-dev python3-dev ninja git
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN addgroup -S app && \
 | 
					RUN apt-get -y install g++ cmake make automake ccache libtool flex bison pkg-config git python3 jq
 | 
				
			||||||
    adduser -S -G app app && \
 | 
					RUN apt-get -y install libjemalloc-dev libssl-dev libmcrypt-dev mcrypt zlib1g lua5.1-dev uuid-dev libz-dev binutils-dev
 | 
				
			||||||
    chown -R app:app /opt && \
 | 
					RUN apt-get -y install libboost-dev libboost-test-dev libboost-program-options-dev libboost-all-dev libboost-regex-dev 
 | 
				
			||||||
    chown -R app:app /usr/local
 | 
					RUN apt-get -y install ninja-build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# There is a bug in CMake where we cannot build from the root top folder
 | 
					COPY . /opt
 | 
				
			||||||
# So we build from /opt
 | 
					 | 
				
			||||||
COPY --chown=app:app . /opt
 | 
					 | 
				
			||||||
WORKDIR /opt
 | 
					WORKDIR /opt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
USER app
 | 
					RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts
 | 
				
			||||||
RUN make ws_mbedtls_install && \
 | 
					RUN rm -rf build
 | 
				
			||||||
    sh tools/trim_repo_for_docker.sh
 | 
					RUN --mount=type=ssh ./build.sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FROM alpine:3.12 as runtime
 | 
					COPY /opt/build/ws/ws /usr/local/bin/ws
 | 
				
			||||||
 | 
					CMD ["/usr/local/bin/ws"]
 | 
				
			||||||
RUN apk add --no-cache libstdc++ mbedtls ca-certificates python3 strace && \
 | 
					 | 
				
			||||||
    addgroup -S app && \
 | 
					 | 
				
			||||||
    adduser -S -G app app
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
COPY --chown=app:app --from=build /usr/local/bin/ws /usr/local/bin/ws
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# COPY --chown=app:app --from=build /opt /opt
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN chmod +x /usr/local/bin/ws && \
 | 
					 | 
				
			||||||
    ldd /usr/local/bin/ws
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Now run in usermode
 | 
					 | 
				
			||||||
USER app
 | 
					 | 
				
			||||||
WORKDIR /home/app
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ENTRYPOINT ["ws"]
 | 
					 | 
				
			||||||
EXPOSE 8008
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,32 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(ixwebsocket) Handle EINTR return code in ix::poll and IXSelectInterrupt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## [11.0.3] - 2020-11-16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(ixwebsocket) Fix #252 / regression in 11.0.2 with string comparisons
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## [11.0.2] - 2020-11-15
 | 
					## [11.0.2] - 2020-11-15
 | 
				
			||||||
 | 
					
 | 
				
			||||||
(ixwebsocket) use a C++11 compatible make_unique shim
 | 
					(ixwebsocket) use a C++11 compatible make_unique shim
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,45 +0,0 @@
 | 
				
			|||||||
#
 | 
					 | 
				
			||||||
# Author: Benjamin Sergeant
 | 
					 | 
				
			||||||
# Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
include(CheckCSourceCompiles)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
check_c_source_compiles("#include <regex>
 | 
					 | 
				
			||||||
                        int main()
 | 
					 | 
				
			||||||
                        { 
 | 
					 | 
				
			||||||
                          const std::regex dsnRegex;
 | 
					 | 
				
			||||||
                          std::smatch group;
 | 
					 | 
				
			||||||
                          std::regex_match(std::string(), group, dsnRegex); 
 | 
					 | 
				
			||||||
                          return 0;
 | 
					 | 
				
			||||||
                        }"
 | 
					 | 
				
			||||||
                        HAVE_STD_REGEX)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
set (IXSENTRY_SOURCES
 | 
					 | 
				
			||||||
    ixsentry/IXSentryClient.cpp
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
set (IXSENTRY_HEADERS
 | 
					 | 
				
			||||||
    ixsentry/IXSentryClient.h
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
add_library(ixsentry STATIC
 | 
					 | 
				
			||||||
  ${IXSENTRY_SOURCES}
 | 
					 | 
				
			||||||
  ${IXSENTRY_HEADERS}
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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} )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if (HAVE_STD_REGEX)
 | 
					 | 
				
			||||||
  target_compile_definitions( ixsentry PUBLIC HAVE_STD_REGEX=1 )
 | 
					 | 
				
			||||||
endif()
 | 
					 | 
				
			||||||
@@ -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);
 | 
				
			||||||
@@ -105,7 +105,22 @@ namespace ix
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return ret;
 | 
					        return ret;
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
        return ::poll(fds, nfds, timeout);
 | 
					        //
 | 
				
			||||||
 | 
					        // It was reported that on Android poll can fail and return -1 with
 | 
				
			||||||
 | 
					        // errno == EINTR, which should be a temp error and should typically
 | 
				
			||||||
 | 
					        // be handled by retrying in a loop.
 | 
				
			||||||
 | 
					        // Maybe we need to put all syscall / C functions in
 | 
				
			||||||
 | 
					        // a new IXSysCalls.cpp and wrap them all.
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        // The style from libuv is as such.
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        int ret = -1;
 | 
				
			||||||
 | 
					        do
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            ret = ::poll(fds, nfds, timeout);
 | 
				
			||||||
 | 
					        } while (ret == -1 && errno == EINTR);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return ret;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -117,8 +117,14 @@ namespace ix
 | 
				
			|||||||
        int fd = _fildes[kPipeWriteIndex];
 | 
					        int fd = _fildes[kPipeWriteIndex];
 | 
				
			||||||
        if (fd == -1) return false;
 | 
					        if (fd == -1) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ssize_t ret = -1;
 | 
				
			||||||
 | 
					        do
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            ret = ::write(fd, &value, sizeof(value));
 | 
				
			||||||
 | 
					        } while (ret == -1 && errno == EINTR);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // we should write 8 bytes for an uint64_t
 | 
					        // we should write 8 bytes for an uint64_t
 | 
				
			||||||
        return write(fd, &value, sizeof(value)) == 8;
 | 
					        return ret == 8;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO: return max uint64_t for errors ?
 | 
					    // TODO: return max uint64_t for errors ?
 | 
				
			||||||
@@ -129,7 +135,12 @@ namespace ix
 | 
				
			|||||||
        int fd = _fildes[kPipeReadIndex];
 | 
					        int fd = _fildes[kPipeReadIndex];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        uint64_t value = 0;
 | 
					        uint64_t value = 0;
 | 
				
			||||||
        ::read(fd, &value, sizeof(value));
 | 
					
 | 
				
			||||||
 | 
					        ssize_t ret = -1;
 | 
				
			||||||
 | 
					        do
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            ret = ::read(fd, &value, sizeof(value));
 | 
				
			||||||
 | 
					        } while (ret == -1 && errno == EINTR);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return value;
 | 
					        return value;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,10 +8,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "IXHttp.h"
 | 
					#include "IXHttp.h"
 | 
				
			||||||
#include "IXSocketConnect.h"
 | 
					#include "IXSocketConnect.h"
 | 
				
			||||||
 | 
					#include "IXStrCaseCompare.h"
 | 
				
			||||||
#include "IXUrlParser.h"
 | 
					#include "IXUrlParser.h"
 | 
				
			||||||
#include "IXUserAgent.h"
 | 
					#include "IXUserAgent.h"
 | 
				
			||||||
#include "IXWebSocketHandshakeKeyGen.h"
 | 
					#include "IXWebSocketHandshakeKeyGen.h"
 | 
				
			||||||
#include "IXStrCaseCompare.h"
 | 
					 | 
				
			||||||
#include <algorithm>
 | 
					#include <algorithm>
 | 
				
			||||||
#include <iostream>
 | 
					#include <iostream>
 | 
				
			||||||
#include <random>
 | 
					#include <random>
 | 
				
			||||||
@@ -36,7 +36,7 @@ namespace ix
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    bool WebSocketHandshake::insensitiveStringCompare(const std::string& a, const std::string& b)
 | 
					    bool WebSocketHandshake::insensitiveStringCompare(const std::string& a, const std::string& b)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return CaseInsensitiveLess::cmp(a, b);
 | 
					        return CaseInsensitiveLess::cmp(a, b) == 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    std::string WebSocketHandshake::genRandomString(const int len)
 | 
					    std::string WebSocketHandshake::genRandomString(const int len)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,6 @@
 | 
				
			|||||||
#include "IXWebSocketOpenInfo.h"
 | 
					#include "IXWebSocketOpenInfo.h"
 | 
				
			||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
#include <thread>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ix
 | 
					namespace ix
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -97,9 +97,10 @@ namespace ix
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        else if (_onClientMessageCallback)
 | 
					        else if (_onClientMessageCallback)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
					            WebSocket* webSocketRawPtr = webSocket.get();
 | 
				
			||||||
            webSocket->setOnMessageCallback(
 | 
					            webSocket->setOnMessageCallback(
 | 
				
			||||||
                [this, &ws = *webSocket.get(), connectionState](const WebSocketMessagePtr& msg) {
 | 
					                [this, webSocketRawPtr, connectionState](const WebSocketMessagePtr& msg) {
 | 
				
			||||||
                    _onClientMessageCallback(connectionState, ws, msg);
 | 
					                    _onClientMessageCallback(connectionState, *webSocketRawPtr, msg);
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else
 | 
					        else
 | 
				
			||||||
@@ -168,4 +169,46 @@ namespace ix
 | 
				
			|||||||
        std::lock_guard<std::mutex> lock(_clientsMutex);
 | 
					        std::lock_guard<std::mutex> lock(_clientsMutex);
 | 
				
			||||||
        return _clients.size();
 | 
					        return _clients.size();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //
 | 
				
			||||||
 | 
					    // Classic servers
 | 
				
			||||||
 | 
					    //
 | 
				
			||||||
 | 
					    void WebSocketServer::makeBroadcastServer()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        setOnClientMessageCallback(
 | 
				
			||||||
 | 
					            [this](std::shared_ptr<ConnectionState> connectionState,
 | 
				
			||||||
 | 
					                   WebSocket& webSocket,
 | 
				
			||||||
 | 
					                   const WebSocketMessagePtr& msg) {
 | 
				
			||||||
 | 
					                auto remoteIp = connectionState->getRemoteIp();
 | 
				
			||||||
 | 
					                if (msg->type == ix::WebSocketMessageType::Message)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    for (auto&& client : getClients())
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        if (client.get() != &webSocket)
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            client->send(msg->str, msg->binary);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            // Make sure the OS send buffer is flushed before moving on
 | 
				
			||||||
 | 
					                            do
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                size_t bufferedAmount = client->bufferedAmount();
 | 
				
			||||||
 | 
					                                std::chrono::duration<double, std::milli> duration(500);
 | 
				
			||||||
 | 
					                                std::this_thread::sleep_for(duration);
 | 
				
			||||||
 | 
					                            } while (client->bufferedAmount() != 0);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int WebSocketServer::listenAndStart()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        auto res = listen();
 | 
				
			||||||
 | 
					        if (!res.first)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return 1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        start();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
} // namespace ix
 | 
					} // namespace ix
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -47,6 +47,9 @@ namespace ix
 | 
				
			|||||||
        // Get all the connected clients
 | 
					        // Get all the connected clients
 | 
				
			||||||
        std::set<std::shared_ptr<WebSocket>> getClients();
 | 
					        std::set<std::shared_ptr<WebSocket>> getClients();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        void makeBroadcastServer();
 | 
				
			||||||
 | 
					        int listenAndStart();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const static int kDefaultHandShakeTimeoutSecs;
 | 
					        const static int kDefaultHandShakeTimeoutSecs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private:
 | 
					    private:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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.2"
 | 
					#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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -13,21 +13,16 @@ option(USE_TLS "Add TLS support" ON)
 | 
				
			|||||||
set (TEST_TARGET_NAMES
 | 
					set (TEST_TARGET_NAMES
 | 
				
			||||||
  IXSocketTest
 | 
					  IXSocketTest
 | 
				
			||||||
  IXSocketConnectTest
 | 
					  IXSocketConnectTest
 | 
				
			||||||
  # IXWebSocketLeakTest.cpp # commented until we have a fix for #224
 | 
					 | 
				
			||||||
  IXWebSocketServerTest
 | 
					  IXWebSocketServerTest
 | 
				
			||||||
  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
 | 
					  IXStrCaseCompareTest
 | 
				
			||||||
  IXWebSocketBroadcastTest
 | 
					 | 
				
			||||||
  IXWebSocketPerMessageDeflateCompressorTest
 | 
					 | 
				
			||||||
  IXStreamSqlTest
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Some unittest don't work on windows yet
 | 
					# Some unittest don't work on windows yet
 | 
				
			||||||
@@ -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()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -47,24 +48,11 @@ endif()
 | 
				
			|||||||
# IXWebSocketPingTest.cpp
 | 
					# IXWebSocketPingTest.cpp
 | 
				
			||||||
# IXWebSocketPingTimeoutTest.cpp
 | 
					# IXWebSocketPingTimeoutTest.cpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# IXWebSocketLeakTest.cpp # commented until we have a fix for #224 /
 | 
				
			||||||
 | 
					# that was was fixed but now the test does not compile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 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}
 | 
				
			||||||
@@ -77,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})
 | 
				
			||||||
@@ -96,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();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										47
									
								
								test/IXStrCaseCompareTest.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								test/IXStrCaseCompareTest.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 *  IXStrCaseCompareTest.cpp
 | 
				
			||||||
 | 
					 *  Author: Benjamin Sergeant
 | 
				
			||||||
 | 
					 *  Copyright (c) 2020 Machine Zone. All rights reserved.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "IXTest.h"
 | 
				
			||||||
 | 
					#include "catch.hpp"
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					#include <ixwebsocket/IXUrlParser.h>
 | 
				
			||||||
 | 
					#include <string.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace ix;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace ix
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    TEST_CASE("str_case_compare", "[str_case_compare]")
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        SECTION("1")
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            using HttpHeaders = std::map<std::string, std::string, CaseInsensitiveLess>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            HttpHeaders httpHeaders;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            httpHeaders["foo"] = "foo";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            REQUIRE(httpHeaders["foo"] == "foo");
 | 
				
			||||||
 | 
					            REQUIRE(httpHeaders["missing"] == "");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Comparison should be case insensitive
 | 
				
			||||||
 | 
					            REQUIRE(httpHeaders["Foo"] == "foo");
 | 
				
			||||||
 | 
					            REQUIRE(httpHeaders["Foo"] != "bar");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        SECTION("2")
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            using HttpHeaders = std::map<std::string, std::string, CaseInsensitiveLess>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            HttpHeaders headers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            headers["Upgrade"] = "webSocket";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            REQUIRE(!CaseInsensitiveLess::cmp(headers["upGRADE"], "webSocket"));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace ix
 | 
				
			||||||
@@ -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;
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user