From dc49af3c59255bec3090f8027fb0b3405751c060 Mon Sep 17 00:00:00 2001 From: Martin Natano Date: Sat, 11 Sep 2021 23:21:34 +0200 Subject: [PATCH] Implement NetworkManager For now there are two methods: - `NETWORK:IsUrlAllowed()`: Check whether access to a certain URL is allowed. - `NETWORK:HttpRequest()`: Perform an HTTP request. By default access to the network is disabled for all target hosts. It can be enabled by setting `HttpEnable=1` in the preferences. Individual hosts have to be added to `HttpAllowHosts` as a comma separated list to allow access. See included docs for more details on usage. --- Docs/Luadoc/Lua.xml | 23 + Docs/Luadoc/LuaDocumentation.xml | 52 + extern/CMakeLists.txt | 1 + extern/CMakeProject-ixwebsocket.cmake | 146 + extern/IXWebSocket-11.3.2/.clang-format | 47 + extern/IXWebSocket-11.3.2/.dockerignore | 5 + .../.github/workflows/docker.yml | 66 + .../.github/workflows/mkdocs.yml | 27 + .../.github/workflows/stale.yml | 19 + .../.github/workflows/unittest_linux.yml | 15 + .../.github/workflows/unittest_linux_asan.yml | 15 + .../workflows/unittest_mac_tsan_mbedtls.yml | 17 + .../workflows/unittest_mac_tsan_openssl.yml | 17 + .../unittest_mac_tsan_sectransport.yml | 15 + .../.github/workflows/unittest_uwp.yml | 45 + .../.github/workflows/unittest_windows.yml | 27 + .../workflows/unittest_windows_gcc.yml | 28 + extern/IXWebSocket-11.3.2/.gitignore | 10 + .../.pre-commit-config.yaml | 12 + .../CMake/FindDeflate.cmake | 19 + .../CMake/FindMbedTLS.cmake | 16 + .../IXWebSocket-11.3.2/CMake/FindSpdLog.cmake | 19 + extern/IXWebSocket-11.3.2/CMakeLists.txt | 302 + extern/IXWebSocket-11.3.2/Dockerfile | 1 + extern/IXWebSocket-11.3.2/LICENSE.txt | 29 + extern/IXWebSocket-11.3.2/README.md | 150 + extern/IXWebSocket-11.3.2/SECURITY.md | 11 + extern/IXWebSocket-11.3.2/appveyor.yml | 22 + extern/IXWebSocket-11.3.2/docker-compose.yml | 11 + .../docker/Dockerfile.alpine | 39 + .../docker/Dockerfile.centos | 41 + .../docker/Dockerfile.centos7 | 26 + .../docker/Dockerfile.debian | 52 + .../docker/Dockerfile.fedora | 43 + .../docker/Dockerfile.ubuntu_bionic | 23 + .../docker/Dockerfile.ubuntu_disco | 24 + .../docker/Dockerfile.ubuntu_groovy | 23 + .../docker/Dockerfile.ubuntu_precise | 27 + .../docker/Dockerfile.ubuntu_trusty | 22 + .../docker/Dockerfile.ubuntu_xenial | 24 + extern/IXWebSocket-11.3.2/docs/CHANGELOG.md | 1210 ++ extern/IXWebSocket-11.3.2/docs/build.md | 93 + extern/IXWebSocket-11.3.2/docs/design.md | 82 + extern/IXWebSocket-11.3.2/docs/index.md | 64 + extern/IXWebSocket-11.3.2/docs/packages.md | 94 + extern/IXWebSocket-11.3.2/docs/performance.md | 37 + extern/IXWebSocket-11.3.2/docs/usage.md | 601 + extern/IXWebSocket-11.3.2/docs/ws.md | 308 + extern/IXWebSocket-11.3.2/httpd.cpp | 46 + .../ixwebsocket/IXBench.cpp | 61 + .../IXWebSocket-11.3.2/ixwebsocket/IXBench.h | 32 + .../ixwebsocket/IXCancellationRequest.cpp | 35 + .../ixwebsocket/IXCancellationRequest.h | 18 + .../ixwebsocket/IXConnectionState.cpp | 73 + .../ixwebsocket/IXConnectionState.h | 54 + .../ixwebsocket/IXDNSLookup.cpp | 195 + .../ixwebsocket/IXDNSLookup.h | 67 + .../ixwebsocket/IXExponentialBackoff.cpp | 31 + .../ixwebsocket/IXExponentialBackoff.h | 16 + .../ixwebsocket/IXGetFreePort.cpp | 97 + .../ixwebsocket/IXGetFreePort.h | 12 + .../ixwebsocket/IXGzipCodec.cpp | 183 + .../ixwebsocket/IXGzipCodec.h | 15 + .../IXWebSocket-11.3.2/ixwebsocket/IXHttp.cpp | 213 + .../IXWebSocket-11.3.2/ixwebsocket/IXHttp.h | 130 + .../ixwebsocket/IXHttpClient.cpp | 751 + .../ixwebsocket/IXHttpClient.h | 123 + .../ixwebsocket/IXHttpServer.cpp | 235 + .../ixwebsocket/IXHttpServer.h | 59 + .../ixwebsocket/IXNetSystem.cpp | 296 + .../ixwebsocket/IXNetSystem.h | 86 + .../ixwebsocket/IXProgressCallback.h | 14 + .../ixwebsocket/IXSelectInterrupt.cpp | 48 + .../ixwebsocket/IXSelectInterrupt.h | 34 + .../ixwebsocket/IXSelectInterruptFactory.cpp | 26 + .../ixwebsocket/IXSelectInterruptFactory.h | 16 + .../ixwebsocket/IXSelectInterruptPipe.cpp | 161 + .../ixwebsocket/IXSelectInterruptPipe.h | 40 + .../ixwebsocket/IXSetThreadName.cpp | 83 + .../ixwebsocket/IXSetThreadName.h | 12 + .../ixwebsocket/IXSocket.cpp | 405 + .../IXWebSocket-11.3.2/ixwebsocket/IXSocket.h | 92 + .../ixwebsocket/IXSocketAppleSSL.cpp | 313 + .../ixwebsocket/IXSocketAppleSSL.h | 52 + .../ixwebsocket/IXSocketConnect.cpp | 155 + .../ixwebsocket/IXSocketConnect.h | 31 + .../ixwebsocket/IXSocketFactory.cpp | 64 + .../ixwebsocket/IXSocketFactory.h | 21 + .../ixwebsocket/IXSocketMbedTLS.cpp | 361 + .../ixwebsocket/IXSocketMbedTLS.h | 60 + .../ixwebsocket/IXSocketOpenSSL.cpp | 849 + .../ixwebsocket/IXSocketOpenSSL.h | 68 + .../ixwebsocket/IXSocketServer.cpp | 489 + .../ixwebsocket/IXSocketServer.h | 130 + .../ixwebsocket/IXSocketTLSOptions.cpp | 93 + .../ixwebsocket/IXSocketTLSOptions.h | 54 + .../ixwebsocket/IXStrCaseCompare.cpp | 37 + .../ixwebsocket/IXStrCaseCompare.h | 25 + .../ixwebsocket/IXUdpSocket.cpp | 126 + .../ixwebsocket/IXUdpSocket.h | 43 + .../ixwebsocket/IXUniquePtr.h | 18 + .../ixwebsocket/IXUrlParser.cpp | 395 + .../ixwebsocket/IXUrlParser.h | 23 + .../ixwebsocket/IXUserAgent.cpp | 89 + .../ixwebsocket/IXUserAgent.h | 14 + .../ixwebsocket/IXUtf8Validator.h | 178 + .../IXWebSocket-11.3.2/ixwebsocket/IXUuid.cpp | 75 + .../IXWebSocket-11.3.2/ixwebsocket/IXUuid.h | 17 + .../ixwebsocket/IXWebSocket.cpp | 596 + .../ixwebsocket/IXWebSocket.h | 171 + .../ixwebsocket/IXWebSocketCloseConstants.cpp | 36 + .../ixwebsocket/IXWebSocketCloseConstants.h | 37 + .../ixwebsocket/IXWebSocketCloseInfo.h | 28 + .../ixwebsocket/IXWebSocketErrorInfo.h | 22 + .../ixwebsocket/IXWebSocketHandshake.cpp | 368 + .../ixwebsocket/IXWebSocketHandshake.h | 54 + .../ixwebsocket/IXWebSocketHandshakeKeyGen.h | 171 + .../ixwebsocket/IXWebSocketHttpHeaders.cpp | 74 + .../ixwebsocket/IXWebSocketHttpHeaders.h | 23 + .../ixwebsocket/IXWebSocketInitResult.h | 36 + .../ixwebsocket/IXWebSocketMessage.h | 60 + .../ixwebsocket/IXWebSocketMessageType.h | 21 + .../ixwebsocket/IXWebSocketOpenInfo.h | 31 + .../IXWebSocketPerMessageDeflate.cpp | 91 + .../IXWebSocketPerMessageDeflate.h | 62 + .../IXWebSocketPerMessageDeflateCodec.cpp | 246 + .../IXWebSocketPerMessageDeflateCodec.h | 62 + .../IXWebSocketPerMessageDeflateOptions.cpp | 185 + .../IXWebSocketPerMessageDeflateOptions.h | 47 + .../ixwebsocket/IXWebSocketProxyServer.cpp | 137 + .../ixwebsocket/IXWebSocketProxyServer.h | 24 + .../ixwebsocket/IXWebSocketSendInfo.h | 27 + .../ixwebsocket/IXWebSocketServer.cpp | 229 + .../ixwebsocket/IXWebSocketServer.h | 77 + .../ixwebsocket/IXWebSocketTransport.cpp | 1199 ++ .../ixwebsocket/IXWebSocketTransport.h | 276 + .../ixwebsocket/IXWebSocketVersion.h | 9 + extern/IXWebSocket-11.3.2/main.cpp | 79 + extern/IXWebSocket-11.3.2/makefile.dev | 220 + extern/IXWebSocket-11.3.2/mkdocs.yml | 1 + .../test/.certs/selfsigned-client-crt.pem | 20 + .../test/.certs/selfsigned-client-key.pem | 27 + .../test/.certs/trusted-ca-crt.pem | 21 + .../test/.certs/trusted-ca-crt.srl | 1 + .../test/.certs/trusted-ca-key.pem | 27 + .../test/.certs/trusted-client-crt.pem | 20 + .../test/.certs/trusted-client-key.pem | 27 + .../test/.certs/trusted-server-crt.pem | 20 + .../test/.certs/trusted-server-key.pem | 27 + .../test/.certs/untrusted-ca-crt.pem | 21 + .../test/.certs/untrusted-ca-crt.srl | 1 + .../test/.certs/untrusted-ca-key.pem | 27 + .../test/.certs/untrusted-client-crt.pem | 20 + .../test/.certs/untrusted-client-key.pem | 27 + extern/IXWebSocket-11.3.2/test/.gitignore | 10 + extern/IXWebSocket-11.3.2/test/CMakeLists.txt | 98 + .../test/Catch2/single_include/catch.hpp | 14995 ++++++++++++++++ .../catch_reporter_automake.hpp | 62 + .../single_include/catch_reporter_tap.hpp | 255 + .../catch_reporter_teamcity.hpp | 220 + .../test/IXDNSLookupTest.cpp | 51 + .../test/IXHttpClientTest.cpp | 224 + .../test/IXHttpServerTest.cpp | 219 + extern/IXWebSocket-11.3.2/test/IXHttpTest.cpp | 53 + .../test/IXSentryClientTest.cpp | 42 + .../test/IXSocketConnectTest.cpp | 50 + .../IXWebSocket-11.3.2/test/IXSocketTest.cpp | 98 + .../test/IXStrCaseCompareTest.cpp | 47 + .../test/IXStreamSqlTest.cpp | 42 + extern/IXWebSocket-11.3.2/test/IXTest.cpp | 208 + extern/IXWebSocket-11.3.2/test/IXTest.h | 57 + .../test/IXUnityBuildsTest.cpp | 52 + .../test/IXUrlParserTest.cpp | 118 + .../test/IXWebSocketBroadcastTest.cpp | 308 + .../test/IXWebSocketChatTest.cpp | 312 + .../test/IXWebSocketCloseTest.cpp | 452 + .../test/IXWebSocketLeakTest.cpp | 183 + ...bSocketPerMessageDeflateCompressorTest.cpp | 76 + .../test/IXWebSocketPingTest.cpp | 451 + .../test/IXWebSocketPingTimeoutTest.cpp | 476 + .../test/IXWebSocketServerTest.cpp | 198 + .../test/IXWebSocketSubProtocolTest.cpp | 109 + ...IXWebSocketTestConnectionDisconnection.cpp | 165 + .../IXWebSocket-11.3.2/test/appsConfig.json | 14 + .../test/broadcast-server.js | 23 + extern/IXWebSocket-11.3.2/test/build_linux.sh | 30 + extern/IXWebSocket-11.3.2/test/cacert.pem | 4430 +++++ .../cpp/libwebsockets/devnull_client.cpp | 171 + .../test/compatibility/csharp/.gitignore | 2 + .../test/compatibility/csharp/Main.cs | 99 + .../csharp/devnull_client.csproj | 6 + .../test/compatibility/node/devnull_client.js | 42 + .../test/compatibility/node/echo_server.js | 11 + .../node/echo_server_permessagedeflate.js | 11 + .../python/websocket-client/generated_file | 1 + .../python/websocket-client/ws_send.py | 36 + .../python/websockets/DOCKER_VERSION | 1 + .../python/websockets/Dockerfile | 17 + .../compatibility/python/websockets/Procfile | 3 + .../compatibility/python/websockets/README.md | 846 + .../python/websockets/broadcast_server.py | 35 + .../python/websockets/broadcast_server_ssl.py | 41 + .../python/websockets/devnull_client.py | 44 + .../python/websockets/echo_client.py | 25 + .../python/websockets/echo_server.py | 22 + .../websockets/echo_server_interactive.py | 28 + .../websockets/echo_server_serve_once.py | 22 + .../python/websockets/echo_server_ssl.py | 27 + .../python/websockets/empty_file | 0 .../python/websockets/entrypoint.sh | 16 + .../python/websockets/localhost.pem | 1 + .../python/websockets/nginx.conf | 36 + .../python/websockets/small_file | 1 + .../python/websockets/trusted-client-crt.pem | 19 + .../python/websockets/trusted-client-key.pem | 27 + .../python/websockets/trusted-server-crt.pem | 19 + .../python/websockets/trusted-server-key.pem | 27 + .../python/websockets/vendor/protocol.py | 1432 ++ .../python/websockets/ws_proxy.py | 47 + .../python/websockets/ws_send.py | 28 + .../test/compatibility/ruby/README.md | 6 + .../test/compatibility/ruby/devnull_client.rb | 59 + extern/IXWebSocket-11.3.2/test/data/foo.txt | 1 + extern/IXWebSocket-11.3.2/test/run.py | 521 + extern/IXWebSocket-11.3.2/test/run.sh | 8 + .../IXWebSocket-11.3.2/test/test_runner.cpp | 29 + .../third_party/.clang-format | 2 + .../IXWebSocket-11.3.2/third_party/README.md | 7 + .../third_party/cli11/CLI11.hpp | 8938 +++++++++ .../third_party/cpp-linenoise/.gitignore | 44 + .../third_party/cpp-linenoise/LICENSE | 22 + .../third_party/cpp-linenoise/README.md | 95 + .../cpp-linenoise/example/CMakeLists.txt | 5 + .../cpp-linenoise/example/example.cpp | 54 + .../cpp-linenoise/example/example.sln | 28 + .../cpp-linenoise/example/example.vcxproj | 153 + .../third_party/cpp-linenoise/linenoise.cpp | 2431 +++ .../third_party/cpp-linenoise/linenoise.hpp | 131 + .../third_party/msgpack11/msgpack11.cpp | 1050 ++ .../third_party/msgpack11/msgpack11.hpp | 217 + .../remote_trailing_whitespaces.sh | 3 + .../IXWebSocket-11.3.2/tools/build_android.sh | 36 + .../tools/extract_latest_change.sh | 3 + .../tools/extract_version.sh | 3 + .../tools/trim_repo_for_docker.sh | 6 + .../tools/update_version.sh | 45 + extern/IXWebSocket-11.3.2/ws/.gitignore | 2 + extern/IXWebSocket-11.3.2/ws/CMakeLists.txt | 36 + extern/IXWebSocket-11.3.2/ws/README.md | 64 + .../IXWebSocket-11.3.2/ws/broadcast-server.js | 28 + .../IXWebSocket-11.3.2/ws/broadcast-server.py | 28 + .../ws/cobraMetricsSample.json | 1 + .../IXWebSocket-11.3.2/ws/generate_certs.sh | 104 + .../IXWebSocket-11.3.2/ws/package-lock.json | 19 + extern/IXWebSocket-11.3.2/ws/proxyConfig.json | 6 + extern/IXWebSocket-11.3.2/ws/test_ws.sh | 84 + extern/IXWebSocket-11.3.2/ws/test_ws_proxy.sh | 39 + extern/IXWebSocket-11.3.2/ws/test_ws_redis.sh | 25 + extern/IXWebSocket-11.3.2/ws/ws.cpp | 2908 +++ src/CMakeData-singletons.cmake | 2 + src/CMakeLists.txt | 2 + src/Makefile.am | 1 + src/NetworkManager.cpp | 422 + src/NetworkManager.h | 105 + src/StepMania.cpp | 3 + 265 files changed, 61571 insertions(+) create mode 100644 extern/CMakeProject-ixwebsocket.cmake create mode 100644 extern/IXWebSocket-11.3.2/.clang-format create mode 100644 extern/IXWebSocket-11.3.2/.dockerignore create mode 100644 extern/IXWebSocket-11.3.2/.github/workflows/docker.yml create mode 100644 extern/IXWebSocket-11.3.2/.github/workflows/mkdocs.yml create mode 100644 extern/IXWebSocket-11.3.2/.github/workflows/stale.yml create mode 100644 extern/IXWebSocket-11.3.2/.github/workflows/unittest_linux.yml create mode 100644 extern/IXWebSocket-11.3.2/.github/workflows/unittest_linux_asan.yml create mode 100644 extern/IXWebSocket-11.3.2/.github/workflows/unittest_mac_tsan_mbedtls.yml create mode 100644 extern/IXWebSocket-11.3.2/.github/workflows/unittest_mac_tsan_openssl.yml create mode 100644 extern/IXWebSocket-11.3.2/.github/workflows/unittest_mac_tsan_sectransport.yml create mode 100644 extern/IXWebSocket-11.3.2/.github/workflows/unittest_uwp.yml create mode 100644 extern/IXWebSocket-11.3.2/.github/workflows/unittest_windows.yml create mode 100644 extern/IXWebSocket-11.3.2/.github/workflows/unittest_windows_gcc.yml create mode 100644 extern/IXWebSocket-11.3.2/.gitignore create mode 100644 extern/IXWebSocket-11.3.2/.pre-commit-config.yaml create mode 100644 extern/IXWebSocket-11.3.2/CMake/FindDeflate.cmake create mode 100644 extern/IXWebSocket-11.3.2/CMake/FindMbedTLS.cmake create mode 100644 extern/IXWebSocket-11.3.2/CMake/FindSpdLog.cmake create mode 100644 extern/IXWebSocket-11.3.2/CMakeLists.txt create mode 120000 extern/IXWebSocket-11.3.2/Dockerfile create mode 100644 extern/IXWebSocket-11.3.2/LICENSE.txt create mode 100644 extern/IXWebSocket-11.3.2/README.md create mode 100644 extern/IXWebSocket-11.3.2/SECURITY.md create mode 100644 extern/IXWebSocket-11.3.2/appveyor.yml create mode 100644 extern/IXWebSocket-11.3.2/docker-compose.yml create mode 100644 extern/IXWebSocket-11.3.2/docker/Dockerfile.alpine create mode 100644 extern/IXWebSocket-11.3.2/docker/Dockerfile.centos create mode 100644 extern/IXWebSocket-11.3.2/docker/Dockerfile.centos7 create mode 100644 extern/IXWebSocket-11.3.2/docker/Dockerfile.debian create mode 100644 extern/IXWebSocket-11.3.2/docker/Dockerfile.fedora create mode 100644 extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_bionic create mode 100644 extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_disco create mode 100644 extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_groovy create mode 100644 extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_precise create mode 100644 extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_trusty create mode 100644 extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_xenial create mode 100644 extern/IXWebSocket-11.3.2/docs/CHANGELOG.md create mode 100644 extern/IXWebSocket-11.3.2/docs/build.md create mode 100644 extern/IXWebSocket-11.3.2/docs/design.md create mode 100644 extern/IXWebSocket-11.3.2/docs/index.md create mode 100644 extern/IXWebSocket-11.3.2/docs/packages.md create mode 100644 extern/IXWebSocket-11.3.2/docs/performance.md create mode 100644 extern/IXWebSocket-11.3.2/docs/usage.md create mode 100644 extern/IXWebSocket-11.3.2/docs/ws.md create mode 100644 extern/IXWebSocket-11.3.2/httpd.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXBench.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXBench.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXCancellationRequest.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXCancellationRequest.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXConnectionState.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXConnectionState.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXDNSLookup.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXDNSLookup.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXExponentialBackoff.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXExponentialBackoff.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXGetFreePort.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXGetFreePort.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXGzipCodec.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXGzipCodec.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXHttp.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXHttp.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXHttpClient.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXHttpClient.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXHttpServer.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXHttpServer.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXNetSystem.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXNetSystem.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXProgressCallback.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterrupt.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterrupt.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterruptFactory.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterruptFactory.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterruptPipe.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterruptPipe.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSetThreadName.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSetThreadName.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSocket.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSocket.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketAppleSSL.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketAppleSSL.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketConnect.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketConnect.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketFactory.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketFactory.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketMbedTLS.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketMbedTLS.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketOpenSSL.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketOpenSSL.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketServer.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketServer.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketTLSOptions.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketTLSOptions.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXStrCaseCompare.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXStrCaseCompare.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXUdpSocket.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXUdpSocket.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXUniquePtr.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXUrlParser.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXUrlParser.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXUserAgent.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXUserAgent.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXUtf8Validator.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXUuid.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXUuid.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocket.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocket.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketCloseConstants.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketCloseConstants.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketCloseInfo.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketErrorInfo.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketHandshake.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketHandshake.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketHandshakeKeyGen.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketHttpHeaders.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketHttpHeaders.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketInitResult.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketMessage.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketMessageType.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketOpenInfo.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflate.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflate.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflateCodec.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflateOptions.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketProxyServer.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketProxyServer.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketSendInfo.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketServer.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketServer.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketTransport.cpp create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketTransport.h create mode 100644 extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketVersion.h create mode 100644 extern/IXWebSocket-11.3.2/main.cpp create mode 100644 extern/IXWebSocket-11.3.2/makefile.dev create mode 100644 extern/IXWebSocket-11.3.2/mkdocs.yml create mode 100644 extern/IXWebSocket-11.3.2/test/.certs/selfsigned-client-crt.pem create mode 100644 extern/IXWebSocket-11.3.2/test/.certs/selfsigned-client-key.pem create mode 100644 extern/IXWebSocket-11.3.2/test/.certs/trusted-ca-crt.pem create mode 100644 extern/IXWebSocket-11.3.2/test/.certs/trusted-ca-crt.srl create mode 100644 extern/IXWebSocket-11.3.2/test/.certs/trusted-ca-key.pem create mode 100644 extern/IXWebSocket-11.3.2/test/.certs/trusted-client-crt.pem create mode 100644 extern/IXWebSocket-11.3.2/test/.certs/trusted-client-key.pem create mode 100644 extern/IXWebSocket-11.3.2/test/.certs/trusted-server-crt.pem create mode 100644 extern/IXWebSocket-11.3.2/test/.certs/trusted-server-key.pem create mode 100644 extern/IXWebSocket-11.3.2/test/.certs/untrusted-ca-crt.pem create mode 100644 extern/IXWebSocket-11.3.2/test/.certs/untrusted-ca-crt.srl create mode 100644 extern/IXWebSocket-11.3.2/test/.certs/untrusted-ca-key.pem create mode 100644 extern/IXWebSocket-11.3.2/test/.certs/untrusted-client-crt.pem create mode 100644 extern/IXWebSocket-11.3.2/test/.certs/untrusted-client-key.pem create mode 100644 extern/IXWebSocket-11.3.2/test/.gitignore create mode 100644 extern/IXWebSocket-11.3.2/test/CMakeLists.txt create mode 100644 extern/IXWebSocket-11.3.2/test/Catch2/single_include/catch.hpp create mode 100644 extern/IXWebSocket-11.3.2/test/Catch2/single_include/catch_reporter_automake.hpp create mode 100644 extern/IXWebSocket-11.3.2/test/Catch2/single_include/catch_reporter_tap.hpp create mode 100644 extern/IXWebSocket-11.3.2/test/Catch2/single_include/catch_reporter_teamcity.hpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXDNSLookupTest.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXHttpClientTest.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXHttpServerTest.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXHttpTest.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXSentryClientTest.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXSocketConnectTest.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXSocketTest.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXStrCaseCompareTest.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXStreamSqlTest.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXTest.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXTest.h create mode 100644 extern/IXWebSocket-11.3.2/test/IXUnityBuildsTest.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXUrlParserTest.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXWebSocketBroadcastTest.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXWebSocketChatTest.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXWebSocketCloseTest.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXWebSocketLeakTest.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXWebSocketPerMessageDeflateCompressorTest.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXWebSocketPingTest.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXWebSocketPingTimeoutTest.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXWebSocketServerTest.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXWebSocketSubProtocolTest.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/IXWebSocketTestConnectionDisconnection.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/appsConfig.json create mode 100644 extern/IXWebSocket-11.3.2/test/broadcast-server.js create mode 100644 extern/IXWebSocket-11.3.2/test/build_linux.sh create mode 100644 extern/IXWebSocket-11.3.2/test/cacert.pem create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/cpp/libwebsockets/devnull_client.cpp create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/csharp/.gitignore create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/csharp/Main.cs create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/csharp/devnull_client.csproj create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/node/devnull_client.js create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/node/echo_server.js create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/node/echo_server_permessagedeflate.js create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websocket-client/generated_file create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websocket-client/ws_send.py create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/DOCKER_VERSION create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/Dockerfile create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/Procfile create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/README.md create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/broadcast_server.py create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/broadcast_server_ssl.py create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/devnull_client.py create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/echo_client.py create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/echo_server.py create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/echo_server_interactive.py create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/echo_server_serve_once.py create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/echo_server_ssl.py create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/empty_file create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/entrypoint.sh create mode 120000 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/localhost.pem create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/nginx.conf create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/small_file create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/trusted-client-crt.pem create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/trusted-client-key.pem create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/trusted-server-crt.pem create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/trusted-server-key.pem create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/vendor/protocol.py create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/ws_proxy.py create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/ws_send.py create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/ruby/README.md create mode 100644 extern/IXWebSocket-11.3.2/test/compatibility/ruby/devnull_client.rb create mode 100644 extern/IXWebSocket-11.3.2/test/data/foo.txt create mode 100755 extern/IXWebSocket-11.3.2/test/run.py create mode 100644 extern/IXWebSocket-11.3.2/test/run.sh create mode 100644 extern/IXWebSocket-11.3.2/test/test_runner.cpp create mode 100644 extern/IXWebSocket-11.3.2/third_party/.clang-format create mode 100644 extern/IXWebSocket-11.3.2/third_party/README.md create mode 100644 extern/IXWebSocket-11.3.2/third_party/cli11/CLI11.hpp create mode 100644 extern/IXWebSocket-11.3.2/third_party/cpp-linenoise/.gitignore create mode 100644 extern/IXWebSocket-11.3.2/third_party/cpp-linenoise/LICENSE create mode 100644 extern/IXWebSocket-11.3.2/third_party/cpp-linenoise/README.md create mode 100644 extern/IXWebSocket-11.3.2/third_party/cpp-linenoise/example/CMakeLists.txt create mode 100644 extern/IXWebSocket-11.3.2/third_party/cpp-linenoise/example/example.cpp create mode 100644 extern/IXWebSocket-11.3.2/third_party/cpp-linenoise/example/example.sln create mode 100644 extern/IXWebSocket-11.3.2/third_party/cpp-linenoise/example/example.vcxproj create mode 100644 extern/IXWebSocket-11.3.2/third_party/cpp-linenoise/linenoise.cpp create mode 100644 extern/IXWebSocket-11.3.2/third_party/cpp-linenoise/linenoise.hpp create mode 100644 extern/IXWebSocket-11.3.2/third_party/msgpack11/msgpack11.cpp create mode 100644 extern/IXWebSocket-11.3.2/third_party/msgpack11/msgpack11.hpp create mode 100644 extern/IXWebSocket-11.3.2/third_party/remote_trailing_whitespaces.sh create mode 100644 extern/IXWebSocket-11.3.2/tools/build_android.sh create mode 100644 extern/IXWebSocket-11.3.2/tools/extract_latest_change.sh create mode 100644 extern/IXWebSocket-11.3.2/tools/extract_version.sh create mode 100644 extern/IXWebSocket-11.3.2/tools/trim_repo_for_docker.sh create mode 100755 extern/IXWebSocket-11.3.2/tools/update_version.sh create mode 100644 extern/IXWebSocket-11.3.2/ws/.gitignore create mode 100644 extern/IXWebSocket-11.3.2/ws/CMakeLists.txt create mode 100644 extern/IXWebSocket-11.3.2/ws/README.md create mode 100644 extern/IXWebSocket-11.3.2/ws/broadcast-server.js create mode 100644 extern/IXWebSocket-11.3.2/ws/broadcast-server.py create mode 100644 extern/IXWebSocket-11.3.2/ws/cobraMetricsSample.json create mode 100755 extern/IXWebSocket-11.3.2/ws/generate_certs.sh create mode 100644 extern/IXWebSocket-11.3.2/ws/package-lock.json create mode 100644 extern/IXWebSocket-11.3.2/ws/proxyConfig.json create mode 100644 extern/IXWebSocket-11.3.2/ws/test_ws.sh create mode 100644 extern/IXWebSocket-11.3.2/ws/test_ws_proxy.sh create mode 100644 extern/IXWebSocket-11.3.2/ws/test_ws_redis.sh create mode 100644 extern/IXWebSocket-11.3.2/ws/ws.cpp create mode 100644 src/NetworkManager.cpp create mode 100644 src/NetworkManager.h diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml index 292e953e92..65dcfc468d 100644 --- a/Docs/Luadoc/Lua.xml +++ b/Docs/Luadoc/Lua.xml @@ -1107,6 +1107,10 @@ + + + + @@ -2262,6 +2266,7 @@ + @@ -2474,6 +2479,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml index 65ebc05efd..a7be9c3084 100644 --- a/Docs/Luadoc/LuaDocumentation.xml +++ b/Docs/Luadoc/LuaDocumentation.xml @@ -3552,6 +3552,58 @@ end Returns a string array of the currently displayed sections in the MusicWheel. + + + This singleton is accessible to Lua via NETWORK.
+ By default, access to the network is disabled for all target hosts. It can be enabled by setting HttpEnabled=1 in the preferences. Individual hosts have to be added to HttpAllowHosts as a comma separated list to allow access. +
+ + Returns true if access to url is allowed. + + + Performs an HTTP request.
+ Usage example: +

+NETWORK:HttpRequest{
+	url="https://api.example.com",
+	method="GET",                           -- default: "GET"
+	body="",                                -- default: ""
+	multipartBoundary="",                   -- default: ""
+	headers={                               -- default: {}
+		["Accept-Language"]="en-US",
+		["Cookie"]="sessionId=42",
+	},
+	connectTimeout=3,                       -- default: 60
+	transferTimeout=10,                     -- default: 1800
+	onResponse=function(response)           -- default: no callback
+		...
+	end,
+}
+
+ Everything but url is optional. Supported methods are GET, POST, PUT, PATCH, DELETE and HEAD.
+ A response looks like this: +

+{
+	statusCode=200,
+	headers={
+		["Content-Type"]="application/json",
+		["Cache-Control"]="max-age=172800",
+		["Server"]="Apache",
+	},
+	body="{...}"
+	uploadSize=204,
+	downloadSize=216,
+}
+
+ In case of an error the callback is called with a structure like this: +

+{
+	error="HttpErrorCode_Blocked",
+	errorMessage="access to https://api.example.com is not allowed",
+}
+
+
+
This singleton is accessible to Lua via NOTESKIN. diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 4365f573ed..1ea51be349 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -19,3 +19,4 @@ include(CMakeProject-pcre.cmake) include(CMakeProject-tomcrypt.cmake) include(CMakeProject-tommath.cmake) include(CMakeProject-png.cmake) +include(CMakeProject-ixwebsocket.cmake) diff --git a/extern/CMakeProject-ixwebsocket.cmake b/extern/CMakeProject-ixwebsocket.cmake new file mode 100644 index 0000000000..807d289a61 --- /dev/null +++ b/extern/CMakeProject-ixwebsocket.cmake @@ -0,0 +1,146 @@ +#set(CMAKE_CXX_STANDARD 11) +#set(CXX_STANDARD_REQUIRED ON) +#set(CMAKE_CXX_EXTENSIONS OFF) + +set(IXW_DIR "${SM_EXTERN_DIR}/IXWebSocket-11.3.2/ixwebsocket") + +list(APPEND IXW_SRC + "${IXW_DIR}/IXBench.cpp" + "${IXW_DIR}/IXCancellationRequest.cpp" + "${IXW_DIR}/IXConnectionState.cpp" + "${IXW_DIR}/IXDNSLookup.cpp" + "${IXW_DIR}/IXExponentialBackoff.cpp" + "${IXW_DIR}/IXGetFreePort.cpp" + "${IXW_DIR}/IXGzipCodec.cpp" + "${IXW_DIR}/IXHttp.cpp" + "${IXW_DIR}/IXHttpClient.cpp" + "${IXW_DIR}/IXHttpServer.cpp" + "${IXW_DIR}/IXNetSystem.cpp" + "${IXW_DIR}/IXSelectInterrupt.cpp" + "${IXW_DIR}/IXSelectInterruptFactory.cpp" + "${IXW_DIR}/IXSelectInterruptPipe.cpp" + "${IXW_DIR}/IXSetThreadName.cpp" + "${IXW_DIR}/IXSocket.cpp" + "${IXW_DIR}/IXSocketConnect.cpp" + "${IXW_DIR}/IXSocketFactory.cpp" + "${IXW_DIR}/IXSocketServer.cpp" + "${IXW_DIR}/IXSocketTLSOptions.cpp" + "${IXW_DIR}/IXStrCaseCompare.cpp" + "${IXW_DIR}/IXUdpSocket.cpp" + "${IXW_DIR}/IXUrlParser.cpp" + "${IXW_DIR}/IXUuid.cpp" + "${IXW_DIR}/IXUserAgent.cpp" + "${IXW_DIR}/IXWebSocket.cpp" + "${IXW_DIR}/IXWebSocketCloseConstants.cpp" + "${IXW_DIR}/IXWebSocketHandshake.cpp" + "${IXW_DIR}/IXWebSocketHttpHeaders.cpp" + "${IXW_DIR}/IXWebSocketPerMessageDeflate.cpp" + "${IXW_DIR}/IXWebSocketPerMessageDeflateCodec.cpp" + "${IXW_DIR}/IXWebSocketPerMessageDeflateOptions.cpp" + "${IXW_DIR}/IXWebSocketProxyServer.cpp" + "${IXW_DIR}/IXWebSocketServer.cpp" + "${IXW_DIR}/IXWebSocketTransport.cpp") + +list(APPEND IXW_HPP + "${IXW_DIR}/IXBench.h" + "${IXW_DIR}/IXCancellationRequest.h" + "${IXW_DIR}/IXConnectionState.h" + "${IXW_DIR}/IXDNSLookup.h" + "${IXW_DIR}/IXExponentialBackoff.h" + "${IXW_DIR}/IXGetFreePort.h" + "${IXW_DIR}/IXGzipCodec.h" + "${IXW_DIR}/IXHttp.h" + "${IXW_DIR}/IXHttpClient.h" + "${IXW_DIR}/IXHttpServer.h" + "${IXW_DIR}/IXNetSystem.h" + "${IXW_DIR}/IXProgressCallback.h" + "${IXW_DIR}/IXSelectInterrupt.h" + "${IXW_DIR}/IXSelectInterruptFactory.h" + "${IXW_DIR}/IXSelectInterruptPipe.h" + "${IXW_DIR}/IXSetThreadName.h" + "${IXW_DIR}/IXSocket.h" + "${IXW_DIR}/IXSocketConnect.h" + "${IXW_DIR}/IXSocketFactory.h" + "${IXW_DIR}/IXSocketServer.h" + "${IXW_DIR}/IXSocketTLSOptions.h" + "${IXW_DIR}/IXStrCaseCompare.h" + "${IXW_DIR}/IXUdpSocket.h" + "${IXW_DIR}/IXUniquePtr.h" + "${IXW_DIR}/IXUrlParser.h" + "${IXW_DIR}/IXUuid.h" + "${IXW_DIR}/IXUtf8Validator.h" + "${IXW_DIR}/IXUserAgent.h" + "${IXW_DIR}/IXWebSocket.h" + "${IXW_DIR}/IXWebSocketCloseConstants.h" + "${IXW_DIR}/IXWebSocketCloseInfo.h" + "${IXW_DIR}/IXWebSocketErrorInfo.h" + "${IXW_DIR}/IXWebSocketHandshake.h" + "${IXW_DIR}/IXWebSocketHandshakeKeyGen.h" + "${IXW_DIR}/IXWebSocketHttpHeaders.h" + "${IXW_DIR}/IXWebSocketInitResult.h" + "${IXW_DIR}/IXWebSocketMessage.h" + "${IXW_DIR}/IXWebSocketMessageType.h" + "${IXW_DIR}/IXWebSocketOpenInfo.h" + "${IXW_DIR}/IXWebSocketPerMessageDeflate.h" + "${IXW_DIR}/IXWebSocketPerMessageDeflateCodec.h" + "${IXW_DIR}/IXWebSocketPerMessageDeflateOptions.h" + "${IXW_DIR}/IXWebSocketProxyServer.h" + "${IXW_DIR}/IXWebSocketSendInfo.h" + "${IXW_DIR}/IXWebSocketServer.h" + "${IXW_DIR}/IXWebSocketTransport.h" + "${IXW_DIR}/IXWebSocketVersion.h") + +if(APPLE) + list(APPEND IXW_SRC "${IXW_DIR}/IXSocketAppleSSL.cpp") + list(APPEND IXW_HPP "${IXW_DIR}/IXSocketAppleSSL.h") +else() + list(APPEND IXW_SRC "${IXW_DIR}/IXSocketOpenSSL.cpp") + list(APPEND IXW_HPP "${IXW_DIR}/IXSocketOpenSSL.h") +endif() + +source_group("" FILES ${IXW_SRC} ${IXW_HPP}) + +add_library("ixwebsocket" STATIC ${IXW_SRC} ${IXW_HPP}) + +set_property(TARGET "ixwebsocket" PROPERTY FOLDER "External Libraries") + +disable_project_warnings("ixwebsocket") + +sm_add_compile_definition("ixwebsocket" IXWEBSOCKET_USE_TLS) + +if(APPLE) + sm_add_compile_definition("ixwebsocket" IXWEBSOCKET_USE_SECURE_TRANSPORT) + target_link_libraries(ixwebsocket "-framework foundation" "-framework security") +else() + sm_add_compile_definition("ixwebsocket" IXWEBSOCKET_USE_OPEN_SSL) + + # Use OPENSSL_ROOT_DIR CMake variable if you need to use your own openssl + find_package(OpenSSL REQUIRED) + message(STATUS "OpenSSL: " ${OPENSSL_VERSION}) + + add_definitions(${OPENSSL_DEFINITIONS}) + target_include_directories(ixwebsocket PUBLIC ${OPENSSL_INCLUDE_DIR}) + target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES}) +endif() + +sm_add_compile_definition("ixwebsocket" IXWEBSOCKET_USE_ZLIB) + +if(WIN32) + target_link_libraries(ixwebsocket wsock32 ws2_32 shlwapi Crypt32) + sm_add_compile_definition(ixwebsocket _CRT_SECURE_NO_WARNINGS) +endif() + +if(UNIX) + find_package(Threads) + target_link_libraries(ixwebsocket ${CMAKE_THREAD_LIBS_INIT}) +endif() + +target_include_directories("ixwebsocket" PUBLIC "${IXW_DIR}") + +set(IXWEBSOCKET_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/IXWebSocket-11.3.2") + +target_include_directories(ixwebsocket PUBLIC + "zlib" + $ + $ +) diff --git a/extern/IXWebSocket-11.3.2/.clang-format b/extern/IXWebSocket-11.3.2/.clang-format new file mode 100644 index 0000000000..02e5226f45 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/.clang-format @@ -0,0 +1,47 @@ +# https://releases.llvm.org/7.0.0/tools/clang/docs/ClangFormatStyleOptions.html + +--- +Language: Cpp + +BasedOnStyle: WebKit + +AlignAfterOpenBracket: Align +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Allman +BreakConstructorInitializersBeforeComma: true +ColumnLimit: 100 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +Cpp11BracedListStyle: true +FixNamespaceComments: true +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^["<](stdafx|pch)\.h[">]$' + Priority: -1 + - Regex: '^$' + Priority: 3 + - Regex: '^<(WinIoCtl|winhttp|Shellapi)\.h>$' + Priority: 4 + - Regex: '.*' + Priority: 2 +IndentCaseLabels: true +IndentWidth: 4 +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: All +PenaltyReturnTypeOnItsOwnLine: 1000 +PointerAlignment: Left +SpaceAfterTemplateKeyword: false +SpaceAfterCStyleCast: true +Standard: Cpp11 +UseTab: Never diff --git a/extern/IXWebSocket-11.3.2/.dockerignore b/extern/IXWebSocket-11.3.2/.dockerignore new file mode 100644 index 0000000000..538648ad27 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/.dockerignore @@ -0,0 +1,5 @@ +build +CMakeCache.txt +ws/CMakeCache.txt +test/build +makefile diff --git a/extern/IXWebSocket-11.3.2/.github/workflows/docker.yml b/extern/IXWebSocket-11.3.2/.github/workflows/docker.yml new file mode 100644 index 0000000000..bedb92b8b0 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/.github/workflows/docker.yml @@ -0,0 +1,66 @@ +name: docker + +# When its time to do a release do a build for amd64 +# and push all of them to Docker Hub. +# Only trigger on semver shaped tags. +on: + push: + tags: + - "v*.*.*" + +jobs: + login: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Prepare + id: prep + run: | + DOCKER_IMAGE=machinezone/ws + VERSION=edge + if [[ $GITHUB_REF == refs/tags/* ]]; then + VERSION=${GITHUB_REF#refs/tags/v} + fi + if [ "${{ github.event_name }}" = "schedule" ]; then + VERSION=nightly + fi + TAGS="${DOCKER_IMAGE}:${VERSION}" + if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then + TAGS="$TAGS,${DOCKER_IMAGE}:latest" + fi + echo ::set-output name=tags::${TAGS} + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@master + + - name: Cache Docker layers + uses: actions/cache@v2 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Login to GitHub Package Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GHCR_TOKEN }} + + - name: Build and push + id: docker_build + uses: docker/build-push-action@v2-build-push + with: + builder: ${{ steps.buildx.outputs.name }} + context: . + file: ./Dockerfile + target: prod + platforms: linux/amd64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.prep.outputs.tags }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache diff --git a/extern/IXWebSocket-11.3.2/.github/workflows/mkdocs.yml b/extern/IXWebSocket-11.3.2/.github/workflows/mkdocs.yml new file mode 100644 index 0000000000..7000643cab --- /dev/null +++ b/extern/IXWebSocket-11.3.2/.github/workflows/mkdocs.yml @@ -0,0 +1,27 @@ +name: mkdocs +on: + push: + paths: + - 'docs/**' + +jobs: + linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.8 + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install mkdocs + pip install mkdocs-material + pip install pygments + - name: Build doc + run: | + git clean -dfx . + git fetch + git pull + mkdocs gh-deploy diff --git a/extern/IXWebSocket-11.3.2/.github/workflows/stale.yml b/extern/IXWebSocket-11.3.2/.github/workflows/stale.yml new file mode 100644 index 0000000000..7bbc0505bf --- /dev/null +++ b/extern/IXWebSocket-11.3.2/.github/workflows/stale.yml @@ -0,0 +1,19 @@ +name: Mark stale issues and pull requests + +on: + schedule: + - cron: "0 0 * * *" + +jobs: + stale: + + runs-on: ubuntu-latest + + steps: + - uses: actions/stale@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'Stale issue message' + stale-pr-message: 'Stale pull request message' + stale-issue-label: 'no-issue-activity' + stale-pr-label: 'no-pr-activity' diff --git a/extern/IXWebSocket-11.3.2/.github/workflows/unittest_linux.yml b/extern/IXWebSocket-11.3.2/.github/workflows/unittest_linux.yml new file mode 100644 index 0000000000..9c6272a5db --- /dev/null +++ b/extern/IXWebSocket-11.3.2/.github/workflows/unittest_linux.yml @@ -0,0 +1,15 @@ +name: linux +on: + push: + paths-ignore: + - 'docs/**' + pull_request: + +jobs: + linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: seanmiddleditch/gha-setup-ninja@master + - name: make test + run: make -f makefile.dev test diff --git a/extern/IXWebSocket-11.3.2/.github/workflows/unittest_linux_asan.yml b/extern/IXWebSocket-11.3.2/.github/workflows/unittest_linux_asan.yml new file mode 100644 index 0000000000..613c618ebb --- /dev/null +++ b/extern/IXWebSocket-11.3.2/.github/workflows/unittest_linux_asan.yml @@ -0,0 +1,15 @@ +name: linux_asan +on: + push: + paths-ignore: + - 'docs/**' + pull_request: + +jobs: + linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: seanmiddleditch/gha-setup-ninja@master + - name: make test_asan + run: make -f makefile.dev test_asan diff --git a/extern/IXWebSocket-11.3.2/.github/workflows/unittest_mac_tsan_mbedtls.yml b/extern/IXWebSocket-11.3.2/.github/workflows/unittest_mac_tsan_mbedtls.yml new file mode 100644 index 0000000000..ab4d226b64 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/.github/workflows/unittest_mac_tsan_mbedtls.yml @@ -0,0 +1,17 @@ +name: mac_tsan_mbedtls +on: + push: + paths-ignore: + - 'docs/**' + pull_request: + +jobs: + mac_tsan_mbedtls: + runs-on: macOS-latest + steps: + - uses: actions/checkout@v1 + - uses: seanmiddleditch/gha-setup-ninja@master + - name: install mbedtls + run: brew install mbedtls + - name: make test + run: make -f makefile.dev test_tsan_mbedtls diff --git a/extern/IXWebSocket-11.3.2/.github/workflows/unittest_mac_tsan_openssl.yml b/extern/IXWebSocket-11.3.2/.github/workflows/unittest_mac_tsan_openssl.yml new file mode 100644 index 0000000000..ddc9c7dfc7 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/.github/workflows/unittest_mac_tsan_openssl.yml @@ -0,0 +1,17 @@ +name: mac_tsan_openssl +on: + push: + paths-ignore: + - 'docs/**' + pull_request: + +jobs: + mac_tsan_openssl: + runs-on: macOS-latest + steps: + - uses: actions/checkout@v1 + - uses: seanmiddleditch/gha-setup-ninja@master + - name: install openssl + run: brew install openssl@1.1 + - name: make test + run: make -f makefile.dev test_tsan_openssl diff --git a/extern/IXWebSocket-11.3.2/.github/workflows/unittest_mac_tsan_sectransport.yml b/extern/IXWebSocket-11.3.2/.github/workflows/unittest_mac_tsan_sectransport.yml new file mode 100644 index 0000000000..739c0c423e --- /dev/null +++ b/extern/IXWebSocket-11.3.2/.github/workflows/unittest_mac_tsan_sectransport.yml @@ -0,0 +1,15 @@ +name: mac_tsan_sectransport +on: + push: + paths-ignore: + - 'docs/**' + pull_request: + +jobs: + mac_tsan_sectransport: + runs-on: macOS-latest + steps: + - uses: actions/checkout@v1 + - uses: seanmiddleditch/gha-setup-ninja@master + - name: make test_tsan_sectransport + run: make -f makefile.dev test_tsan_sectransport diff --git a/extern/IXWebSocket-11.3.2/.github/workflows/unittest_uwp.yml b/extern/IXWebSocket-11.3.2/.github/workflows/unittest_uwp.yml new file mode 100644 index 0000000000..a721e30add --- /dev/null +++ b/extern/IXWebSocket-11.3.2/.github/workflows/unittest_uwp.yml @@ -0,0 +1,45 @@ +name: uwp +on: + push: + paths-ignore: + - 'docs/**' + pull_request: + +jobs: + uwp: + runs-on: windows-latest + steps: + - uses: actions/checkout@v1 + - uses: seanmiddleditch/gha-setup-vsdevenv@master + - uses: seanmiddleditch/gha-setup-ninja@master + - run: | + mkdir build + cd build + 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: | + 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_openssl: +# runs-on: windows-latest +# steps: +# - uses: actions/checkout@v1 +# - uses: seanmiddleditch/gha-setup-vsdevenv@master +# - run: | +# vcpkg install zlib:x64-windows +# vcpkg install openssl:x64-windows +# - run: | +# mkdir build +# cd build +# cmake -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_OPEN_SSL=1 -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. +# - run: cmake --build build +# +# # Running the unittest does not work, the binary cannot be found +# #- run: ../build/test/ixwebsocket_unittest.exe +# # working-directory: test diff --git a/extern/IXWebSocket-11.3.2/.github/workflows/unittest_windows.yml b/extern/IXWebSocket-11.3.2/.github/workflows/unittest_windows.yml new file mode 100644 index 0000000000..cca5cc2a19 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/.github/workflows/unittest_windows.yml @@ -0,0 +1,27 @@ +name: windows +on: + push: + paths-ignore: + - 'docs/**' + pull_request: + +jobs: + windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v1 + - uses: seanmiddleditch/gha-setup-vsdevenv@master + - uses: seanmiddleditch/gha-setup-ninja@master + - run: | + mkdir build + cd build + cmake -GNinja -DCMAKE_CXX_COMPILER=cl.exe -DCMAKE_C_COMPILER=cl.exe -DUSE_WS=1 -DUSE_TEST=1 -DUSE_ZLIB=OFF -DBUILD_SHARED_LIBS=OFF .. + - run: | + cd build + ninja + - run: | + cd build + ninja test + +#- run: ../build/test/ixwebsocket_unittest.exe +# working-directory: test diff --git a/extern/IXWebSocket-11.3.2/.github/workflows/unittest_windows_gcc.yml b/extern/IXWebSocket-11.3.2/.github/workflows/unittest_windows_gcc.yml new file mode 100644 index 0000000000..328cd6ecc3 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/.github/workflows/unittest_windows_gcc.yml @@ -0,0 +1,28 @@ +name: windows_gcc +on: + push: + paths-ignore: + - 'docs/**' + pull_request: + +jobs: + windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v1 + - uses: seanmiddleditch/gha-setup-ninja@master + - uses: egor-tensin/setup-mingw@v2 + - run: | + mkdir build + cd build + cmake -GNinja -DCMAKE_CXX_COMPILER=c++ -DCMAKE_C_COMPILER=cc -DUSE_WS=1 -DUSE_TEST=1 -DUSE_ZLIB=0 -DCMAKE_UNITY_BUILD=ON .. + - run: | + cd build + ninja + - run: | + cd build + ctest -V + # ninja test + +#- run: ../build/test/ixwebsocket_unittest.exe +# working-directory: test diff --git a/extern/IXWebSocket-11.3.2/.gitignore b/extern/IXWebSocket-11.3.2/.gitignore new file mode 100644 index 0000000000..892b7bf6d7 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/.gitignore @@ -0,0 +1,10 @@ +build +*.pyc +venv +ixsnake/ixsnake/.certs/ +site/ +ws/.certs/ +ws/.srl +ixhttpd +makefile +a.out diff --git a/extern/IXWebSocket-11.3.2/.pre-commit-config.yaml b/extern/IXWebSocket-11.3.2/.pre-commit-config.yaml new file mode 100644 index 0000000000..de81d1bd7e --- /dev/null +++ b/extern/IXWebSocket-11.3.2/.pre-commit-config.yaml @@ -0,0 +1,12 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.5.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace +- repo: https://github.com/pocc/pre-commit-hooks + rev: v1.1.1 + hooks: + - id: clang-format + args: [-i, -style=file] diff --git a/extern/IXWebSocket-11.3.2/CMake/FindDeflate.cmake b/extern/IXWebSocket-11.3.2/CMake/FindDeflate.cmake new file mode 100644 index 0000000000..ebbd9fdf04 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/CMake/FindDeflate.cmake @@ -0,0 +1,19 @@ +# Find package structure taken from libcurl + +include(FindPackageHandleStandardArgs) + +find_path(DEFLATE_INCLUDE_DIRS libdeflate.h) +find_library(DEFLATE_LIBRARY deflate) + +find_package_handle_standard_args(Deflate + FOUND_VAR + DEFLATE_FOUND + REQUIRED_VARS + DEFLATE_LIBRARY + DEFLATE_INCLUDE_DIRS + FAIL_MESSAGE + "Could NOT find deflate" +) + +set(DEFLATE_INCLUDE_DIRS ${DEFLATE_INCLUDE_DIRS}) +set(DEFLATE_LIBRARIES ${DEFLATE_LIBRARY}) diff --git a/extern/IXWebSocket-11.3.2/CMake/FindMbedTLS.cmake b/extern/IXWebSocket-11.3.2/CMake/FindMbedTLS.cmake new file mode 100644 index 0000000000..b6ef63ab38 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/CMake/FindMbedTLS.cmake @@ -0,0 +1,16 @@ +find_path(MBEDTLS_INCLUDE_DIRS mbedtls/ssl.h) + +# mbedtls-3.0 changed headers files, and we need to ifdef'out a few things +find_path(MBEDTLS_VERSION_GREATER_THAN_3 mbedtls/build_info.h) + +find_library(MBEDTLS_LIBRARY mbedtls) +find_library(MBEDX509_LIBRARY mbedx509) +find_library(MBEDCRYPTO_LIBRARY mbedcrypto) + +set(MBEDTLS_LIBRARIES "${MBEDTLS_LIBRARY}" "${MBEDX509_LIBRARY}" "${MBEDCRYPTO_LIBRARY}") + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(MbedTLS DEFAULT_MSG + MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY) + +mark_as_advanced(MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY) diff --git a/extern/IXWebSocket-11.3.2/CMake/FindSpdLog.cmake b/extern/IXWebSocket-11.3.2/CMake/FindSpdLog.cmake new file mode 100644 index 0000000000..ffd2dba901 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/CMake/FindSpdLog.cmake @@ -0,0 +1,19 @@ +# Find package structure taken from libcurl + +include(FindPackageHandleStandardArgs) + +find_path(SPDLOG_INCLUDE_DIRS spdlog/spdlog.h) +find_library(JSONCPP_LIBRARY spdlog) + +find_package_handle_standard_args(SPDLOG + FOUND_VAR + SPDLOG_FOUND + REQUIRED_VARS + SPDLOG_LIBRARY + SPDLOG_INCLUDE_DIRS + FAIL_MESSAGE + "Could NOT find spdlog" +) + +set(SPDLOG_INCLUDE_DIRS ${SPDLOG_INCLUDE_DIRS}) +set(SPDLOG_LIBRARIES ${SPDLOG_LIBRARY}) diff --git a/extern/IXWebSocket-11.3.2/CMakeLists.txt b/extern/IXWebSocket-11.3.2/CMakeLists.txt new file mode 100644 index 0000000000..72e5ac45d0 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/CMakeLists.txt @@ -0,0 +1,302 @@ +# +# Author: Benjamin Sergeant +# Copyright (c) 2018 Machine Zone, Inc. All rights reserved. +# + +cmake_minimum_required(VERSION 3.4.1...3.17.2) +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake;${CMAKE_MODULE_PATH}") + +project(ixwebsocket C CXX) + +set (CMAKE_CXX_STANDARD 11) +set (CXX_STANDARD_REQUIRED ON) +set (CMAKE_CXX_EXTENSIONS OFF) + +option (BUILD_DEMO OFF) + +if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + set(CMAKE_POSITION_INDEPENDENT_CODE ON) +endif() + +if (UNIX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic") +endif() + +if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wshorten-64-to-32") +endif() + +set( IXWEBSOCKET_SOURCES + ixwebsocket/IXBench.cpp + ixwebsocket/IXCancellationRequest.cpp + ixwebsocket/IXConnectionState.cpp + ixwebsocket/IXDNSLookup.cpp + ixwebsocket/IXExponentialBackoff.cpp + ixwebsocket/IXGetFreePort.cpp + ixwebsocket/IXGzipCodec.cpp + ixwebsocket/IXHttp.cpp + ixwebsocket/IXHttpClient.cpp + ixwebsocket/IXHttpServer.cpp + ixwebsocket/IXNetSystem.cpp + ixwebsocket/IXSelectInterrupt.cpp + ixwebsocket/IXSelectInterruptFactory.cpp + ixwebsocket/IXSelectInterruptPipe.cpp + ixwebsocket/IXSetThreadName.cpp + ixwebsocket/IXSocket.cpp + ixwebsocket/IXSocketConnect.cpp + ixwebsocket/IXSocketFactory.cpp + ixwebsocket/IXSocketServer.cpp + ixwebsocket/IXSocketTLSOptions.cpp + ixwebsocket/IXStrCaseCompare.cpp + ixwebsocket/IXUdpSocket.cpp + ixwebsocket/IXUrlParser.cpp + ixwebsocket/IXUuid.cpp + ixwebsocket/IXUserAgent.cpp + ixwebsocket/IXWebSocket.cpp + ixwebsocket/IXWebSocketCloseConstants.cpp + ixwebsocket/IXWebSocketHandshake.cpp + ixwebsocket/IXWebSocketHttpHeaders.cpp + ixwebsocket/IXWebSocketPerMessageDeflate.cpp + ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp + ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp + ixwebsocket/IXWebSocketProxyServer.cpp + ixwebsocket/IXWebSocketServer.cpp + ixwebsocket/IXWebSocketTransport.cpp +) + +set( IXWEBSOCKET_HEADERS + ixwebsocket/IXBench.h + ixwebsocket/IXCancellationRequest.h + ixwebsocket/IXConnectionState.h + ixwebsocket/IXDNSLookup.h + ixwebsocket/IXExponentialBackoff.h + ixwebsocket/IXGetFreePort.h + ixwebsocket/IXGzipCodec.h + ixwebsocket/IXHttp.h + ixwebsocket/IXHttpClient.h + ixwebsocket/IXHttpServer.h + ixwebsocket/IXNetSystem.h + ixwebsocket/IXProgressCallback.h + ixwebsocket/IXSelectInterrupt.h + ixwebsocket/IXSelectInterruptFactory.h + ixwebsocket/IXSelectInterruptPipe.h + ixwebsocket/IXSetThreadName.h + ixwebsocket/IXSocket.h + ixwebsocket/IXSocketConnect.h + ixwebsocket/IXSocketFactory.h + ixwebsocket/IXSocketServer.h + ixwebsocket/IXSocketTLSOptions.h + ixwebsocket/IXStrCaseCompare.h + ixwebsocket/IXUdpSocket.h + ixwebsocket/IXUniquePtr.h + ixwebsocket/IXUrlParser.h + ixwebsocket/IXUuid.h + ixwebsocket/IXUtf8Validator.h + ixwebsocket/IXUserAgent.h + ixwebsocket/IXWebSocket.h + ixwebsocket/IXWebSocketCloseConstants.h + ixwebsocket/IXWebSocketCloseInfo.h + ixwebsocket/IXWebSocketErrorInfo.h + ixwebsocket/IXWebSocketHandshake.h + ixwebsocket/IXWebSocketHandshakeKeyGen.h + ixwebsocket/IXWebSocketHttpHeaders.h + ixwebsocket/IXWebSocketInitResult.h + ixwebsocket/IXWebSocketMessage.h + ixwebsocket/IXWebSocketMessageType.h + ixwebsocket/IXWebSocketOpenInfo.h + ixwebsocket/IXWebSocketPerMessageDeflate.h + ixwebsocket/IXWebSocketPerMessageDeflateCodec.h + ixwebsocket/IXWebSocketPerMessageDeflateOptions.h + ixwebsocket/IXWebSocketProxyServer.h + ixwebsocket/IXWebSocketSendInfo.h + ixwebsocket/IXWebSocketServer.h + ixwebsocket/IXWebSocketTransport.h + ixwebsocket/IXWebSocketVersion.h +) + +option(BUILD_SHARED_LIBS "Build shared libraries (.dll/.so) instead of static ones (.lib/.a)" OFF) +option(USE_TLS "Enable TLS support" FALSE) + +if (USE_TLS) + # default to securetranport on Apple if nothing is configured + if (APPLE) + if (NOT USE_MBED_TLS AND NOT USE_OPEN_SSL) # unless we want something else + set(USE_SECURE_TRANSPORT ON) + endif() + # default to mbedtls on windows if nothing is configured + elseif (WIN32) + if (NOT USE_OPEN_SSL) # unless we want something else + set(USE_MBED_TLS ON) + endif() + else() # default to OpenSSL on all other platforms + if (NOT USE_MBED_TLS) # Unless mbedtls is requested + set(USE_OPEN_SSL ON) + endif() + endif() + + if (USE_MBED_TLS) + list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketMbedTLS.h) + list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketMbedTLS.cpp) + elseif (USE_SECURE_TRANSPORT) + list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketAppleSSL.h) + list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketAppleSSL.cpp) + elseif (USE_OPEN_SSL) + list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketOpenSSL.h) + list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketOpenSSL.cpp) + else() + message(FATAL_ERROR "TLS Configuration error: unknown backend") + endif() +endif() + +add_library( ixwebsocket + ${IXWEBSOCKET_SOURCES} + ${IXWEBSOCKET_HEADERS} +) + +if (USE_TLS) + target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_TLS) + if (USE_MBED_TLS) + target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_MBED_TLS) + elseif (USE_OPEN_SSL) + target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_OPEN_SSL) + elseif (USE_SECURE_TRANSPORT) + target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_SECURE_TRANSPORT) + else() + message(FATAL_ERROR "TLS Configuration error: unknown backend") + endif() +endif() + +if (USE_TLS) + if (USE_OPEN_SSL) + message(STATUS "TLS configured to use openssl") + + # Help finding Homebrew's OpenSSL on macOS + if (APPLE) + set(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} /usr/local/opt/openssl/lib) + set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /usr/local/opt/openssl/include) + + # This is for MacPort OpenSSL 1.0 + # set(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} /opt/local/lib/openssl-1.0) + # set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /opt/local/include/openssl-1.0) + endif() + + # This OPENSSL_FOUND check is to help find a cmake manually configured OpenSSL + if (NOT OPENSSL_FOUND) + find_package(OpenSSL REQUIRED) + endif() + message(STATUS "OpenSSL: " ${OPENSSL_VERSION}) + + add_definitions(${OPENSSL_DEFINITIONS}) + target_include_directories(ixwebsocket PUBLIC $) + target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES}) + elseif (USE_MBED_TLS) + message(STATUS "TLS configured to use mbedtls") + + # This MBEDTLS_FOUND check is to help find a cmake manually configured MbedTLS + if (NOT MBEDTLS_FOUND) + find_package(MbedTLS REQUIRED) + + if (MBEDTLS_VERSION_GREATER_THAN_3) + target_compile_definitions(ixwebsocket PRIVATE IXWEBSOCKET_USE_MBED_TLS_MIN_VERSION_3) + endif() + + endif() + target_include_directories(ixwebsocket PUBLIC $) + target_link_libraries(ixwebsocket ${MBEDTLS_LIBRARIES}) + elseif (USE_SECURE_TRANSPORT) + message(STATUS "TLS configured to use secure transport") + target_link_libraries(ixwebsocket "-framework Foundation" "-framework Security") + endif() +endif() + +option(USE_ZLIB "Enable zlib support" TRUE) + +if (USE_ZLIB) + # This ZLIB_FOUND check is to help find a cmake manually configured zlib + if (NOT ZLIB_FOUND) + find_package(ZLIB REQUIRED) + endif() + target_include_directories(ixwebsocket PUBLIC $) + target_link_libraries(ixwebsocket ${ZLIB_LIBRARIES}) + + target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_ZLIB) +endif() + +# brew install libdeflate +find_package(Deflate) +if (DEFLATE_FOUND) + include_directories(${DEFLATE_INCLUDE_DIRS}) + target_link_libraries(ixwebsocket ${DEFLATE_LIBRARIES}) + target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_DEFLATE) +endif() + +if (WIN32) + target_link_libraries(ixwebsocket wsock32 ws2_32 shlwapi) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) + + if (USE_TLS) + target_link_libraries(ixwebsocket Crypt32) + endif() +endif() + +if (UNIX) + find_package(Threads) + target_link_libraries(ixwebsocket ${CMAKE_THREAD_LIBS_INIT}) +endif() + + +set( IXWEBSOCKET_INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR} +) + +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + # Build with Multiple Processes + target_compile_options(ixwebsocket PRIVATE /MP) +endif() + +include(GNUInstallDirs) + +target_include_directories(ixwebsocket PUBLIC + $ + $ +) + +set_target_properties(ixwebsocket PROPERTIES PUBLIC_HEADER "${IXWEBSOCKET_HEADERS}") + +option(IXWEBSOCKET_INSTALL "Install IXWebSocket" TRUE) + +if (IXWEBSOCKET_INSTALL) + install(TARGETS ixwebsocket + EXPORT ixwebsocket + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ixwebsocket/ + ) + + install(EXPORT ixwebsocket + FILE ixwebsocket-config.cmake + NAMESPACE ixwebsocket:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ixwebsocket) +endif() + +if (USE_WS OR USE_TEST) + include(FetchContent) + FetchContent_Declare(spdlog + GIT_REPOSITORY "https://github.com/gabime/spdlog" + GIT_TAG "v1.8.0" + GIT_SHALLOW 1) + + FetchContent_MakeAvailable(spdlog) + + if (USE_WS) + add_subdirectory(ws) + endif() + if (USE_TEST) + enable_testing() + add_subdirectory(test) + endif() +endif() + +if (BUILD_DEMO) + add_executable(demo main.cpp) + target_link_libraries(demo ixwebsocket) +endif() diff --git a/extern/IXWebSocket-11.3.2/Dockerfile b/extern/IXWebSocket-11.3.2/Dockerfile new file mode 120000 index 0000000000..197ac830b3 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/Dockerfile @@ -0,0 +1 @@ +docker/Dockerfile.alpine \ No newline at end of file diff --git a/extern/IXWebSocket-11.3.2/LICENSE.txt b/extern/IXWebSocket-11.3.2/LICENSE.txt new file mode 100644 index 0000000000..d32951a9cd --- /dev/null +++ b/extern/IXWebSocket-11.3.2/LICENSE.txt @@ -0,0 +1,29 @@ +Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. 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. + +3. Neither the name of the copyright holder 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. diff --git a/extern/IXWebSocket-11.3.2/README.md b/extern/IXWebSocket-11.3.2/README.md new file mode 100644 index 0000000000..8f48931c54 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/README.md @@ -0,0 +1,150 @@ +## Hello world + +IXWebSocket is a C++ library for WebSocket client and server development. It has minimal dependencies (no boost), is very simple to use and support everything you'll likely need for websocket dev (SSL, deflate compression, compiles on most platforms, etc...). HTTP client and server code is also available, but it hasn't received as much testing. + +It is been used on big mobile video game titles sending and receiving tons of messages since 2017 (iOS and Android). It was tested on macOS, iOS, Linux, Android, Windows and FreeBSD. Note that the MinGW compiler is not supported at this point. Two important design goals are simplicity and correctness. + +A bad security bug affecting users compiling with SSL enabled and OpenSSL as the backend was just fixed in newly released version 11.0.0. Please upgrade ! (more details in the [https://github.com/machinezone/IXWebSocket/pull/250](PR). + +```cpp +/* + * main.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2020 Machine Zone, Inc. All rights reserved. + * + * Super simple standalone example. See ws folder, unittest and doc/usage.md for more. + * + * On macOS + * $ mkdir -p build ; (cd build ; cmake -DUSE_TLS=1 .. ; make -j ; make install) + * $ clang++ --std=c++11 --stdlib=libc++ main.cpp -lixwebsocket -lz -framework Security -framework Foundation + * $ ./a.out + * + * Or use cmake -DBUILD_DEMO=ON option for other platforms + */ + +#include +#include +#include +#include + +int main() +{ + // Required on Windows + ix::initNetSystem(); + + // Our websocket object + ix::WebSocket webSocket; + + // Connect to a server with encryption + // See https://machinezone.github.io/IXWebSocket/usage/#tls-support-and-configuration + std::string url("wss://echo.websocket.org"); + webSocket.setUrl(url); + + std::cout << "Connecting to " << url << "..." << std::endl; + + // Setup a callback to be fired (in a background thread, watch out for race conditions !) + // when a message or an event (open, close, error) is received + webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg) + { + if (msg->type == ix::WebSocketMessageType::Message) + { + std::cout << "received message: " << msg->str << std::endl; + std::cout << "> " << std::flush; + } + else if (msg->type == ix::WebSocketMessageType::Open) + { + std::cout << "Connection established" << std::endl; + std::cout << "> " << std::flush; + } + else if (msg->type == ix::WebSocketMessageType::Error) + { + // Maybe SSL is not configured properly + std::cout << "Connection error: " << msg->errorInfo.reason << std::endl; + std::cout << "> " << std::flush; + } + } + ); + + // Now that our callback is setup, we can start our background thread and receive messages + webSocket.start(); + + // Send a message to the server (default to TEXT mode) + webSocket.send("hello world"); + + // Display a prompt + std::cout << "> " << std::flush; + + std::string text; + // Read text from the console and send messages in text mode. + // Exit with Ctrl-D on Unix or Ctrl-Z on Windows. + while (std::getline(std::cin, text)) + { + webSocket.send(text); + std::cout << "> " << std::flush; + } + + return 0; +} +``` + +Interested? Go read the [docs](https://machinezone.github.io/IXWebSocket/)! If things don't work as expected, please create an issue on GitHub, or even better a pull request if you know how to fix your problem. + +IXWebSocket is actively being developed, check out the [changelog](https://machinezone.github.io/IXWebSocket/CHANGELOG/) to know what's cooking. If you are looking for a real time messaging service (the chat-like 'server' your websocket code will talk to) with many features such as history, backed by Redis, look at [cobra](https://github.com/machinezone/cobra). + +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 + +If your company or project is using this library, feel free to open an issue or PR to amend this list. + +- [Machine Zone](https://www.mz.com) +- [Tokio](https://gitlab.com/HCInk/tokio), a discord library focused on audio playback with node bindings. +- [libDiscordBot](https://github.com/tostc/libDiscordBot/tree/master), an easy to use Discord-bot framework. +- [gwebsocket](https://github.com/norrbotten/gwebsocket), a websocket (lua) module for Garry's Mod +- [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 +- [Teleport](http://teleportconnect.com/), Teleport is your own personal remote robot avatar + +## 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 +* [wslay](https://github.com/tatsuhiro-t/wslay) - 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 + +| OS | TLS | Sanitizer | Status | +|-------------------|-------------------|-------------------|-------------------| +| Linux | OpenSSL | None | [![Build2][1]][0] | +| macOS | Secure Transport | Thread Sanitizer | [![Build2][2]][0] | +| macOS | OpenSSL | Thread Sanitizer | [![Build2][3]][0] | +| macOS | MbedTLS | Thread Sanitizer | [![Build2][4]][0] | +| Windows | Disabled | None | [![Build2][5]][0] | +| UWP | Disabled | None | [![Build2][6]][0] | +| Linux | OpenSSL | Address Sanitizer | [![Build2][7]][0] | +| Mingw | Disabled | None | [![Build2][8]][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 +[1]: https://github.com/machinezone/IXWebSocket/workflows/linux/badge.svg +[2]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_sectransport/badge.svg +[3]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_openssl/badge.svg +[4]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_mbedtls/badge.svg +[5]: https://github.com/machinezone/IXWebSocket/workflows/windows/badge.svg +[6]: https://github.com/machinezone/IXWebSocket/workflows/uwp/badge.svg +[7]: https://github.com/machinezone/IXWebSocket/workflows/linux_asan/badge.svg +[8]: https://github.com/machinezone/IXWebSocket/workflows/unittest_windows_gcc/badge.svg + diff --git a/extern/IXWebSocket-11.3.2/SECURITY.md b/extern/IXWebSocket-11.3.2/SECURITY.md new file mode 100644 index 0000000000..81aecf1594 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/SECURITY.md @@ -0,0 +1,11 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 7.x.x | :white_check_mark: | + +## Reporting a Vulnerability + +Users should send an email to bsergean@gmail.com to report a vulnerability. diff --git a/extern/IXWebSocket-11.3.2/appveyor.yml b/extern/IXWebSocket-11.3.2/appveyor.yml new file mode 100644 index 0000000000..144facec27 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/appveyor.yml @@ -0,0 +1,22 @@ +image: +- Visual Studio 2017 + +install: +- cd C:\Tools\vcpkg +- git pull +- .\bootstrap-vcpkg.bat +- cd %APPVEYOR_BUILD_FOLDER% +- cmd: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" +- vcpkg install zlib:x64-windows +- vcpkg install mbedtls:x64-windows +- mkdir build +- cd build +- cmake -DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DUSE_WS=1 -DUSE_TEST=1 -DUSE_TLS=1 -G"NMake Makefiles" .. +- nmake +- cd .. +- cd test +- ..\build\test\ixwebsocket_unittest.exe + +cache: c:\tools\vcpkg\installed\ + +build: off diff --git a/extern/IXWebSocket-11.3.2/docker-compose.yml b/extern/IXWebSocket-11.3.2/docker-compose.yml new file mode 100644 index 0000000000..0caf0cb7f7 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/docker-compose.yml @@ -0,0 +1,11 @@ +version: "3.3" +services: + push: + entrypoint: ws push_server --host 0.0.0.0 + image: ${DOCKER_REPO}/ws:build + + autoroute: + entrypoint: ws autoroute ws://push:8008 + image: ${DOCKER_REPO}/ws:build + depends_on: + - push diff --git a/extern/IXWebSocket-11.3.2/docker/Dockerfile.alpine b/extern/IXWebSocket-11.3.2/docker/Dockerfile.alpine new file mode 100644 index 0000000000..e42528f0a1 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/docker/Dockerfile.alpine @@ -0,0 +1,39 @@ +FROM alpine:3.12 as build + +RUN apk add --no-cache \ + gcc g++ musl-dev linux-headers \ + cmake mbedtls-dev make zlib-dev python3-dev ninja git + +RUN addgroup -S app && \ + adduser -S -G app app && \ + chown -R app:app /opt && \ + chown -R app:app /usr/local + +# There is a bug in CMake where we cannot build from the root top folder +# So we build from /opt +COPY --chown=app:app . /opt +WORKDIR /opt + +USER app +RUN make -f makefile.dev ws_mbedtls_install && \ + sh tools/trim_repo_for_docker.sh + +FROM alpine:3.12 as runtime + +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 diff --git a/extern/IXWebSocket-11.3.2/docker/Dockerfile.centos b/extern/IXWebSocket-11.3.2/docker/Dockerfile.centos new file mode 100644 index 0000000000..06757cf703 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/docker/Dockerfile.centos @@ -0,0 +1,41 @@ +FROM centos:8 as build + +RUN yum install -y gcc-c++ make cmake zlib-devel openssl-devel redhat-rpm-config + +RUN yum install -y epel-release +RUN yum install -y mbedtls-devel + +RUN groupadd app && useradd -g app app +RUN chown -R app:app /opt +RUN chown -R app:app /usr/local + +# There is a bug in CMake where we cannot build from the root top folder +# So we build from /opt +COPY --chown=app:app . /opt +WORKDIR /opt + +USER app +RUN [ "make", "ws_mbedtls_install" ] +RUN [ "rm", "-rf", "build" ] + +FROM centos:8 as runtime + +RUN yum install -y gdb strace + +RUN yum install -y epel-release +RUN yum install -y mbedtls + +RUN groupadd app && useradd -g app app +COPY --chown=app:app --from=build /usr/local/bin/ws /usr/local/bin/ws +RUN chmod +x /usr/local/bin/ws +RUN ldd /usr/local/bin/ws + +# Copy source code for gcc +COPY --chown=app:app --from=build /opt /opt + +# Now run in usermode +USER app +WORKDIR /home/app + +ENTRYPOINT ["ws"] +EXPOSE 8008 diff --git a/extern/IXWebSocket-11.3.2/docker/Dockerfile.centos7 b/extern/IXWebSocket-11.3.2/docker/Dockerfile.centos7 new file mode 100644 index 0000000000..adf5c651d5 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/docker/Dockerfile.centos7 @@ -0,0 +1,26 @@ +FROM centos:7 as build + +RUN yum install -y gcc-c++ make zlib-devel openssl-devel redhat-rpm-config + +RUN groupadd app && useradd -g app app +RUN chown -R app:app /opt +RUN chown -R app:app /usr/local + +WORKDIR /tmp +RUN curl -O https://cmake.org/files/v3.14/cmake-3.14.0-Linux-x86_64.tar.gz +RUN tar zxvf cmake-3.14.0-Linux-x86_64.tar.gz +RUN cp -rf cmake-3.14.0-Linux-x86_64/* /usr/ + +RUN yum install -y git + +# There is a bug in CMake where we cannot build from the root top folder +# So we build from /opt +COPY --chown=app:app . /opt +WORKDIR /opt + +USER app +RUN [ "make", "ws_no_python" ] +RUN [ "rm", "-rf", "build" ] + +ENTRYPOINT ["ws"] +CMD ["--help"] diff --git a/extern/IXWebSocket-11.3.2/docker/Dockerfile.debian b/extern/IXWebSocket-11.3.2/docker/Dockerfile.debian new file mode 100644 index 0000000000..9e885e59d6 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/docker/Dockerfile.debian @@ -0,0 +1,52 @@ +# Build time +FROM debian:buster as build + +ENV DEBIAN_FRONTEND noninteractive +RUN apt-get update +RUN apt-get -y install wget +RUN mkdir -p /tmp/cmake +WORKDIR /tmp/cmake +RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz +RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz + +RUN apt-get -y install g++ +RUN apt-get -y install libssl-dev +RUN apt-get -y install libz-dev +RUN apt-get -y install make + +COPY . . + +ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin +ENV PATH="${CMAKE_BIN_PATH}:${PATH}" + +RUN ["make"] + +# Runtime +FROM debian:buster as runtime + +ENV DEBIAN_FRONTEND noninteractive +RUN apt-get update +# Runtime +RUN apt-get install -y libssl1.1 +RUN apt-get install -y ca-certificates +RUN ["update-ca-certificates"] + +# Debugging +RUN apt-get install -y strace +RUN apt-get install -y procps +RUN apt-get install -y htop + +RUN adduser --disabled-password --gecos '' app +COPY --chown=app:app --from=build /usr/local/bin/ws /usr/local/bin/ws +RUN chmod +x /usr/local/bin/ws +RUN ldd /usr/local/bin/ws + +# Now run in usermode +USER app +WORKDIR /home/app + +COPY --chown=app:app ws/snake/appsConfig.json . +COPY --chown=app:app ws/cobraMetricsSample.json . + +ENTRYPOINT ["ws"] +CMD ["--help"] diff --git a/extern/IXWebSocket-11.3.2/docker/Dockerfile.fedora b/extern/IXWebSocket-11.3.2/docker/Dockerfile.fedora new file mode 100644 index 0000000000..d1966fc643 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/docker/Dockerfile.fedora @@ -0,0 +1,43 @@ +FROM fedora:30 as build + +RUN yum install -y gcc-g++ +RUN yum install -y cmake +RUN yum install -y make +RUN yum install -y openssl-devel + +RUN yum install -y wget +RUN mkdir -p /tmp/cmake +WORKDIR /tmp/cmake +RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz +RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz + +ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin +ENV PATH="${CMAKE_BIN_PATH}:${PATH}" + +RUN yum install -y python +RUN yum install -y libtsan +RUN yum install -y zlib-devel + +COPY . . +# RUN ["make", "test"] +RUN ["make"] + +# Runtime +FROM fedora:30 as runtime + +RUN yum install -y libtsan + +RUN groupadd app && useradd -g app app +COPY --chown=app:app --from=build /usr/local/bin/ws /usr/local/bin/ws +RUN chmod +x /usr/local/bin/ws +RUN ldd /usr/local/bin/ws + +# Now run in usermode +USER app +WORKDIR /home/app + +COPY --chown=app:app ws/snake/appsConfig.json . +COPY --chown=app:app ws/cobraMetricsSample.json . + +ENTRYPOINT ["ws"] +CMD ["--help"] diff --git a/extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_bionic b/extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_bionic new file mode 100644 index 0000000000..28221079fb --- /dev/null +++ b/extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_bionic @@ -0,0 +1,23 @@ +# Build time +FROM ubuntu:bionic as build + +ENV DEBIAN_FRONTEND noninteractive +RUN apt-get update +RUN apt-get -y install wget +RUN mkdir -p /tmp/cmake +WORKDIR /tmp/cmake +RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz +RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz + +RUN apt-get -y install g++ +RUN apt-get -y install libssl-dev +RUN apt-get -y install libz-dev +RUN apt-get -y install make +RUN apt-get -y install python + +COPY . . + +ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin +ENV PATH="${CMAKE_BIN_PATH}:${PATH}" + +RUN ["make", "ws"] diff --git a/extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_disco b/extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_disco new file mode 100644 index 0000000000..c60230ad1b --- /dev/null +++ b/extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_disco @@ -0,0 +1,24 @@ +# Build time +FROM ubuntu:disco as build + +ENV DEBIAN_FRONTEND noninteractive +RUN apt-get update +RUN apt-get -y install wget +RUN mkdir -p /tmp/cmake +WORKDIR /tmp/cmake +RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz +RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz + +RUN apt-get -y install g++ +RUN apt-get -y install libssl-dev +RUN apt-get -y install libz-dev +RUN apt-get -y install make +RUN apt-get -y install python + +COPY . . + +ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin +ENV PATH="${CMAKE_BIN_PATH}:${PATH}" + +# RUN ["make", "test"] +CMD ["sh"] diff --git a/extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_groovy b/extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_groovy new file mode 100644 index 0000000000..a5e45a1b6e --- /dev/null +++ b/extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_groovy @@ -0,0 +1,23 @@ +# Build time +FROM ubuntu:groovy as build + +ENV DEBIAN_FRONTEND noninteractive +RUN apt-get update + +RUN apt-get -y install g++ libssl-dev libz-dev make python ninja-build +RUN apt-get -y install cmake +RUN apt-get -y install gdb + +COPY . /opt +WORKDIR /opt + +# +# To use the container interactively for debugging/building +# 1. Build with +# CMD ["ls"] +# 2. Run with +# docker run --entrypoint sh -it docker-game-eng-dev.addsrv.com/ws:9.10.6 +# + +RUN ["make", "test"] +# CMD ["ls"] diff --git a/extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_precise b/extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_precise new file mode 100644 index 0000000000..da72e6dbe5 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_precise @@ -0,0 +1,27 @@ +# Build time +FROM ubuntu:precise as build + +ENV DEBIAN_FRONTEND noninteractive +RUN apt-get update +RUN apt-get -y install wget +RUN mkdir -p /tmp/cmake +WORKDIR /tmp/cmake +RUN wget --no-check-certificate https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz +RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz + +RUN apt-get -y install g++ +RUN apt-get -y install libssl-dev +RUN apt-get -y install libz-dev +RUN apt-get -y install make +RUN apt-get -y install python +RUN apt-get -y install git + +COPY . . + +ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin +ENV PATH="${CMAKE_BIN_PATH}:${PATH}" + +RUN ["make", "ws_no_python"] + +ENTRYPOINT ["ws"] +CMD ["--help"] diff --git a/extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_trusty b/extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_trusty new file mode 100644 index 0000000000..a0701e1e0b --- /dev/null +++ b/extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_trusty @@ -0,0 +1,22 @@ +# Build time +FROM ubuntu:trusty as build + +ENV DEBIAN_FRONTEND noninteractive +RUN apt-get update +RUN apt-get -y install wget +RUN mkdir -p /tmp/cmake +WORKDIR /tmp/cmake +RUN wget --no-check-certificate https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz +RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz + +RUN apt-get -y install g++ libssl-dev libz-dev make python git + +COPY . . + +ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin +ENV PATH="${CMAKE_BIN_PATH}:${PATH}" + +RUN ["make", "ws_no_python"] + +ENTRYPOINT ["ws"] +CMD ["--help"] diff --git a/extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_xenial b/extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_xenial new file mode 100644 index 0000000000..f13652d0cd --- /dev/null +++ b/extern/IXWebSocket-11.3.2/docker/Dockerfile.ubuntu_xenial @@ -0,0 +1,24 @@ +# Build time +FROM ubuntu:xenial as build + +ENV DEBIAN_FRONTEND noninteractive +RUN apt-get update +RUN apt-get -y install wget +RUN mkdir -p /tmp/cmake +WORKDIR /tmp/cmake +RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz +RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz + +RUN apt-get -y install g++ +RUN apt-get -y install libssl-dev +RUN apt-get -y install libz-dev +RUN apt-get -y install make +RUN apt-get -y install python + +COPY . . + +ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin +ENV PATH="${CMAKE_BIN_PATH}:${PATH}" + +# RUN ["make"] +RUN ["make", "test"] diff --git a/extern/IXWebSocket-11.3.2/docs/CHANGELOG.md b/extern/IXWebSocket-11.3.2/docs/CHANGELOG.md new file mode 100644 index 0000000000..4bd4c69d02 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/docs/CHANGELOG.md @@ -0,0 +1,1210 @@ +# Changelog + +All changes to this project will be documented in this file. + +## [11.3.2] - 2021-11-24 + +(server) Add getters for basic Servers properties (like port, host, etc...) (#327) + fix one compiler warning + +## [11.3.1] - 2021-10-22 + +(library/cmake) Compatible with MbedTLS 3 + fix a bug on Windows where the incorrect remote port is computed (#320) + +## [11.3.0] - 2021-09-20 + +(library/cmake) Only find OpenSSL, MbedTLS, zlib if they have not already been found, make CMake install optional (#317) + Use GNUInstallDirs in cmake (#318) + +## [11.2.10] - 2021-07-27 + +(ws) bump CLI command line parsing library from 1.8 to 2.0 + +## [11.2.9] - 2021-06-08 + +(ws) ws connect has a -g option to gzip decompress messages for API such as the websocket Huobi Global. + +## [11.2.8] - 2021-06-03 + +(websocket client + server) WebSocketMessage class tweak to fix unsafe patterns + +## [11.2.7] - 2021-05-27 + +(websocket server) Handle and accept firefox browser special upgrade value (keep-alive, Upgrade) + +## [11.2.6] - 2021-05-18 + +(Windows) move EINVAL (re)definition from IXSocket.h to IXNetSystem.h (fix #289) + +## [11.2.5] - 2021-04-04 + +(http client) DEL is not an HTTP method name, but DELETE is + +## [11.2.4] - 2021-03-25 + +(cmake) install IXUniquePtr.h + +## [11.2.3] - 2021-03-24 + +(ssl + windows) missing include for CertOpenStore function + +## [11.2.2] - 2021-03-23 + +(ixwebsocket) version bump + +## [11.2.1] - 2021-03-23 + +(ixwebsocket) version bump + +## [11.2.0] - 2021-03-23 + +(ixwebsocket) correct mingw support (gcc on windows) + +## [11.1.4] - 2021-03-23 + +(ixwebsocket) add getMinWaitBetweenReconnectionRetries + +## [11.1.3] - 2021-03-23 + +(ixwebsocket) New option to set the min wait between reconnection attempts. Still default to 1ms. (setMinWaitBetweenReconnectionRetries). + +## [11.1.2] - 2021-03-22 + +(ws) initialize maxWaitBetweenReconnectionRetries to a non zero value ; a zero value was causing spurious reconnections attempts + +## [11.1.1] - 2021-03-20 + +(cmake) Library can be built as a static or a dynamic library, controlled with BUILD_SHARED_LIBS. Default to static library + +## [11.1.0] - 2021-03-16 + +(ixwebsocket) Use LEAN_AND_MEAN Windows define to help with undefined link error when building a DLL. Support websocket server disablePerMessageDeflate option correctly. + +## [11.0.9] - 2021-03-07 + +(ixwebsocket) Expose setHandshakeTimeout method + +## [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 + +(ixwebsocket) use a C++11 compatible make_unique shim + +## [11.0.1] - 2020-11-11 + +(socket) replace a std::vector with an std::array used as a tmp buffer in Socket::readBytes + +## [11.0.0] - 2020-11-11 + +(openssl security fix) in the client to server connection, peer verification is not done in all cases. See https://github.com/machinezone/IXWebSocket/pull/250 + +## [10.5.7] - 2020-11-07 + +(docker) build docker container with zlib disabled + +## [10.5.6] - 2020-11-07 + +(cmake) DEFLATE -> Deflate in CMake to stop warnings about casing + +## [10.5.5] - 2020-11-07 + +(ws autoroute) Display result in compliant way (AUTOROUTE IXWebSocket :: N ms) so that result can be parsed easily + +## [10.5.4] - 2020-10-30 + +(ws gunzip + IXGZipCodec) Can decompress gziped data with libdeflate. ws gunzip computed output filename was incorrect (was the extension aka gz) instead of the file without the extension. Also check whether the output file is writeable. + +## [10.5.3] - 2020-10-19 + +(http code) With zlib disabled, some code should not be reached + +## [10.5.2] - 2020-10-12 + +(ws curl) Add support for --data-binary option, to set the request body. When present the request will be sent with the POST verb + +## [10.5.1] - 2020-10-09 + +(http client + server + ws) Add support for compressing http client requests with gzip. --compress_request argument is used in ws to enable this. The Content-Encoding is set to gzip, and decoded on the server side if present. + +## [10.5.0] - 2020-09-30 + +(http client + server + ws) Add support for uploading files with ws -F foo=@filename, new -D http server option to debug incoming client requests, internal api changed for http POST, PUT and PATCH to supply an HttpFormDataParameters + +## [10.4.9] - 2020-09-30 + +(http server + utility code) Add support for doing gzip compression with libdeflate library, if available + +## [10.4.8] - 2020-09-30 + +(cmake) Stop using FetchContent cmake module to retrieve jsoncpp third party dependency + +## [10.4.7] - 2020-09-28 + +(ws) add gzip and gunzip ws sub commands + +## [10.4.6] - 2020-09-26 + +(cmake) use FetchContent cmake module to retrieve jsoncpp third party dependency + +## [10.4.5] - 2020-09-26 + +(cmake) use FetchContent cmake module to retrieve spdlog third party dependency + +## [10.4.4] - 2020-09-22 + +(cobra connection) retrieve cobra server connection id from the cobra handshake message and display it in ws clients, metrics publisher and bots + +## [10.4.3] - 2020-09-22 + +(cobra 2 cobra) specify as an HTTP header which channel we will republish to + +## [10.4.2] - 2020-09-18 + +(cobra bots) change an error log to a warning log when reconnecting because no messages were received for a minute + +## [10.4.1] - 2020-09-18 + +(cobra connection and bots) set an HTTP header when connecting to help with debugging bots + +## [10.4.0] - 2020-09-12 + +(http server) read body request when the Content-Length is specified + set timeout to read the request to 30 seconds max by default, and make it configurable as a constructor parameter + +## [10.3.5] - 2020-09-09 + +(ws) autoroute command exit on its own once all messages have been received + +## [10.3.4] - 2020-09-04 + +(docker) ws docker file installs strace + +## [10.3.3] - 2020-09-02 + +(ws) echo_client command renamed to autoroute. Command exit once the server close the connection. push_server commands exit once N messages have been sent. + +## [10.3.2] - 2020-08-31 + +(ws + cobra bots) add a cobra_to_cobra ws subcommand to subscribe to a channel and republish received events to a different channel + +## [10.3.1] - 2020-08-28 + +(socket servers) merge the ConnectionInfo class with the ConnectionState one, which simplify all the server apis + +## [10.3.0] - 2020-08-26 + +(ws) set the main thread name, to help with debugging in XCode, gdb, lldb etc... + +## [10.2.9] - 2020-08-19 + +(ws) cobra to python bot / take a module python name as argument foo.bar.baz instead of a path foo/bar/baz.py + +## [10.2.8] - 2020-08-19 + +(ws) on Linux with mbedtls, when the system ca certs are specified (the default) pick up sensible OS supplied paths (tested with CentOS and Alpine) + +## [10.2.7] - 2020-08-18 + +(ws push_server) on the server side, stop sending and close the connection when the remote end has disconnected + +## [10.2.6] - 2020-08-17 + +(ixwebsocket) replace std::unique_ptr with std::array for some fixed arrays (which are in C++11) + +## [10.2.5] - 2020-08-15 + +(ws) merge all ws_*.cpp files into a single one to speedup compilation + +## [10.2.4] - 2020-08-15 + +(socket server) in the loop accepting connections, call select without a timeout on unix to avoid busy looping, and only wake up when a new connection happens + +## [10.2.3] - 2020-08-15 + +(socket server) instead of busy looping with a sleep, only wake up the GC thread when a new thread will have to be joined, (we know that thanks to the ConnectionState OnSetTerminated callback + +## [10.2.2] - 2020-08-15 + +(socket server) add a callback to the ConnectionState to be invoked when the connection is terminated. This will be used by the SocketServer in the future to know on time that the associated connection thread can be terminated. + +## [10.2.1] - 2020-08-15 + +(socket server) do not create a select interrupt object everytime when polling for notifications while waiting for new connections, instead use a persistent one which is a member variable + +## [10.2.0] - 2020-08-14 + +(ixwebsocket client) handle HTTP redirects + +## [10.2.0] - 2020-08-13 + +(ws) upgrade to latest version of nlohmann json (3.9.1 from 3.2.0) + +## [10.1.9] - 2020-08-13 + +(websocket proxy server) add ability to map different hosts to different websocket servers, using a json config file + +## [10.1.8] - 2020-08-12 + +(ws) on macOS, with OpenSSL or MbedTLS, use /etc/ssl/cert.pem as the system certs + +## [10.1.7] - 2020-08-11 + +(ws) -q option imply info log level, not warning log level + +## [10.1.6] - 2020-08-06 + +(websocket server) Handle programmer error when the server callback is not registered properly (fix #227) + +## [10.1.5] - 2020-08-02 + +(ws) Add a new ws sub-command, push_server. This command runs a server which sends many messages in a loop to a websocket client. We can receive above 200,000 messages per second (cf #235). + +## [10.1.4] - 2020-08-02 + +(ws) Add a new ws sub-command, echo_client. This command sends a message to an echo server, and send back to a server whatever message it does receive. When connecting to a local ws echo_server, on my MacBook Pro 2015 I can send/receive around 30,000 messages per second. (cf #235) + +## [10.1.3] - 2020-08-02 + +(ws) ws echo_server. Add a -q option to only enable warning and error log levels. This is useful for bench-marking so that we do not print a lot of things on the console. (cf #235) + +## [10.1.2] - 2020-07-31 + +(build) make using zlib optional, with the caveat that some http and websocket features are not available when zlib is absent + +## [10.1.1] - 2020-07-29 + +(websocket client) onProgressCallback not called for short messages on a websocket (fix #233) + +## [10.1.0] - 2020-07-29 + +(websocket client) heartbeat is not sent at the requested frequency (fix #232) + +## [10.0.3] - 2020-07-28 + +compiler warning fixes + +## [10.0.2] - 2020-07-28 + +(ixcobra) CobraConnection: unsubscribe from all subscriptions when disconnecting + +## [10.0.1] - 2020-07-27 + +(socket utility) move ix::getFreePort to ixwebsocket library + +## [10.0.0] - 2020-07-25 + +(ixwebsocket server) change legacy api with 2 nested callbacks, so that the first api takes a weak_ptr as its first argument + +## [9.10.7] - 2020-07-25 + +(ixwebsocket) add WebSocketProxyServer, from ws. Still need to make the interface better. + +## [9.10.6] - 2020-07-24 + +(ws) port broadcast_server sub-command to the new server API + +## [9.10.5] - 2020-07-24 + +(unittest) port most unittests to the new server API + +## [9.10.3] - 2020-07-24 + +(ws) port ws transfer to the new server API + +## [9.10.2] - 2020-07-24 + +(websocket client) reset WebSocketTransport onClose callback in the WebSocket destructor + +## [9.10.1] - 2020-07-24 + +(websocket server) reset client websocket callback when the connection is closed + +## [9.10.0] - 2020-07-23 + +(websocket server) add a new simpler API to handle client connections / that API does not trigger a memory leak while the previous one did + +## [9.9.3] - 2020-07-17 + +(build) merge platform specific files which were used to have different implementations for setting a thread name into a single file, to make it easier to include every source files and build the ixwebsocket library (fix #226) + +## [9.9.2] - 2020-07-10 + +(socket server) bump default max connection count from 32 to 128 + +## [9.9.1] - 2020-07-10 + +(snake) implement super simple stream sql expression support in snake server + +## [9.9.0] - 2020-07-08 + +(socket+websocket+http+redis+snake servers) expose the remote ip and remote port when a new connection is made + +## [9.8.6] - 2020-07-06 + +(cmake) change the way zlib and openssl are searched + +## [9.8.5] - 2020-07-06 + +(cobra python bots) remove the test which stop the bot when events do not follow cobra metrics system schema with an id and a device entry + +## [9.8.4] - 2020-06-26 + +(cobra bots) remove bots which is not required now that we can use Python extensions + +## [9.8.3] - 2020-06-25 + +(cmake) new python code is optional and enabled at cmake time with -DUSE_PYTHON=1 + +## [9.8.2] - 2020-06-24 + +(cobra bots) new cobra metrics bot to send data to statsd using Python for processing the message + +## [9.8.1] - 2020-06-19 + +(cobra metrics to statsd bot) fps slow frame info : do not include os name + +## [9.8.0] - 2020-06-19 + +(cobra metrics to statsd bot) send info about memory warnings + +## [9.7.9] - 2020-06-18 + +(http client) fix deadlock when following redirects + +## [9.7.8] - 2020-06-18 + +(cobra metrics to statsd bot) send info about net requests + +## [9.7.7] - 2020-06-17 + +(cobra client and bots) add batch_size subscription option for retrieving multiple messages at once + +## [9.7.6] - 2020-06-15 + +(websocket) WebSocketServer is not a final class, so that users can extend it (fix #215) + +## [9.7.5] - 2020-06-15 + +(cobra bots) minor aesthetic change, in how we display http headers with a : then space as key value separator instead of :: with no space + +## [9.7.4] - 2020-06-11 + +(cobra metrics to statsd bot) change from a statsd type of gauge to a timing one + +## [9.7.3] - 2020-06-11 + +(redis cobra bots) capture most used devices in a zset + +## [9.7.2] - 2020-06-11 + +(ws) add bare bone redis-cli like sub-command, with command line editing powered by libnoise + +## [9.7.1] - 2020-06-11 + +(redis cobra bots) ws cobra metrics to redis / hostname invalid parsing + +## [9.7.0] - 2020-06-11 + +(redis cobra bots) xadd with maxlen + fix bug in xadd client implementation and ws cobra metrics to redis command argument parsing + +## [9.6.9] - 2020-06-10 + +(redis cobra bots) update the cobra to redis bot to use the bot framework, and change it to report fps metrics into redis streams. + +## [9.6.6] - 2020-06-04 + +(statsd cobra bots) statsd improvement: prefix does not need a dot as a suffix, message size can be larger than 256 bytes, error handling was invalid, use core logger for logging instead of std::cerr + +## [9.6.5] - 2020-05-29 + +(http server) support gzip compression + +## [9.6.4] - 2020-05-20 + +(compiler fix) support clang 5 and earlier (contributed by @LunarWatcher) + +## [9.6.3] - 2020-05-18 + +(cmake) revert CMake changes to fix #203 and be able to use an external OpenSSL + +## [9.6.2] - 2020-05-17 + +(cmake) make install cmake files optional to not conflict with vcpkg + +## [9.6.1] - 2020-05-17 + +(windows + tls) mbedtls is the default windows tls backend + add ability to load system certificates with mbdetls on windows + +## [9.6.0] - 2020-05-12 + +(ixbots) add options to limit how many messages per minute should be processed + +## [9.5.9] - 2020-05-12 + +(ixbots) add new class to configure a bot to simplify passing options around + +## [9.5.8] - 2020-05-08 + +(openssl tls) (openssl < 1.1) logic inversion - crypto locking callback are not registered properly + +## [9.5.7] - 2020-05-08 + +(cmake) default TLS back to mbedtls on Windows Universal Platform + +## [9.5.6] - 2020-05-06 + +(cobra bots) add a --heartbeat_timeout option to specify when the bot should terminate because no events are received + +## [9.5.5] - 2020-05-06 + +(openssl tls) when OpenSSL is older than 1.1, register the crypto locking callback to be thread safe. Should fix lots of CI failures + +## [9.5.4] - 2020-05-04 + +(cobra bots) do not use a queue to store messages pending processing, let the bot handle queuing + +## [9.5.3] - 2020-04-29 + +(http client) better current request cancellation support when the HttpClient destructor is invoked (see #189) + +## [9.5.2] - 2020-04-27 + +(cmake) fix cmake broken tls option parsing + +## [9.5.1] - 2020-04-27 + +(http client) Set default values for most HttpRequestArgs struct members (fix #185) + +## [9.5.0] - 2020-04-25 + +(ssl) Default to OpenSSL on Windows, since it can load the system certificates by default + +## [9.4.1] - 2020-04-25 + +(header) Add a space between header name and header value since most http parsers expects it, although it it not required. Cf #184 and #155 + +## [9.4.0] - 2020-04-24 + +(ssl) Add support for supplying SSL CA from memory, for OpenSSL and MbedTLS backends + +## [9.3.3] - 2020-04-17 + +(ixbots) display sent/receive message, per seconds as accumulated + +## [9.3.2] - 2020-04-17 + +(ws) add a --logfile option to configure all logs to go to a file + +## [9.3.1] - 2020-04-16 + +(cobra bots) add a utility class to factor out the common bots features (heartbeat) and move all bots to used it + convert cobra_subscribe to be a bot and add a unittest for it + +## [9.3.0] - 2020-04-15 + +(websocket) add a positive number to the heartbeat message sent, incremented each time the heartbeat is sent + +## [9.2.9] - 2020-04-15 + +(ixcobra) change cobra event callback to use a struct instead of several objects, which is more flexible/extensible + +## [9.2.8] - 2020-04-15 + +(ixcobra) make CobraConnection_EventType an enum class (CobraEventType) + +## [9.2.7] - 2020-04-14 + +(ixsentry) add a library method to upload a payload directly to sentry + +## [9.2.6] - 2020-04-14 + +(ixcobra) snake server / handle invalid incoming json messages + cobra subscriber in fluentd mode insert a created_at timestamp entry + +## [9.2.5] - 2020-04-13 + +(websocket) WebSocketMessagePtr is a unique_ptr instead of a shared_ptr + +## [9.2.4] - 2020-04-13 + +(websocket) use persistent member variable as temp variables to encode/decode zlib messages in order to reduce transient allocations + +## [9.2.3] - 2020-04-13 + +(ws) add a --runtime option to ws cobra_subscribe to optionally limit how much time it will run + +## [9.2.2] - 2020-04-04 + +(third_party deps) fix #177, update bundled spdlog to 1.6.0 + +## [9.2.1] - 2020-04-04 + +(windows) when using OpenSSL, the system store is used to populate the cacert. No need to ship a cacert.pem file with your app. + +## [9.2.0] - 2020-04-04 + +(windows) ci: windows build with TLS (mbedtls) + verify that we can be build with OpenSSL + +## [9.1.9] - 2020-03-30 + +(cobra to statsd bot) add ability to extract a numerical value and send a timer event to statsd, with the --timer option + +## [9.1.8] - 2020-03-29 + +(cobra to statsd bot) bot init was missing + capture socket error + +## [9.1.7] - 2020-03-29 + +(cobra to statsd bot) add ability to extract a numerical value and send a gauge event to statsd, with the --gauge option + +## [9.1.6] - 2020-03-29 + +(ws cobra subscriber) use a Json::StreamWriter to write to std::cout, and save one std::string allocation for each message printed + +## [9.1.5] - 2020-03-29 + +(docker) trim down docker image (300M -> 12M) / binary built without symbol and size optimization, and source code not copied over + +## [9.1.4] - 2020-03-28 + +(jsoncpp) update bundled copy to version 1.9.3 (at sha 3beb37ea14aec1bdce1a6d542dc464d00f4a6cec) + +## [9.1.3] - 2020-03-27 + +(docker) alpine docker build with release with debug info, and bundle ca-certificates + +## [9.1.2] - 2020-03-26 + +(mac ssl) rename DarwinSSL -> SecureTransport (see this too -> https://github.com/curl/curl/issues/3733) + +## [9.1.1] - 2020-03-26 + +(websocket) fix data race accessing _socket object without mutex protection when calling wakeUpFromPoll in WebSocketTransport.cpp + +## [9.1.0] - 2020-03-26 + +(ixcobra) add explicit event types for handshake, authentication and subscription failure, and handle those by exiting in ws_cobra_subcribe and friends + +## [9.0.3] - 2020-03-24 + +(ws connect) display statistics about how much time it takes to stop the connection + +## [9.0.2] - 2020-03-24 + +(socket) works with unique_ptr instead of shared_ptr in many places + +## [9.0.1] - 2020-03-24 + +(socket) selectInterrupt member is an unique_ptr instead of being a shared_ptr + +## [9.0.0] - 2020-03-23 + +(websocket) reset per-message deflate codec everytime we connect to a server/client + +## [8.3.4] - 2020-03-23 + +(websocket) fix #167, a long standing issue with sending empty messages with per-message deflate extension (and hopefully other zlib bug) + +## [8.3.3] - 2020-03-22 + +(cobra to statsd) port to windows and add a unittest + +## [8.3.2] - 2020-03-20 + +(websocket+tls) fix hang in tls handshake which could lead to ANR, discovered through unittesting. + +## [8.3.1] - 2020-03-20 + +(cobra) CobraMetricsPublisher can be configure with an ix::CobraConfig + more unittest use SSL in server + client + +## [8.3.0] - 2020-03-18 + +(websocket) Simplify ping/pong based heartbeat implementation + +## [8.2.7] - 2020-03-17 + +(ws) ws connect gains a new option to set the interval at which to send pings +(ws) ws echo_server gains a new option (-p) to disable responding to pings with pongs + +``` +IXWebSocket$ ws connect --ping_interval 2 wss://echo.websocket.org +Type Ctrl-D to exit prompt... +Connecting to url: wss://echo.websocket.org +> ws_connect: connected +[2020-03-17 23:53:02.726] [info] Uri: / +[2020-03-17 23:53:02.726] [info] Headers: +[2020-03-17 23:53:02.727] [info] Connection: Upgrade +[2020-03-17 23:53:02.727] [info] Date: Wed, 18 Mar 2020 06:45:05 GMT +[2020-03-17 23:53:02.727] [info] Sec-WebSocket-Accept: 0gtqbxW0aVL/QI/ICpLFnRaiKgA= +[2020-03-17 23:53:02.727] [info] sec-websocket-extensions: +[2020-03-17 23:53:02.727] [info] Server: Kaazing Gateway +[2020-03-17 23:53:02.727] [info] Upgrade: websocket +[2020-03-17 23:53:04.894] [info] Received pong +[2020-03-17 23:53:06.859] [info] Received pong +[2020-03-17 23:53:08.881] [info] Received pong +[2020-03-17 23:53:10.848] [info] Received pong +[2020-03-17 23:53:12.898] [info] Received pong +[2020-03-17 23:53:14.865] [info] Received pong +[2020-03-17 23:53:16.890] [info] Received pong +[2020-03-17 23:53:18.853] [info] Received pong + +[2020-03-17 23:53:19.388] [info] +ws_connect: connection closed: code 1000 reason Normal closure + +[2020-03-17 23:53:19.502] [info] Received 208 bytes +[2020-03-17 23:53:19.502] [info] Sent 0 bytes +``` + +## [8.2.6] - 2020-03-16 + +(cobra to sentry bot + docker) default docker file uses mbedtls + ws cobra_to_sentry pass tls options to sentryClient. + +## [8.2.5] - 2020-03-13 + +(cobra client) ws cobra subscribe resubscribe at latest position after being disconnected + +## [8.2.4] - 2020-03-13 + +(cobra client) can subscribe with a position + +## [8.2.3] - 2020-03-13 + +(cobra client) pass the message position to the subscription data callback + +## [8.2.2] - 2020-03-12 + +(openssl tls backend) Fix a hand in OpenSSL when using TLS v1.3 ... by disabling TLS v1.3 + +## [8.2.1] - 2020-03-11 + +(cobra) IXCobraConfig struct has tlsOptions and per message deflate options + +## [8.2.0] - 2020-03-11 + +(cobra) add IXCobraConfig struct to pass cobra config around + +## [8.1.9] - 2020-03-09 + +(ws cobra_subscribe) add a --fluentd option to wrap a message in an enveloppe so that fluentd can recognize it + +## [8.1.8] - 2020-03-02 + +(websocket server) fix regression with disabling zlib extension on the server side. If a client does not support this extension the server will handle it fine. We still need to figure out how to disable the option. + +## [8.1.7] - 2020-02-26 + +(websocket) traffic tracker received bytes is message size while it should be wire size + +## [8.1.6] - 2020-02-26 + +(ws_connect) display sent/received bytes statistics on exit + +## [8.1.5] - 2020-02-23 + +(server) give thread name to some usual worker threads / unittest is broken !! + +## [8.1.4] - 2020-02-22 + +(websocket server) fix regression from 8.1.2, where per-deflate message compression was always disabled + +## [8.1.3] - 2020-02-21 + +(client + server) Fix #155 / http header parser should treat the space(s) after the : delimiter as optional. Fixing this bug made us discover that websocket sub-protocols are not properly serialiazed, but start with a , + +## [8.1.2] - 2020-02-18 + +(WebSocketServer) add option to disable deflate compression, exposed with the -x option to ws echo_server + +## [8.1.1] - 2020-02-18 + +(ws cobra to statsd and sentry sender) exit if no messages are received for one minute, which is a sign that something goes wrong on the server side. That should be changed to be configurable in the future + +## [8.1.0] - 2020-02-13 + +(http client + sentry minidump upload) Multipart stream closing boundary is invalid + mark some options as mandatory in the command line tools + +## [8.0.7] - 2020-02-12 + +(build) remove the unused subtree which was causing some way of installing to break + +## [8.0.6] - 2020-01-31 + +(snake) add an option to disable answering pongs as response to pings, to test cobra client behavior with hanged connections + +## [8.0.5] - 2020-01-31 + +(IXCobraConnection) set a ping timeout of 90 seconds. If no pong messages are received as responses to ping for a while, give up and close the connection + +## [8.0.4] - 2020-01-31 + +(cobra to sentry) remove noisy logging + +## [8.0.3] - 2020-01-30 + +(ixcobra) check if we are authenticated in publishNext before trying to publish a message + +## [8.0.2] - 2020-01-28 + +Extract severity level when emitting messages to sentry + +## [8.0.1] - 2020-01-28 + +Fix bug #151 - If a socket connection is interrupted, calling stop() on the IXWebSocket object blocks until the next retry + +## [8.0.0] - 2020-01-26 + +(SocketServer) add ability to bind on an ipv6 address + +## [7.9.6] - 2020-01-22 + +(ws) add a dnslookup sub-command, to get the ip address of a remote host + +## [7.9.5] - 2020-01-14 + +(windows) fix #144, get rid of stubbed/un-implemented windows schannel ssl backend + +## [7.9.4] - 2020-01-12 + +(openssl + mbedssl) fix #140, can send large files with ws send over ssl / still broken with apple ssl + +## [7.9.3] - 2020-01-10 + +(apple ssl) model write method after the OpenSSL one for consistency + +## [7.9.2] - 2020-01-06 + +(apple ssl) unify read and write ssl utility code + +## [7.9.1] - 2020-01-06 + +(websocket client) better error propagation when errors are detected while sending data +(ws send) detect failures to send big files, terminate in those cases and report error + +## [7.9.0] - 2020-01-04 + +(ws send) add option (-x) to disable per message deflate compression + +## [7.8.9] - 2020-01-04 + +(ws send + receive) handle all message types (ping + pong + fragment) / investigate #140 + +## [7.8.8] - 2019-12-28 + +(mbedtls) fix related to private key file parsing and initialization + +## [7.8.6] - 2019-12-28 + +(ws cobra to sentry/statsd) fix for handling null events properly for empty queues + use queue to send data to statsd + +## [7.8.5] - 2019-12-28 + +(ws cobra to sentry) handle null events for empty queues + +## [7.8.4] - 2019-12-27 + +(ws cobra to sentry) game is picked in a fair manner, so that all games get the same share of sent events + +## [7.8.3] - 2019-12-27 + +(ws cobra to sentry) refactor queue related code into a class + +## [7.8.2] - 2019-12-25 + +(ws cobra to sentry) bound the queue size used to hold up cobra messages before they are sent to sentry. Default queue size is a 100 messages. Without such limit the program runs out of memory when a subscriber receive a lot of messages that cannot make it to sentry + +## [7.8.1] - 2019-12-25 + +(ws client) use correct compilation defines so that spdlog is not used as a header only library (reduce binary size and increase compilation speed) + +## [7.8.0] - 2019-12-24 + +(ws client) all commands use spdlog instead of std::cerr or std::cout for logging + +## [7.6.5] - 2019-12-24 + +(cobra client) send a websocket ping every 30s to keep the connection opened + +## [7.6.4] - 2019-12-22 + +(client) error handling, quote url in error case when failing to parse one +(ws) ws_cobra_publish: register callbacks before connecting +(doc) mention mbedtls in supported ssl server backend + +## [7.6.3] - 2019-12-20 + +(tls) add a simple description of the TLS configuration routine for debugging + +## [7.6.2] - 2019-12-20 + +(mbedtls) correct support for using own certificate and private key + +## [7.6.1] - 2019-12-20 + +(ws commands) in websocket proxy, disable automatic reconnections + in Dockerfile, use alpine 3.11 + +## [7.6.0] - 2019-12-19 + +(cobra) Add TLS options to all cobra commands and classes. Add example to the doc. + +## [7.5.8] - 2019-12-18 + +(cobra-to-sentry) capture application version from device field + +## [7.5.7] - 2019-12-18 + +(tls) Experimental TLS server support with mbedtls (windows) + process cert tlsoption (client + server) + +## [7.5.6] - 2019-12-18 + +(tls servers) Make it clear that apple ssl and mbedtls backends do not support SSL in server mode + +## [7.5.5] - 2019-12-17 + +(tls options client) TLSOptions struct _validated member should be initialized to false + +## [7.5.4] - 2019-12-16 + +(websocket client) improve the error message when connecting to a non websocket server + +Before: + +``` +Connection error: Got bad status connecting to example.com:443, status: 200, HTTP Status line: HTTP/1.1 200 OK +``` + +After: + +``` +Connection error: Expecting status 101 (Switching Protocol), got 200 status connecting to example.com:443, HTTP Status line: HTTP/1.1 200 OK +``` + +## [7.5.3] - 2019-12-12 + +(server) attempt at fixing #131 by using blocking writes in server mode + +## [7.5.2] - 2019-12-11 + +(ws) cobra to sentry - created events with sentry tags based on tags present in the cobra messages + +## [7.5.1] - 2019-12-06 + +(mac) convert SSL errors to utf8 + +## [7.5.0] - 2019-12-05 + +- (ws) cobra to sentry. Handle Error 429 Too Many Requests and politely wait before sending more data to sentry. + +In the example below sentry we are sending data too fast, sentry asks us to slow down which we do. Notice how the sent count stop increasing, while we are waiting for 41 seconds. + +``` +[2019-12-05 15:50:33.759] [info] messages received 2449 sent 3 +[2019-12-05 15:50:34.759] [info] messages received 5533 sent 7 +[2019-12-05 15:50:35.759] [info] messages received 8612 sent 11 +[2019-12-05 15:50:36.759] [info] messages received 11562 sent 15 +[2019-12-05 15:50:37.759] [info] messages received 14410 sent 19 +[2019-12-05 15:50:38.759] [info] messages received 17236 sent 23 +[2019-12-05 15:50:39.282] [error] Error sending data to sentry: 429 +[2019-12-05 15:50:39.282] [error] Body: {"exception":[{"stacktrace":{"frames":[{"filename":"WorldScene.lua","function":"WorldScene.lua:1935","lineno":1958},{"filename":"WorldScene.lua","function":"onUpdate_WorldCam","lineno":1921},{"filename":"WorldMapTile.lua","function":"__index","lineno":239}]},"value":"noisytypes: Attempt to call nil(nil,2224139838)!"}],"platform":"python","sdk":{"name":"ws","version":"1.0.0"},"tags":[["game","niso"],["userid","107638363"],["environment","live"]],"timestamp":"2019-12-05T23:50:39Z"} + +[2019-12-05 15:50:39.282] [error] Response: {"error_name":"rate_limit","error":"Creation of this event was denied due to rate limiting"} +[2019-12-05 15:50:39.282] [warning] Error 429 - Too Many Requests. ws will sleep and retry after 41 seconds +[2019-12-05 15:50:39.760] [info] messages received 18839 sent 25 +[2019-12-05 15:50:40.760] [info] messages received 18839 sent 25 +[2019-12-05 15:50:41.760] [info] messages received 18839 sent 25 +[2019-12-05 15:50:42.761] [info] messages received 18839 sent 25 +[2019-12-05 15:50:43.762] [info] messages received 18839 sent 25 +[2019-12-05 15:50:44.763] [info] messages received 18839 sent 25 +[2019-12-05 15:50:45.768] [info] messages received 18839 sent 25 +``` + +## [7.4.5] - 2019-12-03 + +- (ws) #125 / fix build problem when jsoncpp is not installed locally + +## [7.4.4] - 2019-12-03 + +- (ws) #125 / cmake detects an already installed jsoncpp and will try to use this one if present + +## [7.4.3] - 2019-12-03 + +- (http client) use std::unordered_map instead of std::map for HttpParameters and HttpFormDataParameters class aliases + +## [7.4.2] - 2019-12-02 + +- (client) internal IXDNSLookup class requires a valid cancellation request function callback to be passed in + +## [7.4.1] - 2019-12-02 + +- (client) fix an overflow in the exponential back off code + +## [7.4.0] - 2019-11-25 + +- (http client) Add support for multipart HTTP POST upload +- (ixsentry) Add support for uploading a minidump to sentry + +## [7.3.5] - 2019-11-20 + +- On Darwin SSL, add ability to skip peer verification. + +## [7.3.4] - 2019-11-20 + +- 32-bits compile fix, courtesy of @fcojavmc + +## [7.3.1] - 2019-11-16 + +- ws proxy_server / remote server close not forwarded to the client + +## [7.3.0] - 2019-11-15 + +- New ws command: `ws proxy_server`. + +## [7.2.2] - 2019-11-01 + +- Tag a release + minor reformating. + +## [7.2.1] - 2019-10-26 + +- Add unittest to IXSentryClient to lua backtrace parsing code + +## [7.2.0] - 2019-10-24 + +- Add cobra_metrics_to_redis sub-command to create streams for each cobra metric event being received. + +## [7.1.0] - 2019-10-13 + +- Add client support for websocket subprotocol. Look for the new addSubProtocol method for details. + +## [7.0.0] - 2019-10-01 + +- TLS support in server code, only implemented for the OpenSSL SSL backend for now. + +## [6.3.4] - 2019-09-30 + +- all ws subcommands propagate tls options to servers (unimplemented) or ws or http client (implemented) (contributed by Matt DeBoer) + +## [6.3.3] - 2019-09-30 + +- ws has a --version option + +## [6.3.2] - 2019-09-29 + +- (http + websocket clients) can specify cacert and some other tls options (not implemented on all backend). This makes it so that server certs can finally be validated on windows. + +## [6.3.1] - 2019-09-29 + +- Add ability to use OpenSSL on apple platforms. + +## [6.3.0] - 2019-09-28 + +- ixcobra / fix crash in CobraConnection::publishNext when the queue is empty + handle CobraConnection_PublishMode_Batch in CobraMetricsThreadedPublisher + +## [6.2.9] - 2019-09-27 + +- mbedtls fixes / the unittest now pass on macOS, and hopefully will on Windows/AppVeyor as well. + +## [6.2.8] - 2019-09-26 + +- Http server: add options to ws https to redirect all requests to a given url. POST requests will get a 200 and an empty response. + +``` +ws httpd -L --redirect_url https://www.google.com +``` + +## [6.2.7] - 2019-09-25 + +- Stop having ws send subcommand send a binary message in text mode, which would cause error in `make ws_test` shell script test. + +## [6.2.6] - 2019-09-24 + +- Fix 2 race conditions detected with TSan, one in CobraMetricsPublisher::push and another one in WebSocketTransport::sendData (that one was bad). + +## [6.2.5] - 2019-09-23 + +- Add simple Redis Server which is only capable of doing publish / subscribe. New ws redis_server sub-command to use it. The server is used in the unittest, so that we can run on CI in environment where redis isn not available like github actions env. + +## [6.2.4] - 2019-09-22 + +- Add options to configure TLS ; contributed by Matt DeBoer. Only implemented for OpenSSL TLS backend for now. + +## [6.2.3] - 2019-09-21 + +- Fix crash in the Linux unittest in the HTTP client code, in Socket::readBytes +- Cobra Metrics Publisher code returns the message id of the message that got published, to be used to validated that it got sent properly when receiving an ack. + +## [6.2.2] - 2019-09-19 + +- In DNS lookup code, make sure the weak pointer we use lives through the expected scope (if branch) + +## [6.2.1] - 2019-09-17 + +- On error while doing a client handshake, additionally display port number next to the host name + +## [6.2.0] - 2019-09-09 + +- websocket and http server: server does not close the bound client socket in many cases +- improve some websocket error messages +- add a utility function with unittest to parse status line and stop using scanf which triggers warnings on Windows +- update ws CLI11 (our command line argument parsing library) to the latest, which fix a compiler bug about optional + +## [6.1.0] - 2019-09-08 + +- move poll wrapper on top of select (only used on Windows) to the ix namespace + +## [6.0.1] - 2019-09-05 + +- add cobra metrics publisher + server unittest +- add cobra client + server unittest +- ws snake (cobra simple server) add basic support for unsubscription + subscribe send the proper subscription data + redis client subscription can be cancelled +- IXCobraConnection / pdu handlers can crash if they receive json data which is not an object + +## [6.0.0] - 2019-09-04 + +- all client autobahn test should pass ! +- zlib/deflate has a bug with windowsbits == 8, so we silently upgrade it to 9/ (fix autobahn test 13.X which uses 8 for the windows size) + +## [5.2.0] - 2019-09-04 + +- Fragmentation: for sent messages which are compressed, the continuation fragments should not have the rsv1 bit set (fix all autobahn tests for zlib compression 12.X) +- Websocket Server / do a case insensitive string search when looking for an Upgrade header whose value is websocket. (some client use WebSocket with some upper-case characters) + +## [5.1.9] - 2019-09-03 + +- ws autobahn / report progress with spdlog::info to get timing info +- ws autobahn / use condition variables for stopping test case + add more logging on errors + +## [5.1.8] - 2019-09-03 + +- Per message deflate/compression: handle fragmented messages (fix autobahn test: 12.1.X and probably others) + +## [5.1.7] - 2019-09-03 + +- Receiving invalid UTF-8 TEXT message should fail and close the connection (fix remaining autobahn test: 6.X UTF-8 Handling) + +## [5.1.6] - 2019-09-03 + +- Sending invalid UTF-8 TEXT message should fail and close the connection (fix remaining autobahn test: 6.X UTF-8 Handling) +- Fix failing unittest which was sending binary data in text mode with WebSocket::send to call properly call WebSocket::sendBinary instead. +- Validate that the reason is proper utf-8. (fix autobahn test 7.5.1) +- Validate close codes. Autobahn 7.9.* + +## [5.1.5] - 2019-09-03 + +Framentation: data and continuation blocks received out of order (fix autobahn test: 5.9 through 5.20 Fragmentation) + +## [5.1.4] - 2019-09-03 + +Sending invalid UTF-8 TEXT message should fail and close the connection (fix **tons** of autobahn test: 6.X UTF-8 Handling) + +## [5.1.3] - 2019-09-03 + +Message type (TEXT or BINARY) is invalid for received fragmented messages (fix autobahn test: 5.3 through 5.8 Fragmentation) + +## [5.1.2] - 2019-09-02 + +Ping and Pong messages cannot be fragmented (fix autobahn test: 5.1 and 5.2 Fragmentation) + +## [5.1.1] - 2019-09-01 + +Close connections when reserved bits are used (fix autobahn test: 3.X Reserved Bits) + +## [5.1.0] - 2019-08-31 + +- ws autobahn / Add code to test websocket client compliance with the autobahn test-suite +- add utf-8 validation code, not hooked up properly yet +- Ping received with a payload too large (> 125 bytes) trigger a connection closure +- cobra / add tracking about published messages +- cobra / publish returns a message id, that can be used when +- cobra / new message type in the message received handler when publish/ok is received (can be used to implement an ack system). + +## [5.0.9] - 2019-08-30 + +- User-Agent header is set when not specified. +- New option to cap the max wait between reconnection attempts. Still default to 10s. (setMaxWaitBetweenReconnectionRetries). + +``` +ws connect --max_wait 5000 ws://example.com # will only wait 5 seconds max between reconnection attempts +``` + +## [5.0.7] - 2019-08-23 +- WebSocket: add new option to pass in extra HTTP headers when connecting. +- `ws connect` add new option (-H, works like [curl](https://stackoverflow.com/questions/356705/how-to-send-a-header-using-a-http-request-through-a-curl-call)) to pass in extra HTTP headers when connecting + +If you run against `ws echo_server` you will see the headers being received printed in the terminal. +``` +ws connect -H "foo: bar" -H "baz: buz" ws://127.0.0.1:8008 +``` + +- CobraConnection: sets a unique id field for all messages sent to [cobra](https://github.com/machinezone/cobra). +- CobraConnection: sets a counter as a field for each event published. + +## [5.0.6] - 2019-08-22 +- Windows: silly compile error (poll should be in the global namespace) + +## [5.0.5] - 2019-08-22 +- Windows: use select instead of WSAPoll, through a poll wrapper + +## [5.0.4] - 2019-08-20 +- Windows build fixes (there was a problem with the use of ::poll that has a different name on Windows (WSAPoll)) + +## [5.0.3] - 2019-08-14 +- CobraMetricThreadedPublisher _enable flag is an atomic, and CobraMetricsPublisher is enabled by default + +## [5.0.2] - 2019-08-01 +- ws cobra_subscribe has a new -q (quiet) option +- ws cobra_subscribe knows to and display msg stats (count and # of messages received per second) +- ws cobra_subscribe, cobra_to_statsd and cobra_to_sentry commands have a new option, --filter to restrict the events they want to receive + +## [5.0.1] - 2019-07-25 +- ws connect command has a new option to send in binary mode (still default to text) +- ws connect command has readline history thanks to libnoise-cpp. Now ws connect one can use using arrows to lookup previous sent messages and edit them + +## [5.0.0] - 2019-06-23 +### Changed +- New HTTP server / still very early. ws gained a new command, httpd can run a simple webserver serving local files. +- IXDNSLookup. Uses weak pointer + smart_ptr + shared_from_this instead of static sets + mutex to handle object going away before dns lookup has resolved +- cobra_to_sentry / backtraces are reversed and line number is not extracted correctly +- mbedtls and zlib are searched with find_package, and we use the vendored version if nothing is found +- travis CI uses g++ on Linux + +## [4.0.0] - 2019-06-09 +### Changed +- WebSocket::send() sends message in TEXT mode by default +- WebSocketMessage sets a new binary field, which tells whether the received incoming message is binary or text +- WebSocket::send takes a third arg, binary which default to true (can be text too) +- WebSocket callback only take one object, a const ix::WebSocketMessagePtr& msg +- Add explicit WebSocket::sendBinary method +- New headers + WebSocketMessage class to hold message data, still not used across the board +- Add test/compatibility folder with small servers and clients written in different languages and different libraries to test compatibility. +- ws echo_server has a -g option to print a greeting message on connect +- IXSocketMbedTLS: better error handling in close and connect + +## [3.1.2] - 2019-06-06 +### Added +- ws connect has a -x option to disable per message deflate +- Add WebSocket::disablePerMessageDeflate() option. + +## [3.0.0] - 2019-06-xx +### Changed +- TLS, aka SSL works on Windows (websocket and http clients) +- ws command line tool build on Windows +- Async API for HttpClient +- HttpClient API changed to use shared_ptr for response and request diff --git a/extern/IXWebSocket-11.3.2/docs/build.md b/extern/IXWebSocket-11.3.2/docs/build.md new file mode 100644 index 0000000000..09c6f4dd45 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/docs/build.md @@ -0,0 +1,93 @@ +## Build + +### CMake + +CMakefiles for the library and the examples are available. This library has few dependencies, so it is possible to just add the source files into your project. Otherwise the usual way will suffice. + +``` +mkdir build # make a build dir so that you can build out of tree. +cd build +cmake -DUSE_TLS=1 .. +make -j +make install # will install to /usr/local on Unix, on macOS it is a good idea to sudo chown -R `whoami`:staff /usr/local +``` + +Headers and a static library will be installed to the target dir. +There is a unittest which can be executed by typing `make test`. + +Options for building: + +* `-DBUILD_SHARED_LIBS=ON` will build the unittest as a shared libary instead of a static library, which is the default +* `-DUSE_ZLIB=1` will enable zlib support, required for http client + server + websocket per message deflate extension +* `-DUSE_TLS=1` will enable TLS support +* `-DUSE_OPEN_SSL=1` will use [openssl](https://www.openssl.org/) for the TLS support (default on Linux and Windows). When using a custom version of openssl (say a prebuilt version, odd runtime problems can happens, as in #319, and special cmake trickery will be required (see this [comment](https://github.com/machinezone/IXWebSocket/issues/175#issuecomment-620231032)) +* `-DUSE_MBED_TLS=1` will use [mbedlts](https://tls.mbed.org/) for the TLS support +* `-DUSE_WS=1` will build the ws interactive command line tool +* `-DUSE_TEST=1` will build the unittest + +If you are on Windows, look at the [appveyor](https://github.com/machinezone/IXWebSocket/blob/master/appveyor.yml) file (not maintained much though) or rather the [github actions](https://github.com/machinezone/IXWebSocket/blob/master/.github/workflows/unittest_windows.yml) which have instructions for building dependencies. + +It is also possible to externally include the project, so that everything is fetched over the wire when you build like so: + +``` + ExternalProject_Add( + IXWebSocket + GIT_REPOSITORY https://github.com/machinezone/IXWebSocket.git + ... + ) +``` + +### vcpkg + +It is possible to get IXWebSocket through Microsoft [vcpkg](https://github.com/microsoft/vcpkg). + +``` +vcpkg install ixwebsocket +``` +To use the installed package within a cmake project, use the following: +```cmake + set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "") # this is super important in order for cmake to include the vcpkg search/lib paths! + + # find library and its headers + find_path(IXWEBSOCKET_INCLUDE_DIR ixwebsocket/IXWebSocket.h) + find_library(IXWEBSOCKET_LIBRARY ixwebsocket) + # include headers + include_directories(${IXWEBSOCKET_INCLUDE_DIR}) + # ... + target_link_libraries(${PROJECT_NAME} ... ${IXWEBSOCKET_LIBRARY}) # Cmake will automatically fail the generation if the lib was not found, i.e is set to NOTFOUNS + +``` + +### Conan + +[ ![Download](https://api.bintray.com/packages/conan/conan-center/ixwebsocket%3A_/images/download.svg) ](https://bintray.com/conan/conan-center/ixwebsocket%3A_/_latestVersion) + +Conan is currently supported through a recipe in [Conan Center](https://github.com/conan-io/conan-center-index/tree/master/recipes/ixwebsocket) ([Bintray entry](https://bintray.com/conan/conan-center/ixwebsocket%3A_)). + +Package reference + +* Conan 1.21.0 and up: `ixwebsocket/7.9.2` +* Earlier versions: `ixwebsocket/7.9.2@_/_` + +Note that the version listed here might not be the latest one. See Bintray or the recipe itself for the latest version. If you're migrating from the previous, custom Bintray remote, note that the package reference _has_ to be lower-case. + +### Docker + +There is a Dockerfile for running the unittest on Linux, and to run the `ws` tool. It is also available on the docker registry. + +``` +docker run docker.pkg.github.com/machinezone/ixwebsocket/ws:latest --help +``` + +To use docker-compose you must make a docker container first. + +``` +$ make docker +... +$ docker compose up & +... +$ docker exec -it ixwebsocket_ws_1 bash +app@ca2340eb9106:~$ ws --help +ws is a websocket tool +... +``` diff --git a/extern/IXWebSocket-11.3.2/docs/design.md b/extern/IXWebSocket-11.3.2/docs/design.md new file mode 100644 index 0000000000..5c3dbb1339 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/docs/design.md @@ -0,0 +1,82 @@ +## Implementation details + +### Per Message Deflate compression. + +The per message deflate compression option is supported. It can lead to very nice bandbwith savings (20x !) if your messages are similar, which is often the case for example for chat applications. All features of the spec should be supported. + +### TLS/SSL + +Connections can be optionally secured and encrypted with TLS/SSL when using a wss:// endpoint, or using normal un-encrypted socket with ws:// endpoints. AppleSSL is used on iOS and macOS, OpenSSL and mbedTLS can be used on Android, Linux and Windows. + +If you are using OpenSSL, try to be on a version higher than 1.1.x as there there are thread safety problems with 1.0.x. + +### Polling and background thread work + +No manual polling to fetch data is required. Data is sent and received instantly by using a background thread for receiving data and the select [system](http://man7.org/linux/man-pages/man2/select.2.html) call to be notified by the OS of incoming data. No timeout is used for select so that the background thread is only woken up when data is available, to optimize battery life. This is also the recommended way of using select according to the select tutorial, section [select law](https://linux.die.net/man/2/select_tut). Read and Writes to the socket are non blocking. Data is sent right away and not enqueued by writing directly to the socket, which is [possible](https://stackoverflow.com/questions/1981372/are-parallel-calls-to-send-recv-on-the-same-socket-valid) since system socket implementations allow concurrent read/writes. However concurrent writes need to be protected with mutex. + +### Automatic reconnection + +If the remote end (server) breaks the connection, the code will try to perpetually reconnect, by using an exponential backoff strategy, capped at one retry every 10 seconds. This behavior can be disabled. + +### Large messages + +Large frames are broken up into smaller chunks or messages to avoid filling up the os tcp buffers, which is permitted thanks to WebSocket [fragmentation](https://tools.ietf.org/html/rfc6455#section-5.4). Messages up to 1G were sent and received succesfully. + +### Testing + +The library has an interactive tool which is handy for testing compatibility ith other libraries. We have tested our client against Python, Erlang, Node.js, and C++ websocket server libraries. + +The unittest tries to be comprehensive, and has been running on multiple platforms, with different sanitizers such as a thread sanitizer to catch data races or the undefined behavior sanitizer. + +The regression test is running after each commit on github actions for multiple configurations. + +* Linux +* macOS with thread sanitizer +* macOS, with OpenSSL, with thread sanitizer +* macOS, with MbedTLS, with thread sanitizer +* Windows, with MbedTLS (the unittest is not run yet) + +## Limitations + +* On some configuration (mostly Android) certificate validation needs to be setup so that SocketTLSOptions.caFile point to a pem file, such as the one distributed by Firefox. Unless that setup is done connecting to a wss endpoint will display an error. With mbedtls the message will contain `error in handshake : X509 - Certificate verification failed, e.g. CRL, CA or signature check failed`. +* Automatic reconnection works at the TCP socket level, and will detect remote end disconnects. However, if the device/computer network become unreachable (by turning off wifi), it is quite hard to reliably and timely detect it at the socket level using `recv` and `send` error codes. [Here](https://stackoverflow.com/questions/14782143/linux-socket-how-to-detect-disconnected-network-in-a-client-program) is a good discussion on the subject. This behavior is consistent with other runtimes such as node.js. One way to detect a disconnected device with low level C code is to do a name resolution with DNS but this can be expensive. Mobile devices have good and reliable API to do that. +* The server code is using select to detect incoming data, and creates one OS thread per connection. This is not as scalable as strategies using epoll or kqueue. + +## C++ code organization + +Here is a simplistic diagram which explains how the code is structured in term of class/modules. + +``` ++-----------------------+ --- Public +| | Start the receiving Background thread. Auto reconnection. Simple websocket Ping. +| IXWebSocket | Interface used by C++ test clients. No IX dependencies. +| | ++-----------------------+ +| | +| IXWebSocketServer | Run a server and give each connections its own WebSocket object. +| | Each connection is handled in a new OS thread. +| | ++-----------------------+ --- Private +| | +| IXWebSocketTransport | Low level websocket code, framing, managing raw socket. Adapted from easywsclient. +| | ++-----------------------+ +| | +| IXWebSocketHandshake | Establish the connection between client and server. +| | ++-----------------------+ +| | +| IXWebSocket | ws:// Unencrypted Socket handler +| IXWebSocketAppleSSL | wss:// TLS encrypted Socket AppleSSL handler. Used on iOS and macOS +| IXWebSocketOpenSSL | wss:// TLS encrypted Socket OpenSSL handler. Used on Android and Linux +| | Can be used on macOS too. ++-----------------------+ +| | +| IXSocketConnect | Connect to the remote host (client). +| | ++-----------------------+ +| | +| IXDNSLookup | Does DNS resolution asynchronously so that it can be interrupted. +| | ++-----------------------+ +``` diff --git a/extern/IXWebSocket-11.3.2/docs/index.md b/extern/IXWebSocket-11.3.2/docs/index.md new file mode 100644 index 0000000000..0f1f5a01e0 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/docs/index.md @@ -0,0 +1,64 @@ +## Introduction + +[*WebSocket*](https://en.wikipedia.org/wiki/WebSocket) is a computer communications protocol, providing full-duplex and bi-directionnal communication channels over a single TCP connection. *IXWebSocket* is a C++ library for client and server Websocket communication, and for client and server HTTP communication. *TLS* aka *SSL* is supported. The code is derived from [easywsclient](https://github.com/dhbaird/easywsclient) and from the [Satori C SDK](https://github.com/satori-com/satori-rtm-sdk-c). It has been tested on the following platforms. + +* macOS +* iOS +* Linux +* Android +* Windows +* FreeBSD + +## Example code + +```c++ +// Required on Windows +ix::initNetSystem(); + +// Our websocket object +ix::WebSocket webSocket; + +std::string url("ws://localhost:8080/"); +webSocket.setUrl(url); + +// Setup a callback to be fired when a message or an event (open, close, error) is received +webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg) + { + if (msg->type == ix::WebSocketMessageType::Message) + { + std::cout << msg->str << std::endl; + } + } +); + +// Now that our callback is setup, we can start our background thread and receive messages +webSocket.start(); + +// Send a message to the server (default to TEXT mode) +webSocket.send("hello world"); +``` + +## Why another library? + +There are 2 main reasons that explain why IXWebSocket got written. First, we needed a C++ cross-platform client library, which should have few dependencies. What looked like the most solid one, [websocketpp](https://github.com/zaphoyd/websocketpp) did depend on boost and this was not an option for us. Secondly, there were other available libraries with fewer dependencies (C ones), but they required calling an explicit poll routine periodically to know if a client had received data from a server, which was not elegant. + +We started by solving those 2 problems, then we added server websocket code, then an HTTP client, and finally a very simple HTTP server. IXWebSocket comes with a command line utility named ws which is quite handy, and is now packaged with alpine linux. You can install it with `apk add ws`. + +* Few dependencies (only zlib) +* Simple to use ; uses std::string and std::function callbacks. +* Complete support of the websocket protocol, and basic http support. +* Client and Server +* TLS support + +## 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 + +## Contributing + +IXWebSocket is developed on [GitHub](https://github.com/machinezone/IXWebSocket). We'd love to hear about how you use it; opening up an issue on GitHub is ok for that. If things don't work as expected, please create an issue on GitHub, or even better a pull request if you know how to fix your problem. diff --git a/extern/IXWebSocket-11.3.2/docs/packages.md b/extern/IXWebSocket-11.3.2/docs/packages.md new file mode 100644 index 0000000000..e1a307db2d --- /dev/null +++ b/extern/IXWebSocket-11.3.2/docs/packages.md @@ -0,0 +1,94 @@ +Notes on how we can update the different packages for ixwebsocket. + +## VCPKG + +Visit the [releases](https://github.com/machinezone/IXWebSocket/releases) page on Github. A tag must have been made first. + +Download the latest entry. + +``` +$ cd /tmp +/tmp$ curl -s -O -L https://github.com/machinezone/IXWebSocket/archive/v9.1.9.tar.gz +/tmp$ +/tmp$ openssl sha512 v9.1.9.tar.gz +SHA512(v9.1.9.tar.gz)= f1fd731b5f6a9ce6d6d10bee22a5d9d9baaa8ea0564d6c4cd7eb91dcb88a45c49b2c7fdb75f8640a3589c1b30cee33ef5df8dcbb55920d013394d1e33ddd3c8e +``` + +Now go punch those values in the vcpkg ixwebsocket port config files. Here is what the diff look like. + +``` +vcpkg$ git diff +diff --git a/ports/ixwebsocket/CONTROL b/ports/ixwebsocket/CONTROL +index db9c2adc9..4acae5c3f 100644 +--- a/ports/ixwebsocket/CONTROL ++++ b/ports/ixwebsocket/CONTROL +@@ -1,5 +1,5 @@ + Source: ixwebsocket +-Version: 8.0.5 ++Version: 9.1.9 + Build-Depends: zlib + Homepage: https://github.com/machinezone/IXWebSocket + Description: Lightweight WebSocket Client and Server + HTTP Client and Server +diff --git a/ports/ixwebsocket/portfile.cmake b/ports/ixwebsocket/portfile.cmake +index de082aece..68e523a05 100644 +--- a/ports/ixwebsocket/portfile.cmake ++++ b/ports/ixwebsocket/portfile.cmake +@@ -1,8 +1,8 @@ + vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO machinezone/IXWebSocket +- REF v8.0.5 +- SHA512 9dcc20d9a0629b92c62a68a8bd7c8206f18dbd9e93289b0b687ec13c478ce9ad1f3563b38c399c8277b0d3812cc78ca725786ba1dedbc3445b9bdb9b689e8add ++ REF v9.1.9 ++ SHA512 f1fd731b5f6a9ce6d6d10bee22a5d9d9baaa8ea0564d6c4cd7eb91dcb88a45c49b2c7fdb75f8640a3589c1b30cee33ef5df8dcbb55920d013394d1e33ddd3c8e + ) +``` + +You will need a fork of the vcpkg repo to make a pull request. + +``` +git fetch upstream +git co master +git reset --hard upstream/master +git push origin master --force +``` + +Make the pull request (I use a new branch to do that). + +``` +vcpkg$ git co -b feature/ixwebsocket_9.1.9 +M ports/ixwebsocket/CONTROL +M ports/ixwebsocket/portfile.cmake +Switched to a new branch 'feature/ixwebsocket_9.1.9' +vcpkg$ +vcpkg$ +vcpkg$ git commit -am 'ixwebsocket: update to 9.1.9' +[feature/ixwebsocket_9.1.9 8587a4881] ixwebsocket: update to 9.1.9 + 2 files changed, 3 insertions(+), 3 deletions(-) +vcpkg$ +vcpkg$ git push +fatal: The current branch feature/ixwebsocket_9.1.9 has no upstream branch. +To push the current branch and set the remote as upstream, use + + git push --set-upstream origin feature/ixwebsocket_9.1.9 + +vcpkg$ git push --set-upstream origin feature/ixwebsocket_9.1.9 + +Enumerating objects: 11, done. +Counting objects: 100% (11/11), done. +Delta compression using up to 8 threads +Compressing objects: 100% (6/6), done. +Writing objects: 100% (6/6), 621 bytes | 621.00 KiB/s, done. +Total 6 (delta 4), reused 0 (delta 0) +remote: Resolving deltas: 100% (4/4), completed with 4 local objects. +remote: +remote: Create a pull request for 'feature/ixwebsocket_9.1.9' on GitHub by visiting: +remote: https://github.com/bsergean/vcpkg/pull/new/feature/ixwebsocket_9.1.9 +remote: +To https://github.com/bsergean/vcpkg.git + * [new branch] feature/ixwebsocket_9.1.9 -> feature/ixwebsocket_9.1.9 +Branch 'feature/ixwebsocket_9.1.9' set up to track remote branch 'feature/ixwebsocket_9.1.9' from 'origin' by rebasing. +vcpkg$ +``` + +Just visit this url, https://github.com/bsergean/vcpkg/pull/new/feature/ixwebsocket_9.1.9, printed on the console, to make the pull request. diff --git a/extern/IXWebSocket-11.3.2/docs/performance.md b/extern/IXWebSocket-11.3.2/docs/performance.md new file mode 100644 index 0000000000..3720bc5977 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/docs/performance.md @@ -0,0 +1,37 @@ + +## WebSocket Client performance + +We will run a client and a server on the same machine, connecting to localhost. This bench is run on a MacBook Pro from 2015. We can receive over 200,000 (small) messages per second, another way to put it is that it takes 5 micro-second to receive and process one message. This is an indication about the minimal latency to receive messages. + +### Receiving messages + +By using the push_server ws sub-command, the server will send the same message in a loop to any connected client. + +``` +ws push_server -q --send_msg 'yo' +``` + +By using the echo_client ws sub-command, with the -m (mute or no_send), we will display statistics on how many messages we can receive per second. + +``` +$ ws echo_client -m ws://localhost:8008 +[2020-08-02 12:31:17.284] [info] ws_echo_client: connected +[2020-08-02 12:31:17.284] [info] Uri: / +[2020-08-02 12:31:17.284] [info] Headers: +[2020-08-02 12:31:17.284] [info] Connection: Upgrade +[2020-08-02 12:31:17.284] [info] Sec-WebSocket-Accept: byy/pMK2d0PtRwExaaiOnXJTQHo= +[2020-08-02 12:31:17.284] [info] Server: ixwebsocket/10.1.4 macos ssl/SecureTransport zlib 1.2.11 +[2020-08-02 12:31:17.284] [info] Upgrade: websocket +[2020-08-02 12:31:17.663] [info] messages received: 0 per second 2595307 total +[2020-08-02 12:31:18.668] [info] messages received: 79679 per second 2674986 total +[2020-08-02 12:31:19.668] [info] messages received: 207438 per second 2882424 total +[2020-08-02 12:31:20.673] [info] messages received: 209207 per second 3091631 total +[2020-08-02 12:31:21.676] [info] messages received: 216056 per second 3307687 total +[2020-08-02 12:31:22.680] [info] messages received: 214927 per second 3522614 total +[2020-08-02 12:31:23.684] [info] messages received: 216960 per second 3739574 total +[2020-08-02 12:31:24.688] [info] messages received: 215232 per second 3954806 total +[2020-08-02 12:31:25.691] [info] messages received: 212300 per second 4167106 total +[2020-08-02 12:31:26.694] [info] messages received: 212501 per second 4379607 total +[2020-08-02 12:31:27.699] [info] messages received: 212330 per second 4591937 total +[2020-08-02 12:31:28.702] [info] messages received: 216511 per second 4808448 total +``` diff --git a/extern/IXWebSocket-11.3.2/docs/usage.md b/extern/IXWebSocket-11.3.2/docs/usage.md new file mode 100644 index 0000000000..f3686ee0ee --- /dev/null +++ b/extern/IXWebSocket-11.3.2/docs/usage.md @@ -0,0 +1,601 @@ +# Examples + +The [*ws*](https://github.com/machinezone/IXWebSocket/tree/master/ws) folder countains many interactive programs for chat, [file transfers](https://github.com/machinezone/IXWebSocket/blob/master/ws/ws_send.cpp), [curl like](https://github.com/machinezone/IXWebSocket/blob/master/ws/ws_http_client.cpp) http clients, demonstrating client and server usage. + +## Windows note + +To use the network system on Windows, you need to initialize it once with *WSAStartup()* and clean it up with *WSACleanup()*. We have helpers for that which you can use, see below. This init would typically take place in your main function. + +```cpp +#include + +int main() +{ + ix::initNetSystem(); + + ... + + ix::uninitNetSystem(); + return 0; +} +``` + +## WebSocket client API + +```cpp +#include + +... + +// Our websocket object +ix::WebSocket webSocket; + +std::string url("ws://localhost:8080/"); +webSocket.setUrl(url); + +// Optional heart beat, sent every 45 seconds when there is not any traffic +// to make sure that load balancers do not kill an idle connection. +webSocket.setPingInterval(45); + +// Per message deflate connection is enabled by default. You can tweak its parameters or disable it +webSocket.disablePerMessageDeflate(); + +// Setup a callback to be fired when a message or an event (open, close, error) is received +webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg) + { + if (msg->type == ix::WebSocketMessageType::Message) + { + std::cout << msg->str << std::endl; + } + } +); + +// Now that our callback is setup, we can start our background thread and receive messages +webSocket.start(); + +// Send a message to the server (default to TEXT mode) +webSocket.send("hello world"); + +// The message can be sent in BINARY mode (useful if you send MsgPack data for example) +webSocket.sendBinary("some serialized binary data"); + +// ... finally ... + +// Stop the connection +webSocket.stop() +``` + +### Sending messages + +`WebSocketSendInfo result = websocket.send("foo")` will send a message. + +If the connection was closed, sending will fail, and the success field of the result object will be set to false. There could also be a compression error in which case the compressError field will be set to true. The payloadSize field and wireSize fields will tell you respectively how much bytes the message weight, and how many bytes were sent on the wire (potentially compressed + counting the message header (a few bytes). + +There is an optional progress callback that can be passed in as the second argument. If a message is large it will be fragmented into chunks which will be sent independantly. Everytime the we can write a fragment into the OS network cache, the callback will be invoked. If a user wants to cancel a slow send, false should be returned from within the callback. + +Here is an example code snippet copied from the ws send sub-command. Each fragment weights 32K, so the total integer is the wireSize divided by 32K. As an example if you are sending 32M of data, uncompressed, total will be 1000. current will be set to 0 for the first fragment, then 1, 2 etc... + +``` +auto result = + _webSocket.sendBinary(serializedMsg, [this, throttle](int current, int total) -> bool { + spdlog::info("ws_send: Step {} out of {}", current + 1, total); + + if (throttle) + { + std::chrono::duration duration(10); + std::this_thread::sleep_for(duration); + } + + return _connected; + }); +``` + +### ReadyState + +`getReadyState()` returns the state of the connection. There are 4 possible states. + +1. ReadyState::Connecting - The connection is not yet open. +2. ReadyState::Open - The connection is open and ready to communicate. +3. ReadyState::Closing - The connection is in the process of closing. +4. ReadyState::Closed - The connection is closed or could not be opened. + +### Open and Close notifications + +The onMessage event will be fired when the connection is opened or closed. This is similar to the [JavaScript browser API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket), which has `open` and `close` events notification that can be registered with the browser `addEventListener`. + +```cpp +webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg) + { + if (msg->type == ix::WebSocketMessageType::Open) + { + std::cout << "send greetings" << std::endl; + + // Headers can be inspected (pairs of string/string) + std::cout << "Handshake Headers:" << std::endl; + for (auto it : msg->headers) + { + std::cout << it.first << ": " << it.second << std::endl; + } + } + else if (msg->type == ix::WebSocketMessageType::Close) + { + std::cout << "disconnected" << std::endl; + + // The server can send an explicit code and reason for closing. + // This data can be accessed through the closeInfo object. + std::cout << msg->closeInfo.code << std::endl; + std::cout << msg->closeInfo.reason << std::endl; + } + } +); +``` + +### Error notification + +A message will be fired when there is an error with the connection. The message type will be `ix::WebSocketMessageType::Error`. Multiple fields will be available on the event to describe the error. + +```cpp +webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg) + { + if (msg->type == ix::WebSocketMessageType::Error) + { + std::stringstream ss; + ss << "Error: " << msg->errorInfo.reason << std::endl; + ss << "#retries: " << msg->eventInfo.retries << std::endl; + ss << "Wait time(ms): " << msg->eventInfo.wait_time << std::endl; + ss << "HTTP Status: " << msg->eventInfo.http_status << std::endl; + std::cout << ss.str() << std::endl; + } + } +); +``` + +### start, stop + +1. `websocket.start()` connect to the remote server and starts the message receiving background thread. +2. `websocket.stop()` disconnect from the remote server and closes the background thread. + +### Configuring the remote url + +The url can be set and queried after a websocket object has been created. You will have to call `stop` and `start` if you want to disconnect and connect to that new url. + +```cpp +std::string url("wss://example.com"); +websocket.configure(url); +``` + +### Ping/Pong support + +Ping/pong messages are used to implement keep-alive. 2 message types exists to identify ping and pong messages. Note that when a ping message is received, a pong is instantly send back as requested by the WebSocket spec. + +```cpp +webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg) + { + if (msg->type == ix::WebSocketMessageType::Ping || + msg->type == ix::WebSocketMessageType::Pong) + { + std::cout << "pong data: " << msg->str << std::endl; + } + } +); +``` + +A ping message can be sent to the server, with an optional data string. + +```cpp +websocket.ping("ping data, optional (empty string is ok): limited to 125 bytes long"); +``` + +### Heartbeat. + +You can configure an optional heart beat / keep-alive, sent every 45 seconds +when there is no any traffic to make sure that load balancers do not kill an +idle connection. + +```cpp +webSocket.setPingInterval(45); +``` + +### Supply extra HTTP headers. + +You can set extra HTTP headers to be sent during the WebSocket handshake. + +```cpp +WebSocketHttpHeaders headers; +headers["foo"] = "bar"; +webSocket.setExtraHeaders(headers); +``` + +### Subprotocols + +You can specify subprotocols to be set during the WebSocket handshake. For more info you can refer to [this doc](https://hpbn.co/websocket/#subprotocol-negotiation). + +```cpp +webSocket.addSubprotocol("appProtocol-v1"); +webSocket.addSubprotocol("appProtocol-v2"); +``` + +The protocol that the server did accept is available in the open info `protocol` field. + +```cpp +std::cout << "protocol: " << msg->openInfo.protocol << std::endl; +``` + +### Automatic reconnection + +Automatic reconnection kicks in when the connection is disconnected without the user consent. This feature is on by default and can be turned off. + +```cpp +webSocket.enableAutomaticReconnection(); // turn on +webSocket.disableAutomaticReconnection(); // turn off +bool enabled = webSocket.isAutomaticReconnectionEnabled(); // query state +``` + +The technique to calculate wait time is called [exponential +backoff](https://docs.aws.amazon.com/general/latest/gr/api-retries.html). Here +are the default waiting times between attempts (from connecting with `ws connect ws://foo.com`) + +``` +> Connection error: Got bad status connecting to foo.com, status: 301, HTTP Status line: HTTP/1.1 301 Moved Permanently + +#retries: 1 +Wait time(ms): 100 +#retries: 2 +Wait time(ms): 200 +#retries: 3 +Wait time(ms): 400 +#retries: 4 +Wait time(ms): 800 +#retries: 5 +Wait time(ms): 1600 +#retries: 6 +Wait time(ms): 3200 +#retries: 7 +Wait time(ms): 6400 +#retries: 8 +Wait time(ms): 10000 +``` + +The waiting time is capped by default at 10s between 2 attempts, but that value +can be changed and queried. The minimum waiting time can also be set. + +```cpp +webSocket.setMaxWaitBetweenReconnectionRetries(5 * 1000); // 5000ms = 5s +uint32_t m = webSocket.getMaxWaitBetweenReconnectionRetries(); + +webSocket.setMinWaitBetweenReconnectionRetries(1000); // 1000ms = 1s +uint32_t m = webSocket.getMinWaitBetweenReconnectionRetries(); +``` + +## Handshake timeout + +You can control how long to wait until timing out while waiting for the websocket handshake to be performed. + +``` +int handshakeTimeoutSecs = 1; +setHandshakeTimeout(handshakeTimeoutSecs); +``` + +## WebSocket server API + +### Legacy api + +This api was actually changed to take a weak_ptr as the first argument to setOnConnectionCallback ; previously it would take a shared_ptr which was creating cycles and then memory leaks problems. + +```cpp +#include + +... + +// Run a server on localhost at a given port. +// Bound host name, max connections and listen backlog can also be passed in as parameters. +ix::WebSocketServer server(port); + +server.setOnConnectionCallback( + [&server](std::weak_ptr webSocket, + std::shared_ptr connectionState) + { + std::cout << "Remote ip: " << connectionState->remoteIp << std::endl; + + auto ws = webSocket.lock(); + if (ws) + { + ws->setOnMessageCallback( + [webSocket, connectionState, &server](const ix::WebSocketMessagePtr msg) + { + if (msg->type == ix::WebSocketMessageType::Open) + { + std::cout << "New connection" << std::endl; + + // A connection state object is available, and has a default id + // You can subclass ConnectionState and pass an alternate factory + // to override it. It is useful if you want to store custom + // attributes per connection (authenticated bool flag, attributes, etc...) + std::cout << "id: " << connectionState->getId() << std::endl; + + // The uri the client did connect to. + std::cout << "Uri: " << msg->openInfo.uri << std::endl; + + std::cout << "Headers:" << std::endl; + for (auto it : msg->openInfo.headers) + { + std::cout << it.first << ": " << it.second << std::endl; + } + } + else if (msg->type == ix::WebSocketMessageType::Message) + { + // For an echo server, we just send back to the client whatever was received by the server + // All connected clients are available in an std::set. See the broadcast cpp example. + // Second parameter tells whether we are sending the message in binary or text mode. + // Here we send it in the same mode as it was received. + auto ws = webSocket.lock(); + if (ws) + { + ws->send(msg->str, msg->binary); + } + } + } + } + ); + } +); + +auto res = server.listen(); +if (!res.first) +{ + // Error handling + return 1; +} + +// Per message deflate connection is enabled by default. It can be disabled +// which might be helpful when running on low power devices such as a Rasbery Pi +server.disablePerMessageDeflate(); + +// Run the server in the background. Server can be stoped by calling server.stop() +server.start(); + +// Block until server.stop() is called. +server.wait(); + +``` + +### New api + +The new API does not require to use 2 nested callbacks, which is a bit annoying. The real fix is that there was a memory leak due to a shared_ptr cycle, due to passing down a shared_ptr down to the callbacks. + +The webSocket reference is guaranteed to be always valid ; by design the callback will never be invoked with a null webSocket object. + +```cpp +#include + +... + +// Run a server on localhost at a given port. +// Bound host name, max connections and listen backlog can also be passed in as parameters. +ix::WebSocketServer server(port); + +server.setOnClientMessageCallback([](std::shared_ptr connectionState, ix::WebSocket & webSocket, const ix::WebSocketMessagePtr & msg) { + // The ConnectionState object contains information about the connection, + // at this point only the client ip address and the port. + std::cout << "Remote ip: " << connectionState->getRemoteIp() << std::endl; + + if (msg->type == ix::WebSocketMessageType::Open) + { + std::cout << "New connection" << std::endl; + + // A connection state object is available, and has a default id + // You can subclass ConnectionState and pass an alternate factory + // to override it. It is useful if you want to store custom + // attributes per connection (authenticated bool flag, attributes, etc...) + std::cout << "id: " << connectionState->getId() << std::endl; + + // The uri the client did connect to. + std::cout << "Uri: " << msg->openInfo.uri << std::endl; + + std::cout << "Headers:" << std::endl; + for (auto it : msg->openInfo.headers) + { + std::cout << "\t" << it.first << ": " << it.second << std::endl; + } + } + else if (msg->type == ix::WebSocketMessageType::Message) + { + // For an echo server, we just send back to the client whatever was received by the server + // All connected clients are available in an std::set. See the broadcast cpp example. + // Second parameter tells whether we are sending the message in binary or text mode. + // Here we send it in the same mode as it was received. + std::cout << "Received: " << msg->str << std::endl; + + webSocket.send(msg->str, msg->binary); + } +}); + +auto res = server.listen(); +if (!res.first) +{ + // Error handling + return 1; +} + +// Per message deflate connection is enabled by default. It can be disabled +// which might be helpful when running on low power devices such as a Rasbery Pi +server.disablePerMessageDeflate(); + +// Run the server in the background. Server can be stoped by calling server.stop() +server.start(); + +// Block until server.stop() is called. +server.wait(); + +``` + +## HTTP client API + +```cpp +#include + +... + +// +// Preparation +// +HttpClient httpClient; +HttpRequestArgsPtr args = httpClient.createRequest(); + +// Custom headers can be set +WebSocketHttpHeaders headers; +headers["Foo"] = "bar"; +args->extraHeaders = headers; + +// Timeout options +args->connectTimeout = connectTimeout; +args->transferTimeout = transferTimeout; + +// Redirect options +args->followRedirects = followRedirects; +args->maxRedirects = maxRedirects; + +// Misc +args->compress = compress; // Enable gzip compression +args->verbose = verbose; +args->logger = [](const std::string& msg) +{ + std::cout << msg; +}; + +// +// Synchronous Request +// +HttpResponsePtr out; +std::string url = "https://www.google.com"; + +// HEAD request +out = httpClient.head(url, args); + +// GET request +out = httpClient.get(url, args); + +// POST request with parameters +HttpParameters httpParameters; +httpParameters["foo"] = "bar"; + +// HTTP form data can be passed in as well, for multi-part upload of files +HttpFormDataParameters httpFormDataParameters; +httpParameters["baz"] = "booz"; + +out = httpClient.post(url, httpParameters, httpFormDataParameters, args); + +// POST request with a body +out = httpClient.post(url, std::string("foo=bar"), args); + +// PUT and PATCH are available too. + +// +// Result +// +auto statusCode = response->statusCode; // Can be HttpErrorCode::Ok, HttpErrorCode::UrlMalformed, etc... +auto errorCode = response->errorCode; // 200, 404, etc... +auto responseHeaders = response->headers; // All the headers in a special case-insensitive unordered_map of (string, string) +auto body = response->body; // All the bytes from the response as an std::string +auto errorMsg = response->errorMsg; // Descriptive error message in case of failure +auto uploadSize = response->uploadSize; // Byte count of uploaded data +auto downloadSize = response->downloadSize; // Byte count of downloaded data + +// +// Asynchronous Request +// +bool async = true; +HttpClient httpClient(async); +auto args = httpClient.createRequest(url, HttpClient::kGet); + +// Push the request to a queue, +bool ok = httpClient.performRequest(args, [](const HttpResponsePtr& response) + { + // This callback execute in a background thread. Make sure you uses appropriate protection such as mutex + auto statusCode = response->statusCode; // acess results + } +); + +// ok will be false if your httpClient is not async +``` + +See this [issue](https://github.com/machinezone/IXWebSocket/issues/209) for links about uploading files with HTTP multipart. + +## HTTP server API + +```cpp +#include + +ix::HttpServer server(port, hostname); + +auto res = server.listen(); +if (!res.first) +{ + std::cerr << res.second << std::endl; + return 1; +} + +server.start(); +server.wait(); +``` + +If you want to handle how requests are processed, implement the setOnConnectionCallback callback, which takes an HttpRequestPtr as input, and returns an HttpResponsePtr. You can look at HttpServer::setDefaultConnectionCallback for a slightly more advanced callback example. + +```cpp +setOnConnectionCallback( + [this](HttpRequestPtr request, + std::shared_ptr connectionState) -> HttpResponsePtr + { + // Build a string for the response + std::stringstream ss; + ss << connectionState->getRemoteIp(); + << " " + << request->method + << " " + << request->uri; + + std::string content = ss.str(); + + return std::make_shared(200, "OK", + HttpErrorCode::Ok, + WebSocketHttpHeaders(), + content); +} +``` + +## TLS support and configuration + +To leverage TLS features, the library must be compiled with the option `USE_TLS=1`. + +If you are using OpenSSL, try to be on a version higher than 1.1.x as there there are thread safety problems with 1.0.x. + +Then, secure sockets are automatically used when connecting to a `wss://*` url. + +Additional TLS options can be configured by passing a `ix::SocketTLSOptions` instance to the +`setTLSOptions` on `ix::WebSocket` (or `ix::WebSocketServer` or `ix::HttpServer`) + +```cpp +webSocket.setTLSOptions({ + .certFile = "path/to/cert/file.pem", + .keyFile = "path/to/key/file.pem", + .caFile = "path/to/trust/bundle/file.pem", // as a file, or in memory buffer in PEM format + .tls = true // required in server mode +}); +``` + +Specifying `certFile` and `keyFile` configures the certificate that will be used to communicate with TLS peers. + +On a client, this is only necessary for connecting to servers that require a client certificate. + +On a server, this is necessary for TLS support. + +Specifying `caFile` configures the trusted roots bundle file (in PEM format) that will be used to verify peer certificates. + - The special value of `SYSTEM` (the default) indicates that the system-configured trust bundle should be used; this is generally what you want when connecting to any publicly exposed API/server. + - The special value of `NONE` can be used to disable peer verification; this is only recommended to rule out certificate verification when testing connectivity. + - If the value contain the special value `-----BEGIN CERTIFICATE-----`, the value will be read from memory, and not from a file. This is convenient on platforms like Android where reading / writing to the file system can be challenging without proper permissions, or without knowing the location of a temp directory. + +For a client, specifying `caFile` can be used if connecting to a server that uses a self-signed cert, or when using a custom CA in an internal environment. + +For a server, specifying `caFile` implies that: +1. You require clients to present a certificate +1. It must be signed by one of the trusted roots in the file diff --git a/extern/IXWebSocket-11.3.2/docs/ws.md b/extern/IXWebSocket-11.3.2/docs/ws.md new file mode 100644 index 0000000000..348e2bafaf --- /dev/null +++ b/extern/IXWebSocket-11.3.2/docs/ws.md @@ -0,0 +1,308 @@ +## General + +ws is a command line tool that should exercise most of the IXWebSocket code, and provide example code. + +``` +ws is a websocket tool +Usage: ws [OPTIONS] SUBCOMMAND + +Options: + -h,--help Print this help message and exit + +Subcommands: + send Send a file + receive Receive a file + transfer Broadcasting server + connect Connect to a remote server + chat Group chat + echo_server Echo server + broadcast_server Broadcasting server + ping Ping pong + curl HTTP Client + httpd HTTP server +``` + +## curl + +The curl subcommand try to be compatible with the curl syntax, to fetch http pages. + +Making a HEAD request with the -I parameter. + +``` +$ ws curl -I https://www.google.com/ + +Accept-Ranges: none +Alt-Svc: quic=":443"; ma=2592000; v="46,43",h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000 +Cache-Control: private, max-age=0 +Content-Type: text/html; charset=ISO-8859-1 +Date: Tue, 08 Oct 2019 21:36:57 GMT +Expires: -1 +P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info." +Server: gws +Set-Cookie: NID=188=ASwfz8GrXQrHCLqAz-AndLOMLcz0rC9yecnf3h0yXZxRL3rTufTU_GDDwERp7qQL7LZ_EB8gCRyPXGERyOSAgaqgnrkoTmvWrwFemRLMaOZ896GrHobi5fV7VLklnSG2w48Gj8xMlwxfP7Z-bX-xR9UZxep1tHM6UmFQdD_GkBE; expires=Wed, 08-Apr-2020 21:36:57 GMT; path=/; domain=.google.com; HttpOnly +Transfer-Encoding: chunked +Vary: Accept-Encoding +X-Frame-Options: SAMEORIGIN +X-XSS-Protection: 0 +Upload size: 143 +Download size: 0 +Status: 200 +``` + +Making a POST request with the -F parameter. + +``` +$ ws curl -F foo=bar https://httpbin.org/post +foo: bar +Downloaded 438 bytes out of 438 +Access-Control-Allow-Credentials: true +Access-Control-Allow-Origin: * +Connection: keep-alive +Content-Encoding: +Content-Length: 438 +Content-Type: application/json +Date: Tue, 08 Oct 2019 21:47:54 GMT +Referrer-Policy: no-referrer-when-downgrade +Server: nginx +X-Content-Type-Options: nosniff +X-Frame-Options: DENY +X-XSS-Protection: 1; mode=block +Upload size: 219 +Download size: 438 +Status: 200 +payload: { + "args": {}, + "data": "", + "files": {}, + "form": { + "foo": "bar" + }, + "headers": { + "Accept": "*/*", + "Content-Length": "7", + "Content-Type": "application/x-www-form-urlencoded", + "Host": "httpbin.org", + "User-Agent": "ixwebsocket/7.0.0 macos ssl/OpenSSL OpenSSL 1.0.2q 20 Nov 2018 zlib 1.2.11" + }, + "json": null, + "origin": "155.94.127.118, 155.94.127.118", + "url": "https://httpbin.org/post" +} +``` + +Passing in a custom header with -H. + +``` +$ ws curl -F foo=bar -H 'my_custom_header: baz' https://httpbin.org/post +my_custom_header: baz +foo: bar +Downloaded 470 bytes out of 470 +Access-Control-Allow-Credentials: true +Access-Control-Allow-Origin: * +Connection: keep-alive +Content-Encoding: +Content-Length: 470 +Content-Type: application/json +Date: Tue, 08 Oct 2019 21:50:25 GMT +Referrer-Policy: no-referrer-when-downgrade +Server: nginx +X-Content-Type-Options: nosniff +X-Frame-Options: DENY +X-XSS-Protection: 1; mode=block +Upload size: 243 +Download size: 470 +Status: 200 +payload: { + "args": {}, + "data": "", + "files": {}, + "form": { + "foo": "bar" + }, + "headers": { + "Accept": "*/*", + "Content-Length": "7", + "Content-Type": "application/x-www-form-urlencoded", + "Host": "httpbin.org", + "My-Custom-Header": "baz", + "User-Agent": "ixwebsocket/7.0.0 macos ssl/OpenSSL OpenSSL 1.0.2q 20 Nov 2018 zlib 1.2.11" + }, + "json": null, + "origin": "155.94.127.118, 155.94.127.118", + "url": "https://httpbin.org/post" +} +``` + +## connect + +The connect command connects to a websocket endpoint, and starts an interactive prompt. Line editing, such as using the direction keys to fetch the last thing you tried to type) is provided. That command is pretty useful to try to send random data to an endpoint and verify that the service handles it with grace (such as sending invalid json). + +``` +ws connect wss://echo.websocket.org +Type Ctrl-D to exit prompt... +Connecting to url: wss://echo.websocket.org +> ws_connect: connected +Uri: / +Handshake Headers: +Connection: Upgrade +Date: Tue, 08 Oct 2019 21:38:44 GMT +Sec-WebSocket-Accept: 2j6LBScZveqrMx1W/GJkCWvZo3M= +sec-websocket-extensions: +Server: Kaazing Gateway +Upgrade: websocket +Received ping +Received ping +Received ping +Hello world ! +> Received 13 bytes +ws_connect: received message: Hello world ! +> Hello world ! +> Received 13 bytes +ws_connect: received message: Hello world ! +``` + +``` +ws connect 'ws://jeanserge.com/v2?appkey=_pubsub' +Type Ctrl-D to exit prompt... +Connecting to url: ws://jeanserge.com/v2?appkey=_pubsub +> ws_connect: connected +Uri: /v2?appkey=_pubsub +Handshake Headers: +Connection: Upgrade +Date: Tue, 08 Oct 2019 21:45:28 GMT +Sec-WebSocket-Accept: LYHmjh9Gsu/Yw7aumQqyPObOEV4= +Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15 +Server: Python/3.7 websockets/8.0.2 +Upgrade: websocket +bababababababab +> ws_connect: connection closed: code 1000 reason + +ws_connect: connected +Uri: /v2?appkey=_pubsub +Handshake Headers: +Connection: Upgrade +Date: Tue, 08 Oct 2019 21:45:44 GMT +Sec-WebSocket-Accept: I1rqxdLgTU+opPi5/zKPBTuXdLw= +Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15 +Server: Python/3.7 websockets/8.0.2 +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 + +``` +ws proxy_server --remote_host ws://127.0.0.1:9000 -v +Listening on 127.0.0.1:8008 +``` + +If you connect to ws://127.0.0.1:8008, the proxy will connect to ws://127.0.0.1:9000 and pass all traffic to this server. + +You can also use a more complex setup if you want to redirect to different websocket servers based on the hostname your client is trying to connect to. If you have multiple CNAME aliases that point to the same server. + +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. + +``` +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. + +``` +ws proxy_server --config_path proxyConfig.json --port 8765 +``` + +## File transfer + +``` +# Start transfer server, which is just a broadcast server at this point +ws transfer # running on port 8080. + +# Start receiver first +ws receive ws://localhost:8080 + +# Then send a file. File will be received and written to disk by the receiver process +ws send ws://localhost:8080 /file/to/path +``` + +## HTTP Client + +``` +$ ws curl --help +HTTP Client +Usage: ws curl [OPTIONS] url + +Positionals: + url TEXT REQUIRED Connection url + +Options: + -h,--help Print this help message and exit + -d TEXT Form data + -F TEXT Form data + -H TEXT Header + --output TEXT Output file + -I Send a HEAD request + -L Follow redirects + --max-redirects INT Max Redirects + -v Verbose + -O Save output to disk + --compress Enable gzip compression + --connect-timeout INT Connection timeout + --transfer-timeout INT Transfer timeout +``` diff --git a/extern/IXWebSocket-11.3.2/httpd.cpp b/extern/IXWebSocket-11.3.2/httpd.cpp new file mode 100644 index 0000000000..b1e580f16f --- /dev/null +++ b/extern/IXWebSocket-11.3.2/httpd.cpp @@ -0,0 +1,46 @@ +/* + * httpd.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2020 Machine Zone, Inc. All rights reserved. + * + * Buid with make httpd + */ + +#include +#include +#include + +int main(int argc, char** argv) +{ + if (argc != 3) + { + std::cerr << "Usage: " << argv[0] + << " " << std::endl; + std::cerr << " " << argv[0] << " 9090 127.0.0.1" << std::endl; + std::cerr << " " << argv[0] << " 9090 0.0.0.0" << std::endl; + return 1; + } + + int port; + std::stringstream ss; + ss << argv[1]; + ss >> port; + std::string hostname(argv[2]); + + std::cout << "Listening on " << hostname + << ":" << port << std::endl; + + ix::HttpServer server(port, hostname); + + auto res = server.listen(); + if (!res.first) + { + std::cout << res.second << std::endl; + return 1; + } + + server.start(); + server.wait(); + + return 0; +} diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXBench.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXBench.cpp new file mode 100644 index 0000000000..75497f8553 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXBench.cpp @@ -0,0 +1,61 @@ +/* + * IXBench.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved. + */ + +#include "IXBench.h" + +#include + +namespace ix +{ + Bench::Bench(const std::string& description) + : _description(description) + { + reset(); + } + + Bench::~Bench() + { + if (!_reported) + { + report(); + } + } + + void Bench::reset() + { + _start = std::chrono::high_resolution_clock::now(); + _reported = false; + } + + void Bench::report() + { + auto now = std::chrono::high_resolution_clock::now(); + auto microseconds = std::chrono::duration_cast(now - _start); + + _duration = microseconds.count(); + std::cerr << _description << " completed in " << _duration << " us" << std::endl; + + setReported(); + } + + void Bench::record() + { + auto now = std::chrono::high_resolution_clock::now(); + auto microseconds = std::chrono::duration_cast(now - _start); + + _duration = microseconds.count(); + } + + void Bench::setReported() + { + _reported = true; + } + + uint64_t Bench::getDuration() const + { + return _duration; + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXBench.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXBench.h new file mode 100644 index 0000000000..c4f904b799 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXBench.h @@ -0,0 +1,32 @@ +/* + * IXBench.h + * Author: Benjamin Sergeant + * Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved. + */ +#pragma once + +#include +#include +#include + +namespace ix +{ + class Bench + { + public: + Bench(const std::string& description); + ~Bench(); + + void reset(); + void record(); + void report(); + void setReported(); + uint64_t getDuration() const; + + private: + std::string _description; + std::chrono::time_point _start; + uint64_t _duration; + bool _reported; + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXCancellationRequest.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXCancellationRequest.cpp new file mode 100644 index 0000000000..fc43e4f00d --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXCancellationRequest.cpp @@ -0,0 +1,35 @@ +/* + * IXCancellationRequest.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#include "IXCancellationRequest.h" + +#include +#include + +namespace ix +{ + CancellationRequest makeCancellationRequestWithTimeout( + int secs, std::atomic& requestInitCancellation) + { + assert(secs > 0); + + auto start = std::chrono::system_clock::now(); + auto timeout = std::chrono::seconds(secs); + + auto isCancellationRequested = [&requestInitCancellation, start, timeout]() -> bool { + // Was an explicit cancellation requested ? + if (requestInitCancellation) return true; + + auto now = std::chrono::system_clock::now(); + if ((now - start) > timeout) return true; + + // No cancellation request + return false; + }; + + return isCancellationRequested; + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXCancellationRequest.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXCancellationRequest.h new file mode 100644 index 0000000000..8b0547d808 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXCancellationRequest.h @@ -0,0 +1,18 @@ +/* + * IXCancellationRequest.h + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include +#include + +namespace ix +{ + using CancellationRequest = std::function; + + CancellationRequest makeCancellationRequestWithTimeout( + int seconds, std::atomic& requestInitCancellation); +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXConnectionState.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXConnectionState.cpp new file mode 100644 index 0000000000..8a559f1919 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXConnectionState.cpp @@ -0,0 +1,73 @@ +/* + * IXConnectionState.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#include "IXConnectionState.h" + +namespace ix +{ + std::atomic ConnectionState::_globalId(0); + + ConnectionState::ConnectionState() + : _terminated(false) + { + computeId(); + } + + void ConnectionState::computeId() + { + _id = std::to_string(_globalId++); + } + + const std::string& ConnectionState::getId() const + { + return _id; + } + + std::shared_ptr ConnectionState::createConnectionState() + { + return std::make_shared(); + } + + void ConnectionState::setOnSetTerminatedCallback(const OnSetTerminatedCallback& callback) + { + _onSetTerminatedCallback = callback; + } + + bool ConnectionState::isTerminated() const + { + return _terminated; + } + + void ConnectionState::setTerminated() + { + _terminated = true; + + if (_onSetTerminatedCallback) + { + _onSetTerminatedCallback(); + } + } + + const std::string& ConnectionState::getRemoteIp() + { + return _remoteIp; + } + + int ConnectionState::getRemotePort() + { + return _remotePort; + } + + void ConnectionState::setRemoteIp(const std::string& remoteIp) + { + _remoteIp = remoteIp; + } + + void ConnectionState::setRemotePort(int remotePort) + { + _remotePort = remotePort; + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXConnectionState.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXConnectionState.h new file mode 100644 index 0000000000..b7530d0b19 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXConnectionState.h @@ -0,0 +1,54 @@ +/* + * IXConnectionState.h + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace ix +{ + using OnSetTerminatedCallback = std::function; + + class ConnectionState + { + public: + ConnectionState(); + virtual ~ConnectionState() = default; + + virtual void computeId(); + virtual const std::string& getId() const; + + void setTerminated(); + bool isTerminated() const; + + const std::string& getRemoteIp(); + int getRemotePort(); + + static std::shared_ptr createConnectionState(); + + private: + void setOnSetTerminatedCallback(const OnSetTerminatedCallback& callback); + + void setRemoteIp(const std::string& remoteIp); + void setRemotePort(int remotePort); + + protected: + std::atomic _terminated; + std::string _id; + OnSetTerminatedCallback _onSetTerminatedCallback; + + static std::atomic _globalId; + + std::string _remoteIp; + int _remotePort; + + friend class SocketServer; + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXDNSLookup.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXDNSLookup.cpp new file mode 100644 index 0000000000..9a2874f18c --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXDNSLookup.cpp @@ -0,0 +1,195 @@ +/* + * IXDNSLookup.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ + +// +// On Windows Universal Platform (uwp), gai_strerror defaults behavior is to returns wchar_t +// which is different from all other platforms. We want the non unicode version. +// See https://github.com/microsoft/vcpkg/pull/11030 +// We could do this in IXNetSystem.cpp but so far we are only using gai_strerror in here. +// +#ifdef _UNICODE +#undef _UNICODE +#endif +#ifdef UNICODE +#undef UNICODE +#endif + +#include "IXDNSLookup.h" + +#include "IXNetSystem.h" +#include +#include +#include + +// mingw build quirks +#if defined(_WIN32) && defined(__GNUC__) +#define AI_NUMERICSERV NI_NUMERICSERV +#define AI_ADDRCONFIG LUP_ADDRCONFIG +#endif + +namespace ix +{ + const int64_t DNSLookup::kDefaultWait = 1; // ms + + DNSLookup::DNSLookup(const std::string& hostname, int port, int64_t wait) + : _hostname(hostname) + , _port(port) + , _wait(wait) + , _res(nullptr) + , _done(false) + { + ; + } + + struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname, + int port, + std::string& errMsg) + { + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + std::string sport = std::to_string(port); + + struct addrinfo* res; + int getaddrinfo_result = getaddrinfo(hostname.c_str(), sport.c_str(), &hints, &res); + if (getaddrinfo_result) + { + errMsg = gai_strerror(getaddrinfo_result); + res = nullptr; + } + return res; + } + + struct addrinfo* DNSLookup::resolve(std::string& errMsg, + const CancellationRequest& isCancellationRequested, + bool cancellable) + { + return cancellable ? resolveCancellable(errMsg, isCancellationRequested) + : resolveUnCancellable(errMsg, isCancellationRequested); + } + + void DNSLookup::release(struct addrinfo* addr) + { + freeaddrinfo(addr); + } + + struct addrinfo* DNSLookup::resolveUnCancellable( + std::string& errMsg, const CancellationRequest& isCancellationRequested) + { + errMsg = "no error"; + + // Maybe a cancellation request got in before the background thread terminated ? + if (isCancellationRequested()) + { + errMsg = "cancellation requested"; + return nullptr; + } + + return getAddrInfo(_hostname, _port, errMsg); + } + + struct addrinfo* DNSLookup::resolveCancellable( + std::string& errMsg, const CancellationRequest& isCancellationRequested) + { + errMsg = "no error"; + + // Can only be called once, otherwise we would have to manage a pool + // of background thread which is overkill for our usage. + if (_done) + { + return nullptr; // programming error, create a second DNSLookup instance + // if you need a second lookup. + } + + // + // Good resource on thread forced termination + // https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread + // + auto ptr = shared_from_this(); + std::weak_ptr self(ptr); + + int port = _port; + std::string hostname(_hostname); + + // We make the background thread doing the work a shared pointer + // instead of a member variable, because it can keep running when + // this object goes out of scope, in case of cancellation + auto t = std::make_shared(&DNSLookup::run, this, self, hostname, port); + t->detach(); + + while (!_done) + { + // Wait for 1 milliseconds, to see if the bg thread has terminated. + // We do not use a condition variable to wait, as destroying this one + // if the bg thread is alive can cause undefined behavior. + std::this_thread::sleep_for(std::chrono::milliseconds(_wait)); + + // Were we cancelled ? + if (isCancellationRequested()) + { + errMsg = "cancellation requested"; + return nullptr; + } + } + + // Maybe a cancellation request got in before the bg terminated ? + if (isCancellationRequested()) + { + errMsg = "cancellation requested"; + return nullptr; + } + + errMsg = getErrMsg(); + return getRes(); + } + + void DNSLookup::run(std::weak_ptr self, + std::string hostname, + int port) // thread runner + { + // We don't want to read or write into members variables of an object that could be + // gone, so we use temporary variables (res) or we pass in by copy everything that + // getAddrInfo needs to work. + std::string errMsg; + struct addrinfo* res = getAddrInfo(hostname, port, errMsg); + + if (auto lock = self.lock()) + { + // Copy result into the member variables + setRes(res); + setErrMsg(errMsg); + + _done = true; + } + } + + void DNSLookup::setErrMsg(const std::string& errMsg) + { + std::lock_guard lock(_errMsgMutex); + _errMsg = errMsg; + } + + const std::string& DNSLookup::getErrMsg() + { + std::lock_guard lock(_errMsgMutex); + return _errMsg; + } + + void DNSLookup::setRes(struct addrinfo* addr) + { + std::lock_guard lock(_resMutex); + _res = addr; + } + + struct addrinfo* DNSLookup::getRes() + { + std::lock_guard lock(_resMutex); + return _res; + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXDNSLookup.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXDNSLookup.h new file mode 100644 index 0000000000..fcdd103d39 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXDNSLookup.h @@ -0,0 +1,67 @@ +/* + * IXDNSLookup.h + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + * + * Resolve a hostname+port to a struct addrinfo obtained with getaddrinfo + * Does this in a background thread so that it can be cancelled, since + * getaddrinfo is a blocking call, and we don't want to block the main thread on Mobile. + */ + +#pragma once + +#include "IXCancellationRequest.h" +#include +#include +#include +#include +#include + +struct addrinfo; + +namespace ix +{ + class DNSLookup : public std::enable_shared_from_this + { + public: + DNSLookup(const std::string& hostname, int port, int64_t wait = DNSLookup::kDefaultWait); + ~DNSLookup() = default; + + struct addrinfo* resolve(std::string& errMsg, + const CancellationRequest& isCancellationRequested, + bool cancellable = true); + + void release(struct addrinfo* addr); + + private: + struct addrinfo* resolveCancellable(std::string& errMsg, + const CancellationRequest& isCancellationRequested); + struct addrinfo* resolveUnCancellable(std::string& errMsg, + const CancellationRequest& isCancellationRequested); + + static struct addrinfo* getAddrInfo(const std::string& hostname, + int port, + std::string& errMsg); + + void run(std::weak_ptr self, std::string hostname, int port); // thread runner + + void setErrMsg(const std::string& errMsg); + const std::string& getErrMsg(); + + void setRes(struct addrinfo* addr); + struct addrinfo* getRes(); + + std::string _hostname; + int _port; + int64_t _wait; + const static int64_t kDefaultWait; + + struct addrinfo* _res; + std::mutex _resMutex; + + std::string _errMsg; + std::mutex _errMsgMutex; + + std::atomic _done; + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXExponentialBackoff.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXExponentialBackoff.cpp new file mode 100644 index 0000000000..1bb57ee529 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXExponentialBackoff.cpp @@ -0,0 +1,31 @@ +/* + * IXExponentialBackoff.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved. + */ + +#include "IXExponentialBackoff.h" + +#include + +namespace ix +{ + uint32_t calculateRetryWaitMilliseconds(uint32_t retryCount, + uint32_t maxWaitBetweenReconnectionRetries, + uint32_t minWaitBetweenReconnectionRetries) + { + uint32_t waitTime = (retryCount < 26) ? (std::pow(2, retryCount) * 100) : 0; + + if (waitTime < minWaitBetweenReconnectionRetries) + { + waitTime = minWaitBetweenReconnectionRetries; + } + + if (waitTime > maxWaitBetweenReconnectionRetries || waitTime == 0) + { + waitTime = maxWaitBetweenReconnectionRetries; + } + + return waitTime; + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXExponentialBackoff.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXExponentialBackoff.h new file mode 100644 index 0000000000..79e19e9fb6 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXExponentialBackoff.h @@ -0,0 +1,16 @@ +/* + * IXExponentialBackoff.h + * Author: Benjamin Sergeant + * Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include + +namespace ix +{ + uint32_t calculateRetryWaitMilliseconds(uint32_t retryCount, + uint32_t maxWaitBetweenReconnectionRetries, + uint32_t minWaitBetweenReconnectionRetries); +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXGetFreePort.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXGetFreePort.cpp new file mode 100644 index 0000000000..e69e8d3dc7 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXGetFreePort.cpp @@ -0,0 +1,97 @@ +/* + * IXGetFreePort.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone. All rights reserved. + */ + +// Using inet_addr will trigger an error on uwp without this +// FIXME: use a different api +#ifdef _WIN32 +#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#endif +#endif + +#include "IXGetFreePort.h" + +#include +#include +#include +#include + +namespace ix +{ + int getAnyFreePortRandom() + { + std::random_device rd; + std::uniform_int_distribution dist(1024 + 1, 65535); + + return dist(rd); + } + + int getAnyFreePort() + { + socket_t sockfd; + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) + { + return getAnyFreePortRandom(); + } + + int enable = 1; + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*) &enable, sizeof(enable)) < 0) + { + return getAnyFreePortRandom(); + } + + // Bind to port 0. This is the standard way to get a free port. + struct sockaddr_in server; // server address information + server.sin_family = AF_INET; + server.sin_port = htons(0); + server.sin_addr.s_addr = inet_addr("127.0.0.1"); + + if (bind(sockfd, (struct sockaddr*) &server, sizeof(server)) < 0) + { + Socket::closeSocket(sockfd); + return getAnyFreePortRandom(); + } + + struct sockaddr_in sa; // server address information + socklen_t len = sizeof(sa); + if (getsockname(sockfd, (struct sockaddr*) &sa, &len) < 0) + { + Socket::closeSocket(sockfd); + return getAnyFreePortRandom(); + } + + int port = ntohs(sa.sin_port); + Socket::closeSocket(sockfd); + + return port; + } + + int getFreePort() + { + while (true) + { +#if defined(__has_feature) +#if __has_feature(address_sanitizer) + int port = getAnyFreePortRandom(); +#else + int port = getAnyFreePort(); +#endif +#else + int port = getAnyFreePort(); +#endif + // + // Only port above 1024 can be used by non root users, but for some + // reason I got port 7 returned with macOS when binding on port 0... + // + if (port > 1024) + { + return port; + } + } + + return -1; + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXGetFreePort.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXGetFreePort.h new file mode 100644 index 0000000000..868faf5264 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXGetFreePort.h @@ -0,0 +1,12 @@ +/* + * IXGetFreePort.h + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone. All rights reserved. + */ + +#pragma once + +namespace ix +{ + int getFreePort(); +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXGzipCodec.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXGzipCodec.cpp new file mode 100644 index 0000000000..fe59bd65a1 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXGzipCodec.cpp @@ -0,0 +1,183 @@ +/* + * IXGzipCodec.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2020 Machine Zone, Inc. All rights reserved. + */ + +#include "IXGzipCodec.h" + +#include "IXBench.h" +#include +#include + +#ifdef IXWEBSOCKET_USE_ZLIB +#include +#endif + +#ifdef IXWEBSOCKET_USE_DEFLATE +#include +#endif + +namespace ix +{ + std::string gzipCompress(const std::string& str) + { +#ifndef IXWEBSOCKET_USE_ZLIB + return std::string(); +#else +#ifdef IXWEBSOCKET_USE_DEFLATE + int compressionLevel = 6; + struct libdeflate_compressor* compressor; + + compressor = libdeflate_alloc_compressor(compressionLevel); + + const void* uncompressed_data = str.data(); + size_t uncompressed_size = str.size(); + void* compressed_data; + size_t actual_compressed_size; + size_t max_compressed_size; + + max_compressed_size = libdeflate_gzip_compress_bound(compressor, uncompressed_size); + compressed_data = malloc(max_compressed_size); + + if (compressed_data == NULL) + { + return std::string(); + } + + actual_compressed_size = libdeflate_gzip_compress( + compressor, uncompressed_data, uncompressed_size, compressed_data, max_compressed_size); + + libdeflate_free_compressor(compressor); + + if (actual_compressed_size == 0) + { + free(compressed_data); + return std::string(); + } + + std::string out; + out.assign(reinterpret_cast(compressed_data), actual_compressed_size); + free(compressed_data); + + return out; +#else + z_stream zs; // z_stream is zlib's control structure + memset(&zs, 0, sizeof(zs)); + + // deflateInit2 configure the file format: request gzip instead of deflate + const int windowBits = 15; + const int GZIP_ENCODING = 16; + + deflateInit2(&zs, + Z_DEFAULT_COMPRESSION, + Z_DEFLATED, + windowBits | GZIP_ENCODING, + 8, + Z_DEFAULT_STRATEGY); + + zs.next_in = (Bytef*) str.data(); + zs.avail_in = (uInt) str.size(); // set the z_stream's input + + int ret; + char outbuffer[32768]; + std::string outstring; + + // retrieve the compressed bytes blockwise + do + { + zs.next_out = reinterpret_cast(outbuffer); + zs.avail_out = sizeof(outbuffer); + + ret = deflate(&zs, Z_FINISH); + + if (outstring.size() < zs.total_out) + { + // append the block to the output string + outstring.append(outbuffer, zs.total_out - outstring.size()); + } + } while (ret == Z_OK); + + deflateEnd(&zs); + + return outstring; +#endif // IXWEBSOCKET_USE_DEFLATE +#endif // IXWEBSOCKET_USE_ZLIB + } + +#ifdef IXWEBSOCKET_USE_DEFLATE + static uint32_t loadDecompressedGzipSize(const uint8_t* p) + { + return ((uint32_t) p[0] << 0) | ((uint32_t) p[1] << 8) | ((uint32_t) p[2] << 16) | + ((uint32_t) p[3] << 24); + } +#endif + + bool gzipDecompress(const std::string& in, std::string& out) + { +#ifndef IXWEBSOCKET_USE_ZLIB + return false; +#else +#ifdef IXWEBSOCKET_USE_DEFLATE + struct libdeflate_decompressor* decompressor; + decompressor = libdeflate_alloc_decompressor(); + + const void* compressed_data = in.data(); + size_t compressed_size = in.size(); + + // Retrieve uncompressed size from the trailer of the gziped data + const uint8_t* ptr = reinterpret_cast(&in.front()); + auto uncompressed_size = loadDecompressedGzipSize(&ptr[compressed_size - 4]); + + // Use it to redimension our output buffer + out.resize(uncompressed_size); + + libdeflate_result result = libdeflate_gzip_decompress( + decompressor, compressed_data, compressed_size, &out.front(), uncompressed_size, NULL); + + libdeflate_free_decompressor(decompressor); + return result == LIBDEFLATE_SUCCESS; +#else + z_stream inflateState; + memset(&inflateState, 0, sizeof(inflateState)); + + inflateState.zalloc = Z_NULL; + inflateState.zfree = Z_NULL; + inflateState.opaque = Z_NULL; + inflateState.avail_in = 0; + inflateState.next_in = Z_NULL; + + if (inflateInit2(&inflateState, 16 + MAX_WBITS) != Z_OK) + { + return false; + } + + inflateState.avail_in = (uInt) in.size(); + inflateState.next_in = (unsigned char*) (const_cast(in.data())); + + const int kBufferSize = 1 << 14; + std::array compressBuffer; + + do + { + inflateState.avail_out = (uInt) kBufferSize; + inflateState.next_out = &compressBuffer.front(); + + int ret = inflate(&inflateState, Z_SYNC_FLUSH); + + if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) + { + inflateEnd(&inflateState); + return false; + } + + out.append(reinterpret_cast(&compressBuffer.front()), + kBufferSize - inflateState.avail_out); + } while (inflateState.avail_out == 0); + + inflateEnd(&inflateState); + return true; +#endif // IXWEBSOCKET_USE_DEFLATE +#endif // IXWEBSOCKET_USE_ZLIB + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXGzipCodec.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXGzipCodec.h new file mode 100644 index 0000000000..8a5fc11306 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXGzipCodec.h @@ -0,0 +1,15 @@ +/* + * IXGzipCodec.h + * Author: Benjamin Sergeant + * Copyright (c) 2020 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include + +namespace ix +{ + std::string gzipCompress(const std::string& str); + bool gzipDecompress(const std::string& in, std::string& out); +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXHttp.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXHttp.cpp new file mode 100644 index 0000000000..56a466c031 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXHttp.cpp @@ -0,0 +1,213 @@ +/* + * IXHttp.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#include "IXHttp.h" + +#include "IXCancellationRequest.h" +#include "IXGzipCodec.h" +#include "IXSocket.h" +#include +#include + +namespace ix +{ + std::string Http::trim(const std::string& str) + { + std::string out; + for (auto c : str) + { + if (c != ' ' && c != '\n' && c != '\r') + { + out += c; + } + } + + return out; + } + + std::pair Http::parseStatusLine(const std::string& line) + { + // Request-Line = Method SP Request-URI SP HTTP-Version CRLF + std::string token; + std::stringstream tokenStream(line); + std::vector tokens; + + // Split by ' ' + while (std::getline(tokenStream, token, ' ')) + { + tokens.push_back(token); + } + + std::string httpVersion; + if (tokens.size() >= 1) + { + httpVersion = trim(tokens[0]); + } + + int statusCode = -1; + if (tokens.size() >= 2) + { + std::stringstream ss; + ss << trim(tokens[1]); + ss >> statusCode; + } + + return std::make_pair(httpVersion, statusCode); + } + + std::tuple Http::parseRequestLine( + const std::string& line) + { + // Request-Line = Method SP Request-URI SP HTTP-Version CRLF + std::string token; + std::stringstream tokenStream(line); + std::vector tokens; + + // Split by ' ' + while (std::getline(tokenStream, token, ' ')) + { + tokens.push_back(token); + } + + std::string method; + if (tokens.size() >= 1) + { + method = trim(tokens[0]); + } + + std::string requestUri; + if (tokens.size() >= 2) + { + requestUri = trim(tokens[1]); + } + + std::string httpVersion; + if (tokens.size() >= 3) + { + httpVersion = trim(tokens[2]); + } + + return std::make_tuple(method, requestUri, httpVersion); + } + + std::tuple Http::parseRequest( + std::unique_ptr& socket, int timeoutSecs) + { + HttpRequestPtr httpRequest; + + std::atomic requestInitCancellation(false); + + auto isCancellationRequested = + makeCancellationRequestWithTimeout(timeoutSecs, requestInitCancellation); + + // Read first line + auto lineResult = socket->readLine(isCancellationRequested); + auto lineValid = lineResult.first; + auto line = lineResult.second; + + if (!lineValid) + { + return std::make_tuple(false, "Error reading HTTP request line", httpRequest); + } + + // Parse request line (GET /foo HTTP/1.1\r\n) + auto requestLine = Http::parseRequestLine(line); + auto method = std::get<0>(requestLine); + auto uri = std::get<1>(requestLine); + auto httpVersion = std::get<2>(requestLine); + + // Retrieve and validate HTTP headers + auto result = parseHttpHeaders(socket, isCancellationRequested); + auto headersValid = result.first; + auto headers = result.second; + + if (!headersValid) + { + return std::make_tuple(false, "Error parsing HTTP headers", httpRequest); + } + + std::string body; + if (headers.find("Content-Length") != headers.end()) + { + int contentLength = 0; + try + { + contentLength = std::stoi(headers["Content-Length"]); + } + catch (const std::exception&) + { + return std::make_tuple( + false, "Error parsing HTTP Header 'Content-Length'", httpRequest); + } + + if (contentLength < 0) + { + return std::make_tuple( + false, "Error: 'Content-Length' should be a positive integer", httpRequest); + } + + auto res = socket->readBytes(contentLength, nullptr, isCancellationRequested); + if (!res.first) + { + return std::make_tuple( + false, std::string("Error reading request: ") + res.second, httpRequest); + } + body = res.second; + } + + // If the content was compressed with gzip, decode it + if (headers["Content-Encoding"] == "gzip") + { +#ifdef IXWEBSOCKET_USE_ZLIB + std::string decompressedPayload; + if (!gzipDecompress(body, decompressedPayload)) + { + return std::make_tuple( + false, std::string("Error during gzip decompression of the body"), httpRequest); + } + body = decompressedPayload; +#else + std::string errorMsg("ixwebsocket was not compiled with gzip support on"); + return std::make_tuple(false, errorMsg, httpRequest); +#endif + } + + httpRequest = std::make_shared(uri, method, httpVersion, body, headers); + return std::make_tuple(true, "", httpRequest); + } + + bool Http::sendResponse(HttpResponsePtr response, std::unique_ptr& socket) + { + // Write the response to the socket + std::stringstream ss; + ss << "HTTP/1.1 "; + ss << response->statusCode; + ss << " "; + ss << response->description; + ss << "\r\n"; + + if (!socket->writeBytes(ss.str(), nullptr)) + { + return false; + } + + // Write headers + ss.str(""); + ss << "Content-Length: " << response->body.size() << "\r\n"; + for (auto&& it : response->headers) + { + ss << it.first << ": " << it.second << "\r\n"; + } + ss << "\r\n"; + + if (!socket->writeBytes(ss.str(), nullptr)) + { + return false; + } + + return response->body.empty() ? true : socket->writeBytes(response->body, nullptr); + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXHttp.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXHttp.h new file mode 100644 index 0000000000..bfdaefccb3 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXHttp.h @@ -0,0 +1,130 @@ +/* + * IXHttp.h + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include "IXProgressCallback.h" +#include "IXWebSocketHttpHeaders.h" +#include +#include + +namespace ix +{ + enum class HttpErrorCode : int + { + Ok = 0, + CannotConnect = 1, + Timeout = 2, + Gzip = 3, + UrlMalformed = 4, + CannotCreateSocket = 5, + SendError = 6, + ReadError = 7, + CannotReadStatusLine = 8, + MissingStatus = 9, + HeaderParsingError = 10, + MissingLocation = 11, + TooManyRedirects = 12, + ChunkReadError = 13, + CannotReadBody = 14, + Invalid = 100 + }; + + struct HttpResponse + { + int statusCode; + std::string description; + HttpErrorCode errorCode; + WebSocketHttpHeaders headers; + std::string body; + std::string errorMsg; + uint64_t uploadSize; + uint64_t downloadSize; + + HttpResponse(int s = 0, + const std::string& des = std::string(), + const HttpErrorCode& c = HttpErrorCode::Ok, + const WebSocketHttpHeaders& h = WebSocketHttpHeaders(), + const std::string& b = std::string(), + const std::string& e = std::string(), + uint64_t u = 0, + uint64_t d = 0) + : statusCode(s) + , description(des) + , errorCode(c) + , headers(h) + , body(b) + , errorMsg(e) + , uploadSize(u) + , downloadSize(d) + { + ; + } + }; + + using HttpResponsePtr = std::shared_ptr; + using HttpParameters = std::unordered_map; + using HttpFormDataParameters = std::unordered_map; + using Logger = std::function; + using OnResponseCallback = std::function; + + struct HttpRequestArgs + { + std::string url; + std::string verb; + WebSocketHttpHeaders extraHeaders; + std::string body; + std::string multipartBoundary; + int connectTimeout = 60; + int transferTimeout = 1800; + bool followRedirects = true; + int maxRedirects = 5; + bool verbose = false; + bool compress = true; + bool compressRequest = false; + Logger logger; + OnProgressCallback onProgressCallback; + }; + + using HttpRequestArgsPtr = std::shared_ptr; + + struct HttpRequest + { + std::string uri; + std::string method; + std::string version; + std::string body; + WebSocketHttpHeaders headers; + + HttpRequest(const std::string& u, + const std::string& m, + const std::string& v, + const std::string& b, + const WebSocketHttpHeaders& h = WebSocketHttpHeaders()) + : uri(u) + , method(m) + , version(v) + , body(b) + , headers(h) + { + } + }; + + using HttpRequestPtr = std::shared_ptr; + + class Http + { + public: + static std::tuple parseRequest( + std::unique_ptr& socket, int timeoutSecs); + static bool sendResponse(HttpResponsePtr response, std::unique_ptr& socket); + + static std::pair parseStatusLine(const std::string& line); + static std::tuple parseRequestLine( + const std::string& line); + static std::string trim(const std::string& str); + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXHttpClient.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXHttpClient.cpp new file mode 100644 index 0000000000..3be8b8b73f --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXHttpClient.cpp @@ -0,0 +1,751 @@ +/* + * IXHttpClient.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#include "IXHttpClient.h" + +#include "IXGzipCodec.h" +#include "IXSocketFactory.h" +#include "IXUrlParser.h" +#include "IXUserAgent.h" +#include "IXWebSocketHttpHeaders.h" +#include +#include +#include +#include +#include +#include + +namespace ix +{ + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods + const std::string HttpClient::kPost = "POST"; + const std::string HttpClient::kGet = "GET"; + const std::string HttpClient::kHead = "HEAD"; + const std::string HttpClient::kDelete = "DELETE"; + const std::string HttpClient::kPut = "PUT"; + const std::string HttpClient::kPatch = "PATCH"; + + HttpClient::HttpClient(bool async) + : _async(async) + , _stop(false) + , _forceBody(false) + { + if (!_async) return; + + _thread = std::thread(&HttpClient::run, this); + } + + HttpClient::~HttpClient() + { + if (!_thread.joinable()) return; + + _stop = true; + _condition.notify_one(); + _thread.join(); + } + + void HttpClient::setTLSOptions(const SocketTLSOptions& tlsOptions) + { + _tlsOptions = tlsOptions; + } + + void HttpClient::setForceBody(bool value) + { + _forceBody = value; + } + + HttpRequestArgsPtr HttpClient::createRequest(const std::string& url, const std::string& verb) + { + auto request = std::make_shared(); + request->url = url; + request->verb = verb; + return request; + } + + bool HttpClient::performRequest(HttpRequestArgsPtr args, + const OnResponseCallback& onResponseCallback) + { + assert(_async && "HttpClient needs its async parameter set to true " + "in order to call performRequest"); + if (!_async) return false; + + // Enqueue the task + { + // acquire lock + std::unique_lock lock(_queueMutex); + + // add the task + _queue.push(std::make_pair(args, onResponseCallback)); + } // release lock + + // wake up one thread + _condition.notify_one(); + + return true; + } + + void HttpClient::run() + { + while (true) + { + HttpRequestArgsPtr args; + OnResponseCallback onResponseCallback; + + { + std::unique_lock lock(_queueMutex); + + while (!_stop && _queue.empty()) + { + _condition.wait(lock); + } + + if (_stop) return; + + auto p = _queue.front(); + _queue.pop(); + + args = p.first; + onResponseCallback = p.second; + } + + if (_stop) return; + + HttpResponsePtr response = request(args->url, args->verb, args->body, args); + onResponseCallback(response); + + if (_stop) return; + } + } + + HttpResponsePtr HttpClient::request(const std::string& url, + const std::string& verb, + const std::string& body, + HttpRequestArgsPtr args, + int redirects) + { + // We only have one socket connection, so we cannot + // make multiple requests concurrently. + std::lock_guard lock(_mutex); + + uint64_t uploadSize = 0; + uint64_t downloadSize = 0; + int code = 0; + WebSocketHttpHeaders headers; + std::string payload; + std::string description; + + std::string protocol, host, path, query; + int port; + + if (!UrlParser::parse(url, protocol, host, path, query, port)) + { + std::stringstream ss; + ss << "Cannot parse url: " << url; + return std::make_shared(code, + description, + HttpErrorCode::UrlMalformed, + headers, + payload, + ss.str(), + uploadSize, + downloadSize); + } + + bool tls = protocol == "https"; + std::string errorMsg; + _socket = createSocket(tls, -1, errorMsg, _tlsOptions); + + if (!_socket) + { + return std::make_shared(code, + description, + HttpErrorCode::CannotCreateSocket, + headers, + payload, + errorMsg, + uploadSize, + downloadSize); + } + + // Build request string + std::stringstream ss; + ss << verb << " " << path << " HTTP/1.1\r\n"; + ss << "Host: " << host << "\r\n"; + +#ifdef IXWEBSOCKET_USE_ZLIB + if (args->compress) + { + ss << "Accept-Encoding: gzip" + << "\r\n"; + } +#endif + + // Append extra headers + for (auto&& it : args->extraHeaders) + { + ss << it.first << ": " << it.second << "\r\n"; + } + + // Set a default Accept header if none is present + if (args->extraHeaders.find("Accept") == args->extraHeaders.end()) + { + ss << "Accept: */*" + << "\r\n"; + } + + // Set a default User agent if none is present + if (args->extraHeaders.find("User-Agent") == args->extraHeaders.end()) + { + ss << "User-Agent: " << userAgent() << "\r\n"; + } + + if (verb == kPost || verb == kPut || verb == kPatch || _forceBody) + { + // Set request compression header +#ifdef IXWEBSOCKET_USE_ZLIB + if (args->compressRequest) + { + ss << "Content-Encoding: gzip" + << "\r\n"; + } +#endif + + ss << "Content-Length: " << body.size() << "\r\n"; + + // Set default Content-Type if unspecified + if (args->extraHeaders.find("Content-Type") == args->extraHeaders.end()) + { + if (args->multipartBoundary.empty()) + { + ss << "Content-Type: application/x-www-form-urlencoded" + << "\r\n"; + } + else + { + ss << "Content-Type: multipart/form-data; boundary=" << args->multipartBoundary + << "\r\n"; + } + } + ss << "\r\n"; + ss << body; + } + else + { + ss << "\r\n"; + } + + std::string req(ss.str()); + std::string errMsg; + + // Make a cancellation object dealing with connection timeout + auto isCancellationRequested = + makeCancellationRequestWithTimeout(args->connectTimeout, _stop); + + bool success = _socket->connect(host, port, errMsg, isCancellationRequested); + if (!success) + { + std::stringstream ss; + ss << "Cannot connect to url: " << url << " / error : " << errMsg; + return std::make_shared(code, + description, + HttpErrorCode::CannotConnect, + headers, + payload, + ss.str(), + uploadSize, + downloadSize); + } + + // Make a new cancellation object dealing with transfer timeout + isCancellationRequested = makeCancellationRequestWithTimeout(args->transferTimeout, _stop); + + if (args->verbose) + { + std::stringstream ss; + ss << "Sending " << verb << " request " + << "to " << host << ":" << port << std::endl + << "request size: " << req.size() << " bytes" << std::endl + << "=============" << std::endl + << req << "=============" << std::endl + << std::endl; + + log(ss.str(), args); + } + + if (!_socket->writeBytes(req, isCancellationRequested)) + { + std::string errorMsg("Cannot send request"); + return std::make_shared(code, + description, + HttpErrorCode::SendError, + headers, + payload, + errorMsg, + uploadSize, + downloadSize); + } + + uploadSize = req.size(); + + auto lineResult = _socket->readLine(isCancellationRequested); + auto lineValid = lineResult.first; + auto line = lineResult.second; + + if (!lineValid) + { + std::string errorMsg("Cannot retrieve status line"); + return std::make_shared(code, + description, + HttpErrorCode::CannotReadStatusLine, + headers, + payload, + errorMsg, + uploadSize, + downloadSize); + } + + if (args->verbose) + { + std::stringstream ss; + ss << "Status line " << line; + log(ss.str(), args); + } + + if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1) + { + std::string errorMsg("Cannot parse response code from status line"); + return std::make_shared(code, + description, + HttpErrorCode::MissingStatus, + headers, + payload, + errorMsg, + uploadSize, + downloadSize); + } + + auto result = parseHttpHeaders(_socket, isCancellationRequested); + auto headersValid = result.first; + headers = result.second; + + if (!headersValid) + { + std::string errorMsg("Cannot parse http headers"); + return std::make_shared(code, + description, + HttpErrorCode::HeaderParsingError, + headers, + payload, + errorMsg, + uploadSize, + downloadSize); + } + + // Redirect ? + if ((code >= 301 && code <= 308) && args->followRedirects) + { + if (headers.find("Location") == headers.end()) + { + std::string errorMsg("Missing location header for redirect"); + return std::make_shared(code, + description, + HttpErrorCode::MissingLocation, + headers, + payload, + errorMsg, + uploadSize, + downloadSize); + } + + if (redirects >= args->maxRedirects) + { + std::stringstream ss; + ss << "Too many redirects: " << redirects; + return std::make_shared(code, + description, + HttpErrorCode::TooManyRedirects, + headers, + payload, + ss.str(), + uploadSize, + downloadSize); + } + + // Recurse + std::string location = headers["Location"]; + return request(location, verb, body, args, redirects + 1); + } + + if (verb == "HEAD") + { + return std::make_shared(code, + description, + HttpErrorCode::Ok, + headers, + payload, + std::string(), + uploadSize, + downloadSize); + } + + // Parse response: + if (headers.find("Content-Length") != headers.end()) + { + ssize_t contentLength = -1; + ss.str(""); + ss << headers["Content-Length"]; + ss >> contentLength; + + payload.reserve(contentLength); + + auto chunkResult = _socket->readBytes( + contentLength, args->onProgressCallback, isCancellationRequested); + if (!chunkResult.first) + { + errorMsg = "Cannot read chunk"; + return std::make_shared(code, + description, + HttpErrorCode::ChunkReadError, + headers, + payload, + errorMsg, + uploadSize, + downloadSize); + } + payload += chunkResult.second; + } + else if (headers.find("Transfer-Encoding") != headers.end() && + headers["Transfer-Encoding"] == "chunked") + { + std::stringstream ss; + + while (true) + { + lineResult = _socket->readLine(isCancellationRequested); + line = lineResult.second; + + if (!lineResult.first) + { + return std::make_shared(code, + description, + HttpErrorCode::ChunkReadError, + headers, + payload, + errorMsg, + uploadSize, + downloadSize); + } + + uint64_t chunkSize; + ss.str(""); + ss << std::hex << line; + ss >> chunkSize; + + if (args->verbose) + { + std::stringstream oss; + oss << "Reading " << chunkSize << " bytes" << std::endl; + log(oss.str(), args); + } + + payload.reserve(payload.size() + (size_t) chunkSize); + + // Read a chunk + auto chunkResult = _socket->readBytes( + (size_t) chunkSize, args->onProgressCallback, isCancellationRequested); + if (!chunkResult.first) + { + errorMsg = "Cannot read chunk"; + return std::make_shared(code, + description, + HttpErrorCode::ChunkReadError, + headers, + payload, + errorMsg, + uploadSize, + downloadSize); + } + payload += chunkResult.second; + + // Read the line that terminates the chunk (\r\n) + lineResult = _socket->readLine(isCancellationRequested); + + if (!lineResult.first) + { + return std::make_shared(code, + description, + HttpErrorCode::ChunkReadError, + headers, + payload, + errorMsg, + uploadSize, + downloadSize); + } + + if (chunkSize == 0) break; + } + } + else if (code == 204) + { + ; // 204 is NoContent response code + } + else + { + std::string errorMsg("Cannot read http body"); + return std::make_shared(code, + description, + HttpErrorCode::CannotReadBody, + headers, + payload, + errorMsg, + uploadSize, + downloadSize); + } + + downloadSize = payload.size(); + + // If the content was compressed with gzip, decode it + if (headers["Content-Encoding"] == "gzip") + { +#ifdef IXWEBSOCKET_USE_ZLIB + std::string decompressedPayload; + if (!gzipDecompress(payload, decompressedPayload)) + { + std::string errorMsg("Error decompressing payload"); + return std::make_shared(code, + description, + HttpErrorCode::Gzip, + headers, + payload, + errorMsg, + uploadSize, + downloadSize); + } + payload = decompressedPayload; +#else + std::string errorMsg("ixwebsocket was not compiled with gzip support on"); + return std::make_shared(code, + description, + HttpErrorCode::Gzip, + headers, + payload, + errorMsg, + uploadSize, + downloadSize); +#endif + } + + return std::make_shared(code, + description, + HttpErrorCode::Ok, + headers, + payload, + std::string(), + uploadSize, + downloadSize); + } + + HttpResponsePtr HttpClient::get(const std::string& url, HttpRequestArgsPtr args) + { + return request(url, kGet, std::string(), args); + } + + HttpResponsePtr HttpClient::head(const std::string& url, HttpRequestArgsPtr args) + { + return request(url, kHead, std::string(), args); + } + + HttpResponsePtr HttpClient::Delete(const std::string& url, HttpRequestArgsPtr args) + { + return request(url, kDelete, std::string(), args); + } + + HttpResponsePtr HttpClient::request(const std::string& url, + const std::string& verb, + const HttpParameters& httpParameters, + const HttpFormDataParameters& httpFormDataParameters, + HttpRequestArgsPtr args) + { + std::string body; + + if (httpFormDataParameters.empty()) + { + body = serializeHttpParameters(httpParameters); + } + else + { + std::string multipartBoundary = generateMultipartBoundary(); + args->multipartBoundary = multipartBoundary; + body = serializeHttpFormDataParameters( + multipartBoundary, httpFormDataParameters, httpParameters); + } + +#ifdef IXWEBSOCKET_USE_ZLIB + if (args->compressRequest) + { + body = gzipCompress(body); + } +#endif + + return request(url, verb, body, args); + } + + HttpResponsePtr HttpClient::post(const std::string& url, + const HttpParameters& httpParameters, + const HttpFormDataParameters& httpFormDataParameters, + HttpRequestArgsPtr args) + { + return request(url, kPost, httpParameters, httpFormDataParameters, args); + } + + HttpResponsePtr HttpClient::post(const std::string& url, + const std::string& body, + HttpRequestArgsPtr args) + { + return request(url, kPost, body, args); + } + + HttpResponsePtr HttpClient::put(const std::string& url, + const HttpParameters& httpParameters, + const HttpFormDataParameters& httpFormDataParameters, + HttpRequestArgsPtr args) + { + return request(url, kPut, httpParameters, httpFormDataParameters, args); + } + + HttpResponsePtr HttpClient::put(const std::string& url, + const std::string& body, + const HttpRequestArgsPtr args) + { + return request(url, kPut, body, args); + } + + HttpResponsePtr HttpClient::patch(const std::string& url, + const HttpParameters& httpParameters, + const HttpFormDataParameters& httpFormDataParameters, + HttpRequestArgsPtr args) + { + return request(url, kPatch, httpParameters, httpFormDataParameters, args); + } + + HttpResponsePtr HttpClient::patch(const std::string& url, + const std::string& body, + const HttpRequestArgsPtr args) + { + return request(url, kPatch, body, args); + } + + std::string HttpClient::urlEncode(const std::string& value) + { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (std::string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) + { + std::string::value_type c = (*i); + + // Keep alphanumeric and other accepted characters intact + if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') + { + escaped << c; + continue; + } + + // Any other characters are percent-encoded + escaped << std::uppercase; + escaped << '%' << std::setw(2) << int((unsigned char) c); + escaped << std::nouppercase; + } + + return escaped.str(); + } + + std::string HttpClient::serializeHttpParameters(const HttpParameters& httpParameters) + { + std::stringstream ss; + size_t count = httpParameters.size(); + size_t i = 0; + + for (auto&& it : httpParameters) + { + ss << urlEncode(it.first) << "=" << urlEncode(it.second); + + if (i++ < (count - 1)) + { + ss << "&"; + } + } + return ss.str(); + } + + std::string HttpClient::serializeHttpFormDataParameters( + const std::string& multipartBoundary, + const HttpFormDataParameters& httpFormDataParameters, + const HttpParameters& httpParameters) + { + // + // --AaB03x + // Content-Disposition: form-data; name="submit-name" + + // Larry + // --AaB03x + // Content-Disposition: form-data; name="foo.txt"; filename="file1.txt" + // Content-Type: text/plain + + // ... contents of file1.txt ... + // --AaB03x-- + // + std::stringstream ss; + + for (auto&& it : httpFormDataParameters) + { + ss << "--" << multipartBoundary << "\r\n" + << "Content-Disposition:" + << " form-data; name=\"" << it.first << "\";" + << " filename=\"" << it.first << "\"" + << "\r\n" + << "Content-Type: application/octet-stream" + << "\r\n" + << "\r\n" + << it.second << "\r\n"; + } + + for (auto&& it : httpParameters) + { + ss << "--" << multipartBoundary << "\r\n" + << "Content-Disposition:" + << " form-data; name=\"" << it.first << "\";" + << "\r\n" + << "\r\n" + << it.second << "\r\n"; + } + + ss << "--" << multipartBoundary << "--\r\n"; + + return ss.str(); + } + + void HttpClient::log(const std::string& msg, HttpRequestArgsPtr args) + { + if (args->logger) + { + args->logger(msg); + } + } + + std::string HttpClient::generateMultipartBoundary() + { + std::string str("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); + + static std::random_device rd; + static std::mt19937 generator(rd()); + + std::shuffle(str.begin(), str.end(), generator); + + return str; + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXHttpClient.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXHttpClient.h new file mode 100644 index 0000000000..c4b0584501 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXHttpClient.h @@ -0,0 +1,123 @@ +/* + * IXHttpClient.h + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include "IXHttp.h" +#include "IXSocket.h" +#include "IXSocketTLSOptions.h" +#include "IXWebSocketHttpHeaders.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ix +{ + class HttpClient + { + public: + HttpClient(bool async = false); + ~HttpClient(); + + HttpResponsePtr get(const std::string& url, HttpRequestArgsPtr args); + HttpResponsePtr head(const std::string& url, HttpRequestArgsPtr args); + HttpResponsePtr Delete(const std::string& url, HttpRequestArgsPtr args); + + HttpResponsePtr post(const std::string& url, + const HttpParameters& httpParameters, + const HttpFormDataParameters& httpFormDataParameters, + HttpRequestArgsPtr args); + HttpResponsePtr post(const std::string& url, + const std::string& body, + HttpRequestArgsPtr args); + + HttpResponsePtr put(const std::string& url, + const HttpParameters& httpParameters, + const HttpFormDataParameters& httpFormDataParameters, + HttpRequestArgsPtr args); + HttpResponsePtr put(const std::string& url, + const std::string& body, + HttpRequestArgsPtr args); + + HttpResponsePtr patch(const std::string& url, + const HttpParameters& httpParameters, + const HttpFormDataParameters& httpFormDataParameters, + HttpRequestArgsPtr args); + HttpResponsePtr patch(const std::string& url, + const std::string& body, + HttpRequestArgsPtr args); + + HttpResponsePtr request(const std::string& url, + const std::string& verb, + const std::string& body, + HttpRequestArgsPtr args, + int redirects = 0); + + HttpResponsePtr request(const std::string& url, + const std::string& verb, + const HttpParameters& httpParameters, + const HttpFormDataParameters& httpFormDataParameters, + HttpRequestArgsPtr args); + + void setForceBody(bool value); + + // Async API + HttpRequestArgsPtr createRequest(const std::string& url = std::string(), + const std::string& verb = HttpClient::kGet); + + bool performRequest(HttpRequestArgsPtr request, + const OnResponseCallback& onResponseCallback); + + // TLS + void setTLSOptions(const SocketTLSOptions& tlsOptions); + + std::string serializeHttpParameters(const HttpParameters& httpParameters); + + std::string serializeHttpFormDataParameters( + const std::string& multipartBoundary, + const HttpFormDataParameters& httpFormDataParameters, + const HttpParameters& httpParameters = HttpParameters()); + + std::string generateMultipartBoundary(); + + std::string urlEncode(const std::string& value); + + const static std::string kPost; + const static std::string kGet; + const static std::string kHead; + const static std::string kDelete; + const static std::string kPut; + const static std::string kPatch; + + private: + void log(const std::string& msg, HttpRequestArgsPtr args); + + // Async API background thread runner + void run(); + // Async API + bool _async; + std::queue> _queue; + mutable std::mutex _queueMutex; + std::condition_variable _condition; + std::atomic _stop; + std::thread _thread; + + std::unique_ptr _socket; + std::recursive_mutex _mutex; // to protect accessing the _socket (only one socket per + // client) the mutex needs to be recursive as this function + // might be called recursively to follow HTTP redirections + + SocketTLSOptions _tlsOptions; + + bool _forceBody; + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXHttpServer.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXHttpServer.cpp new file mode 100644 index 0000000000..563dca9144 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXHttpServer.cpp @@ -0,0 +1,235 @@ +/* + * IXHttpServer.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#include "IXHttpServer.h" + +#include "IXGzipCodec.h" +#include "IXNetSystem.h" +#include "IXSocketConnect.h" +#include "IXUserAgent.h" +#include +#include +#include +#include + +namespace +{ + std::pair> load(const std::string& path) + { + std::vector memblock; + + std::ifstream file(path); + if (!file.is_open()) return std::make_pair(false, memblock); + + file.seekg(0, file.end); + std::streamoff size = file.tellg(); + file.seekg(0, file.beg); + + memblock.resize((size_t) size); + file.read((char*) &memblock.front(), static_cast(size)); + + return std::make_pair(true, memblock); + } + + std::pair readAsString(const std::string& path) + { + auto res = load(path); + auto vec = res.second; + return std::make_pair(res.first, std::string(vec.begin(), vec.end())); + } +} // namespace + +namespace ix +{ + const int HttpServer::kDefaultTimeoutSecs(30); + + HttpServer::HttpServer(int port, + const std::string& host, + int backlog, + size_t maxConnections, + int addressFamily, + int timeoutSecs) + : SocketServer(port, host, backlog, maxConnections, addressFamily) + , _connectedClientsCount(0) + , _timeoutSecs(timeoutSecs) + { + setDefaultConnectionCallback(); + } + + HttpServer::~HttpServer() + { + stop(); + } + + void HttpServer::stop() + { + stopAcceptingConnections(); + + // FIXME: cancelling / closing active clients ... + + SocketServer::stop(); + } + + void HttpServer::setOnConnectionCallback(const OnConnectionCallback& callback) + { + _onConnectionCallback = callback; + } + + void HttpServer::handleConnection(std::unique_ptr socket, + std::shared_ptr connectionState) + { + _connectedClientsCount++; + + auto ret = Http::parseRequest(socket, _timeoutSecs); + // FIXME: handle errors in parseRequest + + if (std::get<0>(ret)) + { + auto response = _onConnectionCallback(std::get<2>(ret), connectionState); + if (!Http::sendResponse(response, socket)) + { + logError("Cannot send response"); + } + } + connectionState->setTerminated(); + + _connectedClientsCount--; + } + + size_t HttpServer::getConnectedClientsCount() + { + return _connectedClientsCount; + } + + void HttpServer::setDefaultConnectionCallback() + { + setOnConnectionCallback( + [this](HttpRequestPtr request, + std::shared_ptr connectionState) -> HttpResponsePtr { + std::string uri(request->uri); + if (uri.empty() || uri == "/") + { + uri = "/index.html"; + } + + WebSocketHttpHeaders headers; + headers["Server"] = userAgent(); + + std::string path("." + uri); + auto res = readAsString(path); + bool found = res.first; + if (!found) + { + return std::make_shared( + 404, "Not Found", HttpErrorCode::Ok, WebSocketHttpHeaders(), std::string()); + } + + std::string content = res.second; + +#ifdef IXWEBSOCKET_USE_ZLIB + std::string acceptEncoding = request->headers["Accept-encoding"]; + if (acceptEncoding == "*" || acceptEncoding.find("gzip") != std::string::npos) + { + content = gzipCompress(content); + headers["Content-Encoding"] = "gzip"; + } +#endif + + // Log request + std::stringstream ss; + ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort() + << " " << request->method << " " << request->headers["User-Agent"] << " " + << request->uri << " " << content.size(); + logInfo(ss.str()); + + // FIXME: check extensions to set the content type + // headers["Content-Type"] = "application/octet-stream"; + headers["Accept-Ranges"] = "none"; + + for (auto&& it : request->headers) + { + headers[it.first] = it.second; + } + + return std::make_shared( + 200, "OK", HttpErrorCode::Ok, headers, content); + }); + } + + void HttpServer::makeRedirectServer(const std::string& redirectUrl) + { + // + // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections + // + setOnConnectionCallback( + [this, + redirectUrl](HttpRequestPtr request, + std::shared_ptr 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; + logInfo(ss.str()); + + if (request->method == "POST") + { + return std::make_shared( + 200, "OK", HttpErrorCode::Ok, headers, std::string()); + } + + headers["Location"] = redirectUrl; + + return std::make_shared( + 301, "OK", HttpErrorCode::Ok, headers, std::string()); + }); + } + + // + // Display the client parameter and body on the console + // + void HttpServer::makeDebugServer() + { + setOnConnectionCallback( + [this](HttpRequestPtr request, + std::shared_ptr 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; + logInfo(ss.str()); + + logInfo("== Headers == "); + for (auto&& it : request->headers) + { + std::ostringstream oss; + oss << it.first << ": " << it.second; + logInfo(oss.str()); + } + logInfo(""); + + logInfo("== Body == "); + logInfo(request->body); + logInfo(""); + + return std::make_shared( + 200, "OK", HttpErrorCode::Ok, headers, std::string("OK")); + }); + } + + int HttpServer::getTimeoutSecs() + { + return _timeoutSecs; + } + +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXHttpServer.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXHttpServer.h new file mode 100644 index 0000000000..7de676319b --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXHttpServer.h @@ -0,0 +1,59 @@ +/* + * IXHttpServer.h + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include "IXHttp.h" +#include "IXSocketServer.h" +#include "IXWebSocket.h" +#include +#include +#include +#include +#include +#include +#include // pair + +namespace ix +{ + class HttpServer final : public SocketServer + { + public: + using OnConnectionCallback = + std::function)>; + + HttpServer(int port = SocketServer::kDefaultPort, + const std::string& host = SocketServer::kDefaultHost, + int backlog = SocketServer::kDefaultTcpBacklog, + size_t maxConnections = SocketServer::kDefaultMaxConnections, + int addressFamily = SocketServer::kDefaultAddressFamily, + int timeoutSecs = HttpServer::kDefaultTimeoutSecs); + virtual ~HttpServer(); + virtual void stop() final; + + void setOnConnectionCallback(const OnConnectionCallback& callback); + + void makeRedirectServer(const std::string& redirectUrl); + + void makeDebugServer(); + + int getTimeoutSecs(); + private: + // Member variables + OnConnectionCallback _onConnectionCallback; + std::atomic _connectedClientsCount; + + const static int kDefaultTimeoutSecs; + int _timeoutSecs; + + // Methods + virtual void handleConnection(std::unique_ptr, + std::shared_ptr connectionState) final; + virtual size_t getConnectedClientsCount() final; + + void setDefaultConnectionCallback(); + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXNetSystem.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXNetSystem.cpp new file mode 100644 index 0000000000..ab7b2a389e --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXNetSystem.cpp @@ -0,0 +1,296 @@ +/* + * IXNetSystem.cpp + * Author: Korchynskyi Dmytro + * Copyright (c) 2019 Machine Zone. All rights reserved. + */ + +#include "IXNetSystem.h" +#include +#include + +namespace ix +{ + bool initNetSystem() + { +#ifdef _WIN32 + WORD wVersionRequested; + WSADATA wsaData; + int err; + + // Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h + wVersionRequested = MAKEWORD(2, 2); + err = WSAStartup(wVersionRequested, &wsaData); + + return err == 0; +#else + return true; +#endif + } + + bool uninitNetSystem() + { +#ifdef _WIN32 + int err = WSACleanup(); + return err == 0; +#else + return true; +#endif + } + + // + // That function could 'return WSAPoll(pfd, nfds, timeout);' + // but WSAPoll is said to have weird behaviors on the internet + // (the curl folks have had problems with it). + // + // So we make it a select wrapper + // + int poll(struct pollfd* fds, nfds_t nfds, int timeout) + { +#ifdef _WIN32 + socket_t maxfd = 0; + fd_set readfds, writefds, errorfds; + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&errorfds); + + for (nfds_t i = 0; i < nfds; ++i) + { + struct pollfd* fd = &fds[i]; + + if (fd->fd > maxfd) + { + maxfd = fd->fd; + } + if ((fd->events & POLLIN)) + { + FD_SET(fd->fd, &readfds); + } + if ((fd->events & POLLOUT)) + { + FD_SET(fd->fd, &writefds); + } + if ((fd->events & POLLERR)) + { + FD_SET(fd->fd, &errorfds); + } + } + + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + int ret = select(maxfd + 1, &readfds, &writefds, &errorfds, timeout != -1 ? &tv : NULL); + + if (ret < 0) + { + return ret; + } + + for (nfds_t i = 0; i < nfds; ++i) + { + struct pollfd* fd = &fds[i]; + fd->revents = 0; + + if (FD_ISSET(fd->fd, &readfds)) + { + fd->revents |= POLLIN; + } + if (FD_ISSET(fd->fd, &writefds)) + { + fd->revents |= POLLOUT; + } + if (FD_ISSET(fd->fd, &errorfds)) + { + fd->revents |= POLLERR; + } + } + + return ret; +#else + // + // 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 + } + + // + // mingw does not have inet_ntop, which were taken as is from the musl C library. + // + const char* inet_ntop(int af, const void* a0, char* s, socklen_t l) + { +#if defined(_WIN32) && defined(__GNUC__) + const unsigned char* a = (const unsigned char*) a0; + int i, j, max, best; + char buf[100]; + + switch (af) + { + case AF_INET: + if (snprintf(s, l, "%d.%d.%d.%d", a[0], a[1], a[2], a[3]) < l) return s; + break; + case AF_INET6: + if (memcmp(a, "\0\0\0\0\0\0\0\0\0\0\377\377", 12)) + snprintf(buf, + sizeof buf, + "%x:%x:%x:%x:%x:%x:%x:%x", + 256 * a[0] + a[1], + 256 * a[2] + a[3], + 256 * a[4] + a[5], + 256 * a[6] + a[7], + 256 * a[8] + a[9], + 256 * a[10] + a[11], + 256 * a[12] + a[13], + 256 * a[14] + a[15]); + else + snprintf(buf, + sizeof buf, + "%x:%x:%x:%x:%x:%x:%d.%d.%d.%d", + 256 * a[0] + a[1], + 256 * a[2] + a[3], + 256 * a[4] + a[5], + 256 * a[6] + a[7], + 256 * a[8] + a[9], + 256 * a[10] + a[11], + a[12], + a[13], + a[14], + a[15]); + /* Replace longest /(^0|:)[:0]{2,}/ with "::" */ + for (i = best = 0, max = 2; buf[i]; i++) + { + if (i && buf[i] != ':') continue; + j = strspn(buf + i, ":0"); + if (j > max) best = i, max = j; + } + if (max > 3) + { + buf[best] = buf[best + 1] = ':'; + memmove(buf + best + 2, buf + best + max, i - best - max + 1); + } + if (strlen(buf) < l) + { + strcpy(s, buf); + return s; + } + break; + default: errno = EAFNOSUPPORT; return 0; + } + errno = ENOSPC; + return 0; +#else + return ::inet_ntop(af, a0, s, l); +#endif + } + +#if defined(_WIN32) && defined(__GNUC__) + static int hexval(unsigned c) + { + if (c - '0' < 10) return c - '0'; + c |= 32; + if (c - 'a' < 6) return c - 'a' + 10; + return -1; + } +#endif + + // + // mingw does not have inet_pton, which were taken as is from the musl C library. + // + int inet_pton(int af, const char* s, void* a0) + { +#if defined(_WIN32) && defined(__GNUC__) + uint16_t ip[8]; + unsigned char* a = (unsigned char*) a0; + int i, j, v, d, brk = -1, need_v4 = 0; + + if (af == AF_INET) + { + for (i = 0; i < 4; i++) + { + for (v = j = 0; j < 3 && isdigit(s[j]); j++) + v = 10 * v + s[j] - '0'; + if (j == 0 || (j > 1 && s[0] == '0') || v > 255) return 0; + a[i] = v; + if (s[j] == 0 && i == 3) return 1; + if (s[j] != '.') return 0; + s += j + 1; + } + return 0; + } + else if (af != AF_INET6) + { + errno = EAFNOSUPPORT; + return -1; + } + + if (*s == ':' && *++s != ':') return 0; + + for (i = 0;; i++) + { + if (s[0] == ':' && brk < 0) + { + brk = i; + ip[i & 7] = 0; + if (!*++s) break; + if (i == 7) return 0; + continue; + } + for (v = j = 0; j < 4 && (d = hexval(s[j])) >= 0; j++) + v = 16 * v + d; + if (j == 0) return 0; + ip[i & 7] = v; + if (!s[j] && (brk >= 0 || i == 7)) break; + if (i == 7) return 0; + if (s[j] != ':') + { + if (s[j] != '.' || (i < 6 && brk < 0)) return 0; + need_v4 = 1; + i++; + break; + } + s += j + 1; + } + if (brk >= 0) + { + memmove(ip + brk + 7 - i, ip + brk, 2 * (i + 1 - brk)); + for (j = 0; j < 7 - i; j++) + ip[brk + j] = 0; + } + for (j = 0; j < 8; j++) + { + *a++ = ip[j] >> 8; + *a++ = ip[j]; + } + if (need_v4 && inet_pton(AF_INET, (const char*) s, a - 4) <= 0) return 0; + return 1; +#else + return ::inet_pton(af, s, a0); +#endif + } + + // Convert network bytes to host bytes. Copied from the ASIO library + unsigned short network_to_host_short(unsigned short value) + { + #if defined(_WIN32) + unsigned char* value_p = reinterpret_cast(&value); + unsigned short result = (static_cast(value_p[0]) << 8) + | static_cast(value_p[1]); + return result; + #else // defined(_WIN32) + return ntohs(value); + #endif // defined(_WIN32) + } + +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXNetSystem.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXNetSystem.h new file mode 100644 index 0000000000..b2220a957d --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXNetSystem.h @@ -0,0 +1,86 @@ +/* + * IXNetSystem.h + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone. All rights reserved. + */ + +#pragma once + +#ifdef _WIN32 + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include +#include +#include +#include +#include + +#undef EWOULDBLOCK +#undef EAGAIN +#undef EINPROGRESS +#undef EBADF +#undef EINVAL + +// map to WSA error codes +#define EWOULDBLOCK WSAEWOULDBLOCK +#define EAGAIN WSATRY_AGAIN +#define EINPROGRESS WSAEINPROGRESS +#define EBADF WSAEBADF +#define EINVAL WSAEINVAL + +// Define our own poll on Windows, as a wrapper on top of select +typedef unsigned long int nfds_t; + +// pollfd is not defined by some versions of mingw64 since _WIN32_WINNT is too low +#if _WIN32_WINNT < 0x0600 +struct pollfd +{ + int fd; /* file descriptor */ + short events; /* requested events */ + short revents; /* returned events */ +}; + +#define POLLIN 0x001 /* There is data to read. */ +#define POLLOUT 0x004 /* Writing now will not block. */ +#define POLLERR 0x008 /* Error condition. */ +#define POLLHUP 0x010 /* Hung up. */ +#define POLLNVAL 0x020 /* Invalid polling request. */ +#endif + +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +namespace ix +{ +#ifdef _WIN32 + typedef SOCKET socket_t; +#else + typedef int socket_t; +#endif + + bool initNetSystem(); + bool uninitNetSystem(); + + int poll(struct pollfd* fds, nfds_t nfds, int timeout); + + const char* inet_ntop(int af, const void* src, char* dst, socklen_t size); + int inet_pton(int af, const char* src, void* dst); + + unsigned short network_to_host_short(unsigned short value); +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXProgressCallback.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXProgressCallback.h new file mode 100644 index 0000000000..879f6a89d3 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXProgressCallback.h @@ -0,0 +1,14 @@ +/* + * IXProgressCallback.h + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include + +namespace ix +{ + using OnProgressCallback = std::function; +} diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterrupt.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterrupt.cpp new file mode 100644 index 0000000000..36dc66c947 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterrupt.cpp @@ -0,0 +1,48 @@ +/* + * IXSelectInterrupt.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#include "IXSelectInterrupt.h" + +namespace ix +{ + const uint64_t SelectInterrupt::kSendRequest = 1; + const uint64_t SelectInterrupt::kCloseRequest = 2; + + SelectInterrupt::SelectInterrupt() + { + ; + } + + SelectInterrupt::~SelectInterrupt() + { + ; + } + + bool SelectInterrupt::init(std::string& /*errorMsg*/) + { + return true; + } + + bool SelectInterrupt::notify(uint64_t /*value*/) + { + return true; + } + + uint64_t SelectInterrupt::read() + { + return 0; + } + + bool SelectInterrupt::clear() + { + return true; + } + + int SelectInterrupt::getFd() const + { + return -1; + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterrupt.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterrupt.h new file mode 100644 index 0000000000..c3bb7f3f99 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterrupt.h @@ -0,0 +1,34 @@ +/* + * IXSelectInterrupt.h + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include +#include +#include + +namespace ix +{ + class SelectInterrupt + { + public: + SelectInterrupt(); + virtual ~SelectInterrupt(); + + virtual bool init(std::string& errorMsg); + + virtual bool notify(uint64_t value); + virtual bool clear(); + virtual uint64_t read(); + virtual int getFd() const; + + // Used as special codes for pipe communication + static const uint64_t kSendRequest; + static const uint64_t kCloseRequest; + }; + + using SelectInterruptPtr = std::unique_ptr; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterruptFactory.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterruptFactory.cpp new file mode 100644 index 0000000000..9018810daa --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterruptFactory.cpp @@ -0,0 +1,26 @@ +/* + * IXSelectInterruptFactory.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#include "IXSelectInterruptFactory.h" + +#include "IXUniquePtr.h" +#if defined(__linux__) || defined(__APPLE__) +#include "IXSelectInterruptPipe.h" +#else +#include "IXSelectInterrupt.h" +#endif + +namespace ix +{ + SelectInterruptPtr createSelectInterrupt() + { +#if defined(__linux__) || defined(__APPLE__) + return ix::make_unique(); +#else + return ix::make_unique(); +#endif + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterruptFactory.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterruptFactory.h new file mode 100644 index 0000000000..5faf1d6eca --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterruptFactory.h @@ -0,0 +1,16 @@ +/* + * IXSelectInterruptFactory.h + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include + +namespace ix +{ + class SelectInterrupt; + using SelectInterruptPtr = std::unique_ptr; + SelectInterruptPtr createSelectInterrupt(); +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterruptPipe.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterruptPipe.cpp new file mode 100644 index 0000000000..75c42f27f1 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterruptPipe.cpp @@ -0,0 +1,161 @@ +/* + * IXSelectInterruptPipe.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved. + */ + +// +// On UNIX we use pipes to wake up select. There is no way to do that +// on Windows so this file is compiled out on Windows. +// +#ifndef _WIN32 + +#include "IXSelectInterruptPipe.h" + +#include +#include +#include +#include +#include // for strerror +#include // for write + +namespace ix +{ + // File descriptor at index 0 in _fildes is the read end of the pipe + // File descriptor at index 1 in _fildes is the write end of the pipe + const int SelectInterruptPipe::kPipeReadIndex = 0; + const int SelectInterruptPipe::kPipeWriteIndex = 1; + + SelectInterruptPipe::SelectInterruptPipe() + { + _fildes[kPipeReadIndex] = -1; + _fildes[kPipeWriteIndex] = -1; + } + + SelectInterruptPipe::~SelectInterruptPipe() + { + ::close(_fildes[kPipeReadIndex]); + ::close(_fildes[kPipeWriteIndex]); + _fildes[kPipeReadIndex] = -1; + _fildes[kPipeWriteIndex] = -1; + } + + bool SelectInterruptPipe::init(std::string& errorMsg) + { + std::lock_guard lock(_fildesMutex); + + // calling init twice is a programming error + assert(_fildes[kPipeReadIndex] == -1); + assert(_fildes[kPipeWriteIndex] == -1); + + if (pipe(_fildes) < 0) + { + std::stringstream ss; + ss << "SelectInterruptPipe::init() failed in pipe() call" + << " : " << strerror(errno); + errorMsg = ss.str(); + return false; + } + + if (fcntl(_fildes[kPipeReadIndex], F_SETFL, O_NONBLOCK) == -1) + { + std::stringstream ss; + ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) call" + << " : " << strerror(errno); + errorMsg = ss.str(); + + _fildes[kPipeReadIndex] = -1; + _fildes[kPipeWriteIndex] = -1; + return false; + } + + if (fcntl(_fildes[kPipeWriteIndex], F_SETFL, O_NONBLOCK) == -1) + { + std::stringstream ss; + ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) call" + << " : " << strerror(errno); + errorMsg = ss.str(); + + _fildes[kPipeReadIndex] = -1; + _fildes[kPipeWriteIndex] = -1; + return false; + } + +#ifdef F_SETNOSIGPIPE + if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1) + { + std::stringstream ss; + ss << "SelectInterruptPipe::init() failed in fcntl(.... F_SETNOSIGPIPE) call" + << " : " << strerror(errno); + errorMsg = ss.str(); + + _fildes[kPipeReadIndex] = -1; + _fildes[kPipeWriteIndex] = -1; + return false; + } + + if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1) + { + std::stringstream ss; + ss << "SelectInterruptPipe::init() failed in fcntl(..., F_SETNOSIGPIPE) call" + << " : " << strerror(errno); + errorMsg = ss.str(); + + _fildes[kPipeReadIndex] = -1; + _fildes[kPipeWriteIndex] = -1; + return false; + } +#endif + + return true; + } + + bool SelectInterruptPipe::notify(uint64_t value) + { + std::lock_guard lock(_fildesMutex); + + int fd = _fildes[kPipeWriteIndex]; + 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 + return ret == 8; + } + + // TODO: return max uint64_t for errors ? + uint64_t SelectInterruptPipe::read() + { + std::lock_guard lock(_fildesMutex); + + int fd = _fildes[kPipeReadIndex]; + + uint64_t value = 0; + + ssize_t ret = -1; + do + { + ret = ::read(fd, &value, sizeof(value)); + } while (ret == -1 && errno == EINTR); + + return value; + } + + bool SelectInterruptPipe::clear() + { + return true; + } + + int SelectInterruptPipe::getFd() const + { + std::lock_guard lock(_fildesMutex); + + return _fildes[kPipeReadIndex]; + } +} // namespace ix + +#endif // !_WIN32 diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterruptPipe.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterruptPipe.h new file mode 100644 index 0000000000..7668915177 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSelectInterruptPipe.h @@ -0,0 +1,40 @@ +/* + * IXSelectInterruptPipe.h + * Author: Benjamin Sergeant + * Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include "IXSelectInterrupt.h" +#include +#include +#include + +namespace ix +{ + class SelectInterruptPipe final : public SelectInterrupt + { + public: + SelectInterruptPipe(); + virtual ~SelectInterruptPipe(); + + bool init(std::string& errorMsg) final; + + bool notify(uint64_t value) final; + bool clear() final; + uint64_t read() final; + int getFd() const final; + + private: + // Store file descriptors used by the communication pipe. Communication + // happens between a control thread and a background thread, which is + // blocked on select. + int _fildes[2]; + mutable std::mutex _fildesMutex; + + // Used to identify the read/write idx + static const int kPipeReadIndex; + static const int kPipeWriteIndex; + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSetThreadName.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSetThreadName.cpp new file mode 100644 index 0000000000..40faf9d97f --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSetThreadName.cpp @@ -0,0 +1,83 @@ +/* + * IXSetThreadName.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2018 2020 Machine Zone, Inc. All rights reserved. + */ +#include "IXSetThreadName.h" + +// unix systems +#if defined(__APPLE__) || defined(__linux__) || defined(BSD) +#include +#endif + +// freebsd needs this header as well +#if defined(BSD) +#include +#endif + +// Windows +#ifdef _WIN32 +#include +#endif + +namespace ix +{ +#ifdef _WIN32 + const DWORD MS_VC_EXCEPTION = 0x406D1388; + +#pragma pack(push, 8) + typedef struct tagTHREADNAME_INFO + { + DWORD dwType; // Must be 0x1000. + LPCSTR szName; // Pointer to name (in user addr space). + DWORD dwThreadID; // Thread ID (-1=caller thread). + DWORD dwFlags; // Reserved for future use, must be zero. + } THREADNAME_INFO; +#pragma pack(pop) + + void SetThreadName(DWORD dwThreadID, const char* threadName) + { +#ifndef __GNUC__ + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = threadName; + info.dwThreadID = dwThreadID; + info.dwFlags = 0; + + __try + { + RaiseException( + MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*) &info); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + } +#endif + } +#endif + + void setThreadName(const std::string& name) + { +#if defined(__APPLE__) + // + // Apple reserves 16 bytes for its thread names + // Notice that the Apple version of pthread_setname_np + // does not take a pthread_t argument + // + pthread_setname_np(name.substr(0, 63).c_str()); +#elif defined(__linux__) + // + // Linux only reserves 16 bytes for its thread names + // See prctl and PR_SET_NAME property in + // http://man7.org/linux/man-pages/man2/prctl.2.html + // + pthread_setname_np(pthread_self(), name.substr(0, 15).c_str()); +#elif defined(_WIN32) + SetThreadName(-1, name.c_str()); +#elif defined(BSD) + pthread_set_name_np(pthread_self(), name.substr(0, 15).c_str()); +#else + // ... assert here ? +#endif + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSetThreadName.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSetThreadName.h new file mode 100644 index 0000000000..de57c4ae04 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSetThreadName.h @@ -0,0 +1,12 @@ +/* + * IXSetThreadName.h + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ +#pragma once +#include + +namespace ix +{ + void setThreadName(const std::string& name); +} diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocket.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocket.cpp new file mode 100644 index 0000000000..bccfe7d912 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocket.cpp @@ -0,0 +1,405 @@ +/* + * IXSocket.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved. + */ + +#include "IXSocket.h" + +#include "IXNetSystem.h" +#include "IXSelectInterrupt.h" +#include "IXSelectInterruptFactory.h" +#include "IXSocketConnect.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef min +#undef min +#endif + +namespace ix +{ + const int Socket::kDefaultPollNoTimeout = -1; // No poll timeout by default + const int Socket::kDefaultPollTimeout = kDefaultPollNoTimeout; + + Socket::Socket(int fd) + : _sockfd(fd) + , _selectInterrupt(createSelectInterrupt()) + { + ; + } + + Socket::~Socket() + { + close(); + } + + PollResultType Socket::poll(bool readyToRead, + int timeoutMs, + int sockfd, + const SelectInterruptPtr& selectInterrupt) + { + // + // We used to use ::select to poll but on Android 9 we get large fds out of + // ::connect which crash in FD_SET as they are larger than FD_SETSIZE. Switching + // to ::poll does fix that. + // + // However poll isn't as portable as select and has bugs on Windows, so we + // have a shim to fallback to select on those platforms. See + // https://github.com/mpv-player/mpv/pull/5203/files for such a select wrapper. + // + nfds_t nfds = 1; + struct pollfd fds[2]; + memset(fds, 0, sizeof(fds)); + + fds[0].fd = sockfd; + fds[0].events = (readyToRead) ? POLLIN : POLLOUT; + + // this is ignored by poll, but our select based poll wrapper on Windows needs it + fds[0].events |= POLLERR; + + // File descriptor used to interrupt select when needed + int interruptFd = -1; + if (selectInterrupt) + { + interruptFd = selectInterrupt->getFd(); + + if (interruptFd != -1) + { + nfds = 2; + fds[1].fd = interruptFd; + fds[1].events = POLLIN; + } + } + + int ret = ix::poll(fds, nfds, timeoutMs); + + PollResultType pollResult = PollResultType::ReadyForRead; + if (ret < 0) + { + pollResult = PollResultType::Error; + } + else if (ret == 0) + { + pollResult = PollResultType::Timeout; + } + else if (interruptFd != -1 && fds[1].revents & POLLIN) + { + uint64_t value = selectInterrupt->read(); + + if (value == SelectInterrupt::kSendRequest) + { + pollResult = PollResultType::SendRequest; + } + else if (value == SelectInterrupt::kCloseRequest) + { + pollResult = PollResultType::CloseRequest; + } + } + else if (sockfd != -1 && readyToRead && fds[0].revents & POLLIN) + { + pollResult = PollResultType::ReadyForRead; + } + else if (sockfd != -1 && !readyToRead && fds[0].revents & POLLOUT) + { + pollResult = PollResultType::ReadyForWrite; + +#ifdef _WIN32 + // On connect error, in async mode, windows will write to the exceptions fds + if (fds[0].revents & POLLERR) + { + pollResult = PollResultType::Error; + } +#else + int optval = -1; + socklen_t optlen = sizeof(optval); + + // getsockopt() puts the errno value for connect into optval so 0 + // means no-error. + if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1 || optval != 0) + { + pollResult = PollResultType::Error; + + // set errno to optval so that external callers can have an + // appropriate error description when calling strerror + errno = optval; + } +#endif + } + else if (sockfd != -1 && (fds[0].revents & POLLERR || fds[0].revents & POLLHUP || + fds[0].revents & POLLNVAL)) + { + pollResult = PollResultType::Error; + } + + return pollResult; + } + + PollResultType Socket::isReadyToRead(int timeoutMs) + { + if (_sockfd == -1) + { + return PollResultType::Error; + } + + bool readyToRead = true; + return poll(readyToRead, timeoutMs, _sockfd, _selectInterrupt); + } + + PollResultType Socket::isReadyToWrite(int timeoutMs) + { + if (_sockfd == -1) + { + return PollResultType::Error; + } + + bool readyToRead = false; + return poll(readyToRead, timeoutMs, _sockfd, _selectInterrupt); + } + + // Wake up from poll/select by writing to the pipe which is watched by select + bool Socket::wakeUpFromPoll(uint64_t wakeUpCode) + { + return _selectInterrupt->notify(wakeUpCode); + } + + bool Socket::accept(std::string& errMsg) + { + if (_sockfd == -1) + { + errMsg = "Socket is uninitialized"; + return false; + } + return true; + } + + bool Socket::connect(const std::string& host, + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested) + { + std::lock_guard lock(_socketMutex); + + if (!_selectInterrupt->clear()) return false; + + _sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested); + return _sockfd != -1; + } + + void Socket::close() + { + std::lock_guard lock(_socketMutex); + + if (_sockfd == -1) return; + + closeSocket(_sockfd); + _sockfd = -1; + } + + ssize_t Socket::send(char* buffer, size_t length) + { + int flags = 0; +#ifdef MSG_NOSIGNAL + flags = MSG_NOSIGNAL; +#endif + + return ::send(_sockfd, buffer, length, flags); + } + + ssize_t Socket::send(const std::string& buffer) + { + return send((char*) &buffer[0], buffer.size()); + } + + ssize_t Socket::recv(void* buffer, size_t length) + { + int flags = 0; +#ifdef MSG_NOSIGNAL + flags = MSG_NOSIGNAL; +#endif + + return ::recv(_sockfd, (char*) buffer, length, flags); + } + + int Socket::getErrno() + { + int err; + +#ifdef _WIN32 + err = WSAGetLastError(); +#else + err = errno; +#endif + + return err; + } + + bool Socket::isWaitNeeded() + { + int err = getErrno(); + + if (err == EWOULDBLOCK || err == EAGAIN || err == EINPROGRESS) + { + return true; + } + + return false; + } + + void Socket::closeSocket(int fd) + { +#ifdef _WIN32 + closesocket(fd); +#else + ::close(fd); +#endif + } + + bool Socket::init(std::string& errorMsg) + { + return _selectInterrupt->init(errorMsg); + } + + bool Socket::writeBytes(const std::string& str, + const CancellationRequest& isCancellationRequested) + { + int offset = 0; + int len = (int) str.size(); + + while (true) + { + if (isCancellationRequested && isCancellationRequested()) return false; + + ssize_t ret = send((char*) &str[offset], len); + + // We wrote some bytes, as needed, all good. + if (ret > 0) + { + if (ret == len) + { + return true; + } + else + { + offset += ret; + len -= ret; + continue; + } + } + // There is possibly something to be writen, try again + else if (ret < 0 && Socket::isWaitNeeded()) + { + continue; + } + // There was an error during the write, abort + else + { + return false; + } + } + } + + bool Socket::readByte(void* buffer, const CancellationRequest& isCancellationRequested) + { + while (true) + { + if (isCancellationRequested && isCancellationRequested()) return false; + + ssize_t ret; + ret = recv(buffer, 1); + + // We read one byte, as needed, all good. + if (ret == 1) + { + return true; + } + // There is possibly something to be read, try again + else if (ret < 0 && Socket::isWaitNeeded()) + { + // Wait with a 1ms timeout until the socket is ready to read. + // This way we are not busy looping + if (isReadyToRead(1) == PollResultType::Error) + { + return false; + } + } + // There was an error during the read, abort + else + { + return false; + } + } + } + + std::pair Socket::readLine( + const CancellationRequest& isCancellationRequested) + { + char c; + std::string line; + line.reserve(64); + + for (int i = 0; i < 2 || (line[i - 2] != '\r' && line[i - 1] != '\n'); ++i) + { + if (!readByte(&c, isCancellationRequested)) + { + // Return what we were able to read + return std::make_pair(false, line); + } + + line += c; + } + + return std::make_pair(true, line); + } + + std::pair Socket::readBytes( + size_t length, + const OnProgressCallback& onProgressCallback, + const CancellationRequest& isCancellationRequested) + { + std::array readBuffer; + + std::vector output; + while (output.size() != length) + { + if (isCancellationRequested && isCancellationRequested()) + { + const std::string errorMsg("Cancellation Requested"); + return std::make_pair(false, errorMsg); + } + + size_t size = std::min(readBuffer.size(), length - output.size()); + ssize_t ret = recv((char*) &readBuffer[0], size); + + if (ret > 0) + { + output.insert(output.end(), readBuffer.begin(), readBuffer.begin() + ret); + } + else if (ret <= 0 && !Socket::isWaitNeeded()) + { + const std::string errorMsg("Recv Error"); + return std::make_pair(false, errorMsg); + } + + if (onProgressCallback) onProgressCallback((int) output.size(), (int) length); + + // Wait with a 1ms timeout until the socket is ready to read. + // This way we are not busy looping + if (isReadyToRead(1) == PollResultType::Error) + { + const std::string errorMsg("Poll Error"); + return std::make_pair(false, errorMsg); + } + } + + return std::make_pair(true, std::string(output.begin(), output.end())); + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocket.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocket.h new file mode 100644 index 0000000000..5604c40d6d --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocket.h @@ -0,0 +1,92 @@ +/* + * IXSocket.h + * Author: Benjamin Sergeant + * Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +typedef SSIZE_T ssize_t; +#endif + +#include "IXCancellationRequest.h" +#include "IXProgressCallback.h" +#include "IXSelectInterrupt.h" + +namespace ix +{ + enum class PollResultType + { + ReadyForRead = 0, + ReadyForWrite = 1, + Timeout = 2, + Error = 3, + SendRequest = 4, + CloseRequest = 5 + }; + + class Socket + { + public: + Socket(int fd = -1); + virtual ~Socket(); + bool init(std::string& errorMsg); + + // Functions to check whether there is activity on the socket + PollResultType poll(int timeoutMs = kDefaultPollTimeout); + bool wakeUpFromPoll(uint64_t wakeUpCode); + + PollResultType isReadyToWrite(int timeoutMs); + PollResultType isReadyToRead(int timeoutMs); + + // Virtual methods + virtual bool accept(std::string& errMsg); + + virtual bool connect(const std::string& host, + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested); + virtual void close(); + + virtual ssize_t send(char* buffer, size_t length); + ssize_t send(const std::string& buffer); + virtual ssize_t recv(void* buffer, size_t length); + + // Blocking and cancellable versions, working with socket that can be set + // to non blocking mode. Used during HTTP upgrade. + bool readByte(void* buffer, const CancellationRequest& isCancellationRequested); + bool writeBytes(const std::string& str, const CancellationRequest& isCancellationRequested); + + std::pair readLine(const CancellationRequest& isCancellationRequested); + std::pair readBytes(size_t length, + const OnProgressCallback& onProgressCallback, + const CancellationRequest& isCancellationRequested); + + static int getErrno(); + static bool isWaitNeeded(); + static void closeSocket(int fd); + + static PollResultType poll(bool readyToRead, + int timeoutMs, + int sockfd, + const SelectInterruptPtr& selectInterrupt); + + protected: + std::atomic _sockfd; + std::mutex _socketMutex; + + private: + static const int kDefaultPollTimeout; + static const int kDefaultPollNoTimeout; + + SelectInterruptPtr _selectInterrupt; + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketAppleSSL.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketAppleSSL.cpp new file mode 100644 index 0000000000..f58e0c8d89 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketAppleSSL.cpp @@ -0,0 +1,313 @@ +/* + * IXSocketAppleSSL.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved. + * + * Adapted from Satori SDK Apple SSL code. + */ +#ifdef IXWEBSOCKET_USE_SECURE_TRANSPORT + +#include "IXSocketAppleSSL.h" + +#include "IXSocketConnect.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define socketerrno errno + +#include + +namespace ix +{ + SocketAppleSSL::SocketAppleSSL(const SocketTLSOptions& tlsOptions, int fd) + : Socket(fd) + , _sslContext(nullptr) + , _tlsOptions(tlsOptions) + { + ; + } + + SocketAppleSSL::~SocketAppleSSL() + { + SocketAppleSSL::close(); + } + + std::string SocketAppleSSL::getSSLErrorDescription(OSStatus status) + { + std::string errMsg("Unknown SSL error."); + + CFErrorRef error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainOSStatus, status, NULL); + if (error) + { + CFStringRef message = CFErrorCopyDescription(error); + if (message) + { + char localBuffer[128]; + Boolean success; + success = CFStringGetCString(message, localBuffer, 128, kCFStringEncodingUTF8); + if (success) + { + errMsg = localBuffer; + } + CFRelease(message); + } + CFRelease(error); + } + + return errMsg; + } + + OSStatus SocketAppleSSL::readFromSocket(SSLConnectionRef connection, void* data, size_t* len) + { + int fd = (int) (long) connection; + if (fd < 0) return errSSLInternal; + + assert(data != nullptr); + assert(len != nullptr); + + size_t requested_sz = *len; + + ssize_t status = read(fd, data, requested_sz); + + if (status > 0) + { + *len = (size_t) status; + if (requested_sz > *len) + { + return errSSLWouldBlock; + } + else + { + return noErr; + } + } + else if (status == 0) + { + *len = 0; + return errSSLClosedGraceful; + } + else + { + *len = 0; + switch (errno) + { + case ENOENT: return errSSLClosedGraceful; + + case EAGAIN: return errSSLWouldBlock; // EWOULDBLOCK is a define for EAGAIN on osx + case EINPROGRESS: return errSSLWouldBlock; + + case ECONNRESET: return errSSLClosedAbort; + + default: return errSecIO; + } + } + } + + OSStatus SocketAppleSSL::writeToSocket(SSLConnectionRef connection, + const void* data, + size_t* len) + { + int fd = (int) (long) connection; + if (fd < 0) return errSSLInternal; + + assert(data != nullptr); + assert(len != nullptr); + + size_t to_write_sz = *len; + ssize_t status = write(fd, data, to_write_sz); + + if (status > 0) + { + *len = (size_t) status; + if (to_write_sz > *len) + { + return errSSLWouldBlock; + } + else + { + return noErr; + } + } + else if (status == 0) + { + *len = 0; + return errSSLClosedGraceful; + } + else + { + *len = 0; + switch (errno) + { + case ENOENT: return errSSLClosedGraceful; + + case EAGAIN: return errSSLWouldBlock; // EWOULDBLOCK is a define for EAGAIN on osx + case EINPROGRESS: return errSSLWouldBlock; + + case ECONNRESET: return errSSLClosedAbort; + + default: return errSecIO; + } + } + } + + + bool SocketAppleSSL::accept(std::string& errMsg) + { + errMsg = "TLS not supported yet in server mode with apple ssl backend"; + return false; + } + + OSStatus SocketAppleSSL::tlsHandShake(std::string& errMsg, + const CancellationRequest& isCancellationRequested) + { + OSStatus status; + + do + { + status = SSLHandshake(_sslContext); + + // Interrupt the handshake + if (isCancellationRequested()) + { + errMsg = "Cancellation requested"; + return errSSLInternal; + } + } while (status == errSSLWouldBlock || status == errSSLServerAuthCompleted); + + return status; + } + + // No wait support + bool SocketAppleSSL::connect(const std::string& host, + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested) + { + OSStatus status; + { + std::lock_guard lock(_mutex); + + _sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested); + if (_sockfd == -1) return false; + + _sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType); + + SSLSetIOFuncs( + _sslContext, SocketAppleSSL::readFromSocket, SocketAppleSSL::writeToSocket); + SSLSetConnection(_sslContext, (SSLConnectionRef)(long) _sockfd); + SSLSetProtocolVersionMin(_sslContext, kTLSProtocol12); + SSLSetPeerDomainName(_sslContext, host.c_str(), host.size()); + + if (_tlsOptions.isPeerVerifyDisabled()) + { + Boolean option(1); + SSLSetSessionOption(_sslContext, kSSLSessionOptionBreakOnServerAuth, option); + + status = tlsHandShake(errMsg, isCancellationRequested); + + if (status == errSSLServerAuthCompleted) + { + // proceed with the handshake + status = tlsHandShake(errMsg, isCancellationRequested); + } + } + else + { + status = tlsHandShake(errMsg, isCancellationRequested); + } + } + + if (status != noErr) + { + errMsg = getSSLErrorDescription(status); + close(); + return false; + } + + return true; + } + + void SocketAppleSSL::close() + { + std::lock_guard lock(_mutex); + + if (_sslContext == nullptr) return; + + SSLClose(_sslContext); + CFRelease(_sslContext); + _sslContext = nullptr; + + Socket::close(); + } + + ssize_t SocketAppleSSL::send(char* buf, size_t nbyte) + { + OSStatus status = errSSLWouldBlock; + while (status == errSSLWouldBlock) + { + size_t processed = 0; + std::lock_guard lock(_mutex); + status = SSLWrite(_sslContext, buf, nbyte, &processed); + + if (processed > 0) return (ssize_t) processed; + + // The connection was reset, inform the caller that this + // Socket should close + if (status == errSSLClosedGraceful || status == errSSLClosedNoNotify || + status == errSSLClosedAbort) + { + errno = ECONNRESET; + return -1; + } + + if (status == errSSLWouldBlock) + { + errno = EWOULDBLOCK; + return -1; + } + } + return -1; + } + + // No wait support + ssize_t SocketAppleSSL::recv(void* buf, size_t nbyte) + { + OSStatus status = errSSLWouldBlock; + while (status == errSSLWouldBlock) + { + size_t processed = 0; + std::lock_guard lock(_mutex); + status = SSLRead(_sslContext, buf, nbyte, &processed); + + if (processed > 0) return (ssize_t) processed; + + // The connection was reset, inform the caller that this + // Socket should close + if (status == errSSLClosedGraceful || status == errSSLClosedNoNotify || + status == errSSLClosedAbort) + { + errno = ECONNRESET; + return -1; + } + + if (status == errSSLWouldBlock) + { + errno = EWOULDBLOCK; + return -1; + } + } + return -1; + } + +} // namespace ix + +#endif // IXWEBSOCKET_USE_SECURE_TRANSPORT diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketAppleSSL.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketAppleSSL.h new file mode 100644 index 0000000000..a693a18784 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketAppleSSL.h @@ -0,0 +1,52 @@ +/* + * IXSocketAppleSSL.h + * Author: Benjamin Sergeant + * Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved. + */ +#ifdef IXWEBSOCKET_USE_SECURE_TRANSPORT + +#pragma once + +#include "IXCancellationRequest.h" +#include "IXSocket.h" +#include "IXSocketTLSOptions.h" +#include +#include +#include + +namespace ix +{ + class SocketAppleSSL final : public Socket + { + public: + SocketAppleSSL(const SocketTLSOptions& tlsOptions, int fd = -1); + ~SocketAppleSSL(); + + virtual bool accept(std::string& errMsg) final; + + virtual bool connect(const std::string& host, + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested) final; + virtual void close() final; + + virtual ssize_t send(char* buffer, size_t length) final; + virtual ssize_t recv(void* buffer, size_t length) final; + + private: + static std::string getSSLErrorDescription(OSStatus status); + static OSStatus writeToSocket(SSLConnectionRef connection, const void* data, size_t* len); + static OSStatus readFromSocket(SSLConnectionRef connection, void* data, size_t* len); + + OSStatus tlsHandShake(std::string& errMsg, + const CancellationRequest& isCancellationRequested); + + SSLContextRef _sslContext; + mutable std::mutex _mutex; // AppleSSL routines are not thread-safe + + SocketTLSOptions _tlsOptions; + }; + +} // namespace ix + +#endif // IXWEBSOCKET_USE_SECURE_TRANSPORT diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketConnect.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketConnect.cpp new file mode 100644 index 0000000000..94ebc4061c --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketConnect.cpp @@ -0,0 +1,155 @@ +/* + * IXSocketConnect.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ + +#include "IXSocketConnect.h" + +#include "IXDNSLookup.h" +#include "IXNetSystem.h" +#include "IXSelectInterrupt.h" +#include "IXSocket.h" +#include "IXUniquePtr.h" +#include +#include +#include + +// Android needs extra headers for TCP_NODELAY and IPPROTO_TCP +#ifdef ANDROID +#include +#include +#endif + +namespace ix +{ + // + // This function can be cancelled every 50 ms + // This is important so that we don't block the main UI thread when shutting down a + // connection which is already trying to reconnect, and can be blocked waiting for + // ::connect to respond. + // + int SocketConnect::connectToAddress(const struct addrinfo* address, + std::string& errMsg, + const CancellationRequest& isCancellationRequested) + { + errMsg = "no error"; + + socket_t fd = socket(address->ai_family, address->ai_socktype, address->ai_protocol); + if (fd < 0) + { + errMsg = "Cannot create a socket"; + return -1; + } + + // Set the socket to non blocking mode, so that slow responses cannot + // block us for too long + SocketConnect::configure(fd); + + int res = ::connect(fd, address->ai_addr, address->ai_addrlen); + + if (res == -1 && !Socket::isWaitNeeded()) + { + errMsg = strerror(Socket::getErrno()); + Socket::closeSocket(fd); + return -1; + } + + for (;;) + { + if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well + { + Socket::closeSocket(fd); + errMsg = "Cancelled"; + return -1; + } + + int timeoutMs = 10; + bool readyToRead = false; + auto selectInterrupt = ix::make_unique(); + PollResultType pollResult = Socket::poll(readyToRead, timeoutMs, fd, selectInterrupt); + + if (pollResult == PollResultType::Timeout) + { + continue; + } + else if (pollResult == PollResultType::Error) + { + Socket::closeSocket(fd); + errMsg = std::string("Connect error: ") + strerror(Socket::getErrno()); + return -1; + } + else if (pollResult == PollResultType::ReadyForWrite) + { + return fd; + } + else + { + Socket::closeSocket(fd); + errMsg = std::string("Connect error: ") + strerror(Socket::getErrno()); + return -1; + } + } + + Socket::closeSocket(fd); + errMsg = "connect timed out after 60 seconds"; + return -1; + } + + int SocketConnect::connect(const std::string& hostname, + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested) + { + // + // First do DNS resolution + // + auto dnsLookup = std::make_shared(hostname, port); + struct addrinfo* res = dnsLookup->resolve(errMsg, isCancellationRequested); + if (res == nullptr) + { + return -1; + } + + int sockfd = -1; + + // iterate through the records to find a working peer + struct addrinfo* address; + for (address = res; address != nullptr; address = address->ai_next) + { + // + // Second try to connect to the remote host + // + sockfd = connectToAddress(address, errMsg, isCancellationRequested); + if (sockfd != -1) + { + break; + } + } + + freeaddrinfo(res); + return sockfd; + } + + // FIXME: configure is a terrible name + void SocketConnect::configure(int sockfd) + { + // 1. disable Nagle's algorithm + int flag = 1; + setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char*) &flag, sizeof(flag)); + + // 2. make socket non blocking +#ifdef _WIN32 + unsigned long nonblocking = 1; + ioctlsocket(sockfd, FIONBIO, &nonblocking); +#else + fcntl(sockfd, F_SETFL, O_NONBLOCK); // make socket non blocking +#endif + + // 3. (apple) prevent SIGPIPE from being emitted when the remote end disconnect +#ifdef SO_NOSIGPIPE + int value = 1; + setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void*) &value, sizeof(value)); +#endif + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketConnect.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketConnect.h new file mode 100644 index 0000000000..84a0858ee2 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketConnect.h @@ -0,0 +1,31 @@ +/* + * IXSocketConnect.h + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include "IXCancellationRequest.h" +#include + +struct addrinfo; + +namespace ix +{ + class SocketConnect + { + public: + static int connect(const std::string& hostname, + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested); + + static void configure(int sockfd); + + private: + static int connectToAddress(const struct addrinfo* address, + std::string& errMsg, + const CancellationRequest& isCancellationRequested); + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketFactory.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketFactory.cpp new file mode 100644 index 0000000000..0273d68349 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketFactory.cpp @@ -0,0 +1,64 @@ +/* + * IXSocketFactory.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#include "IXSocketFactory.h" + +#include "IXUniquePtr.h" +#ifdef IXWEBSOCKET_USE_TLS + +#ifdef IXWEBSOCKET_USE_MBED_TLS +#include "IXSocketMbedTLS.h" +#elif defined(IXWEBSOCKET_USE_OPEN_SSL) +#include "IXSocketOpenSSL.h" +#elif __APPLE__ +#include "IXSocketAppleSSL.h" +#endif + +#else + +#include "IXSocket.h" + +#endif + +namespace ix +{ + std::unique_ptr createSocket(bool tls, + int fd, + std::string& errorMsg, + const SocketTLSOptions& tlsOptions) + { + (void) tlsOptions; + errorMsg.clear(); + std::unique_ptr socket; + + if (!tls) + { + socket = ix::make_unique(fd); + } + else + { +#ifdef IXWEBSOCKET_USE_TLS +#if defined(IXWEBSOCKET_USE_MBED_TLS) + socket = ix::make_unique(tlsOptions, fd); +#elif defined(IXWEBSOCKET_USE_OPEN_SSL) + socket = ix::make_unique(tlsOptions, fd); +#elif defined(__APPLE__) + socket = ix::make_unique(tlsOptions, fd); +#endif +#else + errorMsg = "TLS support is not enabled on this platform."; + return nullptr; +#endif + } + + if (!socket->init(errorMsg)) + { + socket.reset(); + } + + return socket; + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketFactory.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketFactory.h new file mode 100644 index 0000000000..de1eeda665 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketFactory.h @@ -0,0 +1,21 @@ + +/* + * IXSocketFactory.h + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include "IXSocketTLSOptions.h" +#include +#include + +namespace ix +{ + class Socket; + std::unique_ptr createSocket(bool tls, + int fd, + std::string& errorMsg, + const SocketTLSOptions& tlsOptions); +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketMbedTLS.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketMbedTLS.cpp new file mode 100644 index 0000000000..01f8c870ef --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketMbedTLS.cpp @@ -0,0 +1,361 @@ +/* + * IXSocketMbedTLS.cpp + * Author: Benjamin Sergeant, Max Weisel + * Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved. + * + * Some code taken from + * https://github.com/rottor12/WsClientLib/blob/master/lib/src/WsClientLib.cpp + * and mini_client.c example from mbedtls + */ +#ifdef IXWEBSOCKET_USE_MBED_TLS + +#include "IXSocketMbedTLS.h" + +#include "IXNetSystem.h" +#include "IXSocket.h" +#include "IXSocketConnect.h" +#include + +#ifdef _WIN32 +// For manipulating the certificate store +#include +#endif + +namespace ix +{ + SocketMbedTLS::SocketMbedTLS(const SocketTLSOptions& tlsOptions, int fd) + : Socket(fd) + , _tlsOptions(tlsOptions) + { + initMBedTLS(); + } + + SocketMbedTLS::~SocketMbedTLS() + { + SocketMbedTLS::close(); + } + + void SocketMbedTLS::initMBedTLS() + { + std::lock_guard lock(_mutex); + + mbedtls_ssl_init(&_ssl); + mbedtls_ssl_config_init(&_conf); + mbedtls_ctr_drbg_init(&_ctr_drbg); + mbedtls_entropy_init(&_entropy); + mbedtls_x509_crt_init(&_cacert); + mbedtls_x509_crt_init(&_cert); + mbedtls_pk_init(&_pkey); + } + + bool SocketMbedTLS::loadSystemCertificates(std::string& errorMsg) + { +#ifdef _WIN32 + DWORD flags = CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG | + CERT_SYSTEM_STORE_CURRENT_USER; + HCERTSTORE systemStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, flags, L"Root"); + + if (!systemStore) + { + errorMsg = "CertOpenStore failed with "; + errorMsg += std::to_string(GetLastError()); + return false; + } + + PCCERT_CONTEXT certificateIterator = NULL; + + int certificateCount = 0; + while (certificateIterator = CertEnumCertificatesInStore(systemStore, certificateIterator)) + { + if (certificateIterator->dwCertEncodingType & X509_ASN_ENCODING) + { + int ret = mbedtls_x509_crt_parse(&_cacert, + certificateIterator->pbCertEncoded, + certificateIterator->cbCertEncoded); + if (ret == 0) + { + ++certificateCount; + } + } + } + + CertFreeCertificateContext(certificateIterator); + CertCloseStore(systemStore, 0); + + if (certificateCount == 0) + { + errorMsg = "No certificates found"; + return false; + } + + return true; +#else + // On macOS we can query the system cert location from the keychain + // On Linux we could try to fetch some local files based on the distribution + // On Android we could use JNI to get to the system certs + return false; +#endif + } + + bool SocketMbedTLS::init(const std::string& host, bool isClient, std::string& errMsg) + { + initMBedTLS(); + std::lock_guard lock(_mutex); + + const char* pers = "IXSocketMbedTLS"; + + if (mbedtls_ctr_drbg_seed(&_ctr_drbg, + mbedtls_entropy_func, + &_entropy, + (const unsigned char*) pers, + strlen(pers)) != 0) + { + errMsg = "Setting entropy seed failed"; + return false; + } + + if (mbedtls_ssl_config_defaults(&_conf, + (isClient) ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT) != 0) + { + errMsg = "Setting config default failed"; + return false; + } + + mbedtls_ssl_conf_rng(&_conf, mbedtls_ctr_drbg_random, &_ctr_drbg); + + if (_tlsOptions.hasCertAndKey()) + { + if (mbedtls_x509_crt_parse_file(&_cert, _tlsOptions.certFile.c_str()) < 0) + { + errMsg = "Cannot parse cert file '" + _tlsOptions.certFile + "'"; + return false; + } +#ifdef IXWEBSOCKET_USE_MBED_TLS_MIN_VERSION_3 + if (mbedtls_pk_parse_keyfile(&_pkey, _tlsOptions.keyFile.c_str(), "", mbedtls_ctr_drbg_random, &_ctr_drbg) < 0) +#else + if (mbedtls_pk_parse_keyfile(&_pkey, _tlsOptions.keyFile.c_str(), "") < 0) +#endif + { + errMsg = "Cannot parse key file '" + _tlsOptions.keyFile + "'"; + return false; + } + if (mbedtls_ssl_conf_own_cert(&_conf, &_cert, &_pkey) < 0) + { + errMsg = "Problem configuring cert '" + _tlsOptions.certFile + "'"; + return false; + } + } + + if (_tlsOptions.isPeerVerifyDisabled()) + { + mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_NONE); + } + else + { + // FIXME: should we call mbedtls_ssl_conf_verify ? + mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_REQUIRED); + + if (_tlsOptions.isUsingSystemDefaults()) + { + if (!loadSystemCertificates(errMsg)) + { + return false; + } + } + else + { + if (_tlsOptions.isUsingInMemoryCAs()) + { + const char* buffer = _tlsOptions.caFile.c_str(); + size_t bufferSize = + _tlsOptions.caFile.size() + 1; // Needs to include null terminating + // character otherwise mbedtls will fail. + if (mbedtls_x509_crt_parse( + &_cacert, (const unsigned char*) buffer, bufferSize) < 0) + { + errMsg = "Cannot parse CA from memory."; + return false; + } + } + else if (mbedtls_x509_crt_parse_file(&_cacert, _tlsOptions.caFile.c_str()) < 0) + { + errMsg = "Cannot parse CA file '" + _tlsOptions.caFile + "'"; + return false; + } + } + + mbedtls_ssl_conf_ca_chain(&_conf, &_cacert, NULL); + } + + if (mbedtls_ssl_setup(&_ssl, &_conf) != 0) + { + errMsg = "SSL setup failed"; + return false; + } + + if (!host.empty() && mbedtls_ssl_set_hostname(&_ssl, host.c_str()) != 0) + { + errMsg = "SNI setup failed"; + return false; + } + + return true; + } + + bool SocketMbedTLS::accept(std::string& errMsg) + { + bool isClient = false; + bool initialized = init(std::string(), isClient, errMsg); + if (!initialized) + { + close(); + return false; + } + + mbedtls_ssl_set_bio(&_ssl, &_sockfd, mbedtls_net_send, mbedtls_net_recv, NULL); + + int res; + do + { + std::lock_guard lock(_mutex); + res = mbedtls_ssl_handshake(&_ssl); + } while (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE); + + if (res != 0) + { + char buf[256]; + mbedtls_strerror(res, buf, sizeof(buf)); + + errMsg = "error in handshake : "; + errMsg += buf; + + if (res == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) + { + char verifyBuf[512]; + uint32_t flags = mbedtls_ssl_get_verify_result(&_ssl); + + mbedtls_x509_crt_verify_info(verifyBuf, sizeof(verifyBuf), " ! ", flags); + errMsg += " : "; + errMsg += verifyBuf; + } + + close(); + return false; + } + + return true; + } + + bool SocketMbedTLS::connect(const std::string& host, + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested) + { + { + std::lock_guard lock(_mutex); + _sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested); + if (_sockfd == -1) return false; + } + + bool isClient = true; + bool initialized = init(host, isClient, errMsg); + if (!initialized) + { + close(); + return false; + } + + mbedtls_ssl_set_bio(&_ssl, &_sockfd, mbedtls_net_send, mbedtls_net_recv, NULL); + + int res; + do + { + { + std::lock_guard lock(_mutex); + res = mbedtls_ssl_handshake(&_ssl); + } + + if (isCancellationRequested()) + { + errMsg = "Cancellation requested"; + close(); + return false; + } + } while (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE); + + if (res != 0) + { + char buf[256]; + mbedtls_strerror(res, buf, sizeof(buf)); + + errMsg = "error in handshake : "; + errMsg += buf; + + close(); + return false; + } + + return true; + } + + void SocketMbedTLS::close() + { + std::lock_guard lock(_mutex); + + mbedtls_ssl_free(&_ssl); + mbedtls_ssl_config_free(&_conf); + mbedtls_ctr_drbg_free(&_ctr_drbg); + mbedtls_entropy_free(&_entropy); + mbedtls_x509_crt_free(&_cacert); + mbedtls_x509_crt_free(&_cert); + + Socket::close(); + } + + ssize_t SocketMbedTLS::send(char* buf, size_t nbyte) + { + std::lock_guard lock(_mutex); + + ssize_t res = mbedtls_ssl_write(&_ssl, (unsigned char*) buf, nbyte); + + if (res > 0) + { + return res; + } + else if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE) + { + errno = EWOULDBLOCK; + return -1; + } + else + { + return -1; + } + } + + ssize_t SocketMbedTLS::recv(void* buf, size_t nbyte) + { + while (true) + { + std::lock_guard lock(_mutex); + + ssize_t res = mbedtls_ssl_read(&_ssl, (unsigned char*) buf, (int) nbyte); + + if (res > 0) + { + return res; + } + + if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE) + { + errno = EWOULDBLOCK; + } + return -1; + } + } + +} // namespace ix + +#endif // IXWEBSOCKET_USE_MBED_TLS diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketMbedTLS.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketMbedTLS.h new file mode 100644 index 0000000000..9dd73f503c --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketMbedTLS.h @@ -0,0 +1,60 @@ +/* + * IXSocketMbedTLS.h + * Author: Benjamin Sergeant + * Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved. + */ +#ifdef IXWEBSOCKET_USE_MBED_TLS + +#pragma once + +#include "IXSocket.h" +#include "IXSocketTLSOptions.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ix +{ + class SocketMbedTLS final : public Socket + { + public: + SocketMbedTLS(const SocketTLSOptions& tlsOptions, int fd = -1); + ~SocketMbedTLS(); + + virtual bool accept(std::string& errMsg) final; + + virtual bool connect(const std::string& host, + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested) final; + virtual void close() final; + + virtual ssize_t send(char* buffer, size_t length) final; + virtual ssize_t recv(void* buffer, size_t length) final; + + private: + mbedtls_ssl_context _ssl; + mbedtls_ssl_config _conf; + mbedtls_entropy_context _entropy; + mbedtls_ctr_drbg_context _ctr_drbg; + mbedtls_x509_crt _cacert; + mbedtls_x509_crt _cert; + mbedtls_pk_context _pkey; + + std::mutex _mutex; + SocketTLSOptions _tlsOptions; + + bool init(const std::string& host, bool isClient, std::string& errMsg); + void initMBedTLS(); + bool loadSystemCertificates(std::string& errMsg); + }; + +} // namespace ix + +#endif // IXWEBSOCKET_USE_MBED_TLS diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketOpenSSL.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketOpenSSL.cpp new file mode 100644 index 0000000000..c92477a13c --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketOpenSSL.cpp @@ -0,0 +1,849 @@ +/* + * IXSocketOpenSSL.cpp + * Author: Benjamin Sergeant, Matt DeBoer, Max Weisel + * Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved. + * + * Adapted from Satori SDK OpenSSL code. + */ +#ifdef IXWEBSOCKET_USE_OPEN_SSL + +#include "IXSocketOpenSSL.h" + +#include "IXSocketConnect.h" +#include "IXUniquePtr.h" +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif +#if OPENSSL_VERSION_NUMBER < 0x10100000L +#include +#endif +#define socketerrno errno + +#ifdef _WIN32 +// For manipulating the certificate store +#include +#endif + +#ifdef _WIN32 +namespace +{ + bool loadWindowsSystemCertificates(SSL_CTX* ssl, std::string& errorMsg) + { + DWORD flags = CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG | + CERT_SYSTEM_STORE_CURRENT_USER; + HCERTSTORE systemStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, flags, L"Root"); + + if (!systemStore) + { + errorMsg = "CertOpenStore failed with "; + errorMsg += std::to_string(GetLastError()); + return false; + } + + PCCERT_CONTEXT certificateIterator = NULL; + X509_STORE* opensslStore = SSL_CTX_get_cert_store(ssl); + + int certificateCount = 0; + while (certificateIterator = CertEnumCertificatesInStore(systemStore, certificateIterator)) + { + X509* x509 = d2i_X509(NULL, + (const unsigned char**) &certificateIterator->pbCertEncoded, + certificateIterator->cbCertEncoded); + + if (x509) + { + if (X509_STORE_add_cert(opensslStore, x509) == 1) + { + ++certificateCount; + } + + X509_free(x509); + } + } + + CertFreeCertificateContext(certificateIterator); + CertCloseStore(systemStore, 0); + + if (certificateCount == 0) + { + errorMsg = "No certificates found"; + return false; + } + + return true; + } +} // namespace +#endif + +namespace ix +{ + const std::string kDefaultCiphers = + "ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-SHA " + "ECDHE-ECDSA-AES256-SHA ECDHE-ECDSA-AES128-SHA256 ECDHE-ECDSA-AES256-SHA384 " + "ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES128-SHA " + "ECDHE-RSA-AES256-SHA ECDHE-RSA-AES128-SHA256 ECDHE-RSA-AES256-SHA384 " + "DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-SHA " + "DHE-RSA-AES256-SHA DHE-RSA-AES128-SHA256 DHE-RSA-AES256-SHA256 AES128-SHA"; + + std::atomic SocketOpenSSL::_openSSLInitializationSuccessful(false); + std::once_flag SocketOpenSSL::_openSSLInitFlag; + std::vector> openSSLMutexes; + + SocketOpenSSL::SocketOpenSSL(const SocketTLSOptions& tlsOptions, int fd) + : Socket(fd) + , _ssl_connection(nullptr) + , _ssl_context(nullptr) + , _tlsOptions(tlsOptions) + { + std::call_once(_openSSLInitFlag, &SocketOpenSSL::openSSLInitialize, this); + } + + SocketOpenSSL::~SocketOpenSSL() + { + SocketOpenSSL::close(); + } + + void SocketOpenSSL::openSSLInitialize() + { +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + if (!OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, nullptr)) return; +#else + (void) OPENSSL_config(nullptr); + + if (CRYPTO_get_locking_callback() == nullptr) + { + openSSLMutexes.clear(); + for (int i = 0; i < CRYPTO_num_locks(); ++i) + { + openSSLMutexes.push_back(ix::make_unique()); + } + CRYPTO_set_locking_callback(SocketOpenSSL::openSSLLockingCallback); + } +#endif + + (void) OpenSSL_add_ssl_algorithms(); + (void) SSL_load_error_strings(); + + _openSSLInitializationSuccessful = true; + } + + void SocketOpenSSL::openSSLLockingCallback(int mode, + int type, + const char* /*file*/, + int /*line*/) + { + if (mode & CRYPTO_LOCK) + { + openSSLMutexes[type]->lock(); + } + else + { + openSSLMutexes[type]->unlock(); + } + } + + std::string SocketOpenSSL::getSSLError(int ret) + { + unsigned long e; + + int err = SSL_get_error(_ssl_connection, ret); + + if (err == SSL_ERROR_WANT_CONNECT || err == SSL_ERROR_WANT_ACCEPT) + { + return "OpenSSL failed - connection failure"; + } + else if (err == SSL_ERROR_WANT_X509_LOOKUP) + { + return "OpenSSL failed - x509 error"; + } + else if (err == SSL_ERROR_SYSCALL) + { + e = ERR_get_error(); + if (e > 0) + { + std::string errMsg("OpenSSL failed - "); + errMsg += ERR_error_string(e, nullptr); + return errMsg; + } + else if (e == 0 && ret == 0) + { + return "OpenSSL failed - received early EOF"; + } + else + { + return "OpenSSL failed - underlying BIO reported an I/O error"; + } + } + else if (err == SSL_ERROR_SSL) + { + e = ERR_get_error(); + std::string errMsg("OpenSSL failed - "); + errMsg += ERR_error_string(e, nullptr); + return errMsg; + } + else if (err == SSL_ERROR_NONE) + { + return "OpenSSL failed - err none"; + } + else if (err == SSL_ERROR_ZERO_RETURN) + { + return "OpenSSL failed - err zero return"; + } + else + { + return "OpenSSL failed - unknown error"; + } + } + + SSL_CTX* SocketOpenSSL::openSSLCreateContext(std::string& errMsg) + { + const SSL_METHOD* method = SSLv23_client_method(); + if (method == nullptr) + { + errMsg = "SSLv23_client_method failure"; + return nullptr; + } + _ssl_method = method; + + SSL_CTX* ctx = SSL_CTX_new(_ssl_method); + if (ctx) + { + SSL_CTX_set_mode(ctx, + SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + + int options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_CIPHER_SERVER_PREFERENCE; + +#ifdef SSL_OP_NO_TLSv1_3 + // (partially?) work around hang in openssl 1.1.1b, by disabling TLS V1.3 + // https://github.com/openssl/openssl/issues/7967 + options |= SSL_OP_NO_TLSv1_3; +#endif + SSL_CTX_set_options(ctx, options); + } + return ctx; + } + + bool SocketOpenSSL::openSSLAddCARootsFromString(const std::string roots) + { + // Create certificate store + X509_STORE* certificate_store = SSL_CTX_get_cert_store(_ssl_context); + if (certificate_store == nullptr) return false; + + // Configure to allow intermediate certs + X509_STORE_set_flags(certificate_store, + X509_V_FLAG_TRUSTED_FIRST | X509_V_FLAG_PARTIAL_CHAIN); + + // Create a new buffer and populate it with the roots + BIO* buffer = BIO_new_mem_buf((void*) roots.c_str(), static_cast(roots.length())); + if (buffer == nullptr) return false; + + // Read each root in the buffer and add to the certificate store + bool success = true; + size_t number_of_roots = 0; + + while (true) + { + // Read the next root in the buffer + X509* root = PEM_read_bio_X509_AUX(buffer, nullptr, nullptr, (void*) ""); + if (root == nullptr) + { + // No more certs left in the buffer, we're done. + ERR_clear_error(); + break; + } + + // Try adding the root to the certificate store + ERR_clear_error(); + if (!X509_STORE_add_cert(certificate_store, root)) + { + // Failed to add. If the error is unrelated to the x509 lib or the cert already + // exists, we're safe to continue. + unsigned long error = ERR_get_error(); + if (ERR_GET_LIB(error) != ERR_LIB_X509 || + ERR_GET_REASON(error) != X509_R_CERT_ALREADY_IN_HASH_TABLE) + { + // Failed. Clean up and bail. + success = false; + X509_free(root); + break; + } + } + + // Clean up and loop + X509_free(root); + number_of_roots++; + } + + // Clean up buffer + BIO_free(buffer); + + // Make sure we loaded at least one certificate. + if (number_of_roots == 0) success = false; + + return success; + } + + /** + * Check whether a hostname matches a pattern + */ + bool SocketOpenSSL::checkHost(const std::string& host, const char* pattern) + { +#ifdef _WIN32 + return PathMatchSpecA(host.c_str(), pattern); +#else + return fnmatch(pattern, host.c_str(), 0) != FNM_NOMATCH; +#endif + } + + bool SocketOpenSSL::openSSLCheckServerCert(SSL* ssl, + const std::string& hostname, + std::string& errMsg) + { + X509* server_cert = SSL_get_peer_certificate(ssl); + if (server_cert == nullptr) + { + errMsg = "OpenSSL failed - peer didn't present a X509 certificate."; + return false; + } + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + // Check server name + bool hostname_verifies_ok = false; + STACK_OF(GENERAL_NAME)* san_names = (STACK_OF(GENERAL_NAME)*) X509_get_ext_d2i( + (X509*) server_cert, NID_subject_alt_name, NULL, NULL); + if (san_names) + { + for (int i = 0; i < sk_GENERAL_NAME_num(san_names); i++) + { + const GENERAL_NAME* sk_name = sk_GENERAL_NAME_value(san_names, i); + if (sk_name->type == GEN_DNS) + { + char* name = (char*) ASN1_STRING_data(sk_name->d.dNSName); + if ((size_t) ASN1_STRING_length(sk_name->d.dNSName) == strlen(name) && + checkHost(hostname, name)) + { + hostname_verifies_ok = true; + break; + } + } + } + } + sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free); + + if (!hostname_verifies_ok) + { + int cn_pos = X509_NAME_get_index_by_NID( + X509_get_subject_name((X509*) server_cert), NID_commonName, -1); + if (cn_pos) + { + X509_NAME_ENTRY* cn_entry = + X509_NAME_get_entry(X509_get_subject_name((X509*) server_cert), cn_pos); + + if (cn_entry) + { + ASN1_STRING* cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry); + char* cn = (char*) ASN1_STRING_data(cn_asn1); + + if ((size_t) ASN1_STRING_length(cn_asn1) == strlen(cn) && + checkHost(hostname, cn)) + { + hostname_verifies_ok = true; + } + } + } + } + + if (!hostname_verifies_ok) + { + errMsg = "OpenSSL failed - certificate was issued for a different domain."; + return false; + } +#endif + + X509_free(server_cert); + return true; + } + + bool SocketOpenSSL::openSSLClientHandshake(const std::string& host, + std::string& errMsg, + const CancellationRequest& isCancellationRequested) + { + while (true) + { + if (_ssl_connection == nullptr || _ssl_context == nullptr) + { + return false; + } + + if (isCancellationRequested()) + { + errMsg = "Cancellation requested"; + return false; + } + + ERR_clear_error(); + int connect_result = SSL_connect(_ssl_connection); + if (connect_result == 1) + { + return openSSLCheckServerCert(_ssl_connection, host, errMsg); + } + int reason = SSL_get_error(_ssl_connection, connect_result); + + bool rc = false; + if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE) + { + rc = true; + } + else + { + errMsg = getSSLError(connect_result); + rc = false; + } + + if (!rc) + { + return false; + } + } + } + + bool SocketOpenSSL::openSSLServerHandshake(std::string& errMsg) + { + while (true) + { + if (_ssl_connection == nullptr || _ssl_context == nullptr) + { + return false; + } + + ERR_clear_error(); + int accept_result = SSL_accept(_ssl_connection); + if (accept_result == 1) + { + return true; + } + int reason = SSL_get_error(_ssl_connection, accept_result); + + bool rc = false; + if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE) + { + rc = true; + } + else + { + errMsg = getSSLError(accept_result); + rc = false; + } + + if (!rc) + { + return false; + } + } + } + + bool SocketOpenSSL::handleTLSOptions(std::string& errMsg) + { + ERR_clear_error(); + if (_tlsOptions.hasCertAndKey()) + { + if (SSL_CTX_use_certificate_chain_file(_ssl_context, _tlsOptions.certFile.c_str()) != 1) + { + auto sslErr = ERR_get_error(); + errMsg = "OpenSSL failed - SSL_CTX_use_certificate_chain_file(\"" + + _tlsOptions.certFile + "\") failed: "; + errMsg += ERR_error_string(sslErr, nullptr); + } + else if (SSL_CTX_use_PrivateKey_file( + _ssl_context, _tlsOptions.keyFile.c_str(), SSL_FILETYPE_PEM) != 1) + { + auto sslErr = ERR_get_error(); + errMsg = "OpenSSL failed - SSL_CTX_use_PrivateKey_file(\"" + _tlsOptions.keyFile + + "\") failed: "; + errMsg += ERR_error_string(sslErr, nullptr); + } + else if (!SSL_CTX_check_private_key(_ssl_context)) + { + auto sslErr = ERR_get_error(); + errMsg = "OpenSSL failed - cert/key mismatch(\"" + _tlsOptions.certFile + ", " + + _tlsOptions.keyFile + "\")"; + errMsg += ERR_error_string(sslErr, nullptr); + } + } + + ERR_clear_error(); + if (!_tlsOptions.isPeerVerifyDisabled()) + { + if (_tlsOptions.isUsingSystemDefaults()) + { +#ifdef _WIN32 + if (!loadWindowsSystemCertificates(_ssl_context, errMsg)) + { + return false; + } +#else + if (SSL_CTX_set_default_verify_paths(_ssl_context) == 0) + { + auto sslErr = ERR_get_error(); + errMsg = "OpenSSL failed - SSL_CTX_default_verify_paths loading failed: "; + errMsg += ERR_error_string(sslErr, nullptr); + return false; + } +#endif + } + else + { + if (_tlsOptions.isUsingInMemoryCAs()) + { + // Load from memory + openSSLAddCARootsFromString(_tlsOptions.caFile); + } + else + { + if (SSL_CTX_load_verify_locations( + _ssl_context, _tlsOptions.caFile.c_str(), NULL) != 1) + { + auto sslErr = ERR_get_error(); + errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" + + _tlsOptions.caFile + "\") failed: "; + errMsg += ERR_error_string(sslErr, nullptr); + return false; + } + } + } + + SSL_CTX_set_verify(_ssl_context, + SSL_VERIFY_PEER, + [](int preverify, X509_STORE_CTX*) -> int { return preverify; }); + SSL_CTX_set_verify_depth(_ssl_context, 4); + } + else + { + SSL_CTX_set_verify(_ssl_context, SSL_VERIFY_NONE, nullptr); + } + + if (_tlsOptions.isUsingDefaultCiphers()) + { + if (SSL_CTX_set_cipher_list(_ssl_context, kDefaultCiphers.c_str()) != 1) + { + auto sslErr = ERR_get_error(); + errMsg = "OpenSSL failed - SSL_CTX_set_cipher_list(\"" + kDefaultCiphers + + "\") failed: "; + errMsg += ERR_error_string(sslErr, nullptr); + return false; + } + } + else if (SSL_CTX_set_cipher_list(_ssl_context, _tlsOptions.ciphers.c_str()) != 1) + { + auto sslErr = ERR_get_error(); + errMsg = "OpenSSL failed - SSL_CTX_set_cipher_list(\"" + _tlsOptions.ciphers + + "\") failed: "; + errMsg += ERR_error_string(sslErr, nullptr); + return false; + } + + return true; + } + + bool SocketOpenSSL::accept(std::string& errMsg) + { + bool handshakeSuccessful = false; + { + std::lock_guard lock(_mutex); + + if (!_openSSLInitializationSuccessful) + { + errMsg = "OPENSSL_init_ssl failure"; + return false; + } + + if (_sockfd == -1) + { + return false; + } + + { + const SSL_METHOD* method = SSLv23_server_method(); + if (method == nullptr) + { + errMsg = "SSLv23_server_method failure"; + _ssl_context = nullptr; + } + else + { + _ssl_method = method; + + _ssl_context = SSL_CTX_new(_ssl_method); + if (_ssl_context) + { + SSL_CTX_set_mode(_ssl_context, SSL_MODE_ENABLE_PARTIAL_WRITE); + SSL_CTX_set_mode(_ssl_context, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_CTX_set_options(_ssl_context, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); + } + } + } + + if (_ssl_context == nullptr) + { + return false; + } + + ERR_clear_error(); + if (_tlsOptions.hasCertAndKey()) + { + if (SSL_CTX_use_certificate_chain_file(_ssl_context, + _tlsOptions.certFile.c_str()) != 1) + { + auto sslErr = ERR_get_error(); + errMsg = "OpenSSL failed - SSL_CTX_use_certificate_chain_file(\"" + + _tlsOptions.certFile + "\") failed: "; + errMsg += ERR_error_string(sslErr, nullptr); + } + else if (SSL_CTX_use_PrivateKey_file( + _ssl_context, _tlsOptions.keyFile.c_str(), SSL_FILETYPE_PEM) != 1) + { + auto sslErr = ERR_get_error(); + errMsg = "OpenSSL failed - SSL_CTX_use_PrivateKey_file(\"" + + _tlsOptions.keyFile + "\") failed: "; + errMsg += ERR_error_string(sslErr, nullptr); + } + } + + + ERR_clear_error(); + if (!_tlsOptions.isPeerVerifyDisabled()) + { + if (_tlsOptions.isUsingSystemDefaults()) + { + if (SSL_CTX_set_default_verify_paths(_ssl_context) == 0) + { + auto sslErr = ERR_get_error(); + errMsg = "OpenSSL failed - SSL_CTX_default_verify_paths loading failed: "; + errMsg += ERR_error_string(sslErr, nullptr); + } + } + else + { + if (_tlsOptions.isUsingInMemoryCAs()) + { + // Load from memory + openSSLAddCARootsFromString(_tlsOptions.caFile); + } + else + { + const char* root_ca_file = _tlsOptions.caFile.c_str(); + STACK_OF(X509_NAME) * rootCAs; + rootCAs = SSL_load_client_CA_file(root_ca_file); + if (rootCAs == NULL) + { + auto sslErr = ERR_get_error(); + errMsg = "OpenSSL failed - SSL_load_client_CA_file('" + + _tlsOptions.caFile + "') failed: "; + errMsg += ERR_error_string(sslErr, nullptr); + } + else + { + SSL_CTX_set_client_CA_list(_ssl_context, rootCAs); + if (SSL_CTX_load_verify_locations( + _ssl_context, root_ca_file, nullptr) != 1) + { + auto sslErr = ERR_get_error(); + errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" + + _tlsOptions.caFile + "\") failed: "; + errMsg += ERR_error_string(sslErr, nullptr); + } + } + } + } + + SSL_CTX_set_verify( + _ssl_context, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + SSL_CTX_set_verify_depth(_ssl_context, 4); + } + else + { + SSL_CTX_set_verify(_ssl_context, SSL_VERIFY_NONE, nullptr); + } + if (_tlsOptions.isUsingDefaultCiphers()) + { + if (SSL_CTX_set_cipher_list(_ssl_context, kDefaultCiphers.c_str()) != 1) + { + return false; + } + } + else if (SSL_CTX_set_cipher_list(_ssl_context, _tlsOptions.ciphers.c_str()) != 1) + { + return false; + } + + _ssl_connection = SSL_new(_ssl_context); + if (_ssl_connection == nullptr) + { + errMsg = "OpenSSL failed to connect"; + SSL_CTX_free(_ssl_context); + _ssl_context = nullptr; + return false; + } + + SSL_set_ecdh_auto(_ssl_connection, 1); + + SSL_set_fd(_ssl_connection, _sockfd); + + handshakeSuccessful = openSSLServerHandshake(errMsg); + } + + if (!handshakeSuccessful) + { + close(); + return false; + } + + return true; + } + + bool SocketOpenSSL::connect(const std::string& host, + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested) + { + bool handshakeSuccessful = false; + { + std::lock_guard lock(_mutex); + + if (!_openSSLInitializationSuccessful) + { + errMsg = "OPENSSL_init_ssl failure"; + return false; + } + + _sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested); + if (_sockfd == -1) return false; + + _ssl_context = openSSLCreateContext(errMsg); + if (_ssl_context == nullptr) + { + return false; + } + + if (!handleTLSOptions(errMsg)) + { + return false; + } + + _ssl_connection = SSL_new(_ssl_context); + if (_ssl_connection == nullptr) + { + errMsg = "OpenSSL failed to connect"; + SSL_CTX_free(_ssl_context); + _ssl_context = nullptr; + return false; + } + SSL_set_fd(_ssl_connection, _sockfd); + + // SNI support + SSL_set_tlsext_host_name(_ssl_connection, host.c_str()); + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + // Support for server name verification + // (The docs say that this should work from 1.0.2, and is the default from + // 1.1.0, but it does not. To be on the safe side, the manual test + // below is enabled for all versions prior to 1.1.0.) + X509_VERIFY_PARAM* param = SSL_get0_param(_ssl_connection); + X509_VERIFY_PARAM_set1_host(param, host.c_str(), 0); +#endif + handshakeSuccessful = openSSLClientHandshake(host, errMsg, isCancellationRequested); + } + + if (!handshakeSuccessful) + { + close(); + return false; + } + + return true; + } + + void SocketOpenSSL::close() + { + std::lock_guard lock(_mutex); + + if (_ssl_connection != nullptr) + { + SSL_free(_ssl_connection); + _ssl_connection = nullptr; + } + if (_ssl_context != nullptr) + { + SSL_CTX_free(_ssl_context); + _ssl_context = nullptr; + } + + Socket::close(); + } + + ssize_t SocketOpenSSL::send(char* buf, size_t nbyte) + { + std::lock_guard lock(_mutex); + + if (_ssl_connection == nullptr || _ssl_context == nullptr) + { + return 0; + } + + ERR_clear_error(); + ssize_t write_result = SSL_write(_ssl_connection, buf, (int) nbyte); + int reason = SSL_get_error(_ssl_connection, (int) write_result); + + if (reason == SSL_ERROR_NONE) + { + return write_result; + } + else if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE) + { + errno = EWOULDBLOCK; + return -1; + } + else + { + return -1; + } + } + + ssize_t SocketOpenSSL::recv(void* buf, size_t nbyte) + { + while (true) + { + std::lock_guard lock(_mutex); + + if (_ssl_connection == nullptr || _ssl_context == nullptr) + { + return 0; + } + + ERR_clear_error(); + ssize_t read_result = SSL_read(_ssl_connection, buf, (int) nbyte); + + if (read_result > 0) + { + return read_result; + } + + int reason = SSL_get_error(_ssl_connection, (int) read_result); + + if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE) + { + errno = EWOULDBLOCK; + } + return -1; + } + } + +} // namespace ix + +#endif // IXWEBSOCKET_USE_OPEN_SSL diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketOpenSSL.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketOpenSSL.h new file mode 100644 index 0000000000..dea1ffd612 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketOpenSSL.h @@ -0,0 +1,68 @@ +/* + * IXSocketOpenSSL.h + * Author: Benjamin Sergeant, Matt DeBoer + * Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved. + */ +#ifdef IXWEBSOCKET_USE_OPEN_SSL + +#pragma once + +#include "IXCancellationRequest.h" +#include "IXSocket.h" +#include "IXSocketTLSOptions.h" +#include +#include +#include +#include +#include +#include + +namespace ix +{ + class SocketOpenSSL final : public Socket + { + public: + SocketOpenSSL(const SocketTLSOptions& tlsOptions, int fd = -1); + ~SocketOpenSSL(); + + virtual bool accept(std::string& errMsg) final; + + virtual bool connect(const std::string& host, + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested) final; + virtual void close() final; + + virtual ssize_t send(char* buffer, size_t length) final; + virtual ssize_t recv(void* buffer, size_t length) final; + + private: + void openSSLInitialize(); + std::string getSSLError(int ret); + SSL_CTX* openSSLCreateContext(std::string& errMsg); + bool openSSLAddCARootsFromString(const std::string roots); + bool openSSLClientHandshake(const std::string& hostname, + std::string& errMsg, + const CancellationRequest& isCancellationRequested); + bool openSSLCheckServerCert(SSL* ssl, const std::string& hostname, std::string& errMsg); + bool checkHost(const std::string& host, const char* pattern); + bool handleTLSOptions(std::string& errMsg); + bool openSSLServerHandshake(std::string& errMsg); + + // Required for OpenSSL < 1.1 + static void openSSLLockingCallback(int mode, int type, const char* /*file*/, int /*line*/); + + SSL* _ssl_connection; + SSL_CTX* _ssl_context; + const SSL_METHOD* _ssl_method; + SocketTLSOptions _tlsOptions; + + mutable std::mutex _mutex; // OpenSSL routines are not thread-safe + + static std::once_flag _openSSLInitFlag; + static std::atomic _openSSLInitializationSuccessful; + }; + +} // namespace ix + +#endif // IXWEBSOCKET_USE_OPEN_SSL diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketServer.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketServer.cpp new file mode 100644 index 0000000000..ed04bb0c4f --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketServer.cpp @@ -0,0 +1,489 @@ +/* + * IXSocketServer.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ + +#include "IXSocketServer.h" + +#include "IXNetSystem.h" +#include "IXSelectInterrupt.h" +#include "IXSelectInterruptFactory.h" +#include "IXSetThreadName.h" +#include "IXSocket.h" +#include "IXSocketConnect.h" +#include "IXSocketFactory.h" +#include +#include +#include +#include + +namespace ix +{ + const int SocketServer::kDefaultPort(8080); + const std::string SocketServer::kDefaultHost("127.0.0.1"); + const int SocketServer::kDefaultTcpBacklog(5); + const size_t SocketServer::kDefaultMaxConnections(128); + const int SocketServer::kDefaultAddressFamily(AF_INET); + + SocketServer::SocketServer( + int port, const std::string& host, int backlog, size_t maxConnections, int addressFamily) + : _port(port) + , _host(host) + , _backlog(backlog) + , _maxConnections(maxConnections) + , _addressFamily(addressFamily) + , _serverFd(-1) + , _stop(false) + , _stopGc(false) + , _connectionStateFactory(&ConnectionState::createConnectionState) + , _acceptSelectInterrupt(createSelectInterrupt()) + { + } + + SocketServer::~SocketServer() + { + stop(); + } + + void SocketServer::logError(const std::string& str) + { + std::lock_guard lock(_logMutex); + fprintf(stderr, "%s\n", str.c_str()); + } + + void SocketServer::logInfo(const std::string& str) + { + std::lock_guard lock(_logMutex); + fprintf(stdout, "%s\n", str.c_str()); + } + + std::pair SocketServer::listen() + { + std::string acceptSelectInterruptInitErrorMsg; + if (!_acceptSelectInterrupt->init(acceptSelectInterruptInitErrorMsg)) + { + std::stringstream ss; + ss << "SocketServer::listen() error in SelectInterrupt::init: " + << acceptSelectInterruptInitErrorMsg; + + return std::make_pair(false, ss.str()); + } + + if (_addressFamily != AF_INET && _addressFamily != AF_INET6) + { + std::string errMsg("SocketServer::listen() AF_INET and AF_INET6 are currently " + "the only supported address families"); + return std::make_pair(false, errMsg); + } + + // Get a socket for accepting connections. + if ((_serverFd = socket(_addressFamily, SOCK_STREAM, 0)) < 0) + { + std::stringstream ss; + ss << "SocketServer::listen() error creating socket): " << strerror(Socket::getErrno()); + + return std::make_pair(false, ss.str()); + } + + // Make that socket reusable. (allow restarting this server at will) + int enable = 1; + if (setsockopt(_serverFd, SOL_SOCKET, SO_REUSEADDR, (char*) &enable, sizeof(enable)) < 0) + { + std::stringstream ss; + ss << "SocketServer::listen() error calling setsockopt(SO_REUSEADDR) " + << "at address " << _host << ":" << _port << " : " << strerror(Socket::getErrno()); + + Socket::closeSocket(_serverFd); + return std::make_pair(false, ss.str()); + } + + if (_addressFamily == AF_INET) + { + struct sockaddr_in server; + server.sin_family = _addressFamily; + server.sin_port = htons(_port); + + if (ix::inet_pton(_addressFamily, _host.c_str(), &server.sin_addr.s_addr) <= 0) + { + std::stringstream ss; + ss << "SocketServer::listen() error calling inet_pton " + << "at address " << _host << ":" << _port << " : " + << strerror(Socket::getErrno()); + + Socket::closeSocket(_serverFd); + return std::make_pair(false, ss.str()); + } + + // Bind the socket to the server address. + if (bind(_serverFd, (struct sockaddr*) &server, sizeof(server)) < 0) + { + std::stringstream ss; + ss << "SocketServer::listen() error calling bind " + << "at address " << _host << ":" << _port << " : " + << strerror(Socket::getErrno()); + + Socket::closeSocket(_serverFd); + return std::make_pair(false, ss.str()); + } + } + else // AF_INET6 + { + struct sockaddr_in6 server; + server.sin6_family = _addressFamily; + server.sin6_port = htons(_port); + + if (ix::inet_pton(_addressFamily, _host.c_str(), &server.sin6_addr) <= 0) + { + std::stringstream ss; + ss << "SocketServer::listen() error calling inet_pton " + << "at address " << _host << ":" << _port << " : " + << strerror(Socket::getErrno()); + + Socket::closeSocket(_serverFd); + return std::make_pair(false, ss.str()); + } + + // Bind the socket to the server address. + if (bind(_serverFd, (struct sockaddr*) &server, sizeof(server)) < 0) + { + std::stringstream ss; + ss << "SocketServer::listen() error calling bind " + << "at address " << _host << ":" << _port << " : " + << strerror(Socket::getErrno()); + + Socket::closeSocket(_serverFd); + return std::make_pair(false, ss.str()); + } + } + + // + // Listen for connections. Specify the tcp backlog. + // + if (::listen(_serverFd, _backlog) < 0) + { + std::stringstream ss; + ss << "SocketServer::listen() error calling listen " + << "at address " << _host << ":" << _port << " : " << strerror(Socket::getErrno()); + + Socket::closeSocket(_serverFd); + return std::make_pair(false, ss.str()); + } + + return std::make_pair(true, ""); + } + + void SocketServer::start() + { + _stop = false; + + if (!_thread.joinable()) + { + _thread = std::thread(&SocketServer::run, this); + } + + if (!_gcThread.joinable()) + { + _gcThread = std::thread(&SocketServer::runGC, this); + } + } + + void SocketServer::wait() + { + std::unique_lock lock(_conditionVariableMutex); + _conditionVariable.wait(lock); + } + + void SocketServer::stopAcceptingConnections() + { + _stop = true; + } + + void SocketServer::stop() + { + // Stop accepting connections, and close the 'accept' thread + if (_thread.joinable()) + { + _stop = true; + // Wake up select + if (!_acceptSelectInterrupt->notify(SelectInterrupt::kCloseRequest)) + { + logError("SocketServer::stop: Cannot wake up from select"); + } + + _thread.join(); + _stop = false; + } + + // Join all threads and make sure that all connections are terminated + if (_gcThread.joinable()) + { + _stopGc = true; + _conditionVariableGC.notify_one(); + _gcThread.join(); + _stopGc = false; + } + + _conditionVariable.notify_one(); + Socket::closeSocket(_serverFd); + } + + void SocketServer::setConnectionStateFactory( + const ConnectionStateFactory& connectionStateFactory) + { + _connectionStateFactory = connectionStateFactory; + } + + // + // join the threads for connections that have been closed + // + // When a connection is closed by a client, the connection state terminated + // field becomes true, and we can use that to know that we can join that thread + // and remove it from our _connectionsThreads data structure (a list). + // + void SocketServer::closeTerminatedThreads() + { + std::lock_guard lock(_connectionsThreadsMutex); + auto it = _connectionsThreads.begin(); + auto itEnd = _connectionsThreads.end(); + + while (it != itEnd) + { + auto& connectionState = it->first; + auto& thread = it->second; + + if (!connectionState->isTerminated()) + { + ++it; + continue; + } + + if (thread.joinable()) thread.join(); + it = _connectionsThreads.erase(it); + } + } + + void SocketServer::run() + { + // Set the socket to non blocking mode, so that accept calls are not blocking + SocketConnect::configure(_serverFd); + + setThreadName("SocketServer::accept"); + + for (;;) + { + if (_stop) return; + + // Use poll to check whether a new connection is in progress + int timeoutMs = -1; +#ifdef _WIN32 + // select cannot be interrupted on Windows so we need to pass a small timeout + timeoutMs = 10; +#endif + + bool readyToRead = true; + PollResultType pollResult = + Socket::poll(readyToRead, timeoutMs, _serverFd, _acceptSelectInterrupt); + + if (pollResult == PollResultType::Error) + { + std::stringstream ss; + ss << "SocketServer::run() error in select: " << strerror(Socket::getErrno()); + logError(ss.str()); + continue; + } + + if (pollResult != PollResultType::ReadyForRead) + { + continue; + } + + // Accept a connection. + // FIXME: Is this working for ipv6 ? + struct sockaddr_in client; // client address information + int clientFd; // socket connected to client + socklen_t addressLen = sizeof(client); + memset(&client, 0, sizeof(client)); + + if ((clientFd = accept(_serverFd, (struct sockaddr*) &client, &addressLen)) < 0) + { + if (!Socket::isWaitNeeded()) + { + // FIXME: that error should be propagated + int err = Socket::getErrno(); + std::stringstream ss; + ss << "SocketServer::run() error accepting connection: " << err << ", " + << strerror(err); + logError(ss.str()); + } + continue; + } + + if (getConnectedClientsCount() >= _maxConnections) + { + std::stringstream ss; + ss << "SocketServer::run() reached max connections = " << _maxConnections << ". " + << "Not accepting connection"; + logError(ss.str()); + + Socket::closeSocket(clientFd); + + continue; + } + + // Retrieve connection info, the ip address of the remote peer/client) + std::string remoteIp; + int remotePort; + + if (_addressFamily == AF_INET) + { + char remoteIp4[INET_ADDRSTRLEN]; + if (ix::inet_ntop(AF_INET, &client.sin_addr, remoteIp4, INET_ADDRSTRLEN) == nullptr) + { + int err = Socket::getErrno(); + std::stringstream ss; + ss << "SocketServer::run() error calling inet_ntop (ipv4): " << err << ", " + << strerror(err); + logError(ss.str()); + + Socket::closeSocket(clientFd); + + continue; + } + + remotePort = ix::network_to_host_short(client.sin_port); + remoteIp = remoteIp4; + } + else // AF_INET6 + { + char remoteIp6[INET6_ADDRSTRLEN]; + if (ix::inet_ntop(AF_INET6, &client.sin_addr, remoteIp6, INET6_ADDRSTRLEN) == + nullptr) + { + int err = Socket::getErrno(); + std::stringstream ss; + ss << "SocketServer::run() error calling inet_ntop (ipv6): " << err << ", " + << strerror(err); + logError(ss.str()); + + Socket::closeSocket(clientFd); + + continue; + } + + remotePort = ix::network_to_host_short(client.sin_port); + remoteIp = remoteIp6; + } + + std::shared_ptr connectionState; + if (_connectionStateFactory) + { + connectionState = _connectionStateFactory(); + } + connectionState->setOnSetTerminatedCallback([this] { onSetTerminatedCallback(); }); + connectionState->setRemoteIp(remoteIp); + connectionState->setRemotePort(remotePort); + + if (_stop) return; + + // create socket + std::string errorMsg; + bool tls = _socketTLSOptions.tls; + auto socket = createSocket(tls, clientFd, errorMsg, _socketTLSOptions); + + if (socket == nullptr) + { + logError("SocketServer::run() cannot create socket: " + errorMsg); + Socket::closeSocket(clientFd); + continue; + } + + // Set the socket to non blocking mode + other tweaks + SocketConnect::configure(clientFd); + + if (!socket->accept(errorMsg)) + { + logError("SocketServer::run() tls accept failed: " + errorMsg); + Socket::closeSocket(clientFd); + continue; + } + + // Launch the handleConnection work asynchronously in its own thread. + std::lock_guard lock(_connectionsThreadsMutex); + _connectionsThreads.push_back(std::make_pair( + connectionState, + std::thread( + &SocketServer::handleConnection, this, std::move(socket), connectionState))); + } + } + + size_t SocketServer::getConnectionsThreadsCount() + { + std::lock_guard lock(_connectionsThreadsMutex); + return _connectionsThreads.size(); + } + + void SocketServer::runGC() + { + setThreadName("SocketServer::GC"); + + for (;;) + { + // Garbage collection to shutdown/join threads for closed connections. + closeTerminatedThreads(); + + // We quit this thread if all connections are closed and we received + // a stop request by setting _stopGc to true. + if (_stopGc && getConnectionsThreadsCount() == 0) + { + break; + } + + // Unless we are stopping the server, wait for a connection + // to be terminated to run the threads GC, instead of busy waiting + // with a sleep + if (!_stopGc) + { + std::unique_lock lock(_conditionVariableMutexGC); + _conditionVariableGC.wait(lock); + } + } + } + + void SocketServer::setTLSOptions(const SocketTLSOptions& socketTLSOptions) + { + _socketTLSOptions = socketTLSOptions; + } + + void SocketServer::onSetTerminatedCallback() + { + // a connection got terminated, we can run the connection thread GC, + // so wake up the thread responsible for that + _conditionVariableGC.notify_one(); + } + + int SocketServer::getPort() + { + return _port; + } + + std::string SocketServer::getHost() + { + return _host; + } + + int SocketServer::getBacklog() + { + return _backlog; + } + + std::size_t SocketServer::getMaxConnections() + { + return _maxConnections; + } + + int SocketServer::getAddressFamily() + { + return _addressFamily; + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketServer.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketServer.h new file mode 100644 index 0000000000..fe0f7e281e --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketServer.h @@ -0,0 +1,130 @@ +/* + * IXSocketServer.h + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include "IXConnectionState.h" +#include "IXNetSystem.h" +#include "IXSelectInterrupt.h" +#include "IXSocketTLSOptions.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // pair + +namespace ix +{ + class Socket; + + class SocketServer + { + public: + using ConnectionStateFactory = std::function()>; + + // Each connection is handled by its own worker thread. + // We use a list as we only care about remove and append operations. + using ConnectionThreads = + std::list, std::thread>>; + + SocketServer(int port = SocketServer::kDefaultPort, + const std::string& host = SocketServer::kDefaultHost, + int backlog = SocketServer::kDefaultTcpBacklog, + size_t maxConnections = SocketServer::kDefaultMaxConnections, + int addressFamily = SocketServer::kDefaultAddressFamily); + virtual ~SocketServer(); + virtual void stop(); + + // It is possible to override ConnectionState through inheritance + // this method allows user to change the factory by returning an object + // that inherits from ConnectionState but has its own methods. + void setConnectionStateFactory(const ConnectionStateFactory& connectionStateFactory); + + const static int kDefaultPort; + const static std::string kDefaultHost; + const static int kDefaultTcpBacklog; + const static size_t kDefaultMaxConnections; + const static int kDefaultAddressFamily; + + void start(); + std::pair listen(); + void wait(); + + void setTLSOptions(const SocketTLSOptions& socketTLSOptions); + + int getPort(); + std::string getHost(); + int getBacklog(); + std::size_t getMaxConnections(); + int getAddressFamily(); + protected: + // Logging + void logError(const std::string& str); + void logInfo(const std::string& str); + + void stopAcceptingConnections(); + + private: + // Member variables + int _port; + std::string _host; + int _backlog; + size_t _maxConnections; + int _addressFamily; + + // socket for accepting connections + socket_t _serverFd; + + std::atomic _stop; + + std::mutex _logMutex; + + // background thread to wait for incoming connections + std::thread _thread; + void run(); + void onSetTerminatedCallback(); + + // background thread to cleanup (join) terminated threads + std::atomic _stopGc; + std::thread _gcThread; + void runGC(); + + // the list of (connectionState, threads) for each connections + ConnectionThreads _connectionsThreads; + std::mutex _connectionsThreadsMutex; + + // used to have the main control thread for a server + // wait for a 'terminate' notification without busy polling + std::condition_variable _conditionVariable; + std::mutex _conditionVariableMutex; + + // the factory to create ConnectionState objects + ConnectionStateFactory _connectionStateFactory; + + virtual void handleConnection(std::unique_ptr, + std::shared_ptr connectionState) = 0; + virtual size_t getConnectedClientsCount() = 0; + + // Returns true if all connection threads are joined + void closeTerminatedThreads(); + size_t getConnectionsThreadsCount(); + + SocketTLSOptions _socketTLSOptions; + + // to wake up from select + SelectInterruptPtr _acceptSelectInterrupt; + + // used by the gc thread, to know that a thread needs to be garbage collected + // as a connection + std::condition_variable _conditionVariableGC; + std::mutex _conditionVariableMutexGC; + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketTLSOptions.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketTLSOptions.cpp new file mode 100644 index 0000000000..4156920a76 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketTLSOptions.cpp @@ -0,0 +1,93 @@ +/* + * IXSocketTLSOptions.h + * Author: Matt DeBoer + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#include "IXSocketTLSOptions.h" + +#include +#include +#include + +namespace ix +{ + const char* kTLSCAFileUseSystemDefaults = "SYSTEM"; + const char* kTLSCAFileDisableVerify = "NONE"; + const char* kTLSCiphersUseDefault = "DEFAULT"; + const char* kTLSInMemoryMarker = "-----BEGIN CERTIFICATE-----"; + + bool SocketTLSOptions::isValid() const + { + if (!_validated) + { + if (!certFile.empty() && !std::ifstream(certFile)) + { + _errMsg = "certFile not found: " + certFile; + return false; + } + if (!keyFile.empty() && !std::ifstream(keyFile)) + { + _errMsg = "keyFile not found: " + keyFile; + return false; + } + if (!caFile.empty() && caFile != kTLSCAFileDisableVerify && + caFile != kTLSCAFileUseSystemDefaults && !std::ifstream(caFile)) + { + _errMsg = "caFile not found: " + caFile; + return false; + } + + if (certFile.empty() != keyFile.empty()) + { + _errMsg = "certFile and keyFile must be both present, or both absent"; + return false; + } + + _validated = true; + } + return true; + } + + bool SocketTLSOptions::hasCertAndKey() const + { + return !certFile.empty() && !keyFile.empty(); + } + + bool SocketTLSOptions::isUsingSystemDefaults() const + { + return caFile == kTLSCAFileUseSystemDefaults; + } + + bool SocketTLSOptions::isUsingInMemoryCAs() const + { + return caFile.find(kTLSInMemoryMarker) != std::string::npos; + } + + bool SocketTLSOptions::isPeerVerifyDisabled() const + { + return caFile == kTLSCAFileDisableVerify; + } + + bool SocketTLSOptions::isUsingDefaultCiphers() const + { + return ciphers.empty() || ciphers == kTLSCiphersUseDefault; + } + + const std::string& SocketTLSOptions::getErrorMsg() const + { + return _errMsg; + } + + std::string SocketTLSOptions::getDescription() const + { + std::stringstream ss; + ss << "TLS Options:" << std::endl; + ss << " certFile = " << certFile << std::endl; + ss << " keyFile = " << keyFile << std::endl; + ss << " caFile = " << caFile << std::endl; + ss << " ciphers = " << ciphers << std::endl; + ss << " ciphers = " << ciphers << std::endl; + return ss.str(); + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketTLSOptions.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketTLSOptions.h new file mode 100644 index 0000000000..e396b38803 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXSocketTLSOptions.h @@ -0,0 +1,54 @@ +/* + * IXSocketTLSOptions.h + * Author: Matt DeBoer + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include + +namespace ix +{ + struct SocketTLSOptions + { + public: + // check validity of the object + bool isValid() const; + + // the certificate presented to peers + std::string certFile; + + // the key used for signing/encryption + std::string keyFile; + + // the ca certificate (or certificate bundle) file containing + // certificates to be trusted by peers; use 'SYSTEM' to + // leverage the system defaults, use 'NONE' to disable peer verification + std::string caFile = "SYSTEM"; + + // list of ciphers (rsa, etc...) + std::string ciphers = "DEFAULT"; + + // whether tls is enabled, used for server code + bool tls = false; + + bool hasCertAndKey() const; + + bool isUsingSystemDefaults() const; + + bool isUsingInMemoryCAs() const; + + bool isPeerVerifyDisabled() const; + + bool isUsingDefaultCiphers() const; + + const std::string& getErrorMsg() const; + + std::string getDescription() const; + + private: + mutable std::string _errMsg; + mutable bool _validated = false; + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXStrCaseCompare.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXStrCaseCompare.cpp new file mode 100644 index 0000000000..833815ff67 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXStrCaseCompare.cpp @@ -0,0 +1,37 @@ +/* + * IXStrCaseCompare.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2020 Machine Zone. All rights reserved. + */ + +#include "IXStrCaseCompare.h" + +#include +#include + +namespace ix +{ + bool CaseInsensitiveLess::NocaseCompare::operator()(const unsigned char& c1, + const unsigned char& c2) const + { +#if defined(_WIN32) && !defined(__GNUC__) + return std::tolower(c1, std::locale()) < std::tolower(c2, std::locale()); +#else + return std::tolower(c1) < std::tolower(c2); +#endif + } + + bool CaseInsensitiveLess::cmp(const std::string& s1, const std::string& s2) + { + return std::lexicographical_compare(s1.begin(), + s1.end(), // source range + s2.begin(), + s2.end(), // dest range + NocaseCompare()); // comparison + } + + bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s2) const + { + return CaseInsensitiveLess::cmp(s1, s2); + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXStrCaseCompare.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXStrCaseCompare.h new file mode 100644 index 0000000000..8a55de0e63 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXStrCaseCompare.h @@ -0,0 +1,25 @@ +/* + * IXStrCaseCompare.h + * Author: Benjamin Sergeant + * Copyright (c) 2020 Machine Zone. All rights reserved. + */ + +#pragma once + +#include + +namespace ix +{ + struct CaseInsensitiveLess + { + // Case Insensitive compare_less binary function + struct NocaseCompare + { + bool operator()(const unsigned char& c1, const unsigned char& c2) const; + }; + + static bool cmp(const std::string& s1, const std::string& s2); + + bool operator()(const std::string& s1, const std::string& s2) const; + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXUdpSocket.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXUdpSocket.cpp new file mode 100644 index 0000000000..d12482822a --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXUdpSocket.cpp @@ -0,0 +1,126 @@ +/* + * IXUdpSocket.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2020 Machine Zone, Inc. All rights reserved. + */ + +#include "IXUdpSocket.h" + +#include "IXNetSystem.h" +#include +#include + +namespace ix +{ + UdpSocket::UdpSocket(int fd) + : _sockfd(fd) + { + ; + } + + UdpSocket::~UdpSocket() + { + close(); + } + + void UdpSocket::close() + { + if (_sockfd == -1) return; + + closeSocket(_sockfd); + _sockfd = -1; + } + + int UdpSocket::getErrno() + { + int err; + +#ifdef _WIN32 + err = WSAGetLastError(); +#else + err = errno; +#endif + + return err; + } + + bool UdpSocket::isWaitNeeded() + { + int err = getErrno(); + + if (err == EWOULDBLOCK || err == EAGAIN || err == EINPROGRESS) + { + return true; + } + + return false; + } + + void UdpSocket::closeSocket(int fd) + { +#ifdef _WIN32 + closesocket(fd); +#else + ::close(fd); +#endif + } + + bool UdpSocket::init(const std::string& host, int port, std::string& errMsg) + { + _sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (_sockfd < 0) + { + errMsg = "Could not create socket"; + return false; + } + +#ifdef _WIN32 + unsigned long nonblocking = 1; + ioctlsocket(_sockfd, FIONBIO, &nonblocking); +#else + fcntl(_sockfd, F_SETFL, O_NONBLOCK); // make socket non blocking +#endif + + memset(&_server, 0, sizeof(_server)); + _server.sin_family = AF_INET; + _server.sin_port = htons(port); + + // DNS resolution. + struct addrinfo hints, *result = nullptr; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + + int ret = getaddrinfo(host.c_str(), nullptr, &hints, &result); + if (ret != 0) + { + errMsg = strerror(UdpSocket::getErrno()); + freeaddrinfo(result); + close(); + return false; + } + + struct sockaddr_in* host_addr = (struct sockaddr_in*) result->ai_addr; + memcpy(&_server.sin_addr, &host_addr->sin_addr, sizeof(struct in_addr)); + freeaddrinfo(result); + + return true; + } + + ssize_t UdpSocket::sendto(const std::string& buffer) + { + return (ssize_t)::sendto( + _sockfd, buffer.data(), buffer.size(), 0, (struct sockaddr*) &_server, sizeof(_server)); + } + + ssize_t UdpSocket::recvfrom(char* buffer, size_t length) + { +#ifdef _WIN32 + int addressLen = (int) sizeof(_server); +#else + socklen_t addressLen = (socklen_t) sizeof(_server); +#endif + return (ssize_t)::recvfrom( + _sockfd, buffer, length, 0, (struct sockaddr*) &_server, &addressLen); + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXUdpSocket.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXUdpSocket.h new file mode 100644 index 0000000000..048f9fc296 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXUdpSocket.h @@ -0,0 +1,43 @@ +/* + * IXUdpSocket.h + * Author: Benjamin Sergeant + * Copyright (c) 2020 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include +#include +#include + +#ifdef _WIN32 +#include +typedef SSIZE_T ssize_t; +#endif + +#include "IXNetSystem.h" + +namespace ix +{ + class UdpSocket + { + public: + UdpSocket(int fd = -1); + ~UdpSocket(); + + // Virtual methods + bool init(const std::string& host, int port, std::string& errMsg); + ssize_t sendto(const std::string& buffer); + ssize_t recvfrom(char* buffer, size_t length); + + void close(); + + static int getErrno(); + static bool isWaitNeeded(); + static void closeSocket(int fd); + + private: + std::atomic _sockfd; + struct sockaddr_in _server; + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXUniquePtr.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXUniquePtr.h new file mode 100644 index 0000000000..d88ce9bd69 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXUniquePtr.h @@ -0,0 +1,18 @@ +/* + * IXUniquePtr.h + * Author: Benjamin Sergeant + * Copyright (c) 2020 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include + +namespace ix +{ + template + std::unique_ptr make_unique(Args&&... args) + { + return std::unique_ptr(new T(std::forward(args)...)); + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXUrlParser.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXUrlParser.cpp new file mode 100644 index 0000000000..9963052c25 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXUrlParser.cpp @@ -0,0 +1,395 @@ +/* + * Lightweight URL & URI parser (RFC 1738, RFC 3986) + * https://github.com/corporateshark/LUrlParser + * + * The MIT License (MIT) + * + * Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * IXUrlParser.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#include "IXUrlParser.h" + +#include +#include +#include + +namespace +{ + enum LUrlParserError + { + LUrlParserError_Ok = 0, + LUrlParserError_Uninitialized = 1, + LUrlParserError_NoUrlCharacter = 2, + LUrlParserError_InvalidSchemeName = 3, + LUrlParserError_NoDoubleSlash = 4, + LUrlParserError_NoAtSign = 5, + LUrlParserError_UnexpectedEndOfLine = 6, + LUrlParserError_NoSlash = 7, + }; + + class clParseURL + { + public: + LUrlParserError m_ErrorCode; + std::string m_Scheme; + std::string m_Host; + std::string m_Port; + std::string m_Path; + std::string m_Query; + std::string m_Fragment; + std::string m_UserName; + std::string m_Password; + + clParseURL() + : m_ErrorCode(LUrlParserError_Uninitialized) + { + } + + /// return 'true' if the parsing was successful + bool IsValid() const + { + return m_ErrorCode == LUrlParserError_Ok; + } + + /// helper to convert the port number to int, return 'true' if the port is valid (within the + /// 0..65535 range) + bool GetPort(int* OutPort) const; + + /// parse the URL + static clParseURL ParseURL(const std::string& URL); + + private: + explicit clParseURL(LUrlParserError ErrorCode) + : m_ErrorCode(ErrorCode) + { + } + }; + + static bool IsSchemeValid(const std::string& SchemeName) + { + for (auto c : SchemeName) + { + if (!isalpha(c) && c != '+' && c != '-' && c != '.') return false; + } + + return true; + } + + bool clParseURL::GetPort(int* OutPort) const + { + if (!IsValid()) + { + return false; + } + + int Port = atoi(m_Port.c_str()); + + if (Port <= 0 || Port > 65535) + { + return false; + } + + if (OutPort) + { + *OutPort = Port; + } + + return true; + } + + // based on RFC 1738 and RFC 3986 + clParseURL clParseURL::ParseURL(const std::string& URL) + { + clParseURL Result; + + const char* CurrentString = URL.c_str(); + + /* + * : + * := [a-z\+\-\.]+ + * For resiliency, programs interpreting URLs should treat upper case letters as + *equivalent to lower case in scheme names + */ + + // try to read scheme + { + const char* LocalString = strchr(CurrentString, ':'); + + if (!LocalString) + { + return clParseURL(LUrlParserError_NoUrlCharacter); + } + + // save the scheme name + Result.m_Scheme = std::string(CurrentString, LocalString - CurrentString); + + if (!IsSchemeValid(Result.m_Scheme)) + { + return clParseURL(LUrlParserError_InvalidSchemeName); + } + + // scheme should be lowercase + std::transform( + Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower); + + // skip ':' + CurrentString = LocalString + 1; + } + + /* + * //:@:/ + * any ":", "@" and "/" must be normalized + */ + + // skip "//" + if (*CurrentString++ != '/') return clParseURL(LUrlParserError_NoDoubleSlash); + if (*CurrentString++ != '/') return clParseURL(LUrlParserError_NoDoubleSlash); + + // check if the user name and password are specified + bool bHasUserName = false; + + const char* LocalString = CurrentString; + + while (*LocalString) + { + if (*LocalString == '@') + { + // user name and password are specified + bHasUserName = true; + break; + } + else if (*LocalString == '/') + { + // end of : specification + bHasUserName = false; + break; + } + + LocalString++; + } + + // user name and password + LocalString = CurrentString; + + if (bHasUserName) + { + // read user name + while (*LocalString && *LocalString != ':' && *LocalString != '@') + LocalString++; + + Result.m_UserName = std::string(CurrentString, LocalString - CurrentString); + + // proceed with the current pointer + CurrentString = LocalString; + + if (*CurrentString == ':') + { + // skip ':' + CurrentString++; + + // read password + LocalString = CurrentString; + + while (*LocalString && *LocalString != '@') + LocalString++; + + Result.m_Password = std::string(CurrentString, LocalString - CurrentString); + + CurrentString = LocalString; + } + + // skip '@' + if (*CurrentString != '@') + { + return clParseURL(LUrlParserError_NoAtSign); + } + + CurrentString++; + } + + bool bHasBracket = (*CurrentString == '['); + + // go ahead, read the host name + LocalString = CurrentString; + + while (*LocalString) + { + if (bHasBracket && *LocalString == ']') + { + // end of IPv6 address + LocalString++; + break; + } + else if (!bHasBracket && (*LocalString == ':' || *LocalString == '/')) + { + // port number is specified + break; + } + + LocalString++; + } + + Result.m_Host = std::string(CurrentString, LocalString - CurrentString); + + CurrentString = LocalString; + + // is port number specified? + if (*CurrentString == ':') + { + CurrentString++; + + // read port number + LocalString = CurrentString; + + while (*LocalString && *LocalString != '/') + LocalString++; + + Result.m_Port = std::string(CurrentString, LocalString - CurrentString); + + CurrentString = LocalString; + } + + // end of string + if (!*CurrentString) + { + Result.m_ErrorCode = LUrlParserError_Ok; + + return Result; + } + + // skip '/' + if (*CurrentString != '/') + { + return clParseURL(LUrlParserError_NoSlash); + } + + CurrentString++; + + // parse the path + LocalString = CurrentString; + + while (*LocalString && *LocalString != '#' && *LocalString != '?') + LocalString++; + + Result.m_Path = std::string(CurrentString, LocalString - CurrentString); + + CurrentString = LocalString; + + // check for query + if (*CurrentString == '?') + { + // skip '?' + CurrentString++; + + // read query + LocalString = CurrentString; + + while (*LocalString && *LocalString != '#') + LocalString++; + + Result.m_Query = std::string(CurrentString, LocalString - CurrentString); + + CurrentString = LocalString; + } + + // check for fragment + if (*CurrentString == '#') + { + // skip '#' + CurrentString++; + + // read fragment + LocalString = CurrentString; + + while (*LocalString) + LocalString++; + + Result.m_Fragment = std::string(CurrentString, LocalString - CurrentString); + } + + Result.m_ErrorCode = LUrlParserError_Ok; + + return Result; + } +} // namespace + +namespace ix +{ + bool UrlParser::parse(const std::string& url, + std::string& protocol, + std::string& host, + std::string& path, + std::string& query, + int& port) + { + clParseURL res = clParseURL::ParseURL(url); + + if (!res.IsValid()) + { + return false; + } + + protocol = res.m_Scheme; + host = res.m_Host; + path = res.m_Path; + query = res.m_Query; + + if (!res.GetPort(&port)) + { + if (protocol == "ws" || protocol == "http") + { + port = 80; + } + else if (protocol == "wss" || protocol == "https") + { + port = 443; + } + else + { + // Invalid protocol. Should be caught by regex check + // but this missing branch trigger cpplint linter. + return false; + } + } + + if (path.empty()) + { + path = "/"; + } + else if (path[0] != '/') + { + path = '/' + path; + } + + if (!query.empty()) + { + path += "?"; + path += query; + } + + return true; + } + +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXUrlParser.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXUrlParser.h new file mode 100644 index 0000000000..40e6b6e9c0 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXUrlParser.h @@ -0,0 +1,23 @@ +/* + * IXUrlParser.h + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include + +namespace ix +{ + class UrlParser + { + public: + static bool parse(const std::string& url, + std::string& protocol, + std::string& host, + std::string& path, + std::string& query, + int& port); + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXUserAgent.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXUserAgent.cpp new file mode 100644 index 0000000000..89dcb0f367 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXUserAgent.cpp @@ -0,0 +1,89 @@ +/* + * IXUserAgent.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved. + */ + +#include "IXUserAgent.h" + +#include "IXWebSocketVersion.h" +#include +#ifdef IXWEBSOCKET_USE_ZLIB +#include +#endif + +// Platform name +#if defined(_WIN32) +#define PLATFORM_NAME "windows" // Windows +#elif defined(_WIN64) +#define PLATFORM_NAME "windows" // Windows +#elif defined(__CYGWIN__) && !defined(_WIN32) +#define PLATFORM_NAME "windows" // Windows (Cygwin POSIX under Microsoft Window) +#elif defined(__ANDROID__) +#define PLATFORM_NAME "android" // Android (implies Linux, so it must come first) +#elif defined(__linux__) +#define PLATFORM_NAME "linux" // Debian, Ubuntu, Gentoo, Fedora, openSUSE, RedHat, Centos and other +#elif defined(__unix__) || !defined(__APPLE__) && defined(__MACH__) +#include +#if defined(BSD) +#define PLATFORM_NAME "bsd" // FreeBSD, NetBSD, OpenBSD, DragonFly BSD +#endif +#elif defined(__hpux) +#define PLATFORM_NAME "hp-ux" // HP-UX +#elif defined(_AIX) +#define PLATFORM_NAME "aix" // IBM AIX +#elif defined(__APPLE__) && defined(__MACH__) // Apple OSX and iOS (Darwin) +#include +#if TARGET_IPHONE_SIMULATOR == 1 +#define PLATFORM_NAME "ios" // Apple iOS +#elif TARGET_OS_IPHONE == 1 +#define PLATFORM_NAME "ios" // Apple iOS +#elif TARGET_OS_MAC == 1 +#define PLATFORM_NAME "macos" // Apple OSX +#endif +#elif defined(__sun) && defined(__SVR4) +#define PLATFORM_NAME "solaris" // Oracle Solaris, Open Indiana +#else +#define PLATFORM_NAME "unknown platform" +#endif + +// SSL +#ifdef IXWEBSOCKET_USE_MBED_TLS +#include +#elif defined(IXWEBSOCKET_USE_OPEN_SSL) +#include +#endif + +namespace ix +{ + std::string userAgent() + { + std::stringstream ss; + + // IXWebSocket Version + ss << "ixwebsocket/" << IX_WEBSOCKET_VERSION; + + // Platform + ss << " " << PLATFORM_NAME; + + // TLS +#ifdef IXWEBSOCKET_USE_TLS +#ifdef IXWEBSOCKET_USE_MBED_TLS + ss << " ssl/mbedtls " << MBEDTLS_VERSION_STRING; +#elif defined(IXWEBSOCKET_USE_OPEN_SSL) + ss << " ssl/OpenSSL " << OPENSSL_VERSION_TEXT; +#elif __APPLE__ + ss << " ssl/SecureTransport"; +#endif +#else + ss << " nossl"; +#endif + +#ifdef IXWEBSOCKET_USE_ZLIB + // Zlib version + ss << " zlib " << ZLIB_VERSION; +#endif + + return ss.str(); + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXUserAgent.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXUserAgent.h new file mode 100644 index 0000000000..367ba18ec0 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXUserAgent.h @@ -0,0 +1,14 @@ +/* + * IXUserAgent.h + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include + +namespace ix +{ + std::string userAgent(); +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXUtf8Validator.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXUtf8Validator.h new file mode 100644 index 0000000000..bc92c90aa0 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXUtf8Validator.h @@ -0,0 +1,178 @@ +/* + * The following code is adapted from code originally written by Bjoern + * Hoehrmann . See + * http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + * + * The original license: + * + * Copyright (c) 2008-2009 Bjoern Hoehrmann + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * IXUtf8Validator.h + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + * + * From websocketpp. Tiny modifications made for code style, function names etc... + */ + +#pragma once + +#include +#include + +namespace ix +{ + /// State that represents a valid utf8 input sequence + static unsigned int const utf8_accept = 0; + /// State that represents an invalid utf8 input sequence + static unsigned int const utf8_reject = 1; + + /// Lookup table for the UTF8 decode state machine + static uint8_t const utf8d[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7f + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9f + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..bf + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..df + 0xa, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // e0..ef + 0xb, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // f0..ff + 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 + 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 + 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 + 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, + 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s7..s8 + }; + + /// Decode the next byte of a UTF8 sequence + /** + * @param [out] state The decoder state to advance + * @param [out] codep The codepoint to fill in + * @param [in] byte The byte to input + * @return The ending state of the decode operation + */ + inline uint32_t decodeNextByte(uint32_t* state, uint32_t* codep, uint8_t byte) + { + uint32_t type = utf8d[byte]; + + *codep = (*state != utf8_accept) ? (byte & 0x3fu) | (*codep << 6) : (0xff >> type) & (byte); + + *state = utf8d[256 + *state * 16 + type]; + return *state; + } + + /// Provides streaming UTF8 validation functionality + class Utf8Validator + { + public: + /// Construct and initialize the validator + Utf8Validator() + : m_state(utf8_accept) + , m_codepoint(0) + { + } + + /// Advance the state of the validator with the next input byte + /** + * @param byte The byte to advance the validation state with + * @return Whether or not the byte resulted in a validation error. + */ + bool consume(uint8_t byte) + { + if (decodeNextByte(&m_state, &m_codepoint, byte) == utf8_reject) + { + return false; + } + return true; + } + + /// Advance Validator state with input from an iterator pair + /** + * @param begin Input iterator to the start of the input range + * @param end Input iterator to the end of the input range + * @return Whether or not decoding the bytes resulted in a validation error. + */ + template + bool decode(iterator_type begin, iterator_type end) + { + for (iterator_type it = begin; it != end; ++it) + { + unsigned int result = + decodeNextByte(&m_state, &m_codepoint, static_cast(*it)); + + if (result == utf8_reject) + { + return false; + } + } + return true; + } + + /// Return whether the input sequence ended on a valid utf8 codepoint + /** + * @return Whether or not the input sequence ended on a valid codepoint. + */ + bool complete() + { + return m_state == utf8_accept; + } + + /// Reset the Validator to decode another message + void reset() + { + m_state = utf8_accept; + m_codepoint = 0; + } + + private: + uint32_t m_state; + uint32_t m_codepoint; + }; + + /// Validate a UTF8 string + /** + * convenience function that creates a Validator, validates a complete string + * and returns the result. + */ + inline bool validateUtf8(std::string const& s) + { + Utf8Validator v; + if (!v.decode(s.begin(), s.end())) + { + return false; + } + return v.complete(); + } + +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXUuid.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXUuid.cpp new file mode 100644 index 0000000000..0d82ef7926 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXUuid.cpp @@ -0,0 +1,75 @@ +/* + * IXUuid.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone. All rights reserved. + */ + +/** + * Generate a random uuid similar to the uuid python module + * + * >>> import uuid + * >>> uuid.uuid4().hex + * 'bec08155b37d4050a1f3c3fa0276bf12' + * + * Code adapted from https://github.com/r-lyeh-archived/sole + */ + +#include "IXUuid.h" + +#include +#include +#include +#include + + +namespace ix +{ + class Uuid + { + public: + Uuid(); + std::string toString() const; + + private: + uint64_t _ab; + uint64_t _cd; + }; + + Uuid::Uuid() + { + static std::random_device rd; + static std::uniform_int_distribution dist(0, (uint64_t)(~0)); + + _ab = dist(rd); + _cd = dist(rd); + + _ab = (_ab & 0xFFFFFFFFFFFF0FFFULL) | 0x0000000000004000ULL; + _cd = (_cd & 0x3FFFFFFFFFFFFFFFULL) | 0x8000000000000000ULL; + } + + std::string Uuid::toString() const + { + std::stringstream ss; + ss << std::hex << std::nouppercase << std::setfill('0'); + + uint32_t a = (_ab >> 32); + uint32_t b = (_ab & 0xFFFFFFFF); + uint32_t c = (_cd >> 32); + uint32_t d = (_cd & 0xFFFFFFFF); + + ss << std::setw(8) << (a); + ss << std::setw(4) << (b >> 16); + ss << std::setw(4) << (b & 0xFFFF); + ss << std::setw(4) << (c >> 16); + ss << std::setw(4) << (c & 0xFFFF); + ss << std::setw(8) << d; + + return ss.str(); + } + + std::string uuid4() + { + Uuid id; + return id.toString(); + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXUuid.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXUuid.h new file mode 100644 index 0000000000..1000436697 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXUuid.h @@ -0,0 +1,17 @@ +/* + * IXUuid.h + * Author: Benjamin Sergeant + * Copyright (c) 2017 Machine Zone. All rights reserved. + */ +#pragma once + +#include + +namespace ix +{ + /** + * Generate a random uuid + */ + std::string uuid4(); + +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocket.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocket.cpp new file mode 100644 index 0000000000..e272521a73 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocket.cpp @@ -0,0 +1,596 @@ +/* + * IXWebSocket.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved. + */ + +#include "IXWebSocket.h" + +#include "IXExponentialBackoff.h" +#include "IXSetThreadName.h" +#include "IXUniquePtr.h" +#include "IXUtf8Validator.h" +#include "IXWebSocketHandshake.h" +#include +#include + + +namespace +{ + const std::string emptyMsg; +} // namespace + + +namespace ix +{ + OnTrafficTrackerCallback WebSocket::_onTrafficTrackerCallback = nullptr; + const int WebSocket::kDefaultHandShakeTimeoutSecs(60); + const int WebSocket::kDefaultPingIntervalSecs(-1); + const bool WebSocket::kDefaultEnablePong(true); + const uint32_t WebSocket::kDefaultMaxWaitBetweenReconnectionRetries(10 * 1000); // 10s + const uint32_t WebSocket::kDefaultMinWaitBetweenReconnectionRetries(1); // 1 ms + + WebSocket::WebSocket() + : _onMessageCallback(OnMessageCallback()) + , _stop(false) + , _automaticReconnection(true) + , _maxWaitBetweenReconnectionRetries(kDefaultMaxWaitBetweenReconnectionRetries) + , _minWaitBetweenReconnectionRetries(kDefaultMinWaitBetweenReconnectionRetries) + , _handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs) + , _enablePong(kDefaultEnablePong) + , _pingIntervalSecs(kDefaultPingIntervalSecs) + { + _ws.setOnCloseCallback( + [this](uint16_t code, const std::string& reason, size_t wireSize, bool remote) { + _onMessageCallback( + ix::make_unique(WebSocketMessageType::Close, + emptyMsg, + wireSize, + WebSocketErrorInfo(), + WebSocketOpenInfo(), + WebSocketCloseInfo(code, reason, remote))); + }); + } + + WebSocket::~WebSocket() + { + stop(); + _ws.setOnCloseCallback(nullptr); + } + + void WebSocket::setUrl(const std::string& url) + { + std::lock_guard lock(_configMutex); + _url = url; + } + + void WebSocket::setHandshakeTimeout(int handshakeTimeoutSecs) + { + _handshakeTimeoutSecs = handshakeTimeoutSecs; + } + + void WebSocket::setExtraHeaders(const WebSocketHttpHeaders& headers) + { + std::lock_guard lock(_configMutex); + _extraHeaders = headers; + } + + const std::string WebSocket::getUrl() const + { + std::lock_guard lock(_configMutex); + return _url; + } + + void WebSocket::setPerMessageDeflateOptions( + const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions) + { + std::lock_guard lock(_configMutex); + _perMessageDeflateOptions = perMessageDeflateOptions; + } + + void WebSocket::setTLSOptions(const SocketTLSOptions& socketTLSOptions) + { + std::lock_guard lock(_configMutex); + _socketTLSOptions = socketTLSOptions; + } + + const WebSocketPerMessageDeflateOptions WebSocket::getPerMessageDeflateOptions() const + { + std::lock_guard lock(_configMutex); + return _perMessageDeflateOptions; + } + + void WebSocket::setPingInterval(int pingIntervalSecs) + { + std::lock_guard lock(_configMutex); + _pingIntervalSecs = pingIntervalSecs; + } + + int WebSocket::getPingInterval() const + { + std::lock_guard lock(_configMutex); + return _pingIntervalSecs; + } + + void WebSocket::enablePong() + { + std::lock_guard lock(_configMutex); + _enablePong = true; + } + + void WebSocket::disablePong() + { + std::lock_guard lock(_configMutex); + _enablePong = false; + } + + void WebSocket::enablePerMessageDeflate() + { + std::lock_guard lock(_configMutex); + WebSocketPerMessageDeflateOptions perMessageDeflateOptions(true); + _perMessageDeflateOptions = perMessageDeflateOptions; + } + + void WebSocket::disablePerMessageDeflate() + { + std::lock_guard lock(_configMutex); + WebSocketPerMessageDeflateOptions perMessageDeflateOptions(false); + _perMessageDeflateOptions = perMessageDeflateOptions; + } + + void WebSocket::setMaxWaitBetweenReconnectionRetries(uint32_t maxWaitBetweenReconnectionRetries) + { + std::lock_guard lock(_configMutex); + _maxWaitBetweenReconnectionRetries = maxWaitBetweenReconnectionRetries; + } + + void WebSocket::setMinWaitBetweenReconnectionRetries(uint32_t minWaitBetweenReconnectionRetries) + { + std::lock_guard lock(_configMutex); + _minWaitBetweenReconnectionRetries = minWaitBetweenReconnectionRetries; + } + + uint32_t WebSocket::getMaxWaitBetweenReconnectionRetries() const + { + std::lock_guard lock(_configMutex); + return _maxWaitBetweenReconnectionRetries; + } + + uint32_t WebSocket::getMinWaitBetweenReconnectionRetries() const + { + std::lock_guard lock(_configMutex); + return _minWaitBetweenReconnectionRetries; + } + + void WebSocket::start() + { + if (_thread.joinable()) return; // we've already been started + + _thread = std::thread(&WebSocket::run, this); + } + + void WebSocket::stop(uint16_t code, const std::string& reason) + { + close(code, reason); + + if (_thread.joinable()) + { + // wait until working thread will exit + // it will exit after close operation is finished + _stop = true; + _sleepCondition.notify_one(); + _thread.join(); + _stop = false; + } + } + + WebSocketInitResult WebSocket::connect(int timeoutSecs) + { + { + std::lock_guard lock(_configMutex); + _ws.configure( + _perMessageDeflateOptions, _socketTLSOptions, _enablePong, _pingIntervalSecs); + } + + WebSocketHttpHeaders headers(_extraHeaders); + std::string subProtocolsHeader; + auto subProtocols = getSubProtocols(); + if (!subProtocols.empty()) + { + // + // Sub Protocol strings are comma separated. + // Python code to do that is: + // >>> ','.join(['json', 'msgpack']) + // 'json,msgpack' + // + int i = 0; + for (auto subProtocol : subProtocols) + { + if (i++ != 0) + { + subProtocolsHeader += ","; + } + subProtocolsHeader += subProtocol; + } + headers["Sec-WebSocket-Protocol"] = subProtocolsHeader; + } + + WebSocketInitResult status = _ws.connectToUrl(_url, headers, timeoutSecs); + if (!status.success) + { + return status; + } + + _onMessageCallback(ix::make_unique( + WebSocketMessageType::Open, + emptyMsg, + 0, + WebSocketErrorInfo(), + WebSocketOpenInfo(status.uri, status.headers, status.protocol), + WebSocketCloseInfo())); + + if (_pingIntervalSecs > 0) + { + // Send a heart beat right away + _ws.sendHeartBeat(); + } + + return status; + } + + WebSocketInitResult WebSocket::connectToSocket(std::unique_ptr socket, + int timeoutSecs, + bool enablePerMessageDeflate) + { + { + std::lock_guard lock(_configMutex); + _ws.configure( + _perMessageDeflateOptions, _socketTLSOptions, _enablePong, _pingIntervalSecs); + } + + WebSocketInitResult status = + _ws.connectToSocket(std::move(socket), timeoutSecs, enablePerMessageDeflate); + if (!status.success) + { + return status; + } + + _onMessageCallback( + ix::make_unique(WebSocketMessageType::Open, + emptyMsg, + 0, + WebSocketErrorInfo(), + WebSocketOpenInfo(status.uri, status.headers), + WebSocketCloseInfo())); + + if (_pingIntervalSecs > 0) + { + // Send a heart beat right away + _ws.sendHeartBeat(); + } + + return status; + } + + bool WebSocket::isConnected() const + { + return getReadyState() == ReadyState::Open; + } + + bool WebSocket::isClosing() const + { + return getReadyState() == ReadyState::Closing; + } + + void WebSocket::close(uint16_t code, const std::string& reason) + { + _ws.close(code, reason); + } + + void WebSocket::checkConnection(bool firstConnectionAttempt) + { + using millis = std::chrono::duration; + + uint32_t retries = 0; + millis duration(0); + + // Try to connect perpertually + while (true) + { + if (isConnected() || isClosing() || _stop) + { + break; + } + + if (!firstConnectionAttempt && !_automaticReconnection) + { + // Do not attempt to reconnect + break; + } + + firstConnectionAttempt = false; + + // Only sleep if we are retrying + if (duration.count() > 0) + { + std::unique_lock lock(_sleepMutex); + _sleepCondition.wait_for(lock, duration); + } + + if (_stop) + { + break; + } + + // Try to connect synchronously + ix::WebSocketInitResult status = connect(_handshakeTimeoutSecs); + + if (!status.success) + { + WebSocketErrorInfo connectErr; + + if (_automaticReconnection) + { + duration = + millis(calculateRetryWaitMilliseconds(retries++, + _maxWaitBetweenReconnectionRetries, + _minWaitBetweenReconnectionRetries)); + + connectErr.wait_time = duration.count(); + connectErr.retries = retries; + } + + connectErr.reason = status.errorStr; + connectErr.http_status = status.http_status; + + _onMessageCallback(ix::make_unique(WebSocketMessageType::Error, + emptyMsg, + 0, + connectErr, + WebSocketOpenInfo(), + WebSocketCloseInfo())); + } + } + } + + void WebSocket::run() + { + setThreadName(getUrl()); + + bool firstConnectionAttempt = true; + + while (true) + { + // 1. Make sure we are always connected + checkConnection(firstConnectionAttempt); + + firstConnectionAttempt = false; + + // if here we are closed then checkConnection was not able to connect + if (getReadyState() == ReadyState::Closed) + { + break; + } + + // We can avoid to poll if we want to stop and are not closing + if (_stop && !isClosing()) break; + + // 2. Poll to see if there's any new data available + WebSocketTransport::PollResult pollResult = _ws.poll(); + + // 3. Dispatch the incoming messages + _ws.dispatch( + pollResult, + [this](const std::string& msg, + size_t wireSize, + bool decompressionError, + WebSocketTransport::MessageKind messageKind) { + WebSocketMessageType webSocketMessageType{WebSocketMessageType::Error}; + switch (messageKind) + { + case WebSocketTransport::MessageKind::MSG_TEXT: + case WebSocketTransport::MessageKind::MSG_BINARY: + { + webSocketMessageType = WebSocketMessageType::Message; + } + break; + + case WebSocketTransport::MessageKind::PING: + { + webSocketMessageType = WebSocketMessageType::Ping; + } + break; + + case WebSocketTransport::MessageKind::PONG: + { + webSocketMessageType = WebSocketMessageType::Pong; + } + break; + + case WebSocketTransport::MessageKind::FRAGMENT: + { + webSocketMessageType = WebSocketMessageType::Fragment; + } + break; + } + + WebSocketErrorInfo webSocketErrorInfo; + webSocketErrorInfo.decompressionError = decompressionError; + + bool binary = messageKind == WebSocketTransport::MessageKind::MSG_BINARY; + + _onMessageCallback(ix::make_unique(webSocketMessageType, + msg, + wireSize, + webSocketErrorInfo, + WebSocketOpenInfo(), + WebSocketCloseInfo(), + binary)); + + WebSocket::invokeTrafficTrackerCallback(wireSize, true); + }); + } + } + + void WebSocket::setOnMessageCallback(const OnMessageCallback& callback) + { + _onMessageCallback = callback; + } + + bool WebSocket::isOnMessageCallbackRegistered() const + { + return _onMessageCallback != nullptr; + } + + void WebSocket::setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback) + { + _onTrafficTrackerCallback = callback; + } + + void WebSocket::resetTrafficTrackerCallback() + { + setTrafficTrackerCallback(nullptr); + } + + void WebSocket::invokeTrafficTrackerCallback(size_t size, bool incoming) + { + if (_onTrafficTrackerCallback) + { + _onTrafficTrackerCallback(size, incoming); + } + } + + WebSocketSendInfo WebSocket::send(const std::string& data, + bool binary, + const OnProgressCallback& onProgressCallback) + { + return (binary) ? sendBinary(data, onProgressCallback) : sendText(data, onProgressCallback); + } + + WebSocketSendInfo WebSocket::sendBinary(const std::string& text, + const OnProgressCallback& onProgressCallback) + { + return sendMessage(text, SendMessageKind::Binary, onProgressCallback); + } + + WebSocketSendInfo WebSocket::sendText(const std::string& text, + const OnProgressCallback& onProgressCallback) + { + if (!validateUtf8(text)) + { + close(WebSocketCloseConstants::kInvalidFramePayloadData, + WebSocketCloseConstants::kInvalidFramePayloadDataMessage); + return false; + } + return sendMessage(text, SendMessageKind::Text, onProgressCallback); + } + + WebSocketSendInfo WebSocket::ping(const std::string& text) + { + // Standard limit ping message size + constexpr size_t pingMaxPayloadSize = 125; + if (text.size() > pingMaxPayloadSize) return WebSocketSendInfo(false); + + return sendMessage(text, SendMessageKind::Ping); + } + + WebSocketSendInfo WebSocket::sendMessage(const std::string& text, + SendMessageKind sendMessageKind, + const OnProgressCallback& onProgressCallback) + { + if (!isConnected()) return WebSocketSendInfo(false); + + // + // It is OK to read and write on the same socket in 2 different threads. + // https://stackoverflow.com/questions/1981372/are-parallel-calls-to-send-recv-on-the-same-socket-valid + // + // This makes it so that messages are sent right away, and we dont need + // a timeout while we poll to keep wake ups to a minimum (which helps + // with battery life), and use the system select call to notify us when + // incoming messages are arriving / there's data to be received. + // + std::lock_guard lock(_writeMutex); + WebSocketSendInfo webSocketSendInfo; + + switch (sendMessageKind) + { + case SendMessageKind::Text: + { + webSocketSendInfo = _ws.sendText(text, onProgressCallback); + } + break; + + case SendMessageKind::Binary: + { + webSocketSendInfo = _ws.sendBinary(text, onProgressCallback); + } + break; + + case SendMessageKind::Ping: + { + webSocketSendInfo = _ws.sendPing(text); + } + break; + } + + WebSocket::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false); + + return webSocketSendInfo; + } + + ReadyState WebSocket::getReadyState() const + { + switch (_ws.getReadyState()) + { + case ix::WebSocketTransport::ReadyState::OPEN: return ReadyState::Open; + case ix::WebSocketTransport::ReadyState::CONNECTING: return ReadyState::Connecting; + case ix::WebSocketTransport::ReadyState::CLOSING: return ReadyState::Closing; + case ix::WebSocketTransport::ReadyState::CLOSED: return ReadyState::Closed; + default: return ReadyState::Closed; + } + } + + std::string WebSocket::readyStateToString(ReadyState readyState) + { + switch (readyState) + { + case ReadyState::Open: return "OPEN"; + case ReadyState::Connecting: return "CONNECTING"; + case ReadyState::Closing: return "CLOSING"; + case ReadyState::Closed: return "CLOSED"; + default: return "UNKNOWN"; + } + } + + void WebSocket::enableAutomaticReconnection() + { + _automaticReconnection = true; + } + + void WebSocket::disableAutomaticReconnection() + { + _automaticReconnection = false; + } + + bool WebSocket::isAutomaticReconnectionEnabled() const + { + return _automaticReconnection; + } + + size_t WebSocket::bufferedAmount() const + { + return _ws.bufferedAmount(); + } + + void WebSocket::addSubProtocol(const std::string& subProtocol) + { + std::lock_guard lock(_configMutex); + _subProtocols.push_back(subProtocol); + } + + const std::vector& WebSocket::getSubProtocols() + { + std::lock_guard lock(_configMutex); + return _subProtocols; + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocket.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocket.h new file mode 100644 index 0000000000..7cfe0088e2 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocket.h @@ -0,0 +1,171 @@ +/* + * IXWebSocket.h + * Author: Benjamin Sergeant + * Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved. + * + * WebSocket RFC + * https://tools.ietf.org/html/rfc6455 + */ + +#pragma once + +#include "IXProgressCallback.h" +#include "IXSocketTLSOptions.h" +#include "IXWebSocketCloseConstants.h" +#include "IXWebSocketErrorInfo.h" +#include "IXWebSocketHttpHeaders.h" +#include "IXWebSocketMessage.h" +#include "IXWebSocketPerMessageDeflateOptions.h" +#include "IXWebSocketSendInfo.h" +#include "IXWebSocketTransport.h" +#include +#include +#include +#include +#include + +namespace ix +{ + // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants + enum class ReadyState + { + Connecting = 0, + Open = 1, + Closing = 2, + Closed = 3 + }; + + using OnMessageCallback = std::function; + + using OnTrafficTrackerCallback = std::function; + + class WebSocket + { + public: + WebSocket(); + ~WebSocket(); + + void setUrl(const std::string& url); + + // send extra headers in client handshake request + void setExtraHeaders(const WebSocketHttpHeaders& headers); + void setPerMessageDeflateOptions( + const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions); + void setTLSOptions(const SocketTLSOptions& socketTLSOptions); + void setPingInterval(int pingIntervalSecs); + void enablePong(); + void disablePong(); + void enablePerMessageDeflate(); + void disablePerMessageDeflate(); + void addSubProtocol(const std::string& subProtocol); + void setHandshakeTimeout(int handshakeTimeoutSecs); + + // Run asynchronously, by calling start and stop. + void start(); + + // stop is synchronous + void stop(uint16_t code = WebSocketCloseConstants::kNormalClosureCode, + const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage); + + // Run in blocking mode, by connecting first manually, and then calling run. + WebSocketInitResult connect(int timeoutSecs); + void run(); + + // send is in text mode by default + WebSocketSendInfo send(const std::string& data, + bool binary = false, + const OnProgressCallback& onProgressCallback = nullptr); + WebSocketSendInfo sendBinary(const std::string& text, + const OnProgressCallback& onProgressCallback = nullptr); + WebSocketSendInfo sendText(const std::string& text, + const OnProgressCallback& onProgressCallback = nullptr); + WebSocketSendInfo ping(const std::string& text); + + void close(uint16_t code = WebSocketCloseConstants::kNormalClosureCode, + const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage); + + void setOnMessageCallback(const OnMessageCallback& callback); + bool isOnMessageCallbackRegistered() const; + static void setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback); + static void resetTrafficTrackerCallback(); + + ReadyState getReadyState() const; + static std::string readyStateToString(ReadyState readyState); + + const std::string getUrl() const; + const WebSocketPerMessageDeflateOptions getPerMessageDeflateOptions() const; + int getPingInterval() const; + size_t bufferedAmount() const; + + void enableAutomaticReconnection(); + void disableAutomaticReconnection(); + bool isAutomaticReconnectionEnabled() const; + void setMaxWaitBetweenReconnectionRetries(uint32_t maxWaitBetweenReconnectionRetries); + void setMinWaitBetweenReconnectionRetries(uint32_t minWaitBetweenReconnectionRetries); + uint32_t getMaxWaitBetweenReconnectionRetries() const; + uint32_t getMinWaitBetweenReconnectionRetries() const; + const std::vector& getSubProtocols(); + + private: + WebSocketSendInfo sendMessage(const std::string& text, + SendMessageKind sendMessageKind, + const OnProgressCallback& callback = nullptr); + + bool isConnected() const; + bool isClosing() const; + void checkConnection(bool firstConnectionAttempt); + static void invokeTrafficTrackerCallback(size_t size, bool incoming); + + // Server + WebSocketInitResult connectToSocket(std::unique_ptr, + int timeoutSecs, + bool enablePerMessageDeflate); + + WebSocketTransport _ws; + + std::string _url; + WebSocketHttpHeaders _extraHeaders; + + WebSocketPerMessageDeflateOptions _perMessageDeflateOptions; + + SocketTLSOptions _socketTLSOptions; + + mutable std::mutex _configMutex; // protect all config variables access + + OnMessageCallback _onMessageCallback; + static OnTrafficTrackerCallback _onTrafficTrackerCallback; + + std::atomic _stop; + std::thread _thread; + std::mutex _writeMutex; + + // Automatic reconnection + std::atomic _automaticReconnection; + static const uint32_t kDefaultMaxWaitBetweenReconnectionRetries; + static const uint32_t kDefaultMinWaitBetweenReconnectionRetries; + uint32_t _maxWaitBetweenReconnectionRetries; + uint32_t _minWaitBetweenReconnectionRetries; + + // Make the sleeping in the automatic reconnection cancellable + std::mutex _sleepMutex; + std::condition_variable _sleepCondition; + + std::atomic _handshakeTimeoutSecs; + static const int kDefaultHandShakeTimeoutSecs; + + // enable or disable PONG frame response to received PING frame + bool _enablePong; + static const bool kDefaultEnablePong; + + // Optional ping and pong timeout + int _pingIntervalSecs; + int _pingTimeoutSecs; + static const int kDefaultPingIntervalSecs; + static const int kDefaultPingTimeoutSecs; + + // Subprotocols + std::vector _subProtocols; + + friend class WebSocketServer; + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketCloseConstants.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketCloseConstants.cpp new file mode 100644 index 0000000000..d8ba57f6d3 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketCloseConstants.cpp @@ -0,0 +1,36 @@ +/* + * IXWebSocketCloseConstants.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#include "IXWebSocketCloseConstants.h" + +namespace ix +{ + const uint16_t WebSocketCloseConstants::kNormalClosureCode(1000); + const uint16_t WebSocketCloseConstants::kInternalErrorCode(1011); + const uint16_t WebSocketCloseConstants::kAbnormalCloseCode(1006); + const uint16_t WebSocketCloseConstants::kInvalidFramePayloadData(1007); + const uint16_t WebSocketCloseConstants::kProtocolErrorCode(1002); + const uint16_t WebSocketCloseConstants::kNoStatusCodeErrorCode(1005); + + const std::string WebSocketCloseConstants::kNormalClosureMessage("Normal closure"); + const std::string WebSocketCloseConstants::kInternalErrorMessage("Internal error"); + const std::string WebSocketCloseConstants::kAbnormalCloseMessage("Abnormal closure"); + const std::string WebSocketCloseConstants::kPingTimeoutMessage("Ping timeout"); + const std::string WebSocketCloseConstants::kProtocolErrorMessage("Protocol error"); + const std::string WebSocketCloseConstants::kNoStatusCodeErrorMessage("No status code"); + const std::string WebSocketCloseConstants::kProtocolErrorReservedBitUsed("Reserved bit used"); + const std::string WebSocketCloseConstants::kProtocolErrorPingPayloadOversized( + "Ping reason control frame with payload length > 125 octets"); + const std::string WebSocketCloseConstants::kProtocolErrorCodeControlMessageFragmented( + "Control message fragmented"); + const std::string WebSocketCloseConstants::kProtocolErrorCodeDataOpcodeOutOfSequence( + "Fragmentation: data message out of sequence"); + const std::string WebSocketCloseConstants::kProtocolErrorCodeContinuationOpCodeOutOfSequence( + "Fragmentation: continuation opcode out of sequence"); + const std::string WebSocketCloseConstants::kInvalidFramePayloadDataMessage( + "Invalid frame payload data"); + const std::string WebSocketCloseConstants::kInvalidCloseCodeMessage("Invalid close code"); +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketCloseConstants.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketCloseConstants.h new file mode 100644 index 0000000000..145777b9e1 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketCloseConstants.h @@ -0,0 +1,37 @@ +/* + * IXWebSocketCloseConstants.h + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include +#include + +namespace ix +{ + struct WebSocketCloseConstants + { + static const uint16_t kNormalClosureCode; + static const uint16_t kInternalErrorCode; + static const uint16_t kAbnormalCloseCode; + static const uint16_t kProtocolErrorCode; + static const uint16_t kNoStatusCodeErrorCode; + static const uint16_t kInvalidFramePayloadData; + + static const std::string kNormalClosureMessage; + static const std::string kInternalErrorMessage; + static const std::string kAbnormalCloseMessage; + static const std::string kPingTimeoutMessage; + static const std::string kProtocolErrorMessage; + static const std::string kNoStatusCodeErrorMessage; + static const std::string kProtocolErrorReservedBitUsed; + static const std::string kProtocolErrorPingPayloadOversized; + static const std::string kProtocolErrorCodeControlMessageFragmented; + static const std::string kProtocolErrorCodeDataOpcodeOutOfSequence; + static const std::string kProtocolErrorCodeContinuationOpCodeOutOfSequence; + static const std::string kInvalidFramePayloadDataMessage; + static const std::string kInvalidCloseCodeMessage; + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketCloseInfo.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketCloseInfo.h new file mode 100644 index 0000000000..409e74fdfd --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketCloseInfo.h @@ -0,0 +1,28 @@ +/* + * IXWebSocketCloseInfo.h + * Author: Benjamin Sergeant + * Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include +#include + +namespace ix +{ + struct WebSocketCloseInfo + { + uint16_t code; + std::string reason; + bool remote; + + WebSocketCloseInfo(uint16_t c = 0, const std::string& r = std::string(), bool rem = false) + : code(c) + , reason(r) + , remote(rem) + { + ; + } + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketErrorInfo.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketErrorInfo.h new file mode 100644 index 0000000000..16f3215ee9 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketErrorInfo.h @@ -0,0 +1,22 @@ +/* + * IXWebSocketErrorInfo.h + * Author: Benjamin Sergeant + * Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include +#include + +namespace ix +{ + struct WebSocketErrorInfo + { + uint32_t retries = 0; + double wait_time = 0; + int http_status = 0; + std::string reason; + bool decompressionError = false; + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketHandshake.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketHandshake.cpp new file mode 100644 index 0000000000..9a8de059e3 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketHandshake.cpp @@ -0,0 +1,368 @@ +/* + * IXWebSocketHandshake.h + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#include "IXWebSocketHandshake.h" + +#include "IXHttp.h" +#include "IXSocketConnect.h" +#include "IXStrCaseCompare.h" +#include "IXUrlParser.h" +#include "IXUserAgent.h" +#include "IXWebSocketHandshakeKeyGen.h" +#include +#include +#include +#include + + +namespace ix +{ + WebSocketHandshake::WebSocketHandshake( + std::atomic& requestInitCancellation, + std::unique_ptr& socket, + WebSocketPerMessageDeflatePtr& perMessageDeflate, + WebSocketPerMessageDeflateOptions& perMessageDeflateOptions, + std::atomic& enablePerMessageDeflate) + : _requestInitCancellation(requestInitCancellation) + , _socket(socket) + , _perMessageDeflate(perMessageDeflate) + , _perMessageDeflateOptions(perMessageDeflateOptions) + , _enablePerMessageDeflate(enablePerMessageDeflate) + { + } + + bool WebSocketHandshake::insensitiveStringCompare(const std::string& a, const std::string& b) + { + return CaseInsensitiveLess::cmp(a, b) == 0; + } + + std::string WebSocketHandshake::genRandomString(const int len) + { + std::string alphanum = "0123456789" + "ABCDEFGH" + "abcdefgh"; + + std::random_device r; + std::default_random_engine e1(r()); + std::uniform_int_distribution dist(0, (int) alphanum.size() - 1); + + std::string s; + s.resize(len); + + for (int i = 0; i < len; ++i) + { + int x = dist(e1); + s[i] = alphanum[x]; + } + + return s; + } + + WebSocketInitResult WebSocketHandshake::sendErrorResponse(int code, const std::string& reason) + { + std::stringstream ss; + ss << "HTTP/1.1 "; + ss << code; + ss << " "; + ss << reason; + ss << "\r\n"; + ss << "Server: " << userAgent() << "\r\n"; + + // Socket write can only be cancelled through a timeout here, not manually. + static std::atomic requestInitCancellation(false); + auto isCancellationRequested = + makeCancellationRequestWithTimeout(1, requestInitCancellation); + + if (!_socket->writeBytes(ss.str(), isCancellationRequested)) + { + return WebSocketInitResult(false, 500, "Timed out while sending error response"); + } + + return WebSocketInitResult(false, code, reason); + } + + WebSocketInitResult WebSocketHandshake::clientHandshake( + const std::string& url, + const WebSocketHttpHeaders& extraHeaders, + const std::string& host, + const std::string& path, + int port, + int timeoutSecs) + { + _requestInitCancellation = false; + + auto isCancellationRequested = + makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation); + + std::string errMsg; + bool success = _socket->connect(host, port, errMsg, isCancellationRequested); + if (!success) + { + std::stringstream ss; + ss << "Unable to connect to " << host << " on port " << port << ", error: " << errMsg; + return WebSocketInitResult(false, 0, ss.str()); + } + + // + // Generate a random 24 bytes string which looks like it is base64 encoded + // y3JJHMbDL1EzLkh9GBhXDw== + // 0cb3Vd9HkbpVVumoS3Noka== + // + // See https://stackoverflow.com/questions/18265128/what-is-sec-websocket-key-for + // + std::string secWebSocketKey = genRandomString(22); + secWebSocketKey += "=="; + + std::stringstream ss; + ss << "GET " << path << " HTTP/1.1\r\n"; + ss << "Host: " << host << ":" << port << "\r\n"; + ss << "Upgrade: websocket\r\n"; + ss << "Connection: Upgrade\r\n"; + ss << "Sec-WebSocket-Version: 13\r\n"; + ss << "Sec-WebSocket-Key: " << secWebSocketKey << "\r\n"; + + // User-Agent can be customized by users + if (extraHeaders.find("User-Agent") == extraHeaders.end()) + { + ss << "User-Agent: " << userAgent() << "\r\n"; + } + + for (auto& it : extraHeaders) + { + ss << it.first << ": " << it.second << "\r\n"; + } + + if (_enablePerMessageDeflate) + { + ss << _perMessageDeflateOptions.generateHeader(); + } + + ss << "\r\n"; + + if (!_socket->writeBytes(ss.str(), isCancellationRequested)) + { + return WebSocketInitResult( + false, 0, std::string("Failed sending GET request to ") + url); + } + + // Read HTTP status line + auto lineResult = _socket->readLine(isCancellationRequested); + auto lineValid = lineResult.first; + auto line = lineResult.second; + + if (!lineValid) + { + return WebSocketInitResult( + false, 0, std::string("Failed reading HTTP status line from ") + url); + } + + // Validate status + auto statusLine = Http::parseStatusLine(line); + std::string httpVersion = statusLine.first; + int status = statusLine.second; + + // HTTP/1.0 is too old. + if (httpVersion != "HTTP/1.1") + { + std::stringstream ss; + ss << "Expecting HTTP/1.1, got " << httpVersion << ". " + << "Rejecting connection to " << url << ", status: " << status + << ", HTTP Status line: " << line; + return WebSocketInitResult(false, status, ss.str()); + } + + auto result = parseHttpHeaders(_socket, isCancellationRequested); + auto headersValid = result.first; + auto headers = result.second; + + if (!headersValid) + { + return WebSocketInitResult(false, status, "Error parsing HTTP headers"); + } + + // We want an 101 HTTP status for websocket, otherwise it could be + // a redirection (like 301) + if (status != 101) + { + std::stringstream ss; + ss << "Expecting status 101 (Switching Protocol), got " << status + << " status connecting to " << url << ", HTTP Status line: " << line; + + return WebSocketInitResult(false, status, ss.str(), headers, path); + } + + // Check the presence of the connection field + if (headers.find("connection") == headers.end()) + { + std::string errorMsg("Missing connection value"); + return WebSocketInitResult(false, status, errorMsg); + } + + // Check the value of the connection field + // Some websocket servers (Go/Gorilla?) send lowercase values for the + // connection header, so do a case insensitive comparison + // + // See https://github.com/apache/thrift/commit/7c4bdf9914fcba6c89e0f69ae48b9675578f084a + // + if (!insensitiveStringCompare(headers["connection"], "Upgrade")) + { + std::stringstream ss; + ss << "Invalid connection value: " << headers["connection"]; + return WebSocketInitResult(false, status, ss.str()); + } + + char output[29] = {}; + WebSocketHandshakeKeyGen::generate(secWebSocketKey, output); + if (std::string(output) != headers["sec-websocket-accept"]) + { + std::string errorMsg("Invalid Sec-WebSocket-Accept value"); + return WebSocketInitResult(false, status, errorMsg); + } + + if (_enablePerMessageDeflate) + { + // Parse the server response. Does it support deflate ? + std::string header = headers["sec-websocket-extensions"]; + WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(header); + + // If the server does not support that extension, disable it. + if (!webSocketPerMessageDeflateOptions.enabled()) + { + _enablePerMessageDeflate = false; + } + // Otherwise try to initialize the deflate engine (zlib) + else if (!_perMessageDeflate->init(webSocketPerMessageDeflateOptions)) + { + return WebSocketInitResult( + false, 0, "Failed to initialize per message deflate engine"); + } + } + + return WebSocketInitResult(true, status, "", headers, path); + } + + WebSocketInitResult WebSocketHandshake::serverHandshake(int timeoutSecs, + bool enablePerMessageDeflate) + { + _requestInitCancellation = false; + + auto isCancellationRequested = + makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation); + + // Read first line + auto lineResult = _socket->readLine(isCancellationRequested); + auto lineValid = lineResult.first; + auto line = lineResult.second; + + if (!lineValid) + { + return sendErrorResponse(400, "Error reading HTTP request line"); + } + + // Validate request line (GET /foo HTTP/1.1\r\n) + auto requestLine = Http::parseRequestLine(line); + auto method = std::get<0>(requestLine); + auto uri = std::get<1>(requestLine); + auto httpVersion = std::get<2>(requestLine); + + if (method != "GET") + { + return sendErrorResponse(400, "Invalid HTTP method, need GET, got " + method); + } + + if (httpVersion != "HTTP/1.1") + { + return sendErrorResponse(400, + "Invalid HTTP version, need HTTP/1.1, got: " + httpVersion); + } + + // Retrieve and validate HTTP headers + auto result = parseHttpHeaders(_socket, isCancellationRequested); + auto headersValid = result.first; + auto headers = result.second; + + if (!headersValid) + { + return sendErrorResponse(400, "Error parsing HTTP headers"); + } + + if (headers.find("sec-websocket-key") == headers.end()) + { + return sendErrorResponse(400, "Missing Sec-WebSocket-Key value"); + } + + if (headers.find("upgrade") == headers.end()) + { + return sendErrorResponse(400, "Missing Upgrade header"); + } + + if (!insensitiveStringCompare(headers["upgrade"], "WebSocket") && + headers["Upgrade"] != "keep-alive, Upgrade") // special case for firefox + { + return sendErrorResponse(400, + "Invalid Upgrade header, " + "need WebSocket, got " + + headers["upgrade"]); + } + + if (headers.find("sec-websocket-version") == headers.end()) + { + return sendErrorResponse(400, "Missing Sec-WebSocket-Version value"); + } + + { + std::stringstream ss; + ss << headers["sec-websocket-version"]; + int version; + ss >> version; + + if (version != 13) + { + return sendErrorResponse(400, + "Invalid Sec-WebSocket-Version, " + "need 13, got " + + ss.str()); + } + } + + char output[29] = {}; + WebSocketHandshakeKeyGen::generate(headers["sec-websocket-key"], output); + + std::stringstream ss; + ss << "HTTP/1.1 101 Switching Protocols\r\n"; + ss << "Sec-WebSocket-Accept: " << std::string(output) << "\r\n"; + ss << "Upgrade: websocket\r\n"; + ss << "Connection: Upgrade\r\n"; + ss << "Server: " << userAgent() << "\r\n"; + + // Parse the client headers. Does it support deflate ? + std::string header = headers["sec-websocket-extensions"]; + WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(header); + + // If the client has requested that extension, + if (webSocketPerMessageDeflateOptions.enabled() && enablePerMessageDeflate) + { + _enablePerMessageDeflate = true; + + if (!_perMessageDeflate->init(webSocketPerMessageDeflateOptions)) + { + return WebSocketInitResult( + false, 0, "Failed to initialize per message deflate engine"); + } + ss << webSocketPerMessageDeflateOptions.generateHeader(); + } + + ss << "\r\n"; + + if (!_socket->writeBytes(ss.str(), isCancellationRequested)) + { + return WebSocketInitResult( + false, 0, std::string("Failed sending response to remote end")); + } + + return WebSocketInitResult(true, 200, "", headers, uri); + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketHandshake.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketHandshake.h new file mode 100644 index 0000000000..0a275e4336 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketHandshake.h @@ -0,0 +1,54 @@ +/* + * IXWebSocketHandshake.h + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include "IXCancellationRequest.h" +#include "IXSocket.h" +#include "IXWebSocketHttpHeaders.h" +#include "IXWebSocketInitResult.h" +#include "IXWebSocketPerMessageDeflate.h" +#include "IXWebSocketPerMessageDeflateOptions.h" +#include +#include +#include +#include + +namespace ix +{ + class WebSocketHandshake + { + public: + WebSocketHandshake(std::atomic& requestInitCancellation, + std::unique_ptr& _socket, + WebSocketPerMessageDeflatePtr& perMessageDeflate, + WebSocketPerMessageDeflateOptions& perMessageDeflateOptions, + std::atomic& enablePerMessageDeflate); + + WebSocketInitResult clientHandshake(const std::string& url, + const WebSocketHttpHeaders& extraHeaders, + const std::string& host, + const std::string& path, + int port, + int timeoutSecs); + + WebSocketInitResult serverHandshake(int timeoutSecs, bool enablePerMessageDeflate); + + private: + std::string genRandomString(const int len); + + // Parse HTTP headers + WebSocketInitResult sendErrorResponse(int code, const std::string& reason); + + bool insensitiveStringCompare(const std::string& a, const std::string& b); + + std::atomic& _requestInitCancellation; + std::unique_ptr& _socket; + WebSocketPerMessageDeflatePtr& _perMessageDeflate; + WebSocketPerMessageDeflateOptions& _perMessageDeflateOptions; + std::atomic& _enablePerMessageDeflate; + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketHandshakeKeyGen.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketHandshakeKeyGen.h new file mode 100644 index 0000000000..56528fcf2d --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketHandshakeKeyGen.h @@ -0,0 +1,171 @@ +// Copyright (c) 2016 Alex Hultman and contributors + +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. + +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: + +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgement in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +#pragma once + +#include +#include +#include +#include + +class WebSocketHandshakeKeyGen +{ + template + struct static_for + { + void operator()(uint32_t* a, uint32_t* b) + { + static_for()(a, b); + T::template f(a, b); + } + }; + + template + struct static_for<0, T> + { + void operator()(uint32_t* /*a*/, uint32_t* /*hash*/) + { + } + }; + + template + struct Sha1Loop + { + static inline uint32_t rol(uint32_t value, size_t bits) + { + return (value << bits) | (value >> (32 - bits)); + } + static inline uint32_t blk(uint32_t b[16], size_t i) + { + return rol(b[(i + 13) & 15] ^ b[(i + 8) & 15] ^ b[(i + 2) & 15] ^ b[i], 1); + } + + template + static inline void f(uint32_t* a, uint32_t* b) + { + switch (state) + { + case 1: + a[i % 5] += + ((a[(3 + i) % 5] & (a[(2 + i) % 5] ^ a[(1 + i) % 5])) ^ a[(1 + i) % 5]) + + b[i] + 0x5a827999 + rol(a[(4 + i) % 5], 5); + a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30); + break; + case 2: + b[i] = blk(b, i); + a[(1 + i) % 5] += + ((a[(4 + i) % 5] & (a[(3 + i) % 5] ^ a[(2 + i) % 5])) ^ a[(2 + i) % 5]) + + b[i] + 0x5a827999 + rol(a[(5 + i) % 5], 5); + a[(4 + i) % 5] = rol(a[(4 + i) % 5], 30); + break; + case 3: + b[(i + 4) % 16] = blk(b, (i + 4) % 16); + a[i % 5] += (a[(3 + i) % 5] ^ a[(2 + i) % 5] ^ a[(1 + i) % 5]) + + b[(i + 4) % 16] + 0x6ed9eba1 + rol(a[(4 + i) % 5], 5); + a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30); + break; + case 4: + b[(i + 8) % 16] = blk(b, (i + 8) % 16); + a[i % 5] += (((a[(3 + i) % 5] | a[(2 + i) % 5]) & a[(1 + i) % 5]) | + (a[(3 + i) % 5] & a[(2 + i) % 5])) + + b[(i + 8) % 16] + 0x8f1bbcdc + rol(a[(4 + i) % 5], 5); + a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30); + break; + case 5: + b[(i + 12) % 16] = blk(b, (i + 12) % 16); + a[i % 5] += (a[(3 + i) % 5] ^ a[(2 + i) % 5] ^ a[(1 + i) % 5]) + + b[(i + 12) % 16] + 0xca62c1d6 + rol(a[(4 + i) % 5], 5); + a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30); + break; + case 6: b[i] += a[4 - i]; + } + } + }; + + static inline void sha1(uint32_t hash[5], uint32_t b[16]) + { + uint32_t a[5] = {hash[4], hash[3], hash[2], hash[1], hash[0]}; + static_for<16, Sha1Loop<1>>()(a, b); + static_for<4, Sha1Loop<2>>()(a, b); + static_for<20, Sha1Loop<3>>()(a, b); + static_for<20, Sha1Loop<4>>()(a, b); + static_for<20, Sha1Loop<5>>()(a, b); + static_for<5, Sha1Loop<6>>()(a, hash); + } + + static inline void base64(unsigned char* src, char* dst) + { + const char* b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + for (int i = 0; i < 18; i += 3) + { + *dst++ = b64[(src[i] >> 2) & 63]; + *dst++ = b64[((src[i] & 3) << 4) | ((src[i + 1] & 240) >> 4)]; + *dst++ = b64[((src[i + 1] & 15) << 2) | ((src[i + 2] & 192) >> 6)]; + *dst++ = b64[src[i + 2] & 63]; + } + *dst++ = b64[(src[18] >> 2) & 63]; + *dst++ = b64[((src[18] & 3) << 4) | ((src[19] & 240) >> 4)]; + *dst++ = b64[((src[19] & 15) << 2)]; + *dst++ = '='; + } + +public: + static inline void generate(const std::string& inputStr, char output[28]) + { + char input[25] = {}; + strncpy(input, inputStr.c_str(), 25 - 1); + input[25 - 1] = '\0'; + + uint32_t b_output[5] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0}; + uint32_t b_input[16] = {0, + 0, + 0, + 0, + 0, + 0, + 0x32353845, + 0x41464135, + 0x2d453931, + 0x342d3437, + 0x44412d39, + 0x3543412d, + 0x43354142, + 0x30444338, + 0x35423131, + 0x80000000}; + + for (int i = 0; i < 6; i++) + { + b_input[i] = (input[4 * i + 3] & 0xff) | (input[4 * i + 2] & 0xff) << 8 | + (input[4 * i + 1] & 0xff) << 16 | (input[4 * i + 0] & 0xff) << 24; + } + sha1(b_output, b_input); + uint32_t last_b[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 480}; + sha1(b_output, last_b); + for (int i = 0; i < 5; i++) + { + uint32_t tmp = b_output[i]; + char* bytes = (char*) &b_output[i]; + bytes[3] = tmp & 0xff; + bytes[2] = (tmp >> 8) & 0xff; + bytes[1] = (tmp >> 16) & 0xff; + bytes[0] = (tmp >> 24) & 0xff; + } + base64((unsigned char*) b_output, output); + } +}; diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketHttpHeaders.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketHttpHeaders.cpp new file mode 100644 index 0000000000..5563430185 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketHttpHeaders.cpp @@ -0,0 +1,74 @@ +/* + * IXWebSocketHttpHeaders.h + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ + +#include "IXWebSocketHttpHeaders.h" + +#include "IXSocket.h" +#include +#include + +namespace ix +{ + std::pair parseHttpHeaders( + std::unique_ptr& socket, const CancellationRequest& isCancellationRequested) + { + WebSocketHttpHeaders headers; + + char line[1024]; + int i; + + while (true) + { + int colon = 0; + + for (i = 0; i < 2 || (i < 1023 && line[i - 2] != '\r' && line[i - 1] != '\n'); ++i) + { + if (!socket->readByte(line + i, isCancellationRequested)) + { + return std::make_pair(false, headers); + } + + if (line[i] == ':' && colon == 0) + { + colon = i; + } + } + if (line[0] == '\r' && line[1] == '\n') + { + break; + } + + // line is a single header entry. split by ':', and add it to our + // header map. ignore lines with no colon. + if (colon > 0) + { + line[i] = '\0'; + std::string lineStr(line); + // colon is ':', usually colon+1 is ' ', and colon+2 is the start of the value. + // some webservers do not put a space after the colon character, so + // the start of the value might be farther than colon+2. + // The spec says that space after the : should be discarded. + // i is end of string (\0), i-colon is length of string minus key; + // subtract 1 for '\0', 1 for '\n', 1 for '\r', + // 1 for the ' ' after the ':', and total is -4 + // since we use an std::string later on and don't account for '\0', + // plus the optional first space, total is -2 + int start = colon + 1; + while (lineStr[start] == ' ') + { + start++; + } + + std::string name(lineStr.substr(0, colon)); + std::string value(lineStr.substr(start, lineStr.size() - start - 2)); + + headers[name] = value; + } + } + + return std::make_pair(true, headers); + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketHttpHeaders.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketHttpHeaders.h new file mode 100644 index 0000000000..7ba8c4efb6 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketHttpHeaders.h @@ -0,0 +1,23 @@ +/* + * IXWebSocketHttpHeaders.h + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include "IXCancellationRequest.h" +#include "IXStrCaseCompare.h" +#include +#include +#include + +namespace ix +{ + class Socket; + + using WebSocketHttpHeaders = std::map; + + std::pair parseHttpHeaders( + std::unique_ptr& socket, const CancellationRequest& isCancellationRequested); +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketInitResult.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketInitResult.h new file mode 100644 index 0000000000..d60e4f8575 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketInitResult.h @@ -0,0 +1,36 @@ +/* + * IXWebSocketInitResult.h + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include "IXWebSocketHttpHeaders.h" + +namespace ix +{ + struct WebSocketInitResult + { + bool success; + int http_status; + std::string errorStr; + WebSocketHttpHeaders headers; + std::string uri; + std::string protocol; + + WebSocketInitResult(bool s = false, + int status = 0, + const std::string& e = std::string(), + WebSocketHttpHeaders h = WebSocketHttpHeaders(), + const std::string& u = std::string()) + { + success = s; + http_status = status; + errorStr = e; + headers = h; + uri = u; + protocol = h["Sec-WebSocket-Protocol"]; + } + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketMessage.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketMessage.h new file mode 100644 index 0000000000..25a00ce70c --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketMessage.h @@ -0,0 +1,60 @@ +/* + * IXWebSocketMessage.h + * Author: Benjamin Sergeant + * Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include "IXWebSocketCloseInfo.h" +#include "IXWebSocketErrorInfo.h" +#include "IXWebSocketMessageType.h" +#include "IXWebSocketOpenInfo.h" +#include +#include + +namespace ix +{ + struct WebSocketMessage + { + WebSocketMessageType type; + const std::string& str; + size_t wireSize; + WebSocketErrorInfo errorInfo; + WebSocketOpenInfo openInfo; + WebSocketCloseInfo closeInfo; + bool binary; + + WebSocketMessage(WebSocketMessageType t, + const std::string& s, + size_t w, + WebSocketErrorInfo e, + WebSocketOpenInfo o, + WebSocketCloseInfo c, + bool b = false) + : type(t) + , str(s) + , wireSize(w) + , errorInfo(e) + , openInfo(o) + , closeInfo(c) + , binary(b) + { + ; + } + + /** + * @brief Deleted overload to prevent binding `str` to a temporary, which would cause + * undefined behavior since class members don't extend lifetime beyond the constructor call. + */ + WebSocketMessage(WebSocketMessageType t, + std::string&& s, + size_t w, + WebSocketErrorInfo e, + WebSocketOpenInfo o, + WebSocketCloseInfo c, + bool b = false) = delete; + }; + + using WebSocketMessagePtr = std::unique_ptr; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketMessageType.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketMessageType.h new file mode 100644 index 0000000000..c7ee45d930 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketMessageType.h @@ -0,0 +1,21 @@ +/* + * IXWebSocketMessageType.h + * Author: Benjamin Sergeant + * Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +namespace ix +{ + enum class WebSocketMessageType + { + Message = 0, + Open = 1, + Close = 2, + Error = 3, + Ping = 4, + Pong = 5, + Fragment = 6 + }; +} diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketOpenInfo.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketOpenInfo.h new file mode 100644 index 0000000000..3eb32b0b1c --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketOpenInfo.h @@ -0,0 +1,31 @@ +/* + * IXWebSocketOpenInfo.h + * Author: Benjamin Sergeant + * Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include "IXWebSocketHttpHeaders.h" +#include +#include + +namespace ix +{ + struct WebSocketOpenInfo + { + std::string uri; + WebSocketHttpHeaders headers; + std::string protocol; + + WebSocketOpenInfo(const std::string& u = std::string(), + const WebSocketHttpHeaders& h = WebSocketHttpHeaders(), + const std::string& p = std::string()) + : uri(u) + , headers(h) + , protocol(p) + { + ; + } + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflate.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflate.cpp new file mode 100644 index 0000000000..804358701f --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflate.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2015, Peter Thorson. 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 WebSocket++ Project 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 PETER THORSON 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. + */ + +/* + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + * + * Adapted from websocketpp/extensions/permessage_deflate/enabled.hpp + * (same license as MZ: https://opensource.org/licenses/BSD-3-Clause) + * + * - Reused zlib compression + decompression bits. + * - Refactored to have 2 class for compression and decompression, to allow multi-threading + * and make sure that _compressBuffer is not shared between threads. + * - Original code wasn't working for some reason, I had to add checks + * for the presence of the kEmptyUncompressedBlock at the end of buffer so that servers + * would start accepting receiving/decoding compressed messages. Original code was probably + * modifying the passed in buffers before processing in enabled.hpp ? + * - Added more documentation. + * + * Per message Deflate RFC: https://tools.ietf.org/html/rfc7692 + * Chrome websocket -> + * https://github.com/chromium/chromium/tree/2ca8c5037021c9d2ecc00b787d58a31ed8fc8bcb/net/websockets + * + */ + +#include "IXWebSocketPerMessageDeflate.h" + +#include "IXUniquePtr.h" +#include "IXWebSocketPerMessageDeflateCodec.h" +#include "IXWebSocketPerMessageDeflateOptions.h" + +namespace ix +{ + WebSocketPerMessageDeflate::WebSocketPerMessageDeflate() + : _compressor(ix::make_unique()) + , _decompressor(ix::make_unique()) + { + ; + } + + WebSocketPerMessageDeflate::~WebSocketPerMessageDeflate() + { + ; + } + + bool WebSocketPerMessageDeflate::init( + const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions) + { + bool clientNoContextTakeover = perMessageDeflateOptions.getClientNoContextTakeover(); + + uint8_t deflateBits = perMessageDeflateOptions.getClientMaxWindowBits(); + uint8_t inflateBits = perMessageDeflateOptions.getServerMaxWindowBits(); + + return _compressor->init(deflateBits, clientNoContextTakeover) && + _decompressor->init(inflateBits, clientNoContextTakeover); + } + + bool WebSocketPerMessageDeflate::compress(const std::string& in, std::string& out) + { + return _compressor->compress(in, out); + } + + bool WebSocketPerMessageDeflate::decompress(const std::string& in, std::string& out) + { + return _decompressor->decompress(in, out); + } + +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflate.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflate.h new file mode 100644 index 0000000000..9177409d2a --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflate.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2015, Peter Thorson. 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 WebSocket++ Project 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 PETER THORSON 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. + */ + +/* + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + * + * Adapted from websocketpp/extensions/permessage_deflate/enabled.hpp + * (same license as MZ: https://opensource.org/licenses/BSD-3-Clause) + */ + +#pragma once + +#include +#include + +namespace ix +{ + class WebSocketPerMessageDeflateOptions; + class WebSocketPerMessageDeflateCompressor; + class WebSocketPerMessageDeflateDecompressor; + + class WebSocketPerMessageDeflate + { + public: + WebSocketPerMessageDeflate(); + ~WebSocketPerMessageDeflate(); + + bool init(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions); + bool compress(const std::string& in, std::string& out); + bool decompress(const std::string& in, std::string& out); + + private: + std::unique_ptr _compressor; + std::unique_ptr _decompressor; + }; + + using WebSocketPerMessageDeflatePtr = std::unique_ptr; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp new file mode 100644 index 0000000000..d641c47c16 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp @@ -0,0 +1,246 @@ +/* + * IXWebSocketPerMessageDeflateCodec.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved. + */ + +#include "IXWebSocketPerMessageDeflateCodec.h" + +#include "IXWebSocketPerMessageDeflateOptions.h" +#include +#include + +namespace +{ + // The passed in size (4) is important, without it the string litteral + // is treated as a char* and the null termination (\x00) makes it + // look like an empty string. + const std::string kEmptyUncompressedBlock = std::string("\x00\x00\xff\xff", 4); +} // namespace + +namespace ix +{ + // + // Compressor + // + WebSocketPerMessageDeflateCompressor::WebSocketPerMessageDeflateCompressor() + { +#ifdef IXWEBSOCKET_USE_ZLIB + memset(&_deflateState, 0, sizeof(_deflateState)); + + _deflateState.zalloc = Z_NULL; + _deflateState.zfree = Z_NULL; + _deflateState.opaque = Z_NULL; +#endif + } + + WebSocketPerMessageDeflateCompressor::~WebSocketPerMessageDeflateCompressor() + { +#ifdef IXWEBSOCKET_USE_ZLIB + deflateEnd(&_deflateState); +#endif + } + + bool WebSocketPerMessageDeflateCompressor::init(uint8_t deflateBits, + bool clientNoContextTakeOver) + { +#ifdef IXWEBSOCKET_USE_ZLIB + int ret = deflateInit2(&_deflateState, + Z_DEFAULT_COMPRESSION, + Z_DEFLATED, + -1 * deflateBits, + 4, // memory level 1-9 + Z_DEFAULT_STRATEGY); + + if (ret != Z_OK) return false; + + _flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH; + + return true; +#else + return false; +#endif + } + + template + bool WebSocketPerMessageDeflateCompressor::endsWithEmptyUnCompressedBlock(const T& value) + { + if (kEmptyUncompressedBlock.size() > value.size()) return false; + auto N = value.size(); + return value[N - 1] == kEmptyUncompressedBlock[3] && + value[N - 2] == kEmptyUncompressedBlock[2] && + value[N - 3] == kEmptyUncompressedBlock[1] && + value[N - 4] == kEmptyUncompressedBlock[0]; + } + + bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in, std::string& out) + { + return compressData(in, out); + } + + bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in, + std::vector& out) + { + return compressData(in, out); + } + + bool WebSocketPerMessageDeflateCompressor::compress(const std::vector& in, + std::string& out) + { + return compressData(in, out); + } + + bool WebSocketPerMessageDeflateCompressor::compress(const std::vector& in, + std::vector& out) + { + return compressData(in, out); + } + + template + bool WebSocketPerMessageDeflateCompressor::compressData(const T& in, S& out) + { +#ifdef IXWEBSOCKET_USE_ZLIB + // + // 7.2.1. Compression + // + // An endpoint uses the following algorithm to compress a message. + // + // 1. Compress all the octets of the payload of the message using + // DEFLATE. + // + // 2. If the resulting data does not end with an empty DEFLATE block + // with no compression (the "BTYPE" bits are set to 00), append an + // empty DEFLATE block with no compression to the tail end. + // + // 3. Remove 4 octets (that are 0x00 0x00 0xff 0xff) from the tail end. + // After this step, the last octet of the compressed data contains + // (possibly part of) the DEFLATE header bits with the "BTYPE" bits + // set to 00. + // + size_t output; + + // Clear output + out.clear(); + + if (in.empty()) + { + // See issue #167 + // The normal buffer size should be 6 but + // we remove the 4 octets from the tail (#4) + uint8_t buf[2] = {0x02, 0x00}; + out.push_back(buf[0]); + out.push_back(buf[1]); + + return true; + } + + _deflateState.avail_in = (uInt) in.size(); + _deflateState.next_in = (Bytef*) in.data(); + + do + { + // Output to local buffer + _deflateState.avail_out = (uInt) _compressBuffer.size(); + _deflateState.next_out = &_compressBuffer.front(); + + deflate(&_deflateState, _flush); + + output = _compressBuffer.size() - _deflateState.avail_out; + + out.insert(out.end(), _compressBuffer.begin(), _compressBuffer.begin() + output); + } while (_deflateState.avail_out == 0); + + if (endsWithEmptyUnCompressedBlock(out)) + { + out.resize(out.size() - 4); + } + + return true; +#else + return false; +#endif + } + + // + // Decompressor + // + WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor() + { +#ifdef IXWEBSOCKET_USE_ZLIB + memset(&_inflateState, 0, sizeof(_inflateState)); + + _inflateState.zalloc = Z_NULL; + _inflateState.zfree = Z_NULL; + _inflateState.opaque = Z_NULL; + _inflateState.avail_in = 0; + _inflateState.next_in = Z_NULL; +#endif + } + + WebSocketPerMessageDeflateDecompressor::~WebSocketPerMessageDeflateDecompressor() + { +#ifdef IXWEBSOCKET_USE_ZLIB + inflateEnd(&_inflateState); +#endif + } + + bool WebSocketPerMessageDeflateDecompressor::init(uint8_t inflateBits, + bool clientNoContextTakeOver) + { +#ifdef IXWEBSOCKET_USE_ZLIB + int ret = inflateInit2(&_inflateState, -1 * inflateBits); + + if (ret != Z_OK) return false; + + _flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH; + + return true; +#else + return false; +#endif + } + + bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string& in, std::string& out) + { +#ifdef IXWEBSOCKET_USE_ZLIB + // + // 7.2.2. Decompression + // + // An endpoint uses the following algorithm to decompress a message. + // + // 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the + // payload of the message. + // + // 2. Decompress the resulting data using DEFLATE. + // + std::string inFixed(in); + inFixed += kEmptyUncompressedBlock; + + _inflateState.avail_in = (uInt) inFixed.size(); + _inflateState.next_in = (unsigned char*) (const_cast(inFixed.data())); + + // Clear output + out.clear(); + + do + { + _inflateState.avail_out = (uInt) _compressBuffer.size(); + _inflateState.next_out = &_compressBuffer.front(); + + int ret = inflate(&_inflateState, Z_SYNC_FLUSH); + + if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) + { + return false; // zlib error + } + + out.append(reinterpret_cast(&_compressBuffer.front()), + _compressBuffer.size() - _inflateState.avail_out); + } while (_inflateState.avail_out == 0); + + return true; +#else + return false; +#endif + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflateCodec.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflateCodec.h new file mode 100644 index 0000000000..2d06517c1f --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflateCodec.h @@ -0,0 +1,62 @@ +/* + * IXWebSocketPerMessageDeflateCodec.h + * Author: Benjamin Sergeant + * Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#ifdef IXWEBSOCKET_USE_ZLIB +#include "zlib.h" +#endif +#include +#include +#include + +namespace ix +{ + class WebSocketPerMessageDeflateCompressor + { + public: + WebSocketPerMessageDeflateCompressor(); + ~WebSocketPerMessageDeflateCompressor(); + + bool init(uint8_t deflateBits, bool clientNoContextTakeOver); + bool compress(const std::string& in, std::string& out); + bool compress(const std::string& in, std::vector& out); + bool compress(const std::vector& in, std::string& out); + bool compress(const std::vector& in, std::vector& out); + + private: + template + bool compressData(const T& in, S& out); + template + bool endsWithEmptyUnCompressedBlock(const T& value); + + int _flush; + std::array _compressBuffer; + +#ifdef IXWEBSOCKET_USE_ZLIB + z_stream _deflateState; +#endif + }; + + class WebSocketPerMessageDeflateDecompressor + { + public: + WebSocketPerMessageDeflateDecompressor(); + ~WebSocketPerMessageDeflateDecompressor(); + + bool init(uint8_t inflateBits, bool clientNoContextTakeOver); + bool decompress(const std::string& in, std::string& out); + + private: + int _flush; + std::array _compressBuffer; + +#ifdef IXWEBSOCKET_USE_ZLIB + z_stream _inflateState; +#endif + }; + +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp new file mode 100644 index 0000000000..c41a8c3d81 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp @@ -0,0 +1,185 @@ +/* + * IXWebSocketPerMessageDeflateOptions.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ + +#include "IXWebSocketPerMessageDeflateOptions.h" + +#include +#include +#include + +namespace ix +{ + /// Default values as defined in the RFC + const uint8_t WebSocketPerMessageDeflateOptions::kDefaultServerMaxWindowBits = 15; + static const uint8_t minServerMaxWindowBits = 8; + static const uint8_t maxServerMaxWindowBits = 15; + + const uint8_t WebSocketPerMessageDeflateOptions::kDefaultClientMaxWindowBits = 15; + static const uint8_t minClientMaxWindowBits = 8; + static const uint8_t maxClientMaxWindowBits = 15; + + WebSocketPerMessageDeflateOptions::WebSocketPerMessageDeflateOptions( + bool enabled, + bool clientNoContextTakeover, + bool serverNoContextTakeover, + uint8_t clientMaxWindowBits, + uint8_t serverMaxWindowBits) + { + _enabled = enabled; + _clientNoContextTakeover = clientNoContextTakeover; + _serverNoContextTakeover = serverNoContextTakeover; + _clientMaxWindowBits = clientMaxWindowBits; + _serverMaxWindowBits = serverMaxWindowBits; + + sanitizeClientMaxWindowBits(); + } + + // + // Four extension parameters are defined for "permessage-deflate" to + // help endpoints manage per-connection resource usage. + // + // - "server_no_context_takeover" + // - "client_no_context_takeover" + // - "server_max_window_bits" + // - "client_max_window_bits" + // + // Server response could look like that: + // + // Sec-WebSocket-Extensions: permessage-deflate; client_no_context_takeover; + // server_no_context_takeover + // + WebSocketPerMessageDeflateOptions::WebSocketPerMessageDeflateOptions(std::string extension) + { + extension = removeSpaces(extension); + + _enabled = false; + _clientNoContextTakeover = false; + _serverNoContextTakeover = false; + _clientMaxWindowBits = kDefaultClientMaxWindowBits; + _serverMaxWindowBits = kDefaultServerMaxWindowBits; + +#ifdef IXWEBSOCKET_USE_ZLIB + // Split by ; + std::string token; + std::stringstream tokenStream(extension); + + while (std::getline(tokenStream, token, ';')) + { + if (token == "permessage-deflate") + { + _enabled = true; + } + + if (token == "server_no_context_takeover") + { + _serverNoContextTakeover = true; + } + + if (token == "client_no_context_takeover") + { + _clientNoContextTakeover = true; + } + + if (startsWith(token, "server_max_window_bits=")) + { + uint8_t x = strtol(token.substr(token.find_last_of("=") + 1).c_str(), nullptr, 10); + + // Sanitize values to be in the proper range [8, 15] in + // case a server would give us bogus values + _serverMaxWindowBits = + std::min(maxServerMaxWindowBits, std::max(x, minServerMaxWindowBits)); + } + + if (startsWith(token, "client_max_window_bits=")) + { + uint8_t x = strtol(token.substr(token.find_last_of("=") + 1).c_str(), nullptr, 10); + + // Sanitize values to be in the proper range [8, 15] in + // case a server would give us bogus values + _clientMaxWindowBits = + std::min(maxClientMaxWindowBits, std::max(x, minClientMaxWindowBits)); + + sanitizeClientMaxWindowBits(); + } + } +#endif + } + + void WebSocketPerMessageDeflateOptions::sanitizeClientMaxWindowBits() + { + // zlib/deflate has a bug with windowsbits == 8, so we silently upgrade it to 9 + // See https://bugs.chromium.org/p/chromium/issues/detail?id=691074 + if (_clientMaxWindowBits == 8) + { + _clientMaxWindowBits = 9; + } + } + + std::string WebSocketPerMessageDeflateOptions::generateHeader() + { +#ifdef IXWEBSOCKET_USE_ZLIB + std::stringstream ss; + ss << "Sec-WebSocket-Extensions: permessage-deflate"; + + if (_clientNoContextTakeover) ss << "; client_no_context_takeover"; + if (_serverNoContextTakeover) ss << "; server_no_context_takeover"; + + ss << "; server_max_window_bits=" << _serverMaxWindowBits; + ss << "; client_max_window_bits=" << _clientMaxWindowBits; + + ss << "\r\n"; + + return ss.str(); +#else + return std::string(); +#endif + } + + bool WebSocketPerMessageDeflateOptions::enabled() const + { +#ifdef IXWEBSOCKET_USE_ZLIB + return _enabled; +#else + return false; +#endif + } + + bool WebSocketPerMessageDeflateOptions::getClientNoContextTakeover() const + { + return _clientNoContextTakeover; + } + + bool WebSocketPerMessageDeflateOptions::getServerNoContextTakeover() const + { + return _serverNoContextTakeover; + } + + uint8_t WebSocketPerMessageDeflateOptions::getClientMaxWindowBits() const + { + return _clientMaxWindowBits; + } + + uint8_t WebSocketPerMessageDeflateOptions::getServerMaxWindowBits() const + { + return _serverMaxWindowBits; + } + + bool WebSocketPerMessageDeflateOptions::startsWith(const std::string& str, + const std::string& start) + { + return str.compare(0, start.length(), start) == 0; + } + + std::string WebSocketPerMessageDeflateOptions::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 diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflateOptions.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflateOptions.h new file mode 100644 index 0000000000..7cd33c0c9f --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketPerMessageDeflateOptions.h @@ -0,0 +1,47 @@ +/* + * IXWebSocketPerMessageDeflateOptions.h + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include + +namespace ix +{ + class WebSocketPerMessageDeflateOptions + { + public: + WebSocketPerMessageDeflateOptions( + bool enabled = false, + bool clientNoContextTakeover = false, + bool serverNoContextTakeover = false, + uint8_t clientMaxWindowBits = kDefaultClientMaxWindowBits, + uint8_t serverMaxWindowBits = kDefaultServerMaxWindowBits); + + WebSocketPerMessageDeflateOptions(std::string extension); + + std::string generateHeader(); + bool enabled() const; + bool getClientNoContextTakeover() const; + bool getServerNoContextTakeover() const; + uint8_t getServerMaxWindowBits() const; + uint8_t getClientMaxWindowBits() const; + + static bool startsWith(const std::string& str, const std::string& start); + static std::string removeSpaces(const std::string& str); + + static uint8_t const kDefaultClientMaxWindowBits; + static uint8_t const kDefaultServerMaxWindowBits; + + private: + bool _enabled; + bool _clientNoContextTakeover; + bool _serverNoContextTakeover; + uint8_t _clientMaxWindowBits; + uint8_t _serverMaxWindowBits; + + void sanitizeClientMaxWindowBits(); + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketProxyServer.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketProxyServer.cpp new file mode 100644 index 0000000000..4b78c63b0d --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketProxyServer.cpp @@ -0,0 +1,137 @@ +/* + * IXWebSocketProxyServer.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ + +#include "IXWebSocketProxyServer.h" + +#include "IXWebSocketServer.h" +#include + +namespace ix +{ + class ProxyConnectionState : public ix::ConnectionState + { + public: + ProxyConnectionState() + : _connected(false) + { + } + + ix::WebSocket& webSocket() + { + return _serverWebSocket; + } + + bool isConnected() + { + return _connected; + } + + void setConnected() + { + _connected = true; + } + + private: + ix::WebSocket _serverWebSocket; + bool _connected; + }; + + int websocket_proxy_server_main(int port, + const std::string& hostname, + const ix::SocketTLSOptions& tlsOptions, + const std::string& remoteUrl, + const RemoteUrlsMapping& remoteUrlsMapping, + bool /*verbose*/) + { + ix::WebSocketServer server(port, hostname); + server.setTLSOptions(tlsOptions); + + auto factory = []() -> std::shared_ptr { + return std::make_shared(); + }; + server.setConnectionStateFactory(factory); + + server.setOnConnectionCallback( + [remoteUrl, remoteUrlsMapping](std::weak_ptr webSocket, + std::shared_ptr connectionState) { + auto state = std::dynamic_pointer_cast(connectionState); + auto remoteIp = connectionState->getRemoteIp(); + + // Server connection + state->webSocket().setOnMessageCallback( + [webSocket, state, remoteIp](const WebSocketMessagePtr& msg) { + if (msg->type == ix::WebSocketMessageType::Close) + { + state->setTerminated(); + } + else if (msg->type == ix::WebSocketMessageType::Message) + { + auto ws = webSocket.lock(); + if (ws) + { + ws->send(msg->str, msg->binary); + } + } + }); + + // Client connection + auto ws = webSocket.lock(); + if (ws) + { + ws->setOnMessageCallback([state, remoteUrl, remoteUrlsMapping]( + const WebSocketMessagePtr& msg) { + if (msg->type == ix::WebSocketMessageType::Open) + { + // Connect to the 'real' server + std::string url(remoteUrl); + + // maybe we want a different url based on the mapping + std::string host = msg->openInfo.headers["Host"]; + auto it = remoteUrlsMapping.find(host); + if (it != remoteUrlsMapping.end()) + { + url = it->second; + } + + // append the uri to form the full url + // (say ws://localhost:1234/foo/?bar=baz) + url += msg->openInfo.uri; + + state->webSocket().setUrl(url); + state->webSocket().disableAutomaticReconnection(); + state->webSocket().start(); + + // we should sleep here for a bit until we've established the + // connection with the remote server + while (state->webSocket().getReadyState() != ReadyState::Open) + { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + } + else if (msg->type == ix::WebSocketMessageType::Close) + { + state->webSocket().close(msg->closeInfo.code, msg->closeInfo.reason); + } + else if (msg->type == ix::WebSocketMessageType::Message) + { + state->webSocket().send(msg->str, msg->binary); + } + }); + } + }); + + auto res = server.listen(); + if (!res.first) + { + return 1; + } + + server.start(); + server.wait(); + + return 0; + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketProxyServer.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketProxyServer.h new file mode 100644 index 0000000000..7a55e47229 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketProxyServer.h @@ -0,0 +1,24 @@ +/* + * IXWebSocketProxyServer.h + * Author: Benjamin Sergeant + * Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved. + */ +#pragma once + +#include "IXSocketTLSOptions.h" +#include +#include +#include +#include + +namespace ix +{ + using RemoteUrlsMapping = std::map; + + int websocket_proxy_server_main(int port, + const std::string& hostname, + const ix::SocketTLSOptions& tlsOptions, + const std::string& remoteUrl, + const RemoteUrlsMapping& remoteUrlsMapping, + bool verbose); +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketSendInfo.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketSendInfo.h new file mode 100644 index 0000000000..c66d4a6305 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketSendInfo.h @@ -0,0 +1,27 @@ +/* + * IXWebSocketSendInfo.h + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +namespace ix +{ + struct WebSocketSendInfo + { + bool success; + bool compressionError; + size_t payloadSize; + size_t wireSize; + + WebSocketSendInfo(bool s = false, bool c = false, size_t p = 0, size_t w = 0) + : success(s) + , compressionError(c) + , payloadSize(p) + , wireSize(w) + { + ; + } + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketServer.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketServer.cpp new file mode 100644 index 0000000000..64830a5f62 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketServer.cpp @@ -0,0 +1,229 @@ +/* + * IXWebSocketServer.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ + +#include "IXWebSocketServer.h" + +#include "IXNetSystem.h" +#include "IXSetThreadName.h" +#include "IXSocketConnect.h" +#include "IXWebSocket.h" +#include "IXWebSocketTransport.h" +#include +#include +#include + +namespace ix +{ + const int WebSocketServer::kDefaultHandShakeTimeoutSecs(3); // 3 seconds + const bool WebSocketServer::kDefaultEnablePong(true); + + WebSocketServer::WebSocketServer(int port, + const std::string& host, + int backlog, + size_t maxConnections, + int handshakeTimeoutSecs, + int addressFamily) + : SocketServer(port, host, backlog, maxConnections, addressFamily) + , _handshakeTimeoutSecs(handshakeTimeoutSecs) + , _enablePong(kDefaultEnablePong) + , _enablePerMessageDeflate(true) + { + } + + WebSocketServer::~WebSocketServer() + { + stop(); + } + + void WebSocketServer::stop() + { + stopAcceptingConnections(); + + auto clients = getClients(); + for (auto client : clients) + { + client->close(); + } + + SocketServer::stop(); + } + + void WebSocketServer::enablePong() + { + _enablePong = true; + } + + void WebSocketServer::disablePong() + { + _enablePong = false; + } + + void WebSocketServer::disablePerMessageDeflate() + { + _enablePerMessageDeflate = false; + } + + void WebSocketServer::setOnConnectionCallback(const OnConnectionCallback& callback) + { + _onConnectionCallback = callback; + } + + void WebSocketServer::setOnClientMessageCallback(const OnClientMessageCallback& callback) + { + _onClientMessageCallback = callback; + } + + void WebSocketServer::handleConnection(std::unique_ptr socket, + std::shared_ptr connectionState) + { + setThreadName("WebSocketServer::" + connectionState->getId()); + + auto webSocket = std::make_shared(); + if (_onConnectionCallback) + { + _onConnectionCallback(webSocket, connectionState); + + if (!webSocket->isOnMessageCallbackRegistered()) + { + logError("WebSocketServer Application developer error: Server callback improperly " + "registerered."); + logError("Missing call to setOnMessageCallback inside setOnConnectionCallback."); + connectionState->setTerminated(); + return; + } + } + else if (_onClientMessageCallback) + { + WebSocket* webSocketRawPtr = webSocket.get(); + webSocket->setOnMessageCallback( + [this, webSocketRawPtr, connectionState](const WebSocketMessagePtr& msg) { + _onClientMessageCallback(connectionState, *webSocketRawPtr, msg); + }); + } + else + { + logError( + "WebSocketServer Application developer error: No server callback is registerered."); + logError("Missing call to setOnConnectionCallback or setOnClientMessageCallback."); + connectionState->setTerminated(); + return; + } + + webSocket->disableAutomaticReconnection(); + + if (_enablePong) + { + webSocket->enablePong(); + } + else + { + webSocket->disablePong(); + } + + // Add this client to our client set + { + std::lock_guard lock(_clientsMutex); + _clients.insert(webSocket); + } + + auto status = webSocket->connectToSocket( + std::move(socket), _handshakeTimeoutSecs, _enablePerMessageDeflate); + if (status.success) + { + // Process incoming messages and execute callbacks + // until the connection is closed + webSocket->run(); + } + else + { + std::stringstream ss; + ss << "WebSocketServer::handleConnection() HTTP status: " << status.http_status + << " error: " << status.errorStr; + logError(ss.str()); + } + + webSocket->setOnMessageCallback(nullptr); + + // Remove this client from our client set + { + std::lock_guard lock(_clientsMutex); + if (_clients.erase(webSocket) != 1) + { + logError("Cannot delete client"); + } + } + + connectionState->setTerminated(); + } + + std::set> WebSocketServer::getClients() + { + std::lock_guard lock(_clientsMutex); + return _clients; + } + + size_t WebSocketServer::getConnectedClientsCount() + { + std::lock_guard lock(_clientsMutex); + return _clients.size(); + } + + // + // Classic servers + // + void WebSocketServer::makeBroadcastServer() + { + setOnClientMessageCallback([this](std::shared_ptr 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 + { + std::chrono::duration duration(500); + std::this_thread::sleep_for(duration); + } while (client->bufferedAmount() != 0); + } + } + } + }); + } + + bool WebSocketServer::listenAndStart() + { + auto res = listen(); + if (!res.first) + { + return false; + } + + start(); + return true; + } + + int WebSocketServer::getHandshakeTimeoutSecs() + { + return _handshakeTimeoutSecs; + } + + bool WebSocketServer::isPongEnabled() + { + return _enablePong; + } + + bool WebSocketServer::isPerMessageDeflateEnabled() + { + return _enablePerMessageDeflate; + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketServer.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketServer.h new file mode 100644 index 0000000000..6cae633183 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketServer.h @@ -0,0 +1,77 @@ +/* + * IXWebSocketServer.h + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include "IXSocketServer.h" +#include "IXWebSocket.h" +#include +#include +#include +#include +#include +#include +#include +#include // pair + +namespace ix +{ + class WebSocketServer : public SocketServer + { + public: + using OnConnectionCallback = + std::function, std::shared_ptr)>; + + using OnClientMessageCallback = std::function, WebSocket&, const WebSocketMessagePtr&)>; + + WebSocketServer(int port = SocketServer::kDefaultPort, + const std::string& host = SocketServer::kDefaultHost, + int backlog = SocketServer::kDefaultTcpBacklog, + size_t maxConnections = SocketServer::kDefaultMaxConnections, + int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs, + int addressFamily = SocketServer::kDefaultAddressFamily); + virtual ~WebSocketServer(); + virtual void stop() final; + + void enablePong(); + void disablePong(); + void disablePerMessageDeflate(); + + void setOnConnectionCallback(const OnConnectionCallback& callback); + void setOnClientMessageCallback(const OnClientMessageCallback& callback); + + // Get all the connected clients + std::set> getClients(); + + void makeBroadcastServer(); + bool listenAndStart(); + + const static int kDefaultHandShakeTimeoutSecs; + + int getHandshakeTimeoutSecs(); + bool isPongEnabled(); + bool isPerMessageDeflateEnabled(); + private: + // Member variables + int _handshakeTimeoutSecs; + bool _enablePong; + bool _enablePerMessageDeflate; + + OnConnectionCallback _onConnectionCallback; + OnClientMessageCallback _onClientMessageCallback; + + std::mutex _clientsMutex; + std::set> _clients; + + const static bool kDefaultEnablePong; + + // Methods + virtual void handleConnection(std::unique_ptr socket, + std::shared_ptr connectionState); + virtual size_t getConnectedClientsCount() final; + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketTransport.cpp b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketTransport.cpp new file mode 100644 index 0000000000..8537a95e52 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketTransport.cpp @@ -0,0 +1,1199 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2012, 2013 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * IXWebSocketTransport.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved. + */ + +// +// Adapted from https://github.com/dhbaird/easywsclient +// + +#include "IXWebSocketTransport.h" + +#include "IXSocketFactory.h" +#include "IXSocketTLSOptions.h" +#include "IXUniquePtr.h" +#include "IXUrlParser.h" +#include "IXUtf8Validator.h" +#include "IXWebSocketHandshake.h" +#include "IXWebSocketHttpHeaders.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace ix +{ + const std::string WebSocketTransport::kPingMessage("ixwebsocket::heartbeat"); + const int WebSocketTransport::kDefaultPingIntervalSecs(-1); + const bool WebSocketTransport::kDefaultEnablePong(true); + const int WebSocketTransport::kClosingMaximumWaitingDelayInMs(300); + constexpr size_t WebSocketTransport::kChunkSize; + + WebSocketTransport::WebSocketTransport() + : _useMask(true) + , _blockingSend(false) + , _receivedMessageCompressed(false) + , _readyState(ReadyState::CLOSED) + , _closeCode(WebSocketCloseConstants::kInternalErrorCode) + , _closeWireSize(0) + , _closeRemote(false) + , _enablePerMessageDeflate(false) + , _requestInitCancellation(false) + , _closingTimePoint(std::chrono::steady_clock::now()) + , _enablePong(kDefaultEnablePong) + , _pingIntervalSecs(kDefaultPingIntervalSecs) + , _pongReceived(false) + , _pingCount(0) + , _lastSendPingTimePoint(std::chrono::steady_clock::now()) + { + setCloseReason(WebSocketCloseConstants::kInternalErrorMessage); + _readbuf.resize(kChunkSize); + } + + WebSocketTransport::~WebSocketTransport() + { + ; + } + + void WebSocketTransport::configure( + const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions, + const SocketTLSOptions& socketTLSOptions, + bool enablePong, + int pingIntervalSecs) + { + _perMessageDeflateOptions = perMessageDeflateOptions; + _enablePerMessageDeflate = _perMessageDeflateOptions.enabled(); + _socketTLSOptions = socketTLSOptions; + _enablePong = enablePong; + _pingIntervalSecs = pingIntervalSecs; + } + + // Client + WebSocketInitResult WebSocketTransport::connectToUrl(const std::string& url, + const WebSocketHttpHeaders& headers, + int timeoutSecs) + { + std::lock_guard lock(_socketMutex); + + std::string protocol, host, path, query; + int port; + std::string remoteUrl(url); + + WebSocketInitResult result; + const int maxRedirections = 10; + + for (int i = 0; i < maxRedirections; ++i) + { + if (!UrlParser::parse(remoteUrl, protocol, host, path, query, port)) + { + std::stringstream ss; + ss << "Could not parse url: '" << url << "'"; + return WebSocketInitResult(false, 0, ss.str()); + } + + std::string errorMsg; + bool tls = protocol == "wss"; + _socket = createSocket(tls, -1, errorMsg, _socketTLSOptions); + _perMessageDeflate = ix::make_unique(); + + if (!_socket) + { + return WebSocketInitResult(false, 0, errorMsg); + } + + WebSocketHandshake webSocketHandshake(_requestInitCancellation, + _socket, + _perMessageDeflate, + _perMessageDeflateOptions, + _enablePerMessageDeflate); + + result = webSocketHandshake.clientHandshake( + remoteUrl, headers, host, path, port, timeoutSecs); + + if (result.http_status >= 300 && result.http_status < 400) + { + auto it = result.headers.find("Location"); + if (it == result.headers.end()) + { + std::stringstream ss; + ss << "Missing Location Header for HTTP Redirect response. " + << "Rejecting connection to " << url << ", status: " << result.http_status; + result.errorStr = ss.str(); + break; + } + + remoteUrl = it->second; + continue; + } + + if (result.success) + { + setReadyState(ReadyState::OPEN); + } + return result; + } + + return result; + } + + // Server + WebSocketInitResult WebSocketTransport::connectToSocket(std::unique_ptr socket, + int timeoutSecs, + bool enablePerMessageDeflate) + { + std::lock_guard lock(_socketMutex); + + // Server should not mask the data it sends to the client + _useMask = false; + _blockingSend = true; + + _socket = std::move(socket); + _perMessageDeflate = ix::make_unique(); + + WebSocketHandshake webSocketHandshake(_requestInitCancellation, + _socket, + _perMessageDeflate, + _perMessageDeflateOptions, + _enablePerMessageDeflate); + + auto result = webSocketHandshake.serverHandshake(timeoutSecs, enablePerMessageDeflate); + if (result.success) + { + setReadyState(ReadyState::OPEN); + } + return result; + } + + WebSocketTransport::ReadyState WebSocketTransport::getReadyState() const + { + return _readyState; + } + + void WebSocketTransport::setReadyState(ReadyState readyState) + { + // No state change, return + if (_readyState == readyState) return; + + if (readyState == ReadyState::CLOSED) + { + if (_onCloseCallback) + { + _onCloseCallback(_closeCode, getCloseReason(), _closeWireSize, _closeRemote); + } + setCloseReason(WebSocketCloseConstants::kInternalErrorMessage); + _closeCode = WebSocketCloseConstants::kInternalErrorCode; + _closeWireSize = 0; + _closeRemote = false; + } + else if (readyState == ReadyState::OPEN) + { + initTimePointsAfterConnect(); + _pongReceived = false; + } + + _readyState = readyState; + } + + void WebSocketTransport::setOnCloseCallback(const OnCloseCallback& onCloseCallback) + { + _onCloseCallback = onCloseCallback; + } + + void WebSocketTransport::initTimePointsAfterConnect() + { + { + std::lock_guard lock(_lastSendPingTimePointMutex); + _lastSendPingTimePoint = std::chrono::steady_clock::now(); + } + } + + // Only consider send PING time points for that computation. + bool WebSocketTransport::pingIntervalExceeded() + { + if (_pingIntervalSecs <= 0) return false; + + std::lock_guard lock(_lastSendPingTimePointMutex); + auto now = std::chrono::steady_clock::now(); + return now - _lastSendPingTimePoint > std::chrono::seconds(_pingIntervalSecs); + } + + WebSocketSendInfo WebSocketTransport::sendHeartBeat() + { + _pongReceived = false; + std::stringstream ss; + ss << kPingMessage << "::" << _pingIntervalSecs << "s" + << "::" << _pingCount++; + return sendPing(ss.str()); + } + + bool WebSocketTransport::closingDelayExceeded() + { + std::lock_guard lock(_closingTimePointMutex); + auto now = std::chrono::steady_clock::now(); + return now - _closingTimePoint > std::chrono::milliseconds(kClosingMaximumWaitingDelayInMs); + } + + WebSocketTransport::PollResult WebSocketTransport::poll() + { + if (_readyState == ReadyState::OPEN) + { + if (pingIntervalExceeded()) + { + if (!_pongReceived) + { + // ping response (PONG) exceeds the maximum delay, close the connection + close(WebSocketCloseConstants::kInternalErrorCode, + WebSocketCloseConstants::kPingTimeoutMessage); + } + else + { + sendHeartBeat(); + } + } + } + + // No timeout if state is not OPEN, otherwise computed + // pingIntervalOrTimeoutGCD (equals to -1 if no ping and no ping timeout are set) + int lastingTimeoutDelayInMs = (_readyState != ReadyState::OPEN) ? 0 : _pingIntervalSecs; + + if (_pingIntervalSecs > 0) + { + // compute lasting delay to wait for next ping / timeout, if at least one set + auto now = std::chrono::steady_clock::now(); + int timeSinceLastPingMs = (int) std::chrono::duration_cast( + now - _lastSendPingTimePoint) + .count(); + lastingTimeoutDelayInMs = (1000 * _pingIntervalSecs) - timeSinceLastPingMs; + } + +#ifdef _WIN32 + // Windows does not have select interrupt capabilities, so wait with a small timeout + if (lastingTimeoutDelayInMs <= 0) + { + lastingTimeoutDelayInMs = 20; + } +#endif + + // If we are requesting a cancellation, pass in a positive and small timeout + // to never poll forever without a timeout. + if (_requestInitCancellation) + { + lastingTimeoutDelayInMs = 100; + } + + // poll the socket + PollResultType pollResult = _socket->isReadyToRead(lastingTimeoutDelayInMs); + + // Make sure we send all the buffered data + // there can be a lot of it for large messages. + if (pollResult == PollResultType::SendRequest) + { + if (!flushSendBuffer()) + { + return PollResult::CannotFlushSendBuffer; + } + } + else if (pollResult == PollResultType::ReadyForRead) + { + if (!receiveFromSocket()) + { + return PollResult::AbnormalClose; + } + } + else if (pollResult == PollResultType::Error) + { + closeSocket(); + } + else if (pollResult == PollResultType::CloseRequest) + { + closeSocket(); + } + + if (_readyState == ReadyState::CLOSING && closingDelayExceeded()) + { + _rxbuf.clear(); + // close code and reason were set when calling close() + closeSocket(); + setReadyState(ReadyState::CLOSED); + } + + return PollResult::Succeeded; + } + + bool WebSocketTransport::isSendBufferEmpty() const + { + std::lock_guard lock(_txbufMutex); + return _txbuf.empty(); + } + + template + void WebSocketTransport::appendToSendBuffer(const std::vector& header, + Iterator begin, + Iterator end, + uint64_t message_size, + uint8_t masking_key[4]) + { + std::lock_guard lock(_txbufMutex); + + _txbuf.insert(_txbuf.end(), header.begin(), header.end()); + _txbuf.insert(_txbuf.end(), begin, end); + + if (_useMask) + { + for (size_t i = 0; i != (size_t) message_size; ++i) + { + *(_txbuf.end() - (size_t) message_size + i) ^= masking_key[i & 0x3]; + } + } + } + + void WebSocketTransport::unmaskReceiveBuffer(const wsheader_type& ws) + { + if (ws.mask) + { + for (size_t j = 0; j != ws.N; ++j) + { + _rxbuf[j + ws.header_size] ^= ws.masking_key[j & 0x3]; + } + } + } + + // + // http://tools.ietf.org/html/rfc6455#section-5.2 Base Framing Protocol + // + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-------+-+-------------+-------------------------------+ + // |F|R|R|R| opcode|M| Payload len | Extended payload length | + // |I|S|S|S| (4) |A| (7) | (16/64) | + // |N|V|V|V| |S| | (if payload len==126/127) | + // | |1|2|3| |K| | | + // +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + // | Extended payload length continued, if payload len == 127 | + // + - - - - - - - - - - - - - - - +-------------------------------+ + // | |Masking-key, if MASK set to 1 | + // +-------------------------------+-------------------------------+ + // | Masking-key (continued) | Payload Data | + // +-------------------------------- - - - - - - - - - - - - - - - + + // : Payload Data continued ... : + // + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + // | Payload Data continued ... | + // +---------------------------------------------------------------+ + // + void WebSocketTransport::dispatch(WebSocketTransport::PollResult pollResult, + const OnMessageCallback& onMessageCallback) + { + while (true) + { + wsheader_type ws; + if (_rxbuf.size() < 2) break; /* Need at least 2 */ + const uint8_t* data = (uint8_t*) &_rxbuf[0]; // peek, but don't consume + ws.fin = (data[0] & 0x80) == 0x80; + ws.rsv1 = (data[0] & 0x40) == 0x40; + ws.rsv2 = (data[0] & 0x20) == 0x20; + ws.rsv3 = (data[0] & 0x10) == 0x10; + ws.opcode = (wsheader_type::opcode_type)(data[0] & 0x0f); + ws.mask = (data[1] & 0x80) == 0x80; + ws.N0 = (data[1] & 0x7f); + ws.header_size = + 2 + (ws.N0 == 126 ? 2 : 0) + (ws.N0 == 127 ? 8 : 0) + (ws.mask ? 4 : 0); + if (_rxbuf.size() < ws.header_size) break; /* Need: ws.header_size - _rxbuf.size() */ + + if ((ws.rsv1 && !_enablePerMessageDeflate) || ws.rsv2 || ws.rsv3) + { + close(WebSocketCloseConstants::kProtocolErrorCode, + WebSocketCloseConstants::kProtocolErrorReservedBitUsed, + _rxbuf.size()); + return; + } + + // + // Calculate payload length: + // 0-125 mean the payload is that long. + // 126 means that the following two bytes indicate the length, + // 127 means the next 8 bytes indicate the length. + // + int i = 0; + if (ws.N0 < 126) + { + ws.N = ws.N0; + i = 2; + } + else if (ws.N0 == 126) + { + ws.N = 0; + ws.N |= ((uint64_t) data[2]) << 8; + ws.N |= ((uint64_t) data[3]) << 0; + i = 4; + } + else if (ws.N0 == 127) + { + ws.N = 0; + ws.N |= ((uint64_t) data[2]) << 56; + ws.N |= ((uint64_t) data[3]) << 48; + ws.N |= ((uint64_t) data[4]) << 40; + ws.N |= ((uint64_t) data[5]) << 32; + ws.N |= ((uint64_t) data[6]) << 24; + ws.N |= ((uint64_t) data[7]) << 16; + ws.N |= ((uint64_t) data[8]) << 8; + ws.N |= ((uint64_t) data[9]) << 0; + i = 10; + } + else + { + // invalid payload length according to the spec. bail out + return; + } + + if (ws.mask) + { + ws.masking_key[0] = ((uint8_t) data[i + 0]) << 0; + ws.masking_key[1] = ((uint8_t) data[i + 1]) << 0; + ws.masking_key[2] = ((uint8_t) data[i + 2]) << 0; + ws.masking_key[3] = ((uint8_t) data[i + 3]) << 0; + } + else + { + ws.masking_key[0] = 0; + ws.masking_key[1] = 0; + ws.masking_key[2] = 0; + ws.masking_key[3] = 0; + } + + // Prevent integer overflow in the next conditional + const uint64_t maxFrameSize(1ULL << 63); + if (ws.N > maxFrameSize) + { + return; + } + + if (_rxbuf.size() < ws.header_size + ws.N) + { + return; /* Need: ws.header_size+ws.N - _rxbuf.size() */ + } + + if (!ws.fin && (ws.opcode == wsheader_type::PING || ws.opcode == wsheader_type::PONG || + ws.opcode == wsheader_type::CLOSE)) + { + // Control messages should not be fragmented + close(WebSocketCloseConstants::kProtocolErrorCode, + WebSocketCloseConstants::kProtocolErrorCodeControlMessageFragmented); + return; + } + + unmaskReceiveBuffer(ws); + std::string frameData(_rxbuf.begin() + ws.header_size, + _rxbuf.begin() + ws.header_size + (size_t) ws.N); + + // We got a whole message, now do something with it: + if (ws.opcode == wsheader_type::TEXT_FRAME || + ws.opcode == wsheader_type::BINARY_FRAME || + ws.opcode == wsheader_type::CONTINUATION) + { + if (ws.opcode != wsheader_type::CONTINUATION) + { + _fragmentedMessageKind = (ws.opcode == wsheader_type::TEXT_FRAME) + ? MessageKind::MSG_TEXT + : MessageKind::MSG_BINARY; + + _receivedMessageCompressed = _enablePerMessageDeflate && ws.rsv1; + + // Continuation message needs to follow a non-fin TEXT or BINARY message + if (!_chunks.empty()) + { + close(WebSocketCloseConstants::kProtocolErrorCode, + WebSocketCloseConstants::kProtocolErrorCodeDataOpcodeOutOfSequence); + } + } + else if (_chunks.empty()) + { + // Continuation message need to follow a non-fin TEXT or BINARY message + close( + WebSocketCloseConstants::kProtocolErrorCode, + WebSocketCloseConstants::kProtocolErrorCodeContinuationOpCodeOutOfSequence); + } + + // + // Usual case. Small unfragmented messages + // + if (ws.fin && _chunks.empty()) + { + emitMessage(_fragmentedMessageKind, + frameData, + _receivedMessageCompressed, + onMessageCallback); + + _receivedMessageCompressed = false; + } + else + { + // + // Add intermediary message to our chunk list. + // We use a chunk list instead of a big buffer because resizing + // large buffer can be very costly when we need to re-allocate + // the internal buffer which is slow and can let the internal OS + // receive buffer fill out. + // + _chunks.emplace_back(frameData); + + if (ws.fin) + { + emitMessage(_fragmentedMessageKind, + getMergedChunks(), + _receivedMessageCompressed, + onMessageCallback); + + _chunks.clear(); + _receivedMessageCompressed = false; + } + else + { + emitMessage(MessageKind::FRAGMENT, std::string(), false, onMessageCallback); + } + } + } + else if (ws.opcode == wsheader_type::PING) + { + // too large + if (frameData.size() > 125) + { + // Unexpected frame type + close(WebSocketCloseConstants::kProtocolErrorCode, + WebSocketCloseConstants::kProtocolErrorPingPayloadOversized); + return; + } + + if (_enablePong) + { + // Reply back right away + bool compress = false; + sendData(wsheader_type::PONG, frameData, compress); + } + + emitMessage(MessageKind::PING, frameData, false, onMessageCallback); + } + else if (ws.opcode == wsheader_type::PONG) + { + _pongReceived = true; + emitMessage(MessageKind::PONG, frameData, false, onMessageCallback); + } + else if (ws.opcode == wsheader_type::CLOSE) + { + std::string reason; + uint16_t code = 0; + + if (ws.N >= 2) + { + // Extract the close code first, available as the first 2 bytes + code |= ((uint64_t) _rxbuf[ws.header_size]) << 8; + code |= ((uint64_t) _rxbuf[ws.header_size + 1]) << 0; + + // Get the reason. + if (ws.N > 2) + { + reason = frameData.substr(2, frameData.size()); + } + + // Validate that the reason is proper utf-8. Autobahn 7.5.1 + if (!validateUtf8(reason)) + { + code = WebSocketCloseConstants::kInvalidFramePayloadData; + reason = WebSocketCloseConstants::kInvalidFramePayloadDataMessage; + } + + // + // Validate close codes. Autobahn 7.9.* + // 1014, 1015 are debattable. The firefox MSDN has a description for them. + // Full list of status code and status range is defined in the dedicated + // RFC section at https://tools.ietf.org/html/rfc6455#page-45 + // + if (code < 1000 || code == 1004 || code == 1006 || (code > 1013 && code < 3000)) + { + // build up an error message containing the bad error code + std::stringstream ss; + ss << WebSocketCloseConstants::kInvalidCloseCodeMessage << ": " << code; + reason = ss.str(); + + code = WebSocketCloseConstants::kProtocolErrorCode; + } + } + else + { + // no close code received + code = WebSocketCloseConstants::kNoStatusCodeErrorCode; + reason = WebSocketCloseConstants::kNoStatusCodeErrorMessage; + } + + // We receive a CLOSE frame from remote and are NOT the ones who triggered the close + if (_readyState != ReadyState::CLOSING) + { + // send back the CLOSE frame + sendCloseFrame(code, reason); + + wakeUpFromPoll(SelectInterrupt::kCloseRequest); + + bool remote = true; + closeSocketAndSwitchToClosedState(code, reason, _rxbuf.size(), remote); + } + else + { + // we got the CLOSE frame answer from our close, so we can close the connection + // if the code/reason are the same + bool identicalReason = _closeCode == code && getCloseReason() == reason; + + if (identicalReason) + { + bool remote = false; + closeSocketAndSwitchToClosedState(code, reason, _rxbuf.size(), remote); + } + } + } + else + { + // Unexpected frame type + close(WebSocketCloseConstants::kProtocolErrorCode, + WebSocketCloseConstants::kProtocolErrorMessage, + _rxbuf.size()); + } + + // Erase the message that has been processed from the input/read buffer + _rxbuf.erase(_rxbuf.begin(), _rxbuf.begin() + ws.header_size + (size_t) ws.N); + } + + // if an abnormal closure was raised in poll, and nothing else triggered a CLOSED state in + // the received and processed data then close the connection + if (pollResult != PollResult::Succeeded) + { + _rxbuf.clear(); + + // if we previously closed the connection (CLOSING state), then set state to CLOSED + // (code/reason were set before) + if (_readyState == ReadyState::CLOSING) + { + closeSocket(); + setReadyState(ReadyState::CLOSED); + } + // if we weren't closing, then close using abnormal close code and message + else if (_readyState != ReadyState::CLOSED) + { + closeSocketAndSwitchToClosedState(WebSocketCloseConstants::kAbnormalCloseCode, + WebSocketCloseConstants::kAbnormalCloseMessage, + 0, + false); + } + } + } + + std::string WebSocketTransport::getMergedChunks() const + { + size_t length = 0; + for (auto&& chunk : _chunks) + { + length += chunk.size(); + } + + std::string msg; + msg.reserve(length); + + for (auto&& chunk : _chunks) + { + msg += chunk; + } + + return msg; + } + + void WebSocketTransport::emitMessage(MessageKind messageKind, + const std::string& message, + bool compressedMessage, + const OnMessageCallback& onMessageCallback) + { + size_t wireSize = message.size(); + + // When the RSV1 bit is 1 it means the message is compressed + if (compressedMessage && messageKind != MessageKind::FRAGMENT) + { + bool success = _perMessageDeflate->decompress(message, _decompressedMessage); + + if (messageKind == MessageKind::MSG_TEXT && !validateUtf8(_decompressedMessage)) + { + close(WebSocketCloseConstants::kInvalidFramePayloadData, + WebSocketCloseConstants::kInvalidFramePayloadDataMessage); + } + else + { + onMessageCallback(_decompressedMessage, wireSize, !success, messageKind); + } + } + else + { + if (messageKind == MessageKind::MSG_TEXT && !validateUtf8(message)) + { + close(WebSocketCloseConstants::kInvalidFramePayloadData, + WebSocketCloseConstants::kInvalidFramePayloadDataMessage); + } + else + { + onMessageCallback(message, wireSize, false, messageKind); + } + } + } + + unsigned WebSocketTransport::getRandomUnsigned() + { + auto now = std::chrono::system_clock::now(); + auto seconds = + std::chrono::duration_cast(now.time_since_epoch()).count(); + return static_cast(seconds); + } + + template + WebSocketSendInfo WebSocketTransport::sendData(wsheader_type::opcode_type type, + const T& message, + bool compress, + const OnProgressCallback& onProgressCallback) + { + if (_readyState != ReadyState::OPEN && _readyState != ReadyState::CLOSING) + { + return WebSocketSendInfo(false); + } + + size_t payloadSize = message.size(); + size_t wireSize = message.size(); + bool compressionError = false; + + auto message_begin = message.cbegin(); + auto message_end = message.cend(); + + if (compress) + { + if (!_perMessageDeflate->compress(message, _compressedMessage)) + { + bool success = false; + compressionError = true; + payloadSize = 0; + wireSize = 0; + return WebSocketSendInfo(success, compressionError, payloadSize, wireSize); + } + compressionError = false; + wireSize = _compressedMessage.size(); + + message_begin = _compressedMessage.cbegin(); + message_end = _compressedMessage.cend(); + } + + { + std::lock_guard lock(_txbufMutex); + _txbuf.reserve(wireSize); + } + + bool success = true; + + // Common case for most message. No fragmentation required. + if (wireSize < kChunkSize) + { + success = sendFragment(type, true, message_begin, message_end, compress); + + if (onProgressCallback) + { + onProgressCallback(0, 1); + } + } + else + { + // + // Large messages need to be fragmented + // + // Rules: + // First message needs to specify a proper type (BINARY or TEXT) + // Intermediary and last messages need to be of type CONTINUATION + // Last message must set the fin byte. + // + auto steps = wireSize / kChunkSize; + + std::string::const_iterator begin = message_begin; + std::string::const_iterator end = message_end; + + for (uint64_t i = 0; i < steps; ++i) + { + bool firstStep = i == 0; + bool lastStep = (i + 1) == steps; + bool fin = lastStep; + + end = begin + kChunkSize; + if (lastStep) + { + end = message_end; + } + + auto opcodeType = type; + if (!firstStep) + { + opcodeType = wsheader_type::CONTINUATION; + } + + // Send message + if (!sendFragment(opcodeType, fin, begin, end, compress)) + { + return WebSocketSendInfo(false); + } + + if (onProgressCallback && !onProgressCallback((int) i, (int) steps)) + { + break; + } + + begin += kChunkSize; + } + } + + // Request to flush the send buffer on the background thread if it isn't empty + if (!isSendBufferEmpty()) + { + wakeUpFromPoll(SelectInterrupt::kSendRequest); + + // FIXME: we should have a timeout when sending large messages: see #131 + if (_blockingSend && !flushSendBuffer()) + { + success = false; + } + } + + return WebSocketSendInfo(success, compressionError, payloadSize, wireSize); + } + + template + bool WebSocketTransport::sendFragment(wsheader_type::opcode_type type, + bool fin, + Iterator message_begin, + Iterator message_end, + bool compress) + { + uint64_t message_size = static_cast(message_end - message_begin); + + unsigned x = getRandomUnsigned(); + uint8_t masking_key[4] = {}; + masking_key[0] = (x >> 24); + masking_key[1] = (x >> 16) & 0xff; + masking_key[2] = (x >> 8) & 0xff; + masking_key[3] = (x) &0xff; + + std::vector header; + header.assign(2 + (message_size >= 126 ? 2 : 0) + (message_size >= 65536 ? 6 : 0) + + (_useMask ? 4 : 0), + 0); + header[0] = type; + + // The fin bit indicate that this is the last fragment. Fin is French for end. + if (fin) + { + header[0] |= 0x80; + } + + // The rsv1 bit indicate that the frame is compressed + // continuation opcodes should not set it. Autobahn 12.2.10 and others 12.X + if (compress && type != wsheader_type::CONTINUATION) + { + header[0] |= 0x40; + } + + if (message_size < 126) + { + header[1] = (message_size & 0xff) | (_useMask ? 0x80 : 0); + + if (_useMask) + { + header[2] = masking_key[0]; + header[3] = masking_key[1]; + header[4] = masking_key[2]; + header[5] = masking_key[3]; + } + } + else if (message_size < 65536) + { + header[1] = 126 | (_useMask ? 0x80 : 0); + header[2] = (message_size >> 8) & 0xff; + header[3] = (message_size >> 0) & 0xff; + + if (_useMask) + { + header[4] = masking_key[0]; + header[5] = masking_key[1]; + header[6] = masking_key[2]; + header[7] = masking_key[3]; + } + } + else + { // TODO: run coverage testing here + header[1] = 127 | (_useMask ? 0x80 : 0); + header[2] = (message_size >> 56) & 0xff; + header[3] = (message_size >> 48) & 0xff; + header[4] = (message_size >> 40) & 0xff; + header[5] = (message_size >> 32) & 0xff; + header[6] = (message_size >> 24) & 0xff; + header[7] = (message_size >> 16) & 0xff; + header[8] = (message_size >> 8) & 0xff; + header[9] = (message_size >> 0) & 0xff; + + if (_useMask) + { + header[10] = masking_key[0]; + header[11] = masking_key[1]; + header[12] = masking_key[2]; + header[13] = masking_key[3]; + } + } + + // _txbuf will keep growing until it can be transmitted over the socket: + appendToSendBuffer(header, message_begin, message_end, message_size, masking_key); + + // Now actually send this data + return sendOnSocket(); + } + + WebSocketSendInfo WebSocketTransport::sendPing(const std::string& message) + { + bool compress = false; + WebSocketSendInfo info = sendData(wsheader_type::PING, message, compress); + + if (info.success) + { + std::lock_guard lck(_lastSendPingTimePointMutex); + _lastSendPingTimePoint = std::chrono::steady_clock::now(); + } + + return info; + } + + WebSocketSendInfo WebSocketTransport::sendBinary(const std::string& message, + const OnProgressCallback& onProgressCallback) + + { + return sendData( + wsheader_type::BINARY_FRAME, message, _enablePerMessageDeflate, onProgressCallback); + } + + WebSocketSendInfo WebSocketTransport::sendText(const std::string& message, + const OnProgressCallback& onProgressCallback) + + { + return sendData( + wsheader_type::TEXT_FRAME, message, _enablePerMessageDeflate, onProgressCallback); + } + + bool WebSocketTransport::sendOnSocket() + { + std::lock_guard lock(_txbufMutex); + + while (_txbuf.size()) + { + ssize_t ret = 0; + { + std::lock_guard lock(_socketMutex); + ret = _socket->send((char*) &_txbuf[0], _txbuf.size()); + } + + if (ret < 0 && Socket::isWaitNeeded()) + { + break; + } + else if (ret <= 0) + { + closeSocket(); + setReadyState(ReadyState::CLOSED); + return false; + } + else + { + _txbuf.erase(_txbuf.begin(), _txbuf.begin() + ret); + } + } + + return true; + } + + bool WebSocketTransport::receiveFromSocket() + { + while (true) + { + ssize_t ret = _socket->recv((char*) &_readbuf[0], _readbuf.size()); + + if (ret < 0 && Socket::isWaitNeeded()) + { + break; + } + else if (ret <= 0) + { + // if there are received data pending to be processed, then delay the abnormal + // closure to after dispatch (other close code/reason could be read from the + // buffer) + + closeSocket(); + return false; + } + else + { + _rxbuf.insert(_rxbuf.end(), _readbuf.begin(), _readbuf.begin() + ret); + } + } + + return true; + } + + void WebSocketTransport::sendCloseFrame(uint16_t code, const std::string& reason) + { + bool compress = false; + + // if a status is set/was read + if (code != WebSocketCloseConstants::kNoStatusCodeErrorCode) + { + // See list of close events here: + // https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent + std::string closure {(char) (code >> 8), (char) (code & 0xff)}; + + // copy reason after code + closure.append(reason); + + sendData(wsheader_type::CLOSE, closure, compress); + } + else + { + // no close code/reason set + sendData(wsheader_type::CLOSE, std::string(""), compress); + } + } + + void WebSocketTransport::closeSocket() + { + std::lock_guard lock(_socketMutex); + _socket->close(); + } + + bool WebSocketTransport::wakeUpFromPoll(uint64_t wakeUpCode) + { + std::lock_guard lock(_socketMutex); + return _socket->wakeUpFromPoll(wakeUpCode); + } + + void WebSocketTransport::closeSocketAndSwitchToClosedState(uint16_t code, + const std::string& reason, + size_t closeWireSize, + bool remote) + { + closeSocket(); + + setCloseReason(reason); + _closeCode = code; + _closeWireSize = closeWireSize; + _closeRemote = remote; + + setReadyState(ReadyState::CLOSED); + _requestInitCancellation = false; + } + + void WebSocketTransport::close(uint16_t code, + const std::string& reason, + size_t closeWireSize, + bool remote) + { + _requestInitCancellation = true; + + if (_readyState == ReadyState::CLOSING || _readyState == ReadyState::CLOSED) return; + + if (closeWireSize == 0) + { + closeWireSize = reason.size(); + } + + setCloseReason(reason); + _closeCode = code; + _closeWireSize = closeWireSize; + _closeRemote = remote; + + { + std::lock_guard lock(_closingTimePointMutex); + _closingTimePoint = std::chrono::steady_clock::now(); + } + setReadyState(ReadyState::CLOSING); + + sendCloseFrame(code, reason); + + // wake up the poll, but do not close yet + wakeUpFromPoll(SelectInterrupt::kSendRequest); + } + + size_t WebSocketTransport::bufferedAmount() const + { + std::lock_guard lock(_txbufMutex); + return _txbuf.size(); + } + + bool WebSocketTransport::flushSendBuffer() + { + while (!isSendBufferEmpty() && !_requestInitCancellation) + { + // Wait with a 10ms timeout until the socket is ready to write. + // This way we are not busy looping + PollResultType result = _socket->isReadyToWrite(10); + + if (result == PollResultType::Error) + { + closeSocket(); + setReadyState(ReadyState::CLOSED); + return false; + } + else if (result == PollResultType::ReadyForWrite) + { + if (!sendOnSocket()) + { + return false; + } + } + } + + return true; + } + + void WebSocketTransport::setCloseReason(const std::string& reason) + { + std::lock_guard lock(_closeReasonMutex); + _closeReason = reason; + } + + const std::string& WebSocketTransport::getCloseReason() const + { + std::lock_guard lock(_closeReasonMutex); + return _closeReason; + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketTransport.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketTransport.h new file mode 100644 index 0000000000..777e29c0e6 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketTransport.h @@ -0,0 +1,276 @@ +/* + * IXWebSocketTransport.h + * Author: Benjamin Sergeant + * Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +// +// Adapted from https://github.com/dhbaird/easywsclient +// + +#include "IXCancellationRequest.h" +#include "IXProgressCallback.h" +#include "IXSocketTLSOptions.h" +#include "IXWebSocketCloseConstants.h" +#include "IXWebSocketHandshake.h" +#include "IXWebSocketHttpHeaders.h" +#include "IXWebSocketPerMessageDeflate.h" +#include "IXWebSocketPerMessageDeflateOptions.h" +#include "IXWebSocketSendInfo.h" +#include +#include +#include +#include +#include +#include +#include + +namespace ix +{ + class Socket; + + enum class SendMessageKind + { + Text, + Binary, + Ping + }; + + class WebSocketTransport + { + public: + enum class ReadyState + { + CLOSING, + CLOSED, + CONNECTING, + OPEN + }; + + enum class MessageKind + { + MSG_TEXT, + MSG_BINARY, + PING, + PONG, + FRAGMENT + }; + + enum class PollResult + { + Succeeded, + AbnormalClose, + CannotFlushSendBuffer + }; + + using OnMessageCallback = + std::function; + using OnCloseCallback = std::function; + + WebSocketTransport(); + ~WebSocketTransport(); + + void configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions, + const SocketTLSOptions& socketTLSOptions, + bool enablePong, + int pingIntervalSecs); + + // Client + WebSocketInitResult connectToUrl(const std::string& url, + const WebSocketHttpHeaders& headers, + int timeoutSecs); + + // Server + WebSocketInitResult connectToSocket(std::unique_ptr socket, + int timeoutSecs, + bool enablePerMessageDeflate); + + PollResult poll(); + WebSocketSendInfo sendBinary(const std::string& message, + const OnProgressCallback& onProgressCallback); + WebSocketSendInfo sendText(const std::string& message, + const OnProgressCallback& onProgressCallback); + WebSocketSendInfo sendPing(const std::string& message); + + void close(uint16_t code = WebSocketCloseConstants::kNormalClosureCode, + const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage, + size_t closeWireSize = 0, + bool remote = false); + + void closeSocket(); + + ReadyState getReadyState() const; + void setReadyState(ReadyState readyState); + void setOnCloseCallback(const OnCloseCallback& onCloseCallback); + void dispatch(PollResult pollResult, const OnMessageCallback& onMessageCallback); + size_t bufferedAmount() const; + + // internal + WebSocketSendInfo sendHeartBeat(); + + private: + std::string _url; + + struct wsheader_type + { + unsigned header_size; + bool fin; + bool rsv1; + bool rsv2; + bool rsv3; + bool mask; + enum opcode_type + { + CONTINUATION = 0x0, + TEXT_FRAME = 0x1, + BINARY_FRAME = 0x2, + CLOSE = 8, + PING = 9, + PONG = 0xa, + } opcode; + int N0; + uint64_t N; + uint8_t masking_key[4]; + }; + + // Tells whether we should mask the data we send. + // client should mask but server should not + std::atomic _useMask; + + // Tells whether we should flush the send buffer before + // saying that a send is complete. This is the mode for server code. + std::atomic _blockingSend; + + // Buffer for reading from our socket. That buffer is never resized. + std::vector _readbuf; + + // Contains all messages that were fetched in the last socket read. + // This could be a mix of control messages (Close, Ping, etc...) and + // data messages. That buffer is resized + std::vector _rxbuf; + + // Contains all messages that are waiting to be sent + std::vector _txbuf; + mutable std::mutex _txbufMutex; + + // Hold fragments for multi-fragments messages in a list. We support receiving very large + // messages (tested messages up to 700M) and we cannot put them in a single + // buffer that is resized, as this operation can be slow when a buffer has its + // size increased 2 fold, while appending to a list has a fixed cost. + std::list _chunks; + + // Record the message kind (will be TEXT or BINARY) for a fragmented + // message, present in the first chunk, since the final chunk will be a + // CONTINUATION opcode and doesn't tell the full message kind + MessageKind _fragmentedMessageKind; + + // Ditto for whether a message is compressed + bool _receivedMessageCompressed; + + // Fragments are 32K long + static constexpr size_t kChunkSize = 1 << 15; + + // Underlying TCP socket + std::unique_ptr _socket; + std::mutex _socketMutex; + + // Hold the state of the connection (OPEN, CLOSED, etc...) + std::atomic _readyState; + + OnCloseCallback _onCloseCallback; + std::string _closeReason; + mutable std::mutex _closeReasonMutex; + std::atomic _closeCode; + std::atomic _closeWireSize; + std::atomic _closeRemote; + + // Data used for Per Message Deflate compression (with zlib) + WebSocketPerMessageDeflatePtr _perMessageDeflate; + WebSocketPerMessageDeflateOptions _perMessageDeflateOptions; + std::atomic _enablePerMessageDeflate; + + std::string _decompressedMessage; + std::string _compressedMessage; + + // Used to control TLS connection behavior + SocketTLSOptions _socketTLSOptions; + + // Used to cancel dns lookup + socket connect + http upgrade + std::atomic _requestInitCancellation; + + mutable std::mutex _closingTimePointMutex; + std::chrono::time_point _closingTimePoint; + static const int kClosingMaximumWaitingDelayInMs; + + // enable auto response to ping + std::atomic _enablePong; + static const bool kDefaultEnablePong; + + // Optional ping and pong timeout + int _pingIntervalSecs; + std::atomic _pongReceived; + + static const int kDefaultPingIntervalSecs; + static const std::string kPingMessage; + std::atomic _pingCount; + + // We record when ping are being sent so that we can know when to send the next one + mutable std::mutex _lastSendPingTimePointMutex; + std::chrono::time_point _lastSendPingTimePoint; + + // If this function returns true, it is time to send a new ping + bool pingIntervalExceeded(); + void initTimePointsAfterConnect(); + + // after calling close(), if no CLOSE frame answer is received back from the remote, we + // should close the connexion + bool closingDelayExceeded(); + + void sendCloseFrame(uint16_t code, const std::string& reason); + + void closeSocketAndSwitchToClosedState(uint16_t code, + const std::string& reason, + size_t closeWireSize, + bool remote); + + bool wakeUpFromPoll(uint64_t wakeUpCode); + + bool flushSendBuffer(); + bool sendOnSocket(); + bool receiveFromSocket(); + + template + WebSocketSendInfo sendData(wsheader_type::opcode_type type, + const T& message, + bool compress, + const OnProgressCallback& onProgressCallback = nullptr); + + template + bool sendFragment( + wsheader_type::opcode_type type, bool fin, Iterator begin, Iterator end, bool compress); + + void emitMessage(MessageKind messageKind, + const std::string& message, + bool compressedMessage, + const OnMessageCallback& onMessageCallback); + + bool isSendBufferEmpty() const; + + template + void appendToSendBuffer(const std::vector& header, + Iterator begin, + Iterator end, + uint64_t message_size, + uint8_t masking_key[4]); + + unsigned getRandomUnsigned(); + void unmaskReceiveBuffer(const wsheader_type& ws); + + std::string getMergedChunks() const; + + void setCloseReason(const std::string& reason); + const std::string& getCloseReason() const; + }; +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketVersion.h b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketVersion.h new file mode 100644 index 0000000000..ccaec84549 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/ixwebsocket/IXWebSocketVersion.h @@ -0,0 +1,9 @@ +/* + * IXWebSocketVersion.h + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#define IX_WEBSOCKET_VERSION "11.3.2" diff --git a/extern/IXWebSocket-11.3.2/main.cpp b/extern/IXWebSocket-11.3.2/main.cpp new file mode 100644 index 0000000000..8512537f1a --- /dev/null +++ b/extern/IXWebSocket-11.3.2/main.cpp @@ -0,0 +1,79 @@ +/* + * main.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2020 Machine Zone, Inc. All rights reserved. + * + * Super simple standalone example. See ws folder, unittest and doc/usage.md for more. + * + * On macOS + * $ mkdir -p build ; cd build ; cmake -DUSE_TLS=1 .. ; make -j ; make install + * $ clang++ --std=c++14 --stdlib=libc++ main.cpp -lixwebsocket -lz -framework Security -framework Foundation + * $ ./a.out + * + * Or use cmake -DBUILD_DEMO=ON option for other platform + */ + +#include +#include +#include +#include + +int main() +{ + // Required on Windows + ix::initNetSystem(); + + // Our websocket object + ix::WebSocket webSocket; + + // Connect to a server with encryption + // See https://machinezone.github.io/IXWebSocket/usage/#tls-support-and-configuration + std::string url("wss://echo.websocket.org"); + webSocket.setUrl(url); + + std::cout << ix::userAgent() << std::endl; + std::cout << "Connecting to " << url << "..." << std::endl; + + // Setup a callback to be fired (in a background thread, watch out for race conditions !) + // when a message or an event (open, close, error) is received + webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg) + { + if (msg->type == ix::WebSocketMessageType::Message) + { + std::cout << "received message: " << msg->str << std::endl; + std::cout << "> " << std::flush; + } + else if (msg->type == ix::WebSocketMessageType::Open) + { + std::cout << "Connection established" << std::endl; + std::cout << "> " << std::flush; + } + else if (msg->type == ix::WebSocketMessageType::Error) + { + // Maybe SSL is not configured properly + std::cout << "Connection error: " << msg->errorInfo.reason << std::endl; + std::cout << "> " << std::flush; + } + } + ); + + // Now that our callback is setup, we can start our background thread and receive messages + webSocket.start(); + + // Send a message to the server (default to TEXT mode) + webSocket.send("hello world"); + + // Display a prompt + std::cout << "> " << std::flush; + + std::string text; + // Read text from the console and send messages in text mode. + // Exit with Ctrl-D on Unix or Ctrl-Z on Windows. + while (std::getline(std::cin, text)) + { + webSocket.send(text); + std::cout << "> " << std::flush; + } + + return 0; +} diff --git a/extern/IXWebSocket-11.3.2/makefile.dev b/extern/IXWebSocket-11.3.2/makefile.dev new file mode 100644 index 0000000000..e4c57c3750 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/makefile.dev @@ -0,0 +1,220 @@ +# +# This makefile is used for convenience, and wrap simple cmake commands +# You don't need to use it as an end user, it is more for developer. +# +# * work with docker (linux build) +# * execute the unittest +# +# The default target will install ws, the command line tool coming with +# IXWebSocket into /usr/local/bin +# +# +all: brew + +install: brew + +# Use -DCMAKE_INSTALL_PREFIX= to install into another location +# on osx it is good practice to make /usr/local user writable +# sudo chown -R `whoami`/staff /usr/local +# +# Release, Debug, MinSizeRel, RelWithDebInfo are the build types +# +# Default rule does not use python as that requires first time users to have Python3 installed +# +brew: + mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=OFF -DCMAKE_INSTALL_MESSAGE=LAZY -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 +# server side ?) and I can't work-around it easily, so we're using mbedtls on +# Linux for the SSL backend, which works great. +ws_mbedtls_install: + mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_ZLIB=OFF -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; ninja install) + +ws: + mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install) + +ws_unity: + mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install) + +ws_install: + mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. -DUSE_TEST=0 && ninja install) + +ws_install_release: + mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. -DUSE_TEST=0 && ninja install) + +ws_openssl_install: + mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_OPEN_SSL=1 .. ; ninja install) + +ws_mbedtls: + mkdir -p build && (cd build ; cmake -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; make -j 4) + +ws_no_ssl: + mkdir -p build && (cd build ; cmake -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_WS=1 .. ; make -j 4) + +ws_no_python: + mkdir -p build && (cd build ; cmake -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j4 install) + +uninstall: + xargs rm -fv < build/install_manifest.txt + +tag: + git tag v"`sh tools/extract_version.sh`" + +xcode: + cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 -GXcode && open ixwebsocket.xcodeproj + +xcode_openssl: + cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 -DUSE_OPEN_SSL=1 -GXcode && open ixwebsocket.xcodeproj + +.PHONY: docker + +NAME := ${DOCKER_REPO}/ws +TAG := $(shell sh tools/extract_version.sh) +IMG := ${NAME}:${TAG} +LATEST := ${NAME}:latest +BUILD := ${NAME}:build + +print_version: + @echo 'IXWebSocket version =>' ${TAG} + +set_version: + sh tools/update_version.sh ${VERSION} + +docker_test: + docker build -f docker/Dockerfile.debian -t bsergean/ixwebsocket_test:build . + +docker: + git clean -dfx + docker build -t ${IMG} . + docker tag ${IMG} ${BUILD} + +docker_push: + docker tag ${IMG} ${LATEST} + docker push ${LATEST} + docker push ${IMG} + +deploy: docker docker_push + +run: + docker run --cap-add sys_ptrace --entrypoint=sh -it bsergean/ws:build + +# this is helpful to remove trailing whitespaces +trail: + sh third_party/remote_trailing_whitespaces.sh + +format: + clang-format -i `find test ixwebsocket ws -name '*.cpp' -o -name '*.h'` + +# That target is used to start a node server, but isn't required as we have +# a builtin C++ server started in the unittest now +test_server: + (cd test && npm i ws && node broadcast-server.js) + +test: + mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 ..) + (cd build ; ninja) + (cd build ; ninja -v test) + +test_asan: + mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -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 ; ctest -V .) + +test_tsan_mbedtls: + mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -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 test) + +test_tsan_openssl: + mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -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 test) + +test_tsan_sectransport: + mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -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 test) + +ws_test: ws + (cd ws ; env DEBUG=1 PATH=../ws/build:$$PATH bash test_ws.sh) + +autobahn_report: + cp -rvf ~/sandbox/reports/clients/* ../bsergean.github.io/IXWebSocket/autobahn/ + +httpd: + clang++ --std=c++14 --stdlib=libc++ -o ixhttpd httpd.cpp \ + ixwebsocket/IXSelectInterruptFactory.cpp \ + ixwebsocket/IXCancellationRequest.cpp \ + ixwebsocket/IXSocketTLSOptions.cpp \ + ixwebsocket/IXUserAgent.cpp \ + ixwebsocket/IXDNSLookup.cpp \ + ixwebsocket/IXBench.cpp \ + ixwebsocket/IXWebSocketHttpHeaders.cpp \ + ixwebsocket/IXSelectInterruptPipe.cpp \ + ixwebsocket/IXHttp.cpp \ + ixwebsocket/IXSocketConnect.cpp \ + ixwebsocket/IXSocket.cpp \ + ixwebsocket/IXSocketServer.cpp \ + ixwebsocket/IXNetSystem.cpp \ + ixwebsocket/IXHttpServer.cpp \ + ixwebsocket/IXSocketFactory.cpp \ + ixwebsocket/IXConnectionState.cpp \ + ixwebsocket/IXUrlParser.cpp \ + ixwebsocket/IXSelectInterrupt.cpp \ + ixwebsocket/IXSetThreadName.cpp \ + -lz + +httpd_linux: + g++ --std=c++14 -o ixhttpd httpd.cpp -Iixwebsocket \ + ixwebsocket/IXSelectInterruptFactory.cpp \ + ixwebsocket/IXCancellationRequest.cpp \ + ixwebsocket/IXSocketTLSOptions.cpp \ + ixwebsocket/IXUserAgent.cpp \ + ixwebsocket/IXDNSLookup.cpp \ + ixwebsocket/IXBench.cpp \ + ixwebsocket/IXWebSocketHttpHeaders.cpp \ + ixwebsocket/IXSelectInterruptPipe.cpp \ + ixwebsocket/IXHttp.cpp \ + ixwebsocket/IXSocketConnect.cpp \ + ixwebsocket/IXSocket.cpp \ + ixwebsocket/IXSocketServer.cpp \ + ixwebsocket/IXNetSystem.cpp \ + ixwebsocket/IXHttpServer.cpp \ + ixwebsocket/IXSocketFactory.cpp \ + ixwebsocket/IXConnectionState.cpp \ + ixwebsocket/IXUrlParser.cpp \ + ixwebsocket/IXSelectInterrupt.cpp \ + ixwebsocket/IXSetThreadName.cpp \ + -lz -lpthread + cp -f ixhttpd /usr/local/bin + +# For the fork that is configured with appveyor +rebase_upstream: + git fetch upstream + git checkout master + git reset --hard upstream/master + git push origin master --force + +# Legacy target +install_cmake_for_linux: + mkdir -p /tmp/cmake + (cd /tmp/cmake ; curl -L -O https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz ; tar zxf cmake-3.14.0-Linux-x86_64.tar.gz) + +# python -m venv venv +# source venv/bin/activate +# pip install mkdocs +doc: + mkdocs gh-deploy + +change: format + vim ixwebsocket/IXWebSocketVersion.h docs/CHANGELOG.md + +change_no_format: + vim ixwebsocket/IXWebSocketVersion.h docs/CHANGELOG.md + +commit: + git commit -am "`sh tools/extract_latest_change.sh`" + +.PHONY: test +.PHONY: build +.PHONY: ws diff --git a/extern/IXWebSocket-11.3.2/mkdocs.yml b/extern/IXWebSocket-11.3.2/mkdocs.yml new file mode 100644 index 0000000000..9b8b49360b --- /dev/null +++ b/extern/IXWebSocket-11.3.2/mkdocs.yml @@ -0,0 +1 @@ +site_name: IXWebSocket diff --git a/extern/IXWebSocket-11.3.2/test/.certs/selfsigned-client-crt.pem b/extern/IXWebSocket-11.3.2/test/.certs/selfsigned-client-crt.pem new file mode 100644 index 0000000000..10dfd91d1a --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/.certs/selfsigned-client-crt.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgIUNJBwOQdDle1TI/MHGd+cSpxIllwwDQYJKoZIhvcNAQEL +BQAwSDEUMBIGA1UECgwLbWFjaGluZXpvbmUxFDASBgNVBAoMC0lYV2ViU29ja2V0 +MRowGAYDVQQDDBFzZWxmc2lnbmVkLWNsaWVudDAeFw0yMDAzMTIyMzA0MzdaFw0y +MTAzMTIyMzA0MzdaMEgxFDASBgNVBAoMC21hY2hpbmV6b25lMRQwEgYDVQQKDAtJ +WFdlYlNvY2tldDEaMBgGA1UEAwwRc2VsZnNpZ25lZC1jbGllbnQwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7q6W0f5vRSHaNOuM1VQpY0rC0a5u04J5Z +nssUD1QfgilY1UEaaR/4K6ILE4oClqeDsQy/7+04Wt6i/ttceB/k1Jk6n0kgdtvA +CsX1H+nA7JL7ANBZvQ6W2E1mwJieTDSVDgL4YB9qzJQu3PdwZJgm5GTlVK66DMr1 +IH2EYwu73M/ZwOzfgyd7m0TcgkRV8OHiD1dVDERNQR9gzDUsBtCoWPmzXxgPMOSE +Oq1sEhNC0bPaG3zTDvCv0t4Hti33po/U8PZwOtz2b8StSjS5BnvEDnksAtEZuNEu +4B3KJN4Oxrtgh7DYdiF7S9Gh0dN6yqtRfDWkGyC9WkyoqpFKCM4fAgMBAAGjIzAh +MB8GA1UdEQQYMBaCCWxvY2FsaG9zdIIJMTI3LjAuMC4xMA0GCSqGSIb3DQEBCwUA +A4IBAQB4oIutDYbCRfsyWRAiAY+D9rhYsJYlsQjyml1q2+pCv7BJ1kWsKk7m2VMX +Tl6CM+PI0zXPpLN6Ot79jf/jxEbDMvqrBgGpYfddvLhyTFnzIZpG8d63RvzPADF6 +lV3x34eZf/EdtrWgZAHK+5oZjtzePGHwKDFIPva9nvJXYIxNwKYWGRX8HSm0OZi2 +FQiaOt6WYLo7ZdefNPS9nugFRM6hfztJe6WvvglKm+BTnHbCSKj5xRuT9iA80+jX +Zij7po8opY3S+zEZ0eNUCHxMBQ+2Jdq3HxggJ2cFQVRHdvKfwzmavVeGgni75d16 ++xFD5nS3g3eIEME+lZ8c8GbL0AJ4 +-----END CERTIFICATE----- diff --git a/extern/IXWebSocket-11.3.2/test/.certs/selfsigned-client-key.pem b/extern/IXWebSocket-11.3.2/test/.certs/selfsigned-client-key.pem new file mode 100644 index 0000000000..c319e0934a --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/.certs/selfsigned-client-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAu6ultH+b0Uh2jTrjNVUKWNKwtGubtOCeWZ7LFA9UH4IpWNVB +Gmkf+CuiCxOKApang7EMv+/tOFreov7bXHgf5NSZOp9JIHbbwArF9R/pwOyS+wDQ +Wb0OlthNZsCYnkw0lQ4C+GAfasyULtz3cGSYJuRk5VSuugzK9SB9hGMLu9zP2cDs +34Mne5tE3IJEVfDh4g9XVQxETUEfYMw1LAbQqFj5s18YDzDkhDqtbBITQtGz2ht8 +0w7wr9LeB7Yt96aP1PD2cDrc9m/ErUo0uQZ7xA55LALRGbjRLuAdyiTeDsa7YIew +2HYhe0vRodHTesqrUXw1pBsgvVpMqKqRSgjOHwIDAQABAoIBAQC2q7IESj2x7TWv +7ITiEZ+bq6DiTOfnnMeldkI3iWAZt0lltVXETlUW6+mznFY2hMwTDE/bt78Qnqqc +vzNoA2kQBLwNaqP0XJ0zhYkAOwr9hYjflwA2iSZdP7e/b3JeitCX0WakunN6Mh1+ +rAiRtui+2os3CkF0yST4iqKCLSJrvSvvK5fU92aKEaE3k9kznBljvVOJIIRQBUPz +G8tvtPgpLALrT7XMnGfaCyGS8c1IbFMm84KTxAlVV0bnuGgeYQ2VupqUmZpJjcJ0 +B08hr7vPfxz3UXSOKwYY8TRfmF3X370ky5Ov2I9ddg27V1QoeRTWlL7VMxRtiSer +hoM5SPKpAoGBAO0vBd1Z6425wGT0PClUbJAVm2OBYMDnl2RmhBK5TAxrKjs9ag08 +65jfVCMD8wDMDhEbvbmzkgRa9BC6AY97JBmyr4m9oGfA7oenuou+a9LYAKqtO0ts +hxHf2LnpC1HCyh4+l5gohjlUG7gSVu/oBhNTJNKmqUKuQ8v1b6My/JR9AoGBAMqP +DugL9DusECncKHQbaIEzvEBe+QErcUxXxq+G4LLvFTZVvthHbrZ/0cxm5Ve6rfd2 +krqjYFA3WPOuTcKEUouNeRK2A4V6PbnSdpf0kagN6KbEjK66ZSZs8wnWitghqo7J +n2IHcSDEEACTyjS7K8HjPx0fQGU1tzkG/7/xs3vLAoGAI61JEoyuE/l26TibvBPI +6Lt3TjZt2VZ8vUt2XmKk/9E23wZT533canhdbY7whJQtIYGsvjw2oJUV1VZFWdHK +EluAcBWoBTNOLfWa595S1bpMD2BTZPsELjofnYdifn/wazA7GVYvKnxuVvfbP+cE +0u9UwKL1HuSbqhhXHJNUzvkCgYAeFRLsqWHTPuGDpfuoCq4BijJqCPDIGLCR2vNZ +/BkA2fr3f9KBAlLR7be1uI5U8heGCekOqNbT8vRV9Ev+GHK94PvbKIbrWtUx9KzC +MoMzRyWHJueRx4LgKwwJKQCjypQu8oimIV7Os++AdnJwVF/SQrKL26lPnqOgZ4ax +9e5m8wKBgQCF626EmJk34+WTGEa5gdTx567Y+1EAbag+7fQSskwiRPvRN2fcg3H8 +ynUAtIgWbrecgKhblXxc7zwJrl41P71uQzCFspgvOPXMxL2xqN+tnTfuz84OXk26 +h1xSdS3e+JYsWUIxqbH1W59S+dC7KtklBAcUxb8DNpDoVjVBeAEqzw== +-----END RSA PRIVATE KEY----- diff --git a/extern/IXWebSocket-11.3.2/test/.certs/trusted-ca-crt.pem b/extern/IXWebSocket-11.3.2/test/.certs/trusted-ca-crt.pem new file mode 100644 index 0000000000..a1f7576243 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/.certs/trusted-ca-crt.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDYzCCAkugAwIBAgIUD0V1mxoZF9TNpsoyvuHU2zhrg0wwDQYJKoZIhvcNAQEL +BQAwQTEUMBIGA1UECgwLbWFjaGluZXpvbmUxFDASBgNVBAoMC0lYV2ViU29ja2V0 +MRMwEQYDVQQDDAp0cnVzdGVkLWNhMB4XDTIwMDMxMjIzMDQzN1oXDTMwMDMxMDIz +MDQzN1owQTEUMBIGA1UECgwLbWFjaGluZXpvbmUxFDASBgNVBAoMC0lYV2ViU29j +a2V0MRMwEQYDVQQDDAp0cnVzdGVkLWNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAr2nVpfIzxsxK76Va+HaBfZ7aqjk90zipzH3/CWuMMN9wzBhg2HPE +cRreq1vKm2M/L9CZH6y6fnr68n8lW4rDATmbH0GeY4OqI9jw/mfjL4jUsAxwRi4X +kkk4G2nz1G81LvWLFXXAZlOxeHSZtpPh5OP1tNGiJNL4eGVxjlwFJIFwDvweJ/tW +J7dh/FTzO0jqh8FheJTeJO64Gflqfln64WRUOPSpO7v4KmyesM/BGwGMfZjcwhs/ +KZT+OKXpPgYhdmAZJE24ftwWTP84DP9wnJbNqTRt0r5ud+q8EusKIjw/Pbf/tPUF +7J0bkMp4y5/+7MMuIxeZ+s2uHdp6hmwdJQIDAQABo1MwUTAdBgNVHQ4EFgQUPARq +Vm19yGgWqEnpNT1ILIkfWhEwHwYDVR0jBBgwFoAUPARqVm19yGgWqEnpNT1ILIkf +WhEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAEIsTvJhs6r2r +x1xrHKaGo4sSuywJiZqMabvC9g22Xw3Cno5qGVFYWi4k0qjX/j9DN36DyOY1rei+ +kNBnOnLdtdNDltcvaLeA/9SeIhxRYOwjXpPzy9AqHpGZPui988qtptA+DI+IOLAm +mQyssYC4doDcohMXaI7KumKHojTDAPrF2INJRTF9zWgbsFjvSWU5CY5CNERWCydh +OXfzFylifScNOppioZL9VTa6At7R+MGg834kMi6WDIvtD6Ibn+pw0bV60aiMhBe8 +8qgZ8lxjGOHlvQrjqdk65smhfaECJcFJxybOSA3Z1f+Y9j/p0e0hyUJM/b/NouaE +64H6vXczLQ== +-----END CERTIFICATE----- diff --git a/extern/IXWebSocket-11.3.2/test/.certs/trusted-ca-crt.srl b/extern/IXWebSocket-11.3.2/test/.certs/trusted-ca-crt.srl new file mode 100644 index 0000000000..4fa18e787c --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/.certs/trusted-ca-crt.srl @@ -0,0 +1 @@ +297E3BFAD1F1F96A60A2AF0F48B092E705C0C68A diff --git a/extern/IXWebSocket-11.3.2/test/.certs/trusted-ca-key.pem b/extern/IXWebSocket-11.3.2/test/.certs/trusted-ca-key.pem new file mode 100644 index 0000000000..5f6dd36e20 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/.certs/trusted-ca-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAr2nVpfIzxsxK76Va+HaBfZ7aqjk90zipzH3/CWuMMN9wzBhg +2HPEcRreq1vKm2M/L9CZH6y6fnr68n8lW4rDATmbH0GeY4OqI9jw/mfjL4jUsAxw +Ri4Xkkk4G2nz1G81LvWLFXXAZlOxeHSZtpPh5OP1tNGiJNL4eGVxjlwFJIFwDvwe +J/tWJ7dh/FTzO0jqh8FheJTeJO64Gflqfln64WRUOPSpO7v4KmyesM/BGwGMfZjc +whs/KZT+OKXpPgYhdmAZJE24ftwWTP84DP9wnJbNqTRt0r5ud+q8EusKIjw/Pbf/ +tPUF7J0bkMp4y5/+7MMuIxeZ+s2uHdp6hmwdJQIDAQABAoIBAH4sPkUTJjMEl5Iw ++nJlq1bUgKyYZ+QaiehRaLU56qjsz5G+p0qKWu6QSUIw0Fdc2AJopPunnq2DgCYV +VqW19fZXnUCqTmd+OU93qEEWMM/sODA5gji4xrOufvEZEQ3ov/R7IgPZov73jFv8 +YuR1ErM1VXMuptad+aOANGIVxo0ubDEXKK/zhOfUUXQy7ZsEruJCCIpigULU159r +sVOq2lwgLz+hClFBIq0IKAKqiPWpw2GtHtU5WtAo3qZEMJkNM5SppjDmS2Wy3qN6 +Gq6sXtlAmLFZAyVpXXklQK3mCaAs5gcV94nm+r++F884obaOtJ126uDdIKlL+A6k +l41DXwECgYEA4KAswbdoa18J1Ql2QtwW3+knEaUO62JH11RO5VV02uiYv4v4mHmA +prnl1jsfgbc3qfIlZWDLlNRovKCfQSj/HzOe4Hd+gEPiSYjA77PRqQeYTPXTf0Ml +IQ3j9z1CdBWNoKJ18CEiIncvjpDYkdFf3RsawcnYXklXRjmm6kIJJzUCgYEAx+oA +gm/xXK28P/CFksZzsseF5i/1MPdniyP3oY34DlEmDvl9ZA1Z52De8vojfNd9X12M +ccjiGMMGgknJqncCB+uTWYFy2pWnr9dVVxf+oirAlT1Z03AkT5gxmIZ3FUQw8VkB +HjKJYD1mpTwoSlc+DR3R0xNdl84nkUI2hxGErDECgYEAjdsZ6MyXGRfP8cYj9V1g +5M8taStAHM7YZ9hKavJo9cZmkLEoscIpySElUQHNh/HZKW5Ox5M1fiwWaOlXKaNm +WqIS99b/AKneQmomzjpVcdXmDNRCWOBilllbWkxJp13lL0jqClgiYnm6guJeotgD +HnN7ll6OUh0nDKZkDxTdCvECgYEAtlQZet2WCKz70GURrjgJNbj7ymFbAvniGekH +5PSSlJw2Vdn+Hs5+fKTBMmIpE6eF1QCBIxXQAD1/Jj0eDLbVx1t33F5P3kQ32AxQ +7UoZFtZfJr35uvnAZEeulCmvWloDOVuvxVbaLEhT4cfoB0VidpwHzrcO2XFQbQ8y +pCW6F0ECgYBbO0NU/Jlu3acIzGwAv69CMo8udwnWrhzGStZD67swdQ/yxHVpx2RH +0sNk6UfLku8Mal7Pp+RglAmsOZEjSgk1V92J9lXYjYD8IUNwNyRRCpQ8xu0KPgDM +XGeUca/Ao7jRVcsPOiqFH7wgfEjyzpO85X/K9BoBnA0EcUTOScaqmw== +-----END RSA PRIVATE KEY----- diff --git a/extern/IXWebSocket-11.3.2/test/.certs/trusted-client-crt.pem b/extern/IXWebSocket-11.3.2/test/.certs/trusted-client-crt.pem new file mode 100644 index 0000000000..e76067ad9b --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/.certs/trusted-client-crt.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDNzCCAh+gAwIBAgIUKX47+tHx+Wpgoq8PSLCS5wXAxoowDQYJKoZIhvcNAQEL +BQAwQTEUMBIGA1UECgwLbWFjaGluZXpvbmUxFDASBgNVBAoMC0lYV2ViU29ja2V0 +MRMwEQYDVQQDDAp0cnVzdGVkLWNhMB4XDTIwMDMxMjIzMDQzN1oXDTIxMDMxMjIz +MDQzN1owRTEUMBIGA1UECgwLbWFjaGluZXpvbmUxFDASBgNVBAoMC0lYV2ViU29j +a2V0MRcwFQYDVQQDDA50cnVzdGVkLWNsaWVudDCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBALijaV0JhdoRAXnD5fX5W/9nZFb6jor6lIGW56Mdn+11ICYw +GoJ7ATnygUwfBMepoD5RfJ5pkNxYewo8N5JR+8rb4V9atJCSYQLT8P7Dm2YNMtkq +mNiRuRLrTqoPYajEzz5ENWSNnsjUB1GMGEpcCvRDgsTF24OsVV9BmLV166BEye7w +ah+jk1YYJHbEnNT4wzr4drJSGEYh2aRO72yY+ROe49Tz/GVVXfCamcj88z5hOS/+ ++nCF/odLLB9Ij4xhR8WwTrwE/TxlkIQRBBPTsNetZjvMQZT+TkKw9nNjdoHiDlz9 +BLOYxovUIB8OtOQQfour8V7nwZ2bL9Pp51mnmBsCAwEAAaMjMCEwHwYDVR0RBBgw +FoIJbG9jYWxob3N0ggkxMjcuMC4wLjEwDQYJKoZIhvcNAQELBQADggEBAFTus7o2 +fQuSMk52qXUESVWG4ygvd2scV58zRrLxZL7Ug9p4DIJo0cY59l3Vhwn2xDSYlAFi +1h/qSEGkR2a0U2LzMK7BPSkqqYceSwnUvnwHvCwgH9aL1Rvk/4f1sFfsKegjScle +wraYsRmpidEZJYICvokHev36mX3fHaZZEU+WIoTvChgu0OtD+qkI4DECywLgtB92 +/geabKC3C5JgiW0Jz8AScWoO2uKHFeuD2nfI1SiAbfMIAmG3RTanbZ8JMEVomVep +txMNGnojun923KTEScnH3cQfnkJjm2AM5yKgT6I/OHELe9Gg7R0IOJbiPmSru7/k +x5tBp3iMsZZ26VE= +-----END CERTIFICATE----- diff --git a/extern/IXWebSocket-11.3.2/test/.certs/trusted-client-key.pem b/extern/IXWebSocket-11.3.2/test/.certs/trusted-client-key.pem new file mode 100644 index 0000000000..1205d564de --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/.certs/trusted-client-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAuKNpXQmF2hEBecPl9flb/2dkVvqOivqUgZbnox2f7XUgJjAa +gnsBOfKBTB8Ex6mgPlF8nmmQ3Fh7Cjw3klH7ytvhX1q0kJJhAtPw/sObZg0y2SqY +2JG5EutOqg9hqMTPPkQ1ZI2eyNQHUYwYSlwK9EOCxMXbg6xVX0GYtXXroETJ7vBq +H6OTVhgkdsSc1PjDOvh2slIYRiHZpE7vbJj5E57j1PP8ZVVd8JqZyPzzPmE5L/76 +cIX+h0ssH0iPjGFHxbBOvAT9PGWQhBEEE9Ow161mO8xBlP5OQrD2c2N2geIOXP0E +s5jGi9QgHw605BB+i6vxXufBnZsv0+nnWaeYGwIDAQABAoIBAFQ2XAEOLdmW9ghW +fBUjRX2I56/wGYFz5rXwYPf5tA625BHm0MCAX7/RRn20jBaQ3EBwJBmQZnzJclzp +uCLpd6E/hlxaX46s5MhIaFuaVc9G59E653mnhTUG09smptE16pwouf2BxlEsu6XK +8u0/a9Oa0xLydztoJ4wJvB/Ph8eRsbdbfL/ZAe+vk+bEp9ugyec3B5KTc+hWRneH +BRfe239OX4mEhxNoO1tPJz1hJLjJH5F/iE1wkjSzLr1SI/cSbcbnyYj/kyXmktZw +uaeFptkT6rB9GO0YunEPzzuQ4EEPpK9F63uu74dGqyW56STq26km7diAHhEpFdp1 +7X0rfHECgYEA5YPtjdqKEn5pQEdehqFnzi3IIu593o7baM6qEyFpMsTP53QCjUKX +rrImyr2opfFKrXrI0IYXlDgOZApb2sKLoeP/wpfZiGSyqrzj+Y49cNRHjH643ClL +Ri5eO6TRBukAW1gQFwuVBPbcnswaU6Ah85uTxqj+hO0g18rkuVdf72MCgYEAzfHH +lb9TMf4DZEoL7GMpc4gDG9V66UWWzXyJB4CWHd6QX1vl6Ow5wE7q3fewD4SNgvDs +DHZ8oqK2OMKJH/h/tqxyu+g1huajOhPqy1TIt5ncMjS0sguQ+7bQeHASKLxHhjPC +YdqGMxOBQI5olWGq5U9Td5TYE95qk50KoIyNnekCgYEAkhMwa1tPC0w3UrjZuZga +yEetHEZsB+0mSgNWjYxzNuO6atYUFbHvdjlepSSmpM74t4bxLn5ZnXU7+4H4SjgN +xMCm9EPPKJbme/Jyqk9UXW5OB2ZT45PIm+dBBHb2ro43MuvOecxeUOWJLuw6SUUe +trwrBoJiU1nU0GMKxceNgH8CgYEAzeNMpDG9S7ply6qXVwEf3Kd6bCY1leaDR/Wb +zMtJyJzL+vmV1RHs/ownFDfeZPUgwGp5olAGdFV1FTOvAS5fB9JJdgBFGxOS1ao5 +zoN5kswYLn0wtNsJXAy9R9rK3Ly2SL2QNGHSTlfOnSqB9e3JeyyeBmvgxaRTKjYS +/MTng5kCgYAIL79seoBnd9ZSp8A7QUBighxBn6DwrvLgexaysmC0zYqxbatczHk9 +iFbQRmPnFHhUt4URhxyhCoTgd7F0JpxklQODNfseVwtDiDMj8Fu8Tfmn6+9GdFRv +0QEU+dR3gi98bO6G4IuAFGO9emXho3Snu6odRmh4HZVNOdLCuQe1Cw== +-----END RSA PRIVATE KEY----- diff --git a/extern/IXWebSocket-11.3.2/test/.certs/trusted-server-crt.pem b/extern/IXWebSocket-11.3.2/test/.certs/trusted-server-crt.pem new file mode 100644 index 0000000000..6d5e9e3b3f --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/.certs/trusted-server-crt.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDNzCCAh+gAwIBAgIUKX47+tHx+Wpgoq8PSLCS5wXAxokwDQYJKoZIhvcNAQEL +BQAwQTEUMBIGA1UECgwLbWFjaGluZXpvbmUxFDASBgNVBAoMC0lYV2ViU29ja2V0 +MRMwEQYDVQQDDAp0cnVzdGVkLWNhMB4XDTIwMDMxMjIzMDQzN1oXDTIxMDMxMjIz +MDQzN1owRTEUMBIGA1UECgwLbWFjaGluZXpvbmUxFDASBgNVBAoMC0lYV2ViU29j +a2V0MRcwFQYDVQQDDA50cnVzdGVkLXNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAMK5XAcHJwVSor1SfoMM5H5aNfNnM4JKq8kfAOl6KlXCsgs3 +bBcrJ24gEG6/goxkgLxhC1SXdbebt3Jay2lxAa9/7Uj87yztozSsctMkxXE0u3R+ +ih+9sP7ctpZ1hrF2Gv+ztd49/mXe1iRLPhkPijGpPlNsfie/TYybrw3WQlGH8jUm +MnW12QUOzoBrIOCO6uIxFBJ1qiMq5mIBLlYOMj+MQubnQdvaQPNf1zaZWsCVGyTv +95roHAb/s70Ie4r4ATcubtZs/ftjvzSmJegodTprPAedkrJ/k6Od9as7hpL37605 +haBU5pMyPNMWYi1MwYc9k0R0IpCKdyeX0huHfpkCAwEAAaMjMCEwHwYDVR0RBBgw +FoIJbG9jYWxob3N0ggkxMjcuMC4wLjEwDQYJKoZIhvcNAQELBQADggEBAI41ZI4Z +WbPFB1e+wIWQE7O2rJMeTEImjBOtcJEN3bqhsdE3Zqk0fPaE6jNz0Fp4IXqUYXzo +SGsgBroV6sgknuLo8HdcTLcg8p9qZ3FGFHFQD1QYINn4ykupJZE2KcrIV8BZ/Tiv +ciFrJ7i/qwpOrTRBV/w47yP3WZ3v8UdBnj5URD0v/yaAfkaReDO59Dlht/wyItQi +GkDczMqMF1GTqcLqBwZdfpHq7B/UI8sp58a6eR9lOgryYCr+QJn7TcZrYzkcSWzg +KE6VuzK6+NElvtg1hSST2Rc/RuuKzexsO/PLesVzaU/6NdDwXmpuSxeCiWm1mosA +xfQZ9fSOQG6reFk= +-----END CERTIFICATE----- diff --git a/extern/IXWebSocket-11.3.2/test/.certs/trusted-server-key.pem b/extern/IXWebSocket-11.3.2/test/.certs/trusted-server-key.pem new file mode 100644 index 0000000000..f7387bdf8e --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/.certs/trusted-server-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAwrlcBwcnBVKivVJ+gwzkflo182czgkqryR8A6XoqVcKyCzds +FysnbiAQbr+CjGSAvGELVJd1t5u3clrLaXEBr3/tSPzvLO2jNKxy0yTFcTS7dH6K +H72w/ty2lnWGsXYa/7O13j3+Zd7WJEs+GQ+KMak+U2x+J79NjJuvDdZCUYfyNSYy +dbXZBQ7OgGsg4I7q4jEUEnWqIyrmYgEuVg4yP4xC5udB29pA81/XNplawJUbJO/3 +mugcBv+zvQh7ivgBNy5u1mz9+2O/NKYl6Ch1Oms8B52Ssn+To531qzuGkvfvrTmF +oFTmkzI80xZiLUzBhz2TRHQikIp3J5fSG4d+mQIDAQABAoIBACFw4dwXH11rpqUq +4K0y7p7AcVl+1LrAhiYBHA/8uf6GdDs25mpIL/paqVfLrejcbbtsUxzQ8hd5N5T9 +AMf371kreB27ynuFyCyInSOjwgDCFJtaC/CNjDMIxpaqUlpxtQtK2qXzMZhfH5mW +DnERWSNUNG7xR+0djnziU7rlm/gSOxUA2gS/5ik9JXAx0yoML7HWlBbk0PJ5o8Ac +sy6w/YwJVZAjXyUuYptPy2bK8WpGAsthw6RmW1fdOdDAUC3wz7TIKLuPD0AP9g7j +u8grDYtD+U3ls1Z1Grow2UUG8CedotzVE8KIhDWi35aNiGIuaMFnnLf2OO/Mgd6G +V82kkLECgYEA4sBtNsmlfFteFmHPS0s8wzg7lzLN15yifk7kO9GQcsbymXVSgU93 +XnvADAflly382pyMpr7Fb6V126iOx+YQhr6ya116S4UtAKq4kCau3Im6OedKefwx +B71rST+vuAlUcv2ZcAVRJK8GtQQvwcAeI24ShPOXC+vyFAaaZ5eZo/0CgYEA29dZ +LcREVlv2Tgy/YJVnZ7EYRGiheuF0rl0d0+Stggj34fSS1cexv3gMF13HBk2HpXfW +3LfJyj2iGRZE9OUjN0ozVIVZWgZS/cwZbEUyl3o6IK0kPE4fZBaE16Onh4OGXKwr +XTG+EjmIJRVRawECbLMONj5rHQLdcy+5YIH28c0CgYEA1XSL2y2MCTsBoVRGDf0v +oB7JihYbTEN5fCnMFLu8nS/HpMqa9nvWRS19plWwvdZe13TTuwyPVACQqE1Oy8M5 +/354+zUuMPWXXa9YuuqPZbCJjIS8yYSsqzqXSocXZcnyo6Uz0g5PSpcxWyorwtqW +BIhUCrA8ms5sPonQxIAj9AkCgYBd/6g7722g11VrbfvuWjOKnKhZp7tUBU6Ut2/n +iCHANgF3ddHK4sXXrobM/uX4hfH4CFOwsEzx0oSa4XC+nbL/ExT7kMDxwz59EmXU +a4oERtjP2/hgaK73ZsGKSol5Yf1zZpJsGLbCqCLUaFcVv6q/u5faDbpS/0Sc2c0T +vL5QCQKBgQCL6ySxvEb5+zst/kRxXcnefXjoB+LSYsU4zy8WfkcP4r38AAQ2Hn+F +f3/9BUX+2gNr99VDMjI+TUEf+NdQA/nFu4RbFvJ9Wpw9pXkIJpJkZ9g3Why4Ziji +h0IrXm5JCet71+EIMwP0LhKJKrXZudlzP4DYMWmA7Avqb3HIdPXd7Q== +-----END RSA PRIVATE KEY----- diff --git a/extern/IXWebSocket-11.3.2/test/.certs/untrusted-ca-crt.pem b/extern/IXWebSocket-11.3.2/test/.certs/untrusted-ca-crt.pem new file mode 100644 index 0000000000..2f034c9fc4 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/.certs/untrusted-ca-crt.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDZzCCAk+gAwIBAgIUBEbp5x1IlwAV6OcQ/4xHk1Y+K4UwDQYJKoZIhvcNAQEL +BQAwQzEUMBIGA1UECgwLbWFjaGluZXpvbmUxFDASBgNVBAoMC0lYV2ViU29ja2V0 +MRUwEwYDVQQDDAx1bnRydXN0ZWQtY2EwHhcNMjAwMzEyMjMwNDM3WhcNMzAwMzEw +MjMwNDM3WjBDMRQwEgYDVQQKDAttYWNoaW5lem9uZTEUMBIGA1UECgwLSVhXZWJT +b2NrZXQxFTATBgNVBAMMDHVudHJ1c3RlZC1jYTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAMmmYROZf/Kg46b/0Zvq4pUY8ghUEA+eYit8dLyUZ/onoW4l +xl3CK5NhJIer62Olv7QIu8WhU/hYoeE+8lLva9v0HaJgGjKmPQ3tyej319PzIc7o +uKatrQ0BAi/KReBQOoqAGqa+DBIGAHoi29x4wZ/ZGSjeVManNb58Lz3+caFlZRCW +8vcrE5J8OcpD+0O/CKM1UJDlTVFSBJS229my5WjxQnfNZeuxRnMxOCah/qaJsZZr +FdRd0th2mRZtpjM8vZfXuoUcK+XVSENuJKdqFR4hXQU5Xq62ofxz+IiToPHO24zi +S1lp7ggeIrgZXaz2I+7LmIy6gnZWP6oXE8XcyW8CAwEAAaNTMFEwHQYDVR0OBBYE +FJLe6w7SsBTwFnIQYjjjH16p/3zDMB8GA1UdIwQYMBaAFJLe6w7SsBTwFnIQYjjj +H16p/3zDMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAGrHNKNe +5UqrNPdIXlGwpabOdrhmAc9yN/tXiB386lByktIeOShS6pvD+UuV14PcTXUFGCwW +2o8I5OE/+O8w+InyWyV7qC7dgeWyEL4qDAuIYmxs71T2VOv/eekYp1Zq/o3kL3hI +f0oxonJVZXkR4p39L4TCS3z6EiWRJxWlI4LVNcvWgkwJB8w7wIxSbql0Y/EO9yoU +07u8QHVj7Nth7YteacOpj8jEy42SuWq5sdW7ccMgEfptRSYiVAmgD7mOCaELCBHz +NVqyLRPkvWqX7apqDy9vR3ZnMiHWEpTPeQqK12GJbVMW53AVEDWKiL0bhrjnY/uS +dwnpMp7fEUJLXQk= +-----END CERTIFICATE----- diff --git a/extern/IXWebSocket-11.3.2/test/.certs/untrusted-ca-crt.srl b/extern/IXWebSocket-11.3.2/test/.certs/untrusted-ca-crt.srl new file mode 100644 index 0000000000..7e2278e1d8 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/.certs/untrusted-ca-crt.srl @@ -0,0 +1 @@ +5CB637D0B24622D344F4C956FE5930B22CF87221 diff --git a/extern/IXWebSocket-11.3.2/test/.certs/untrusted-ca-key.pem b/extern/IXWebSocket-11.3.2/test/.certs/untrusted-ca-key.pem new file mode 100644 index 0000000000..aeb029bdf2 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/.certs/untrusted-ca-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAyaZhE5l/8qDjpv/Rm+rilRjyCFQQD55iK3x0vJRn+iehbiXG +XcIrk2Ekh6vrY6W/tAi7xaFT+Fih4T7yUu9r2/QdomAaMqY9De3J6PfX0/Mhzui4 +pq2tDQECL8pF4FA6ioAapr4MEgYAeiLb3HjBn9kZKN5Uxqc1vnwvPf5xoWVlEJby +9ysTknw5ykP7Q78IozVQkOVNUVIElLbb2bLlaPFCd81l67FGczE4JqH+pomxlmsV +1F3S2HaZFm2mMzy9l9e6hRwr5dVIQ24kp2oVHiFdBTlerrah/HP4iJOg8c7bjOJL +WWnuCB4iuBldrPYj7suYjLqCdlY/qhcTxdzJbwIDAQABAoIBAQCWRBLRLTDoWDZs +6vODEczZOGacCDCTwv3609qV8K1u/3tPfnzMv3YDdH9pTpaxggFSIrPyeN7/EOVI +2cRwQxQIK2it6Jl9Jt4WdB1jKtW9js+hxVBcfM2ZBChh/oSFvKNzNDUoDjUmdSyD +11gpeh8ng/s4tj1Mb6wgD6CQvPxmPLsJZ3swxdSFgR5hpXXELtAK+oOlP0Y6SFpi +d5AyiaMP9imBKQV7qgJSiKWVtSAvMhfCOPaeYM9wPCA9nha6dYGC8Fgh9FklOf2+ +fj+0dqmbWwa3xuEBfZ7oS+uKnzzcBvTxtNz/U8b9bPzTtoJU6Z6P3wLIB5x8DgQ3 +NcDqVbtRAoGBAOnEl1hfHsm1Ni0flugvNSY5pRF9CGQjbTk2tQxEfsPc7LiNZxjF +NFyJK2wVs17bsCI4PUO9nnMjnCi86SMKj0ifVoroYlMkt4ruY9iQPTLbrJpTBF/X +LkU77s6TSeOQdzUlVPcIXfTCYwguicpIP6kOcohHplmzdurWtl723GqHAoGBANzT +1G2h8dS7UtR0GRO4u9QM8jhRFszariovI6eOEKPaVhBhPeiwwcWRq40un7koCLzU +WA5CV6h1fGQVvN8pjpZdXYUAa26jlnISQLvNgNvwD2b5UjRi4tH2QuV0LOAMiMGs +vcQtpjM12RNfii/Tdun0mYZ9pcb65T4p5VubM9vZAoGADl4i3y+ZeNRGbCeQ4txj +6+GHH7gLl/wFborKPeLH18nwUrd+KquUOEvF+3Kp/56JCNFkEpHI91Ks+mQCAEFZ +5SDF9Ourf2i2Tzevs1PKLyIJTcLkde+HzIGOf+vVksMCUKXmvvgori50X8Bcf65J +G17j8zRUKRc6q9xegR+zFGkCgYEAtA2UG3/76nSCaO/wsn/hxlh39ytG5+k2MPcW +nzvanX8cxWZEUEIu/KR1uDvXx+S4mx6YXagCSTziG8kNovgDZt7hrdxVvHRt6ryv +Q3GgK7RlGpUXTdeDEac1jFlZbaVKrH/oitidtwuk34L67VwCjWf+9gXk8YUI/dKz +TCoT8qECgYEAyUbioIZuc6iWF2oIk3VuPdWHUvhuhzvYr+gb++P/xIXraEUMI81c +UFUDOw+jVVF6H0aioD1rRUczF9vJVE1pUZrXHbAViPECr6QgZTl1mluHtxnT8Asq +7sXS69HdlV+k+P+YZ51qRXRLKZsjwSJwn8fCRWS+7HPdQ3ogIWp1Q+A= +-----END RSA PRIVATE KEY----- diff --git a/extern/IXWebSocket-11.3.2/test/.certs/untrusted-client-crt.pem b/extern/IXWebSocket-11.3.2/test/.certs/untrusted-client-crt.pem new file mode 100644 index 0000000000..120073c77e --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/.certs/untrusted-client-crt.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDOzCCAiOgAwIBAgIUXLY30LJGItNE9MlW/lkwsiz4ciEwDQYJKoZIhvcNAQEL +BQAwQzEUMBIGA1UECgwLbWFjaGluZXpvbmUxFDASBgNVBAoMC0lYV2ViU29ja2V0 +MRUwEwYDVQQDDAx1bnRydXN0ZWQtY2EwHhcNMjAwMzEyMjMwNDM3WhcNMjEwMzEy +MjMwNDM3WjBHMRQwEgYDVQQKDAttYWNoaW5lem9uZTEUMBIGA1UECgwLSVhXZWJT +b2NrZXQxGTAXBgNVBAMMEHVudHJ1c3RlZC1jbGllbnQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC5SOVG06/37lekGxkBJUt7AN3Xw708jN8XI7DR1sq+ +NPeGN/wEfCUSIHJXQq1fqBJQkYKpyYa9EkvQs2RhrOXahul3ZdX1kP3zxQLvvbxU +EcB2gMS4B61EqnmBHRMsj+dI91++YSEFE1hkolD3+gQtm0+FVbPoXt5Y3rBAF/l0 +UMvrBsgraB12OHUlqqj8WkUIul37u8XcnsnPWKoigWb2k+/W47LCGsd+haRnulIK +ADQOsjNs7wy3IV9d8zCifEV0YUT5ZPBg2K2f1lpYfOSobK7JLqgV03HVrkROQfej +FTvMRtDAxlsa6bHLrGUeBhCNaO7SLj16oo5nMCq4DnTjAgMBAAGjIzAhMB8GA1Ud +EQQYMBaCCWxvY2FsaG9zdIIJMTI3LjAuMC4xMA0GCSqGSIb3DQEBCwUAA4IBAQDH +7XbX6dCzUCGj91835gvTPr5FgKrTqocVQ+EtCxJxRVvqB4zj7/80SHxByyWz9XJQ +IBZmDz298nVqfW6uegq3qU29sG9OAOOg6I0SpWOL9qq/ZKMoEqRv6fHnjHhRiOwT +isqdZISh1vhoIvcUpNsm1PwpaDxerjeE3oPyuNO0P0lKI5jykO3orDANGvyC8fzx +jxlDsSXCgmcaPh99752vBe8UlY1M8t4GxsJAV8DXxdDZCYIWMe+/C5aQ2xDvj3+l +vYht9+yc6ebl5uGOttgWSYPxdryCynDKsdBfXxet9Ix/qdsLF9hwU2JokVDh50J+ +er36eML3WvEO2HuBKTq8 +-----END CERTIFICATE----- diff --git a/extern/IXWebSocket-11.3.2/test/.certs/untrusted-client-key.pem b/extern/IXWebSocket-11.3.2/test/.certs/untrusted-client-key.pem new file mode 100644 index 0000000000..d7040cdd92 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/.certs/untrusted-client-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAuUjlRtOv9+5XpBsZASVLewDd18O9PIzfFyOw0dbKvjT3hjf8 +BHwlEiByV0KtX6gSUJGCqcmGvRJL0LNkYazl2obpd2XV9ZD988UC7728VBHAdoDE +uAetRKp5gR0TLI/nSPdfvmEhBRNYZKJQ9/oELZtPhVWz6F7eWN6wQBf5dFDL6wbI +K2gddjh1Jaqo/FpFCLpd+7vF3J7Jz1iqIoFm9pPv1uOywhrHfoWkZ7pSCgA0DrIz +bO8MtyFfXfMwonxFdGFE+WTwYNitn9ZaWHzkqGyuyS6oFdNx1a5ETkH3oxU7zEbQ +wMZbGumxy6xlHgYQjWju0i49eqKOZzAquA504wIDAQABAoIBAHle6OG2dUShmkNj +hMOdXI5ciPV3wRRS6yhLNt6eJvzl0WbYcXu2nsn6+ytyAAPzItwoFUGHQ33C6Grz +uEPLcF3vliuiR7+ulMwEN+I3lZA0eLCntTUfwj6CtUkAdLjyIv1HHi6ljW23uGVj +dkqaOfZuEG81Lr5+toPci/PQQJYR4btVJJHCXJ6KVx6w8i++fwcRwby9riNhWAzk +8OhUiSTTsx9sioBk62QRB8Qs0LVR5tGbDSrpQW5Ns9KnH7sayInwEN94PTsPKcqY +i/oNNZG+qvSf8jG8QiIMdyGx/goVKuQVx5Gev8my5mnfuVM/oXB20T56z2iII64V +kNh/sNECgYEA3qjAPqTY0rnu2tmvQN1PwAfyENz3XmTVWpVtBvedPj3qiV3mXJGZ +qQoS0wb/2t/D05GhTxBJARk8foorNzGchMVtECMlGxDAs1vBw6dwSK5hJnw47PQQ +Q68Vz/zwvrzJgmeijPow87PdpomYECgerTa6BynH8W0ffuSNIJC8Ke8CgYEA1Qd2 +FpwFjUFqhYbcvR3VG8qAMIF8RKLzmQDDZh7liKMeHLypdXRz1ZEa6NFkvsg7qRZh +ahe/ULubnRhdOxs0JVyZPS0dU3ZmHT9bBcIuLzC//5e1ictXUZspFzIHE9T4suLC +Xnh2vqQzlEy3iZLx5B6FMzc3ws7LM7q7L2AfqE0CgYAMkvEQWJTaCaAAgeyQuC7J +xGkaJLBfh0g5LlkS3Kbnne2Bxmi874gC8MuxWSLXxG01pHK8mUnWIwu0ha79FfMl +2FRZZfKxfZe0SUk++FSx9g8MclVwpDPK7rdHoJwj2Vtz3tBiL7rV+GFbB0gsGWfq +Fj4ZK3XcH3J44wVJQoMtxwKBgQDM/ZkMuKY+/yvZwaS39vUTARHJm1BRW9y85pcg +tap6iTx4urL2a1Drue4DCzu+uj9uvjKPPLrEnUNpMADG166eJTTwQXFu1wf8LPMR +34FBt8+JzBrMtfcYeA5aW7Gjy9Rljv8qmRDq8mcP1aLnp5dMxHG4jvIBa6zt4kot +lHniIQKBgQDWuMWA2Q1kcKKy7OJszp60jO+ftq306QMoDsPNFLUUUtCxNSrpAeC2 +MVvI4kzIn+6hYsMdRsqDSadosuKE4ZzCPIfuyadiAKTAO5esBJs7KAQFMJXSnfY7 ++Zs1QUcdLZAWivO7j3ZASbR8L/1mawlBMgyIaT9YKp1+iW+uzaYgUQ== +-----END RSA PRIVATE KEY----- diff --git a/extern/IXWebSocket-11.3.2/test/.gitignore b/extern/IXWebSocket-11.3.2/test/.gitignore new file mode 100644 index 0000000000..910eaa657a --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/.gitignore @@ -0,0 +1,10 @@ +CMakeCache.txt +package-lock.json +CMakeFiles +ixwebsocket_unittest +cmake_install.cmake +node_modules +ixwebsocket +Makefile +build +ixwebsocket_unittest.xml diff --git a/extern/IXWebSocket-11.3.2/test/CMakeLists.txt b/extern/IXWebSocket-11.3.2/test/CMakeLists.txt new file mode 100644 index 0000000000..661aebb195 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/CMakeLists.txt @@ -0,0 +1,98 @@ +# +# Author: Benjamin Sergeant +# Copyright (c) 2018 Machine Zone, Inc. All rights reserved. +# +cmake_minimum_required (VERSION 3.14) +project (ixwebsocket_unittest) + +set (CMAKE_CXX_STANDARD 11) + +option(USE_TLS "Add TLS support" ON) + +# Shared sources +set (TEST_TARGET_NAMES + IXSocketTest + IXSocketConnectTest + IXWebSocketServerTest + IXWebSocketTestConnectionDisconnection + IXUrlParserTest + IXHttpClientTest + IXUnityBuildsTest + IXHttpTest + IXDNSLookupTest + IXWebSocketSubProtocolTest + # IXWebSocketBroadcastTest ## FIXME was depending on cobra / take a broadcast server from ws + IXStrCaseCompareTest +) + +# Some unittest don't work on windows yet +# Windows without TLS does not have hmac yet +if (UNIX) + list(APPEND TEST_TARGET_NAMES + IXWebSocketCloseTest + + # Fail on Windows in CI probably because the pathing is wrong and + # some resource files cannot be found + IXHttpServerTest + IXWebSocketChatTest + ) +endif() + +if (USE_ZLIB) + list(APPEND TEST_TARGET_NAMES + IXWebSocketPerMessageDeflateCompressorTest + ) +endif() + +# Ping test fails intermittently, disabling them for now +# IXWebSocketPingTest.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 + +add_library(ixwebsocket_test) +target_sources(ixwebsocket_test PRIVATE + ${JSONCPP_SOURCES} + test_runner.cpp + IXTest.cpp + ../third_party/msgpack11/msgpack11.cpp +) +target_compile_definitions(ixwebsocket_test PRIVATE ${TEST_PROGRAMS_DEFINITIONS}) +target_include_directories(ixwebsocket_test PRIVATE + ${PROJECT_SOURCE_DIR}/Catch2/single_include + ../third_party +) +target_link_libraries(ixwebsocket_test ixwebsocket) +target_link_libraries(ixwebsocket_test spdlog) + +foreach(TEST_TARGET_NAME ${TEST_TARGET_NAMES}) + add_executable(${TEST_TARGET_NAME} + ${TEST_TARGET_NAME}.cpp + ) + + target_include_directories(${TEST_TARGET_NAME} PRIVATE + ${PROJECT_SOURCE_DIR}/Catch2/single_include + ../third_party + ../third_party/msgpack11 + ) + + target_compile_definitions(${TEST_TARGET_NAME} PRIVATE SPDLOG_COMPILED_LIB=1) + + if (APPLE AND USE_TLS) + target_link_libraries(${TEST_TARGET_NAME} "-framework foundation" "-framework security") + endif() + + # library with the most dependencies come first + target_link_libraries(${TEST_TARGET_NAME} ixwebsocket_test) + target_link_libraries(${TEST_TARGET_NAME} ixwebsocket) + + target_link_libraries(${TEST_TARGET_NAME} spdlog) + + add_test(NAME ${TEST_TARGET_NAME} + COMMAND ${TEST_TARGET_NAME} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +endforeach() diff --git a/extern/IXWebSocket-11.3.2/test/Catch2/single_include/catch.hpp b/extern/IXWebSocket-11.3.2/test/Catch2/single_include/catch.hpp new file mode 100644 index 0000000000..f9d6a1d872 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/Catch2/single_include/catch.hpp @@ -0,0 +1,14995 @@ +/* + * Catch v2.7.1 + * Generated: 2019-04-05 18:22:37.720122 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2019 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +// start catch.hpp + + +#define CATCH_VERSION_MAJOR 2 +#define CATCH_VERSION_MINOR 7 +#define CATCH_VERSION_PATCH 1 + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// start catch_suppress_warnings.h + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif +#elif defined __GNUC__ + // Because REQUIREs trigger GCC's -Wparentheses, and because still + // supported version of g++ have only buggy support for _Pragmas, + // Wparentheses have to be suppressed globally. +# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details + +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wpadded" +#endif +// end catch_suppress_warnings.h +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +# define CATCH_CONFIG_ALL_PARTS +#endif + +// In the impl file, we want to have access to all parts of the headers +// Can also be used to sanely support PCHs +#if defined(CATCH_CONFIG_ALL_PARTS) +# define CATCH_CONFIG_EXTERNAL_INTERFACES +# if defined(CATCH_CONFIG_DISABLE_MATCHERS) +# undef CATCH_CONFIG_DISABLE_MATCHERS +# endif +# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# endif +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) +// start catch_platform.h + +#ifdef __APPLE__ +# include +# if TARGET_OS_OSX == 1 +# define CATCH_PLATFORM_MAC +# elif TARGET_OS_IPHONE == 1 +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS +#endif + +// end catch_platform.h + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// start catch_user_interfaces.h + +namespace Catch { + unsigned int rngSeed(); +} + +// end catch_user_interfaces.h +// start catch_tag_alias_autoregistrar.h + +// start catch_common.h + +// start catch_compiler_capabilities.h + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +#ifdef __cplusplus + +# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +# define CATCH_CPP14_OR_GREATER +# endif + +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +# endif + +#endif + +#if defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#ifdef __clang__ + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") +# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Assume that non-Windows platforms support posix signals by default +#if !defined(CATCH_PLATFORM_WINDOWS) + #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) + #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#endif + +#ifdef __OS400__ +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# define CATCH_CONFIG_COLOUR_NONE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +# if _MSC_VER >= 1900 // Visual Studio 2015 or newer +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +# endif + +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +# define CATCH_CONFIG_COLOUR_NONE +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif + +// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ +// _MSVC_TRADITIONAL == 0 means new conformant preprocessor +// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif + +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + +//////////////////////////////////////////////////////////////////////////////// +// DJGPP +#ifdef __DJGPP__ +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) + #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// Use of __COUNTER__ is suppressed during code analysis in +// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly +// handled by it. +// Otherwise all supported compilers support COUNTER macro, +// but user still might want to turn it off +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Check if string_view is available and usable +// The check is split apart to work around v140 (VS2015) preprocessor issue... +#if defined(__has_include) +#if __has_include() && defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW +#endif +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Check if optional is available and usable +#if defined(__has_include) +# if __has_include() && defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL +# endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // __has_include + +//////////////////////////////////////////////////////////////////////////////// +// Check if variant is available and usable +#if defined(__has_include) +# if __has_include() && defined(CATCH_CPP17_OR_GREATER) +# if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 +# include +# if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) +# define CATCH_CONFIG_NO_CPP17_VARIANT +# else +# define CATCH_INTERNAL_CONFIG_CPP17_VARIANT +# endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) +# else +# define CATCH_INTERNAL_CONFIG_CPP17_VARIANT +# endif // defined(__clang__) && (__clang_major__ < 8) +# endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // __has_include + +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif +// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. +#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) +# define CATCH_CONFIG_WCHAR +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +# define CATCH_CONFIG_CPP17_OPTIONAL +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) +# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +# define CATCH_CONFIG_POLYFILL_ISNAN +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS +#endif + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif + +// end catch_compiler_capabilities.h +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#include +#include +#include + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy {}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + +namespace Catch { + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + + class NonCopyable { + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; + + protected: + NonCopyable(); + virtual ~NonCopyable(); + }; + + struct SourceLineInfo { + + SourceLineInfo() = delete; + SourceLineInfo( char const* _file, std::size_t _line ) noexcept + : file( _file ), + line( _line ) + {} + + SourceLineInfo( SourceLineInfo const& other ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo( SourceLineInfo&& ) noexcept = default; + SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; + + bool empty() const noexcept; + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // Bring in operator<< from global namespace into Catch namespace + // This is necessary because the overload of operator<< above makes + // lookup stop at namespace Catch + using ::operator<<; + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() const; + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) + +// end catch_common.h +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + +// end catch_tag_alias_autoregistrar.h +// start catch_test_registry.h + +// start catch_interfaces_testcase.h + +#include + +namespace Catch { + + class TestSpec; + + struct ITestInvoker { + virtual void invoke () const = 0; + virtual ~ITestInvoker(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + +} + +// end catch_interfaces_testcase.h +// start catch_stringref.h + +#include +#include +#include + +namespace Catch { + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. c_str() must return a null terminated + /// string, however, and so the StringRef will internally take ownership + /// (taking a copy), if necessary. In theory this ownership is not externally + /// visible - but it does mean (substring) StringRefs should not be shared between + /// threads. + class StringRef { + public: + using size_type = std::size_t; + + private: + friend struct StringRefTestAccess; + + char const* m_start; + size_type m_size; + + char* m_data = nullptr; + + void takeOwnership(); + + static constexpr char const* const s_empty = ""; + + public: // construction/ assignment + StringRef() noexcept + : StringRef( s_empty, 0 ) + {} + + StringRef( StringRef const& other ) noexcept + : m_start( other.m_start ), + m_size( other.m_size ) + {} + + StringRef( StringRef&& other ) noexcept + : m_start( other.m_start ), + m_size( other.m_size ), + m_data( other.m_data ) + { + other.m_data = nullptr; + } + + StringRef( char const* rawChars ) noexcept; + + StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + ~StringRef() noexcept { + delete[] m_data; + } + + auto operator = ( StringRef const &other ) noexcept -> StringRef& { + delete[] m_data; + m_data = nullptr; + m_start = other.m_start; + m_size = other.m_size; + return *this; + } + + operator std::string() const; + + void swap( StringRef& other ) noexcept; + + public: // operators + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator != ( StringRef const& other ) const noexcept -> bool; + + auto operator[] ( size_type index ) const noexcept -> char; + + public: // named queries + auto empty() const noexcept -> bool { + return m_size == 0; + } + auto size() const noexcept -> size_type { + return m_size; + } + + auto numberOfCharacters() const noexcept -> size_type; + auto c_str() const -> char const*; + + public: // substrings and searches + auto substr( size_type start, size_type size ) const noexcept -> StringRef; + + // Returns the current start pointer. + // Note that the pointer can change when if the StringRef is a substring + auto currentData() const noexcept -> char const*; + + private: // ownership queries - may not be consistent between calls + auto isOwned() const noexcept -> bool; + auto isSubstring() const noexcept -> bool; + }; + + auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string; + auto operator + ( StringRef const& lhs, char const* rhs ) -> std::string; + auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string; + + auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; + auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; + + inline auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } + +} // namespace Catch + +inline auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + +// end catch_stringref.h +// start catch_type_traits.hpp + + +#include + +namespace Catch{ + +#ifdef CATCH_CPP17_OR_GREATER + template + inline constexpr auto is_unique = std::true_type{}; + + template + inline constexpr auto is_unique = std::bool_constant< + (!std::is_same_v && ...) && is_unique + >{}; +#else + +template +struct is_unique : std::true_type{}; + +template +struct is_unique : std::integral_constant +::value + && is_unique::value + && is_unique::value +>{}; + +#endif +} + +// end catch_type_traits.hpp +// start catch_preprocessor.hpp + + +#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ +#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) + +#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME2(Name, ...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME3(Name, __VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME3(Name,...) Name " - " #__VA_ARGS__ +#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME(Name,...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME2(Name, INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#else +// MSVC is adding extra space and needs more calls to properly remove () +#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME3(Name,...) Name " -" #__VA_ARGS__ +#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME1(Name, ...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME2(Name, __VA_ARGS__) +#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME(Name, ...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME1(Name, INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LIST(types) Catch::TypeList + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(types)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,INTERNAL_CATCH_REMOVE_PARENS(types)) + +// end catch_preprocessor.hpp +// start catch_meta.hpp + + +#include + +namespace Catch { +template< typename... > +struct TypeList {}; + +template< typename... > +struct append; + +template< template class L1 + , typename...E1 + , template class L2 + , typename...E2 +> +struct append< L1, L2 > { + using type = L1; +}; + +template< template class L1 + , typename...E1 + , template class L2 + , typename...E2 + , typename...Rest +> +struct append< L1, L2, Rest...> { + using type = typename append< L1, Rest... >::type; +}; + +template< template class + , typename... +> +struct rewrap; + +template< template class Container + , template class List + , typename...elems +> +struct rewrap> { + using type = TypeList< Container< elems... > >; +}; + +template< template class Container + , template class List + , class...Elems + , typename...Elements> + struct rewrap, Elements...> { + using type = typename append>, typename rewrap::type>::type; +}; + +template< template class...Containers > +struct combine { + template< typename...Types > + struct with_types { + template< template class Final > + struct into { + using type = typename append, typename rewrap::type...>::type; + }; + }; +}; + +template +struct always_false : std::false_type {}; + +} // namespace Catch + +// end catch_meta.hpp +namespace Catch { + +template +class TestInvokerAsMethod : public ITestInvoker { + void (C::*m_testAsMethod)(); +public: + TestInvokerAsMethod( void (C::*testAsMethod)() ) noexcept : m_testAsMethod( testAsMethod ) {} + + void invoke() const override { + C obj; + (obj.*m_testAsMethod)(); + } +}; + +auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker*; + +template +auto makeTestInvoker( void (C::*testAsMethod)() ) noexcept -> ITestInvoker* { + return new(std::nothrow) TestInvokerAsMethod( testAsMethod ); +} + +struct NameAndTags { + NameAndTags( StringRef const& name_ = StringRef(), StringRef const& tags_ = StringRef() ) noexcept; + StringRef name; + StringRef tags; +}; + +struct AutoReg : NonCopyable { + AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept; + ~AutoReg(); +}; + +} // end namespace Catch + +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \ + namespace{ \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ + void test(); \ + }; \ + } \ + void TestName::test() + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION( TestName, ... ) \ + template \ + static void TestName() + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \ + namespace{ \ + template \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName ) { \ + void test(); \ + }; \ + } \ + template \ + void TestName::test() +#endif + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ + static void TestName(); \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE( ... ) \ + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &QualifiedMethod ), CATCH_INTERNAL_LINEINFO, "&" #QualifiedMethod, Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + void TestName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(TestName, TestFunc, Name, Tags, ... )\ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + template \ + static void TestFunc();\ + namespace {\ + template \ + struct TestName{\ + template \ + TestName(Ts...names){\ + CATCH_INTERNAL_CHECK_UNIQUE_TYPES(CATCH_REC_LIST(INTERNAL_CATCH_REMOVE_PARENS, __VA_ARGS__)) \ + using expander = int[];\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ names, Tags } ), 0)... };/* NOLINT */ \ + }\ + };\ + INTERNAL_CATCH_TEMPLATE_REGISTRY_INITIATE(TestName, Name, __VA_ARGS__) \ + }\ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + template \ + static void TestFunc() + +#if defined(CATCH_CPP17_OR_GREATER) +#define CATCH_INTERNAL_CHECK_UNIQUE_TYPES(...) static_assert(Catch::is_unique<__VA_ARGS__>,"Duplicate type detected in declaration of template test case"); +#else +#define CATCH_INTERNAL_CHECK_UNIQUE_TYPES(...) static_assert(Catch::is_unique<__VA_ARGS__>::value,"Duplicate type detected in declaration of template test case"); +#endif + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, __VA_ARGS__ ) ) +#endif + + #define INTERNAL_CATCH_TEMPLATE_REGISTRY_INITIATE(TestName, Name, ...)\ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + TestName(CATCH_REC_LIST_UD(INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME,Name, __VA_ARGS__));\ + return 0;\ + }(); + + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(TestName, TestFuncName, Name, Tags, TmplTypes, TypesList) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + template static void TestFuncName(); \ + namespace { \ + template \ + struct TestName { \ + TestName() { \ + CATCH_INTERNAL_CHECK_UNIQUE_TYPES(Types...) \ + int index = 0; \ + using expander = int[]; \ + constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\ + constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\ + constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFuncName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + "<" + std::string(types_list[index % num_types]) + ">", Tags } ), index++, 0)... };/* NOLINT */\ + } \ + }; \ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ + using TestInit = Catch::combine \ + ::with_types::into::type; \ + TestInit(); \ + return 0; \ + }(); \ + } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + template \ + static void TestFuncName() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...)\ + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ),Name,Tags,__VA_ARGS__) +#else + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...)\ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, __VA_ARGS__ ) ) +#endif + + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, ... ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ \ + template \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName ) { \ + void test();\ + };\ + template \ + struct TestNameClass{\ + template \ + TestNameClass(Ts...names){\ + CATCH_INTERNAL_CHECK_UNIQUE_TYPES(CATCH_REC_LIST(INTERNAL_CATCH_REMOVE_PARENS, __VA_ARGS__)) \ + using expander = int[];\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ names, Tags } ), 0)... };/* NOLINT */ \ + }\ + };\ + INTERNAL_CATCH_TEMPLATE_REGISTRY_INITIATE(TestNameClass, Name, __VA_ARGS__)\ + }\ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS\ + template \ + void TestName::test() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, __VA_ARGS__ ) ) +#endif + + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(TestNameClass, TestName, ClassName, Name, Tags, TmplTypes, TypesList)\ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + template \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName ) { \ + void test();\ + };\ + namespace {\ + template\ + struct TestNameClass{\ + TestNameClass(){\ + CATCH_INTERNAL_CHECK_UNIQUE_TYPES(Types...)\ + int index = 0;\ + using expander = int[];\ + constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\ + constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\ + constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + "<" + std::string(types_list[index % num_types]) + ">", Tags } ), index++, 0)... };/* NOLINT */ \ + }\ + };\ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + using TestInit = Catch::combine\ + ::with_types::into::type;\ + TestInit();\ + return 0;\ + }(); \ + }\ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + template \ + void TestName::test() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( ClassName, Name, Tags, ... )\ + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( ClassName, Name, Tags, ... )\ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, __VA_ARGS__ ) ) +#endif + +// end catch_test_registry.h +// start catch_capture.hpp + +// start catch_assertionhandler.h + +// start catch_assertioninfo.h + +// start catch_result_type.h + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; }; + + bool isOk( ResultWas::OfType resultType ); + bool isJustInfo( int flags ); + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x01, + + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test + }; }; + + ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ); + + bool shouldContinueOnFailure( int flags ); + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + bool shouldSuppressFailure( int flags ); + +} // end namespace Catch + +// end catch_result_type.h +namespace Catch { + + struct AssertionInfo + { + StringRef macroName; + SourceLineInfo lineInfo; + StringRef capturedExpression; + ResultDisposition::Flags resultDisposition; + + // We want to delete this constructor but a compiler bug in 4.8 means + // the struct is then treated as non-aggregate + //AssertionInfo() = delete; + }; + +} // end namespace Catch + +// end catch_assertioninfo.h +// start catch_decomposer.h + +// start catch_tostring.h + +#include +#include +#include +#include +// start catch_stream.h + +#include +#include +#include + +namespace Catch { + + std::ostream& cout(); + std::ostream& cerr(); + std::ostream& clog(); + + class StringRef; + + struct IStream { + virtual ~IStream(); + virtual std::ostream& stream() const = 0; + }; + + auto makeStream( StringRef const &filename ) -> IStream const*; + + class ReusableStringStream { + std::size_t m_index; + std::ostream* m_oss; + public: + ReusableStringStream(); + ~ReusableStringStream(); + + auto str() const -> std::string; + + template + auto operator << ( T const& value ) -> ReusableStringStream& { + *m_oss << value; + return *this; + } + auto get() -> std::ostream& { return *m_oss; } + }; +} + +// end catch_stream.h + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +#include +#endif + +#ifdef __OBJC__ +// start catch_objc_arc.hpp + +#import + +#ifdef __has_feature +#define CATCH_ARC_ENABLED __has_feature(objc_arc) +#else +#define CATCH_ARC_ENABLED 0 +#endif + +void arcSafeRelease( NSObject* obj ); +id performOptionalSelector( id obj, SEL sel ); + +#if !CATCH_ARC_ENABLED +inline void arcSafeRelease( NSObject* obj ) { + [obj release]; +} +inline id performOptionalSelector( id obj, SEL sel ) { + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; + return nil; +} +#define CATCH_UNSAFE_UNRETAINED +#define CATCH_ARC_STRONG +#else +inline void arcSafeRelease( NSObject* ){} +inline id performOptionalSelector( id obj, SEL sel ) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +#endif + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + return nil; +} +#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained +#define CATCH_ARC_STRONG __strong +#endif + +// end catch_objc_arc.hpp +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless +#endif + +namespace Catch { + namespace Detail { + + extern const std::string unprintableString; + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template + std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } + + template + class IsStreamInsertable { + template + static auto test(int) + -> decltype(std::declval() << std::declval(), std::true_type()); + + template + static auto test(...)->std::false_type; + + public: + static const bool value = decltype(test(0))::value; + }; + + template + std::string convertUnknownEnumToString( E e ); + + template + typename std::enable_if< + !std::is_enum::value && !std::is_base_of::value, + std::string>::type convertUnstreamable( T const& ) { + return Detail::unprintableString; + } + template + typename std::enable_if< + !std::is_enum::value && std::is_base_of::value, + std::string>::type convertUnstreamable(T const& ex) { + return ex.what(); + } + + template + typename std::enable_if< + std::is_enum::value + , std::string>::type convertUnstreamable( T const& value ) { + return convertUnknownEnumToString( value ); + } + +#if defined(_MANAGED) + //! Convert a CLR string to a utf8 std::string + template + std::string clrReferenceToString( T^ ref ) { + if (ref == nullptr) + return std::string("null"); + auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString()); + cli::pin_ptr p = &bytes[0]; + return std::string(reinterpret_cast(p), bytes->Length); + } +#endif + + } // namespace Detail + + // If we decide for C++14, change these to enable_if_ts + template + struct StringMaker { + template + static + typename std::enable_if<::Catch::Detail::IsStreamInsertable::value, std::string>::type + convert(const Fake& value) { + ReusableStringStream rss; + // NB: call using the function-like syntax to avoid ambiguity with + // user-defined templated operator<< under clang. + rss.operator<<(value); + return rss.str(); + } + + template + static + typename std::enable_if::value, std::string>::type + convert( const Fake& value ) { +#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) + return Detail::convertUnstreamable(value); +#else + return CATCH_CONFIG_FALLBACK_STRINGIFIER(value); +#endif + } + }; + + namespace Detail { + + // This function dispatches all stringification requests inside of Catch. + // Should be preferably called fully qualified, like ::Catch::Detail::stringify + template + std::string stringify(const T& e) { + return ::Catch::StringMaker::type>::type>::convert(e); + } + + template + std::string convertUnknownEnumToString( E e ) { + return ::Catch::Detail::stringify(static_cast::type>(e)); + } + +#if defined(_MANAGED) + template + std::string stringify( T^ e ) { + return ::Catch::StringMaker::convert(e); + } +#endif + + } // namespace Detail + + // Some predefined specializations + + template<> + struct StringMaker { + static std::string convert(const std::string& str); + }; + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW + template<> + struct StringMaker { + static std::string convert(std::string_view str); + }; +#endif + + template<> + struct StringMaker { + static std::string convert(char const * str); + }; + template<> + struct StringMaker { + static std::string convert(char * str); + }; + +#ifdef CATCH_CONFIG_WCHAR + template<> + struct StringMaker { + static std::string convert(const std::wstring& wstr); + }; + +# ifdef CATCH_CONFIG_CPP17_STRING_VIEW + template<> + struct StringMaker { + static std::string convert(std::wstring_view str); + }; +# endif + + template<> + struct StringMaker { + static std::string convert(wchar_t const * str); + }; + template<> + struct StringMaker { + static std::string convert(wchar_t * str); + }; +#endif + + // TBD: Should we use `strnlen` to ensure that we don't go out of the buffer, + // while keeping string semantics? + template + struct StringMaker { + static std::string convert(char const* str) { + return ::Catch::Detail::stringify(std::string{ str }); + } + }; + template + struct StringMaker { + static std::string convert(signed char const* str) { + return ::Catch::Detail::stringify(std::string{ reinterpret_cast(str) }); + } + }; + template + struct StringMaker { + static std::string convert(unsigned char const* str) { + return ::Catch::Detail::stringify(std::string{ reinterpret_cast(str) }); + } + }; + + template<> + struct StringMaker { + static std::string convert(int value); + }; + template<> + struct StringMaker { + static std::string convert(long value); + }; + template<> + struct StringMaker { + static std::string convert(long long value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned int value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned long value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned long long value); + }; + + template<> + struct StringMaker { + static std::string convert(bool b); + }; + + template<> + struct StringMaker { + static std::string convert(char c); + }; + template<> + struct StringMaker { + static std::string convert(signed char c); + }; + template<> + struct StringMaker { + static std::string convert(unsigned char c); + }; + + template<> + struct StringMaker { + static std::string convert(std::nullptr_t); + }; + + template<> + struct StringMaker { + static std::string convert(float value); + }; + template<> + struct StringMaker { + static std::string convert(double value); + }; + + template + struct StringMaker { + template + static std::string convert(U* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + + template + struct StringMaker { + static std::string convert(R C::* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + +#if defined(_MANAGED) + template + struct StringMaker { + static std::string convert( T^ ref ) { + return ::Catch::Detail::clrReferenceToString(ref); + } + }; +#endif + + namespace Detail { + template + std::string rangeToString(InputIterator first, InputIterator last) { + ReusableStringStream rss; + rss << "{ "; + if (first != last) { + rss << ::Catch::Detail::stringify(*first); + for (++first; first != last; ++first) + rss << ", " << ::Catch::Detail::stringify(*first); + } + rss << " }"; + return rss.str(); + } + } + +#ifdef __OBJC__ + template<> + struct StringMaker { + static std::string convert(NSString * nsstring) { + if (!nsstring) + return "nil"; + return std::string("@") + [nsstring UTF8String]; + } + }; + template<> + struct StringMaker { + static std::string convert(NSObject* nsObject) { + return ::Catch::Detail::stringify([nsObject description]); + } + + }; + namespace Detail { + inline std::string stringify( NSString* nsstring ) { + return StringMaker::convert( nsstring ); + } + + } // namespace Detail +#endif // __OBJC__ + +} // namespace Catch + +////////////////////////////////////////////////////// +// Separate std-lib types stringification, so it can be selectively enabled +// This means that we do not bring in + +#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) +# define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER +# define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER +# define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# define CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER +#endif + +// Separate std::pair specialization +#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER) +#include +namespace Catch { + template + struct StringMaker > { + static std::string convert(const std::pair& pair) { + ReusableStringStream rss; + rss << "{ " + << ::Catch::Detail::stringify(pair.first) + << ", " + << ::Catch::Detail::stringify(pair.second) + << " }"; + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER + +#if defined(CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_OPTIONAL) +#include +namespace Catch { + template + struct StringMaker > { + static std::string convert(const std::optional& optional) { + ReusableStringStream rss; + if (optional.has_value()) { + rss << ::Catch::Detail::stringify(*optional); + } else { + rss << "{ }"; + } + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER + +// Separate std::tuple specialization +#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) +#include +namespace Catch { + namespace Detail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size::value) + > + struct TupleElementPrinter { + static void print(const Tuple& tuple, std::ostream& os) { + os << (N ? ", " : " ") + << ::Catch::Detail::stringify(std::get(tuple)); + TupleElementPrinter::print(tuple, os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct TupleElementPrinter { + static void print(const Tuple&, std::ostream&) {} + }; + + } + + template + struct StringMaker> { + static std::string convert(const std::tuple& tuple) { + ReusableStringStream rss; + rss << '{'; + Detail::TupleElementPrinter>::print(tuple, rss.get()); + rss << " }"; + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER + +#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT) +#include +namespace Catch { + template<> + struct StringMaker { + static std::string convert(const std::monostate&) { + return "{ }"; + } + }; + + template + struct StringMaker> { + static std::string convert(const std::variant& variant) { + if (variant.valueless_by_exception()) { + return "{valueless variant}"; + } else { + return std::visit( + [](const auto& value) { + return ::Catch::Detail::stringify(value); + }, + variant + ); + } + } + }; +} +#endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER + +namespace Catch { + struct not_this_one {}; // Tag type for detecting which begin/ end are being selected + + // Import begin/ end from std here so they are considered alongside the fallback (...) overloads in this namespace + using std::begin; + using std::end; + + not_this_one begin( ... ); + not_this_one end( ... ); + + template + struct is_range { + static const bool value = + !std::is_same())), not_this_one>::value && + !std::is_same())), not_this_one>::value; + }; + +#if defined(_MANAGED) // Managed types are never ranges + template + struct is_range { + static const bool value = false; + }; +#endif + + template + std::string rangeToString( Range const& range ) { + return ::Catch::Detail::rangeToString( begin( range ), end( range ) ); + } + + // Handle vector specially + template + std::string rangeToString( std::vector const& v ) { + ReusableStringStream rss; + rss << "{ "; + bool first = true; + for( bool b : v ) { + if( first ) + first = false; + else + rss << ", "; + rss << ::Catch::Detail::stringify( b ); + } + rss << " }"; + return rss.str(); + } + + template + struct StringMaker::value && !::Catch::Detail::IsStreamInsertable::value>::type> { + static std::string convert( R const& range ) { + return rangeToString( range ); + } + }; + + template + struct StringMaker { + static std::string convert(T const(&arr)[SZ]) { + return rangeToString(arr); + } + }; + +} // namespace Catch + +// Separate std::chrono::duration specialization +#if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +#include +#include +#include + +namespace Catch { + +template +struct ratio_string { + static std::string symbol(); +}; + +template +std::string ratio_string::symbol() { + Catch::ReusableStringStream rss; + rss << '[' << Ratio::num << '/' + << Ratio::den << ']'; + return rss.str(); +} +template <> +struct ratio_string { + static std::string symbol(); +}; +template <> +struct ratio_string { + static std::string symbol(); +}; +template <> +struct ratio_string { + static std::string symbol(); +}; +template <> +struct ratio_string { + static std::string symbol(); +}; +template <> +struct ratio_string { + static std::string symbol(); +}; +template <> +struct ratio_string { + static std::string symbol(); +}; + + //////////// + // std::chrono::duration specializations + template + struct StringMaker> { + static std::string convert(std::chrono::duration const& duration) { + ReusableStringStream rss; + rss << duration.count() << ' ' << ratio_string::symbol() << 's'; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " s"; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " m"; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " h"; + return rss.str(); + } + }; + + //////////// + // std::chrono::time_point specialization + // Generic time_point cannot be specialized, only std::chrono::time_point + template + struct StringMaker> { + static std::string convert(std::chrono::time_point const& time_point) { + return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch"; + } + }; + // std::chrono::time_point specialization + template + struct StringMaker> { + static std::string convert(std::chrono::time_point const& time_point) { + auto converted = std::chrono::system_clock::to_time_t(time_point); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &converted); +#else + std::tm* timeInfo = std::gmtime(&converted); +#endif + + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// end catch_tostring.h +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#pragma warning(disable:4018) // more "signed/unsigned mismatch" +#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) +#pragma warning(disable:4180) // qualifier applied to function type has no meaning +#pragma warning(disable:4800) // Forcing result to true or false +#endif + +namespace Catch { + + struct ITransientExpression { + auto isBinaryExpression() const -> bool { return m_isBinaryExpression; } + auto getResult() const -> bool { return m_result; } + virtual void streamReconstructedExpression( std::ostream &os ) const = 0; + + ITransientExpression( bool isBinaryExpression, bool result ) + : m_isBinaryExpression( isBinaryExpression ), + m_result( result ) + {} + + // We don't actually need a virtual destructor, but many static analysers + // complain if it's not here :-( + virtual ~ITransientExpression(); + + bool m_isBinaryExpression; + bool m_result; + + }; + + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ); + + template + class BinaryExpr : public ITransientExpression { + LhsT m_lhs; + StringRef m_op; + RhsT m_rhs; + + void streamReconstructedExpression( std::ostream &os ) const override { + formatReconstructedExpression + ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) ); + } + + public: + BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs ) + : ITransientExpression{ true, comparisonResult }, + m_lhs( lhs ), + m_op( op ), + m_rhs( rhs ) + {} + + template + auto operator && ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator || ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator == ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator != ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator > ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator < ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator >= ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator <= ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + }; + + template + class UnaryExpr : public ITransientExpression { + LhsT m_lhs; + + void streamReconstructedExpression( std::ostream &os ) const override { + os << Catch::Detail::stringify( m_lhs ); + } + + public: + explicit UnaryExpr( LhsT lhs ) + : ITransientExpression{ false, static_cast(lhs) }, + m_lhs( lhs ) + {} + }; + + // Specialised comparison functions to handle equality comparisons between ints and pointers (NULL deduces as an int) + template + auto compareEqual( LhsT const& lhs, RhsT const& rhs ) -> bool { return static_cast(lhs == rhs); } + template + auto compareEqual( T* const& lhs, int rhs ) -> bool { return lhs == reinterpret_cast( rhs ); } + template + auto compareEqual( T* const& lhs, long rhs ) -> bool { return lhs == reinterpret_cast( rhs ); } + template + auto compareEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) == rhs; } + template + auto compareEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) == rhs; } + + template + auto compareNotEqual( LhsT const& lhs, RhsT&& rhs ) -> bool { return static_cast(lhs != rhs); } + template + auto compareNotEqual( T* const& lhs, int rhs ) -> bool { return lhs != reinterpret_cast( rhs ); } + template + auto compareNotEqual( T* const& lhs, long rhs ) -> bool { return lhs != reinterpret_cast( rhs ); } + template + auto compareNotEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) != rhs; } + template + auto compareNotEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) != rhs; } + + template + class ExprLhs { + LhsT m_lhs; + public: + explicit ExprLhs( LhsT lhs ) : m_lhs( lhs ) {} + + template + auto operator == ( RhsT const& rhs ) -> BinaryExpr const { + return { compareEqual( m_lhs, rhs ), m_lhs, "==", rhs }; + } + auto operator == ( bool rhs ) -> BinaryExpr const { + return { m_lhs == rhs, m_lhs, "==", rhs }; + } + + template + auto operator != ( RhsT const& rhs ) -> BinaryExpr const { + return { compareNotEqual( m_lhs, rhs ), m_lhs, "!=", rhs }; + } + auto operator != ( bool rhs ) -> BinaryExpr const { + return { m_lhs != rhs, m_lhs, "!=", rhs }; + } + + template + auto operator > ( RhsT const& rhs ) -> BinaryExpr const { + return { static_cast(m_lhs > rhs), m_lhs, ">", rhs }; + } + template + auto operator < ( RhsT const& rhs ) -> BinaryExpr const { + return { static_cast(m_lhs < rhs), m_lhs, "<", rhs }; + } + template + auto operator >= ( RhsT const& rhs ) -> BinaryExpr const { + return { static_cast(m_lhs >= rhs), m_lhs, ">=", rhs }; + } + template + auto operator <= ( RhsT const& rhs ) -> BinaryExpr const { + return { static_cast(m_lhs <= rhs), m_lhs, "<=", rhs }; + } + + template + auto operator && ( RhsT const& ) -> BinaryExpr const { + static_assert(always_false::value, + "operator&& is not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator || ( RhsT const& ) -> BinaryExpr const { + static_assert(always_false::value, + "operator|| is not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + auto makeUnaryExpr() const -> UnaryExpr { + return UnaryExpr{ m_lhs }; + } + }; + + void handleExpression( ITransientExpression const& expr ); + + template + void handleExpression( ExprLhs const& expr ) { + handleExpression( expr.makeUnaryExpr() ); + } + + struct Decomposer { + template + auto operator <= ( T const& lhs ) -> ExprLhs { + return ExprLhs{ lhs }; + } + + auto operator <=( bool value ) -> ExprLhs { + return ExprLhs{ value }; + } + }; + +} // end namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// end catch_decomposer.h +// start catch_interfaces_capture.h + +#include + +namespace Catch { + + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct SectionEndInfo; + struct MessageInfo; + struct MessageBuilder; + struct Counts; + struct BenchmarkInfo; + struct BenchmarkStats; + struct AssertionReaction; + struct SourceLineInfo; + + struct ITransientExpression; + struct IGeneratorTracker; + + struct IResultCapture { + + virtual ~IResultCapture(); + + virtual bool sectionStarted( SectionInfo const& sectionInfo, + Counts& assertions ) = 0; + virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; + + virtual auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& = 0; + + virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0; + virtual void benchmarkEnded( BenchmarkStats const& stats ) = 0; + + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; + + virtual void emplaceUnscopedMessage( MessageBuilder const& builder ) = 0; + + virtual void handleFatalErrorCondition( StringRef message ) = 0; + + virtual void handleExpr + ( AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction ) = 0; + virtual void handleMessage + ( AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedExceptionNotThrown + ( AssertionInfo const& info, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedInflightException + ( AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction ) = 0; + virtual void handleIncomplete + ( AssertionInfo const& info ) = 0; + virtual void handleNonExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction ) = 0; + + virtual bool lastAssertionPassed() = 0; + virtual void assertionPassed() = 0; + + // Deprecated, do not use: + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + virtual void exceptionEarlyReported() = 0; + }; + + IResultCapture& getResultCapture(); +} + +// end catch_interfaces_capture.h +namespace Catch { + + struct TestFailureException{}; + struct AssertionResultData; + struct IResultCapture; + class RunContext; + + class LazyExpression { + friend class AssertionHandler; + friend struct AssertionStats; + friend class RunContext; + + ITransientExpression const* m_transientExpression = nullptr; + bool m_isNegated; + public: + LazyExpression( bool isNegated ); + LazyExpression( LazyExpression const& other ); + LazyExpression& operator = ( LazyExpression const& ) = delete; + + explicit operator bool() const; + + friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&; + }; + + struct AssertionReaction { + bool shouldDebugBreak = false; + bool shouldThrow = false; + }; + + class AssertionHandler { + AssertionInfo m_assertionInfo; + AssertionReaction m_reaction; + bool m_completed = false; + IResultCapture& m_resultCapture; + + public: + AssertionHandler + ( StringRef const& macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ); + ~AssertionHandler() { + if ( !m_completed ) { + m_resultCapture.handleIncomplete( m_assertionInfo ); + } + } + + template + void handleExpr( ExprLhs const& expr ) { + handleExpr( expr.makeUnaryExpr() ); + } + void handleExpr( ITransientExpression const& expr ); + + void handleMessage(ResultWas::OfType resultType, StringRef const& message); + + void handleExceptionThrownAsExpected(); + void handleUnexpectedExceptionNotThrown(); + void handleExceptionNotThrownAsExpected(); + void handleThrowingCallSkipped(); + void handleUnexpectedInflightException(); + + void complete(); + void setCompleted(); + + // query + auto allowThrows() const -> bool; + }; + + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef const& matcherString ); + +} // namespace Catch + +// end catch_assertionhandler.h +// start catch_message.h + +#include +#include + +namespace Catch { + + struct MessageInfo { + MessageInfo( StringRef const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + StringRef macroName; + std::string message; + SourceLineInfo lineInfo; + ResultWas::OfType type; + unsigned int sequence; + + bool operator == ( MessageInfo const& other ) const; + bool operator < ( MessageInfo const& other ) const; + private: + static unsigned int globalCount; + }; + + struct MessageStream { + + template + MessageStream& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + ReusableStringStream m_stream; + }; + + struct MessageBuilder : MessageStream { + MessageBuilder( StringRef const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ); + + template + MessageBuilder& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + MessageInfo m_info; + }; + + class ScopedMessage { + public: + explicit ScopedMessage( MessageBuilder const& builder ); + ScopedMessage( ScopedMessage& duplicate ) = delete; + ScopedMessage( ScopedMessage&& old ); + ~ScopedMessage(); + + MessageInfo m_info; + bool m_moved; + }; + + class Capturer { + std::vector m_messages; + IResultCapture& m_resultCapture = getResultCapture(); + size_t m_captured = 0; + public: + Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ); + ~Capturer(); + + void captureValue( size_t index, std::string const& value ); + + template + void captureValues( size_t index, T const& value ) { + captureValue( index, Catch::Detail::stringify( value ) ); + } + + template + void captureValues( size_t index, T const& value, Ts const&... values ) { + captureValue( index, Catch::Detail::stringify(value) ); + captureValues( index+1, values... ); + } + }; + +} // end namespace Catch + +// end catch_message.h +#if !defined(CATCH_CONFIG_DISABLE) + +#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) + #define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__ +#else + #define CATCH_INTERNAL_STRINGIFY(...) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION" +#endif + +#if defined(CATCH_CONFIG_FAST_COMPILE) || defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + +/////////////////////////////////////////////////////////////////////////////// +// Another way to speed-up compilation is to omit local try-catch for REQUIRE* +// macros. +#define INTERNAL_CATCH_TRY +#define INTERNAL_CATCH_CATCH( capturer ) + +#else // CATCH_CONFIG_FAST_COMPILE + +#define INTERNAL_CATCH_TRY try +#define INTERNAL_CATCH_CATCH( handler ) catch(...) { handler.handleUnexpectedInflightException(); } + +#endif + +#define INTERNAL_CATCH_REACT( handler ) handler.complete(); + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + INTERNAL_CATCH_TRY { \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + catchAssertionHandler.handleExpr( Catch::Decomposer() <= __VA_ARGS__ ); \ + CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( (void)0, (false) && static_cast( !!(__VA_ARGS__) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look + // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( !Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + try { \ + static_cast(__VA_ARGS__); \ + catchAssertionHandler.handleExceptionNotThrownAsExpected(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast(__VA_ARGS__); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast(expr); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( exceptionType const& ) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition ); \ + catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_CAPTURE( varName, macroName, ... ) \ + auto varName = Catch::Capturer( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info, #__VA_ARGS__ ); \ + varName.captureValues( 0, __VA_ARGS__ ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO( macroName, log ) \ + Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ); + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \ + Catch::getResultCapture().emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) + +/////////////////////////////////////////////////////////////////////////////// +// Although this is matcher-based, it can be used with just a string +#define INTERNAL_CATCH_THROWS_STR_MATCHES( macroName, resultDisposition, matcher, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast(__VA_ARGS__); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( ... ) { \ + Catch::handleExceptionMatchExpr( catchAssertionHandler, matcher, #matcher##_catch_sr ); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +#endif // CATCH_CONFIG_DISABLE + +// end catch_capture.hpp +// start catch_section.h + +// start catch_section_info.h + +// start catch_totals.h + +#include + +namespace Catch { + + struct Counts { + Counts operator - ( Counts const& other ) const; + Counts& operator += ( Counts const& other ); + + std::size_t total() const; + bool allPassed() const; + bool allOk() const; + + std::size_t passed = 0; + std::size_t failed = 0; + std::size_t failedButOk = 0; + }; + + struct Totals { + + Totals operator - ( Totals const& other ) const; + Totals& operator += ( Totals const& other ); + + Totals delta( Totals const& prevTotals ) const; + + int error = 0; + Counts assertions; + Counts testCases; + }; +} + +// end catch_totals.h +#include + +namespace Catch { + + struct SectionInfo { + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name ); + + // Deprecated + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& ) : SectionInfo( _lineInfo, _name ) {} + + std::string name; + std::string description; // !Deprecated: this will always be empty + SourceLineInfo lineInfo; + }; + + struct SectionEndInfo { + SectionInfo sectionInfo; + Counts prevAssertions; + double durationInSeconds; + }; + +} // end namespace Catch + +// end catch_section_info.h +// start catch_timer.h + +#include + +namespace Catch { + + auto getCurrentNanosecondsSinceEpoch() -> uint64_t; + auto getEstimatedClockResolution() -> uint64_t; + + class Timer { + uint64_t m_nanoseconds = 0; + public: + void start(); + auto getElapsedNanoseconds() const -> uint64_t; + auto getElapsedMicroseconds() const -> uint64_t; + auto getElapsedMilliseconds() const -> unsigned int; + auto getElapsedSeconds() const -> double; + }; + +} // namespace Catch + +// end catch_timer.h +#include + +namespace Catch { + + class Section : NonCopyable { + public: + Section( SectionInfo const& info ); + ~Section(); + + // This indicates whether the section should be executed or not + explicit operator bool() const; + + private: + SectionInfo m_info; + + std::string m_name; + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#define INTERNAL_CATCH_SECTION( ... ) \ + CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) \ + CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS + +#define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \ + CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, (Catch::ReusableStringStream() << __VA_ARGS__).str() ) ) \ + CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS + +// end catch_section.h +// start catch_benchmark.h + +#include +#include + +namespace Catch { + + class BenchmarkLooper { + + std::string m_name; + std::size_t m_count = 0; + std::size_t m_iterationsToRun = 1; + uint64_t m_resolution; + Timer m_timer; + + static auto getResolution() -> uint64_t; + public: + // Keep most of this inline as it's on the code path that is being timed + BenchmarkLooper( StringRef name ) + : m_name( name ), + m_resolution( getResolution() ) + { + reportStart(); + m_timer.start(); + } + + explicit operator bool() { + if( m_count < m_iterationsToRun ) + return true; + return needsMoreIterations(); + } + + void increment() { + ++m_count; + } + + void reportStart(); + auto needsMoreIterations() -> bool; + }; + +} // end namespace Catch + +#define BENCHMARK( name ) \ + for( Catch::BenchmarkLooper looper( name ); looper; looper.increment() ) + +// end catch_benchmark.h +// start catch_interfaces_exception.h + +// start catch_interfaces_registry_hub.h + +#include +#include + +namespace Catch { + + class TestCase; + struct ITestCaseRegistry; + struct IExceptionTranslatorRegistry; + struct IExceptionTranslator; + struct IReporterRegistry; + struct IReporterFactory; + struct ITagAliasRegistry; + class StartupExceptionRegistry; + + using IReporterFactoryPtr = std::shared_ptr; + + struct IRegistryHub { + virtual ~IRegistryHub(); + + virtual IReporterRegistry const& getReporterRegistry() const = 0; + virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; + + virtual IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const = 0; + + virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0; + }; + + struct IMutableRegistryHub { + virtual ~IMutableRegistryHub(); + virtual void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) = 0; + virtual void registerListener( IReporterFactoryPtr const& factory ) = 0; + virtual void registerTest( TestCase const& testInfo ) = 0; + virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; + virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; + virtual void registerStartupException() noexcept = 0; + }; + + IRegistryHub const& getRegistryHub(); + IMutableRegistryHub& getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} + +// end catch_interfaces_registry_hub.h +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( translatorName, signature) \ + static std::string translatorName( signature ) +#endif + +#include +#include +#include + +namespace Catch { + using exceptionTranslateFunction = std::string(*)(); + + struct IExceptionTranslator; + using ExceptionTranslators = std::vector>; + + struct IExceptionTranslator { + virtual ~IExceptionTranslator(); + virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; + }; + + struct IExceptionTranslatorRegistry { + virtual ~IExceptionTranslatorRegistry(); + + virtual std::string translateActiveException() const = 0; + }; + + class ExceptionTranslatorRegistrar { + template + class ExceptionTranslator : public IExceptionTranslator { + public: + + ExceptionTranslator( std::string(*translateFunction)( T& ) ) + : m_translateFunction( translateFunction ) + {} + + std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const override { + try { + if( it == itEnd ) + std::rethrow_exception(std::current_exception()); + else + return (*it)->translate( it+1, itEnd ); + } + catch( T& ex ) { + return m_translateFunction( ex ); + } + } + + protected: + std::string(*m_translateFunction)( T& ); + }; + + public: + template + ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { + getMutableRegistryHub().registerTranslator + ( new ExceptionTranslator( translateFunction ) ); + } + }; +} + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ + static std::string translatorName( signature ); \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + static std::string translatorName( signature ) + +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) + +// end catch_interfaces_exception.h +// start catch_approx.h + +#include + +namespace Catch { +namespace Detail { + + class Approx { + private: + bool equalityComparisonImpl(double other) const; + // Validates the new margin (margin >= 0) + // out-of-line to avoid including stdexcept in the header + void setMargin(double margin); + // Validates the new epsilon (0 < epsilon < 1) + // out-of-line to avoid including stdexcept in the header + void setEpsilon(double epsilon); + + public: + explicit Approx ( double value ); + + static Approx custom(); + + Approx operator-() const; + + template ::value>::type> + Approx operator()( T const& value ) { + Approx approx( static_cast(value) ); + approx.m_epsilon = m_epsilon; + approx.m_margin = m_margin; + approx.m_scale = m_scale; + return approx; + } + + template ::value>::type> + explicit Approx( T const& value ): Approx(static_cast(value)) + {} + + template ::value>::type> + friend bool operator == ( const T& lhs, Approx const& rhs ) { + auto lhs_v = static_cast(lhs); + return rhs.equalityComparisonImpl(lhs_v); + } + + template ::value>::type> + friend bool operator == ( Approx const& lhs, const T& rhs ) { + return operator==( rhs, lhs ); + } + + template ::value>::type> + friend bool operator != ( T const& lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + template ::value>::type> + friend bool operator != ( Approx const& lhs, T const& rhs ) { + return !operator==( rhs, lhs ); + } + + template ::value>::type> + friend bool operator <= ( T const& lhs, Approx const& rhs ) { + return static_cast(lhs) < rhs.m_value || lhs == rhs; + } + + template ::value>::type> + friend bool operator <= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value < static_cast(rhs) || lhs == rhs; + } + + template ::value>::type> + friend bool operator >= ( T const& lhs, Approx const& rhs ) { + return static_cast(lhs) > rhs.m_value || lhs == rhs; + } + + template ::value>::type> + friend bool operator >= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value > static_cast(rhs) || lhs == rhs; + } + + template ::value>::type> + Approx& epsilon( T const& newEpsilon ) { + double epsilonAsDouble = static_cast(newEpsilon); + setEpsilon(epsilonAsDouble); + return *this; + } + + template ::value>::type> + Approx& margin( T const& newMargin ) { + double marginAsDouble = static_cast(newMargin); + setMargin(marginAsDouble); + return *this; + } + + template ::value>::type> + Approx& scale( T const& newScale ) { + m_scale = static_cast(newScale); + return *this; + } + + std::string toString() const; + + private: + double m_epsilon; + double m_margin; + double m_scale; + double m_value; + }; +} // end namespace Detail + +namespace literals { + Detail::Approx operator "" _a(long double val); + Detail::Approx operator "" _a(unsigned long long val); +} // end namespace literals + +template<> +struct StringMaker { + static std::string convert(Catch::Detail::Approx const& value); +}; + +} // end namespace Catch + +// end catch_approx.h +// start catch_string_manip.h + +#include +#include + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ); + bool startsWith( std::string const& s, char prefix ); + bool endsWith( std::string const& s, std::string const& suffix ); + bool endsWith( std::string const& s, char suffix ); + bool contains( std::string const& s, std::string const& infix ); + void toLowerInPlace( std::string& s ); + std::string toLower( std::string const& s ); + std::string trim( std::string const& str ); + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); + + struct pluralise { + pluralise( std::size_t count, std::string const& label ); + + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); + + std::size_t m_count; + std::string m_label; + }; +} + +// end catch_string_manip.h +#ifndef CATCH_CONFIG_DISABLE_MATCHERS +// start catch_capture_matchers.h + +// start catch_matchers.h + +#include +#include + +namespace Catch { +namespace Matchers { + namespace Impl { + + template struct MatchAllOf; + template struct MatchAnyOf; + template struct MatchNotOf; + + class MatcherUntypedBase { + public: + MatcherUntypedBase() = default; + MatcherUntypedBase ( MatcherUntypedBase const& ) = default; + MatcherUntypedBase& operator = ( MatcherUntypedBase const& ) = delete; + std::string toString() const; + + protected: + virtual ~MatcherUntypedBase(); + virtual std::string describe() const = 0; + mutable std::string m_cachedToString; + }; + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnon-virtual-dtor" +#endif + + template + struct MatcherMethod { + virtual bool match( ObjectT const& arg ) const = 0; + }; + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + + template + struct MatcherBase : MatcherUntypedBase, MatcherMethod { + + MatchAllOf operator && ( MatcherBase const& other ) const; + MatchAnyOf operator || ( MatcherBase const& other ) const; + MatchNotOf operator ! () const; + }; + + template + struct MatchAllOf : MatcherBase { + bool match( ArgT const& arg ) const override { + for( auto matcher : m_matchers ) { + if (!matcher->match(arg)) + return false; + } + return true; + } + std::string describe() const override { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + bool first = true; + for( auto matcher : m_matchers ) { + if( first ) + first = false; + else + description += " and "; + description += matcher->toString(); + } + description += " )"; + return description; + } + + MatchAllOf& operator && ( MatcherBase const& other ) { + m_matchers.push_back( &other ); + return *this; + } + + std::vector const*> m_matchers; + }; + template + struct MatchAnyOf : MatcherBase { + + bool match( ArgT const& arg ) const override { + for( auto matcher : m_matchers ) { + if (matcher->match(arg)) + return true; + } + return false; + } + std::string describe() const override { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + bool first = true; + for( auto matcher : m_matchers ) { + if( first ) + first = false; + else + description += " or "; + description += matcher->toString(); + } + description += " )"; + return description; + } + + MatchAnyOf& operator || ( MatcherBase const& other ) { + m_matchers.push_back( &other ); + return *this; + } + + std::vector const*> m_matchers; + }; + + template + struct MatchNotOf : MatcherBase { + + MatchNotOf( MatcherBase const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {} + + bool match( ArgT const& arg ) const override { + return !m_underlyingMatcher.match( arg ); + } + + std::string describe() const override { + return "not " + m_underlyingMatcher.toString(); + } + MatcherBase const& m_underlyingMatcher; + }; + + template + MatchAllOf MatcherBase::operator && ( MatcherBase const& other ) const { + return MatchAllOf() && *this && other; + } + template + MatchAnyOf MatcherBase::operator || ( MatcherBase const& other ) const { + return MatchAnyOf() || *this || other; + } + template + MatchNotOf MatcherBase::operator ! () const { + return MatchNotOf( *this ); + } + + } // namespace Impl + +} // namespace Matchers + +using namespace Matchers; +using Matchers::Impl::MatcherBase; + +} // namespace Catch + +// end catch_matchers.h +// start catch_matchers_floating.h + +#include +#include + +namespace Catch { +namespace Matchers { + + namespace Floating { + + enum class FloatingPointKind : uint8_t; + + struct WithinAbsMatcher : MatcherBase { + WithinAbsMatcher(double target, double margin); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + double m_margin; + }; + + struct WithinUlpsMatcher : MatcherBase { + WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + int m_ulps; + FloatingPointKind m_type; + }; + + } // namespace Floating + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff); + Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff); + Floating::WithinAbsMatcher WithinAbs(double target, double margin); + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_floating.h +// start catch_matchers_generic.hpp + +#include +#include + +namespace Catch { +namespace Matchers { +namespace Generic { + +namespace Detail { + std::string finalizeDescription(const std::string& desc); +} + +template +class PredicateMatcher : public MatcherBase { + std::function m_predicate; + std::string m_description; +public: + + PredicateMatcher(std::function const& elem, std::string const& descr) + :m_predicate(std::move(elem)), + m_description(Detail::finalizeDescription(descr)) + {} + + bool match( T const& item ) const override { + return m_predicate(item); + } + + std::string describe() const override { + return m_description; + } +}; + +} // namespace Generic + + // The following functions create the actual matcher objects. + // The user has to explicitly specify type to the function, because + // infering std::function is hard (but possible) and + // requires a lot of TMP. + template + Generic::PredicateMatcher Predicate(std::function const& predicate, std::string const& description = "") { + return Generic::PredicateMatcher(predicate, description); + } + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_generic.hpp +// start catch_matchers_string.h + +#include + +namespace Catch { +namespace Matchers { + + namespace StdString { + + struct CasedString + { + CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ); + std::string adjustString( std::string const& str ) const; + std::string caseSensitivitySuffix() const; + + CaseSensitive::Choice m_caseSensitivity; + std::string m_str; + }; + + struct StringMatcherBase : MatcherBase { + StringMatcherBase( std::string const& operation, CasedString const& comparator ); + std::string describe() const override; + + CasedString m_comparator; + std::string m_operation; + }; + + struct EqualsMatcher : StringMatcherBase { + EqualsMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + struct ContainsMatcher : StringMatcherBase { + ContainsMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + struct StartsWithMatcher : StringMatcherBase { + StartsWithMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + struct EndsWithMatcher : StringMatcherBase { + EndsWithMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + + struct RegexMatcher : MatcherBase { + RegexMatcher( std::string regex, CaseSensitive::Choice caseSensitivity ); + bool match( std::string const& matchee ) const override; + std::string describe() const override; + + private: + std::string m_regex; + CaseSensitive::Choice m_caseSensitivity; + }; + + } // namespace StdString + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::RegexMatcher Matches( std::string const& regex, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_string.h +// start catch_matchers_vector.h + +#include + +namespace Catch { +namespace Matchers { + + namespace Vector { + namespace Detail { + template + size_t count(InputIterator first, InputIterator last, T const& item) { + size_t cnt = 0; + for (; first != last; ++first) { + if (*first == item) { + ++cnt; + } + } + return cnt; + } + template + bool contains(InputIterator first, InputIterator last, T const& item) { + for (; first != last; ++first) { + if (*first == item) { + return true; + } + } + return false; + } + } + + template + struct ContainsElementMatcher : MatcherBase> { + + ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {} + + bool match(std::vector const &v) const override { + for (auto const& el : v) { + if (el == m_comparator) { + return true; + } + } + return false; + } + + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify( m_comparator ); + } + + T const& m_comparator; + }; + + template + struct ContainsMatcher : MatcherBase> { + + ContainsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} + + bool match(std::vector const &v) const override { + // !TBD: see note in EqualsMatcher + if (m_comparator.size() > v.size()) + return false; + for (auto const& comparator : m_comparator) { + auto present = false; + for (const auto& el : v) { + if (el == comparator) { + present = true; + break; + } + } + if (!present) { + return false; + } + } + return true; + } + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify( m_comparator ); + } + + std::vector const& m_comparator; + }; + + template + struct EqualsMatcher : MatcherBase> { + + EqualsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} + + bool match(std::vector const &v) const override { + // !TBD: This currently works if all elements can be compared using != + // - a more general approach would be via a compare template that defaults + // to using !=. but could be specialised for, e.g. std::vector etc + // - then just call that directly + if (m_comparator.size() != v.size()) + return false; + for (std::size_t i = 0; i < v.size(); ++i) + if (m_comparator[i] != v[i]) + return false; + return true; + } + std::string describe() const override { + return "Equals: " + ::Catch::Detail::stringify( m_comparator ); + } + std::vector const& m_comparator; + }; + + template + struct UnorderedEqualsMatcher : MatcherBase> { + UnorderedEqualsMatcher(std::vector const& target) : m_target(target) {} + bool match(std::vector const& vec) const override { + // Note: This is a reimplementation of std::is_permutation, + // because I don't want to include inside the common path + if (m_target.size() != vec.size()) { + return false; + } + auto lfirst = m_target.begin(), llast = m_target.end(); + auto rfirst = vec.begin(), rlast = vec.end(); + // Cut common prefix to optimize checking of permuted parts + while (lfirst != llast && *lfirst == *rfirst) { + ++lfirst; ++rfirst; + } + if (lfirst == llast) { + return true; + } + + for (auto mid = lfirst; mid != llast; ++mid) { + // Skip already counted items + if (Detail::contains(lfirst, mid, *mid)) { + continue; + } + size_t num_vec = Detail::count(rfirst, rlast, *mid); + if (num_vec == 0 || Detail::count(lfirst, llast, *mid) != num_vec) { + return false; + } + } + + return true; + } + + std::string describe() const override { + return "UnorderedEquals: " + ::Catch::Detail::stringify(m_target); + } + private: + std::vector const& m_target; + }; + + } // namespace Vector + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + template + Vector::ContainsMatcher Contains( std::vector const& comparator ) { + return Vector::ContainsMatcher( comparator ); + } + + template + Vector::ContainsElementMatcher VectorContains( T const& comparator ) { + return Vector::ContainsElementMatcher( comparator ); + } + + template + Vector::EqualsMatcher Equals( std::vector const& comparator ) { + return Vector::EqualsMatcher( comparator ); + } + + template + Vector::UnorderedEqualsMatcher UnorderedEquals(std::vector const& target) { + return Vector::UnorderedEqualsMatcher(target); + } + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_vector.h +namespace Catch { + + template + class MatchExpr : public ITransientExpression { + ArgT const& m_arg; + MatcherT m_matcher; + StringRef m_matcherString; + public: + MatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef const& matcherString ) + : ITransientExpression{ true, matcher.match( arg ) }, + m_arg( arg ), + m_matcher( matcher ), + m_matcherString( matcherString ) + {} + + void streamReconstructedExpression( std::ostream &os ) const override { + auto matcherAsString = m_matcher.toString(); + os << Catch::Detail::stringify( m_arg ) << ' '; + if( matcherAsString == Detail::unprintableString ) + os << m_matcherString; + else + os << matcherAsString; + } + }; + + using StringMatcher = Matchers::Impl::MatcherBase; + + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString ); + + template + auto makeMatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef const& matcherString ) -> MatchExpr { + return MatchExpr( arg, matcher, matcherString ); + } + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + INTERNAL_CATCH_TRY { \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( arg, matcher, #matcher##_catch_sr ) ); \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_MATCHES( macroName, exceptionType, resultDisposition, matcher, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(exceptionType) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast(__VA_ARGS__ ); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( exceptionType const& ex ) { \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( ex, matcher, #matcher##_catch_sr ) ); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +// end catch_capture_matchers.h +#endif +// start catch_generators.hpp + +// start catch_interfaces_generatortracker.h + + +#include + +namespace Catch { + + namespace Generators { + class GeneratorUntypedBase { + public: + GeneratorUntypedBase() = default; + virtual ~GeneratorUntypedBase(); + // Attempts to move the generator to the next element + // + // Returns true iff the move succeeded (and a valid element + // can be retrieved). + virtual bool next() = 0; + }; + using GeneratorBasePtr = std::unique_ptr; + + } // namespace Generators + + struct IGeneratorTracker { + virtual ~IGeneratorTracker(); + virtual auto hasGenerator() const -> bool = 0; + virtual auto getGenerator() const -> Generators::GeneratorBasePtr const& = 0; + virtual void setGenerator( Generators::GeneratorBasePtr&& generator ) = 0; + }; + +} // namespace Catch + +// end catch_interfaces_generatortracker.h +// start catch_enforce.h + +#include + +namespace Catch { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + template + [[noreturn]] + void throw_exception(Ex const& e) { + throw e; + } +#else // ^^ Exceptions are enabled // Exceptions are disabled vv + [[noreturn]] + void throw_exception(std::exception const& e); +#endif +} // namespace Catch; + +#define CATCH_PREPARE_EXCEPTION( type, msg ) \ + type( ( Catch::ReusableStringStream() << msg ).str() ) +#define CATCH_INTERNAL_ERROR( msg ) \ + Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::logic_error, CATCH_INTERNAL_LINEINFO << ": Internal Catch error: " << msg)) +#define CATCH_ERROR( msg ) \ + Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::domain_error, msg )) +#define CATCH_RUNTIME_ERROR( msg ) \ + Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::runtime_error, msg )) +#define CATCH_ENFORCE( condition, msg ) \ + do{ if( !(condition) ) CATCH_ERROR( msg ); } while(false) + +// end catch_enforce.h +#include +#include +#include + +#include +#include + +namespace Catch { + +class GeneratorException : public std::exception { + const char* const m_msg = ""; + +public: + GeneratorException(const char* msg): + m_msg(msg) + {} + + const char* what() const noexcept override final; +}; + +namespace Generators { + + // !TBD move this into its own location? + namespace pf{ + template + std::unique_ptr make_unique( Args&&... args ) { + return std::unique_ptr(new T(std::forward(args)...)); + } + } + + template + struct IGenerator : GeneratorUntypedBase { + virtual ~IGenerator() = default; + + // Returns the current element of the generator + // + // \Precondition The generator is either freshly constructed, + // or the last call to `next()` returned true + virtual T const& get() const = 0; + using type = T; + }; + + template + class SingleValueGenerator final : public IGenerator { + T m_value; + public: + SingleValueGenerator(T const& value) : m_value( value ) {} + SingleValueGenerator(T&& value) : m_value(std::move(value)) {} + + T const& get() const override { + return m_value; + } + bool next() override { + return false; + } + }; + + template + class FixedValuesGenerator final : public IGenerator { + std::vector m_values; + size_t m_idx = 0; + public: + FixedValuesGenerator( std::initializer_list values ) : m_values( values ) {} + + T const& get() const override { + return m_values[m_idx]; + } + bool next() override { + ++m_idx; + return m_idx < m_values.size(); + } + }; + + template + class GeneratorWrapper final { + std::unique_ptr> m_generator; + public: + GeneratorWrapper(std::unique_ptr> generator): + m_generator(std::move(generator)) + {} + T const& get() const { + return m_generator->get(); + } + bool next() { + return m_generator->next(); + } + }; + + template + GeneratorWrapper value(T&& value) { + return GeneratorWrapper(pf::make_unique>(std::forward(value))); + } + template + GeneratorWrapper values(std::initializer_list values) { + return GeneratorWrapper(pf::make_unique>(values)); + } + + template + class Generators : public IGenerator { + std::vector> m_generators; + size_t m_current = 0; + + void populate(GeneratorWrapper&& generator) { + m_generators.emplace_back(std::move(generator)); + } + void populate(T&& val) { + m_generators.emplace_back(value(std::move(val))); + } + template + void populate(U&& val) { + populate(T(std::move(val))); + } + template + void populate(U&& valueOrGenerator, Gs... moreGenerators) { + populate(std::forward(valueOrGenerator)); + populate(std::forward(moreGenerators)...); + } + + public: + template + Generators(Gs... moreGenerators) { + m_generators.reserve(sizeof...(Gs)); + populate(std::forward(moreGenerators)...); + } + + T const& get() const override { + return m_generators[m_current].get(); + } + + bool next() override { + if (m_current >= m_generators.size()) { + return false; + } + const bool current_status = m_generators[m_current].next(); + if (!current_status) { + ++m_current; + } + return m_current < m_generators.size(); + } + }; + + template + GeneratorWrapper> table( std::initializer_list::type...>> tuples ) { + return values>( tuples ); + } + + // Tag type to signal that a generator sequence should convert arguments to a specific type + template + struct as {}; + + template + auto makeGenerators( GeneratorWrapper&& generator, Gs... moreGenerators ) -> Generators { + return Generators(std::move(generator), std::forward(moreGenerators)...); + } + template + auto makeGenerators( GeneratorWrapper&& generator ) -> Generators { + return Generators(std::move(generator)); + } + template + auto makeGenerators( T&& val, Gs... moreGenerators ) -> Generators { + return makeGenerators( value( std::forward( val ) ), std::forward( moreGenerators )... ); + } + template + auto makeGenerators( as, U&& val, Gs... moreGenerators ) -> Generators { + return makeGenerators( value( T( std::forward( val ) ) ), std::forward( moreGenerators )... ); + } + + auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker&; + + template + // Note: The type after -> is weird, because VS2015 cannot parse + // the expression used in the typedef inside, when it is in + // return type. Yeah. + auto generate( SourceLineInfo const& lineInfo, L const& generatorExpression ) -> decltype(std::declval().get()) { + using UnderlyingType = typename decltype(generatorExpression())::type; + + IGeneratorTracker& tracker = acquireGeneratorTracker( lineInfo ); + if (!tracker.hasGenerator()) { + tracker.setGenerator(pf::make_unique>(generatorExpression())); + } + + auto const& generator = static_cast const&>( *tracker.getGenerator() ); + return generator.get(); + } + +} // namespace Generators +} // namespace Catch + +#define GENERATE( ... ) \ + Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, [ ]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) +#define GENERATE_COPY( ... ) \ + Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, [=]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) +#define GENERATE_REF( ... ) \ + Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, [&]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) + +// end catch_generators.hpp +// start catch_generators_generic.hpp + +namespace Catch { +namespace Generators { + + template + class TakeGenerator : public IGenerator { + GeneratorWrapper m_generator; + size_t m_returned = 0; + size_t m_target; + public: + TakeGenerator(size_t target, GeneratorWrapper&& generator): + m_generator(std::move(generator)), + m_target(target) + { + assert(target != 0 && "Empty generators are not allowed"); + } + T const& get() const override { + return m_generator.get(); + } + bool next() override { + ++m_returned; + if (m_returned >= m_target) { + return false; + } + + const auto success = m_generator.next(); + // If the underlying generator does not contain enough values + // then we cut short as well + if (!success) { + m_returned = m_target; + } + return success; + } + }; + + template + GeneratorWrapper take(size_t target, GeneratorWrapper&& generator) { + return GeneratorWrapper(pf::make_unique>(target, std::move(generator))); + } + + template + class FilterGenerator : public IGenerator { + GeneratorWrapper m_generator; + Predicate m_predicate; + public: + template + FilterGenerator(P&& pred, GeneratorWrapper&& generator): + m_generator(std::move(generator)), + m_predicate(std::forward

(pred)) + { + if (!m_predicate(m_generator.get())) { + // It might happen that there are no values that pass the + // filter. In that case we throw an exception. + auto has_initial_value = next(); + if (!has_initial_value) { + Catch::throw_exception(GeneratorException("No valid value found in filtered generator")); + } + } + } + + T const& get() const override { + return m_generator.get(); + } + + bool next() override { + bool success = m_generator.next(); + if (!success) { + return false; + } + while (!m_predicate(m_generator.get()) && (success = m_generator.next()) == true); + return success; + } + }; + + template + GeneratorWrapper filter(Predicate&& pred, GeneratorWrapper&& generator) { + return GeneratorWrapper(std::unique_ptr>(pf::make_unique>(std::forward(pred), std::move(generator)))); + } + + template + class RepeatGenerator : public IGenerator { + GeneratorWrapper m_generator; + mutable std::vector m_returned; + size_t m_target_repeats; + size_t m_current_repeat = 0; + size_t m_repeat_index = 0; + public: + RepeatGenerator(size_t repeats, GeneratorWrapper&& generator): + m_generator(std::move(generator)), + m_target_repeats(repeats) + { + assert(m_target_repeats > 0 && "Repeat generator must repeat at least once"); + } + + T const& get() const override { + if (m_current_repeat == 0) { + m_returned.push_back(m_generator.get()); + return m_returned.back(); + } + return m_returned[m_repeat_index]; + } + + bool next() override { + // There are 2 basic cases: + // 1) We are still reading the generator + // 2) We are reading our own cache + + // In the first case, we need to poke the underlying generator. + // If it happily moves, we are left in that state, otherwise it is time to start reading from our cache + if (m_current_repeat == 0) { + const auto success = m_generator.next(); + if (!success) { + ++m_current_repeat; + } + return m_current_repeat < m_target_repeats; + } + + // In the second case, we need to move indices forward and check that we haven't run up against the end + ++m_repeat_index; + if (m_repeat_index == m_returned.size()) { + m_repeat_index = 0; + ++m_current_repeat; + } + return m_current_repeat < m_target_repeats; + } + }; + + template + GeneratorWrapper repeat(size_t repeats, GeneratorWrapper&& generator) { + return GeneratorWrapper(pf::make_unique>(repeats, std::move(generator))); + } + + template + class MapGenerator : public IGenerator { + // TBD: provide static assert for mapping function, for friendly error message + GeneratorWrapper m_generator; + Func m_function; + // To avoid returning dangling reference, we have to save the values + T m_cache; + public: + template + MapGenerator(F2&& function, GeneratorWrapper&& generator) : + m_generator(std::move(generator)), + m_function(std::forward(function)), + m_cache(m_function(m_generator.get())) + {} + + T const& get() const override { + return m_cache; + } + bool next() override { + const auto success = m_generator.next(); + if (success) { + m_cache = m_function(m_generator.get()); + } + return success; + } + }; + +#if defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable >= 201703 + // std::result_of is deprecated in C++17 and removed in C++20. Hence, it is + // replaced with std::invoke_result here. Also *_t format is preferred over + // typename *::type format. + template + using MapFunctionReturnType = std::remove_reference_t>>; +#else + template + using MapFunctionReturnType = typename std::remove_reference::type>::type>::type; +#endif + + template > + GeneratorWrapper map(Func&& function, GeneratorWrapper&& generator) { + return GeneratorWrapper( + pf::make_unique>(std::forward(function), std::move(generator)) + ); + } + + template + GeneratorWrapper map(Func&& function, GeneratorWrapper&& generator) { + return GeneratorWrapper( + pf::make_unique>(std::forward(function), std::move(generator)) + ); + } + + template + class ChunkGenerator final : public IGenerator> { + std::vector m_chunk; + size_t m_chunk_size; + GeneratorWrapper m_generator; + bool m_used_up = false; + public: + ChunkGenerator(size_t size, GeneratorWrapper generator) : + m_chunk_size(size), m_generator(std::move(generator)) + { + m_chunk.reserve(m_chunk_size); + m_chunk.push_back(m_generator.get()); + for (size_t i = 1; i < m_chunk_size; ++i) { + if (!m_generator.next()) { + Catch::throw_exception(GeneratorException("Not enough values to initialize the first chunk")); + } + m_chunk.push_back(m_generator.get()); + } + } + std::vector const& get() const override { + return m_chunk; + } + bool next() override { + m_chunk.clear(); + for (size_t idx = 0; idx < m_chunk_size; ++idx) { + if (!m_generator.next()) { + return false; + } + m_chunk.push_back(m_generator.get()); + } + return true; + } + }; + + template + GeneratorWrapper> chunk(size_t size, GeneratorWrapper&& generator) { + return GeneratorWrapper>( + pf::make_unique>(size, std::move(generator)) + ); + } + +} // namespace Generators +} // namespace Catch + +// end catch_generators_generic.hpp +// start catch_generators_specific.hpp + +// start catch_context.h + +#include + +namespace Catch { + + struct IResultCapture; + struct IRunner; + struct IConfig; + struct IMutableContext; + + using IConfigPtr = std::shared_ptr; + + struct IContext + { + virtual ~IContext(); + + virtual IResultCapture* getResultCapture() = 0; + virtual IRunner* getRunner() = 0; + virtual IConfigPtr const& getConfig() const = 0; + }; + + struct IMutableContext : IContext + { + virtual ~IMutableContext(); + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setRunner( IRunner* runner ) = 0; + virtual void setConfig( IConfigPtr const& config ) = 0; + + private: + static IMutableContext *currentContext; + friend IMutableContext& getCurrentMutableContext(); + friend void cleanUpContext(); + static void createContext(); + }; + + inline IMutableContext& getCurrentMutableContext() + { + if( !IMutableContext::currentContext ) + IMutableContext::createContext(); + return *IMutableContext::currentContext; + } + + inline IContext& getCurrentContext() + { + return getCurrentMutableContext(); + } + + void cleanUpContext(); +} + +// end catch_context.h +// start catch_interfaces_config.h + +#include +#include +#include +#include + +namespace Catch { + + enum class Verbosity { + Quiet = 0, + Normal, + High + }; + + struct WarnAbout { enum What { + Nothing = 0x00, + NoAssertions = 0x01, + NoTests = 0x02 + }; }; + + struct ShowDurations { enum OrNot { + DefaultForReporter, + Always, + Never + }; }; + struct RunTests { enum InWhatOrder { + InDeclarationOrder, + InLexicographicalOrder, + InRandomOrder + }; }; + struct UseColour { enum YesOrNo { + Auto, + Yes, + No + }; }; + struct WaitForKeypress { enum When { + Never, + BeforeStart = 1, + BeforeExit = 2, + BeforeStartAndExit = BeforeStart | BeforeExit + }; }; + + class TestSpec; + + struct IConfig : NonCopyable { + + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual std::ostream& stream() const = 0; + virtual std::string name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual bool warnAboutNoTests() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations::OrNot showDurations() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual bool hasTestFilters() const = 0; + virtual std::vector const& getTestsOrTags() const = 0; + virtual RunTests::InWhatOrder runOrder() const = 0; + virtual unsigned int rngSeed() const = 0; + virtual int benchmarkResolutionMultiple() const = 0; + virtual UseColour::YesOrNo useColour() const = 0; + virtual std::vector const& getSectionsToRun() const = 0; + virtual Verbosity verbosity() const = 0; + }; + + using IConfigPtr = std::shared_ptr; +} + +// end catch_interfaces_config.h +#include + +namespace Catch { +namespace Generators { + +template +class RandomFloatingGenerator final : public IGenerator { + // FIXME: What is the right seed? + std::minstd_rand m_rand; + std::uniform_real_distribution m_dist; + Float m_current_number; +public: + + RandomFloatingGenerator(Float a, Float b): + m_rand(getCurrentContext().getConfig()->rngSeed()), + m_dist(a, b) { + static_cast(next()); + } + + Float const& get() const override { + return m_current_number; + } + bool next() override { + m_current_number = m_dist(m_rand); + return true; + } +}; + +template +class RandomIntegerGenerator final : public IGenerator { + std::minstd_rand m_rand; + std::uniform_int_distribution m_dist; + Integer m_current_number; +public: + + RandomIntegerGenerator(Integer a, Integer b): + m_rand(getCurrentContext().getConfig()->rngSeed()), + m_dist(a, b) { + static_cast(next()); + } + + Integer const& get() const override { + return m_current_number; + } + bool next() override { + m_current_number = m_dist(m_rand); + return true; + } +}; + +// TODO: Ideally this would be also constrained against the various char types, +// but I don't expect users to run into that in practice. +template +typename std::enable_if::value && !std::is_same::value, +GeneratorWrapper>::type +random(T a, T b) { + return GeneratorWrapper( + pf::make_unique>(a, b) + ); +} + +template +typename std::enable_if::value, +GeneratorWrapper>::type +random(T a, T b) { + return GeneratorWrapper( + pf::make_unique>(a, b) + ); +} + +template +class RangeGenerator final : public IGenerator { + T m_current; + T m_end; + T m_step; + bool m_positive; + +public: + RangeGenerator(T const& start, T const& end, T const& step): + m_current(start), + m_end(end), + m_step(step), + m_positive(m_step > T(0)) + { + assert(m_current != m_end && "Range start and end cannot be equal"); + assert(m_step != T(0) && "Step size cannot be zero"); + assert(((m_positive && m_current <= m_end) || (!m_positive && m_current >= m_end)) && "Step moves away from end"); + } + + RangeGenerator(T const& start, T const& end): + RangeGenerator(start, end, (start < end) ? T(1) : T(-1)) + {} + + T const& get() const override { + return m_current; + } + + bool next() override { + m_current += m_step; + return (m_positive) ? (m_current < m_end) : (m_current > m_end); + } +}; + +template +GeneratorWrapper range(T const& start, T const& end, T const& step) { + static_assert(std::is_integral::value && !std::is_same::value, "Type must be an integer"); + return GeneratorWrapper(pf::make_unique>(start, end, step)); +} + +template +GeneratorWrapper range(T const& start, T const& end) { + static_assert(std::is_integral::value && !std::is_same::value, "Type must be an integer"); + return GeneratorWrapper(pf::make_unique>(start, end)); +} + +} // namespace Generators +} // namespace Catch + +// end catch_generators_specific.hpp + +// These files are included here so the single_include script doesn't put them +// in the conditionally compiled sections +// start catch_test_case_info.h + +#include +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + struct ITestInvoker; + + struct TestCaseInfo { + enum SpecialProperties{ + None = 0, + IsHidden = 1 << 1, + ShouldFail = 1 << 2, + MayFail = 1 << 3, + Throws = 1 << 4, + NonPortable = 1 << 5, + Benchmark = 1 << 6 + }; + + TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::vector const& _tags, + SourceLineInfo const& _lineInfo ); + + friend void setTags( TestCaseInfo& testCaseInfo, std::vector tags ); + + bool isHidden() const; + bool throws() const; + bool okToFail() const; + bool expectedToFail() const; + + std::string tagsAsString() const; + + std::string name; + std::string className; + std::string description; + std::vector tags; + std::vector lcaseTags; + SourceLineInfo lineInfo; + SpecialProperties properties; + }; + + class TestCase : public TestCaseInfo { + public: + + TestCase( ITestInvoker* testCase, TestCaseInfo&& info ); + + TestCase withName( std::string const& _newName ) const; + + void invoke() const; + + TestCaseInfo const& getTestCaseInfo() const; + + bool operator == ( TestCase const& other ) const; + bool operator < ( TestCase const& other ) const; + + private: + std::shared_ptr test; + }; + + TestCase makeTestCase( ITestInvoker* testCase, + std::string const& className, + NameAndTags const& nameAndTags, + SourceLineInfo const& lineInfo ); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_test_case_info.h +// start catch_interfaces_runner.h + +namespace Catch { + + struct IRunner { + virtual ~IRunner(); + virtual bool aborting() const = 0; + }; +} + +// end catch_interfaces_runner.h + +#ifdef __OBJC__ +// start catch_objc.hpp + +#import + +#include + +// NB. Any general catch headers included here must be included +// in catch.hpp first to make sure they are included by the single +// header for non obj-usage + +/////////////////////////////////////////////////////////////////////////////// +// This protocol is really only here for (self) documenting purposes, since +// all its methods are optional. +@protocol OcFixture + +@optional + +-(void) setUp; +-(void) tearDown; + +@end + +namespace Catch { + + class OcMethod : public ITestInvoker { + + public: + OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} + + virtual void invoke() const { + id obj = [[m_cls alloc] init]; + + performOptionalSelector( obj, @selector(setUp) ); + performOptionalSelector( obj, m_sel ); + performOptionalSelector( obj, @selector(tearDown) ); + + arcSafeRelease( obj ); + } + private: + virtual ~OcMethod() {} + + Class m_cls; + SEL m_sel; + }; + + namespace Detail{ + + inline std::string getAnnotation( Class cls, + std::string const& annotationName, + std::string const& testCaseName ) { + NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; + SEL sel = NSSelectorFromString( selStr ); + arcSafeRelease( selStr ); + id value = performOptionalSelector( cls, sel ); + if( value ) + return [(NSString*)value UTF8String]; + return ""; + } + } + + inline std::size_t registerTestMethods() { + std::size_t noTestMethods = 0; + int noClasses = objc_getClassList( nullptr, 0 ); + + Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); + objc_getClassList( classes, noClasses ); + + for( int c = 0; c < noClasses; c++ ) { + Class cls = classes[c]; + { + u_int count; + Method* methods = class_copyMethodList( cls, &count ); + for( u_int m = 0; m < count ; m++ ) { + SEL selector = method_getName(methods[m]); + std::string methodName = sel_getName(selector); + if( startsWith( methodName, "Catch_TestCase_" ) ) { + std::string testCaseName = methodName.substr( 15 ); + std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); + std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); + const char* className = class_getName( cls ); + + getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, NameAndTags( name.c_str(), desc.c_str() ), SourceLineInfo("",0) ) ); + noTestMethods++; + } + } + free(methods); + } + } + return noTestMethods; + } + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) + + namespace Matchers { + namespace Impl { + namespace NSStringMatchers { + + struct StringHolder : MatcherBase{ + StringHolder( NSString* substr ) : m_substr( [substr copy] ){} + StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} + StringHolder() { + arcSafeRelease( m_substr ); + } + + bool match( NSString* const& str ) const override { + return false; + } + + NSString* CATCH_ARC_STRONG m_substr; + }; + + struct Equals : StringHolder { + Equals( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* const& str ) const override { + return (str != nil || m_substr == nil ) && + [str isEqualToString:m_substr]; + } + + std::string describe() const override { + return "equals string: " + Catch::Detail::stringify( m_substr ); + } + }; + + struct Contains : StringHolder { + Contains( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* const& str ) const override { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location != NSNotFound; + } + + std::string describe() const override { + return "contains string: " + Catch::Detail::stringify( m_substr ); + } + }; + + struct StartsWith : StringHolder { + StartsWith( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* const& str ) const override { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == 0; + } + + std::string describe() const override { + return "starts with: " + Catch::Detail::stringify( m_substr ); + } + }; + struct EndsWith : StringHolder { + EndsWith( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* const& str ) const override { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == [str length] - [m_substr length]; + } + + std::string describe() const override { + return "ends with: " + Catch::Detail::stringify( m_substr ); + } + }; + + } // namespace NSStringMatchers + } // namespace Impl + + inline Impl::NSStringMatchers::Equals + Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } + + inline Impl::NSStringMatchers::Contains + Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } + + inline Impl::NSStringMatchers::StartsWith + StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } + + inline Impl::NSStringMatchers::EndsWith + EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } + + } // namespace Matchers + + using namespace Matchers; + +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define OC_MAKE_UNIQUE_NAME( root, uniqueSuffix ) root##uniqueSuffix +#define OC_TEST_CASE2( name, desc, uniqueSuffix ) \ ++(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Name_test_, uniqueSuffix ) \ +{ \ +return @ name; \ +} \ ++(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Description_test_, uniqueSuffix ) \ +{ \ +return @ desc; \ +} \ +-(void) OC_MAKE_UNIQUE_NAME( Catch_TestCase_test_, uniqueSuffix ) + +#define OC_TEST_CASE( name, desc ) OC_TEST_CASE2( name, desc, __LINE__ ) + +// end catch_objc.hpp +#endif + +#ifdef CATCH_CONFIG_EXTERNAL_INTERFACES +// start catch_external_interfaces.h + +// start catch_reporter_bases.hpp + +// start catch_interfaces_reporter.h + +// start catch_config.hpp + +// start catch_test_spec_parser.h + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// start catch_test_spec.h + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// start catch_wildcard_pattern.h + +namespace Catch +{ + class WildcardPattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + + WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ); + virtual ~WildcardPattern() = default; + virtual bool matches( std::string const& str ) const; + + private: + std::string adjustCase( std::string const& str ) const; + CaseSensitive::Choice m_caseSensitivity; + WildcardPosition m_wildcard = NoWildcard; + std::string m_pattern; + }; +} + +// end catch_wildcard_pattern.h +#include +#include +#include + +namespace Catch { + + class TestSpec { + struct Pattern { + virtual ~Pattern(); + virtual bool matches( TestCaseInfo const& testCase ) const = 0; + }; + using PatternPtr = std::shared_ptr; + + class NamePattern : public Pattern { + public: + NamePattern( std::string const& name ); + virtual ~NamePattern(); + virtual bool matches( TestCaseInfo const& testCase ) const override; + private: + WildcardPattern m_wildcardPattern; + }; + + class TagPattern : public Pattern { + public: + TagPattern( std::string const& tag ); + virtual ~TagPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const override; + private: + std::string m_tag; + }; + + class ExcludedPattern : public Pattern { + public: + ExcludedPattern( PatternPtr const& underlyingPattern ); + virtual ~ExcludedPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const override; + private: + PatternPtr m_underlyingPattern; + }; + + struct Filter { + std::vector m_patterns; + + bool matches( TestCaseInfo const& testCase ) const; + }; + + public: + bool hasFilters() const; + bool matches( TestCaseInfo const& testCase ) const; + + private: + std::vector m_filters; + + friend class TestSpecParser; + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_test_spec.h +// start catch_interfaces_tag_alias_registry.h + +#include + +namespace Catch { + + struct TagAlias; + + struct ITagAliasRegistry { + virtual ~ITagAliasRegistry(); + // Nullptr if not present + virtual TagAlias const* find( std::string const& alias ) const = 0; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + + static ITagAliasRegistry const& get(); + }; + +} // end namespace Catch + +// end catch_interfaces_tag_alias_registry.h +namespace Catch { + + class TestSpecParser { + enum Mode{ None, Name, QuotedName, Tag, EscapedName }; + Mode m_mode = None; + bool m_exclusion = false; + std::size_t m_start = std::string::npos, m_pos = 0; + std::string m_arg; + std::vector m_escapeChars; + TestSpec::Filter m_currentFilter; + TestSpec m_testSpec; + ITagAliasRegistry const* m_tagAliases = nullptr; + + public: + TestSpecParser( ITagAliasRegistry const& tagAliases ); + + TestSpecParser& parse( std::string const& arg ); + TestSpec testSpec(); + + private: + void visitChar( char c ); + void startNewMode( Mode mode, std::size_t start ); + void escape(); + std::string subString() const; + + template + void addPattern() { + std::string token = subString(); + for( std::size_t i = 0; i < m_escapeChars.size(); ++i ) + token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 ); + m_escapeChars.clear(); + if( startsWith( token, "exclude:" ) ) { + m_exclusion = true; + token = token.substr( 8 ); + } + if( !token.empty() ) { + TestSpec::PatternPtr pattern = std::make_shared( token ); + if( m_exclusion ) + pattern = std::make_shared( pattern ); + m_currentFilter.m_patterns.push_back( pattern ); + } + m_exclusion = false; + m_mode = None; + } + + void addFilter(); + }; + TestSpec parseTestSpec( std::string const& arg ); + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_test_spec_parser.h +// Libstdc++ doesn't like incomplete classes for unique_ptr + +#include +#include +#include + +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { + + struct IStream; + + struct ConfigData { + bool listTests = false; + bool listTags = false; + bool listReporters = false; + bool listTestNamesOnly = false; + + bool showSuccessfulTests = false; + bool shouldDebugBreak = false; + bool noThrow = false; + bool showHelp = false; + bool showInvisibles = false; + bool filenamesAsTags = false; + bool libIdentify = false; + + int abortAfter = -1; + unsigned int rngSeed = 0; + int benchmarkResolutionMultiple = 100; + + Verbosity verbosity = Verbosity::Normal; + WarnAbout::What warnings = WarnAbout::Nothing; + ShowDurations::OrNot showDurations = ShowDurations::DefaultForReporter; + RunTests::InWhatOrder runOrder = RunTests::InDeclarationOrder; + UseColour::YesOrNo useColour = UseColour::Auto; + WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; + + std::string outputFilename; + std::string name; + std::string processName; +#ifndef CATCH_CONFIG_DEFAULT_REPORTER +#define CATCH_CONFIG_DEFAULT_REPORTER "console" +#endif + std::string reporterName = CATCH_CONFIG_DEFAULT_REPORTER; +#undef CATCH_CONFIG_DEFAULT_REPORTER + + std::vector testsOrTags; + std::vector sectionsToRun; + }; + + class Config : public IConfig { + public: + + Config() = default; + Config( ConfigData const& data ); + virtual ~Config() = default; + + std::string const& getFilename() const; + + bool listTests() const; + bool listTestNamesOnly() const; + bool listTags() const; + bool listReporters() const; + + std::string getProcessName() const; + std::string const& getReporterName() const; + + std::vector const& getTestsOrTags() const override; + std::vector const& getSectionsToRun() const override; + + virtual TestSpec const& testSpec() const override; + bool hasTestFilters() const override; + + bool showHelp() const; + + // IConfig interface + bool allowThrows() const override; + std::ostream& stream() const override; + std::string name() const override; + bool includeSuccessfulResults() const override; + bool warnAboutMissingAssertions() const override; + bool warnAboutNoTests() const override; + ShowDurations::OrNot showDurations() const override; + RunTests::InWhatOrder runOrder() const override; + unsigned int rngSeed() const override; + int benchmarkResolutionMultiple() const override; + UseColour::YesOrNo useColour() const override; + bool shouldDebugBreak() const override; + int abortAfter() const override; + bool showInvisibles() const override; + Verbosity verbosity() const override; + + private: + + IStream const* openStream(); + ConfigData m_data; + + std::unique_ptr m_stream; + TestSpec m_testSpec; + bool m_hasTestFilters = false; + }; + +} // end namespace Catch + +// end catch_config.hpp +// start catch_assertionresult.h + +#include + +namespace Catch { + + struct AssertionResultData + { + AssertionResultData() = delete; + + AssertionResultData( ResultWas::OfType _resultType, LazyExpression const& _lazyExpression ); + + std::string message; + mutable std::string reconstructedExpression; + LazyExpression lazyExpression; + ResultWas::OfType resultType; + + std::string reconstructExpression() const; + }; + + class AssertionResult { + public: + AssertionResult() = delete; + AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + std::string getMessage() const; + SourceLineInfo getSourceInfo() const; + StringRef getTestMacroName() const; + + //protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end namespace Catch + +// end catch_assertionresult.h +// start catch_option.hpp + +namespace Catch { + + // An optional type + template + class Option { + public: + Option() : nullableValue( nullptr ) {} + Option( T const& _value ) + : nullableValue( new( storage ) T( _value ) ) + {} + Option( Option const& _other ) + : nullableValue( _other ? new( storage ) T( *_other ) : nullptr ) + {} + + ~Option() { + reset(); + } + + Option& operator= ( Option const& _other ) { + if( &_other != this ) { + reset(); + if( _other ) + nullableValue = new( storage ) T( *_other ); + } + return *this; + } + Option& operator = ( T const& _value ) { + reset(); + nullableValue = new( storage ) T( _value ); + return *this; + } + + void reset() { + if( nullableValue ) + nullableValue->~T(); + nullableValue = nullptr; + } + + T& operator*() { return *nullableValue; } + T const& operator*() const { return *nullableValue; } + T* operator->() { return nullableValue; } + const T* operator->() const { return nullableValue; } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != nullptr; } + bool none() const { return nullableValue == nullptr; } + + bool operator !() const { return nullableValue == nullptr; } + explicit operator bool() const { + return some(); + } + + private: + T *nullableValue; + alignas(alignof(T)) char storage[sizeof(T)]; + }; + +} // end namespace Catch + +// end catch_option.hpp +#include +#include +#include +#include +#include + +namespace Catch { + + struct ReporterConfig { + explicit ReporterConfig( IConfigPtr const& _fullConfig ); + + ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream ); + + std::ostream& stream() const; + IConfigPtr fullConfig() const; + + private: + std::ostream* m_stream; + IConfigPtr m_fullConfig; + }; + + struct ReporterPreferences { + bool shouldRedirectStdOut = false; + bool shouldReportAllAssertions = false; + }; + + template + struct LazyStat : Option { + LazyStat& operator=( T const& _value ) { + Option::operator=( _value ); + used = false; + return *this; + } + void reset() { + Option::reset(); + used = false; + } + bool used = false; + }; + + struct TestRunInfo { + TestRunInfo( std::string const& _name ); + std::string name; + }; + struct GroupInfo { + GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ); + + std::string name; + std::size_t groupIndex; + std::size_t groupsCounts; + }; + + struct AssertionStats { + AssertionStats( AssertionResult const& _assertionResult, + std::vector const& _infoMessages, + Totals const& _totals ); + + AssertionStats( AssertionStats const& ) = default; + AssertionStats( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = delete; + AssertionStats& operator = ( AssertionStats && ) = delete; + virtual ~AssertionStats(); + + AssertionResult assertionResult; + std::vector infoMessages; + Totals totals; + }; + + struct SectionStats { + SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ); + SectionStats( SectionStats const& ) = default; + SectionStats( SectionStats && ) = default; + SectionStats& operator = ( SectionStats const& ) = default; + SectionStats& operator = ( SectionStats && ) = default; + virtual ~SectionStats(); + + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; + + struct TestCaseStats { + TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ); + + TestCaseStats( TestCaseStats const& ) = default; + TestCaseStats( TestCaseStats && ) = default; + TestCaseStats& operator = ( TestCaseStats const& ) = default; + TestCaseStats& operator = ( TestCaseStats && ) = default; + virtual ~TestCaseStats(); + + TestCaseInfo testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; + + struct TestGroupStats { + TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ); + TestGroupStats( GroupInfo const& _groupInfo ); + + TestGroupStats( TestGroupStats const& ) = default; + TestGroupStats( TestGroupStats && ) = default; + TestGroupStats& operator = ( TestGroupStats const& ) = default; + TestGroupStats& operator = ( TestGroupStats && ) = default; + virtual ~TestGroupStats(); + + GroupInfo groupInfo; + Totals totals; + bool aborting; + }; + + struct TestRunStats { + TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ); + + TestRunStats( TestRunStats const& ) = default; + TestRunStats( TestRunStats && ) = default; + TestRunStats& operator = ( TestRunStats const& ) = default; + TestRunStats& operator = ( TestRunStats && ) = default; + virtual ~TestRunStats(); + + TestRunInfo runInfo; + Totals totals; + bool aborting; + }; + + struct BenchmarkInfo { + std::string name; + }; + struct BenchmarkStats { + BenchmarkInfo info; + std::size_t iterations; + uint64_t elapsedTimeInNanoseconds; + }; + + struct IStreamingReporter { + virtual ~IStreamingReporter() = default; + + // Implementing class must also provide the following static methods: + // static std::string getDescription(); + // static std::set getSupportedVerbosities() + + virtual ReporterPreferences getPreferences() const = 0; + + virtual void noMatchingTestCases( std::string const& spec ) = 0; + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; + virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + + // *** experimental *** + virtual void benchmarkStarting( BenchmarkInfo const& ) {} + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + + // *** experimental *** + virtual void benchmarkEnded( BenchmarkStats const& ) {} + + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; + virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + + virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + + // Default empty implementation provided + virtual void fatalErrorEncountered( StringRef name ); + + virtual bool isMulti() const; + }; + using IStreamingReporterPtr = std::unique_ptr; + + struct IReporterFactory { + virtual ~IReporterFactory(); + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + using IReporterFactoryPtr = std::shared_ptr; + + struct IReporterRegistry { + using FactoryMap = std::map; + using Listeners = std::vector; + + virtual ~IReporterRegistry(); + virtual IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const = 0; + virtual FactoryMap const& getFactories() const = 0; + virtual Listeners const& getListeners() const = 0; + }; + +} // end namespace Catch + +// end catch_interfaces_reporter.h +#include +#include +#include +#include +#include +#include +#include + +namespace Catch { + void prepareExpandedExpression(AssertionResult& result); + + // Returns double formatted as %.3f (format expected on output) + std::string getFormattedDuration( double duration ); + + std::string serializeFilters( std::vector const& container ); + + template + struct StreamingReporterBase : IStreamingReporter { + + StreamingReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) ) + CATCH_ERROR( "Verbosity level not supported by this reporter" ); + } + + ReporterPreferences getPreferences() const override { + return m_reporterPrefs; + } + + static std::set getSupportedVerbosities() { + return { Verbosity::Normal }; + } + + ~StreamingReporterBase() override = default; + + void noMatchingTestCases(std::string const&) override {} + + void testRunStarting(TestRunInfo const& _testRunInfo) override { + currentTestRunInfo = _testRunInfo; + } + + void testGroupStarting(GroupInfo const& _groupInfo) override { + currentGroupInfo = _groupInfo; + } + + void testCaseStarting(TestCaseInfo const& _testInfo) override { + currentTestCaseInfo = _testInfo; + } + void sectionStarting(SectionInfo const& _sectionInfo) override { + m_sectionStack.push_back(_sectionInfo); + } + + void sectionEnded(SectionStats const& /* _sectionStats */) override { + m_sectionStack.pop_back(); + } + void testCaseEnded(TestCaseStats const& /* _testCaseStats */) override { + currentTestCaseInfo.reset(); + } + void testGroupEnded(TestGroupStats const& /* _testGroupStats */) override { + currentGroupInfo.reset(); + } + void testRunEnded(TestRunStats const& /* _testRunStats */) override { + currentTestCaseInfo.reset(); + currentGroupInfo.reset(); + currentTestRunInfo.reset(); + } + + void skipTest(TestCaseInfo const&) override { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. + } + + IConfigPtr m_config; + std::ostream& stream; + + LazyStat currentTestRunInfo; + LazyStat currentGroupInfo; + LazyStat currentTestCaseInfo; + + std::vector m_sectionStack; + ReporterPreferences m_reporterPrefs; + }; + + template + struct CumulativeReporterBase : IStreamingReporter { + template + struct Node { + explicit Node( T const& _value ) : value( _value ) {} + virtual ~Node() {} + + using ChildNodes = std::vector>; + T value; + ChildNodes children; + }; + struct SectionNode { + explicit SectionNode(SectionStats const& _stats) : stats(_stats) {} + virtual ~SectionNode() = default; + + bool operator == (SectionNode const& other) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + bool operator == (std::shared_ptr const& other) const { + return operator==(*other); + } + + SectionStats stats; + using ChildSections = std::vector>; + using Assertions = std::vector; + ChildSections childSections; + Assertions assertions; + std::string stdOut; + std::string stdErr; + }; + + struct BySectionInfo { + BySectionInfo( SectionInfo const& other ) : m_other( other ) {} + BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} + bool operator() (std::shared_ptr const& node) const { + return ((node->stats.sectionInfo.name == m_other.name) && + (node->stats.sectionInfo.lineInfo == m_other.lineInfo)); + } + void operator=(BySectionInfo const&) = delete; + + private: + SectionInfo const& m_other; + }; + + using TestCaseNode = Node; + using TestGroupNode = Node; + using TestRunNode = Node; + + CumulativeReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) ) + CATCH_ERROR( "Verbosity level not supported by this reporter" ); + } + ~CumulativeReporterBase() override = default; + + ReporterPreferences getPreferences() const override { + return m_reporterPrefs; + } + + static std::set getSupportedVerbosities() { + return { Verbosity::Normal }; + } + + void testRunStarting( TestRunInfo const& ) override {} + void testGroupStarting( GroupInfo const& ) override {} + + void testCaseStarting( TestCaseInfo const& ) override {} + + void sectionStarting( SectionInfo const& sectionInfo ) override { + SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); + std::shared_ptr node; + if( m_sectionStack.empty() ) { + if( !m_rootSection ) + m_rootSection = std::make_shared( incompleteStats ); + node = m_rootSection; + } + else { + SectionNode& parentNode = *m_sectionStack.back(); + auto it = + std::find_if( parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo( sectionInfo ) ); + if( it == parentNode.childSections.end() ) { + node = std::make_shared( incompleteStats ); + parentNode.childSections.push_back( node ); + } + else + node = *it; + } + m_sectionStack.push_back( node ); + m_deepestSection = std::move(node); + } + + void assertionStarting(AssertionInfo const&) override {} + + bool assertionEnded(AssertionStats const& assertionStats) override { + assert(!m_sectionStack.empty()); + // AssertionResult holds a pointer to a temporary DecomposedExpression, + // which getExpandedExpression() calls to build the expression string. + // Our section stack copy of the assertionResult will likely outlive the + // temporary, so it must be expanded or discarded now to avoid calling + // a destroyed object later. + prepareExpandedExpression(const_cast( assertionStats.assertionResult ) ); + SectionNode& sectionNode = *m_sectionStack.back(); + sectionNode.assertions.push_back(assertionStats); + return true; + } + void sectionEnded(SectionStats const& sectionStats) override { + assert(!m_sectionStack.empty()); + SectionNode& node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + void testCaseEnded(TestCaseStats const& testCaseStats) override { + auto node = std::make_shared(testCaseStats); + assert(m_sectionStack.size() == 0); + node->children.push_back(m_rootSection); + m_testCases.push_back(node); + m_rootSection.reset(); + + assert(m_deepestSection); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + void testGroupEnded(TestGroupStats const& testGroupStats) override { + auto node = std::make_shared(testGroupStats); + node->children.swap(m_testCases); + m_testGroups.push_back(node); + } + void testRunEnded(TestRunStats const& testRunStats) override { + auto node = std::make_shared(testRunStats); + node->children.swap(m_testGroups); + m_testRuns.push_back(node); + testRunEndedCumulative(); + } + virtual void testRunEndedCumulative() = 0; + + void skipTest(TestCaseInfo const&) override {} + + IConfigPtr m_config; + std::ostream& stream; + std::vector m_assertions; + std::vector>> m_sections; + std::vector> m_testCases; + std::vector> m_testGroups; + + std::vector> m_testRuns; + + std::shared_ptr m_rootSection; + std::shared_ptr m_deepestSection; + std::vector> m_sectionStack; + ReporterPreferences m_reporterPrefs; + }; + + template + char const* getLineOfChars() { + static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; + if( !*line ) { + std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); + line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; + } + return line; + } + + struct TestEventListenerBase : StreamingReporterBase { + TestEventListenerBase( ReporterConfig const& _config ); + + static std::set getSupportedVerbosities(); + + void assertionStarting(AssertionInfo const&) override; + bool assertionEnded(AssertionStats const&) override; + }; + +} // end namespace Catch + +// end catch_reporter_bases.hpp +// start catch_console_colour.h + +namespace Catch { + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + BrightYellow = Bright | Yellow, + + // By intention + FileName = LightGrey, + Warning = BrightYellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + + OriginalExpression = Cyan, + ReconstructedExpression = BrightYellow, + + SecondaryText = LightGrey, + Headers = White + }; + + // Use constructed object for RAII guard + Colour( Code _colourCode ); + Colour( Colour&& other ) noexcept; + Colour& operator=( Colour&& other ) noexcept; + ~Colour(); + + // Use static method for one-shot changes + static void use( Code _colourCode ); + + private: + bool m_moved = false; + }; + + std::ostream& operator << ( std::ostream& os, Colour const& ); + +} // end namespace Catch + +// end catch_console_colour.h +// start catch_reporter_registrars.hpp + + +namespace Catch { + + template + class ReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { + return std::unique_ptr( new T( config ) ); + } + + virtual std::string getDescription() const override { + return T::getDescription(); + } + }; + + public: + + explicit ReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, std::make_shared() ); + } + }; + + template + class ListenerRegistrar { + + class ListenerFactory : public IReporterFactory { + + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { + return std::unique_ptr( new T( config ) ); + } + virtual std::string getDescription() const override { + return std::string(); + } + }; + + public: + + ListenerRegistrar() { + getMutableRegistryHub().registerListener( std::make_shared() ); + } + }; +} + +#if !defined(CATCH_CONFIG_DISABLE) + +#define CATCH_REGISTER_REPORTER( name, reporterType ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + +#define CATCH_REGISTER_LISTENER( listenerType ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; } \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#else // CATCH_CONFIG_DISABLE + +#define CATCH_REGISTER_REPORTER(name, reporterType) +#define CATCH_REGISTER_LISTENER(listenerType) + +#endif // CATCH_CONFIG_DISABLE + +// end catch_reporter_registrars.hpp +// Allow users to base their work off existing reporters +// start catch_reporter_compact.h + +namespace Catch { + + struct CompactReporter : StreamingReporterBase { + + using StreamingReporterBase::StreamingReporterBase; + + ~CompactReporter() override; + + static std::string getDescription(); + + ReporterPreferences getPreferences() const override; + + void noMatchingTestCases(std::string const& spec) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& _assertionStats) override; + + void sectionEnded(SectionStats const& _sectionStats) override; + + void testRunEnded(TestRunStats const& _testRunStats) override; + + }; + +} // end namespace Catch + +// end catch_reporter_compact.h +// start catch_reporter_console.h + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + // Fwd decls + struct SummaryColumn; + class TablePrinter; + + struct ConsoleReporter : StreamingReporterBase { + std::unique_ptr m_tablePrinter; + + ConsoleReporter(ReporterConfig const& config); + ~ConsoleReporter() override; + static std::string getDescription(); + + void noMatchingTestCases(std::string const& spec) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& _assertionStats) override; + + void sectionStarting(SectionInfo const& _sectionInfo) override; + void sectionEnded(SectionStats const& _sectionStats) override; + + void benchmarkStarting(BenchmarkInfo const& info) override; + void benchmarkEnded(BenchmarkStats const& stats) override; + + void testCaseEnded(TestCaseStats const& _testCaseStats) override; + void testGroupEnded(TestGroupStats const& _testGroupStats) override; + void testRunEnded(TestRunStats const& _testRunStats) override; + void testRunStarting(TestRunInfo const& _testRunInfo) override; + private: + + void lazyPrint(); + + void lazyPrintWithoutClosingBenchmarkTable(); + void lazyPrintRunInfo(); + void lazyPrintGroupInfo(); + void printTestCaseAndSectionHeader(); + + void printClosedHeader(std::string const& _name); + void printOpenHeader(std::string const& _name); + + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString(std::string const& _string, std::size_t indent = 0); + + void printTotals(Totals const& totals); + void printSummaryRow(std::string const& label, std::vector const& cols, std::size_t row); + + void printTotalsDivider(Totals const& totals); + void printSummaryDivider(); + void printTestFilters(); + + private: + bool m_headerPrinted = false; + }; + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +// end catch_reporter_console.h +// start catch_reporter_junit.h + +// start catch_xmlwriter.h + +#include + +namespace Catch { + + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); + + void encodeTo( std::ostream& os ) const; + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); + + private: + std::string m_str; + ForWhat m_forWhat; + }; + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ); + + ScopedElement( ScopedElement&& other ) noexcept; + ScopedElement& operator=( ScopedElement&& other ) noexcept; + + ~ScopedElement(); + + ScopedElement& writeText( std::string const& text, bool indent = true ); + + template + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer = nullptr; + }; + + XmlWriter( std::ostream& os = Catch::cout() ); + ~XmlWriter(); + + XmlWriter( XmlWriter const& ) = delete; + XmlWriter& operator=( XmlWriter const& ) = delete; + + XmlWriter& startElement( std::string const& name ); + + ScopedElement scopedElement( std::string const& name ); + + XmlWriter& endElement(); + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); + + XmlWriter& writeAttribute( std::string const& name, bool attribute ); + + template + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + ReusableStringStream rss; + rss << attribute; + return writeAttribute( name, rss.str() ); + } + + XmlWriter& writeText( std::string const& text, bool indent = true ); + + XmlWriter& writeComment( std::string const& text ); + + void writeStylesheetRef( std::string const& url ); + + XmlWriter& writeBlankLine(); + + void ensureTagClosed(); + + private: + + void writeDeclaration(); + + void newlineIfNecessary(); + + bool m_tagIsOpen = false; + bool m_needsNewline = false; + std::vector m_tags; + std::string m_indent; + std::ostream& m_os; + }; + +} + +// end catch_xmlwriter.h +namespace Catch { + + class JunitReporter : public CumulativeReporterBase { + public: + JunitReporter(ReporterConfig const& _config); + + ~JunitReporter() override; + + static std::string getDescription(); + + void noMatchingTestCases(std::string const& /*spec*/) override; + + void testRunStarting(TestRunInfo const& runInfo) override; + + void testGroupStarting(GroupInfo const& groupInfo) override; + + void testCaseStarting(TestCaseInfo const& testCaseInfo) override; + bool assertionEnded(AssertionStats const& assertionStats) override; + + void testCaseEnded(TestCaseStats const& testCaseStats) override; + + void testGroupEnded(TestGroupStats const& testGroupStats) override; + + void testRunEndedCumulative() override; + + void writeGroup(TestGroupNode const& groupNode, double suiteTime); + + void writeTestCase(TestCaseNode const& testCaseNode); + + void writeSection(std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode); + + void writeAssertions(SectionNode const& sectionNode); + void writeAssertion(AssertionStats const& stats); + + XmlWriter xml; + Timer suiteTimer; + std::string stdOutForSuite; + std::string stdErrForSuite; + unsigned int unexpectedExceptions = 0; + bool m_okToFail = false; + }; + +} // end namespace Catch + +// end catch_reporter_junit.h +// start catch_reporter_xml.h + +namespace Catch { + class XmlReporter : public StreamingReporterBase { + public: + XmlReporter(ReporterConfig const& _config); + + ~XmlReporter() override; + + static std::string getDescription(); + + virtual std::string getStylesheetRef() const; + + void writeSourceInfo(SourceLineInfo const& sourceInfo); + + public: // StreamingReporterBase + + void noMatchingTestCases(std::string const& s) override; + + void testRunStarting(TestRunInfo const& testInfo) override; + + void testGroupStarting(GroupInfo const& groupInfo) override; + + void testCaseStarting(TestCaseInfo const& testInfo) override; + + void sectionStarting(SectionInfo const& sectionInfo) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& assertionStats) override; + + void sectionEnded(SectionStats const& sectionStats) override; + + void testCaseEnded(TestCaseStats const& testCaseStats) override; + + void testGroupEnded(TestGroupStats const& testGroupStats) override; + + void testRunEnded(TestRunStats const& testRunStats) override; + + private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth = 0; + }; + +} // end namespace Catch + +// end catch_reporter_xml.h + +// end catch_external_interfaces.h +#endif + +#endif // ! CATCH_CONFIG_IMPL_ONLY + +#ifdef CATCH_IMPL +// start catch_impl.hpp + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +// Keep these here for external reporters +// start catch_test_case_tracker.h + +#include +#include +#include + +namespace Catch { +namespace TestCaseTracking { + + struct NameAndLocation { + std::string name; + SourceLineInfo location; + + NameAndLocation( std::string const& _name, SourceLineInfo const& _location ); + }; + + struct ITracker; + + using ITrackerPtr = std::shared_ptr; + + struct ITracker { + virtual ~ITracker(); + + // static queries + virtual NameAndLocation const& nameAndLocation() const = 0; + + // dynamic queries + virtual bool isComplete() const = 0; // Successfully completed or failed + virtual bool isSuccessfullyCompleted() const = 0; + virtual bool isOpen() const = 0; // Started but not complete + virtual bool hasChildren() const = 0; + + virtual ITracker& parent() = 0; + + // actions + virtual void close() = 0; // Successfully complete + virtual void fail() = 0; + virtual void markAsNeedingAnotherRun() = 0; + + virtual void addChild( ITrackerPtr const& child ) = 0; + virtual ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) = 0; + virtual void openChild() = 0; + + // Debug/ checking + virtual bool isSectionTracker() const = 0; + virtual bool isGeneratorTracker() const = 0; + }; + + class TrackerContext { + + enum RunState { + NotStarted, + Executing, + CompletedCycle + }; + + ITrackerPtr m_rootTracker; + ITracker* m_currentTracker = nullptr; + RunState m_runState = NotStarted; + + public: + + static TrackerContext& instance(); + + ITracker& startRun(); + void endRun(); + + void startCycle(); + void completeCycle(); + + bool completedCycle() const; + ITracker& currentTracker(); + void setCurrentTracker( ITracker* tracker ); + }; + + class TrackerBase : public ITracker { + protected: + enum CycleState { + NotStarted, + Executing, + ExecutingChildren, + NeedsAnotherRun, + CompletedSuccessfully, + Failed + }; + + using Children = std::vector; + NameAndLocation m_nameAndLocation; + TrackerContext& m_ctx; + ITracker* m_parent; + Children m_children; + CycleState m_runState = NotStarted; + + public: + TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); + + NameAndLocation const& nameAndLocation() const override; + bool isComplete() const override; + bool isSuccessfullyCompleted() const override; + bool isOpen() const override; + bool hasChildren() const override; + + void addChild( ITrackerPtr const& child ) override; + + ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) override; + ITracker& parent() override; + + void openChild() override; + + bool isSectionTracker() const override; + bool isGeneratorTracker() const override; + + void open(); + + void close() override; + void fail() override; + void markAsNeedingAnotherRun() override; + + private: + void moveToParent(); + void moveToThis(); + }; + + class SectionTracker : public TrackerBase { + std::vector m_filters; + public: + SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); + + bool isSectionTracker() const override; + + bool isComplete() const override; + + static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ); + + void tryOpen(); + + void addInitialFilters( std::vector const& filters ); + void addNextFilters( std::vector const& filters ); + }; + +} // namespace TestCaseTracking + +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; + +} // namespace Catch + +// end catch_test_case_tracker.h + +// start catch_leak_detector.h + +namespace Catch { + + struct LeakDetector { + LeakDetector(); + ~LeakDetector(); + }; + +} +// end catch_leak_detector.h +// Cpp files will be included in the single-header file here +// start catch_approx.cpp + +#include +#include + +namespace { + +// Performs equivalent check of std::fabs(lhs - rhs) <= margin +// But without the subtraction to allow for INFINITY in comparison +bool marginComparison(double lhs, double rhs, double margin) { + return (lhs + margin >= rhs) && (rhs + margin >= lhs); +} + +} + +namespace Catch { +namespace Detail { + + Approx::Approx ( double value ) + : m_epsilon( std::numeric_limits::epsilon()*100 ), + m_margin( 0.0 ), + m_scale( 0.0 ), + m_value( value ) + {} + + Approx Approx::custom() { + return Approx( 0 ); + } + + Approx Approx::operator-() const { + auto temp(*this); + temp.m_value = -temp.m_value; + return temp; + } + + std::string Approx::toString() const { + ReusableStringStream rss; + rss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )"; + return rss.str(); + } + + bool Approx::equalityComparisonImpl(const double other) const { + // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value + // Thanks to Richard Harris for his help refining the scaled margin value + return marginComparison(m_value, other, m_margin) || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(m_value))); + } + + void Approx::setMargin(double margin) { + CATCH_ENFORCE(margin >= 0, + "Invalid Approx::margin: " << margin << '.' + << " Approx::Margin has to be non-negative."); + m_margin = margin; + } + + void Approx::setEpsilon(double epsilon) { + CATCH_ENFORCE(epsilon >= 0 && epsilon <= 1.0, + "Invalid Approx::epsilon: " << epsilon << '.' + << " Approx::epsilon has to be in [0, 1]"); + m_epsilon = epsilon; + } + +} // end namespace Detail + +namespace literals { + Detail::Approx operator "" _a(long double val) { + return Detail::Approx(val); + } + Detail::Approx operator "" _a(unsigned long long val) { + return Detail::Approx(val); + } +} // end namespace literals + +std::string StringMaker::convert(Catch::Detail::Approx const& value) { + return value.toString(); +} + +} // end namespace Catch +// end catch_approx.cpp +// start catch_assertionhandler.cpp + +// start catch_debugger.h + +namespace Catch { + bool isDebuggerActive(); +} + +#ifdef CATCH_PLATFORM_MAC + + #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ + +#elif defined(CATCH_PLATFORM_LINUX) + // If we can use inline assembler, do it because this allows us to break + // directly at the location of the failing check instead of breaking inside + // raise() called from it, i.e. one stack frame below. + #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) + #define CATCH_TRAP() asm volatile ("int $3") /* NOLINT */ + #else // Fall back to the generic way. + #include + + #define CATCH_TRAP() raise(SIGTRAP) + #endif +#elif defined(_MSC_VER) + #define CATCH_TRAP() __debugbreak() +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) void __stdcall DebugBreak(); + #define CATCH_TRAP() DebugBreak() +#endif + +#ifdef CATCH_TRAP + #define CATCH_BREAK_INTO_DEBUGGER() []{ if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } }() +#else + #define CATCH_BREAK_INTO_DEBUGGER() []{}() +#endif + +// end catch_debugger.h +// start catch_run_context.h + +// start catch_fatal_condition.h + +// start catch_windows_h_proxy.h + + +#if defined(CATCH_PLATFORM_WINDOWS) + +#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) +# define CATCH_DEFINED_NOMINMAX +# define NOMINMAX +#endif +#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) +# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#ifdef __AFXDLL +#include +#else +#include +#endif + +#ifdef CATCH_DEFINED_NOMINMAX +# undef NOMINMAX +#endif +#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# undef WIN32_LEAN_AND_MEAN +#endif + +#endif // defined(CATCH_PLATFORM_WINDOWS) + +// end catch_windows_h_proxy.h +#if defined( CATCH_CONFIG_WINDOWS_SEH ) + +namespace Catch { + + struct FatalConditionHandler { + + static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo); + FatalConditionHandler(); + static void reset(); + ~FatalConditionHandler(); + + private: + static bool isSet; + static ULONG guaranteeSize; + static PVOID exceptionHandlerHandle; + }; + +} // namespace Catch + +#elif defined ( CATCH_CONFIG_POSIX_SIGNALS ) + +#include + +namespace Catch { + + struct FatalConditionHandler { + + static bool isSet; + static struct sigaction oldSigActions[]; + static stack_t oldSigStack; + static char altStackMem[]; + + static void handleSignal( int sig ); + + FatalConditionHandler(); + ~FatalConditionHandler(); + static void reset(); + }; + +} // namespace Catch + +#else + +namespace Catch { + struct FatalConditionHandler { + void reset(); + }; +} + +#endif + +// end catch_fatal_condition.h +#include + +namespace Catch { + + struct IMutableContext; + + /////////////////////////////////////////////////////////////////////////// + + class RunContext : public IResultCapture, public IRunner { + + public: + RunContext( RunContext const& ) = delete; + RunContext& operator =( RunContext const& ) = delete; + + explicit RunContext( IConfigPtr const& _config, IStreamingReporterPtr&& reporter ); + + ~RunContext() override; + + void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ); + void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ); + + Totals runTest(TestCase const& testCase); + + IConfigPtr config() const; + IStreamingReporter& reporter() const; + + public: // IResultCapture + + // Assertion handlers + void handleExpr + ( AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction ) override; + void handleMessage + ( AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction ) override; + void handleUnexpectedExceptionNotThrown + ( AssertionInfo const& info, + AssertionReaction& reaction ) override; + void handleUnexpectedInflightException + ( AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction ) override; + void handleIncomplete + ( AssertionInfo const& info ) override; + void handleNonExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction ) override; + + bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) override; + + void sectionEnded( SectionEndInfo const& endInfo ) override; + void sectionEndedEarly( SectionEndInfo const& endInfo ) override; + + auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& override; + + void benchmarkStarting( BenchmarkInfo const& info ) override; + void benchmarkEnded( BenchmarkStats const& stats ) override; + + void pushScopedMessage( MessageInfo const& message ) override; + void popScopedMessage( MessageInfo const& message ) override; + + void emplaceUnscopedMessage( MessageBuilder const& builder ) override; + + std::string getCurrentTestName() const override; + + const AssertionResult* getLastResult() const override; + + void exceptionEarlyReported() override; + + void handleFatalErrorCondition( StringRef message ) override; + + bool lastAssertionPassed() override; + + void assertionPassed() override; + + public: + // !TBD We need to do this another way! + bool aborting() const final; + + private: + + void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ); + void invokeActiveTestCase(); + + void resetAssertionInfo(); + bool testForMissingAssertions( Counts& assertions ); + + void assertionEnded( AssertionResult const& result ); + void reportExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated ); + + void populateReaction( AssertionReaction& reaction ); + + private: + + void handleUnfinishedSections(); + + TestRunInfo m_runInfo; + IMutableContext& m_context; + TestCase const* m_activeTestCase = nullptr; + ITracker* m_testCaseTracker = nullptr; + Option m_lastResult; + + IConfigPtr m_config; + Totals m_totals; + IStreamingReporterPtr m_reporter; + std::vector m_messages; + std::vector m_messageScopes; /* Keeps owners of so-called unscoped messages. */ + AssertionInfo m_lastAssertionInfo; + std::vector m_unfinishedSections; + std::vector m_activeSections; + TrackerContext m_trackerContext; + bool m_lastAssertionPassed = false; + bool m_shouldReportUnexpected = true; + bool m_includeSuccessfulResults; + }; + +} // end namespace Catch + +// end catch_run_context.h +namespace Catch { + + namespace { + auto operator <<( std::ostream& os, ITransientExpression const& expr ) -> std::ostream& { + expr.streamReconstructedExpression( os ); + return os; + } + } + + LazyExpression::LazyExpression( bool isNegated ) + : m_isNegated( isNegated ) + {} + + LazyExpression::LazyExpression( LazyExpression const& other ) : m_isNegated( other.m_isNegated ) {} + + LazyExpression::operator bool() const { + return m_transientExpression != nullptr; + } + + auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream& { + if( lazyExpr.m_isNegated ) + os << "!"; + + if( lazyExpr ) { + if( lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression() ) + os << "(" << *lazyExpr.m_transientExpression << ")"; + else + os << *lazyExpr.m_transientExpression; + } + else { + os << "{** error - unchecked empty expression requested **}"; + } + return os; + } + + AssertionHandler::AssertionHandler + ( StringRef const& macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ) + : m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition }, + m_resultCapture( getResultCapture() ) + {} + + void AssertionHandler::handleExpr( ITransientExpression const& expr ) { + m_resultCapture.handleExpr( m_assertionInfo, expr, m_reaction ); + } + void AssertionHandler::handleMessage(ResultWas::OfType resultType, StringRef const& message) { + m_resultCapture.handleMessage( m_assertionInfo, resultType, message, m_reaction ); + } + + auto AssertionHandler::allowThrows() const -> bool { + return getCurrentContext().getConfig()->allowThrows(); + } + + void AssertionHandler::complete() { + setCompleted(); + if( m_reaction.shouldDebugBreak ) { + + // If you find your debugger stopping you here then go one level up on the + // call-stack for the code that caused it (typically a failed assertion) + + // (To go back to the test and change execution, jump over the throw, next) + CATCH_BREAK_INTO_DEBUGGER(); + } + if (m_reaction.shouldThrow) { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + throw Catch::TestFailureException(); +#else + CATCH_ERROR( "Test failure requires aborting test!" ); +#endif + } + } + void AssertionHandler::setCompleted() { + m_completed = true; + } + + void AssertionHandler::handleUnexpectedInflightException() { + m_resultCapture.handleUnexpectedInflightException( m_assertionInfo, Catch::translateActiveException(), m_reaction ); + } + + void AssertionHandler::handleExceptionThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + void AssertionHandler::handleExceptionNotThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + + void AssertionHandler::handleUnexpectedExceptionNotThrown() { + m_resultCapture.handleUnexpectedExceptionNotThrown( m_assertionInfo, m_reaction ); + } + + void AssertionHandler::handleThrowingCallSkipped() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + + // This is the overload that takes a string and infers the Equals matcher from it + // The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef const& matcherString ) { + handleExceptionMatchExpr( handler, Matchers::Equals( str ), matcherString ); + } + +} // namespace Catch +// end catch_assertionhandler.cpp +// start catch_assertionresult.cpp + +namespace Catch { + AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const & _lazyExpression): + lazyExpression(_lazyExpression), + resultType(_resultType) {} + + std::string AssertionResultData::reconstructExpression() const { + + if( reconstructedExpression.empty() ) { + if( lazyExpression ) { + ReusableStringStream rss; + rss << lazyExpression; + reconstructedExpression = rss.str(); + } + } + return reconstructedExpression; + } + + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) + : m_info( info ), + m_resultData( data ) + {} + + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk( m_resultData.resultType ); + } + + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + } + + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } + + bool AssertionResult::hasExpression() const { + return m_info.capturedExpression[0] != 0; + } + + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } + + std::string AssertionResult::getExpression() const { + if( isFalseTest( m_info.resultDisposition ) ) + return "!(" + m_info.capturedExpression + ")"; + else + return m_info.capturedExpression; + } + + std::string AssertionResult::getExpressionInMacro() const { + std::string expr; + if( m_info.macroName[0] == 0 ) + expr = m_info.capturedExpression; + else { + expr.reserve( m_info.macroName.size() + m_info.capturedExpression.size() + 4 ); + expr += m_info.macroName; + expr += "( "; + expr += m_info.capturedExpression; + expr += " )"; + } + return expr; + } + + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } + + std::string AssertionResult::getExpandedExpression() const { + std::string expr = m_resultData.reconstructExpression(); + return expr.empty() + ? getExpression() + : expr; + } + + std::string AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } + + StringRef AssertionResult::getTestMacroName() const { + return m_info.macroName; + } + +} // end namespace Catch +// end catch_assertionresult.cpp +// start catch_benchmark.cpp + +namespace Catch { + + auto BenchmarkLooper::getResolution() -> uint64_t { + return getEstimatedClockResolution() * getCurrentContext().getConfig()->benchmarkResolutionMultiple(); + } + + void BenchmarkLooper::reportStart() { + getResultCapture().benchmarkStarting( { m_name } ); + } + auto BenchmarkLooper::needsMoreIterations() -> bool { + auto elapsed = m_timer.getElapsedNanoseconds(); + + // Exponentially increasing iterations until we're confident in our timer resolution + if( elapsed < m_resolution ) { + m_iterationsToRun *= 10; + return true; + } + + getResultCapture().benchmarkEnded( { { m_name }, m_count, elapsed } ); + return false; + } + +} // end namespace Catch +// end catch_benchmark.cpp +// start catch_capture_matchers.cpp + +namespace Catch { + + using StringMatcher = Matchers::Impl::MatcherBase; + + // This is the general overload that takes a any string matcher + // There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers + // the Equals matcher (so the header does not mention matchers) + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString ) { + std::string exceptionMessage = Catch::translateActiveException(); + MatchExpr expr( exceptionMessage, matcher, matcherString ); + handler.handleExpr( expr ); + } + +} // namespace Catch +// end catch_capture_matchers.cpp +// start catch_commandline.cpp + +// start catch_commandline.h + +// start catch_clara.h + +// Use Catch's value for console width (store Clara's off to the side, if present) +#ifdef CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#undef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#endif +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH-1 + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#pragma clang diagnostic ignored "-Wshadow" +#endif + +// start clara.hpp +// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See https://github.com/philsquared/Clara for more details + +// Clara v1.1.5 + + +#ifndef CATCH_CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_CONFIG_CONSOLE_WIDTH 80 +#endif + +#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +#ifndef CLARA_CONFIG_OPTIONAL_TYPE +#ifdef __has_include +#if __has_include() && __cplusplus >= 201703L +#include +#define CLARA_CONFIG_OPTIONAL_TYPE std::optional +#endif +#endif +#endif + +// ----------- #included from clara_textflow.hpp ----------- + +// TextFlowCpp +// +// A single-header library for wrapping and laying out basic text, by Phil Nash +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// This project is hosted at https://github.com/philsquared/textflowcpp + + +#include +#include +#include +#include + +#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { +namespace clara { +namespace TextFlow { + +inline auto isWhitespace(char c) -> bool { + static std::string chars = " \t\n\r"; + return chars.find(c) != std::string::npos; +} +inline auto isBreakableBefore(char c) -> bool { + static std::string chars = "[({<|"; + return chars.find(c) != std::string::npos; +} +inline auto isBreakableAfter(char c) -> bool { + static std::string chars = "])}>.,:;*+-=&/\\"; + return chars.find(c) != std::string::npos; +} + +class Columns; + +class Column { + std::vector m_strings; + size_t m_width = CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; + size_t m_indent = 0; + size_t m_initialIndent = std::string::npos; + +public: + class iterator { + friend Column; + + Column const& m_column; + size_t m_stringIndex = 0; + size_t m_pos = 0; + + size_t m_len = 0; + size_t m_end = 0; + bool m_suffix = false; + + iterator(Column const& column, size_t stringIndex) + : m_column(column), + m_stringIndex(stringIndex) {} + + auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; } + + auto isBoundary(size_t at) const -> bool { + assert(at > 0); + assert(at <= line().size()); + + return at == line().size() || + (isWhitespace(line()[at]) && !isWhitespace(line()[at - 1])) || + isBreakableBefore(line()[at]) || + isBreakableAfter(line()[at - 1]); + } + + void calcLength() { + assert(m_stringIndex < m_column.m_strings.size()); + + m_suffix = false; + auto width = m_column.m_width - indent(); + m_end = m_pos; + while (m_end < line().size() && line()[m_end] != '\n') + ++m_end; + + if (m_end < m_pos + width) { + m_len = m_end - m_pos; + } else { + size_t len = width; + while (len > 0 && !isBoundary(m_pos + len)) + --len; + while (len > 0 && isWhitespace(line()[m_pos + len - 1])) + --len; + + if (len > 0) { + m_len = len; + } else { + m_suffix = true; + m_len = width - 1; + } + } + } + + auto indent() const -> size_t { + auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; + return initial == std::string::npos ? m_column.m_indent : initial; + } + + auto addIndentAndSuffix(std::string const &plain) const -> std::string { + return std::string(indent(), ' ') + (m_suffix ? plain + "-" : plain); + } + + public: + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using pointer = value_type * ; + using reference = value_type & ; + using iterator_category = std::forward_iterator_tag; + + explicit iterator(Column const& column) : m_column(column) { + assert(m_column.m_width > m_column.m_indent); + assert(m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent); + calcLength(); + if (m_len == 0) + m_stringIndex++; // Empty string + } + + auto operator *() const -> std::string { + assert(m_stringIndex < m_column.m_strings.size()); + assert(m_pos <= m_end); + return addIndentAndSuffix(line().substr(m_pos, m_len)); + } + + auto operator ++() -> iterator& { + m_pos += m_len; + if (m_pos < line().size() && line()[m_pos] == '\n') + m_pos += 1; + else + while (m_pos < line().size() && isWhitespace(line()[m_pos])) + ++m_pos; + + if (m_pos == line().size()) { + m_pos = 0; + ++m_stringIndex; + } + if (m_stringIndex < m_column.m_strings.size()) + calcLength(); + return *this; + } + auto operator ++(int) -> iterator { + iterator prev(*this); + operator++(); + return prev; + } + + auto operator ==(iterator const& other) const -> bool { + return + m_pos == other.m_pos && + m_stringIndex == other.m_stringIndex && + &m_column == &other.m_column; + } + auto operator !=(iterator const& other) const -> bool { + return !operator==(other); + } + }; + using const_iterator = iterator; + + explicit Column(std::string const& text) { m_strings.push_back(text); } + + auto width(size_t newWidth) -> Column& { + assert(newWidth > 0); + m_width = newWidth; + return *this; + } + auto indent(size_t newIndent) -> Column& { + m_indent = newIndent; + return *this; + } + auto initialIndent(size_t newIndent) -> Column& { + m_initialIndent = newIndent; + return *this; + } + + auto width() const -> size_t { return m_width; } + auto begin() const -> iterator { return iterator(*this); } + auto end() const -> iterator { return { *this, m_strings.size() }; } + + inline friend std::ostream& operator << (std::ostream& os, Column const& col) { + bool first = true; + for (auto line : col) { + if (first) + first = false; + else + os << "\n"; + os << line; + } + return os; + } + + auto operator + (Column const& other)->Columns; + + auto toString() const -> std::string { + std::ostringstream oss; + oss << *this; + return oss.str(); + } +}; + +class Spacer : public Column { + +public: + explicit Spacer(size_t spaceWidth) : Column("") { + width(spaceWidth); + } +}; + +class Columns { + std::vector m_columns; + +public: + + class iterator { + friend Columns; + struct EndTag {}; + + std::vector const& m_columns; + std::vector m_iterators; + size_t m_activeIterators; + + iterator(Columns const& columns, EndTag) + : m_columns(columns.m_columns), + m_activeIterators(0) { + m_iterators.reserve(m_columns.size()); + + for (auto const& col : m_columns) + m_iterators.push_back(col.end()); + } + + public: + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using pointer = value_type * ; + using reference = value_type & ; + using iterator_category = std::forward_iterator_tag; + + explicit iterator(Columns const& columns) + : m_columns(columns.m_columns), + m_activeIterators(m_columns.size()) { + m_iterators.reserve(m_columns.size()); + + for (auto const& col : m_columns) + m_iterators.push_back(col.begin()); + } + + auto operator ==(iterator const& other) const -> bool { + return m_iterators == other.m_iterators; + } + auto operator !=(iterator const& other) const -> bool { + return m_iterators != other.m_iterators; + } + auto operator *() const -> std::string { + std::string row, padding; + + for (size_t i = 0; i < m_columns.size(); ++i) { + auto width = m_columns[i].width(); + if (m_iterators[i] != m_columns[i].end()) { + std::string col = *m_iterators[i]; + row += padding + col; + if (col.size() < width) + padding = std::string(width - col.size(), ' '); + else + padding = ""; + } else { + padding += std::string(width, ' '); + } + } + return row; + } + auto operator ++() -> iterator& { + for (size_t i = 0; i < m_columns.size(); ++i) { + if (m_iterators[i] != m_columns[i].end()) + ++m_iterators[i]; + } + return *this; + } + auto operator ++(int) -> iterator { + iterator prev(*this); + operator++(); + return prev; + } + }; + using const_iterator = iterator; + + auto begin() const -> iterator { return iterator(*this); } + auto end() const -> iterator { return { *this, iterator::EndTag() }; } + + auto operator += (Column const& col) -> Columns& { + m_columns.push_back(col); + return *this; + } + auto operator + (Column const& col) -> Columns { + Columns combined = *this; + combined += col; + return combined; + } + + inline friend std::ostream& operator << (std::ostream& os, Columns const& cols) { + + bool first = true; + for (auto line : cols) { + if (first) + first = false; + else + os << "\n"; + os << line; + } + return os; + } + + auto toString() const -> std::string { + std::ostringstream oss; + oss << *this; + return oss.str(); + } +}; + +inline auto Column::operator + (Column const& other) -> Columns { + Columns cols; + cols += *this; + cols += other; + return cols; +} +} + +} +} + +// ----------- end of #include from clara_textflow.hpp ----------- +// ........... back in clara.hpp + +#include +#include +#include +#include +#include + +#if !defined(CATCH_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ) +#define CATCH_PLATFORM_WINDOWS +#endif + +namespace Catch { namespace clara { +namespace detail { + + // Traits for extracting arg and return type of lambdas (for single argument lambdas) + template + struct UnaryLambdaTraits : UnaryLambdaTraits {}; + + template + struct UnaryLambdaTraits { + static const bool isValid = false; + }; + + template + struct UnaryLambdaTraits { + static const bool isValid = true; + using ArgType = typename std::remove_const::type>::type; + using ReturnType = ReturnT; + }; + + class TokenStream; + + // Transport for raw args (copied from main args, or supplied via init list for testing) + class Args { + friend TokenStream; + std::string m_exeName; + std::vector m_args; + + public: + Args( int argc, char const* const* argv ) + : m_exeName(argv[0]), + m_args(argv + 1, argv + argc) {} + + Args( std::initializer_list args ) + : m_exeName( *args.begin() ), + m_args( args.begin()+1, args.end() ) + {} + + auto exeName() const -> std::string { + return m_exeName; + } + }; + + // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string + // may encode an option + its argument if the : or = form is used + enum class TokenType { + Option, Argument + }; + struct Token { + TokenType type; + std::string token; + }; + + inline auto isOptPrefix( char c ) -> bool { + return c == '-' +#ifdef CATCH_PLATFORM_WINDOWS + || c == '/' +#endif + ; + } + + // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled + class TokenStream { + using Iterator = std::vector::const_iterator; + Iterator it; + Iterator itEnd; + std::vector m_tokenBuffer; + + void loadBuffer() { + m_tokenBuffer.resize( 0 ); + + // Skip any empty strings + while( it != itEnd && it->empty() ) + ++it; + + if( it != itEnd ) { + auto const &next = *it; + if( isOptPrefix( next[0] ) ) { + auto delimiterPos = next.find_first_of( " :=" ); + if( delimiterPos != std::string::npos ) { + m_tokenBuffer.push_back( { TokenType::Option, next.substr( 0, delimiterPos ) } ); + m_tokenBuffer.push_back( { TokenType::Argument, next.substr( delimiterPos + 1 ) } ); + } else { + if( next[1] != '-' && next.size() > 2 ) { + std::string opt = "- "; + for( size_t i = 1; i < next.size(); ++i ) { + opt[1] = next[i]; + m_tokenBuffer.push_back( { TokenType::Option, opt } ); + } + } else { + m_tokenBuffer.push_back( { TokenType::Option, next } ); + } + } + } else { + m_tokenBuffer.push_back( { TokenType::Argument, next } ); + } + } + } + + public: + explicit TokenStream( Args const &args ) : TokenStream( args.m_args.begin(), args.m_args.end() ) {} + + TokenStream( Iterator it, Iterator itEnd ) : it( it ), itEnd( itEnd ) { + loadBuffer(); + } + + explicit operator bool() const { + return !m_tokenBuffer.empty() || it != itEnd; + } + + auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); } + + auto operator*() const -> Token { + assert( !m_tokenBuffer.empty() ); + return m_tokenBuffer.front(); + } + + auto operator->() const -> Token const * { + assert( !m_tokenBuffer.empty() ); + return &m_tokenBuffer.front(); + } + + auto operator++() -> TokenStream & { + if( m_tokenBuffer.size() >= 2 ) { + m_tokenBuffer.erase( m_tokenBuffer.begin() ); + } else { + if( it != itEnd ) + ++it; + loadBuffer(); + } + return *this; + } + }; + + class ResultBase { + public: + enum Type { + Ok, LogicError, RuntimeError + }; + + protected: + ResultBase( Type type ) : m_type( type ) {} + virtual ~ResultBase() = default; + + virtual void enforceOk() const = 0; + + Type m_type; + }; + + template + class ResultValueBase : public ResultBase { + public: + auto value() const -> T const & { + enforceOk(); + return m_value; + } + + protected: + ResultValueBase( Type type ) : ResultBase( type ) {} + + ResultValueBase( ResultValueBase const &other ) : ResultBase( other ) { + if( m_type == ResultBase::Ok ) + new( &m_value ) T( other.m_value ); + } + + ResultValueBase( Type, T const &value ) : ResultBase( Ok ) { + new( &m_value ) T( value ); + } + + auto operator=( ResultValueBase const &other ) -> ResultValueBase & { + if( m_type == ResultBase::Ok ) + m_value.~T(); + ResultBase::operator=(other); + if( m_type == ResultBase::Ok ) + new( &m_value ) T( other.m_value ); + return *this; + } + + ~ResultValueBase() override { + if( m_type == Ok ) + m_value.~T(); + } + + union { + T m_value; + }; + }; + + template<> + class ResultValueBase : public ResultBase { + protected: + using ResultBase::ResultBase; + }; + + template + class BasicResult : public ResultValueBase { + public: + template + explicit BasicResult( BasicResult const &other ) + : ResultValueBase( other.type() ), + m_errorMessage( other.errorMessage() ) + { + assert( type() != ResultBase::Ok ); + } + + template + static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; } + static auto ok() -> BasicResult { return { ResultBase::Ok }; } + static auto logicError( std::string const &message ) -> BasicResult { return { ResultBase::LogicError, message }; } + static auto runtimeError( std::string const &message ) -> BasicResult { return { ResultBase::RuntimeError, message }; } + + explicit operator bool() const { return m_type == ResultBase::Ok; } + auto type() const -> ResultBase::Type { return m_type; } + auto errorMessage() const -> std::string { return m_errorMessage; } + + protected: + void enforceOk() const override { + + // Errors shouldn't reach this point, but if they do + // the actual error message will be in m_errorMessage + assert( m_type != ResultBase::LogicError ); + assert( m_type != ResultBase::RuntimeError ); + if( m_type != ResultBase::Ok ) + std::abort(); + } + + std::string m_errorMessage; // Only populated if resultType is an error + + BasicResult( ResultBase::Type type, std::string const &message ) + : ResultValueBase(type), + m_errorMessage(message) + { + assert( m_type != ResultBase::Ok ); + } + + using ResultValueBase::ResultValueBase; + using ResultBase::m_type; + }; + + enum class ParseResultType { + Matched, NoMatch, ShortCircuitAll, ShortCircuitSame + }; + + class ParseState { + public: + + ParseState( ParseResultType type, TokenStream const &remainingTokens ) + : m_type(type), + m_remainingTokens( remainingTokens ) + {} + + auto type() const -> ParseResultType { return m_type; } + auto remainingTokens() const -> TokenStream { return m_remainingTokens; } + + private: + ParseResultType m_type; + TokenStream m_remainingTokens; + }; + + using Result = BasicResult; + using ParserResult = BasicResult; + using InternalParseResult = BasicResult; + + struct HelpColumns { + std::string left; + std::string right; + }; + + template + inline auto convertInto( std::string const &source, T& target ) -> ParserResult { + std::stringstream ss; + ss << source; + ss >> target; + if( ss.fail() ) + return ParserResult::runtimeError( "Unable to convert '" + source + "' to destination type" ); + else + return ParserResult::ok( ParseResultType::Matched ); + } + inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult { + target = source; + return ParserResult::ok( ParseResultType::Matched ); + } + inline auto convertInto( std::string const &source, bool &target ) -> ParserResult { + std::string srcLC = source; + std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( char c ) { return static_cast( std::tolower(c) ); } ); + if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") + target = true; + else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") + target = false; + else + return ParserResult::runtimeError( "Expected a boolean value but did not recognise: '" + source + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + } +#ifdef CLARA_CONFIG_OPTIONAL_TYPE + template + inline auto convertInto( std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE& target ) -> ParserResult { + T temp; + auto result = convertInto( source, temp ); + if( result ) + target = std::move(temp); + return result; + } +#endif // CLARA_CONFIG_OPTIONAL_TYPE + + struct NonCopyable { + NonCopyable() = default; + NonCopyable( NonCopyable const & ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable &operator=( NonCopyable const & ) = delete; + NonCopyable &operator=( NonCopyable && ) = delete; + }; + + struct BoundRef : NonCopyable { + virtual ~BoundRef() = default; + virtual auto isContainer() const -> bool { return false; } + virtual auto isFlag() const -> bool { return false; } + }; + struct BoundValueRefBase : BoundRef { + virtual auto setValue( std::string const &arg ) -> ParserResult = 0; + }; + struct BoundFlagRefBase : BoundRef { + virtual auto setFlag( bool flag ) -> ParserResult = 0; + virtual auto isFlag() const -> bool { return true; } + }; + + template + struct BoundValueRef : BoundValueRefBase { + T &m_ref; + + explicit BoundValueRef( T &ref ) : m_ref( ref ) {} + + auto setValue( std::string const &arg ) -> ParserResult override { + return convertInto( arg, m_ref ); + } + }; + + template + struct BoundValueRef> : BoundValueRefBase { + std::vector &m_ref; + + explicit BoundValueRef( std::vector &ref ) : m_ref( ref ) {} + + auto isContainer() const -> bool override { return true; } + + auto setValue( std::string const &arg ) -> ParserResult override { + T temp; + auto result = convertInto( arg, temp ); + if( result ) + m_ref.push_back( temp ); + return result; + } + }; + + struct BoundFlagRef : BoundFlagRefBase { + bool &m_ref; + + explicit BoundFlagRef( bool &ref ) : m_ref( ref ) {} + + auto setFlag( bool flag ) -> ParserResult override { + m_ref = flag; + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + template + struct LambdaInvoker { + static_assert( std::is_same::value, "Lambda must return void or clara::ParserResult" ); + + template + static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { + return lambda( arg ); + } + }; + + template<> + struct LambdaInvoker { + template + static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { + lambda( arg ); + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + template + inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult { + ArgType temp{}; + auto result = convertInto( arg, temp ); + return !result + ? result + : LambdaInvoker::ReturnType>::invoke( lambda, temp ); + } + + template + struct BoundLambda : BoundValueRefBase { + L m_lambda; + + static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); + explicit BoundLambda( L const &lambda ) : m_lambda( lambda ) {} + + auto setValue( std::string const &arg ) -> ParserResult override { + return invokeLambda::ArgType>( m_lambda, arg ); + } + }; + + template + struct BoundFlagLambda : BoundFlagRefBase { + L m_lambda; + + static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); + static_assert( std::is_same::ArgType, bool>::value, "flags must be boolean" ); + + explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {} + + auto setFlag( bool flag ) -> ParserResult override { + return LambdaInvoker::ReturnType>::invoke( m_lambda, flag ); + } + }; + + enum class Optionality { Optional, Required }; + + struct Parser; + + class ParserBase { + public: + virtual ~ParserBase() = default; + virtual auto validate() const -> Result { return Result::ok(); } + virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult = 0; + virtual auto cardinality() const -> size_t { return 1; } + + auto parse( Args const &args ) const -> InternalParseResult { + return parse( args.exeName(), TokenStream( args ) ); + } + }; + + template + class ComposableParserImpl : public ParserBase { + public: + template + auto operator|( T const &other ) const -> Parser; + + template + auto operator+( T const &other ) const -> Parser; + }; + + // Common code and state for Args and Opts + template + class ParserRefImpl : public ComposableParserImpl { + protected: + Optionality m_optionality = Optionality::Optional; + std::shared_ptr m_ref; + std::string m_hint; + std::string m_description; + + explicit ParserRefImpl( std::shared_ptr const &ref ) : m_ref( ref ) {} + + public: + template + ParserRefImpl( T &ref, std::string const &hint ) + : m_ref( std::make_shared>( ref ) ), + m_hint( hint ) + {} + + template + ParserRefImpl( LambdaT const &ref, std::string const &hint ) + : m_ref( std::make_shared>( ref ) ), + m_hint(hint) + {} + + auto operator()( std::string const &description ) -> DerivedT & { + m_description = description; + return static_cast( *this ); + } + + auto optional() -> DerivedT & { + m_optionality = Optionality::Optional; + return static_cast( *this ); + }; + + auto required() -> DerivedT & { + m_optionality = Optionality::Required; + return static_cast( *this ); + }; + + auto isOptional() const -> bool { + return m_optionality == Optionality::Optional; + } + + auto cardinality() const -> size_t override { + if( m_ref->isContainer() ) + return 0; + else + return 1; + } + + auto hint() const -> std::string { return m_hint; } + }; + + class ExeName : public ComposableParserImpl { + std::shared_ptr m_name; + std::shared_ptr m_ref; + + template + static auto makeRef(LambdaT const &lambda) -> std::shared_ptr { + return std::make_shared>( lambda) ; + } + + public: + ExeName() : m_name( std::make_shared( "" ) ) {} + + explicit ExeName( std::string &ref ) : ExeName() { + m_ref = std::make_shared>( ref ); + } + + template + explicit ExeName( LambdaT const& lambda ) : ExeName() { + m_ref = std::make_shared>( lambda ); + } + + // The exe name is not parsed out of the normal tokens, but is handled specially + auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); + } + + auto name() const -> std::string { return *m_name; } + auto set( std::string const& newName ) -> ParserResult { + + auto lastSlash = newName.find_last_of( "\\/" ); + auto filename = ( lastSlash == std::string::npos ) + ? newName + : newName.substr( lastSlash+1 ); + + *m_name = filename; + if( m_ref ) + return m_ref->setValue( filename ); + else + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + class Arg : public ParserRefImpl { + public: + using ParserRefImpl::ParserRefImpl; + + auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override { + auto validationResult = validate(); + if( !validationResult ) + return InternalParseResult( validationResult ); + + auto remainingTokens = tokens; + auto const &token = *remainingTokens; + if( token.type != TokenType::Argument ) + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); + + assert( !m_ref->isFlag() ); + auto valueRef = static_cast( m_ref.get() ); + + auto result = valueRef->setValue( remainingTokens->token ); + if( !result ) + return InternalParseResult( result ); + else + return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); + } + }; + + inline auto normaliseOpt( std::string const &optName ) -> std::string { +#ifdef CATCH_PLATFORM_WINDOWS + if( optName[0] == '/' ) + return "-" + optName.substr( 1 ); + else +#endif + return optName; + } + + class Opt : public ParserRefImpl { + protected: + std::vector m_optNames; + + public: + template + explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared>( ref ) ) {} + + explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared( ref ) ) {} + + template + Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} + + template + Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} + + auto operator[]( std::string const &optName ) -> Opt & { + m_optNames.push_back( optName ); + return *this; + } + + auto getHelpColumns() const -> std::vector { + std::ostringstream oss; + bool first = true; + for( auto const &opt : m_optNames ) { + if (first) + first = false; + else + oss << ", "; + oss << opt; + } + if( !m_hint.empty() ) + oss << " <" << m_hint << ">"; + return { { oss.str(), m_description } }; + } + + auto isMatch( std::string const &optToken ) const -> bool { + auto normalisedToken = normaliseOpt( optToken ); + for( auto const &name : m_optNames ) { + if( normaliseOpt( name ) == normalisedToken ) + return true; + } + return false; + } + + using ParserBase::parse; + + auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + auto validationResult = validate(); + if( !validationResult ) + return InternalParseResult( validationResult ); + + auto remainingTokens = tokens; + if( remainingTokens && remainingTokens->type == TokenType::Option ) { + auto const &token = *remainingTokens; + if( isMatch(token.token ) ) { + if( m_ref->isFlag() ) { + auto flagRef = static_cast( m_ref.get() ); + auto result = flagRef->setFlag( true ); + if( !result ) + return InternalParseResult( result ); + if( result.value() == ParseResultType::ShortCircuitAll ) + return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); + } else { + auto valueRef = static_cast( m_ref.get() ); + ++remainingTokens; + if( !remainingTokens ) + return InternalParseResult::runtimeError( "Expected argument following " + token.token ); + auto const &argToken = *remainingTokens; + if( argToken.type != TokenType::Argument ) + return InternalParseResult::runtimeError( "Expected argument following " + token.token ); + auto result = valueRef->setValue( argToken.token ); + if( !result ) + return InternalParseResult( result ); + if( result.value() == ParseResultType::ShortCircuitAll ) + return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); + } + return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); + } + } + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); + } + + auto validate() const -> Result override { + if( m_optNames.empty() ) + return Result::logicError( "No options supplied to Opt" ); + for( auto const &name : m_optNames ) { + if( name.empty() ) + return Result::logicError( "Option name cannot be empty" ); +#ifdef CATCH_PLATFORM_WINDOWS + if( name[0] != '-' && name[0] != '/' ) + return Result::logicError( "Option name must begin with '-' or '/'" ); +#else + if( name[0] != '-' ) + return Result::logicError( "Option name must begin with '-'" ); +#endif + } + return ParserRefImpl::validate(); + } + }; + + struct Help : Opt { + Help( bool &showHelpFlag ) + : Opt([&]( bool flag ) { + showHelpFlag = flag; + return ParserResult::ok( ParseResultType::ShortCircuitAll ); + }) + { + static_cast( *this ) + ("display usage information") + ["-?"]["-h"]["--help"] + .optional(); + } + }; + + struct Parser : ParserBase { + + mutable ExeName m_exeName; + std::vector m_options; + std::vector m_args; + + auto operator|=( ExeName const &exeName ) -> Parser & { + m_exeName = exeName; + return *this; + } + + auto operator|=( Arg const &arg ) -> Parser & { + m_args.push_back(arg); + return *this; + } + + auto operator|=( Opt const &opt ) -> Parser & { + m_options.push_back(opt); + return *this; + } + + auto operator|=( Parser const &other ) -> Parser & { + m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); + m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); + return *this; + } + + template + auto operator|( T const &other ) const -> Parser { + return Parser( *this ) |= other; + } + + // Forward deprecated interface with '+' instead of '|' + template + auto operator+=( T const &other ) -> Parser & { return operator|=( other ); } + template + auto operator+( T const &other ) const -> Parser { return operator|( other ); } + + auto getHelpColumns() const -> std::vector { + std::vector cols; + for (auto const &o : m_options) { + auto childCols = o.getHelpColumns(); + cols.insert( cols.end(), childCols.begin(), childCols.end() ); + } + return cols; + } + + void writeToStream( std::ostream &os ) const { + if (!m_exeName.name().empty()) { + os << "usage:\n" << " " << m_exeName.name() << " "; + bool required = true, first = true; + for( auto const &arg : m_args ) { + if (first) + first = false; + else + os << " "; + if( arg.isOptional() && required ) { + os << "["; + required = false; + } + os << "<" << arg.hint() << ">"; + if( arg.cardinality() == 0 ) + os << " ... "; + } + if( !required ) + os << "]"; + if( !m_options.empty() ) + os << " options"; + os << "\n\nwhere options are:" << std::endl; + } + + auto rows = getHelpColumns(); + size_t consoleWidth = CATCH_CLARA_CONFIG_CONSOLE_WIDTH; + size_t optWidth = 0; + for( auto const &cols : rows ) + optWidth = (std::max)(optWidth, cols.left.size() + 2); + + optWidth = (std::min)(optWidth, consoleWidth/2); + + for( auto const &cols : rows ) { + auto row = + TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) + + TextFlow::Spacer(4) + + TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth ); + os << row << std::endl; + } + } + + friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& { + parser.writeToStream( os ); + return os; + } + + auto validate() const -> Result override { + for( auto const &opt : m_options ) { + auto result = opt.validate(); + if( !result ) + return result; + } + for( auto const &arg : m_args ) { + auto result = arg.validate(); + if( !result ) + return result; + } + return Result::ok(); + } + + using ParserBase::parse; + + auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { + + struct ParserInfo { + ParserBase const* parser = nullptr; + size_t count = 0; + }; + const size_t totalParsers = m_options.size() + m_args.size(); + assert( totalParsers < 512 ); + // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do + ParserInfo parseInfos[512]; + + { + size_t i = 0; + for (auto const &opt : m_options) parseInfos[i++].parser = &opt; + for (auto const &arg : m_args) parseInfos[i++].parser = &arg; + } + + m_exeName.set( exeName ); + + auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); + while( result.value().remainingTokens() ) { + bool tokenParsed = false; + + for( size_t i = 0; i < totalParsers; ++i ) { + auto& parseInfo = parseInfos[i]; + if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) { + result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); + if (!result) + return result; + if (result.value().type() != ParseResultType::NoMatch) { + tokenParsed = true; + ++parseInfo.count; + break; + } + } + } + + if( result.value().type() == ParseResultType::ShortCircuitAll ) + return result; + if( !tokenParsed ) + return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token ); + } + // !TBD Check missing required options + return result; + } + }; + + template + template + auto ComposableParserImpl::operator|( T const &other ) const -> Parser { + return Parser() | static_cast( *this ) | other; + } +} // namespace detail + +// A Combined parser +using detail::Parser; + +// A parser for options +using detail::Opt; + +// A parser for arguments +using detail::Arg; + +// Wrapper for argc, argv from main() +using detail::Args; + +// Specifies the name of the executable +using detail::ExeName; + +// Convenience wrapper for option parser that specifies the help option +using detail::Help; + +// enum of result types from a parse +using detail::ParseResultType; + +// Result type for parser operation +using detail::ParserResult; + +}} // namespace Catch::clara + +// end clara.hpp +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// Restore Clara's value for console width, if present +#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +// end catch_clara.h +namespace Catch { + + clara::Parser makeCommandLineParser( ConfigData& config ); + +} // end namespace Catch + +// end catch_commandline.h +#include +#include + +namespace Catch { + + clara::Parser makeCommandLineParser( ConfigData& config ) { + + using namespace clara; + + auto const setWarning = [&]( std::string const& warning ) { + auto warningSet = [&]() { + if( warning == "NoAssertions" ) + return WarnAbout::NoAssertions; + + if ( warning == "NoTests" ) + return WarnAbout::NoTests; + + return WarnAbout::Nothing; + }(); + + if (warningSet == WarnAbout::Nothing) + return ParserResult::runtimeError( "Unrecognised warning: '" + warning + "'" ); + config.warnings = static_cast( config.warnings | warningSet ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const loadTestNamesFromFile = [&]( std::string const& filename ) { + std::ifstream f( filename.c_str() ); + if( !f.is_open() ) + return ParserResult::runtimeError( "Unable to load input file: '" + filename + "'" ); + + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, '#' ) ) { + if( !startsWith( line, '"' ) ) + line = '"' + line + '"'; + config.testsOrTags.push_back( line + ',' ); + } + } + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setTestOrder = [&]( std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = RunTests::InDeclarationOrder; + else if( startsWith( "lexical", order ) ) + config.runOrder = RunTests::InLexicographicalOrder; + else if( startsWith( "random", order ) ) + config.runOrder = RunTests::InRandomOrder; + else + return clara::ParserResult::runtimeError( "Unrecognised ordering: '" + order + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setRngSeed = [&]( std::string const& seed ) { + if( seed != "time" ) + return clara::detail::convertInto( seed, config.rngSeed ); + config.rngSeed = static_cast( std::time(nullptr) ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setColourUsage = [&]( std::string const& useColour ) { + auto mode = toLower( useColour ); + + if( mode == "yes" ) + config.useColour = UseColour::Yes; + else if( mode == "no" ) + config.useColour = UseColour::No; + else if( mode == "auto" ) + config.useColour = UseColour::Auto; + else + return ParserResult::runtimeError( "colour mode must be one of: auto, yes or no. '" + useColour + "' not recognised" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setWaitForKeypress = [&]( std::string const& keypress ) { + auto keypressLc = toLower( keypress ); + if( keypressLc == "start" ) + config.waitForKeypress = WaitForKeypress::BeforeStart; + else if( keypressLc == "exit" ) + config.waitForKeypress = WaitForKeypress::BeforeExit; + else if( keypressLc == "both" ) + config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; + else + return ParserResult::runtimeError( "keypress argument must be one of: start, exit or both. '" + keypress + "' not recognised" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setVerbosity = [&]( std::string const& verbosity ) { + auto lcVerbosity = toLower( verbosity ); + if( lcVerbosity == "quiet" ) + config.verbosity = Verbosity::Quiet; + else if( lcVerbosity == "normal" ) + config.verbosity = Verbosity::Normal; + else if( lcVerbosity == "high" ) + config.verbosity = Verbosity::High; + else + return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setReporter = [&]( std::string const& reporter ) { + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + + auto lcReporter = toLower( reporter ); + auto result = factories.find( lcReporter ); + + if( factories.end() != result ) + config.reporterName = lcReporter; + else + return ParserResult::runtimeError( "Unrecognized reporter, '" + reporter + "'. Check available with --list-reporters" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + + auto cli + = ExeName( config.processName ) + | Help( config.showHelp ) + | Opt( config.listTests ) + ["-l"]["--list-tests"] + ( "list all/matching test cases" ) + | Opt( config.listTags ) + ["-t"]["--list-tags"] + ( "list all/matching tags" ) + | Opt( config.showSuccessfulTests ) + ["-s"]["--success"] + ( "include successful tests in output" ) + | Opt( config.shouldDebugBreak ) + ["-b"]["--break"] + ( "break into debugger on failure" ) + | Opt( config.noThrow ) + ["-e"]["--nothrow"] + ( "skip exception tests" ) + | Opt( config.showInvisibles ) + ["-i"]["--invisibles"] + ( "show invisibles (tabs, newlines)" ) + | Opt( config.outputFilename, "filename" ) + ["-o"]["--out"] + ( "output filename" ) + | Opt( setReporter, "name" ) + ["-r"]["--reporter"] + ( "reporter to use (defaults to console)" ) + | Opt( config.name, "name" ) + ["-n"]["--name"] + ( "suite name" ) + | Opt( [&]( bool ){ config.abortAfter = 1; } ) + ["-a"]["--abort"] + ( "abort at first failure" ) + | Opt( [&]( int x ){ config.abortAfter = x; }, "no. failures" ) + ["-x"]["--abortx"] + ( "abort after x failures" ) + | Opt( setWarning, "warning name" ) + ["-w"]["--warn"] + ( "enable warnings" ) + | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" ) + ["-d"]["--durations"] + ( "show test durations" ) + | Opt( loadTestNamesFromFile, "filename" ) + ["-f"]["--input-file"] + ( "load test names to run from a file" ) + | Opt( config.filenamesAsTags ) + ["-#"]["--filenames-as-tags"] + ( "adds a tag for the filename" ) + | Opt( config.sectionsToRun, "section name" ) + ["-c"]["--section"] + ( "specify section to run" ) + | Opt( setVerbosity, "quiet|normal|high" ) + ["-v"]["--verbosity"] + ( "set output verbosity" ) + | Opt( config.listTestNamesOnly ) + ["--list-test-names-only"] + ( "list all/matching test cases names only" ) + | Opt( config.listReporters ) + ["--list-reporters"] + ( "list all reporters" ) + | Opt( setTestOrder, "decl|lex|rand" ) + ["--order"] + ( "test case order (defaults to decl)" ) + | Opt( setRngSeed, "'time'|number" ) + ["--rng-seed"] + ( "set a specific seed for random numbers" ) + | Opt( setColourUsage, "yes|no" ) + ["--use-colour"] + ( "should output be colourised" ) + | Opt( config.libIdentify ) + ["--libidentify"] + ( "report name and version according to libidentify standard" ) + | Opt( setWaitForKeypress, "start|exit|both" ) + ["--wait-for-keypress"] + ( "waits for a keypress before exiting" ) + | Opt( config.benchmarkResolutionMultiple, "multiplier" ) + ["--benchmark-resolution-multiple"] + ( "multiple of clock resolution to run benchmarks" ) + + | Arg( config.testsOrTags, "test name|pattern|tags" ) + ( "which test or tests to use" ); + + return cli; + } + +} // end namespace Catch +// end catch_commandline.cpp +// start catch_common.cpp + +#include +#include + +namespace Catch { + + bool SourceLineInfo::empty() const noexcept { + return file[0] == '\0'; + } + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const noexcept { + return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); + } + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const noexcept { + // We can assume that the same file will usually have the same pointer. + // Thus, if the pointers are the same, there is no point in calling the strcmp + return line < other.line || ( line == other.line && file != other.file && (std::strcmp(file, other.file) < 0)); + } + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << '(' << info.line << ')'; +#else + os << info.file << ':' << info.line; +#endif + return os; + } + + std::string StreamEndStop::operator+() const { + return std::string(); + } + + NonCopyable::NonCopyable() = default; + NonCopyable::~NonCopyable() = default; + +} +// end catch_common.cpp +// start catch_config.cpp + +namespace Catch { + + Config::Config( ConfigData const& data ) + : m_data( data ), + m_stream( openStream() ) + { + TestSpecParser parser(ITagAliasRegistry::get()); + if (data.testsOrTags.empty()) { + parser.parse("~[.]"); // All not hidden tests + } + else { + m_hasTestFilters = true; + for( auto const& testOrTags : data.testsOrTags ) + parser.parse( testOrTags ); + } + m_testSpec = parser.testSpec(); + } + + std::string const& Config::getFilename() const { + return m_data.outputFilename ; + } + + bool Config::listTests() const { return m_data.listTests; } + bool Config::listTestNamesOnly() const { return m_data.listTestNamesOnly; } + bool Config::listTags() const { return m_data.listTags; } + bool Config::listReporters() const { return m_data.listReporters; } + + std::string Config::getProcessName() const { return m_data.processName; } + std::string const& Config::getReporterName() const { return m_data.reporterName; } + + std::vector const& Config::getTestsOrTags() const { return m_data.testsOrTags; } + std::vector const& Config::getSectionsToRun() const { return m_data.sectionsToRun; } + + TestSpec const& Config::testSpec() const { return m_testSpec; } + bool Config::hasTestFilters() const { return m_hasTestFilters; } + + bool Config::showHelp() const { return m_data.showHelp; } + + // IConfig interface + bool Config::allowThrows() const { return !m_data.noThrow; } + std::ostream& Config::stream() const { return m_stream->stream(); } + std::string Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } + bool Config::includeSuccessfulResults() const { return m_data.showSuccessfulTests; } + bool Config::warnAboutMissingAssertions() const { return !!(m_data.warnings & WarnAbout::NoAssertions); } + bool Config::warnAboutNoTests() const { return !!(m_data.warnings & WarnAbout::NoTests); } + ShowDurations::OrNot Config::showDurations() const { return m_data.showDurations; } + RunTests::InWhatOrder Config::runOrder() const { return m_data.runOrder; } + unsigned int Config::rngSeed() const { return m_data.rngSeed; } + int Config::benchmarkResolutionMultiple() const { return m_data.benchmarkResolutionMultiple; } + UseColour::YesOrNo Config::useColour() const { return m_data.useColour; } + bool Config::shouldDebugBreak() const { return m_data.shouldDebugBreak; } + int Config::abortAfter() const { return m_data.abortAfter; } + bool Config::showInvisibles() const { return m_data.showInvisibles; } + Verbosity Config::verbosity() const { return m_data.verbosity; } + + IStream const* Config::openStream() { + return Catch::makeStream(m_data.outputFilename); + } + +} // end namespace Catch +// end catch_config.cpp +// start catch_console_colour.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +// start catch_errno_guard.h + +namespace Catch { + + class ErrnoGuard { + public: + ErrnoGuard(); + ~ErrnoGuard(); + private: + int m_oldErrno; + }; + +} + +// end catch_errno_guard.h +#include + +namespace Catch { + namespace { + + struct IColourImpl { + virtual ~IColourImpl() = default; + virtual void use( Colour::Code _colourCode ) = 0; + }; + + struct NoColourImpl : IColourImpl { + void use( Colour::Code ) {} + + static IColourImpl* instance() { + static NoColourImpl s_instance; + return &s_instance; + } + }; + + } // anon namespace +} // namespace Catch + +#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) +# ifdef CATCH_PLATFORM_WINDOWS +# define CATCH_CONFIG_COLOUR_WINDOWS +# else +# define CATCH_CONFIG_COLOUR_ANSI +# endif +#endif + +#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// + +namespace Catch { +namespace { + + class Win32ColourImpl : public IColourImpl { + public: + Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) + { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); + originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); + originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); + } + + virtual void use( Colour::Code _colourCode ) override { + switch( _colourCode ) { + case Colour::None: return setTextAttribute( originalForegroundAttributes ); + case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::Red: return setTextAttribute( FOREGROUND_RED ); + case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); + case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); + case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); + case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); + case Colour::Grey: return setTextAttribute( 0 ); + + case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); + case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); + case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); + case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::BrightYellow: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN ); + + case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + + default: + CATCH_ERROR( "Unknown colour requested" ); + } + } + + private: + void setTextAttribute( WORD _textAttribute ) { + SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); + } + HANDLE stdoutHandle; + WORD originalForegroundAttributes; + WORD originalBackgroundAttributes; + }; + + IColourImpl* platformColourInstance() { + static Win32ColourImpl s_instance; + + IConfigPtr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = UseColour::Yes; + return colourMode == UseColour::Yes + ? &s_instance + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// + +#include + +namespace Catch { +namespace { + + // use POSIX/ ANSI console terminal codes + // Thanks to Adam Strzelecki for original contribution + // (http://github.com/nanoant) + // https://github.com/philsquared/Catch/pull/131 + class PosixColourImpl : public IColourImpl { + public: + virtual void use( Colour::Code _colourCode ) override { + switch( _colourCode ) { + case Colour::None: + case Colour::White: return setColour( "[0m" ); + case Colour::Red: return setColour( "[0;31m" ); + case Colour::Green: return setColour( "[0;32m" ); + case Colour::Blue: return setColour( "[0;34m" ); + case Colour::Cyan: return setColour( "[0;36m" ); + case Colour::Yellow: return setColour( "[0;33m" ); + case Colour::Grey: return setColour( "[1;30m" ); + + case Colour::LightGrey: return setColour( "[0;37m" ); + case Colour::BrightRed: return setColour( "[1;31m" ); + case Colour::BrightGreen: return setColour( "[1;32m" ); + case Colour::BrightWhite: return setColour( "[1;37m" ); + case Colour::BrightYellow: return setColour( "[1;33m" ); + + case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + default: CATCH_INTERNAL_ERROR( "Unknown colour requested" ); + } + } + static IColourImpl* instance() { + static PosixColourImpl s_instance; + return &s_instance; + } + + private: + void setColour( const char* _escapeCode ) { + getCurrentContext().getConfig()->stream() + << '\033' << _escapeCode; + } + }; + + bool useColourOnPlatform() { + return +#ifdef CATCH_PLATFORM_MAC + !isDebuggerActive() && +#endif +#if !(defined(__DJGPP__) && defined(__STRICT_ANSI__)) + isatty(STDOUT_FILENO) +#else + false +#endif + ; + } + IColourImpl* platformColourInstance() { + ErrnoGuard guard; + IConfigPtr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = useColourOnPlatform() + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes + ? PosixColourImpl::instance() + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#else // not Windows or ANSI /////////////////////////////////////////////// + +namespace Catch { + + static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } + +} // end namespace Catch + +#endif // Windows/ ANSI/ None + +namespace Catch { + + Colour::Colour( Code _colourCode ) { use( _colourCode ); } + Colour::Colour( Colour&& rhs ) noexcept { + m_moved = rhs.m_moved; + rhs.m_moved = true; + } + Colour& Colour::operator=( Colour&& rhs ) noexcept { + m_moved = rhs.m_moved; + rhs.m_moved = true; + return *this; + } + + Colour::~Colour(){ if( !m_moved ) use( None ); } + + void Colour::use( Code _colourCode ) { + static IColourImpl* impl = platformColourInstance(); + impl->use( _colourCode ); + } + + std::ostream& operator << ( std::ostream& os, Colour const& ) { + return os; + } + +} // end namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +// end catch_console_colour.cpp +// start catch_context.cpp + +namespace Catch { + + class Context : public IMutableContext, NonCopyable { + + public: // IContext + virtual IResultCapture* getResultCapture() override { + return m_resultCapture; + } + virtual IRunner* getRunner() override { + return m_runner; + } + + virtual IConfigPtr const& getConfig() const override { + return m_config; + } + + virtual ~Context() override; + + public: // IMutableContext + virtual void setResultCapture( IResultCapture* resultCapture ) override { + m_resultCapture = resultCapture; + } + virtual void setRunner( IRunner* runner ) override { + m_runner = runner; + } + virtual void setConfig( IConfigPtr const& config ) override { + m_config = config; + } + + friend IMutableContext& getCurrentMutableContext(); + + private: + IConfigPtr m_config; + IRunner* m_runner = nullptr; + IResultCapture* m_resultCapture = nullptr; + }; + + IMutableContext *IMutableContext::currentContext = nullptr; + + void IMutableContext::createContext() + { + currentContext = new Context(); + } + + void cleanUpContext() { + delete IMutableContext::currentContext; + IMutableContext::currentContext = nullptr; + } + IContext::~IContext() = default; + IMutableContext::~IMutableContext() = default; + Context::~Context() = default; +} +// end catch_context.cpp +// start catch_debug_console.cpp + +// start catch_debug_console.h + +#include + +namespace Catch { + void writeToDebugConsole( std::string const& text ); +} + +// end catch_debug_console.h +#ifdef CATCH_PLATFORM_WINDOWS + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + ::OutputDebugStringA( text.c_str() ); + } + } + +#else + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + // !TBD: Need a version for Mac/ XCode and other IDEs + Catch::cout() << text; + } + } + +#endif // Platform +// end catch_debug_console.cpp +// start catch_debugger.cpp + +#ifdef CATCH_PLATFORM_MAC + +# include +# include +# include +# include +# include +# include +# include + +namespace Catch { + + // The following function is taken directly from the following technical note: + // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive(){ + + int mib[4]; + struct kinfo_proc info; + std::size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0 ) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; + return false; + } + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + } // namespace Catch + +#elif defined(CATCH_PLATFORM_LINUX) + #include + #include + + namespace Catch{ + // The standard POSIX way of detecting a debugger is to attempt to + // ptrace() the process, but this needs to be done from a child and not + // this process itself to still allow attaching to this process later + // if wanted, so is rather heavy. Under Linux we have the PID of the + // "debugger" (which doesn't need to be gdb, of course, it could also + // be strace, for example) in /proc/$PID/status, so just get it from + // there instead. + bool isDebuggerActive(){ + // Libstdc++ has a bug, where std::ifstream sets errno to 0 + // This way our users can properly assert over errno values + ErrnoGuard guard; + std::ifstream in("/proc/self/status"); + for( std::string line; std::getline(in, line); ) { + static const int PREFIX_LEN = 11; + if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) { + // We're traced if the PID is not 0 and no other PID starts + // with 0 digit, so it's enough to check for just a single + // character. + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } + + return false; + } + } // namespace Catch +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#else + namespace Catch { + bool isDebuggerActive() { return false; } + } +#endif // Platform +// end catch_debugger.cpp +// start catch_decomposer.cpp + +namespace Catch { + + ITransientExpression::~ITransientExpression() = default; + + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ) { + if( lhs.size() + rhs.size() < 40 && + lhs.find('\n') == std::string::npos && + rhs.find('\n') == std::string::npos ) + os << lhs << " " << op << " " << rhs; + else + os << lhs << "\n" << op << "\n" << rhs; + } +} +// end catch_decomposer.cpp +// start catch_enforce.cpp + +namespace Catch { +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER) + [[noreturn]] + void throw_exception(std::exception const& e) { + Catch::cerr() << "Catch will terminate because it needed to throw an exception.\n" + << "The message was: " << e.what() << '\n'; + std::terminate(); + } +#endif +} // namespace Catch; +// end catch_enforce.cpp +// start catch_errno_guard.cpp + +#include + +namespace Catch { + ErrnoGuard::ErrnoGuard():m_oldErrno(errno){} + ErrnoGuard::~ErrnoGuard() { errno = m_oldErrno; } +} +// end catch_errno_guard.cpp +// start catch_exception_translator_registry.cpp + +// start catch_exception_translator_registry.h + +#include +#include +#include + +namespace Catch { + + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + public: + ~ExceptionTranslatorRegistry(); + virtual void registerTranslator( const IExceptionTranslator* translator ); + virtual std::string translateActiveException() const override; + std::string tryTranslators() const; + + private: + std::vector> m_translators; + }; +} + +// end catch_exception_translator_registry.h +#ifdef __OBJC__ +#import "Foundation/Foundation.h" +#endif + +namespace Catch { + + ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() { + } + + void ExceptionTranslatorRegistry::registerTranslator( const IExceptionTranslator* translator ) { + m_translators.push_back( std::unique_ptr( translator ) ); + } + +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + std::string ExceptionTranslatorRegistry::translateActiveException() const { + try { +#ifdef __OBJC__ + // In Objective-C try objective-c exceptions first + @try { + return tryTranslators(); + } + @catch (NSException *exception) { + return Catch::Detail::stringify( [exception description] ); + } +#else + // Compiling a mixed mode project with MSVC means that CLR + // exceptions will be caught in (...) as well. However, these + // do not fill-in std::current_exception and thus lead to crash + // when attempting rethrow. + // /EHa switch also causes structured exceptions to be caught + // here, but they fill-in current_exception properly, so + // at worst the output should be a little weird, instead of + // causing a crash. + if (std::current_exception() == nullptr) { + return "Non C++ exception. Possibly a CLR exception."; + } + return tryTranslators(); +#endif + } + catch( TestFailureException& ) { + std::rethrow_exception(std::current_exception()); + } + catch( std::exception& ex ) { + return ex.what(); + } + catch( std::string& msg ) { + return msg; + } + catch( const char* msg ) { + return msg; + } + catch(...) { + return "Unknown exception"; + } + } + + std::string ExceptionTranslatorRegistry::tryTranslators() const { + if (m_translators.empty()) { + std::rethrow_exception(std::current_exception()); + } else { + return m_translators[0]->translate(m_translators.begin() + 1, m_translators.end()); + } + } + +#else // ^^ Exceptions are enabled // Exceptions are disabled vv + std::string ExceptionTranslatorRegistry::translateActiveException() const { + CATCH_INTERNAL_ERROR("Attempted to translate active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); + } + + std::string ExceptionTranslatorRegistry::tryTranslators() const { + CATCH_INTERNAL_ERROR("Attempted to use exception translators under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); + } +#endif + +} +// end catch_exception_translator_registry.cpp +// start catch_fatal_condition.cpp + +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace { + // Report the error condition + void reportFatal( char const * const message ) { + Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); + } +} + +#endif // signals/SEH handling + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) + +namespace Catch { + struct SignalDefs { DWORD id; const char* name; }; + + // There is no 1-1 mapping between signals and windows exceptions. + // Windows can easily distinguish between SO and SigSegV, + // but SigInt, SigTerm, etc are handled differently. + static SignalDefs signalDefs[] = { + { static_cast(EXCEPTION_ILLEGAL_INSTRUCTION), "SIGILL - Illegal instruction signal" }, + { static_cast(EXCEPTION_STACK_OVERFLOW), "SIGSEGV - Stack overflow" }, + { static_cast(EXCEPTION_ACCESS_VIOLATION), "SIGSEGV - Segmentation violation signal" }, + { static_cast(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error" }, + }; + + LONG CALLBACK FatalConditionHandler::handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { + for (auto const& def : signalDefs) { + if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { + reportFatal(def.name); + } + } + // If its not an exception we care about, pass it along. + // This stops us from eating debugger breaks etc. + return EXCEPTION_CONTINUE_SEARCH; + } + + FatalConditionHandler::FatalConditionHandler() { + isSet = true; + // 32k seems enough for Catch to handle stack overflow, + // but the value was found experimentally, so there is no strong guarantee + guaranteeSize = 32 * 1024; + exceptionHandlerHandle = nullptr; + // Register as first handler in current chain + exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); + // Pass in guarantee size to be filled + SetThreadStackGuarantee(&guaranteeSize); + } + + void FatalConditionHandler::reset() { + if (isSet) { + RemoveVectoredExceptionHandler(exceptionHandlerHandle); + SetThreadStackGuarantee(&guaranteeSize); + exceptionHandlerHandle = nullptr; + isSet = false; + } + } + + FatalConditionHandler::~FatalConditionHandler() { + reset(); + } + +bool FatalConditionHandler::isSet = false; +ULONG FatalConditionHandler::guaranteeSize = 0; +PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr; + +} // namespace Catch + +#elif defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace Catch { + + struct SignalDefs { + int id; + const char* name; + }; + + // 32kb for the alternate stack seems to be sufficient. However, this value + // is experimentally determined, so that's not guaranteed. + constexpr static std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ; + + static SignalDefs signalDefs[] = { + { SIGINT, "SIGINT - Terminal interrupt signal" }, + { SIGILL, "SIGILL - Illegal instruction signal" }, + { SIGFPE, "SIGFPE - Floating point error signal" }, + { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, + { SIGTERM, "SIGTERM - Termination request signal" }, + { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } + }; + + void FatalConditionHandler::handleSignal( int sig ) { + char const * name = ""; + for (auto const& def : signalDefs) { + if (sig == def.id) { + name = def.name; + break; + } + } + reset(); + reportFatal(name); + raise( sig ); + } + + FatalConditionHandler::FatalConditionHandler() { + isSet = true; + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = sigStackSize; + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = { }; + + sa.sa_handler = handleSignal; + sa.sa_flags = SA_ONSTACK; + for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } + } + + FatalConditionHandler::~FatalConditionHandler() { + reset(); + } + + void FatalConditionHandler::reset() { + if( isSet ) { + // Set signals back to previous values -- hopefully nobody overwrote them in the meantime + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + isSet = false; + } + } + + bool FatalConditionHandler::isSet = false; + struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; + stack_t FatalConditionHandler::oldSigStack = {}; + char FatalConditionHandler::altStackMem[sigStackSize] = {}; + +} // namespace Catch + +#else + +namespace Catch { + void FatalConditionHandler::reset() {} +} + +#endif // signals/SEH handling + +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif +// end catch_fatal_condition.cpp +// start catch_generators.cpp + +// start catch_random_number_generator.h + +#include +#include + +namespace Catch { + + struct IConfig; + + std::mt19937& rng(); + void seedRng( IConfig const& config ); + unsigned int rngSeed(); + +} + +// end catch_random_number_generator.h +#include +#include + +namespace Catch { + +IGeneratorTracker::~IGeneratorTracker() {} + +const char* GeneratorException::what() const noexcept { + return m_msg; +} + +namespace Generators { + + GeneratorUntypedBase::~GeneratorUntypedBase() {} + + auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { + return getResultCapture().acquireGeneratorTracker( lineInfo ); + } + +} // namespace Generators +} // namespace Catch +// end catch_generators.cpp +// start catch_interfaces_capture.cpp + +namespace Catch { + IResultCapture::~IResultCapture() = default; +} +// end catch_interfaces_capture.cpp +// start catch_interfaces_config.cpp + +namespace Catch { + IConfig::~IConfig() = default; +} +// end catch_interfaces_config.cpp +// start catch_interfaces_exception.cpp + +namespace Catch { + IExceptionTranslator::~IExceptionTranslator() = default; + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default; +} +// end catch_interfaces_exception.cpp +// start catch_interfaces_registry_hub.cpp + +namespace Catch { + IRegistryHub::~IRegistryHub() = default; + IMutableRegistryHub::~IMutableRegistryHub() = default; +} +// end catch_interfaces_registry_hub.cpp +// start catch_interfaces_reporter.cpp + +// start catch_reporter_listening.h + +namespace Catch { + + class ListeningReporter : public IStreamingReporter { + using Reporters = std::vector; + Reporters m_listeners; + IStreamingReporterPtr m_reporter = nullptr; + ReporterPreferences m_preferences; + + public: + ListeningReporter(); + + void addListener( IStreamingReporterPtr&& listener ); + void addReporter( IStreamingReporterPtr&& reporter ); + + public: // IStreamingReporter + + ReporterPreferences getPreferences() const override; + + void noMatchingTestCases( std::string const& spec ) override; + + static std::set getSupportedVerbosities(); + + void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override; + void benchmarkEnded( BenchmarkStats const& benchmarkStats ) override; + + void testRunStarting( TestRunInfo const& testRunInfo ) override; + void testGroupStarting( GroupInfo const& groupInfo ) override; + void testCaseStarting( TestCaseInfo const& testInfo ) override; + void sectionStarting( SectionInfo const& sectionInfo ) override; + void assertionStarting( AssertionInfo const& assertionInfo ) override; + + // The return value indicates if the messages buffer should be cleared: + bool assertionEnded( AssertionStats const& assertionStats ) override; + void sectionEnded( SectionStats const& sectionStats ) override; + void testCaseEnded( TestCaseStats const& testCaseStats ) override; + void testGroupEnded( TestGroupStats const& testGroupStats ) override; + void testRunEnded( TestRunStats const& testRunStats ) override; + + void skipTest( TestCaseInfo const& testInfo ) override; + bool isMulti() const override; + + }; + +} // end namespace Catch + +// end catch_reporter_listening.h +namespace Catch { + + ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig ) + : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} + + ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream ) + : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} + + std::ostream& ReporterConfig::stream() const { return *m_stream; } + IConfigPtr ReporterConfig::fullConfig() const { return m_fullConfig; } + + TestRunInfo::TestRunInfo( std::string const& _name ) : name( _name ) {} + + GroupInfo::GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ) + : name( _name ), + groupIndex( _groupIndex ), + groupsCounts( _groupsCount ) + {} + + AssertionStats::AssertionStats( AssertionResult const& _assertionResult, + std::vector const& _infoMessages, + Totals const& _totals ) + : assertionResult( _assertionResult ), + infoMessages( _infoMessages ), + totals( _totals ) + { + assertionResult.m_resultData.lazyExpression.m_transientExpression = _assertionResult.m_resultData.lazyExpression.m_transientExpression; + + if( assertionResult.hasMessage() ) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); + builder << assertionResult.getMessage(); + builder.m_info.message = builder.m_stream.str(); + + infoMessages.push_back( builder.m_info ); + } + } + + AssertionStats::~AssertionStats() = default; + + SectionStats::SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ) + : sectionInfo( _sectionInfo ), + assertions( _assertions ), + durationInSeconds( _durationInSeconds ), + missingAssertions( _missingAssertions ) + {} + + SectionStats::~SectionStats() = default; + + TestCaseStats::TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ) + : testInfo( _testInfo ), + totals( _totals ), + stdOut( _stdOut ), + stdErr( _stdErr ), + aborting( _aborting ) + {} + + TestCaseStats::~TestCaseStats() = default; + + TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ) + : groupInfo( _groupInfo ), + totals( _totals ), + aborting( _aborting ) + {} + + TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo ) + : groupInfo( _groupInfo ), + aborting( false ) + {} + + TestGroupStats::~TestGroupStats() = default; + + TestRunStats::TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ) + : runInfo( _runInfo ), + totals( _totals ), + aborting( _aborting ) + {} + + TestRunStats::~TestRunStats() = default; + + void IStreamingReporter::fatalErrorEncountered( StringRef ) {} + bool IStreamingReporter::isMulti() const { return false; } + + IReporterFactory::~IReporterFactory() = default; + IReporterRegistry::~IReporterRegistry() = default; + +} // end namespace Catch +// end catch_interfaces_reporter.cpp +// start catch_interfaces_runner.cpp + +namespace Catch { + IRunner::~IRunner() = default; +} +// end catch_interfaces_runner.cpp +// start catch_interfaces_testcase.cpp + +namespace Catch { + ITestInvoker::~ITestInvoker() = default; + ITestCaseRegistry::~ITestCaseRegistry() = default; +} +// end catch_interfaces_testcase.cpp +// start catch_leak_detector.cpp + +#ifdef CATCH_CONFIG_WINDOWS_CRTDBG +#include + +namespace Catch { + + LeakDetector::LeakDetector() { + int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + flag |= _CRTDBG_LEAK_CHECK_DF; + flag |= _CRTDBG_ALLOC_MEM_DF; + _CrtSetDbgFlag(flag); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + // Change this to leaking allocation's number to break there + _CrtSetBreakAlloc(-1); + } +} + +#else + + Catch::LeakDetector::LeakDetector() {} + +#endif + +Catch::LeakDetector::~LeakDetector() { + Catch::cleanUp(); +} +// end catch_leak_detector.cpp +// start catch_list.cpp + +// start catch_list.h + +#include + +namespace Catch { + + std::size_t listTests( Config const& config ); + + std::size_t listTestsNamesOnly( Config const& config ); + + struct TagInfo { + void add( std::string const& spelling ); + std::string all() const; + + std::set spellings; + std::size_t count = 0; + }; + + std::size_t listTags( Config const& config ); + + std::size_t listReporters(); + + Option list( std::shared_ptr const& config ); + +} // end namespace Catch + +// end catch_list.h +// start catch_text.h + +namespace Catch { + using namespace clara::TextFlow; +} + +// end catch_text.h +#include +#include +#include + +namespace Catch { + + std::size_t listTests( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.hasTestFilters() ) + Catch::cout() << "Matching test cases:\n"; + else { + Catch::cout() << "All available test cases:\n"; + } + + auto matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCaseInfo : matchedTestCases ) { + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + Colour colourGuard( colour ); + + Catch::cout() << Column( testCaseInfo.name ).initialIndent( 2 ).indent( 4 ) << "\n"; + if( config.verbosity() >= Verbosity::High ) { + Catch::cout() << Column( Catch::Detail::stringify( testCaseInfo.lineInfo ) ).indent(4) << std::endl; + std::string description = testCaseInfo.description; + if( description.empty() ) + description = "(NO DESCRIPTION)"; + Catch::cout() << Column( description ).indent(4) << std::endl; + } + if( !testCaseInfo.tags.empty() ) + Catch::cout() << Column( testCaseInfo.tagsAsString() ).indent( 6 ) << "\n"; + } + + if( !config.hasTestFilters() ) + Catch::cout() << pluralise( matchedTestCases.size(), "test case" ) << '\n' << std::endl; + else + Catch::cout() << pluralise( matchedTestCases.size(), "matching test case" ) << '\n' << std::endl; + return matchedTestCases.size(); + } + + std::size_t listTestsNamesOnly( Config const& config ) { + TestSpec testSpec = config.testSpec(); + std::size_t matchedTests = 0; + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCaseInfo : matchedTestCases ) { + matchedTests++; + if( startsWith( testCaseInfo.name, '#' ) ) + Catch::cout() << '"' << testCaseInfo.name << '"'; + else + Catch::cout() << testCaseInfo.name; + if ( config.verbosity() >= Verbosity::High ) + Catch::cout() << "\t@" << testCaseInfo.lineInfo; + Catch::cout() << std::endl; + } + return matchedTests; + } + + void TagInfo::add( std::string const& spelling ) { + ++count; + spellings.insert( spelling ); + } + + std::string TagInfo::all() const { + std::string out; + for( auto const& spelling : spellings ) + out += "[" + spelling + "]"; + return out; + } + + std::size_t listTags( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.hasTestFilters() ) + Catch::cout() << "Tags for matching test cases:\n"; + else { + Catch::cout() << "All available tags:\n"; + } + + std::map tagCounts; + + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCase : matchedTestCases ) { + for( auto const& tagName : testCase.getTestCaseInfo().tags ) { + std::string lcaseTagName = toLower( tagName ); + auto countIt = tagCounts.find( lcaseTagName ); + if( countIt == tagCounts.end() ) + countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; + countIt->second.add( tagName ); + } + } + + for( auto const& tagCount : tagCounts ) { + ReusableStringStream rss; + rss << " " << std::setw(2) << tagCount.second.count << " "; + auto str = rss.str(); + auto wrapper = Column( tagCount.second.all() ) + .initialIndent( 0 ) + .indent( str.size() ) + .width( CATCH_CONFIG_CONSOLE_WIDTH-10 ); + Catch::cout() << str << wrapper << '\n'; + } + Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl; + return tagCounts.size(); + } + + std::size_t listReporters() { + Catch::cout() << "Available reporters:\n"; + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + std::size_t maxNameLen = 0; + for( auto const& factoryKvp : factories ) + maxNameLen = (std::max)( maxNameLen, factoryKvp.first.size() ); + + for( auto const& factoryKvp : factories ) { + Catch::cout() + << Column( factoryKvp.first + ":" ) + .indent(2) + .width( 5+maxNameLen ) + + Column( factoryKvp.second->getDescription() ) + .initialIndent(0) + .indent(2) + .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) + << "\n"; + } + Catch::cout() << std::endl; + return factories.size(); + } + + Option list( std::shared_ptr const& config ) { + Option listedCount; + getCurrentMutableContext().setConfig( config ); + if( config->listTests() ) + listedCount = listedCount.valueOr(0) + listTests( *config ); + if( config->listTestNamesOnly() ) + listedCount = listedCount.valueOr(0) + listTestsNamesOnly( *config ); + if( config->listTags() ) + listedCount = listedCount.valueOr(0) + listTags( *config ); + if( config->listReporters() ) + listedCount = listedCount.valueOr(0) + listReporters(); + return listedCount; + } + +} // end namespace Catch +// end catch_list.cpp +// start catch_matchers.cpp + +namespace Catch { +namespace Matchers { + namespace Impl { + + std::string MatcherUntypedBase::toString() const { + if( m_cachedToString.empty() ) + m_cachedToString = describe(); + return m_cachedToString; + } + + MatcherUntypedBase::~MatcherUntypedBase() = default; + + } // namespace Impl +} // namespace Matchers + +using namespace Matchers; +using Matchers::Impl::MatcherBase; + +} // namespace Catch +// end catch_matchers.cpp +// start catch_matchers_floating.cpp + +// start catch_polyfills.hpp + +namespace Catch { + bool isnan(float f); + bool isnan(double d); +} + +// end catch_polyfills.hpp +// start catch_to_string.hpp + +#include + +namespace Catch { + template + std::string to_string(T const& t) { +#if defined(CATCH_CONFIG_CPP11_TO_STRING) + return std::to_string(t); +#else + ReusableStringStream rss; + rss << t; + return rss.str(); +#endif + } +} // end namespace Catch + +// end catch_to_string.hpp +#include +#include +#include + +namespace Catch { +namespace Matchers { +namespace Floating { +enum class FloatingPointKind : uint8_t { + Float, + Double +}; +} +} +} + +namespace { + +template +struct Converter; + +template <> +struct Converter { + static_assert(sizeof(float) == sizeof(int32_t), "Important ULP matcher assumption violated"); + Converter(float f) { + std::memcpy(&i, &f, sizeof(f)); + } + int32_t i; +}; + +template <> +struct Converter { + static_assert(sizeof(double) == sizeof(int64_t), "Important ULP matcher assumption violated"); + Converter(double d) { + std::memcpy(&i, &d, sizeof(d)); + } + int64_t i; +}; + +template +auto convert(T t) -> Converter { + return Converter(t); +} + +template +bool almostEqualUlps(FP lhs, FP rhs, int maxUlpDiff) { + // Comparison with NaN should always be false. + // This way we can rule it out before getting into the ugly details + if (Catch::isnan(lhs) || Catch::isnan(rhs)) { + return false; + } + + auto lc = convert(lhs); + auto rc = convert(rhs); + + if ((lc.i < 0) != (rc.i < 0)) { + // Potentially we can have +0 and -0 + return lhs == rhs; + } + + auto ulpDiff = std::abs(lc.i - rc.i); + return ulpDiff <= maxUlpDiff; +} + +} + +namespace Catch { +namespace Matchers { +namespace Floating { + WithinAbsMatcher::WithinAbsMatcher(double target, double margin) + :m_target{ target }, m_margin{ margin } { + CATCH_ENFORCE(margin >= 0, "Invalid margin: " << margin << '.' + << " Margin has to be non-negative."); + } + + // Performs equivalent check of std::fabs(lhs - rhs) <= margin + // But without the subtraction to allow for INFINITY in comparison + bool WithinAbsMatcher::match(double const& matchee) const { + return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee); + } + + std::string WithinAbsMatcher::describe() const { + return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target); + } + + WithinUlpsMatcher::WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType) + :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } { + CATCH_ENFORCE(ulps >= 0, "Invalid ULP setting: " << ulps << '.' + << " ULPs have to be non-negative."); + } + +#if defined(__clang__) +#pragma clang diagnostic push +// Clang <3.5 reports on the default branch in the switch below +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + + bool WithinUlpsMatcher::match(double const& matchee) const { + switch (m_type) { + case FloatingPointKind::Float: + return almostEqualUlps(static_cast(matchee), static_cast(m_target), m_ulps); + case FloatingPointKind::Double: + return almostEqualUlps(matchee, m_target, m_ulps); + default: + CATCH_INTERNAL_ERROR( "Unknown FloatingPointKind value" ); + } + } + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + + std::string WithinUlpsMatcher::describe() const { + return "is within " + Catch::to_string(m_ulps) + " ULPs of " + ::Catch::Detail::stringify(m_target) + ((m_type == FloatingPointKind::Float)? "f" : ""); + } + +}// namespace Floating + +Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff) { + return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Double); +} + +Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff) { + return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Float); +} + +Floating::WithinAbsMatcher WithinAbs(double target, double margin) { + return Floating::WithinAbsMatcher(target, margin); +} + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_floating.cpp +// start catch_matchers_generic.cpp + +std::string Catch::Matchers::Generic::Detail::finalizeDescription(const std::string& desc) { + if (desc.empty()) { + return "matches undescribed predicate"; + } else { + return "matches predicate: \"" + desc + '"'; + } +} +// end catch_matchers_generic.cpp +// start catch_matchers_string.cpp + +#include + +namespace Catch { +namespace Matchers { + + namespace StdString { + + CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_str( adjustString( str ) ) + {} + std::string CasedString::adjustString( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No + ? toLower( str ) + : str; + } + std::string CasedString::caseSensitivitySuffix() const { + return m_caseSensitivity == CaseSensitive::No + ? " (case insensitive)" + : std::string(); + } + + StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator ) + : m_comparator( comparator ), + m_operation( operation ) { + } + + std::string StringMatcherBase::describe() const { + std::string description; + description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + + m_comparator.caseSensitivitySuffix().size()); + description += m_operation; + description += ": \""; + description += m_comparator.m_str; + description += "\""; + description += m_comparator.caseSensitivitySuffix(); + return description; + } + + EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {} + + bool EqualsMatcher::match( std::string const& source ) const { + return m_comparator.adjustString( source ) == m_comparator.m_str; + } + + ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {} + + bool ContainsMatcher::match( std::string const& source ) const { + return contains( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {} + + bool StartsWithMatcher::match( std::string const& source ) const { + return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {} + + bool EndsWithMatcher::match( std::string const& source ) const { + return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + RegexMatcher::RegexMatcher(std::string regex, CaseSensitive::Choice caseSensitivity): m_regex(std::move(regex)), m_caseSensitivity(caseSensitivity) {} + + bool RegexMatcher::match(std::string const& matchee) const { + auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway + if (m_caseSensitivity == CaseSensitive::Choice::No) { + flags |= std::regex::icase; + } + auto reg = std::regex(m_regex, flags); + return std::regex_match(matchee, reg); + } + + std::string RegexMatcher::describe() const { + return "matches " + ::Catch::Detail::stringify(m_regex) + ((m_caseSensitivity == CaseSensitive::Choice::Yes)? " case sensitively" : " case insensitively"); + } + + } // namespace StdString + + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + + StdString::RegexMatcher Matches(std::string const& regex, CaseSensitive::Choice caseSensitivity) { + return StdString::RegexMatcher(regex, caseSensitivity); + } + +} // namespace Matchers +} // namespace Catch +// end catch_matchers_string.cpp +// start catch_message.cpp + +// start catch_uncaught_exceptions.h + +namespace Catch { + bool uncaught_exceptions(); +} // end namespace Catch + +// end catch_uncaught_exceptions.h +#include +#include + +namespace Catch { + + MessageInfo::MessageInfo( StringRef const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + type( _type ), + sequence( ++globalCount ) + {} + + bool MessageInfo::operator==( MessageInfo const& other ) const { + return sequence == other.sequence; + } + + bool MessageInfo::operator<( MessageInfo const& other ) const { + return sequence < other.sequence; + } + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + + //////////////////////////////////////////////////////////////////////////// + + Catch::MessageBuilder::MessageBuilder( StringRef const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ) + :m_info(macroName, lineInfo, type) {} + + //////////////////////////////////////////////////////////////////////////// + + ScopedMessage::ScopedMessage( MessageBuilder const& builder ) + : m_info( builder.m_info ), m_moved() + { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + + ScopedMessage::ScopedMessage( ScopedMessage&& old ) + : m_info( old.m_info ), m_moved() + { + old.m_moved = true; + } + + ScopedMessage::~ScopedMessage() { + if ( !uncaught_exceptions() && !m_moved ){ + getResultCapture().popScopedMessage(m_info); + } + } + + Capturer::Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ) { + auto trimmed = [&] (size_t start, size_t end) { + while (names[start] == ',' || isspace(names[start])) { + ++start; + } + while (names[end] == ',' || isspace(names[end])) { + --end; + } + return names.substr(start, end - start + 1); + }; + + size_t start = 0; + std::stack openings; + for (size_t pos = 0; pos < names.size(); ++pos) { + char c = names[pos]; + switch (c) { + case '[': + case '{': + case '(': + // It is basically impossible to disambiguate between + // comparison and start of template args in this context +// case '<': + openings.push(c); + break; + case ']': + case '}': + case ')': +// case '>': + openings.pop(); + break; + case ',': + if (start != pos && openings.size() == 0) { + m_messages.emplace_back(macroName, lineInfo, resultType); + m_messages.back().message = trimmed(start, pos); + m_messages.back().message += " := "; + start = pos; + } + } + } + assert(openings.size() == 0 && "Mismatched openings"); + m_messages.emplace_back(macroName, lineInfo, resultType); + m_messages.back().message = trimmed(start, names.size() - 1); + m_messages.back().message += " := "; + } + Capturer::~Capturer() { + if ( !uncaught_exceptions() ){ + assert( m_captured == m_messages.size() ); + for( size_t i = 0; i < m_captured; ++i ) + m_resultCapture.popScopedMessage( m_messages[i] ); + } + } + + void Capturer::captureValue( size_t index, std::string const& value ) { + assert( index < m_messages.size() ); + m_messages[index].message += value; + m_resultCapture.pushScopedMessage( m_messages[index] ); + m_captured++; + } + +} // end namespace Catch +// end catch_message.cpp +// start catch_output_redirect.cpp + +// start catch_output_redirect.h +#ifndef TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H +#define TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H + +#include +#include +#include + +namespace Catch { + + class RedirectedStream { + std::ostream& m_originalStream; + std::ostream& m_redirectionStream; + std::streambuf* m_prevBuf; + + public: + RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ); + ~RedirectedStream(); + }; + + class RedirectedStdOut { + ReusableStringStream m_rss; + RedirectedStream m_cout; + public: + RedirectedStdOut(); + auto str() const -> std::string; + }; + + // StdErr has two constituent streams in C++, std::cerr and std::clog + // This means that we need to redirect 2 streams into 1 to keep proper + // order of writes + class RedirectedStdErr { + ReusableStringStream m_rss; + RedirectedStream m_cerr; + RedirectedStream m_clog; + public: + RedirectedStdErr(); + auto str() const -> std::string; + }; + + class RedirectedStreams { + public: + RedirectedStreams(RedirectedStreams const&) = delete; + RedirectedStreams& operator=(RedirectedStreams const&) = delete; + RedirectedStreams(RedirectedStreams&&) = delete; + RedirectedStreams& operator=(RedirectedStreams&&) = delete; + + RedirectedStreams(std::string& redirectedCout, std::string& redirectedCerr); + ~RedirectedStreams(); + private: + std::string& m_redirectedCout; + std::string& m_redirectedCerr; + RedirectedStdOut m_redirectedStdOut; + RedirectedStdErr m_redirectedStdErr; + }; + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + + // Windows's implementation of std::tmpfile is terrible (it tries + // to create a file inside system folder, thus requiring elevated + // privileges for the binary), so we have to use tmpnam(_s) and + // create the file ourselves there. + class TempFile { + public: + TempFile(TempFile const&) = delete; + TempFile& operator=(TempFile const&) = delete; + TempFile(TempFile&&) = delete; + TempFile& operator=(TempFile&&) = delete; + + TempFile(); + ~TempFile(); + + std::FILE* getFile(); + std::string getContents(); + + private: + std::FILE* m_file = nullptr; + #if defined(_MSC_VER) + char m_buffer[L_tmpnam] = { 0 }; + #endif + }; + + class OutputRedirect { + public: + OutputRedirect(OutputRedirect const&) = delete; + OutputRedirect& operator=(OutputRedirect const&) = delete; + OutputRedirect(OutputRedirect&&) = delete; + OutputRedirect& operator=(OutputRedirect&&) = delete; + + OutputRedirect(std::string& stdout_dest, std::string& stderr_dest); + ~OutputRedirect(); + + private: + int m_originalStdout = -1; + int m_originalStderr = -1; + TempFile m_stdoutFile; + TempFile m_stderrFile; + std::string& m_stdoutDest; + std::string& m_stderrDest; + }; + +#endif + +} // end namespace Catch + +#endif // TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H +// end catch_output_redirect.h +#include +#include +#include +#include +#include + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + #if defined(_MSC_VER) + #include //_dup and _dup2 + #define dup _dup + #define dup2 _dup2 + #define fileno _fileno + #else + #include // dup and dup2 + #endif +#endif + +namespace Catch { + + RedirectedStream::RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ) + : m_originalStream( originalStream ), + m_redirectionStream( redirectionStream ), + m_prevBuf( m_originalStream.rdbuf() ) + { + m_originalStream.rdbuf( m_redirectionStream.rdbuf() ); + } + + RedirectedStream::~RedirectedStream() { + m_originalStream.rdbuf( m_prevBuf ); + } + + RedirectedStdOut::RedirectedStdOut() : m_cout( Catch::cout(), m_rss.get() ) {} + auto RedirectedStdOut::str() const -> std::string { return m_rss.str(); } + + RedirectedStdErr::RedirectedStdErr() + : m_cerr( Catch::cerr(), m_rss.get() ), + m_clog( Catch::clog(), m_rss.get() ) + {} + auto RedirectedStdErr::str() const -> std::string { return m_rss.str(); } + + RedirectedStreams::RedirectedStreams(std::string& redirectedCout, std::string& redirectedCerr) + : m_redirectedCout(redirectedCout), + m_redirectedCerr(redirectedCerr) + {} + + RedirectedStreams::~RedirectedStreams() { + m_redirectedCout += m_redirectedStdOut.str(); + m_redirectedCerr += m_redirectedStdErr.str(); + } + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + +#if defined(_MSC_VER) + TempFile::TempFile() { + if (tmpnam_s(m_buffer)) { + CATCH_RUNTIME_ERROR("Could not get a temp filename"); + } + if (fopen_s(&m_file, m_buffer, "w")) { + char buffer[100]; + if (strerror_s(buffer, errno)) { + CATCH_RUNTIME_ERROR("Could not translate errno to a string"); + } + CATCH_RUNTIME_ERROR("Coul dnot open the temp file: '" << m_buffer << "' because: " << buffer); + } + } +#else + TempFile::TempFile() { + m_file = std::tmpfile(); + if (!m_file) { + CATCH_RUNTIME_ERROR("Could not create a temp file."); + } + } + +#endif + + TempFile::~TempFile() { + // TBD: What to do about errors here? + std::fclose(m_file); + // We manually create the file on Windows only, on Linux + // it will be autodeleted +#if defined(_MSC_VER) + std::remove(m_buffer); +#endif + } + + FILE* TempFile::getFile() { + return m_file; + } + + std::string TempFile::getContents() { + std::stringstream sstr; + char buffer[100] = {}; + std::rewind(m_file); + while (std::fgets(buffer, sizeof(buffer), m_file)) { + sstr << buffer; + } + return sstr.str(); + } + + OutputRedirect::OutputRedirect(std::string& stdout_dest, std::string& stderr_dest) : + m_originalStdout(dup(1)), + m_originalStderr(dup(2)), + m_stdoutDest(stdout_dest), + m_stderrDest(stderr_dest) { + dup2(fileno(m_stdoutFile.getFile()), 1); + dup2(fileno(m_stderrFile.getFile()), 2); + } + + OutputRedirect::~OutputRedirect() { + Catch::cout() << std::flush; + fflush(stdout); + // Since we support overriding these streams, we flush cerr + // even though std::cerr is unbuffered + Catch::cerr() << std::flush; + Catch::clog() << std::flush; + fflush(stderr); + + dup2(m_originalStdout, 1); + dup2(m_originalStderr, 2); + + m_stdoutDest += m_stdoutFile.getContents(); + m_stderrDest += m_stderrFile.getContents(); + } + +#endif // CATCH_CONFIG_NEW_CAPTURE + +} // namespace Catch + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + #if defined(_MSC_VER) + #undef dup + #undef dup2 + #undef fileno + #endif +#endif +// end catch_output_redirect.cpp +// start catch_polyfills.cpp + +#include + +namespace Catch { + +#if !defined(CATCH_CONFIG_POLYFILL_ISNAN) + bool isnan(float f) { + return std::isnan(f); + } + bool isnan(double d) { + return std::isnan(d); + } +#else + // For now we only use this for embarcadero + bool isnan(float f) { + return std::_isnan(f); + } + bool isnan(double d) { + return std::_isnan(d); + } +#endif + +} // end namespace Catch +// end catch_polyfills.cpp +// start catch_random_number_generator.cpp + +namespace Catch { + + std::mt19937& rng() { + static std::mt19937 s_rng; + return s_rng; + } + + void seedRng( IConfig const& config ) { + if( config.rngSeed() != 0 ) { + std::srand( config.rngSeed() ); + rng().seed( config.rngSeed() ); + } + } + + unsigned int rngSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } +} +// end catch_random_number_generator.cpp +// start catch_registry_hub.cpp + +// start catch_test_case_registry_impl.h + +#include +#include +#include +#include + +namespace Catch { + + class TestCase; + struct IConfig; + + std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ); + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + + void enforceNoDuplicateTestCases( std::vector const& functions ); + + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + + class TestRegistry : public ITestCaseRegistry { + public: + virtual ~TestRegistry() = default; + + virtual void registerTest( TestCase const& testCase ); + + std::vector const& getAllTests() const override; + std::vector const& getAllTestsSorted( IConfig const& config ) const override; + + private: + std::vector m_functions; + mutable RunTests::InWhatOrder m_currentSortOrder = RunTests::InDeclarationOrder; + mutable std::vector m_sortedFunctions; + std::size_t m_unnamedCount = 0; + std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised + }; + + /////////////////////////////////////////////////////////////////////////// + + class TestInvokerAsFunction : public ITestInvoker { + void(*m_testAsFunction)(); + public: + TestInvokerAsFunction( void(*testAsFunction)() ) noexcept; + + void invoke() const override; + }; + + std::string extractClassName( StringRef const& classOrQualifiedMethodName ); + + /////////////////////////////////////////////////////////////////////////// + +} // end namespace Catch + +// end catch_test_case_registry_impl.h +// start catch_reporter_registry.h + +#include + +namespace Catch { + + class ReporterRegistry : public IReporterRegistry { + + public: + + ~ReporterRegistry() override; + + IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const override; + + void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ); + void registerListener( IReporterFactoryPtr const& factory ); + + FactoryMap const& getFactories() const override; + Listeners const& getListeners() const override; + + private: + FactoryMap m_factories; + Listeners m_listeners; + }; +} + +// end catch_reporter_registry.h +// start catch_tag_alias_registry.h + +// start catch_tag_alias.h + +#include + +namespace Catch { + + struct TagAlias { + TagAlias(std::string const& _tag, SourceLineInfo _lineInfo); + + std::string tag; + SourceLineInfo lineInfo; + }; + +} // end namespace Catch + +// end catch_tag_alias.h +#include + +namespace Catch { + + class TagAliasRegistry : public ITagAliasRegistry { + public: + ~TagAliasRegistry() override; + TagAlias const* find( std::string const& alias ) const override; + std::string expandAliases( std::string const& unexpandedTestSpec ) const override; + void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ); + + private: + std::map m_registry; + }; + +} // end namespace Catch + +// end catch_tag_alias_registry.h +// start catch_startup_exception_registry.h + +#include +#include + +namespace Catch { + + class StartupExceptionRegistry { + public: + void add(std::exception_ptr const& exception) noexcept; + std::vector const& getExceptions() const noexcept; + private: + std::vector m_exceptions; + }; + +} // end namespace Catch + +// end catch_startup_exception_registry.h +// start catch_singletons.hpp + +namespace Catch { + + struct ISingleton { + virtual ~ISingleton(); + }; + + void addSingleton( ISingleton* singleton ); + void cleanupSingletons(); + + template + class Singleton : SingletonImplT, public ISingleton { + + static auto getInternal() -> Singleton* { + static Singleton* s_instance = nullptr; + if( !s_instance ) { + s_instance = new Singleton; + addSingleton( s_instance ); + } + return s_instance; + } + + public: + static auto get() -> InterfaceT const& { + return *getInternal(); + } + static auto getMutable() -> MutableInterfaceT& { + return *getInternal(); + } + }; + +} // namespace Catch + +// end catch_singletons.hpp +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, public IMutableRegistryHub, + private NonCopyable { + + public: // IRegistryHub + RegistryHub() = default; + IReporterRegistry const& getReporterRegistry() const override { + return m_reporterRegistry; + } + ITestCaseRegistry const& getTestCaseRegistry() const override { + return m_testCaseRegistry; + } + IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const override { + return m_exceptionTranslatorRegistry; + } + ITagAliasRegistry const& getTagAliasRegistry() const override { + return m_tagAliasRegistry; + } + StartupExceptionRegistry const& getStartupExceptionRegistry() const override { + return m_exceptionRegistry; + } + + public: // IMutableRegistryHub + void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) override { + m_reporterRegistry.registerReporter( name, factory ); + } + void registerListener( IReporterFactoryPtr const& factory ) override { + m_reporterRegistry.registerListener( factory ); + } + void registerTest( TestCase const& testInfo ) override { + m_testCaseRegistry.registerTest( testInfo ); + } + void registerTranslator( const IExceptionTranslator* translator ) override { + m_exceptionTranslatorRegistry.registerTranslator( translator ); + } + void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override { + m_tagAliasRegistry.add( alias, tag, lineInfo ); + } + void registerStartupException() noexcept override { + m_exceptionRegistry.add(std::current_exception()); + } + + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + TagAliasRegistry m_tagAliasRegistry; + StartupExceptionRegistry m_exceptionRegistry; + }; + } + + using RegistryHubSingleton = Singleton; + + IRegistryHub const& getRegistryHub() { + return RegistryHubSingleton::get(); + } + IMutableRegistryHub& getMutableRegistryHub() { + return RegistryHubSingleton::getMutable(); + } + void cleanUp() { + cleanupSingletons(); + cleanUpContext(); + } + std::string translateActiveException() { + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); + } + +} // end namespace Catch +// end catch_registry_hub.cpp +// start catch_reporter_registry.cpp + +namespace Catch { + + ReporterRegistry::~ReporterRegistry() = default; + + IStreamingReporterPtr ReporterRegistry::create( std::string const& name, IConfigPtr const& config ) const { + auto it = m_factories.find( name ); + if( it == m_factories.end() ) + return nullptr; + return it->second->create( ReporterConfig( config ) ); + } + + void ReporterRegistry::registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) { + m_factories.emplace(name, factory); + } + void ReporterRegistry::registerListener( IReporterFactoryPtr const& factory ) { + m_listeners.push_back( factory ); + } + + IReporterRegistry::FactoryMap const& ReporterRegistry::getFactories() const { + return m_factories; + } + IReporterRegistry::Listeners const& ReporterRegistry::getListeners() const { + return m_listeners; + } + +} +// end catch_reporter_registry.cpp +// start catch_result_type.cpp + +namespace Catch { + + bool isOk( ResultWas::OfType resultType ) { + return ( resultType & ResultWas::FailureBit ) == 0; + } + bool isJustInfo( int flags ) { + return flags == ResultWas::Info; + } + + ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { + return static_cast( static_cast( lhs ) | static_cast( rhs ) ); + } + + bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } + bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } + +} // end namespace Catch +// end catch_result_type.cpp +// start catch_run_context.cpp + +#include +#include +#include + +namespace Catch { + + namespace Generators { + struct GeneratorTracker : TestCaseTracking::TrackerBase, IGeneratorTracker { + GeneratorBasePtr m_generator; + + GeneratorTracker( TestCaseTracking::NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( nameAndLocation, ctx, parent ) + {} + ~GeneratorTracker(); + + static GeneratorTracker& acquire( TrackerContext& ctx, TestCaseTracking::NameAndLocation const& nameAndLocation ) { + std::shared_ptr tracker; + + ITracker& currentTracker = ctx.currentTracker(); + if( TestCaseTracking::ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isGeneratorTracker() ); + tracker = std::static_pointer_cast( childTracker ); + } + else { + tracker = std::make_shared( nameAndLocation, ctx, ¤tTracker ); + currentTracker.addChild( tracker ); + } + + if( !ctx.completedCycle() && !tracker->isComplete() ) { + tracker->open(); + } + + return *tracker; + } + + // TrackerBase interface + bool isGeneratorTracker() const override { return true; } + auto hasGenerator() const -> bool override { + return !!m_generator; + } + void close() override { + TrackerBase::close(); + // Generator interface only finds out if it has another item on atual move + if (m_runState == CompletedSuccessfully && m_generator->next()) { + m_children.clear(); + m_runState = Executing; + } + } + + // IGeneratorTracker interface + auto getGenerator() const -> GeneratorBasePtr const& override { + return m_generator; + } + void setGenerator( GeneratorBasePtr&& generator ) override { + m_generator = std::move( generator ); + } + }; + GeneratorTracker::~GeneratorTracker() {} + } + + RunContext::RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter) + : m_runInfo(_config->name()), + m_context(getCurrentMutableContext()), + m_config(_config), + m_reporter(std::move(reporter)), + m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal }, + m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ) + { + m_context.setRunner(this); + m_context.setConfig(m_config); + m_context.setResultCapture(this); + m_reporter->testRunStarting(m_runInfo); + } + + RunContext::~RunContext() { + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting())); + } + + void RunContext::testGroupStarting(std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount) { + m_reporter->testGroupStarting(GroupInfo(testSpec, groupIndex, groupsCount)); + } + + void RunContext::testGroupEnded(std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount) { + m_reporter->testGroupEnded(TestGroupStats(GroupInfo(testSpec, groupIndex, groupsCount), totals, aborting())); + } + + Totals RunContext::runTest(TestCase const& testCase) { + Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + auto const& testInfo = testCase.getTestCaseInfo(); + + m_reporter->testCaseStarting(testInfo); + + m_activeTestCase = &testCase; + + ITracker& rootTracker = m_trackerContext.startRun(); + assert(rootTracker.isSectionTracker()); + static_cast(rootTracker).addInitialFilters(m_config->getSectionsToRun()); + do { + m_trackerContext.startCycle(); + m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(testInfo.name, testInfo.lineInfo)); + runCurrentTest(redirectedCout, redirectedCerr); + } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting()); + + Totals deltaTotals = m_totals.delta(prevTotals); + if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) { + deltaTotals.assertions.failed++; + deltaTotals.testCases.passed--; + deltaTotals.testCases.failed++; + } + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + redirectedCout, + redirectedCerr, + aborting())); + + m_activeTestCase = nullptr; + m_testCaseTracker = nullptr; + + return deltaTotals; + } + + IConfigPtr RunContext::config() const { + return m_config; + } + + IStreamingReporter& RunContext::reporter() const { + return *m_reporter; + } + + void RunContext::assertionEnded(AssertionResult const & result) { + if (result.getResultType() == ResultWas::Ok) { + m_totals.assertions.passed++; + m_lastAssertionPassed = true; + } else if (!result.isOk()) { + m_lastAssertionPassed = false; + if( m_activeTestCase->getTestCaseInfo().okToFail() ) + m_totals.assertions.failedButOk++; + else + m_totals.assertions.failed++; + } + else { + m_lastAssertionPassed = true; + } + + // We have no use for the return value (whether messages should be cleared), because messages were made scoped + // and should be let to clear themselves out. + static_cast(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); + + if (result.getResultType() != ResultWas::Warning) + m_messageScopes.clear(); + + // Reset working state + resetAssertionInfo(); + m_lastResult = result; + } + void RunContext::resetAssertionInfo() { + m_lastAssertionInfo.macroName = StringRef(); + m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr; + } + + bool RunContext::sectionStarted(SectionInfo const & sectionInfo, Counts & assertions) { + ITracker& sectionTracker = SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(sectionInfo.name, sectionInfo.lineInfo)); + if (!sectionTracker.isOpen()) + return false; + m_activeSections.push_back(§ionTracker); + + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + m_reporter->sectionStarting(sectionInfo); + + assertions = m_totals.assertions; + + return true; + } + auto RunContext::acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { + using namespace Generators; + GeneratorTracker& tracker = GeneratorTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( "generator", lineInfo ) ); + assert( tracker.isOpen() ); + m_lastAssertionInfo.lineInfo = lineInfo; + return tracker; + } + + bool RunContext::testForMissingAssertions(Counts& assertions) { + if (assertions.total() != 0) + return false; + if (!m_config->warnAboutMissingAssertions()) + return false; + if (m_trackerContext.currentTracker().hasChildren()) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; + } + + void RunContext::sectionEnded(SectionEndInfo const & endInfo) { + Counts assertions = m_totals.assertions - endInfo.prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + + if (!m_activeSections.empty()) { + m_activeSections.back()->close(); + m_activeSections.pop_back(); + } + + m_reporter->sectionEnded(SectionStats(endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions)); + m_messages.clear(); + m_messageScopes.clear(); + } + + void RunContext::sectionEndedEarly(SectionEndInfo const & endInfo) { + if (m_unfinishedSections.empty()) + m_activeSections.back()->fail(); + else + m_activeSections.back()->close(); + m_activeSections.pop_back(); + + m_unfinishedSections.push_back(endInfo); + } + void RunContext::benchmarkStarting( BenchmarkInfo const& info ) { + m_reporter->benchmarkStarting( info ); + } + void RunContext::benchmarkEnded( BenchmarkStats const& stats ) { + m_reporter->benchmarkEnded( stats ); + } + + void RunContext::pushScopedMessage(MessageInfo const & message) { + m_messages.push_back(message); + } + + void RunContext::popScopedMessage(MessageInfo const & message) { + m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end()); + } + + void RunContext::emplaceUnscopedMessage( MessageBuilder const& builder ) { + m_messageScopes.emplace_back( builder ); + } + + std::string RunContext::getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : std::string(); + } + + const AssertionResult * RunContext::getLastResult() const { + return &(*m_lastResult); + } + + void RunContext::exceptionEarlyReported() { + m_shouldReportUnexpected = false; + } + + void RunContext::handleFatalErrorCondition( StringRef message ) { + // First notify reporter that bad things happened + m_reporter->fatalErrorEncountered(message); + + // Don't rebuild the result -- the stringification itself can cause more fatal errors + // Instead, fake a result data. + AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } ); + tempResult.message = message; + AssertionResult result(m_lastAssertionInfo, tempResult); + + assertionEnded(result); + + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats(testCaseSection, assertions, 0, false); + m_reporter->sectionEnded(testCaseSectionStats); + + auto const& testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + deltaTotals.assertions.failed = 1; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + std::string(), + std::string(), + false)); + m_totals.testCases.failed++; + testGroupEnded(std::string(), m_totals, 1, 1); + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); + } + + bool RunContext::lastAssertionPassed() { + return m_lastAssertionPassed; + } + + void RunContext::assertionPassed() { + m_lastAssertionPassed = true; + ++m_totals.assertions.passed; + resetAssertionInfo(); + m_messageScopes.clear(); + } + + bool RunContext::aborting() const { + return m_totals.assertions.failed >= static_cast(m_config->abortAfter()); + } + + void RunContext::runCurrentTest(std::string & redirectedCout, std::string & redirectedCerr) { + auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); + m_reporter->sectionStarting(testCaseSection); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + m_shouldReportUnexpected = true; + m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal }; + + seedRng(*m_config); + + Timer timer; + CATCH_TRY { + if (m_reporter->getPreferences().shouldRedirectStdOut) { +#if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) + RedirectedStreams redirectedStreams(redirectedCout, redirectedCerr); + + timer.start(); + invokeActiveTestCase(); +#else + OutputRedirect r(redirectedCout, redirectedCerr); + timer.start(); + invokeActiveTestCase(); +#endif + } else { + timer.start(); + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } CATCH_CATCH_ANON (TestFailureException&) { + // This just means the test was aborted due to failure + } CATCH_CATCH_ALL { + // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions + // are reported without translation at the point of origin. + if( m_shouldReportUnexpected ) { + AssertionReaction dummyReaction; + handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), dummyReaction ); + } + } + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + + m_testCaseTracker->close(); + handleUnfinishedSections(); + m_messages.clear(); + m_messageScopes.clear(); + + SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions); + m_reporter->sectionEnded(testCaseSectionStats); + } + + void RunContext::invokeActiveTestCase() { + FatalConditionHandler fatalConditionHandler; // Handle signals + m_activeTestCase->invoke(); + fatalConditionHandler.reset(); + } + + void RunContext::handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for (auto it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it) + sectionEnded(*it); + m_unfinishedSections.clear(); + } + + void RunContext::handleExpr( + AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction + ) { + m_reporter->assertionStarting( info ); + + bool negated = isFalseTest( info.resultDisposition ); + bool result = expr.getResult() != negated; + + if( result ) { + if (!m_includeSuccessfulResults) { + assertionPassed(); + } + else { + reportExpr(info, ResultWas::Ok, &expr, negated); + } + } + else { + reportExpr(info, ResultWas::ExpressionFailed, &expr, negated ); + populateReaction( reaction ); + } + } + void RunContext::reportExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated ) { + + m_lastAssertionInfo = info; + AssertionResultData data( resultType, LazyExpression( negated ) ); + + AssertionResult assertionResult{ info, data }; + assertionResult.m_resultData.lazyExpression.m_transientExpression = expr; + + assertionEnded( assertionResult ); + } + + void RunContext::handleMessage( + AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction + ) { + m_reporter->assertionStarting( info ); + + m_lastAssertionInfo = info; + + AssertionResultData data( resultType, LazyExpression( false ) ); + data.message = message; + AssertionResult assertionResult{ m_lastAssertionInfo, data }; + assertionEnded( assertionResult ); + if( !assertionResult.isOk() ) + populateReaction( reaction ); + } + void RunContext::handleUnexpectedExceptionNotThrown( + AssertionInfo const& info, + AssertionReaction& reaction + ) { + handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction); + } + + void RunContext::handleUnexpectedInflightException( + AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); + data.message = message; + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + populateReaction( reaction ); + } + + void RunContext::populateReaction( AssertionReaction& reaction ) { + reaction.shouldDebugBreak = m_config->shouldDebugBreak(); + reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal); + } + + void RunContext::handleIncomplete( + AssertionInfo const& info + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); + data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"; + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + } + void RunContext::handleNonExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( resultType, LazyExpression( false ) ); + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + + if( !assertionResult.isOk() ) + populateReaction( reaction ); + } + + IResultCapture& getResultCapture() { + if (auto* capture = getCurrentContext().getResultCapture()) + return *capture; + else + CATCH_INTERNAL_ERROR("No result capture instance"); + } +} +// end catch_run_context.cpp +// start catch_section.cpp + +namespace Catch { + + Section::Section( SectionInfo const& info ) + : m_info( info ), + m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) + { + m_timer.start(); + } + + Section::~Section() { + if( m_sectionIncluded ) { + SectionEndInfo endInfo{ m_info, m_assertions, m_timer.getElapsedSeconds() }; + if( uncaught_exceptions() ) + getResultCapture().sectionEndedEarly( endInfo ); + else + getResultCapture().sectionEnded( endInfo ); + } + } + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + +} // end namespace Catch +// end catch_section.cpp +// start catch_section_info.cpp + +namespace Catch { + + SectionInfo::SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name ) + : name( _name ), + lineInfo( _lineInfo ) + {} + +} // end namespace Catch +// end catch_section_info.cpp +// start catch_session.cpp + +// start catch_session.h + +#include + +namespace Catch { + + class Session : NonCopyable { + public: + + Session(); + ~Session() override; + + void showHelp() const; + void libIdentify(); + + int applyCommandLine( int argc, char const * const * argv ); + #if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE) + int applyCommandLine( int argc, wchar_t const * const * argv ); + #endif + + void useConfigData( ConfigData const& configData ); + + template + int run(int argc, CharT const * const argv[]) { + if (m_startupExceptions) + return 1; + int returnCode = applyCommandLine(argc, argv); + if (returnCode == 0) + returnCode = run(); + return returnCode; + } + + int run(); + + clara::Parser const& cli() const; + void cli( clara::Parser const& newParser ); + ConfigData& configData(); + Config& config(); + private: + int runInternal(); + + clara::Parser m_cli; + ConfigData m_configData; + std::shared_ptr m_config; + bool m_startupExceptions = false; + }; + +} // end namespace Catch + +// end catch_session.h +// start catch_version.h + +#include + +namespace Catch { + + // Versioning information + struct Version { + Version( Version const& ) = delete; + Version& operator=( Version const& ) = delete; + Version( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ); + + unsigned int const majorVersion; + unsigned int const minorVersion; + unsigned int const patchNumber; + + // buildNumber is only used if branchName is not null + char const * const branchName; + unsigned int const buildNumber; + + friend std::ostream& operator << ( std::ostream& os, Version const& version ); + }; + + Version const& libraryVersion(); +} + +// end catch_version.h +#include +#include + +namespace Catch { + + namespace { + const int MaxExitCode = 255; + + IStreamingReporterPtr createReporter(std::string const& reporterName, IConfigPtr const& config) { + auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config); + CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << "'"); + + return reporter; + } + + IStreamingReporterPtr makeReporter(std::shared_ptr const& config) { + if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()) { + return createReporter(config->getReporterName(), config); + } + + // On older platforms, returning std::unique_ptr + // when the return type is std::unique_ptr + // doesn't compile without a std::move call. However, this causes + // a warning on newer platforms. Thus, we have to work around + // it a bit and downcast the pointer manually. + auto ret = std::unique_ptr(new ListeningReporter); + auto& multi = static_cast(*ret); + auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); + for (auto const& listener : listeners) { + multi.addListener(listener->create(Catch::ReporterConfig(config))); + } + multi.addReporter(createReporter(config->getReporterName(), config)); + return ret; + } + + Catch::Totals runTests(std::shared_ptr const& config) { + auto reporter = makeReporter(config); + + RunContext context(config, std::move(reporter)); + + Totals totals; + + context.testGroupStarting(config->name(), 1, 1); + + TestSpec testSpec = config->testSpec(); + + auto const& allTestCases = getAllTestCasesSorted(*config); + for (auto const& testCase : allTestCases) { + if (!context.aborting() && matchTest(testCase, testSpec, *config)) + totals += context.runTest(testCase); + else + context.reporter().skipTest(testCase); + } + + if (config->warnAboutNoTests() && totals.testCases.total() == 0) { + ReusableStringStream testConfig; + + bool first = true; + for (const auto& input : config->getTestsOrTags()) { + if (!first) { testConfig << ' '; } + first = false; + testConfig << input; + } + + context.reporter().noMatchingTestCases(testConfig.str()); + totals.error = -1; + } + + context.testGroupEnded(config->name(), totals, 1, 1); + return totals; + } + + void applyFilenamesAsTags(Catch::IConfig const& config) { + auto& tests = const_cast&>(getAllTestCasesSorted(config)); + for (auto& testCase : tests) { + auto tags = testCase.tags; + + std::string filename = testCase.lineInfo.file; + auto lastSlash = filename.find_last_of("\\/"); + if (lastSlash != std::string::npos) { + filename.erase(0, lastSlash); + filename[0] = '#'; + } + + auto lastDot = filename.find_last_of('.'); + if (lastDot != std::string::npos) { + filename.erase(lastDot); + } + + tags.push_back(std::move(filename)); + setTags(testCase, tags); + } + } + + } // anon namespace + + Session::Session() { + static bool alreadyInstantiated = false; + if( alreadyInstantiated ) { + CATCH_TRY { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); } + CATCH_CATCH_ALL { getMutableRegistryHub().registerStartupException(); } + } + + // There cannot be exceptions at startup in no-exception mode. +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); + if ( !exceptions.empty() ) { + m_startupExceptions = true; + Colour colourGuard( Colour::Red ); + Catch::cerr() << "Errors occurred during startup!" << '\n'; + // iterate over all exceptions and notify user + for ( const auto& ex_ptr : exceptions ) { + try { + std::rethrow_exception(ex_ptr); + } catch ( std::exception const& ex ) { + Catch::cerr() << Column( ex.what() ).indent(2) << '\n'; + } + } + } +#endif + + alreadyInstantiated = true; + m_cli = makeCommandLineParser( m_configData ); + } + Session::~Session() { + Catch::cleanUp(); + } + + void Session::showHelp() const { + Catch::cout() + << "\nCatch v" << libraryVersion() << "\n" + << m_cli << std::endl + << "For more detailed usage please see the project docs\n" << std::endl; + } + void Session::libIdentify() { + Catch::cout() + << std::left << std::setw(16) << "description: " << "A Catch test executable\n" + << std::left << std::setw(16) << "category: " << "testframework\n" + << std::left << std::setw(16) << "framework: " << "Catch Test\n" + << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl; + } + + int Session::applyCommandLine( int argc, char const * const * argv ) { + if( m_startupExceptions ) + return 1; + + auto result = m_cli.parse( clara::Args( argc, argv ) ); + if( !result ) { + config(); + getCurrentMutableContext().setConfig(m_config); + Catch::cerr() + << Colour( Colour::Red ) + << "\nError(s) in input:\n" + << Column( result.errorMessage() ).indent( 2 ) + << "\n\n"; + Catch::cerr() << "Run with -? for usage\n" << std::endl; + return MaxExitCode; + } + + if( m_configData.showHelp ) + showHelp(); + if( m_configData.libIdentify ) + libIdentify(); + m_config.reset(); + return 0; + } + +#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE) + int Session::applyCommandLine( int argc, wchar_t const * const * argv ) { + + char **utf8Argv = new char *[ argc ]; + + for ( int i = 0; i < argc; ++i ) { + int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL ); + + utf8Argv[ i ] = new char[ bufSize ]; + + WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL ); + } + + int returnCode = applyCommandLine( argc, utf8Argv ); + + for ( int i = 0; i < argc; ++i ) + delete [] utf8Argv[ i ]; + + delete [] utf8Argv; + + return returnCode; + } +#endif + + void Session::useConfigData( ConfigData const& configData ) { + m_configData = configData; + m_config.reset(); + } + + int Session::run() { + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before starting" << std::endl; + static_cast(std::getchar()); + } + int exitCode = runInternal(); + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl; + static_cast(std::getchar()); + } + return exitCode; + } + + clara::Parser const& Session::cli() const { + return m_cli; + } + void Session::cli( clara::Parser const& newParser ) { + m_cli = newParser; + } + ConfigData& Session::configData() { + return m_configData; + } + Config& Session::config() { + if( !m_config ) + m_config = std::make_shared( m_configData ); + return *m_config; + } + + int Session::runInternal() { + if( m_startupExceptions ) + return 1; + + if (m_configData.showHelp || m_configData.libIdentify) { + return 0; + } + + CATCH_TRY { + config(); // Force config to be constructed + + seedRng( *m_config ); + + if( m_configData.filenamesAsTags ) + applyFilenamesAsTags( *m_config ); + + // Handle list request + if( Option listed = list( m_config ) ) + return static_cast( *listed ); + + auto totals = runTests( m_config ); + // Note that on unices only the lower 8 bits are usually used, clamping + // the return value to 255 prevents false negative when some multiple + // of 256 tests has failed + return (std::min) (MaxExitCode, (std::max) (totals.error, static_cast(totals.assertions.failed))); + } +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + catch( std::exception& ex ) { + Catch::cerr() << ex.what() << std::endl; + return MaxExitCode; + } +#endif + } + +} // end namespace Catch +// end catch_session.cpp +// start catch_singletons.cpp + +#include + +namespace Catch { + + namespace { + static auto getSingletons() -> std::vector*& { + static std::vector* g_singletons = nullptr; + if( !g_singletons ) + g_singletons = new std::vector(); + return g_singletons; + } + } + + ISingleton::~ISingleton() {} + + void addSingleton(ISingleton* singleton ) { + getSingletons()->push_back( singleton ); + } + void cleanupSingletons() { + auto& singletons = getSingletons(); + for( auto singleton : *singletons ) + delete singleton; + delete singletons; + singletons = nullptr; + } + +} // namespace Catch +// end catch_singletons.cpp +// start catch_startup_exception_registry.cpp + +namespace Catch { +void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept { + CATCH_TRY { + m_exceptions.push_back(exception); + } CATCH_CATCH_ALL { + // If we run out of memory during start-up there's really not a lot more we can do about it + std::terminate(); + } + } + + std::vector const& StartupExceptionRegistry::getExceptions() const noexcept { + return m_exceptions; + } + +} // end namespace Catch +// end catch_startup_exception_registry.cpp +// start catch_stream.cpp + +#include +#include +#include +#include +#include +#include + +namespace Catch { + + Catch::IStream::~IStream() = default; + + namespace detail { namespace { + template + class StreamBufImpl : public std::streambuf { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() noexcept { + StreamBufImpl::sync(); + } + + private: + int overflow( int c ) override { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast( c ) ) ); + else + sputc( static_cast( c ) ); + } + return 0; + } + + int sync() override { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + struct OutputDebugWriter { + + void operator()( std::string const&str ) { + writeToDebugConsole( str ); + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class FileStream : public IStream { + mutable std::ofstream m_ofs; + public: + FileStream( StringRef filename ) { + m_ofs.open( filename.c_str() ); + CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << "'" ); + } + ~FileStream() override = default; + public: // IStream + std::ostream& stream() const override { + return m_ofs; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class CoutStream : public IStream { + mutable std::ostream m_os; + public: + // Store the streambuf from cout up-front because + // cout may get redirected when running tests + CoutStream() : m_os( Catch::cout().rdbuf() ) {} + ~CoutStream() override = default; + + public: // IStream + std::ostream& stream() const override { return m_os; } + }; + + /////////////////////////////////////////////////////////////////////////// + + class DebugOutStream : public IStream { + std::unique_ptr> m_streamBuf; + mutable std::ostream m_os; + public: + DebugOutStream() + : m_streamBuf( new StreamBufImpl() ), + m_os( m_streamBuf.get() ) + {} + + ~DebugOutStream() override = default; + + public: // IStream + std::ostream& stream() const override { return m_os; } + }; + + }} // namespace anon::detail + + /////////////////////////////////////////////////////////////////////////// + + auto makeStream( StringRef const &filename ) -> IStream const* { + if( filename.empty() ) + return new detail::CoutStream(); + else if( filename[0] == '%' ) { + if( filename == "%debug" ) + return new detail::DebugOutStream(); + else + CATCH_ERROR( "Unrecognised stream: '" << filename << "'" ); + } + else + return new detail::FileStream( filename ); + } + + // This class encapsulates the idea of a pool of ostringstreams that can be reused. + struct StringStreams { + std::vector> m_streams; + std::vector m_unused; + std::ostringstream m_referenceStream; // Used for copy state/ flags from + + auto add() -> std::size_t { + if( m_unused.empty() ) { + m_streams.push_back( std::unique_ptr( new std::ostringstream ) ); + return m_streams.size()-1; + } + else { + auto index = m_unused.back(); + m_unused.pop_back(); + return index; + } + } + + void release( std::size_t index ) { + m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state + m_unused.push_back(index); + } + }; + + ReusableStringStream::ReusableStringStream() + : m_index( Singleton::getMutable().add() ), + m_oss( Singleton::getMutable().m_streams[m_index].get() ) + {} + + ReusableStringStream::~ReusableStringStream() { + static_cast( m_oss )->str(""); + m_oss->clear(); + Singleton::getMutable().release( m_index ); + } + + auto ReusableStringStream::str() const -> std::string { + return static_cast( m_oss )->str(); + } + + /////////////////////////////////////////////////////////////////////////// + +#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions + std::ostream& cout() { return std::cout; } + std::ostream& cerr() { return std::cerr; } + std::ostream& clog() { return std::clog; } +#endif +} +// end catch_stream.cpp +// start catch_string_manip.cpp + +#include +#include +#include +#include + +namespace Catch { + + namespace { + char toLowerCh(char c) { + return static_cast( std::tolower( c ) ); + } + } + + bool startsWith( std::string const& s, std::string const& prefix ) { + return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); + } + bool startsWith( std::string const& s, char prefix ) { + return !s.empty() && s[0] == prefix; + } + bool endsWith( std::string const& s, std::string const& suffix ) { + return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); + } + bool endsWith( std::string const& s, char suffix ) { + return !s.empty() && s[s.size()-1] == suffix; + } + bool contains( std::string const& s, std::string const& infix ) { + return s.find( infix ) != std::string::npos; + } + void toLowerInPlace( std::string& s ) { + std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); + } + std::string toLower( std::string const& s ) { + std::string lc = s; + toLowerInPlace( lc ); + return lc; + } + std::string trim( std::string const& str ) { + static char const* whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of( whitespaceChars ); + std::string::size_type end = str.find_last_not_of( whitespaceChars ); + + return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string(); + } + + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { + bool replaced = false; + std::size_t i = str.find( replaceThis ); + while( i != std::string::npos ) { + replaced = true; + str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); + if( i < str.size()-withThis.size() ) + i = str.find( replaceThis, i+withThis.size() ); + else + i = std::string::npos; + } + return replaced; + } + + pluralise::pluralise( std::size_t count, std::string const& label ) + : m_count( count ), + m_label( label ) + {} + + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { + os << pluraliser.m_count << ' ' << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << 's'; + return os; + } + +} +// end catch_string_manip.cpp +// start catch_stringref.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +#include +#include +#include + +namespace { + const uint32_t byte_2_lead = 0xC0; + const uint32_t byte_3_lead = 0xE0; + const uint32_t byte_4_lead = 0xF0; +} + +namespace Catch { + StringRef::StringRef( char const* rawChars ) noexcept + : StringRef( rawChars, static_cast(std::strlen(rawChars) ) ) + {} + + StringRef::operator std::string() const { + return std::string( m_start, m_size ); + } + + void StringRef::swap( StringRef& other ) noexcept { + std::swap( m_start, other.m_start ); + std::swap( m_size, other.m_size ); + std::swap( m_data, other.m_data ); + } + + auto StringRef::c_str() const -> char const* { + if( isSubstring() ) + const_cast( this )->takeOwnership(); + return m_start; + } + auto StringRef::currentData() const noexcept -> char const* { + return m_start; + } + + auto StringRef::isOwned() const noexcept -> bool { + return m_data != nullptr; + } + auto StringRef::isSubstring() const noexcept -> bool { + return m_start[m_size] != '\0'; + } + + void StringRef::takeOwnership() { + if( !isOwned() ) { + m_data = new char[m_size+1]; + memcpy( m_data, m_start, m_size ); + m_data[m_size] = '\0'; + m_start = m_data; + } + } + auto StringRef::substr( size_type start, size_type size ) const noexcept -> StringRef { + if( start < m_size ) + return StringRef( m_start+start, size ); + else + return StringRef(); + } + auto StringRef::operator == ( StringRef const& other ) const noexcept -> bool { + return + size() == other.size() && + (std::strncmp( m_start, other.m_start, size() ) == 0); + } + auto StringRef::operator != ( StringRef const& other ) const noexcept -> bool { + return !operator==( other ); + } + + auto StringRef::operator[](size_type index) const noexcept -> char { + return m_start[index]; + } + + auto StringRef::numberOfCharacters() const noexcept -> size_type { + size_type noChars = m_size; + // Make adjustments for uft encodings + for( size_type i=0; i < m_size; ++i ) { + char c = m_start[i]; + if( ( c & byte_2_lead ) == byte_2_lead ) { + noChars--; + if (( c & byte_3_lead ) == byte_3_lead ) + noChars--; + if( ( c & byte_4_lead ) == byte_4_lead ) + noChars--; + } + } + return noChars; + } + + auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string { + std::string str; + str.reserve( lhs.size() + rhs.size() ); + str += lhs; + str += rhs; + return str; + } + auto operator + ( StringRef const& lhs, const char* rhs ) -> std::string { + return std::string( lhs ) + std::string( rhs ); + } + auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string { + return std::string( lhs ) + std::string( rhs ); + } + + auto operator << ( std::ostream& os, StringRef const& str ) -> std::ostream& { + return os.write(str.currentData(), str.size()); + } + + auto operator+=( std::string& lhs, StringRef const& rhs ) -> std::string& { + lhs.append(rhs.currentData(), rhs.size()); + return lhs; + } + +} // namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +// end catch_stringref.cpp +// start catch_tag_alias.cpp + +namespace Catch { + TagAlias::TagAlias(std::string const & _tag, SourceLineInfo _lineInfo): tag(_tag), lineInfo(_lineInfo) {} +} +// end catch_tag_alias.cpp +// start catch_tag_alias_autoregistrar.cpp + +namespace Catch { + + RegistrarForTagAliases::RegistrarForTagAliases(char const* alias, char const* tag, SourceLineInfo const& lineInfo) { + CATCH_TRY { + getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo); + } CATCH_CATCH_ALL { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } + +} +// end catch_tag_alias_autoregistrar.cpp +// start catch_tag_alias_registry.cpp + +#include + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() {} + + TagAlias const* TagAliasRegistry::find( std::string const& alias ) const { + auto it = m_registry.find( alias ); + if( it != m_registry.end() ) + return &(it->second); + else + return nullptr; + } + + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( auto const& registryKvp : m_registry ) { + std::size_t pos = expandedTestSpec.find( registryKvp.first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + registryKvp.second.tag + + expandedTestSpec.substr( pos + registryKvp.first.size() ); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) { + CATCH_ENFORCE( startsWith(alias, "[@") && endsWith(alias, ']'), + "error: tag alias, '" << alias << "' is not of the form [@alias name].\n" << lineInfo ); + + CATCH_ENFORCE( m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second, + "error: tag alias, '" << alias << "' already registered.\n" + << "\tFirst seen at: " << find(alias)->lineInfo << "\n" + << "\tRedefined at: " << lineInfo ); + } + + ITagAliasRegistry::~ITagAliasRegistry() {} + + ITagAliasRegistry const& ITagAliasRegistry::get() { + return getRegistryHub().getTagAliasRegistry(); + } + +} // end namespace Catch +// end catch_tag_alias_registry.cpp +// start catch_test_case_info.cpp + +#include +#include +#include +#include + +namespace Catch { + + namespace { + TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { + if( startsWith( tag, '.' ) || + tag == "!hide" ) + return TestCaseInfo::IsHidden; + else if( tag == "!throws" ) + return TestCaseInfo::Throws; + else if( tag == "!shouldfail" ) + return TestCaseInfo::ShouldFail; + else if( tag == "!mayfail" ) + return TestCaseInfo::MayFail; + else if( tag == "!nonportable" ) + return TestCaseInfo::NonPortable; + else if( tag == "!benchmark" ) + return static_cast( TestCaseInfo::Benchmark | TestCaseInfo::IsHidden ); + else + return TestCaseInfo::None; + } + bool isReservedTag( std::string const& tag ) { + return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( static_cast(tag[0]) ); + } + void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { + CATCH_ENFORCE( !isReservedTag(tag), + "Tag name: [" << tag << "] is not allowed.\n" + << "Tag names starting with non alpha-numeric characters are reserved\n" + << _lineInfo ); + } + } + + TestCase makeTestCase( ITestInvoker* _testCase, + std::string const& _className, + NameAndTags const& nameAndTags, + SourceLineInfo const& _lineInfo ) + { + bool isHidden = false; + + // Parse out tags + std::vector tags; + std::string desc, tag; + bool inTag = false; + std::string _descOrTags = nameAndTags.tags; + for (char c : _descOrTags) { + if( !inTag ) { + if( c == '[' ) + inTag = true; + else + desc += c; + } + else { + if( c == ']' ) { + TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); + if( ( prop & TestCaseInfo::IsHidden ) != 0 ) + isHidden = true; + else if( prop == TestCaseInfo::None ) + enforceNotReservedTag( tag, _lineInfo ); + + // Merged hide tags like `[.approvals]` should be added as + // `[.][approvals]`. The `[.]` is added at later point, so + // we only strip the prefix + if (startsWith(tag, '.') && tag.size() > 1) { + tag.erase(0, 1); + } + tags.push_back( tag ); + tag.clear(); + inTag = false; + } + else + tag += c; + } + } + if( isHidden ) { + tags.push_back( "." ); + } + + TestCaseInfo info( nameAndTags.name, _className, desc, tags, _lineInfo ); + return TestCase( _testCase, std::move(info) ); + } + + void setTags( TestCaseInfo& testCaseInfo, std::vector tags ) { + std::sort(begin(tags), end(tags)); + tags.erase(std::unique(begin(tags), end(tags)), end(tags)); + testCaseInfo.lcaseTags.clear(); + + for( auto const& tag : tags ) { + std::string lcaseTag = toLower( tag ); + testCaseInfo.properties = static_cast( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); + testCaseInfo.lcaseTags.push_back( lcaseTag ); + } + testCaseInfo.tags = std::move(tags); + } + + TestCaseInfo::TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::vector const& _tags, + SourceLineInfo const& _lineInfo ) + : name( _name ), + className( _className ), + description( _description ), + lineInfo( _lineInfo ), + properties( None ) + { + setTags( *this, _tags ); + } + + bool TestCaseInfo::isHidden() const { + return ( properties & IsHidden ) != 0; + } + bool TestCaseInfo::throws() const { + return ( properties & Throws ) != 0; + } + bool TestCaseInfo::okToFail() const { + return ( properties & (ShouldFail | MayFail ) ) != 0; + } + bool TestCaseInfo::expectedToFail() const { + return ( properties & (ShouldFail ) ) != 0; + } + + std::string TestCaseInfo::tagsAsString() const { + std::string ret; + // '[' and ']' per tag + std::size_t full_size = 2 * tags.size(); + for (const auto& tag : tags) { + full_size += tag.size(); + } + ret.reserve(full_size); + for (const auto& tag : tags) { + ret.push_back('['); + ret.append(tag); + ret.push_back(']'); + } + + return ret; + } + + TestCase::TestCase( ITestInvoker* testCase, TestCaseInfo&& info ) : TestCaseInfo( std::move(info) ), test( testCase ) {} + + TestCase TestCase::withName( std::string const& _newName ) const { + TestCase other( *this ); + other.name = _newName; + return other; + } + + void TestCase::invoke() const { + test->invoke(); + } + + bool TestCase::operator == ( TestCase const& other ) const { + return test.get() == other.test.get() && + name == other.name && + className == other.className; + } + + bool TestCase::operator < ( TestCase const& other ) const { + return name < other.name; + } + + TestCaseInfo const& TestCase::getTestCaseInfo() const + { + return *this; + } + +} // end namespace Catch +// end catch_test_case_info.cpp +// start catch_test_case_registry_impl.cpp + +#include + +namespace Catch { + + std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ) { + + std::vector sorted = unsortedTestCases; + + switch( config.runOrder() ) { + case RunTests::InLexicographicalOrder: + std::sort( sorted.begin(), sorted.end() ); + break; + case RunTests::InRandomOrder: + seedRng( config ); + std::shuffle( sorted.begin(), sorted.end(), rng() ); + break; + case RunTests::InDeclarationOrder: + // already in declaration order + break; + } + return sorted; + } + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { + return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); + } + + void enforceNoDuplicateTestCases( std::vector const& functions ) { + std::set seenFunctions; + for( auto const& function : functions ) { + auto prev = seenFunctions.insert( function ); + CATCH_ENFORCE( prev.second, + "error: TEST_CASE( \"" << function.name << "\" ) already defined.\n" + << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" + << "\tRedefined at " << function.getTestCaseInfo().lineInfo ); + } + } + + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ) { + std::vector filtered; + filtered.reserve( testCases.size() ); + for( auto const& testCase : testCases ) + if( matchTest( testCase, testSpec, config ) ) + filtered.push_back( testCase ); + return filtered; + } + std::vector const& getAllTestCasesSorted( IConfig const& config ) { + return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); + } + + void TestRegistry::registerTest( TestCase const& testCase ) { + std::string name = testCase.getTestCaseInfo().name; + if( name.empty() ) { + ReusableStringStream rss; + rss << "Anonymous test case " << ++m_unnamedCount; + return registerTest( testCase.withName( rss.str() ) ); + } + m_functions.push_back( testCase ); + } + + std::vector const& TestRegistry::getAllTests() const { + return m_functions; + } + std::vector const& TestRegistry::getAllTestsSorted( IConfig const& config ) const { + if( m_sortedFunctions.empty() ) + enforceNoDuplicateTestCases( m_functions ); + + if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { + m_sortedFunctions = sortTests( config, m_functions ); + m_currentSortOrder = config.runOrder(); + } + return m_sortedFunctions; + } + + /////////////////////////////////////////////////////////////////////////// + TestInvokerAsFunction::TestInvokerAsFunction( void(*testAsFunction)() ) noexcept : m_testAsFunction( testAsFunction ) {} + + void TestInvokerAsFunction::invoke() const { + m_testAsFunction(); + } + + std::string extractClassName( StringRef const& classOrQualifiedMethodName ) { + std::string className = classOrQualifiedMethodName; + if( startsWith( className, '&' ) ) + { + std::size_t lastColons = className.rfind( "::" ); + std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); + if( penultimateColons == std::string::npos ) + penultimateColons = 1; + className = className.substr( penultimateColons, lastColons-penultimateColons ); + } + return className; + } + +} // end namespace Catch +// end catch_test_case_registry_impl.cpp +// start catch_test_case_tracker.cpp + +#include +#include +#include +#include +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +namespace Catch { +namespace TestCaseTracking { + + NameAndLocation::NameAndLocation( std::string const& _name, SourceLineInfo const& _location ) + : name( _name ), + location( _location ) + {} + + ITracker::~ITracker() = default; + + TrackerContext& TrackerContext::instance() { + static TrackerContext s_instance; + return s_instance; + } + + ITracker& TrackerContext::startRun() { + m_rootTracker = std::make_shared( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, nullptr ); + m_currentTracker = nullptr; + m_runState = Executing; + return *m_rootTracker; + } + + void TrackerContext::endRun() { + m_rootTracker.reset(); + m_currentTracker = nullptr; + m_runState = NotStarted; + } + + void TrackerContext::startCycle() { + m_currentTracker = m_rootTracker.get(); + m_runState = Executing; + } + void TrackerContext::completeCycle() { + m_runState = CompletedCycle; + } + + bool TrackerContext::completedCycle() const { + return m_runState == CompletedCycle; + } + ITracker& TrackerContext::currentTracker() { + return *m_currentTracker; + } + void TrackerContext::setCurrentTracker( ITracker* tracker ) { + m_currentTracker = tracker; + } + + TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : m_nameAndLocation( nameAndLocation ), + m_ctx( ctx ), + m_parent( parent ) + {} + + NameAndLocation const& TrackerBase::nameAndLocation() const { + return m_nameAndLocation; + } + bool TrackerBase::isComplete() const { + return m_runState == CompletedSuccessfully || m_runState == Failed; + } + bool TrackerBase::isSuccessfullyCompleted() const { + return m_runState == CompletedSuccessfully; + } + bool TrackerBase::isOpen() const { + return m_runState != NotStarted && !isComplete(); + } + bool TrackerBase::hasChildren() const { + return !m_children.empty(); + } + + void TrackerBase::addChild( ITrackerPtr const& child ) { + m_children.push_back( child ); + } + + ITrackerPtr TrackerBase::findChild( NameAndLocation const& nameAndLocation ) { + auto it = std::find_if( m_children.begin(), m_children.end(), + [&nameAndLocation]( ITrackerPtr const& tracker ){ + return + tracker->nameAndLocation().location == nameAndLocation.location && + tracker->nameAndLocation().name == nameAndLocation.name; + } ); + return( it != m_children.end() ) + ? *it + : nullptr; + } + ITracker& TrackerBase::parent() { + assert( m_parent ); // Should always be non-null except for root + return *m_parent; + } + + void TrackerBase::openChild() { + if( m_runState != ExecutingChildren ) { + m_runState = ExecutingChildren; + if( m_parent ) + m_parent->openChild(); + } + } + + bool TrackerBase::isSectionTracker() const { return false; } + bool TrackerBase::isGeneratorTracker() const { return false; } + + void TrackerBase::open() { + m_runState = Executing; + moveToThis(); + if( m_parent ) + m_parent->openChild(); + } + + void TrackerBase::close() { + + // Close any still open children (e.g. generators) + while( &m_ctx.currentTracker() != this ) + m_ctx.currentTracker().close(); + + switch( m_runState ) { + case NeedsAnotherRun: + break; + + case Executing: + m_runState = CompletedSuccessfully; + break; + case ExecutingChildren: + if( m_children.empty() || m_children.back()->isComplete() ) + m_runState = CompletedSuccessfully; + break; + + case NotStarted: + case CompletedSuccessfully: + case Failed: + CATCH_INTERNAL_ERROR( "Illogical state: " << m_runState ); + + default: + CATCH_INTERNAL_ERROR( "Unknown state: " << m_runState ); + } + moveToParent(); + m_ctx.completeCycle(); + } + void TrackerBase::fail() { + m_runState = Failed; + if( m_parent ) + m_parent->markAsNeedingAnotherRun(); + moveToParent(); + m_ctx.completeCycle(); + } + void TrackerBase::markAsNeedingAnotherRun() { + m_runState = NeedsAnotherRun; + } + + void TrackerBase::moveToParent() { + assert( m_parent ); + m_ctx.setCurrentTracker( m_parent ); + } + void TrackerBase::moveToThis() { + m_ctx.setCurrentTracker( this ); + } + + SectionTracker::SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( nameAndLocation, ctx, parent ) + { + if( parent ) { + while( !parent->isSectionTracker() ) + parent = &parent->parent(); + + SectionTracker& parentSection = static_cast( *parent ); + addNextFilters( parentSection.m_filters ); + } + } + + bool SectionTracker::isComplete() const { + bool complete = true; + + if ((m_filters.empty() || m_filters[0] == "") || + std::find(m_filters.begin(), m_filters.end(), + m_nameAndLocation.name) != m_filters.end()) + complete = TrackerBase::isComplete(); + return complete; + + } + + bool SectionTracker::isSectionTracker() const { return true; } + + SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) { + std::shared_ptr section; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isSectionTracker() ); + section = std::static_pointer_cast( childTracker ); + } + else { + section = std::make_shared( nameAndLocation, ctx, ¤tTracker ); + currentTracker.addChild( section ); + } + if( !ctx.completedCycle() ) + section->tryOpen(); + return *section; + } + + void SectionTracker::tryOpen() { + if( !isComplete() && (m_filters.empty() || m_filters[0].empty() || m_filters[0] == m_nameAndLocation.name ) ) + open(); + } + + void SectionTracker::addInitialFilters( std::vector const& filters ) { + if( !filters.empty() ) { + m_filters.push_back(""); // Root - should never be consulted + m_filters.push_back(""); // Test Case - not a section filter + m_filters.insert( m_filters.end(), filters.begin(), filters.end() ); + } + } + void SectionTracker::addNextFilters( std::vector const& filters ) { + if( filters.size() > 1 ) + m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() ); + } + +} // namespace TestCaseTracking + +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; + +} // namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +// end catch_test_case_tracker.cpp +// start catch_test_registry.cpp + +namespace Catch { + + auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker* { + return new(std::nothrow) TestInvokerAsFunction( testAsFunction ); + } + + NameAndTags::NameAndTags( StringRef const& name_ , StringRef const& tags_ ) noexcept : name( name_ ), tags( tags_ ) {} + + AutoReg::AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept { + CATCH_TRY { + getMutableRegistryHub() + .registerTest( + makeTestCase( + invoker, + extractClassName( classOrMethod ), + nameAndTags, + lineInfo)); + } CATCH_CATCH_ALL { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } + + AutoReg::~AutoReg() = default; +} +// end catch_test_registry.cpp +// start catch_test_spec.cpp + +#include +#include +#include +#include + +namespace Catch { + + TestSpec::Pattern::~Pattern() = default; + TestSpec::NamePattern::~NamePattern() = default; + TestSpec::TagPattern::~TagPattern() = default; + TestSpec::ExcludedPattern::~ExcludedPattern() = default; + + TestSpec::NamePattern::NamePattern( std::string const& name ) + : m_wildcardPattern( toLower( name ), CaseSensitive::No ) + {} + bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const { + return m_wildcardPattern.matches( toLower( testCase.name ) ); + } + + TestSpec::TagPattern::TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} + bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const { + return std::find(begin(testCase.lcaseTags), + end(testCase.lcaseTags), + m_tag) != end(testCase.lcaseTags); + } + + TestSpec::ExcludedPattern::ExcludedPattern( PatternPtr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} + bool TestSpec::ExcludedPattern::matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + + bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const { + // All patterns in a filter must match for the filter to be a match + for( auto const& pattern : m_patterns ) { + if( !pattern->matches( testCase ) ) + return false; + } + return true; + } + + bool TestSpec::hasFilters() const { + return !m_filters.empty(); + } + bool TestSpec::matches( TestCaseInfo const& testCase ) const { + // A TestSpec matches if any filter matches + for( auto const& filter : m_filters ) + if( filter.matches( testCase ) ) + return true; + return false; + } +} +// end catch_test_spec.cpp +// start catch_test_spec_parser.cpp + +namespace Catch { + + TestSpecParser::TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} + + TestSpecParser& TestSpecParser::parse( std::string const& arg ) { + m_mode = None; + m_exclusion = false; + m_start = std::string::npos; + m_arg = m_tagAliases->expandAliases( arg ); + m_escapeChars.clear(); + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) + visitChar( m_arg[m_pos] ); + if( m_mode == Name ) + addPattern(); + return *this; + } + TestSpec TestSpecParser::testSpec() { + addFilter(); + return m_testSpec; + } + + void TestSpecParser::visitChar( char c ) { + if( m_mode == None ) { + switch( c ) { + case ' ': return; + case '~': m_exclusion = true; return; + case '[': return startNewMode( Tag, ++m_pos ); + case '"': return startNewMode( QuotedName, ++m_pos ); + case '\\': return escape(); + default: startNewMode( Name, m_pos ); break; + } + } + if( m_mode == Name ) { + if( c == ',' ) { + addPattern(); + addFilter(); + } + else if( c == '[' ) { + if( subString() == "exclude:" ) + m_exclusion = true; + else + addPattern(); + startNewMode( Tag, ++m_pos ); + } + else if( c == '\\' ) + escape(); + } + else if( m_mode == EscapedName ) + m_mode = Name; + else if( m_mode == QuotedName && c == '"' ) + addPattern(); + else if( m_mode == Tag && c == ']' ) + addPattern(); + } + void TestSpecParser::startNewMode( Mode mode, std::size_t start ) { + m_mode = mode; + m_start = start; + } + void TestSpecParser::escape() { + if( m_mode == None ) + m_start = m_pos; + m_mode = EscapedName; + m_escapeChars.push_back( m_pos ); + } + std::string TestSpecParser::subString() const { return m_arg.substr( m_start, m_pos - m_start ); } + + void TestSpecParser::addFilter() { + if( !m_currentFilter.m_patterns.empty() ) { + m_testSpec.m_filters.push_back( m_currentFilter ); + m_currentFilter = TestSpec::Filter(); + } + } + + TestSpec parseTestSpec( std::string const& arg ) { + return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); + } + +} // namespace Catch +// end catch_test_spec_parser.cpp +// start catch_timer.cpp + +#include + +static const uint64_t nanosecondsInSecond = 1000000000; + +namespace Catch { + + auto getCurrentNanosecondsSinceEpoch() -> uint64_t { + return std::chrono::duration_cast( std::chrono::high_resolution_clock::now().time_since_epoch() ).count(); + } + + namespace { + auto estimateClockResolution() -> uint64_t { + uint64_t sum = 0; + static const uint64_t iterations = 1000000; + + auto startTime = getCurrentNanosecondsSinceEpoch(); + + for( std::size_t i = 0; i < iterations; ++i ) { + + uint64_t ticks; + uint64_t baseTicks = getCurrentNanosecondsSinceEpoch(); + do { + ticks = getCurrentNanosecondsSinceEpoch(); + } while( ticks == baseTicks ); + + auto delta = ticks - baseTicks; + sum += delta; + + // If we have been calibrating for over 3 seconds -- the clock + // is terrible and we should move on. + // TBD: How to signal that the measured resolution is probably wrong? + if (ticks > startTime + 3 * nanosecondsInSecond) { + return sum / ( i + 1u ); + } + } + + // We're just taking the mean, here. To do better we could take the std. dev and exclude outliers + // - and potentially do more iterations if there's a high variance. + return sum/iterations; + } + } + auto getEstimatedClockResolution() -> uint64_t { + static auto s_resolution = estimateClockResolution(); + return s_resolution; + } + + void Timer::start() { + m_nanoseconds = getCurrentNanosecondsSinceEpoch(); + } + auto Timer::getElapsedNanoseconds() const -> uint64_t { + return getCurrentNanosecondsSinceEpoch() - m_nanoseconds; + } + auto Timer::getElapsedMicroseconds() const -> uint64_t { + return getElapsedNanoseconds()/1000; + } + auto Timer::getElapsedMilliseconds() const -> unsigned int { + return static_cast(getElapsedMicroseconds()/1000); + } + auto Timer::getElapsedSeconds() const -> double { + return getElapsedMicroseconds()/1000000.0; + } + +} // namespace Catch +// end catch_timer.cpp +// start catch_tostring.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +# pragma clang diagnostic ignored "-Wglobal-constructors" +#endif + +// Enable specific decls locally +#if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +#endif + +#include +#include + +namespace Catch { + +namespace Detail { + + const std::string unprintableString = "{?}"; + + namespace { + const int hexThreshold = 255; + + struct Endianness { + enum Arch { Big, Little }; + + static Arch which() { + union _{ + int asInt; + char asChar[sizeof (int)]; + } u; + + u.asInt = 1; + return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; + } + }; + } + + std::string rawMemoryToString( const void *object, std::size_t size ) { + // Reverse order for little endian architectures + int i = 0, end = static_cast( size ), inc = 1; + if( Endianness::which() == Endianness::Little ) { + i = end-1; + end = inc = -1; + } + + unsigned char const *bytes = static_cast(object); + ReusableStringStream rss; + rss << "0x" << std::setfill('0') << std::hex; + for( ; i != end; i += inc ) + rss << std::setw(2) << static_cast(bytes[i]); + return rss.str(); + } +} + +template +std::string fpToString( T value, int precision ) { + if (Catch::isnan(value)) { + return "nan"; + } + + ReusableStringStream rss; + rss << std::setprecision( precision ) + << std::fixed + << value; + std::string d = rss.str(); + std::size_t i = d.find_last_not_of( '0' ); + if( i != std::string::npos && i != d.size()-1 ) { + if( d[i] == '.' ) + i++; + d = d.substr( 0, i+1 ); + } + return d; +} + +//// ======================================================= //// +// +// Out-of-line defs for full specialization of StringMaker +// +//// ======================================================= //// + +std::string StringMaker::convert(const std::string& str) { + if (!getCurrentContext().getConfig()->showInvisibles()) { + return '"' + str + '"'; + } + + std::string s("\""); + for (char c : str) { + switch (c) { + case '\n': + s.append("\\n"); + break; + case '\t': + s.append("\\t"); + break; + default: + s.push_back(c); + break; + } + } + s.append("\""); + return s; +} + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +std::string StringMaker::convert(std::string_view str) { + return ::Catch::Detail::stringify(std::string{ str }); +} +#endif + +std::string StringMaker::convert(char const* str) { + if (str) { + return ::Catch::Detail::stringify(std::string{ str }); + } else { + return{ "{null string}" }; + } +} +std::string StringMaker::convert(char* str) { + if (str) { + return ::Catch::Detail::stringify(std::string{ str }); + } else { + return{ "{null string}" }; + } +} + +#ifdef CATCH_CONFIG_WCHAR +std::string StringMaker::convert(const std::wstring& wstr) { + std::string s; + s.reserve(wstr.size()); + for (auto c : wstr) { + s += (c <= 0xff) ? static_cast(c) : '?'; + } + return ::Catch::Detail::stringify(s); +} + +# ifdef CATCH_CONFIG_CPP17_STRING_VIEW +std::string StringMaker::convert(std::wstring_view str) { + return StringMaker::convert(std::wstring(str)); +} +# endif + +std::string StringMaker::convert(wchar_t const * str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{ str }); + } else { + return{ "{null string}" }; + } +} +std::string StringMaker::convert(wchar_t * str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{ str }); + } else { + return{ "{null string}" }; + } +} +#endif + +std::string StringMaker::convert(int value) { + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(long value) { + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(long long value) { + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) { + rss << " (0x" << std::hex << value << ')'; + } + return rss.str(); +} + +std::string StringMaker::convert(unsigned int value) { + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(unsigned long value) { + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(unsigned long long value) { + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) { + rss << " (0x" << std::hex << value << ')'; + } + return rss.str(); +} + +std::string StringMaker::convert(bool b) { + return b ? "true" : "false"; +} + +std::string StringMaker::convert(signed char value) { + if (value == '\r') { + return "'\\r'"; + } else if (value == '\f') { + return "'\\f'"; + } else if (value == '\n') { + return "'\\n'"; + } else if (value == '\t') { + return "'\\t'"; + } else if ('\0' <= value && value < ' ') { + return ::Catch::Detail::stringify(static_cast(value)); + } else { + char chstr[] = "' '"; + chstr[1] = value; + return chstr; + } +} +std::string StringMaker::convert(char c) { + return ::Catch::Detail::stringify(static_cast(c)); +} +std::string StringMaker::convert(unsigned char c) { + return ::Catch::Detail::stringify(static_cast(c)); +} + +std::string StringMaker::convert(std::nullptr_t) { + return "nullptr"; +} + +std::string StringMaker::convert(float value) { + return fpToString(value, 5) + 'f'; +} +std::string StringMaker::convert(double value) { + return fpToString(value, 10); +} + +std::string ratio_string::symbol() { return "a"; } +std::string ratio_string::symbol() { return "f"; } +std::string ratio_string::symbol() { return "p"; } +std::string ratio_string::symbol() { return "n"; } +std::string ratio_string::symbol() { return "u"; } +std::string ratio_string::symbol() { return "m"; } + +} // end namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +// end catch_tostring.cpp +// start catch_totals.cpp + +namespace Catch { + + Counts Counts::operator - ( Counts const& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + return diff; + } + + Counts& Counts::operator += ( Counts const& other ) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + return *this; + } + + std::size_t Counts::total() const { + return passed + failed + failedButOk; + } + bool Counts::allPassed() const { + return failed == 0 && failedButOk == 0; + } + bool Counts::allOk() const { + return failed == 0; + } + + Totals Totals::operator - ( Totals const& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Totals& Totals::operator += ( Totals const& other ) { + assertions += other.assertions; + testCases += other.testCases; + return *this; + } + + Totals Totals::delta( Totals const& prevTotals ) const { + Totals diff = *this - prevTotals; + if( diff.assertions.failed > 0 ) + ++diff.testCases.failed; + else if( diff.assertions.failedButOk > 0 ) + ++diff.testCases.failedButOk; + else + ++diff.testCases.passed; + return diff; + } + +} +// end catch_totals.cpp +// start catch_uncaught_exceptions.cpp + +#include + +namespace Catch { + bool uncaught_exceptions() { +#if defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) + return std::uncaught_exceptions() > 0; +#else + return std::uncaught_exception(); +#endif + } +} // end namespace Catch +// end catch_uncaught_exceptions.cpp +// start catch_version.cpp + +#include + +namespace Catch { + + Version::Version + ( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + patchNumber( _patchNumber ), + branchName( _branchName ), + buildNumber( _buildNumber ) + {} + + std::ostream& operator << ( std::ostream& os, Version const& version ) { + os << version.majorVersion << '.' + << version.minorVersion << '.' + << version.patchNumber; + // branchName is never null -> 0th char is \0 if it is empty + if (version.branchName[0]) { + os << '-' << version.branchName + << '.' << version.buildNumber; + } + return os; + } + + Version const& libraryVersion() { + static Version version( 2, 7, 1, "", 0 ); + return version; + } + +} +// end catch_version.cpp +// start catch_wildcard_pattern.cpp + +#include + +namespace Catch { + + WildcardPattern::WildcardPattern( std::string const& pattern, + CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_pattern( adjustCase( pattern ) ) + { + if( startsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); + m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); + } + } + + bool WildcardPattern::matches( std::string const& str ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_pattern == adjustCase( str ); + case WildcardAtStart: + return endsWith( adjustCase( str ), m_pattern ); + case WildcardAtEnd: + return startsWith( adjustCase( str ), m_pattern ); + case WildcardAtBothEnds: + return contains( adjustCase( str ), m_pattern ); + default: + CATCH_INTERNAL_ERROR( "Unknown enum" ); + } + } + + std::string WildcardPattern::adjustCase( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; + } +} +// end catch_wildcard_pattern.cpp +// start catch_xmlwriter.cpp + +#include + +using uchar = unsigned char; + +namespace Catch { + +namespace { + + size_t trailingBytes(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return 2; + } + if ((c & 0xF0) == 0xE0) { + return 3; + } + if ((c & 0xF8) == 0xF0) { + return 4; + } + CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + uint32_t headerValue(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return c & 0x1F; + } + if ((c & 0xF0) == 0xE0) { + return c & 0x0F; + } + if ((c & 0xF8) == 0xF0) { + return c & 0x07; + } + CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + void hexEscapeChar(std::ostream& os, unsigned char c) { + std::ios_base::fmtflags f(os.flags()); + os << "\\x" + << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast(c); + os.flags(f); + } + +} // anonymous namespace + + XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) + : m_str( str ), + m_forWhat( forWhat ) + {} + + void XmlEncode::encodeTo( std::ostream& os ) const { + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: http://www.w3.org/TR/xml/#syntax) + + for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { + uchar c = m_str[idx]; + switch (c) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: http://www.w3.org/TR/xml/#syntax + if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') + os << ">"; + else + os << c; + break; + + case '\"': + if (m_forWhat == ForAttributes) + os << """; + else + os << c; + break; + + default: + // Check for control characters and invalid utf-8 + + // Escape control characters in standard ascii + // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { + hexEscapeChar(os, c); + break; + } + + // Plain ASCII: Write it to stream + if (c < 0x7F) { + os << c; + break; + } + + // UTF-8 territory + // Check if the encoding is valid and if it is not, hex escape bytes. + // Important: We do not check the exact decoded values for validity, only the encoding format + // First check that this bytes is a valid lead byte: + // This means that it is not encoded as 1111 1XXX + // Or as 10XX XXXX + if (c < 0xC0 || + c >= 0xF8) { + hexEscapeChar(os, c); + break; + } + + auto encBytes = trailingBytes(c); + // Are there enough bytes left to avoid accessing out-of-bounds memory? + if (idx + encBytes - 1 >= m_str.size()) { + hexEscapeChar(os, c); + break; + } + // The header is valid, check data + // The next encBytes bytes must together be a valid utf-8 + // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) + bool valid = true; + uint32_t value = headerValue(c); + for (std::size_t n = 1; n < encBytes; ++n) { + uchar nc = m_str[idx + n]; + valid &= ((nc & 0xC0) == 0x80); + value = (value << 6) | (nc & 0x3F); + } + + if ( + // Wrong bit pattern of following bytes + (!valid) || + // Overlong encodings + (value < 0x80) || + (0x80 <= value && value < 0x800 && encBytes > 2) || + (0x800 < value && value < 0x10000 && encBytes > 3) || + // Encoded value out of range + (value >= 0x110000) + ) { + hexEscapeChar(os, c); + break; + } + + // If we got here, this is in fact a valid(ish) utf-8 sequence + for (std::size_t n = 0; n < encBytes; ++n) { + os << m_str[idx + n]; + } + idx += encBytes - 1; + break; + } + } + } + + std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept + : m_writer( other.m_writer ){ + other.m_writer = nullptr; + } + XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { + if ( m_writer ) { + m_writer->endElement(); + } + m_writer = other.m_writer; + other.m_writer = nullptr; + return *this; + } + + XmlWriter::ScopedElement::~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { + m_writer->writeText( text, indent ); + return *this; + } + + XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) + { + writeDeclaration(); + } + + XmlWriter::~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + + XmlWriter& XmlWriter::startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + m_os << m_indent << '<' << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& XmlWriter::endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + m_os << "/>"; + m_tagIsOpen = false; + } + else { + m_os << m_indent << ""; + } + m_os << std::endl; + m_tags.pop_back(); + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { + m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + m_os << m_indent; + m_os << XmlEncode( text ); + m_needsNewline = true; + } + return *this; + } + + XmlWriter& XmlWriter::writeComment( std::string const& text ) { + ensureTagClosed(); + m_os << m_indent << ""; + m_needsNewline = true; + return *this; + } + + void XmlWriter::writeStylesheetRef( std::string const& url ) { + m_os << "\n"; + } + + XmlWriter& XmlWriter::writeBlankLine() { + ensureTagClosed(); + m_os << '\n'; + return *this; + } + + void XmlWriter::ensureTagClosed() { + if( m_tagIsOpen ) { + m_os << ">" << std::endl; + m_tagIsOpen = false; + } + } + + void XmlWriter::writeDeclaration() { + m_os << "\n"; + } + + void XmlWriter::newlineIfNecessary() { + if( m_needsNewline ) { + m_os << std::endl; + m_needsNewline = false; + } + } +} +// end catch_xmlwriter.cpp +// start catch_reporter_bases.cpp + +#include +#include +#include +#include +#include + +namespace Catch { + void prepareExpandedExpression(AssertionResult& result) { + result.getExpandedExpression(); + } + + // Because formatting using c++ streams is stateful, drop down to C is required + // Alternatively we could use stringstream, but its performance is... not good. + std::string getFormattedDuration( double duration ) { + // Max exponent + 1 is required to represent the whole part + // + 1 for decimal point + // + 3 for the 3 decimal places + // + 1 for null terminator + const std::size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; + char buffer[maxDoubleSize]; + + // Save previous errno, to prevent sprintf from overwriting it + ErrnoGuard guard; +#ifdef _MSC_VER + sprintf_s(buffer, "%.3f", duration); +#else + std::sprintf(buffer, "%.3f", duration); +#endif + return std::string(buffer); + } + + std::string serializeFilters( std::vector const& container ) { + ReusableStringStream oss; + bool first = true; + for (auto&& filter : container) + { + if (!first) + oss << ' '; + else + first = false; + + oss << filter; + } + return oss.str(); + } + + TestEventListenerBase::TestEventListenerBase(ReporterConfig const & _config) + :StreamingReporterBase(_config) {} + + std::set TestEventListenerBase::getSupportedVerbosities() { + return { Verbosity::Quiet, Verbosity::Normal, Verbosity::High }; + } + + void TestEventListenerBase::assertionStarting(AssertionInfo const &) {} + + bool TestEventListenerBase::assertionEnded(AssertionStats const &) { + return false; + } + +} // end namespace Catch +// end catch_reporter_bases.cpp +// start catch_reporter_compact.cpp + +namespace { + +#ifdef CATCH_PLATFORM_MAC + const char* failedString() { return "FAILED"; } + const char* passedString() { return "PASSED"; } +#else + const char* failedString() { return "failed"; } + const char* passedString() { return "passed"; } +#endif + + // Colour::LightGrey + Catch::Colour::Code dimColour() { return Catch::Colour::FileName; } + + std::string bothOrAll( std::size_t count ) { + return count == 1 ? std::string() : + count == 2 ? "both " : "all " ; + } + +} // anon namespace + +namespace Catch { +namespace { +// Colour, message variants: +// - white: No tests ran. +// - red: Failed [both/all] N test cases, failed [both/all] M assertions. +// - white: Passed [both/all] N test cases (no assertions). +// - red: Failed N tests cases, failed M assertions. +// - green: Passed [both/all] N tests cases with M assertions. +void printTotals(std::ostream& out, const Totals& totals) { + if (totals.testCases.total() == 0) { + out << "No tests ran."; + } else if (totals.testCases.failed == totals.testCases.total()) { + Colour colour(Colour::ResultError); + const std::string qualify_assertions_failed = + totals.assertions.failed == totals.assertions.total() ? + bothOrAll(totals.assertions.failed) : std::string(); + out << + "Failed " << bothOrAll(totals.testCases.failed) + << pluralise(totals.testCases.failed, "test case") << ", " + "failed " << qualify_assertions_failed << + pluralise(totals.assertions.failed, "assertion") << '.'; + } else if (totals.assertions.total() == 0) { + out << + "Passed " << bothOrAll(totals.testCases.total()) + << pluralise(totals.testCases.total(), "test case") + << " (no assertions)."; + } else if (totals.assertions.failed) { + Colour colour(Colour::ResultError); + out << + "Failed " << pluralise(totals.testCases.failed, "test case") << ", " + "failed " << pluralise(totals.assertions.failed, "assertion") << '.'; + } else { + Colour colour(Colour::ResultSuccess); + out << + "Passed " << bothOrAll(totals.testCases.passed) + << pluralise(totals.testCases.passed, "test case") << + " with " << pluralise(totals.assertions.passed, "assertion") << '.'; + } +} + +// Implementation of CompactReporter formatting +class AssertionPrinter { +public: + AssertionPrinter& operator= (AssertionPrinter const&) = delete; + AssertionPrinter(AssertionPrinter const&) = delete; + AssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) + : stream(_stream) + , result(_stats.assertionResult) + , messages(_stats.infoMessages) + , itMessage(_stats.infoMessages.begin()) + , printInfoMessages(_printInfoMessages) {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch (result.getResultType()) { + case ResultWas::Ok: + printResultType(Colour::ResultSuccess, passedString()); + printOriginalExpression(); + printReconstructedExpression(); + if (!result.hasExpression()) + printRemainingMessages(Colour::None); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) + printResultType(Colour::ResultSuccess, failedString() + std::string(" - but was ok")); + else + printResultType(Colour::Error, failedString()); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType(Colour::Error, failedString()); + printIssue("unexpected exception with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType(Colour::Error, failedString()); + printIssue("fatal error condition with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType(Colour::Error, failedString()); + printIssue("expected exception, got none"); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType(Colour::None, "info"); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType(Colour::None, "warning"); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType(Colour::Error, failedString()); + printIssue("explicitly"); + printRemainingMessages(Colour::None); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType(Colour::Error, "** internal error **"); + break; + } + } + +private: + void printSourceInfo() const { + Colour colourGuard(Colour::FileName); + stream << result.getSourceInfo() << ':'; + } + + void printResultType(Colour::Code colour, std::string const& passOrFail) const { + if (!passOrFail.empty()) { + { + Colour colourGuard(colour); + stream << ' ' << passOrFail; + } + stream << ':'; + } + } + + void printIssue(std::string const& issue) const { + stream << ' ' << issue; + } + + void printExpressionWas() { + if (result.hasExpression()) { + stream << ';'; + { + Colour colour(dimColour()); + stream << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if (result.hasExpression()) { + stream << ' ' << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + { + Colour colour(dimColour()); + stream << " for: "; + } + stream << result.getExpandedExpression(); + } + } + + void printMessage() { + if (itMessage != messages.end()) { + stream << " '" << itMessage->message << '\''; + ++itMessage; + } + } + + void printRemainingMessages(Colour::Code colour = dimColour()) { + if (itMessage == messages.end()) + return; + + // using messages.end() directly yields (or auto) compilation error: + std::vector::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast(std::distance(itMessage, itEnd)); + + { + Colour colourGuard(colour); + stream << " with " << pluralise(N, "message") << ':'; + } + + for (; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || itMessage->type != ResultWas::Info) { + stream << " '" << itMessage->message << '\''; + if (++itMessage != itEnd) { + Colour colourGuard(dimColour()); + stream << " and"; + } + } + } + } + +private: + std::ostream& stream; + AssertionResult const& result; + std::vector messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; +}; + +} // anon namespace + + std::string CompactReporter::getDescription() { + return "Reports test results on a single line, suitable for IDEs"; + } + + ReporterPreferences CompactReporter::getPreferences() const { + return m_reporterPrefs; + } + + void CompactReporter::noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << '\'' << std::endl; + } + + void CompactReporter::assertionStarting( AssertionInfo const& ) {} + + bool CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + + stream << std::endl; + return true; + } + + void CompactReporter::sectionEnded(SectionStats const& _sectionStats) { + if (m_config->showDurations() == ShowDurations::Always) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + } + + void CompactReporter::testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( stream, _testRunStats.totals ); + stream << '\n' << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + CompactReporter::~CompactReporter() {} + + CATCH_REGISTER_REPORTER( "compact", CompactReporter ) + +} // end namespace Catch +// end catch_reporter_compact.cpp +// start catch_reporter_console.cpp + +#include +#include + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + +namespace { + +// Formatter impl for ConsoleReporter +class ConsoleAssertionPrinter { +public: + ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) + : stream(_stream), + stats(_stats), + result(_stats.assertionResult), + colour(Colour::None), + message(result.getMessage()), + messages(_stats.infoMessages), + printInfoMessages(_printInfoMessages) { + switch (result.getResultType()) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"; + //if( result.hasMessage() ) + if (_stats.infoMessages.size() == 1) + messageLabel = "with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "with messages"; + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"; + } else { + colour = Colour::Error; + passOrFail = "FAILED"; + } + if (_stats.infoMessages.size() == 1) + messageLabel = "with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "with messages"; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to unexpected exception with "; + if (_stats.infoMessages.size() == 1) + messageLabel += "message"; + if (_stats.infoMessages.size() > 1) + messageLabel += "messages"; + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to a fatal error condition"; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "because no exception was thrown where one was expected"; + break; + case ResultWas::Info: + messageLabel = "info"; + break; + case ResultWas::Warning: + messageLabel = "warning"; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"; + colour = Colour::Error; + if (_stats.infoMessages.size() == 1) + messageLabel = "explicitly with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "explicitly with messages"; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"; + colour = Colour::Error; + break; + } + } + + void print() const { + printSourceInfo(); + if (stats.totals.assertions.total() > 0) { + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } else { + stream << '\n'; + } + printMessage(); + } + +private: + void printResultType() const { + if (!passOrFail.empty()) { + Colour colourGuard(colour); + stream << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if (result.hasExpression()) { + Colour colourGuard(Colour::OriginalExpression); + stream << " "; + stream << result.getExpressionInMacro(); + stream << '\n'; + } + } + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + stream << "with expansion:\n"; + Colour colourGuard(Colour::ReconstructedExpression); + stream << Column(result.getExpandedExpression()).indent(2) << '\n'; + } + } + void printMessage() const { + if (!messageLabel.empty()) + stream << messageLabel << ':' << '\n'; + for (auto const& msg : messages) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || msg.type != ResultWas::Info) + stream << Column(msg.message).indent(2) << '\n'; + } + } + void printSourceInfo() const { + Colour colourGuard(Colour::FileName); + stream << result.getSourceInfo() << ": "; + } + + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + std::string passOrFail; + std::string messageLabel; + std::string message; + std::vector messages; + bool printInfoMessages; +}; + +std::size_t makeRatio(std::size_t number, std::size_t total) { + std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0; + return (ratio == 0 && number > 0) ? 1 : ratio; +} + +std::size_t& findMax(std::size_t& i, std::size_t& j, std::size_t& k) { + if (i > j && i > k) + return i; + else if (j > k) + return j; + else + return k; +} + +struct ColumnInfo { + enum Justification { Left, Right }; + std::string name; + int width; + Justification justification; +}; +struct ColumnBreak {}; +struct RowBreak {}; + +class Duration { + enum class Unit { + Auto, + Nanoseconds, + Microseconds, + Milliseconds, + Seconds, + Minutes + }; + static const uint64_t s_nanosecondsInAMicrosecond = 1000; + static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond; + static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond; + static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond; + + uint64_t m_inNanoseconds; + Unit m_units; + +public: + explicit Duration(uint64_t inNanoseconds, Unit units = Unit::Auto) + : m_inNanoseconds(inNanoseconds), + m_units(units) { + if (m_units == Unit::Auto) { + if (m_inNanoseconds < s_nanosecondsInAMicrosecond) + m_units = Unit::Nanoseconds; + else if (m_inNanoseconds < s_nanosecondsInAMillisecond) + m_units = Unit::Microseconds; + else if (m_inNanoseconds < s_nanosecondsInASecond) + m_units = Unit::Milliseconds; + else if (m_inNanoseconds < s_nanosecondsInAMinute) + m_units = Unit::Seconds; + else + m_units = Unit::Minutes; + } + + } + + auto value() const -> double { + switch (m_units) { + case Unit::Microseconds: + return m_inNanoseconds / static_cast(s_nanosecondsInAMicrosecond); + case Unit::Milliseconds: + return m_inNanoseconds / static_cast(s_nanosecondsInAMillisecond); + case Unit::Seconds: + return m_inNanoseconds / static_cast(s_nanosecondsInASecond); + case Unit::Minutes: + return m_inNanoseconds / static_cast(s_nanosecondsInAMinute); + default: + return static_cast(m_inNanoseconds); + } + } + auto unitsAsString() const -> std::string { + switch (m_units) { + case Unit::Nanoseconds: + return "ns"; + case Unit::Microseconds: + return "us"; + case Unit::Milliseconds: + return "ms"; + case Unit::Seconds: + return "s"; + case Unit::Minutes: + return "m"; + default: + return "** internal error **"; + } + + } + friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& { + return os << duration.value() << " " << duration.unitsAsString(); + } +}; +} // end anon namespace + +class TablePrinter { + std::ostream& m_os; + std::vector m_columnInfos; + std::ostringstream m_oss; + int m_currentColumn = -1; + bool m_isOpen = false; + +public: + TablePrinter( std::ostream& os, std::vector columnInfos ) + : m_os( os ), + m_columnInfos( std::move( columnInfos ) ) {} + + auto columnInfos() const -> std::vector const& { + return m_columnInfos; + } + + void open() { + if (!m_isOpen) { + m_isOpen = true; + *this << RowBreak(); + for (auto const& info : m_columnInfos) + *this << info.name << ColumnBreak(); + *this << RowBreak(); + m_os << Catch::getLineOfChars<'-'>() << "\n"; + } + } + void close() { + if (m_isOpen) { + *this << RowBreak(); + m_os << std::endl; + m_isOpen = false; + } + } + + template + friend TablePrinter& operator << (TablePrinter& tp, T const& value) { + tp.m_oss << value; + return tp; + } + + friend TablePrinter& operator << (TablePrinter& tp, ColumnBreak) { + auto colStr = tp.m_oss.str(); + // This takes account of utf8 encodings + auto strSize = Catch::StringRef(colStr).numberOfCharacters(); + tp.m_oss.str(""); + tp.open(); + if (tp.m_currentColumn == static_cast(tp.m_columnInfos.size() - 1)) { + tp.m_currentColumn = -1; + tp.m_os << "\n"; + } + tp.m_currentColumn++; + + auto colInfo = tp.m_columnInfos[tp.m_currentColumn]; + auto padding = (strSize + 2 < static_cast(colInfo.width)) + ? std::string(colInfo.width - (strSize + 2), ' ') + : std::string(); + if (colInfo.justification == ColumnInfo::Left) + tp.m_os << colStr << padding << " "; + else + tp.m_os << padding << colStr << " "; + return tp; + } + + friend TablePrinter& operator << (TablePrinter& tp, RowBreak) { + if (tp.m_currentColumn > 0) { + tp.m_os << "\n"; + tp.m_currentColumn = -1; + } + return tp; + } +}; + +ConsoleReporter::ConsoleReporter(ReporterConfig const& config) + : StreamingReporterBase(config), + m_tablePrinter(new TablePrinter(config.stream(), + { + { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 32, ColumnInfo::Left }, + { "iters", 8, ColumnInfo::Right }, + { "elapsed ns", 14, ColumnInfo::Right }, + { "average", 14, ColumnInfo::Right } + })) {} +ConsoleReporter::~ConsoleReporter() = default; + +std::string ConsoleReporter::getDescription() { + return "Reports test results as plain lines of text"; +} + +void ConsoleReporter::noMatchingTestCases(std::string const& spec) { + stream << "No test cases matched '" << spec << '\'' << std::endl; +} + +void ConsoleReporter::assertionStarting(AssertionInfo const&) {} + +bool ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + // Drop out if result was successful but we're not printing them. + if (!includeResults && result.getResultType() != ResultWas::Warning) + return false; + + lazyPrint(); + + ConsoleAssertionPrinter printer(stream, _assertionStats, includeResults); + printer.print(); + stream << std::endl; + return true; +} + +void ConsoleReporter::sectionStarting(SectionInfo const& _sectionInfo) { + m_headerPrinted = false; + StreamingReporterBase::sectionStarting(_sectionInfo); +} +void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) { + m_tablePrinter->close(); + if (_sectionStats.missingAssertions) { + lazyPrint(); + Colour colour(Colour::ResultError); + if (m_sectionStack.size() > 1) + stream << "\nNo assertions in section"; + else + stream << "\nNo assertions in test case"; + stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; + } + if (m_config->showDurations() == ShowDurations::Always) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + if (m_headerPrinted) { + m_headerPrinted = false; + } + StreamingReporterBase::sectionEnded(_sectionStats); +} + +void ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) { + lazyPrintWithoutClosingBenchmarkTable(); + + auto nameCol = Column( info.name ).width( static_cast( m_tablePrinter->columnInfos()[0].width - 2 ) ); + + bool firstLine = true; + for (auto line : nameCol) { + if (!firstLine) + (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak(); + else + firstLine = false; + + (*m_tablePrinter) << line << ColumnBreak(); + } +} +void ConsoleReporter::benchmarkEnded(BenchmarkStats const& stats) { + Duration average(stats.elapsedTimeInNanoseconds / stats.iterations); + (*m_tablePrinter) + << stats.iterations << ColumnBreak() + << stats.elapsedTimeInNanoseconds << ColumnBreak() + << average << ColumnBreak(); +} + +void ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) { + m_tablePrinter->close(); + StreamingReporterBase::testCaseEnded(_testCaseStats); + m_headerPrinted = false; +} +void ConsoleReporter::testGroupEnded(TestGroupStats const& _testGroupStats) { + if (currentGroupInfo.used) { + printSummaryDivider(); + stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; + printTotals(_testGroupStats.totals); + stream << '\n' << std::endl; + } + StreamingReporterBase::testGroupEnded(_testGroupStats); +} +void ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) { + printTotalsDivider(_testRunStats.totals); + printTotals(_testRunStats.totals); + stream << std::endl; + StreamingReporterBase::testRunEnded(_testRunStats); +} +void ConsoleReporter::testRunStarting(TestRunInfo const& _testInfo) { + StreamingReporterBase::testRunStarting(_testInfo); + printTestFilters(); +} + +void ConsoleReporter::lazyPrint() { + + m_tablePrinter->close(); + lazyPrintWithoutClosingBenchmarkTable(); +} + +void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() { + + if (!currentTestRunInfo.used) + lazyPrintRunInfo(); + if (!currentGroupInfo.used) + lazyPrintGroupInfo(); + + if (!m_headerPrinted) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } +} +void ConsoleReporter::lazyPrintRunInfo() { + stream << '\n' << getLineOfChars<'~'>() << '\n'; + Colour colour(Colour::SecondaryText); + stream << currentTestRunInfo->name + << " is a Catch v" << libraryVersion() << " host application.\n" + << "Run with -? for options\n\n"; + + if (m_config->rngSeed() != 0) + stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; + + currentTestRunInfo.used = true; +} +void ConsoleReporter::lazyPrintGroupInfo() { + if (!currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1) { + printClosedHeader("Group: " + currentGroupInfo->name); + currentGroupInfo.used = true; + } +} +void ConsoleReporter::printTestCaseAndSectionHeader() { + assert(!m_sectionStack.empty()); + printOpenHeader(currentTestCaseInfo->name); + + if (m_sectionStack.size() > 1) { + Colour colourGuard(Colour::Headers); + + auto + it = m_sectionStack.begin() + 1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for (; it != itEnd; ++it) + printHeaderString(it->name, 2); + } + + SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; + + if (!lineInfo.empty()) { + stream << getLineOfChars<'-'>() << '\n'; + Colour colourGuard(Colour::FileName); + stream << lineInfo << '\n'; + } + stream << getLineOfChars<'.'>() << '\n' << std::endl; +} + +void ConsoleReporter::printClosedHeader(std::string const& _name) { + printOpenHeader(_name); + stream << getLineOfChars<'.'>() << '\n'; +} +void ConsoleReporter::printOpenHeader(std::string const& _name) { + stream << getLineOfChars<'-'>() << '\n'; + { + Colour colourGuard(Colour::Headers); + printHeaderString(_name); + } +} + +// if string has a : in first line will set indent to follow it on +// subsequent lines +void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) { + std::size_t i = _string.find(": "); + if (i != std::string::npos) + i += 2; + else + i = 0; + stream << Column(_string).indent(indent + i).initialIndent(indent) << '\n'; +} + +struct SummaryColumn { + + SummaryColumn( std::string _label, Colour::Code _colour ) + : label( std::move( _label ) ), + colour( _colour ) {} + SummaryColumn addRow( std::size_t count ) { + ReusableStringStream rss; + rss << count; + std::string row = rss.str(); + for (auto& oldRow : rows) { + while (oldRow.size() < row.size()) + oldRow = ' ' + oldRow; + while (oldRow.size() > row.size()) + row = ' ' + row; + } + rows.push_back(row); + return *this; + } + + std::string label; + Colour::Code colour; + std::vector rows; + +}; + +void ConsoleReporter::printTotals( Totals const& totals ) { + if (totals.testCases.total() == 0) { + stream << Colour(Colour::Warning) << "No tests ran\n"; + } else if (totals.assertions.total() > 0 && totals.testCases.allPassed()) { + stream << Colour(Colour::ResultSuccess) << "All tests passed"; + stream << " (" + << pluralise(totals.assertions.passed, "assertion") << " in " + << pluralise(totals.testCases.passed, "test case") << ')' + << '\n'; + } else { + + std::vector columns; + columns.push_back(SummaryColumn("", Colour::None) + .addRow(totals.testCases.total()) + .addRow(totals.assertions.total())); + columns.push_back(SummaryColumn("passed", Colour::Success) + .addRow(totals.testCases.passed) + .addRow(totals.assertions.passed)); + columns.push_back(SummaryColumn("failed", Colour::ResultError) + .addRow(totals.testCases.failed) + .addRow(totals.assertions.failed)); + columns.push_back(SummaryColumn("failed as expected", Colour::ResultExpectedFailure) + .addRow(totals.testCases.failedButOk) + .addRow(totals.assertions.failedButOk)); + + printSummaryRow("test cases", columns, 0); + printSummaryRow("assertions", columns, 1); + } +} +void ConsoleReporter::printSummaryRow(std::string const& label, std::vector const& cols, std::size_t row) { + for (auto col : cols) { + std::string value = col.rows[row]; + if (col.label.empty()) { + stream << label << ": "; + if (value != "0") + stream << value; + else + stream << Colour(Colour::Warning) << "- none -"; + } else if (value != "0") { + stream << Colour(Colour::LightGrey) << " | "; + stream << Colour(col.colour) + << value << ' ' << col.label; + } + } + stream << '\n'; +} + +void ConsoleReporter::printTotalsDivider(Totals const& totals) { + if (totals.testCases.total() > 0) { + std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total()); + std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total()); + std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total()); + while (failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio)++; + while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio)--; + + stream << Colour(Colour::Error) << std::string(failedRatio, '='); + stream << Colour(Colour::ResultExpectedFailure) << std::string(failedButOkRatio, '='); + if (totals.testCases.allPassed()) + stream << Colour(Colour::ResultSuccess) << std::string(passedRatio, '='); + else + stream << Colour(Colour::Success) << std::string(passedRatio, '='); + } else { + stream << Colour(Colour::Warning) << std::string(CATCH_CONFIG_CONSOLE_WIDTH - 1, '='); + } + stream << '\n'; +} +void ConsoleReporter::printSummaryDivider() { + stream << getLineOfChars<'-'>() << '\n'; +} + +void ConsoleReporter::printTestFilters() { + if (m_config->testSpec().hasFilters()) + stream << Colour(Colour::BrightYellow) << "Filters: " << serializeFilters( m_config->getTestsOrTags() ) << '\n'; +} + +CATCH_REGISTER_REPORTER("console", ConsoleReporter) + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +// end catch_reporter_console.cpp +// start catch_reporter_junit.cpp + +#include +#include +#include +#include + +namespace Catch { + + namespace { + std::string getCurrentTimestamp() { + // Beware, this is not reentrant because of backward compatibility issues + // Also, UTC only, again because of backward compatibility (%z is C++11) + time_t rawtime; + std::time(&rawtime); + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &rawtime); +#else + std::tm* timeInfo; + timeInfo = std::gmtime(&rawtime); +#endif + + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp); + } + + std::string fileNameTag(const std::vector &tags) { + auto it = std::find_if(begin(tags), + end(tags), + [] (std::string const& tag) {return tag.front() == '#'; }); + if (it != tags.end()) + return it->substr(1); + return std::string(); + } + } // anonymous namespace + + JunitReporter::JunitReporter( ReporterConfig const& _config ) + : CumulativeReporterBase( _config ), + xml( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = true; + m_reporterPrefs.shouldReportAllAssertions = true; + } + + JunitReporter::~JunitReporter() {} + + std::string JunitReporter::getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } + + void JunitReporter::noMatchingTestCases( std::string const& /*spec*/ ) {} + + void JunitReporter::testRunStarting( TestRunInfo const& runInfo ) { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + + if ( m_config->hasTestFilters() || m_config->rngSeed() != 0 ) + xml.startElement("properties"); + + if ( m_config->hasTestFilters() ) { + xml.scopedElement( "property" ) + .writeAttribute( "name" , "filters" ) + .writeAttribute( "value" , serializeFilters( m_config->getTestsOrTags() ) ); + } + + if( m_config->rngSeed() != 0 ) { + xml.scopedElement( "property" ) + .writeAttribute( "name", "random-seed" ) + .writeAttribute( "value", m_config->rngSeed() ); + xml.endElement(); + } + } + + void JunitReporter::testGroupStarting( GroupInfo const& groupInfo ) { + suiteTimer.start(); + stdOutForSuite.clear(); + stdErrForSuite.clear(); + unexpectedExceptions = 0; + CumulativeReporterBase::testGroupStarting( groupInfo ); + } + + void JunitReporter::testCaseStarting( TestCaseInfo const& testCaseInfo ) { + m_okToFail = testCaseInfo.okToFail(); + } + + bool JunitReporter::assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) + unexpectedExceptions++; + return CumulativeReporterBase::assertionEnded( assertionStats ); + } + + void JunitReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + stdOutForSuite += testCaseStats.stdOut; + stdErrForSuite += testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); + } + + void JunitReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + double suiteTime = suiteTimer.getElapsedSeconds(); + CumulativeReporterBase::testGroupEnded( testGroupStats ); + writeGroup( *m_testGroups.back(), suiteTime ); + } + + void JunitReporter::testRunEndedCumulative() { + xml.endElement(); + } + + void JunitReporter::writeGroup( TestGroupNode const& groupNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + TestGroupStats const& stats = groupNode.value; + xml.writeAttribute( "name", stats.groupInfo.name ); + xml.writeAttribute( "errors", unexpectedExceptions ); + xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests", stats.totals.assertions.total() ); + xml.writeAttribute( "hostname", "tbd" ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time", "" ); + else + xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "timestamp", getCurrentTimestamp() ); + + // Write test cases + for( auto const& child : groupNode.children ) + writeTestCase( *child ); + + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), false ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), false ); + } + + void JunitReporter::writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; + + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); + + std::string className = stats.testInfo.className; + + if( className.empty() ) { + className = fileNameTag(stats.testInfo.tags); + if ( className.empty() ) + className = "global"; + } + + if ( !m_config->name().empty() ) + className = m_config->name() + "." + className; + + writeSection( className, "", rootSection ); + } + + void JunitReporter::writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode ) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + '/' + name; + + if( !sectionNode.assertions.empty() || + !sectionNode.stdOut.empty() || + !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname", name ); + xml.writeAttribute( "name", "root" ); + } + else { + xml.writeAttribute( "classname", className ); + xml.writeAttribute( "name", name ); + } + xml.writeAttribute( "time", ::Catch::Detail::stringify( sectionNode.stats.durationInSeconds ) ); + + writeAssertions( sectionNode ); + + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); + } + for( auto const& childNode : sectionNode.childSections ) + if( className.empty() ) + writeSection( name, "", *childNode ); + else + writeSection( className, name, *childNode ); + } + + void JunitReporter::writeAssertions( SectionNode const& sectionNode ) { + for( auto const& assertion : sectionNode.assertions ) + writeAssertion( assertion ); + } + + void JunitReporter::writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if( !result.isOk() ) { + std::string elementName; + switch( result.getResultType() ) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + elementName = "failure"; + break; + case ResultWas::ExpressionFailed: + elementName = "failure"; + break; + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message", result.getExpandedExpression() ); + xml.writeAttribute( "type", result.getTestMacroName() ); + + ReusableStringStream rss; + if( !result.getMessage().empty() ) + rss << result.getMessage() << '\n'; + for( auto const& msg : stats.infoMessages ) + if( msg.type == ResultWas::Info ) + rss << msg.message << '\n'; + + rss << "at " << result.getSourceInfo(); + xml.writeText( rss.str(), false ); + } + } + + CATCH_REGISTER_REPORTER( "junit", JunitReporter ) + +} // end namespace Catch +// end catch_reporter_junit.cpp +// start catch_reporter_listening.cpp + +#include + +namespace Catch { + + ListeningReporter::ListeningReporter() { + // We will assume that listeners will always want all assertions + m_preferences.shouldReportAllAssertions = true; + } + + void ListeningReporter::addListener( IStreamingReporterPtr&& listener ) { + m_listeners.push_back( std::move( listener ) ); + } + + void ListeningReporter::addReporter(IStreamingReporterPtr&& reporter) { + assert(!m_reporter && "Listening reporter can wrap only 1 real reporter"); + m_reporter = std::move( reporter ); + m_preferences.shouldRedirectStdOut = m_reporter->getPreferences().shouldRedirectStdOut; + } + + ReporterPreferences ListeningReporter::getPreferences() const { + return m_preferences; + } + + std::set ListeningReporter::getSupportedVerbosities() { + return std::set{ }; + } + + void ListeningReporter::noMatchingTestCases( std::string const& spec ) { + for ( auto const& listener : m_listeners ) { + listener->noMatchingTestCases( spec ); + } + m_reporter->noMatchingTestCases( spec ); + } + + void ListeningReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) { + for ( auto const& listener : m_listeners ) { + listener->benchmarkStarting( benchmarkInfo ); + } + m_reporter->benchmarkStarting( benchmarkInfo ); + } + void ListeningReporter::benchmarkEnded( BenchmarkStats const& benchmarkStats ) { + for ( auto const& listener : m_listeners ) { + listener->benchmarkEnded( benchmarkStats ); + } + m_reporter->benchmarkEnded( benchmarkStats ); + } + + void ListeningReporter::testRunStarting( TestRunInfo const& testRunInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testRunStarting( testRunInfo ); + } + m_reporter->testRunStarting( testRunInfo ); + } + + void ListeningReporter::testGroupStarting( GroupInfo const& groupInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testGroupStarting( groupInfo ); + } + m_reporter->testGroupStarting( groupInfo ); + } + + void ListeningReporter::testCaseStarting( TestCaseInfo const& testInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testCaseStarting( testInfo ); + } + m_reporter->testCaseStarting( testInfo ); + } + + void ListeningReporter::sectionStarting( SectionInfo const& sectionInfo ) { + for ( auto const& listener : m_listeners ) { + listener->sectionStarting( sectionInfo ); + } + m_reporter->sectionStarting( sectionInfo ); + } + + void ListeningReporter::assertionStarting( AssertionInfo const& assertionInfo ) { + for ( auto const& listener : m_listeners ) { + listener->assertionStarting( assertionInfo ); + } + m_reporter->assertionStarting( assertionInfo ); + } + + // The return value indicates if the messages buffer should be cleared: + bool ListeningReporter::assertionEnded( AssertionStats const& assertionStats ) { + for( auto const& listener : m_listeners ) { + static_cast( listener->assertionEnded( assertionStats ) ); + } + return m_reporter->assertionEnded( assertionStats ); + } + + void ListeningReporter::sectionEnded( SectionStats const& sectionStats ) { + for ( auto const& listener : m_listeners ) { + listener->sectionEnded( sectionStats ); + } + m_reporter->sectionEnded( sectionStats ); + } + + void ListeningReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + for ( auto const& listener : m_listeners ) { + listener->testCaseEnded( testCaseStats ); + } + m_reporter->testCaseEnded( testCaseStats ); + } + + void ListeningReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + for ( auto const& listener : m_listeners ) { + listener->testGroupEnded( testGroupStats ); + } + m_reporter->testGroupEnded( testGroupStats ); + } + + void ListeningReporter::testRunEnded( TestRunStats const& testRunStats ) { + for ( auto const& listener : m_listeners ) { + listener->testRunEnded( testRunStats ); + } + m_reporter->testRunEnded( testRunStats ); + } + + void ListeningReporter::skipTest( TestCaseInfo const& testInfo ) { + for ( auto const& listener : m_listeners ) { + listener->skipTest( testInfo ); + } + m_reporter->skipTest( testInfo ); + } + + bool ListeningReporter::isMulti() const { + return true; + } + +} // end namespace Catch +// end catch_reporter_listening.cpp +// start catch_reporter_xml.cpp + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + XmlReporter::XmlReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_xml(_config.stream()) + { + m_reporterPrefs.shouldRedirectStdOut = true; + m_reporterPrefs.shouldReportAllAssertions = true; + } + + XmlReporter::~XmlReporter() = default; + + std::string XmlReporter::getDescription() { + return "Reports test results as an XML document"; + } + + std::string XmlReporter::getStylesheetRef() const { + return std::string(); + } + + void XmlReporter::writeSourceInfo( SourceLineInfo const& sourceInfo ) { + m_xml + .writeAttribute( "filename", sourceInfo.file ) + .writeAttribute( "line", sourceInfo.line ); + } + + void XmlReporter::noMatchingTestCases( std::string const& s ) { + StreamingReporterBase::noMatchingTestCases( s ); + } + + void XmlReporter::testRunStarting( TestRunInfo const& testInfo ) { + StreamingReporterBase::testRunStarting( testInfo ); + std::string stylesheetRef = getStylesheetRef(); + if( !stylesheetRef.empty() ) + m_xml.writeStylesheetRef( stylesheetRef ); + m_xml.startElement( "Catch" ); + if( !m_config->name().empty() ) + m_xml.writeAttribute( "name", m_config->name() ); + if (m_config->testSpec().hasFilters()) + m_xml.writeAttribute( "filters", serializeFilters( m_config->getTestsOrTags() ) ); + if( m_config->rngSeed() != 0 ) + m_xml.scopedElement( "Randomness" ) + .writeAttribute( "seed", m_config->rngSeed() ); + } + + void XmlReporter::testGroupStarting( GroupInfo const& groupInfo ) { + StreamingReporterBase::testGroupStarting( groupInfo ); + m_xml.startElement( "Group" ) + .writeAttribute( "name", groupInfo.name ); + } + + void XmlReporter::testCaseStarting( TestCaseInfo const& testInfo ) { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ) + .writeAttribute( "name", trim( testInfo.name ) ) + .writeAttribute( "description", testInfo.description ) + .writeAttribute( "tags", testInfo.tagsAsString() ); + + writeSourceInfo( testInfo.lineInfo ); + + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + m_xml.ensureTagClosed(); + } + + void XmlReporter::sectionStarting( SectionInfo const& sectionInfo ) { + StreamingReporterBase::sectionStarting( sectionInfo ); + if( m_sectionDepth++ > 0 ) { + m_xml.startElement( "Section" ) + .writeAttribute( "name", trim( sectionInfo.name ) ); + writeSourceInfo( sectionInfo.lineInfo ); + m_xml.ensureTagClosed(); + } + } + + void XmlReporter::assertionStarting( AssertionInfo const& ) { } + + bool XmlReporter::assertionEnded( AssertionStats const& assertionStats ) { + + AssertionResult const& result = assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + if( includeResults || result.getResultType() == ResultWas::Warning ) { + // Print any info messages in tags. + for( auto const& msg : assertionStats.infoMessages ) { + if( msg.type == ResultWas::Info && includeResults ) { + m_xml.scopedElement( "Info" ) + .writeText( msg.message ); + } else if ( msg.type == ResultWas::Warning ) { + m_xml.scopedElement( "Warning" ) + .writeText( msg.message ); + } + } + } + + // Drop out if result was successful but we're not printing them. + if( !includeResults && result.getResultType() != ResultWas::Warning ) + return true; + + // Print the expression if there is one. + if( result.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success", result.succeeded() ) + .writeAttribute( "type", result.getTestMacroName() ); + + writeSourceInfo( result.getSourceInfo() ); + + m_xml.scopedElement( "Original" ) + .writeText( result.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( result.getExpandedExpression() ); + } + + // And... Print a result applicable to each result type. + switch( result.getResultType() ) { + case ResultWas::ThrewException: + m_xml.startElement( "Exception" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::FatalErrorCondition: + m_xml.startElement( "FatalErrorCondition" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( result.getMessage() ); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.startElement( "Failure" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + default: + break; + } + + if( result.hasExpression() ) + m_xml.endElement(); + + return true; + } + + void XmlReporter::sectionEnded( SectionStats const& sectionStats ) { + StreamingReporterBase::sectionEnded( sectionStats ); + if( --m_sectionDepth > 0 ) { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes", sectionStats.assertions.passed ); + e.writeAttribute( "failures", sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); + + m_xml.endElement(); + } + } + + void XmlReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); + + if( !testCaseStats.stdOut.empty() ) + m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false ); + if( !testCaseStats.stdErr.empty() ) + m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false ); + + m_xml.endElement(); + } + + void XmlReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + StreamingReporterBase::testGroupEnded( testGroupStats ); + // TODO: Check testGroupStats.aborting and act accordingly. + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) + .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + void XmlReporter::testRunEnded( TestRunStats const& testRunStats ) { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testRunStats.totals.assertions.passed ) + .writeAttribute( "failures", testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + CATCH_REGISTER_REPORTER( "xml", XmlReporter ) + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +// end catch_reporter_xml.cpp + +namespace Catch { + LeakDetector leakDetector; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_impl.hpp +#endif + +#ifdef CATCH_CONFIG_MAIN +// start catch_default_main.hpp + +#ifndef __OBJC__ + +#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) +// Standard C/C++ Win32 Unicode wmain entry point +extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) { +#else +// Standard C/C++ main entry point +int main (int argc, char * argv[]) { +#endif + + return Catch::Session().run( argc, argv ); +} + +#else // __OBJC__ + +// Objective-C entry point +int main (int argc, char * const argv[]) { +#if !CATCH_ARC_ENABLED + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +#endif + + Catch::registerTestMethods(); + int result = Catch::Session().run( argc, (char**)argv ); + +#if !CATCH_ARC_ENABLED + [pool drain]; +#endif + + return result; +} + +#endif // __OBJC__ + +// end catch_default_main.hpp +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) + +#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED +# undef CLARA_CONFIG_MAIN +#endif + +#if !defined(CATCH_CONFIG_DISABLE) +////// +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define CATCH_REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + +#define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) +#endif// CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + +#define CATCH_CHECK( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) +#define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + +#define CATCH_CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) +#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE",__VA_ARGS__ ) + +#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) +#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) +#define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) +#define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) +#define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#else +#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) +#endif + +#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) +#define CATCH_STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__ , #__VA_ARGS__ ); CATCH_SUCCEED( #__VA_ARGS__ ) +#define CATCH_STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); CATCH_SUCCEED( #__VA_ARGS__ ) +#else +#define CATCH_STATIC_REQUIRE( ... ) CATCH_REQUIRE( __VA_ARGS__ ) +#define CATCH_STATIC_REQUIRE_FALSE( ... ) CATCH_REQUIRE_FALSE( __VA_ARGS__ ) +#endif + +// "BDD-style" convenience wrappers +#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#define CATCH_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) +#define CATCH_AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) +#define CATCH_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) +#define CATCH_AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) +#define CATCH_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) +#define CATCH_AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE( ... ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + +#define REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + +#define CHECK( ... ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) +#define CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + +#define CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) +#define UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "UNSCOPED_INFO", msg ) +#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE",__VA_ARGS__ ) + +#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) +#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) +#define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) +#define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) +#define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#else +#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) ) +#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) +#define TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) ) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) +#endif + +#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) +#define STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__, #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ ) +#define STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); SUCCEED( "!(" #__VA_ARGS__ ")" ) +#else +#define STATIC_REQUIRE( ... ) REQUIRE( __VA_ARGS__ ) +#define STATIC_REQUIRE_FALSE( ... ) REQUIRE_FALSE( __VA_ARGS__ ) +#endif + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) + +// "BDD-style" convenience wrappers +#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) + +#define GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) +#define AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) +#define WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) +#define AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) +#define THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) +#define AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +using Catch::Detail::Approx; + +#else // CATCH_CONFIG_DISABLE + +////// +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE( ... ) (void)(0) +#define CATCH_REQUIRE_FALSE( ... ) (void)(0) + +#define CATCH_REQUIRE_THROWS( ... ) (void)(0) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif// CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_REQUIRE_NOTHROW( ... ) (void)(0) + +#define CATCH_CHECK( ... ) (void)(0) +#define CATCH_CHECK_FALSE( ... ) (void)(0) +#define CATCH_CHECKED_IF( ... ) if (__VA_ARGS__) +#define CATCH_CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) +#define CATCH_CHECK_NOFAIL( ... ) (void)(0) + +#define CATCH_CHECK_THROWS( ... ) (void)(0) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) (void)(0) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_CHECK_NOTHROW( ... ) (void)(0) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THAT( arg, matcher ) (void)(0) + +#define CATCH_REQUIRE_THAT( arg, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define CATCH_INFO( msg ) (void)(0) +#define CATCH_WARN( msg ) (void)(0) +#define CATCH_CAPTURE( msg ) (void)(0) + +#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_METHOD_AS_TEST_CASE( method, ... ) +#define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0) +#define CATCH_SECTION( ... ) +#define CATCH_DYNAMIC_SECTION( ... ) +#define CATCH_FAIL( ... ) (void)(0) +#define CATCH_FAIL_CHECK( ... ) (void)(0) +#define CATCH_SUCCEED( ... ) (void)(0) + +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#else +#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) ) ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className ) ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#endif + +// "BDD-style" convenience wrappers +#define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) +#define CATCH_GIVEN( desc ) +#define CATCH_AND_GIVEN( desc ) +#define CATCH_WHEN( desc ) +#define CATCH_AND_WHEN( desc ) +#define CATCH_THEN( desc ) +#define CATCH_AND_THEN( desc ) + +#define CATCH_STATIC_REQUIRE( ... ) (void)(0) +#define CATCH_STATIC_REQUIRE_FALSE( ... ) (void)(0) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE( ... ) (void)(0) +#define REQUIRE_FALSE( ... ) (void)(0) + +#define REQUIRE_THROWS( ... ) (void)(0) +#define REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) +#define REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define REQUIRE_NOTHROW( ... ) (void)(0) + +#define CHECK( ... ) (void)(0) +#define CHECK_FALSE( ... ) (void)(0) +#define CHECKED_IF( ... ) if (__VA_ARGS__) +#define CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) +#define CHECK_NOFAIL( ... ) (void)(0) + +#define CHECK_THROWS( ... ) (void)(0) +#define CHECK_THROWS_AS( expr, exceptionType ) (void)(0) +#define CHECK_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CHECK_NOTHROW( ... ) (void)(0) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THAT( arg, matcher ) (void)(0) + +#define REQUIRE_THAT( arg, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define INFO( msg ) (void)(0) +#define WARN( msg ) (void)(0) +#define CAPTURE( msg ) (void)(0) + +#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define METHOD_AS_TEST_CASE( method, ... ) +#define REGISTER_TEST_CASE( Function, ... ) (void)(0) +#define SECTION( ... ) +#define DYNAMIC_SECTION( ... ) +#define FAIL( ... ) (void)(0) +#define FAIL_CHECK( ... ) (void)(0) +#define SUCCEED( ... ) (void)(0) +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) ) +#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className ) +#define TEMPLATE_PRODUCT_TEST_CASE( ... ) TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#else +#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) ) ) +#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className ) ) +#define TEMPLATE_PRODUCT_TEST_CASE( ... ) TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#endif + +#define STATIC_REQUIRE( ... ) (void)(0) +#define STATIC_REQUIRE_FALSE( ... ) (void)(0) + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) + +// "BDD-style" convenience wrappers +#define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) + +#define GIVEN( desc ) +#define AND_GIVEN( desc ) +#define WHEN( desc ) +#define AND_WHEN( desc ) +#define THEN( desc ) +#define AND_THEN( desc ) + +using Catch::Detail::Approx; + +#endif + +#endif // ! CATCH_CONFIG_IMPL_ONLY + +// start catch_reenable_warnings.h + + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(pop) +# else +# pragma clang diagnostic pop +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + +// end catch_reenable_warnings.h +// end catch.hpp +#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED diff --git a/extern/IXWebSocket-11.3.2/test/Catch2/single_include/catch_reporter_automake.hpp b/extern/IXWebSocket-11.3.2/test/Catch2/single_include/catch_reporter_automake.hpp new file mode 100644 index 0000000000..dbebe97516 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/Catch2/single_include/catch_reporter_automake.hpp @@ -0,0 +1,62 @@ +/* + * Created by Justin R. Wilson on 2/19/2017. + * Copyright 2017 Justin R. Wilson. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_CATCH_REPORTER_AUTOMAKE_HPP_INCLUDED +#define TWOBLUECUBES_CATCH_REPORTER_AUTOMAKE_HPP_INCLUDED + +// Don't #include any Catch headers here - we can assume they are already +// included before this header. +// This is not good practice in general but is necessary in this case so this +// file can be distributed as a single header that works with the main +// Catch single header. + +namespace Catch { + + struct AutomakeReporter : StreamingReporterBase { + AutomakeReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + {} + + ~AutomakeReporter() override; + + static std::string getDescription() { + return "Reports test results in the format of Automake .trs files"; + } + + void assertionStarting( AssertionInfo const& ) override {} + + bool assertionEnded( AssertionStats const& /*_assertionStats*/ ) override { return true; } + + void testCaseEnded( TestCaseStats const& _testCaseStats ) override { + // Possible values to emit are PASS, XFAIL, SKIP, FAIL, XPASS and ERROR. + stream << ":test-result: "; + if (_testCaseStats.totals.assertions.allPassed()) { + stream << "PASS"; + } else if (_testCaseStats.totals.assertions.allOk()) { + stream << "XFAIL"; + } else { + stream << "FAIL"; + } + stream << ' ' << _testCaseStats.testInfo.name << '\n'; + StreamingReporterBase::testCaseEnded( _testCaseStats ); + } + + void skipTest( TestCaseInfo const& testInfo ) override { + stream << ":test-result: SKIP " << testInfo.name << '\n'; + } + + }; + +#ifdef CATCH_IMPL + AutomakeReporter::~AutomakeReporter() {} +#endif + + CATCH_REGISTER_REPORTER( "automake", AutomakeReporter) + +} // end namespace Catch + +#endif // TWOBLUECUBES_CATCH_REPORTER_AUTOMAKE_HPP_INCLUDED diff --git a/extern/IXWebSocket-11.3.2/test/Catch2/single_include/catch_reporter_tap.hpp b/extern/IXWebSocket-11.3.2/test/Catch2/single_include/catch_reporter_tap.hpp new file mode 100644 index 0000000000..19e54ed197 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/Catch2/single_include/catch_reporter_tap.hpp @@ -0,0 +1,255 @@ +/* + * Created by Colton Wolkins on 2015-08-15. + * Copyright 2015 Martin Moene. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_CATCH_REPORTER_TAP_HPP_INCLUDED +#define TWOBLUECUBES_CATCH_REPORTER_TAP_HPP_INCLUDED + + +// Don't #include any Catch headers here - we can assume they are already +// included before this header. +// This is not good practice in general but is necessary in this case so this +// file can be distributed as a single header that works with the main +// Catch single header. + +#include + +namespace Catch { + + struct TAPReporter : StreamingReporterBase { + + using StreamingReporterBase::StreamingReporterBase; + + ~TAPReporter() override; + + static std::string getDescription() { + return "Reports test results in TAP format, suitable for test harnesses"; + } + + ReporterPreferences getPreferences() const override { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = false; + return prefs; + } + + void noMatchingTestCases( std::string const& spec ) override { + stream << "# No test cases matched '" << spec << "'" << std::endl; + } + + void assertionStarting( AssertionInfo const& ) override {} + + bool assertionEnded( AssertionStats const& _assertionStats ) override { + ++counter; + + AssertionPrinter printer( stream, _assertionStats, counter ); + printer.print(); + stream << " # " << currentTestCaseInfo->name ; + + stream << std::endl; + return true; + } + + void testRunEnded( TestRunStats const& _testRunStats ) override { + printTotals( _testRunStats.totals ); + stream << "\n" << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + std::size_t counter = 0; + class AssertionPrinter { + public: + AssertionPrinter& operator= ( AssertionPrinter const& ) = delete; + AssertionPrinter( AssertionPrinter const& ) = delete; + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, std::size_t _counter ) + : stream( _stream ) + , result( _stats.assertionResult ) + , messages( _stats.infoMessages ) + , itMessage( _stats.infoMessages.begin() ) + , printInfoMessages( true ) + , counter(_counter) + {} + + void print() { + itMessage = messages.begin(); + + switch( result.getResultType() ) { + case ResultWas::Ok: + printResultType( passedString() ); + printOriginalExpression(); + printReconstructedExpression(); + if ( ! result.hasExpression() ) + printRemainingMessages( Colour::None ); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) { + printResultType(passedString()); + } else { + printResultType(failedString()); + } + printOriginalExpression(); + printReconstructedExpression(); + if (result.isOk()) { + printIssue(" # TODO"); + } + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType( failedString() ); + printIssue( "unexpected exception with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType( failedString() ); + printIssue( "fatal error condition with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType( failedString() ); + printIssue( "expected exception, got none" ); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType( "info" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType( "warning" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType( failedString() ); + printIssue( "explicitly" ); + printRemainingMessages( Colour::None ); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType( "** internal error **" ); + break; + } + } + + private: + static Colour::Code dimColour() { return Colour::FileName; } + + static const char* failedString() { return "not ok"; } + static const char* passedString() { return "ok"; } + + void printSourceInfo() const { + Colour colourGuard( dimColour() ); + stream << result.getSourceInfo() << ":"; + } + + void printResultType( std::string const& passOrFail ) const { + if( !passOrFail.empty() ) { + stream << passOrFail << ' ' << counter << " -"; + } + } + + void printIssue( std::string const& issue ) const { + stream << " " << issue; + } + + void printExpressionWas() { + if( result.hasExpression() ) { + stream << ";"; + { + Colour colour( dimColour() ); + stream << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if( result.hasExpression() ) { + stream << " " << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + { + Colour colour( dimColour() ); + stream << " for: "; + } + std::string expr = result.getExpandedExpression(); + std::replace( expr.begin(), expr.end(), '\n', ' '); + stream << expr; + } + } + + void printMessage() { + if ( itMessage != messages.end() ) { + stream << " '" << itMessage->message << "'"; + ++itMessage; + } + } + + void printRemainingMessages( Colour::Code colour = dimColour() ) { + if (itMessage == messages.end()) { + return; + } + + // using messages.end() directly (or auto) yields compilation error: + std::vector::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast( std::distance( itMessage, itEnd ) ); + + { + Colour colourGuard( colour ); + stream << " with " << pluralise( N, "message" ) << ":"; + } + + for(; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || itMessage->type != ResultWas::Info ) { + stream << " '" << itMessage->message << "'"; + if ( ++itMessage != itEnd ) { + Colour colourGuard( dimColour() ); + stream << " and"; + } + } + } + } + + private: + std::ostream& stream; + AssertionResult const& result; + std::vector messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; + std::size_t counter; + }; + + void printTotals( const Totals& totals ) const { + if( totals.testCases.total() == 0 ) { + stream << "1..0 # Skipped: No tests ran."; + } else { + stream << "1.." << counter; + } + } + }; + +#ifdef CATCH_IMPL + TAPReporter::~TAPReporter() {} +#endif + + CATCH_REGISTER_REPORTER( "tap", TAPReporter ) + +} // end namespace Catch + +#endif // TWOBLUECUBES_CATCH_REPORTER_TAP_HPP_INCLUDED diff --git a/extern/IXWebSocket-11.3.2/test/Catch2/single_include/catch_reporter_teamcity.hpp b/extern/IXWebSocket-11.3.2/test/Catch2/single_include/catch_reporter_teamcity.hpp new file mode 100644 index 0000000000..dbd0db532c --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/Catch2/single_include/catch_reporter_teamcity.hpp @@ -0,0 +1,220 @@ +/* + * Created by Phil Nash on 19th December 2014 + * Copyright 2014 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_CATCH_REPORTER_TEAMCITY_HPP_INCLUDED +#define TWOBLUECUBES_CATCH_REPORTER_TEAMCITY_HPP_INCLUDED + +// Don't #include any Catch headers here - we can assume they are already +// included before this header. +// This is not good practice in general but is necessary in this case so this +// file can be distributed as a single header that works with the main +// Catch single header. + +#include + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + struct TeamCityReporter : StreamingReporterBase { + TeamCityReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + { + m_reporterPrefs.shouldRedirectStdOut = true; + } + + static std::string escape( std::string const& str ) { + std::string escaped = str; + replaceInPlace( escaped, "|", "||" ); + replaceInPlace( escaped, "'", "|'" ); + replaceInPlace( escaped, "\n", "|n" ); + replaceInPlace( escaped, "\r", "|r" ); + replaceInPlace( escaped, "[", "|[" ); + replaceInPlace( escaped, "]", "|]" ); + return escaped; + } + ~TeamCityReporter() override; + + static std::string getDescription() { + return "Reports test results as TeamCity service messages"; + } + + void skipTest( TestCaseInfo const& /* testInfo */ ) override { + } + + void noMatchingTestCases( std::string const& /* spec */ ) override {} + + void testGroupStarting( GroupInfo const& groupInfo ) override { + StreamingReporterBase::testGroupStarting( groupInfo ); + stream << "##teamcity[testSuiteStarted name='" + << escape( groupInfo.name ) << "']\n"; + } + void testGroupEnded( TestGroupStats const& testGroupStats ) override { + StreamingReporterBase::testGroupEnded( testGroupStats ); + stream << "##teamcity[testSuiteFinished name='" + << escape( testGroupStats.groupInfo.name ) << "']\n"; + } + + + void assertionStarting( AssertionInfo const& ) override {} + + bool assertionEnded( AssertionStats const& assertionStats ) override { + AssertionResult const& result = assertionStats.assertionResult; + if( !result.isOk() ) { + + ReusableStringStream msg; + if( !m_headerPrintedForThisSection ) + printSectionHeader( msg.get() ); + m_headerPrintedForThisSection = true; + + msg << result.getSourceInfo() << "\n"; + + switch( result.getResultType() ) { + case ResultWas::ExpressionFailed: + msg << "expression failed"; + break; + case ResultWas::ThrewException: + msg << "unexpected exception"; + break; + case ResultWas::FatalErrorCondition: + msg << "fatal error condition"; + break; + case ResultWas::DidntThrowException: + msg << "no exception was thrown where one was expected"; + break; + case ResultWas::ExplicitFailure: + msg << "explicit failure"; + break; + + // We shouldn't get here because of the isOk() test + case ResultWas::Ok: + case ResultWas::Info: + case ResultWas::Warning: + throw std::domain_error( "Internal error in TeamCity reporter" ); + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + throw std::domain_error( "Not implemented" ); + } + if( assertionStats.infoMessages.size() == 1 ) + msg << " with message:"; + if( assertionStats.infoMessages.size() > 1 ) + msg << " with messages:"; + for( auto const& messageInfo : assertionStats.infoMessages ) + msg << "\n \"" << messageInfo.message << "\""; + + + if( result.hasExpression() ) { + msg << + "\n " << result.getExpressionInMacro() << "\n" + "with expansion:\n" << + " " << result.getExpandedExpression() << "\n"; + } + + if( currentTestCaseInfo->okToFail() ) { + msg << "- failure ignore as test marked as 'ok to fail'\n"; + stream << "##teamcity[testIgnored" + << " name='" << escape( currentTestCaseInfo->name )<< "'" + << " message='" << escape( msg.str() ) << "'" + << "]\n"; + } + else { + stream << "##teamcity[testFailed" + << " name='" << escape( currentTestCaseInfo->name )<< "'" + << " message='" << escape( msg.str() ) << "'" + << "]\n"; + } + } + stream.flush(); + return true; + } + + void sectionStarting( SectionInfo const& sectionInfo ) override { + m_headerPrintedForThisSection = false; + StreamingReporterBase::sectionStarting( sectionInfo ); + } + + void testCaseStarting( TestCaseInfo const& testInfo ) override { + m_testTimer.start(); + StreamingReporterBase::testCaseStarting( testInfo ); + stream << "##teamcity[testStarted name='" + << escape( testInfo.name ) << "']\n"; + stream.flush(); + } + + void testCaseEnded( TestCaseStats const& testCaseStats ) override { + StreamingReporterBase::testCaseEnded( testCaseStats ); + if( !testCaseStats.stdOut.empty() ) + stream << "##teamcity[testStdOut name='" + << escape( testCaseStats.testInfo.name ) + << "' out='" << escape( testCaseStats.stdOut ) << "']\n"; + if( !testCaseStats.stdErr.empty() ) + stream << "##teamcity[testStdErr name='" + << escape( testCaseStats.testInfo.name ) + << "' out='" << escape( testCaseStats.stdErr ) << "']\n"; + stream << "##teamcity[testFinished name='" + << escape( testCaseStats.testInfo.name ) << "' duration='" + << m_testTimer.getElapsedMilliseconds() << "']\n"; + stream.flush(); + } + + private: + void printSectionHeader( std::ostream& os ) { + assert( !m_sectionStack.empty() ); + + if( m_sectionStack.size() > 1 ) { + os << getLineOfChars<'-'>() << "\n"; + + std::vector::const_iterator + it = m_sectionStack.begin()+1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for( ; it != itEnd; ++it ) + printHeaderString( os, it->name ); + os << getLineOfChars<'-'>() << "\n"; + } + + SourceLineInfo lineInfo = m_sectionStack.front().lineInfo; + + if( !lineInfo.empty() ) + os << lineInfo << "\n"; + os << getLineOfChars<'.'>() << "\n\n"; + } + + // if string has a : in first line will set indent to follow it on + // subsequent lines + static void printHeaderString( std::ostream& os, std::string const& _string, std::size_t indent = 0 ) { + std::size_t i = _string.find( ": " ); + if( i != std::string::npos ) + i+=2; + else + i = 0; + os << Column( _string ) + .indent( indent+i) + .initialIndent( indent ) << "\n"; + } + private: + bool m_headerPrintedForThisSection = false; + Timer m_testTimer; + }; + +#ifdef CATCH_IMPL + TeamCityReporter::~TeamCityReporter() {} +#endif + + CATCH_REGISTER_REPORTER( "teamcity", TeamCityReporter ) + +} // end namespace Catch + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + +#endif // TWOBLUECUBES_CATCH_REPORTER_TEAMCITY_HPP_INCLUDED diff --git a/extern/IXWebSocket-11.3.2/test/IXDNSLookupTest.cpp b/extern/IXWebSocket-11.3.2/test/IXDNSLookupTest.cpp new file mode 100644 index 0000000000..a2405042f6 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXDNSLookupTest.cpp @@ -0,0 +1,51 @@ +/* + * IXDNSLookupTest.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone. All rights reserved. + */ + +#include "IXTest.h" +#include "catch.hpp" +#include +#include + +using namespace ix; + + +TEST_CASE("dns", "[net]") +{ + SECTION("Test resolving a known hostname") + { + auto dnsLookup = std::make_shared("www.google.com", 80); + + std::string errMsg; + struct addrinfo* res; + + res = dnsLookup->resolve(errMsg, [] { return false; }); + std::cerr << "Error message: " << errMsg << std::endl; + REQUIRE(res != nullptr); + + dnsLookup->release(res); + } + + SECTION("Test resolving a non-existing hostname") + { + auto dnsLookup = std::make_shared("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", 80); + + std::string errMsg; + struct addrinfo* res = dnsLookup->resolve(errMsg, [] { return false; }); + std::cerr << "Error message: " << errMsg << std::endl; + REQUIRE(res == nullptr); + } + + SECTION("Test resolving a good hostname, with cancellation") + { + auto dnsLookup = std::make_shared("www.google.com", 80, 1); + + std::string errMsg; + // The callback returning true means we are requesting cancellation + struct addrinfo* res = dnsLookup->resolve(errMsg, [] { return true; }); + std::cerr << "Error message: " << errMsg << std::endl; + REQUIRE(res == nullptr); + } +} diff --git a/extern/IXWebSocket-11.3.2/test/IXHttpClientTest.cpp b/extern/IXWebSocket-11.3.2/test/IXHttpClientTest.cpp new file mode 100644 index 0000000000..735dcdb113 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXHttpClientTest.cpp @@ -0,0 +1,224 @@ +/* + * IXSocketTest.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone. All rights reserved. + */ + +#include "catch.hpp" +#include +#include + +using namespace ix; + +TEST_CASE("http_client", "[http]") +{ + SECTION("Connect to a remote HTTP server") + { + HttpClient httpClient; + WebSocketHttpHeaders headers; + + std::string url("http://httpbin.org/"); + auto args = httpClient.createRequest(url); + + args->extraHeaders = headers; + args->connectTimeout = 60; + args->transferTimeout = 60; + args->followRedirects = true; + args->maxRedirects = 10; + args->verbose = true; + args->compress = true; + args->logger = [](const std::string& msg) { std::cout << msg; }; + args->onProgressCallback = [](int current, int total) -> bool { + std::cerr << "\r" + << "Downloaded " << current << " bytes out of " << total; + return true; + }; + + auto response = httpClient.get(url, args); + + for (auto it : response->headers) + { + std::cerr << it.first << ": " << it.second << std::endl; + } + + std::cerr << "Upload size: " << response->uploadSize << std::endl; + std::cerr << "Download size: " << response->downloadSize << std::endl; + std::cerr << "Status: " << response->statusCode << std::endl; + std::cerr << "Error message: " << response->errorMsg << std::endl; + + REQUIRE(response->errorCode == HttpErrorCode::Ok); + REQUIRE(response->statusCode == 200); + } + +#ifdef IXWEBSOCKET_USE_TLS + SECTION("Connect to a remote HTTPS server") + { + HttpClient httpClient; + WebSocketHttpHeaders headers; + + SocketTLSOptions tlsOptions; + tlsOptions.caFile = "cacert.pem"; + httpClient.setTLSOptions(tlsOptions); + + std::string url("https://httpbin.org/"); + auto args = httpClient.createRequest(url); + + args->extraHeaders = headers; + args->connectTimeout = 60; + args->transferTimeout = 60; + args->followRedirects = true; + args->maxRedirects = 10; + args->verbose = true; + args->compress = true; + args->logger = [](const std::string& msg) { std::cout << msg; }; + args->onProgressCallback = [](int current, int total) -> bool { + std::cerr << "\r" + << "Downloaded " << current << " bytes out of " << total; + return true; + }; + + auto response = httpClient.get(url, args); + + for (auto it : response->headers) + { + std::cerr << it.first << ": " << it.second << std::endl; + } + + std::cerr << "Upload size: " << response->uploadSize << std::endl; + std::cerr << "Download size: " << response->downloadSize << std::endl; + std::cerr << "Status: " << response->statusCode << std::endl; + std::cerr << "Error message: " << response->errorMsg << std::endl; + + REQUIRE(response->errorCode == HttpErrorCode::Ok); + REQUIRE(response->statusCode == 200); + } +#endif + + SECTION("Async API, one call") + { + bool async = true; + HttpClient httpClient(async); + WebSocketHttpHeaders headers; + + SocketTLSOptions tlsOptions; + tlsOptions.caFile = "cacert.pem"; + httpClient.setTLSOptions(tlsOptions); + + std::string url("http://httpbin.org/"); + auto args = httpClient.createRequest(url); + + args->extraHeaders = headers; + args->connectTimeout = 60; + args->transferTimeout = 60; + args->followRedirects = true; + args->maxRedirects = 10; + args->verbose = true; + args->compress = true; + args->logger = [](const std::string& msg) { std::cout << msg; }; + args->onProgressCallback = [](int current, int total) -> bool { + std::cerr << "\r" + << "Downloaded " << current << " bytes out of " << total; + return true; + }; + + std::atomic requestCompleted(false); + std::atomic statusCode(0); + + httpClient.performRequest( + args, [&requestCompleted, &statusCode](const HttpResponsePtr& response) { + std::cerr << "Upload size: " << response->uploadSize << std::endl; + std::cerr << "Download size: " << response->downloadSize << std::endl; + std::cerr << "Status: " << response->statusCode << std::endl; + std::cerr << "Error message: " << response->errorMsg << std::endl; + + // In case of failure, print response->errorMsg + statusCode = response->statusCode; + requestCompleted = true; + }); + + int wait = 0; + while (wait < 5000) + { + if (requestCompleted) break; + + std::chrono::duration duration(10); + std::this_thread::sleep_for(duration); + wait += 10; + } + + std::cerr << "Done" << std::endl; + REQUIRE(statusCode == 200); + } + + SECTION("Async API, multiple calls") + { + bool async = true; + HttpClient httpClient(async); + WebSocketHttpHeaders headers; + + std::string url("http://httpbin.org/"); + auto args = httpClient.createRequest(url); + + args->extraHeaders = headers; + args->connectTimeout = 60; + args->transferTimeout = 60; + args->followRedirects = true; + args->maxRedirects = 10; + args->verbose = true; + args->compress = true; + args->logger = [](const std::string& msg) { std::cout << msg; }; + args->onProgressCallback = [](int current, int total) -> bool { + std::cerr << "\r" + << "Downloaded " << current << " bytes out of " << total; + return true; + }; + + std::atomic requestCompleted(false); + std::atomic statusCode0(0); + std::atomic statusCode1(0); + std::atomic statusCode2(0); + + for (int i = 0; i < 3; ++i) + { + httpClient.performRequest( + args, + [i, &requestCompleted, &statusCode0, &statusCode1, &statusCode2]( + const HttpResponsePtr& response) { + std::cerr << "Upload size: " << response->uploadSize << std::endl; + std::cerr << "Download size: " << response->downloadSize << std::endl; + std::cerr << "Status: " << response->statusCode << std::endl; + std::cerr << "Error message: " << response->errorMsg << std::endl; + + // In case of failure, print response->errorMsg + if (i == 0) + { + statusCode0 = response->statusCode; + } + else if (i == 1) + { + statusCode1 = response->statusCode; + } + else if (i == 2) + { + statusCode2 = response->statusCode; + requestCompleted = true; + } + }); + } + + int wait = 0; + while (wait < 10000) + { + if (requestCompleted) break; + + std::chrono::duration duration(10); + std::this_thread::sleep_for(duration); + wait += 10; + } + + std::cerr << "Done" << std::endl; + REQUIRE(statusCode0 == 200); + REQUIRE(statusCode1 == 200); + REQUIRE(statusCode2 == 200); + } +} diff --git a/extern/IXWebSocket-11.3.2/test/IXHttpServerTest.cpp b/extern/IXWebSocket-11.3.2/test/IXHttpServerTest.cpp new file mode 100644 index 0000000000..e06f958cd9 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXHttpServerTest.cpp @@ -0,0 +1,219 @@ +/* + * IXSocketTest.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone. All rights reserved. + */ + +#include "catch.hpp" +#include +#include +#include +#include + +using namespace ix; + +TEST_CASE("http server", "[httpd]") +{ + SECTION("Connect to a local HTTP server") + { + int port = getFreePort(); + ix::HttpServer server(port, "127.0.0.1"); + + auto res = server.listen(); + REQUIRE(res.first); + server.start(); + + HttpClient httpClient; + WebSocketHttpHeaders headers; + + std::string url("http://127.0.0.1:"); + url += std::to_string(port); + url += "/data/foo.txt"; + auto args = httpClient.createRequest(url); + + args->extraHeaders = headers; + args->connectTimeout = 60; + args->transferTimeout = 60; + args->followRedirects = true; + args->maxRedirects = 10; + args->verbose = true; + args->compress = true; + args->logger = [](const std::string& msg) { std::cout << msg; }; + args->onProgressCallback = [](int current, int total) -> bool { + std::cerr << "\r" + << "Downloaded " << current << " bytes out of " << total; + return true; + }; + + auto response = httpClient.get(url, args); + + for (auto it : response->headers) + { + std::cerr << it.first << ": " << it.second << std::endl; + } + + std::cerr << "Upload size: " << response->uploadSize << std::endl; + std::cerr << "Download size: " << response->downloadSize << std::endl; + std::cerr << "Status: " << response->statusCode << std::endl; + std::cerr << "Error message: " << response->errorMsg << std::endl; + + REQUIRE(response->errorCode == HttpErrorCode::Ok); + REQUIRE(response->statusCode == 200); + REQUIRE(response->headers["Accept-Encoding"] == "gzip"); + + server.stop(); + } + + SECTION("Posting plain text data to a local HTTP server") + { + int port = getFreePort(); + ix::HttpServer server(port, "127.0.0.1"); + + server.setOnConnectionCallback( + [](HttpRequestPtr request, std::shared_ptr) -> HttpResponsePtr { + if (request->method == "POST") + { + return std::make_shared( + 200, "OK", HttpErrorCode::Ok, WebSocketHttpHeaders(), request->body); + } + + return std::make_shared(400, "BAD REQUEST"); + }); + + auto res = server.listen(); + REQUIRE(res.first); + server.start(); + + HttpClient httpClient; + WebSocketHttpHeaders headers; + headers["Content-Type"] = "text/plain"; + + std::string url("http://127.0.0.1:"); + url += std::to_string(port); + auto args = httpClient.createRequest(url); + + args->extraHeaders = headers; + args->connectTimeout = 60; + args->transferTimeout = 60; + args->verbose = true; + args->logger = [](const std::string& msg) { std::cout << msg; }; + args->body = "Hello World!"; + + auto response = httpClient.post(url, args->body, args); + + std::cerr << "Status: " << response->statusCode << std::endl; + std::cerr << "Error message: " << response->errorMsg << std::endl; + std::cerr << "Body: " << response->body << std::endl; + + REQUIRE(response->errorCode == HttpErrorCode::Ok); + REQUIRE(response->statusCode == 200); + REQUIRE(response->body == args->body); + + server.stop(); + } +} + +TEST_CASE("http server redirection", "[httpd_redirect]") +{ + SECTION( + "Connect to a local HTTP server, with redirection enabled, but we do not follow redirects") + { + int port = getFreePort(); + ix::HttpServer server(port, "127.0.0.1"); + server.makeRedirectServer("http://example.com"); + + auto res = server.listen(); + REQUIRE(res.first); + server.start(); + + HttpClient httpClient; + WebSocketHttpHeaders headers; + + std::string url("http://127.0.0.1:"); + url += std::to_string(port); + url += "/data/foo.txt"; + auto args = httpClient.createRequest(url); + + args->extraHeaders = headers; + args->connectTimeout = 60; + args->transferTimeout = 60; + args->followRedirects = false; // we dont want to follow redirect during testing + args->maxRedirects = 10; + args->verbose = true; + args->compress = true; + args->logger = [](const std::string& msg) { std::cout << msg; }; + args->onProgressCallback = [](int current, int total) -> bool { + std::cerr << "\r" + << "Downloaded " << current << " bytes out of " << total; + return true; + }; + + auto response = httpClient.get(url, args); + + for (auto it : response->headers) + { + std::cerr << it.first << ": " << it.second << std::endl; + } + + std::cerr << "Upload size: " << response->uploadSize << std::endl; + std::cerr << "Download size: " << response->downloadSize << std::endl; + std::cerr << "Status: " << response->statusCode << std::endl; + std::cerr << "Error message: " << response->errorMsg << std::endl; + + REQUIRE(response->errorCode == HttpErrorCode::Ok); + REQUIRE(response->statusCode == 301); + REQUIRE(response->headers["Location"] == "http://example.com"); + + server.stop(); + } + + SECTION("Connect to a local HTTP server, with redirection enabled, but we do follow redirects") + { + int port = getFreePort(); + ix::HttpServer server(port, "127.0.0.1"); + server.makeRedirectServer("http://www.google.com"); + + auto res = server.listen(); + REQUIRE(res.first); + server.start(); + + HttpClient httpClient; + WebSocketHttpHeaders headers; + + std::string url("http://127.0.0.1:"); + url += std::to_string(port); + url += "/data/foo.txt"; + auto args = httpClient.createRequest(url); + + args->extraHeaders = headers; + args->connectTimeout = 60; + args->transferTimeout = 60; + args->followRedirects = true; + args->maxRedirects = 10; + args->verbose = true; + args->compress = true; + args->logger = [](const std::string& msg) { std::cout << msg; }; + args->onProgressCallback = [](int current, int total) -> bool { + std::cerr << "\r" + << "Downloaded " << current << " bytes out of " << total; + return true; + }; + + auto response = httpClient.get(url, args); + + for (auto it : response->headers) + { + std::cerr << it.first << ": " << it.second << std::endl; + } + + std::cerr << "Upload size: " << response->uploadSize << std::endl; + std::cerr << "Download size: " << response->downloadSize << std::endl; + std::cerr << "Status: " << response->statusCode << std::endl; + std::cerr << "Error message: " << response->errorMsg << std::endl; + + REQUIRE(response->errorCode == HttpErrorCode::Ok); + REQUIRE(response->statusCode == 200); + + server.stop(); + } +} diff --git a/extern/IXWebSocket-11.3.2/test/IXHttpTest.cpp b/extern/IXWebSocket-11.3.2/test/IXHttpTest.cpp new file mode 100644 index 0000000000..421faafc32 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXHttpTest.cpp @@ -0,0 +1,53 @@ +/* + * IXHttpTest.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone. All rights reserved. + */ + +#include "catch.hpp" +#include +#include +#include + +namespace ix +{ + TEST_CASE("http", "[http]") + { + SECTION("Normal case") + { + std::string line = "HTTP/1.1 200"; + auto result = Http::parseStatusLine(line); + + REQUIRE(result.first == "HTTP/1.1"); + REQUIRE(result.second == 200); + } + + SECTION("http/1.0 case") + { + std::string line = "HTTP/1.0 200"; + auto result = Http::parseStatusLine(line); + + REQUIRE(result.first == "HTTP/1.0"); + REQUIRE(result.second == 200); + } + + SECTION("empty case") + { + std::string line = ""; + auto result = Http::parseStatusLine(line); + + REQUIRE(result.first == ""); + REQUIRE(result.second == -1); + } + + SECTION("empty case") + { + std::string line = "HTTP/1.1"; + auto result = Http::parseStatusLine(line); + + REQUIRE(result.first == "HTTP/1.1"); + REQUIRE(result.second == -1); + } + } + +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/test/IXSentryClientTest.cpp b/extern/IXWebSocket-11.3.2/test/IXSentryClientTest.cpp new file mode 100644 index 0000000000..f2e154a871 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXSentryClientTest.cpp @@ -0,0 +1,42 @@ +/* + * IXSentryClientTest.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone. All rights reserved. + * + * (cd .. ; make) && ../build/test/ixwebsocket_unittest sentry + */ + +#include "catch.hpp" +#include +#include +#include + +using namespace ix; + +namespace ix +{ + TEST_CASE("sentry", "[sentry]") + { + SECTION("Attempt to index nil") + { + SentryClient sentryClient(""); + std::string stack = "Attempt to index nil[overlay]!\nstack traceback:\n\tfoo.lua:2661: " + "in function 'getFoo'\n\tfoo.lua:1666: in function " + "'onUpdate'\n\tfoo.lua:1751: in function "; + auto frames = sentryClient.parseLuaStackTrace(stack); + + REQUIRE(frames.size() == 3); + } + + SECTION("Attempt to perform nil") + { + SentryClient sentryClient(""); + std::string stack = "Attempt to perform nil - 1572111278.299\nstack " + "traceback:\n\tfoo.lua:57: in function "; + auto frames = sentryClient.parseLuaStackTrace(stack); + + REQUIRE(frames.size() == 1); + } + } + +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/test/IXSocketConnectTest.cpp b/extern/IXWebSocket-11.3.2/test/IXSocketConnectTest.cpp new file mode 100644 index 0000000000..3952bb49d1 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXSocketConnectTest.cpp @@ -0,0 +1,50 @@ +/* + * IXSocketConnectTest.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone. All rights reserved. + */ + +#include "IXTest.h" +#include "catch.hpp" +#include +#include + +using namespace ix; + + +TEST_CASE("socket_connect", "[net]") +{ + SECTION("Test connecting to a known hostname") + { + int port = getFreePort(); + ix::WebSocketServer server(port); + REQUIRE(startWebSocketEchoServer(server)); + + std::string errMsg; + int fd = SocketConnect::connect("127.0.0.1", port, errMsg, [] { return false; }); + std::cerr << "Error message: " << errMsg << std::endl; + REQUIRE(fd != -1); + } + + SECTION("Test connecting to a non-existing hostname") + { + std::string errMsg; + std::string hostname("12313lhjlkjhopiupoijlkasdckljqwehrlkqjwehraospidcuaposidcasdc"); + int fd = SocketConnect::connect(hostname, 80, errMsg, [] { return false; }); + std::cerr << "Error message: " << errMsg << std::endl; + REQUIRE(fd == -1); + } + + SECTION("Test connecting to a good hostname, with cancellation") + { + int port = getFreePort(); + ix::WebSocketServer server(port); + REQUIRE(startWebSocketEchoServer(server)); + + std::string errMsg; + // The callback returning true means we are requesting cancellation + int fd = SocketConnect::connect("127.0.0.1", port, errMsg, [] { return true; }); + std::cerr << "Error message: " << errMsg << std::endl; + REQUIRE(fd == -1); + } +} diff --git a/extern/IXWebSocket-11.3.2/test/IXSocketTest.cpp b/extern/IXWebSocket-11.3.2/test/IXSocketTest.cpp new file mode 100644 index 0000000000..ca4237065f --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXSocketTest.cpp @@ -0,0 +1,98 @@ +/* + * IXSocketTest.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone. All rights reserved. + */ + +#include "IXTest.h" +#include "catch.hpp" +#include +#include +#include +#include +#include + +using namespace ix; + +namespace ix +{ + void testSocket(const std::string& host, + int port, + const std::string& request, + std::shared_ptr socket, + int expectedStatus, + int timeoutSecs) + { + std::string errMsg; + static std::atomic requestInitCancellation(false); + auto isCancellationRequested = + makeCancellationRequestWithTimeout(timeoutSecs, requestInitCancellation); + + bool success = socket->connect(host, port, errMsg, isCancellationRequested); + TLogger() << "errMsg: " << errMsg; + REQUIRE(success); + + TLogger() << "Sending request: " << request << "to " << host << ":" << port; + REQUIRE(socket->writeBytes(request, isCancellationRequested)); + + auto lineResult = socket->readLine(isCancellationRequested); + auto lineValid = lineResult.first; + auto line = lineResult.second; + + TLogger() << "read error: " << strerror(Socket::getErrno()); + + REQUIRE(lineValid); + + int status = -1; + REQUIRE(sscanf(line.c_str(), "HTTP/1.1 %d", &status) == 1); + REQUIRE(status == expectedStatus); + } +} // namespace ix + +TEST_CASE("socket", "[socket]") +{ + SECTION("Connect to a local websocket server over a free port. Send GET request without " + "header. Should return 400") + { + // Start a server first which we'll hit with our socket code + int port = getFreePort(); + ix::WebSocketServer server(port); + REQUIRE(startWebSocketEchoServer(server)); + + std::string errMsg; + bool tls = false; + SocketTLSOptions tlsOptions; + std::shared_ptr socket = createSocket(tls, -1, errMsg, tlsOptions); + std::string host("127.0.0.1"); + + std::stringstream ss; + ss << "GET / HTTP/1.1\r\n"; + ss << "Host: " << host << "\r\n"; + ss << "\r\n"; + std::string request(ss.str()); + + int expectedStatus = 400; + int timeoutSecs = 3; + + testSocket(host, port, request, socket, expectedStatus, timeoutSecs); + } + +#if defined(IXWEBSOCKET_USE_TLS) + SECTION("Connect to google HTTPS server over port 443. Send GET request without header. Should " + "return 200") + { + std::string errMsg; + bool tls = true; + SocketTLSOptions tlsOptions; + tlsOptions.caFile = "cacert.pem"; + std::shared_ptr socket = createSocket(tls, -1, errMsg, tlsOptions); + std::string host("www.google.com"); + int port = 443; + std::string request("GET / HTTP/1.1\r\n\r\n"); + int expectedStatus = 200; + int timeoutSecs = 3; + + testSocket(host, port, request, socket, expectedStatus, timeoutSecs); + } +#endif +} diff --git a/extern/IXWebSocket-11.3.2/test/IXStrCaseCompareTest.cpp b/extern/IXWebSocket-11.3.2/test/IXStrCaseCompareTest.cpp new file mode 100644 index 0000000000..8a4408d46f --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXStrCaseCompareTest.cpp @@ -0,0 +1,47 @@ +/* + * IXStrCaseCompareTest.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2020 Machine Zone. All rights reserved. + */ + +#include "IXTest.h" +#include "catch.hpp" +#include +#include +#include + +using namespace ix; + +namespace ix +{ + TEST_CASE("str_case_compare", "[str_case_compare]") + { + SECTION("1") + { + using HttpHeaders = std::map; + + 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; + + HttpHeaders headers; + + headers["Upgrade"] = "webSocket"; + + REQUIRE(!CaseInsensitiveLess::cmp(headers["upGRADE"], "webSocket")); + } + } + +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/test/IXStreamSqlTest.cpp b/extern/IXWebSocket-11.3.2/test/IXStreamSqlTest.cpp new file mode 100644 index 0000000000..e14c126de9 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXStreamSqlTest.cpp @@ -0,0 +1,42 @@ +/* + * IXStreamSqlTest.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2020 Machine Zone. All rights reserved. + */ + +#include "IXTest.h" +#include "catch.hpp" +#include +#include +#include + +using namespace ix; + +namespace ix +{ + TEST_CASE("stream_sql", "[streamsql]") + { + SECTION("expression A") + { + snake::StreamSql streamSql( + "select * from subscriber_republished_v1_neo where session LIKE '%123456%'"); + + nlohmann::json msg = {{"session", "123456"}, {"id", "foo_id"}, {"timestamp", 12}}; + + CHECK(streamSql.match(msg)); + } + + SECTION("expression A") + { + snake::StreamSql streamSql("select * from `subscriber_republished_v1_neo` where " + "session = '30091320ed8d4e50b758f8409b83bed7'"); + + nlohmann::json msg = {{"session", "30091320ed8d4e50b758f8409b83bed7"}, + {"id", "foo_id"}, + {"timestamp", 12}}; + + CHECK(streamSql.match(msg)); + } + } + +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/test/IXTest.cpp b/extern/IXWebSocket-11.3.2/test/IXTest.cpp new file mode 100644 index 0000000000..a07caa68a8 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXTest.cpp @@ -0,0 +1,208 @@ +/* + * IXTest.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone. All rights reserved. + */ + +#include "IXTest.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace ix +{ + std::atomic incomingBytes(0); + std::atomic outgoingBytes(0); + std::mutex TLogger::_mutex; + std::stack freePorts; + + void setupWebSocketTrafficTrackerCallback() + { + ix::WebSocket::setTrafficTrackerCallback([](size_t size, bool incoming) { + if (incoming) + { + incomingBytes += size; + } + else + { + outgoingBytes += size; + } + }); + } + + void reportWebSocketTraffic() + { + TLogger() << incomingBytes; + TLogger() << "Incoming bytes: " << incomingBytes; + TLogger() << "Outgoing bytes: " << outgoingBytes; + } + + void msleep(int ms) + { + std::chrono::duration duration(ms); + std::this_thread::sleep_for(duration); + } + + std::string generateSessionId() + { + auto now = std::chrono::system_clock::now(); + auto seconds = + std::chrono::duration_cast(now.time_since_epoch()).count(); + + return std::to_string(seconds); + } + + void log(const std::string& msg) + { + TLogger() << msg; + } + + void hexDump(const std::string& prefix, const std::string& s) + { + std::ostringstream ss; + bool upper_case = false; + + for (std::string::size_type i = 0; i < s.length(); ++i) + { + ss << std::hex << std::setfill('0') << std::setw(2) + << (upper_case ? std::uppercase : std::nouppercase) << (int) s[i]; + } + + std::cout << prefix << ": " << s << " => " << ss.str() << std::endl; + } + + bool startWebSocketEchoServer(ix::WebSocketServer& server) + { + server.setOnClientMessageCallback( + [&server](std::shared_ptr connectionState, + WebSocket& webSocket, + const ix::WebSocketMessagePtr& msg) { + auto remoteIp = connectionState->getRemoteIp(); + if (msg->type == ix::WebSocketMessageType::Open) + { + TLogger() << "New connection"; + TLogger() << "Remote ip: " << remoteIp; + TLogger() << "Uri: " << msg->openInfo.uri; + TLogger() << "Headers:"; + for (auto it : msg->openInfo.headers) + { + TLogger() << it.first << ": " << it.second; + } + } + else if (msg->type == ix::WebSocketMessageType::Close) + { + TLogger() << "Closed connection"; + } + else if (msg->type == ix::WebSocketMessageType::Message) + { + for (auto&& client : server.getClients()) + { + if (client.get() != &webSocket) + { + client->send(msg->str, msg->binary); + } + } + } + }); + + auto res = server.listen(); + if (!res.first) + { + TLogger() << res.second; + return false; + } + + server.start(); + return true; + } + + std::vector load(const std::string& path) + { + std::vector memblock; + + std::ifstream file(path); + if (!file.is_open()) return memblock; + + file.seekg(0, file.end); + std::streamoff size = file.tellg(); + file.seekg(0, file.beg); + + memblock.reserve((size_t) size); + memblock.insert( + memblock.begin(), std::istream_iterator(file), std::istream_iterator()); + + return memblock; + } + + std::string readAsString(const std::string& path) + { + auto vec = load(path); + return std::string(vec.begin(), vec.end()); + } + + SocketTLSOptions makeClientTLSOptions() + { + SocketTLSOptions tlsOptionsClient; + tlsOptionsClient.certFile = ".certs/trusted-client-crt.pem"; + tlsOptionsClient.keyFile = ".certs/trusted-client-key.pem"; + tlsOptionsClient.caFile = ".certs/trusted-ca-crt.pem"; + + return tlsOptionsClient; + } + + SocketTLSOptions makeServerTLSOptions(bool preferTLS) + { + // Start a fake sentry http server + SocketTLSOptions tlsOptionsServer; + tlsOptionsServer.certFile = ".certs/trusted-server-crt.pem"; + tlsOptionsServer.keyFile = ".certs/trusted-server-key.pem"; + tlsOptionsServer.caFile = ".certs/trusted-ca-crt.pem"; + +#if defined(IXWEBSOCKET_USE_MBED_TLS) || defined(IXWEBSOCKET_USE_OPEN_SSL) + tlsOptionsServer.tls = preferTLS; +#else + tlsOptionsServer.tls = false; +#endif + return tlsOptionsServer; + } + + std::string getHttpScheme() + { +#if defined(IXWEBSOCKET_USE_MBED_TLS) || defined(IXWEBSOCKET_USE_OPEN_SSL) + std::string scheme("https://"); +#else + std::string scheme("http://"); +#endif + return scheme; + } + + std::string getWsScheme(bool preferTLS) + { + std::string scheme; +#if defined(IXWEBSOCKET_USE_MBED_TLS) || defined(IXWEBSOCKET_USE_OPEN_SSL) + if (preferTLS) + { + scheme = "wss://"; + } + else + { + scheme = "ws://"; + } +#else + scheme = "ws://"; +#endif + return scheme; + } +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/test/IXTest.h b/extern/IXWebSocket-11.3.2/test/IXTest.h new file mode 100644 index 0000000000..27b3a93c8c --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXTest.h @@ -0,0 +1,57 @@ +/* + * IXTest.h + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone. All rights reserved. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ix +{ + // Sleep for ms milliseconds. + void msleep(int ms); + + // Generate a relatively random string + std::string generateSessionId(); + + // Record and report websocket traffic + void setupWebSocketTrafficTrackerCallback(); + void reportWebSocketTraffic(); + + struct TLogger + { + public: + template + TLogger& operator<<(T const& obj) + { + std::lock_guard lock(_mutex); + + std::stringstream ss; + ss << obj; + spdlog::info(ss.str()); + return *this; + } + + private: + static std::mutex _mutex; + }; + + void log(const std::string& msg); + + bool startWebSocketEchoServer(ix::WebSocketServer& server); + + SocketTLSOptions makeClientTLSOptions(); + SocketTLSOptions makeServerTLSOptions(bool preferTLS); + std::string getHttpScheme(); + std::string getWsScheme(bool preferTLS); +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/test/IXUnityBuildsTest.cpp b/extern/IXWebSocket-11.3.2/test/IXUnityBuildsTest.cpp new file mode 100644 index 0000000000..518523eac5 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXUnityBuildsTest.cpp @@ -0,0 +1,52 @@ +/* + * IXUnityBuildsTest.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone. All rights reserved. + */ + +#include "catch.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ix; + +TEST_CASE("unity build", "[unity_build]") +{ + SECTION("dummy test") + { + REQUIRE(true); + } +} diff --git a/extern/IXWebSocket-11.3.2/test/IXUrlParserTest.cpp b/extern/IXWebSocket-11.3.2/test/IXUrlParserTest.cpp new file mode 100644 index 0000000000..cee3f3bfa7 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXUrlParserTest.cpp @@ -0,0 +1,118 @@ +/* + * IXSocketTest.cpp + * Author: Korchynskyi Dmytro + * Copyright (c) 2019 Machine Zone. All rights reserved. + */ + +#include "IXTest.h" +#include "catch.hpp" +#include +#include +#include + +using namespace ix; + +namespace ix +{ + TEST_CASE("urlParser", "[urlParser]") + { + SECTION("http://google.com") + { + std::string url = "http://google.com"; + std::string protocol, host, path, query; + int port; + bool res; + + res = UrlParser::parse(url, protocol, host, path, query, port); + + REQUIRE(res); + REQUIRE(protocol == "http"); + REQUIRE(host == "google.com"); + REQUIRE(path == "/"); + REQUIRE(query == ""); + REQUIRE(port == 80); // default port for http + } + + SECTION("https://google.com") + { + std::string url = "https://google.com"; + std::string protocol, host, path, query; + int port; + bool res; + + res = UrlParser::parse(url, protocol, host, path, query, port); + + REQUIRE(res); + REQUIRE(protocol == "https"); + REQUIRE(host == "google.com"); + REQUIRE(path == "/"); + REQUIRE(query == ""); + REQUIRE(port == 443); // default port for https + } + + SECTION("ws://google.com") + { + std::string url = "ws://google.com"; + std::string protocol, host, path, query; + int port; + bool res; + + res = UrlParser::parse(url, protocol, host, path, query, port); + + REQUIRE(res); + REQUIRE(protocol == "ws"); + REQUIRE(host == "google.com"); + REQUIRE(path == "/"); + REQUIRE(query == ""); + REQUIRE(port == 80); // default port for ws + } + + SECTION("wss://google.com/ws?arg=value") + { + std::string url = "wss://google.com/ws?arg=value&arg2=value2"; + std::string protocol, host, path, query; + int port; + bool res; + + res = UrlParser::parse(url, protocol, host, path, query, port); + + REQUIRE(res); + REQUIRE(protocol == "wss"); + REQUIRE(host == "google.com"); + REQUIRE(path == "/ws?arg=value&arg2=value2"); + REQUIRE(query == "arg=value&arg2=value2"); + REQUIRE(port == 443); // default port for wss + } + + SECTION("real test") + { + std::string url = + "ws://127.0.0.1:7350/" + "ws?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." + "eyJleHAiOjE1NTcxNzAwNzIsInVpZCI6ImMwZmZjOGE1LTk4OTktNDAwYi1hNGU5LTJjNWM3NjFmNWQxZi" + "IsInVzbiI6InN2YmhOdlNJSmEifQ.5L8BUbpTA4XAHlSrdwhIVlrlIpRtjExepim7Yh5eEO4&status=" + "true&format=protobuf"; + std::string protocol, host, path, query; + int port; + bool res; + + res = UrlParser::parse(url, protocol, host, path, query, port); + + REQUIRE(res); + REQUIRE(protocol == "ws"); + REQUIRE(host == "127.0.0.1"); + REQUIRE(path == + "/ws?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." + "eyJleHAiOjE1NTcxNzAwNzIsInVpZCI6ImMwZmZjOGE1LTk4OTktNDAwYi1hNGU5LTJjNWM3NjFmNW" + "QxZiIsInVzbiI6InN2YmhOdlNJSmEifQ.5L8BUbpTA4XAHlSrdwhIVlrlIpRtjExepim7Yh5eEO4&" + "status=true&format=protobuf"); + REQUIRE(query == + "token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." + "eyJleHAiOjE1NTcxNzAwNzIsInVpZCI6ImMwZmZjOGE1LTk4OTktNDAwYi1hNGU5LTJjNWM3NjFmNW" + "QxZiIsInVzbiI6InN2YmhOdlNJSmEifQ.5L8BUbpTA4XAHlSrdwhIVlrlIpRtjExepim7Yh5eEO4&" + "status=true&format=protobuf"); + REQUIRE(port == 7350); + } + } + +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/test/IXWebSocketBroadcastTest.cpp b/extern/IXWebSocket-11.3.2/test/IXWebSocketBroadcastTest.cpp new file mode 100644 index 0000000000..4032447629 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXWebSocketBroadcastTest.cpp @@ -0,0 +1,308 @@ +/* + * IXWebSocketServerTest.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone. All rights reserved. + */ + +#include "IXTest.h" +#include "catch.hpp" +#include "msgpack11.hpp" +#include +#include +#include +#include +#include + +using msgpack11::MsgPack; +using namespace ix; + +namespace +{ + class WebSocketBroadcastChat + { + public: + WebSocketBroadcastChat(const std::string& user, const std::string& session, int port); + + void subscribe(const std::string& channel); + void start(); + void stop(); + bool isReady() const; + + void sendMessage(const std::string& text); + size_t getReceivedMessagesCount() const; + const std::vector& getReceivedMessages() const; + + std::string encodeMessage(const std::string& text); + std::pair decodeMessage(const std::string& str); + void appendMessage(const std::string& message); + + private: + std::string _user; + std::string _session; + int _port; + + ix::WebSocket _webSocket; + + std::vector _receivedMessages; + mutable std::mutex _mutex; + }; + + WebSocketBroadcastChat::WebSocketBroadcastChat(const std::string& user, + const std::string& session, + int port) + : _user(user) + , _session(session) + , _port(port) + { + _webSocket.setTLSOptions(makeClientTLSOptions()); + } + + size_t WebSocketBroadcastChat::getReceivedMessagesCount() const + { + std::lock_guard lock(_mutex); + return _receivedMessages.size(); + } + + const std::vector& WebSocketBroadcastChat::getReceivedMessages() const + { + std::lock_guard lock(_mutex); + return _receivedMessages; + } + + void WebSocketBroadcastChat::appendMessage(const std::string& message) + { + std::lock_guard lock(_mutex); + _receivedMessages.push_back(message); + } + + bool WebSocketBroadcastChat::isReady() const + { + return _webSocket.getReadyState() == ix::ReadyState::Open; + } + + void WebSocketBroadcastChat::stop() + { + _webSocket.stop(); + } + + void WebSocketBroadcastChat::start() + { + // + // Which server ?? + // + std::string url; + _webSocket.setUrl(url); + + std::stringstream ss; + log(std::string("Connecting to url: ") + url); + + _webSocket.setOnMessageCallback([this](const ix::WebSocketMessagePtr& msg) { + std::stringstream ss; + if (msg->type == ix::WebSocketMessageType::Open) + { + ss << "websocket_broadcast_client: " << _user << " Connected !"; + log(ss.str()); + } + else if (msg->type == ix::WebSocketMessageType::Close) + { + ss << "websocket_broadcast_client: " << _user << " disconnected !"; + log(ss.str()); + } + else if (msg->type == ix::WebSocketMessageType::Message) + { + auto result = decodeMessage(msg->str); + + // Our "chat" / "broacast" node.js server does not send us + // the messages we send, so we don't need to have a msg_user != user + // as we do for the satori chat example. + + // store text + appendMessage(result.second); + + std::string payload = result.second; + if (payload.size() > 2000) + { + payload = ""; + } + + ss << std::endl << result.first << " > " << payload << std::endl << _user << " > "; + log(ss.str()); + } + else if (msg->type == ix::WebSocketMessageType::Error) + { + ss << "websocket_broadcast_client: " << _user << " Error ! " + << msg->errorInfo.reason; + log(ss.str()); + } + else if (msg->type == ix::WebSocketMessageType::Ping) + { + log("websocket_broadcast_client: received ping message"); + } + else if (msg->type == ix::WebSocketMessageType::Pong) + { + log("websocket_broadcast_client: received pong message"); + } + else if (msg->type == ix::WebSocketMessageType::Fragment) + { + log("websocket_broadcast_client: received message fragment"); + } + else + { + ss << "Unexpected ix::WebSocketMessageType"; + log(ss.str()); + } + }); + + _webSocket.start(); + } + + std::pair WebSocketBroadcastChat::decodeMessage( + const std::string& str) + { + std::string errMsg; + MsgPack msg = MsgPack::parse(str, errMsg); + + std::string msg_user = msg["user"].string_value(); + std::string msg_text = msg["text"].string_value(); + + return std::pair(msg_user, msg_text); + } + + std::string WebSocketBroadcastChat::encodeMessage(const std::string& text) + { + std::map obj; + obj["user"] = _user; + obj["text"] = text; + + MsgPack msg(obj); + + std::string output = msg.dump(); + return output; + } + + void WebSocketBroadcastChat::sendMessage(const std::string& text) + { + _webSocket.sendBinary(encodeMessage(text)); + } + + bool startServer(ix::WebSocketServer& server, std::string& connectionId) + { + bool preferTLS = true; + server.setTLSOptions(makeServerTLSOptions(preferTLS)); + + server.setOnClientMessageCallback( + [&server, &connectionId](std::shared_ptr connectionState, + WebSocket& webSocket, + const ix::WebSocketMessagePtr& msg) { + auto remoteIp = connectionState->getRemoteIp(); + + if (msg->type == ix::WebSocketMessageType::Open) + { + TLogger() << "New connection"; + connectionState->computeId(); + TLogger() << "remote ip: " << remoteIp; + TLogger() << "id: " << connectionState->getId(); + TLogger() << "Uri: " << msg->openInfo.uri; + TLogger() << "Headers:"; + for (auto it : msg->openInfo.headers) + { + TLogger() << it.first << ": " << it.second; + } + + connectionId = connectionState->getId(); + } + else if (msg->type == ix::WebSocketMessageType::Close) + { + TLogger() << "Closed connection"; + } + else if (msg->type == ix::WebSocketMessageType::Message) + { + for (auto&& client : server.getClients()) + { + if (client.get() != &webSocket) + { + client->send(msg->str, msg->binary); + } + } + } + }); + + auto res = server.listen(); + if (!res.first) + { + TLogger() << res.second; + return false; + } + + server.start(); + return true; + } +} // namespace + +TEST_CASE("Websocket_broadcast_server", "[websocket_server]") +{ + SECTION("Connect to the server, do not send anything. Should timeout and return 400") + { + int port = getFreePort(); + ix::WebSocketServer server(port); + std::string connectionId; + REQUIRE(startServer(server, connectionId)); + + std::string session = ix::generateSessionId(); + std::vector> chatClients; + for (int i = 0; i < 10; ++i) + { + std::string user("user_" + std::to_string(i)); + chatClients.push_back(std::make_shared(user, session, port)); + chatClients[i]->start(); + ix::msleep(50); + } + + // Wait for all chat instance to be ready + while (true) + { + bool allReady = true; + for (size_t i = 0; i < chatClients.size(); ++i) + { + allReady &= chatClients[i]->isReady(); + } + if (allReady) break; + ix::msleep(10); + } + + for (int j = 0; j < 1000; j++) + { + for (size_t i = 0; i < chatClients.size(); ++i) + { + chatClients[i]->sendMessage("hello world"); + } + + if (j == 250) + { + server.stop(); + ix::msleep(100); + } + if (j == 500) + { + server.start(); + ix::msleep(100); + } + } + + // wait 1 second + ix::msleep(2000); + + // Stop all clients + size_t messageCount = chatClients.size() * 50; + for (size_t i = 0; i < chatClients.size(); ++i) + { + REQUIRE(chatClients[i]->getReceivedMessagesCount() >= messageCount); + chatClients[i]->stop(); + } + + // Give us 500ms for the server to notice that clients went away + ix::msleep(500); + server.stop(); + REQUIRE(server.getClients().size() == 0); + } +} diff --git a/extern/IXWebSocket-11.3.2/test/IXWebSocketChatTest.cpp b/extern/IXWebSocket-11.3.2/test/IXWebSocketChatTest.cpp new file mode 100644 index 0000000000..16c81319dd --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXWebSocketChatTest.cpp @@ -0,0 +1,312 @@ +/* + * cmd_websocket_chat.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2017 Machine Zone. All rights reserved. + */ + +// +// Simple chat program that talks to the node.js server at +// websocket_chat_server/broacast-server.js +// + +#include "IXTest.h" +#include "catch.hpp" +#include "msgpack11.hpp" +#include +#include +#include +#include +#include +#include + +using msgpack11::MsgPack; +using namespace ix; + +namespace +{ + class WebSocketChat + { + public: + WebSocketChat(const std::string& user, const std::string& session, int port); + + void subscribe(const std::string& channel); + void start(); + void stop(); + bool isReady() const; + + void sendMessage(const std::string& text); + size_t getReceivedMessagesCount() const; + const std::vector& getReceivedMessages() const; + + std::string encodeMessage(const std::string& text); + std::pair decodeMessage(const std::string& str); + void appendMessage(const std::string& message); + + private: + std::string _user; + std::string _session; + int _port; + + ix::WebSocket _webSocket; + + std::vector _receivedMessages; + mutable std::mutex _mutex; + }; + + WebSocketChat::WebSocketChat(const std::string& user, const std::string& session, int port) + : _user(user) + , _session(session) + , _port(port) + { + ; + } + + size_t WebSocketChat::getReceivedMessagesCount() const + { + std::lock_guard lock(_mutex); + return _receivedMessages.size(); + } + + const std::vector& WebSocketChat::getReceivedMessages() const + { + std::lock_guard lock(_mutex); + return _receivedMessages; + } + + void WebSocketChat::appendMessage(const std::string& message) + { + std::lock_guard lock(_mutex); + _receivedMessages.push_back(message); + } + + bool WebSocketChat::isReady() const + { + return _webSocket.getReadyState() == ix::ReadyState::Open; + } + + void WebSocketChat::stop() + { + _webSocket.stop(); + } + + void WebSocketChat::start() + { + std::string url; + { + std::stringstream ss; + ss << "ws://127.0.0.1:" << _port << "/" << _user; + + url = ss.str(); + } + + _webSocket.setUrl(url); + + std::stringstream ss; + log(std::string("Connecting to url: ") + url); + + _webSocket.setOnMessageCallback([this](const ix::WebSocketMessagePtr& msg) { + std::stringstream ss; + if (msg->type == ix::WebSocketMessageType::Open) + { + ss << "cmd_websocket_chat: user " << _user << " Connected !"; + log(ss.str()); + } + else if (msg->type == ix::WebSocketMessageType::Close) + { + ss << "cmd_websocket_chat: user " << _user << " disconnected !"; + log(ss.str()); + } + else if (msg->type == ix::WebSocketMessageType::Message) + { + auto result = decodeMessage(msg->str); + + // Our "chat" / "broacast" node.js server does not send us + // the messages we send, so we don't need to have a msg_user != user + // as we do for the satori chat example. + + // store text + appendMessage(result.second); + + std::string payload = result.second; + if (payload.size() > 2000) + { + payload = ""; + } + + ss << std::endl << result.first << " > " << payload << std::endl << _user << " > "; + log(ss.str()); + } + else if (msg->type == ix::WebSocketMessageType::Error) + { + ss << "cmd_websocket_chat: Error ! " << msg->errorInfo.reason; + log(ss.str()); + } + else if (msg->type == ix::WebSocketMessageType::Ping) + { + log("cmd_websocket_chat: received ping message"); + } + else if (msg->type == ix::WebSocketMessageType::Pong) + { + log("cmd_websocket_chat: received pong message"); + } + else if (msg->type == ix::WebSocketMessageType::Fragment) + { + log("cmd_websocket_chat: received message fragment"); + } + else + { + ss << "Unexpected ix::WebSocketMessageType"; + log(ss.str()); + } + }); + + _webSocket.start(); + } + + std::pair WebSocketChat::decodeMessage(const std::string& str) + { + std::string errMsg; + MsgPack msg = MsgPack::parse(str, errMsg); + + std::string msg_user = msg["user"].string_value(); + std::string msg_text = msg["text"].string_value(); + + return std::pair(msg_user, msg_text); + } + + std::string WebSocketChat::encodeMessage(const std::string& text) + { + std::map obj; + obj["user"] = _user; + obj["text"] = text; + + MsgPack msg(obj); + + std::string output = msg.dump(); + return output; + } + + void WebSocketChat::sendMessage(const std::string& text) + { + _webSocket.sendBinary(encodeMessage(text)); + } + + bool startServer(ix::WebSocketServer& server) + { + server.setOnClientMessageCallback( + [&server](std::shared_ptr connectionState, + WebSocket& webSocket, + const ix::WebSocketMessagePtr& msg) { + auto remoteIp = connectionState->getRemoteIp(); + if (msg->type == ix::WebSocketMessageType::Open) + { + TLogger() << "New connection"; + TLogger() << "remote ip: " << remoteIp; + TLogger() << "id: " << connectionState->getId(); + TLogger() << "Uri: " << msg->openInfo.uri; + TLogger() << "Headers:"; + for (auto it : msg->openInfo.headers) + { + TLogger() << it.first << ": " << it.second; + } + } + else if (msg->type == ix::WebSocketMessageType::Close) + { + log("Closed connection"); + } + else if (msg->type == ix::WebSocketMessageType::Message) + { + for (auto&& client : server.getClients()) + { + if (client.get() != &webSocket) + { + client->sendBinary(msg->str); + } + } + } + }); + + auto res = server.listen(); + if (!res.first) + { + log(res.second); + return false; + } + + server.start(); + return true; + } +} // namespace + +TEST_CASE("Websocket_chat", "[websocket_chat]") +{ + SECTION("Exchange and count sent/received messages.") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = 8090; + ix::WebSocketServer server(port); + REQUIRE(startServer(server)); + + std::string session = ix::generateSessionId(); + WebSocketChat chatA("jean", session, port); + WebSocketChat chatB("paul", session, port); + + chatA.start(); + chatB.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (chatA.isReady() && chatB.isReady()) break; + ix::msleep(10); + } + + REQUIRE(server.getClients().size() == 2); + + // Add a bit of extra time, for the subscription to be active + ix::msleep(200); + + chatA.sendMessage("from A1"); + chatA.sendMessage("from A2"); + chatA.sendMessage("from A3"); + + chatB.sendMessage("from B1"); + chatB.sendMessage("from B2"); + + // Test large messages that need to be broken into small fragments + size_t size = 1 * 1024 * 1024; // ~1Mb + std::string bigMessage(size, 'a'); + chatB.sendMessage(bigMessage); + + log("Sent all messages"); + + // Wait until all messages are received. 10s timeout + int attempts = 0; + while (chatA.getReceivedMessagesCount() != 3 || chatB.getReceivedMessagesCount() != 3) + { + CHECK(attempts++ < 10); + ix::msleep(1000); + } + + chatA.stop(); + chatB.stop(); + + CHECK(chatA.getReceivedMessagesCount() == 3); + CHECK(chatB.getReceivedMessagesCount() == 3); + + CHECK(chatB.getReceivedMessages()[0] == "from A1"); + CHECK(chatB.getReceivedMessages()[1] == "from A2"); + CHECK(chatB.getReceivedMessages()[2] == "from A3"); + + CHECK(chatA.getReceivedMessages()[0] == "from B1"); + CHECK(chatA.getReceivedMessages()[1] == "from B2"); + CHECK(chatA.getReceivedMessages()[2].size() == bigMessage.size()); + + // Give us 1000ms for the server to notice that clients went away + ix::msleep(1000); + CHECK(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +} diff --git a/extern/IXWebSocket-11.3.2/test/IXWebSocketCloseTest.cpp b/extern/IXWebSocket-11.3.2/test/IXWebSocketCloseTest.cpp new file mode 100644 index 0000000000..76404d984a --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXWebSocketCloseTest.cpp @@ -0,0 +1,452 @@ +/* + * IXWebSocketCloseTest.cpp + * Author: Alexandre Konieczny + * Copyright (c) 2019 Machine Zone. All rights reserved. + */ + +#include "IXTest.h" +#include "catch.hpp" +#include +#include +#include +#include +#include + +using namespace ix; + +namespace +{ + class WebSocketClient + { + public: + WebSocketClient(int port); + + void subscribe(const std::string& channel); + void start(); + void stop(); + void stop(uint16_t code, const std::string& reason); + bool isReady() const; + + uint16_t getCloseCode(); + const std::string& getCloseReason(); + bool getCloseRemote(); + + bool hasConnectionError() const; + + private: + ix::WebSocket _webSocket; + int _port; + + mutable std::mutex _mutexCloseData; + uint16_t _closeCode; + std::string _closeReason; + bool _closeRemote; + std::atomic _connectionError; + }; + + WebSocketClient::WebSocketClient(int port) + : _port(port) + , _closeCode(0) + , _closeReason(std::string("")) + , _closeRemote(false) + , _connectionError(false) + { + ; + } + + bool WebSocketClient::hasConnectionError() const + { + return _connectionError; + } + + bool WebSocketClient::isReady() const + { + return _webSocket.getReadyState() == ix::ReadyState::Open; + } + + uint16_t WebSocketClient::getCloseCode() + { + std::lock_guard lck(_mutexCloseData); + + return _closeCode; + } + + const std::string& WebSocketClient::getCloseReason() + { + std::lock_guard lck(_mutexCloseData); + + return _closeReason; + } + + bool WebSocketClient::getCloseRemote() + { + std::lock_guard lck(_mutexCloseData); + + return _closeRemote; + } + + void WebSocketClient::stop() + { + _webSocket.stop(); + } + + void WebSocketClient::stop(uint16_t code, const std::string& reason) + { + _webSocket.stop(code, reason); + } + + void WebSocketClient::start() + { + std::string url; + { + std::stringstream ss; + ss << "ws://localhost:" << _port << "/"; + + url = ss.str(); + } + + _webSocket.setUrl(url); + _webSocket.disableAutomaticReconnection(); + + std::stringstream ss; + log(std::string("Connecting to url: ") + url); + + _webSocket.setOnMessageCallback([this](const ix::WebSocketMessagePtr& msg) { + std::stringstream ss; + if (msg->type == ix::WebSocketMessageType::Open) + { + log("client connected"); + } + else if (msg->type == ix::WebSocketMessageType::Close) + { + std::stringstream ss; + ss << "client disconnected(" << msg->closeInfo.code << "," << msg->closeInfo.reason + << ")"; + log(ss.str()); + + std::lock_guard lck(_mutexCloseData); + + _closeCode = msg->closeInfo.code; + _closeReason = std::string(msg->closeInfo.reason); + _closeRemote = msg->closeInfo.remote; + } + else if (msg->type == ix::WebSocketMessageType::Error) + { + _connectionError = true; + ss << "Error ! " << msg->errorInfo.reason; + log(ss.str()); + } + else if (msg->type == ix::WebSocketMessageType::Pong) + { + ss << "Received pong message " << msg->str; + log(ss.str()); + } + else if (msg->type == ix::WebSocketMessageType::Ping) + { + ss << "Received ping message " << msg->str; + log(ss.str()); + } + else if (msg->type == ix::WebSocketMessageType::Message) + { + ss << "Received message " << msg->str; + log(ss.str()); + } + else + { + ss << "Invalid ix::WebSocketMessageType"; + log(ss.str()); + } + }); + + _webSocket.start(); + } + + bool startServer(ix::WebSocketServer& server, + uint16_t& receivedCloseCode, + std::string& receivedCloseReason, + bool& receivedCloseRemote, + std::mutex& mutexWrite) + { + // A dev/null server + server.setOnClientMessageCallback( + [&receivedCloseCode, &receivedCloseReason, &receivedCloseRemote, &mutexWrite]( + std::shared_ptr connectionState, + WebSocket& /*webSocket*/, + const ix::WebSocketMessagePtr& msg) { + auto remoteIp = connectionState->getRemoteIp(); + if (msg->type == ix::WebSocketMessageType::Open) + { + TLogger() << "New server connection"; + TLogger() << "remote ip: " << remoteIp; + TLogger() << "id: " << connectionState->getId(); + TLogger() << "Uri: " << msg->openInfo.uri; + TLogger() << "Headers:"; + for (auto it : msg->openInfo.headers) + { + TLogger() << it.first << ": " << it.second; + } + } + else if (msg->type == ix::WebSocketMessageType::Close) + { + std::stringstream ss; + ss << "Server closed connection(" << msg->closeInfo.code << "," + << msg->closeInfo.reason << ")"; + log(ss.str()); + + std::lock_guard lck(mutexWrite); + + receivedCloseCode = msg->closeInfo.code; + receivedCloseReason = std::string(msg->closeInfo.reason); + receivedCloseRemote = msg->closeInfo.remote; + } + }); + + auto res = server.listen(); + if (!res.first) + { + log(res.second); + return false; + } + + server.start(); + return true; + } +} // namespace + +TEST_CASE("Websocket_client_close_default", "[close]") +{ + SECTION("Make sure that close code and reason was used and sent to server.") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + + uint16_t serverReceivedCloseCode(0); + bool serverReceivedCloseRemote(false); + std::string serverReceivedCloseReason(""); + std::mutex mutexWrite; + + REQUIRE(startServer(server, + serverReceivedCloseCode, + serverReceivedCloseReason, + serverReceivedCloseRemote, + mutexWrite)); + + std::string session = ix::generateSessionId(); + WebSocketClient webSocketClient(port); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + REQUIRE(!webSocketClient.hasConnectionError()); + if (webSocketClient.isReady()) break; + ix::msleep(10); + } + + REQUIRE(server.getClients().size() == 1); + + ix::msleep(500); + + webSocketClient.stop(); + + ix::msleep(500); + + // ensure client close is the same as values given + REQUIRE(webSocketClient.getCloseCode() == 1000); + REQUIRE(webSocketClient.getCloseReason() == "Normal closure"); + REQUIRE(webSocketClient.getCloseRemote() == false); + + { + std::lock_guard lck(mutexWrite); + + // Here we read the code/reason received by the server, and ensure that remote is true + REQUIRE(serverReceivedCloseCode == 1000); + REQUIRE(serverReceivedCloseReason == "Normal closure"); + REQUIRE(serverReceivedCloseRemote == true); + } + + // Give us 1000ms for the server to notice that clients went away + ix::msleep(1000); + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +} + +TEST_CASE("Websocket_client_close_params_given", "[close]") +{ + SECTION("Make sure that close code and reason was used and sent to server.") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + + uint16_t serverReceivedCloseCode(0); + bool serverReceivedCloseRemote(false); + std::string serverReceivedCloseReason(""); + std::mutex mutexWrite; + + REQUIRE(startServer(server, + serverReceivedCloseCode, + serverReceivedCloseReason, + serverReceivedCloseRemote, + mutexWrite)); + + std::string session = ix::generateSessionId(); + WebSocketClient webSocketClient(port); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(10); + } + + REQUIRE(server.getClients().size() == 1); + + ix::msleep(500); + + webSocketClient.stop(4000, "My reason"); + + ix::msleep(500); + + // ensure client close is the same as values given + REQUIRE(webSocketClient.getCloseCode() == 4000); + REQUIRE(webSocketClient.getCloseReason() == "My reason"); + REQUIRE(webSocketClient.getCloseRemote() == false); + + { + std::lock_guard lck(mutexWrite); + + // Here we read the code/reason received by the server, and ensure that remote is true + REQUIRE(serverReceivedCloseCode == 4000); + REQUIRE(serverReceivedCloseReason == "My reason"); + REQUIRE(serverReceivedCloseRemote == true); + } + + // Give us 1000ms for the server to notice that clients went away + ix::msleep(1000); + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +} + +TEST_CASE("Websocket_server_close", "[close]") +{ + SECTION("Make sure that close code and reason was read from server.") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + + uint16_t serverReceivedCloseCode(0); + bool serverReceivedCloseRemote(false); + std::string serverReceivedCloseReason(""); + std::mutex mutexWrite; + + REQUIRE(startServer(server, + serverReceivedCloseCode, + serverReceivedCloseReason, + serverReceivedCloseRemote, + mutexWrite)); + + std::string session = ix::generateSessionId(); + WebSocketClient webSocketClient(port); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(10); + } + + REQUIRE(server.getClients().size() == 1); + + ix::msleep(500); + + server.stop(); + + ix::msleep(500); + + // ensure client close is the same as values given + REQUIRE(webSocketClient.getCloseCode() == 1000); + REQUIRE(webSocketClient.getCloseReason() == "Normal closure"); + REQUIRE(webSocketClient.getCloseRemote() == true); + + { + std::lock_guard lck(mutexWrite); + + // Here we read the code/reason received by the server, and ensure that remote is true + REQUIRE(serverReceivedCloseCode == 1000); + REQUIRE(serverReceivedCloseReason == "Normal closure"); + REQUIRE(serverReceivedCloseRemote == false); + } + + // Give us 1000ms for the server to notice that clients went away + ix::msleep(1000); + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +} + +TEST_CASE("Websocket_server_close_immediatly", "[close]") +{ + SECTION("Make sure that close code and reason was read from server.") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + + uint16_t serverReceivedCloseCode(0); + bool serverReceivedCloseRemote(false); + std::string serverReceivedCloseReason(""); + std::mutex mutexWrite; + + REQUIRE(startServer(server, + serverReceivedCloseCode, + serverReceivedCloseReason, + serverReceivedCloseRemote, + mutexWrite)); + + std::string session = ix::generateSessionId(); + WebSocketClient webSocketClient(port); + + webSocketClient.start(); + + server.stop(); + + ix::msleep(500); + + // ensure client close hasn't been called + REQUIRE(webSocketClient.getCloseCode() == 0); + REQUIRE(webSocketClient.getCloseReason() == ""); + REQUIRE(webSocketClient.getCloseRemote() == false); + + { + std::lock_guard lck(mutexWrite); + + // Here we ensure that the code/reason wasn't received by the server + REQUIRE(serverReceivedCloseCode == 0); + REQUIRE(serverReceivedCloseReason == ""); + REQUIRE(serverReceivedCloseRemote == false); + } + + // Give us 1000ms for the server to notice that clients went away + ix::msleep(1000); + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +} diff --git a/extern/IXWebSocket-11.3.2/test/IXWebSocketLeakTest.cpp b/extern/IXWebSocket-11.3.2/test/IXWebSocketLeakTest.cpp new file mode 100644 index 0000000000..abbb4e2ca3 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXWebSocketLeakTest.cpp @@ -0,0 +1,183 @@ +/* + * IXWebSocketServer.cpp + * Author: Benjamin Sergeant, @marcelkauf + * Copyright (c) 2020 Machine Zone, Inc. All rights reserved. + */ + +#include "IXTest.h" +#include "catch.hpp" +#include +#include +#include +#include + +using namespace ix; + +namespace +{ + class WebSocketClient + { + public: + WebSocketClient(int port); + void start(); + void stop(); + bool isReady() const; + bool hasConnectionError() const; + + private: + ix::WebSocket _webSocket; + int _port; + std::atomic _connectionError; + }; + + WebSocketClient::WebSocketClient(int port) + : _port(port) + , _connectionError(false) + { + } + + bool WebSocketClient::hasConnectionError() const + { + return _connectionError; + } + + bool WebSocketClient::isReady() const + { + return _webSocket.getReadyState() == ix::ReadyState::Open; + } + + void WebSocketClient::stop() + { + _webSocket.stop(); + } + + void WebSocketClient::start() + { + std::string url; + { + std::stringstream ss; + ss << "ws://localhost:" << _port << "/"; + + url = ss.str(); + } + + _webSocket.setUrl(url); + _webSocket.disableAutomaticReconnection(); + + std::stringstream ss; + log(std::string("Connecting to url: ") + url); + + _webSocket.setOnMessageCallback([this](const ix::WebSocketMessagePtr& msg) { + std::stringstream ss; + if (msg->type == ix::WebSocketMessageType::Open) + { + log("client connected"); + } + else if (msg->type == ix::WebSocketMessageType::Close) + { + log("client disconnected"); + } + else if (msg->type == ix::WebSocketMessageType::Error) + { + _connectionError = true; + log("error"); + } + else if (msg->type == ix::WebSocketMessageType::Pong) + { + log("pong"); + } + else if (msg->type == ix::WebSocketMessageType::Ping) + { + log("ping"); + } + else if (msg->type == ix::WebSocketMessageType::Message) + { + log("message"); + } + else + { + log("invalid type"); + } + }); + + _webSocket.start(); + } +} // namespace + +TEST_CASE("Websocket leak test") +{ + SECTION("Websocket destructor is called when closing the connection.") + { + // stores the server websocket in order to check the use_count + std::shared_ptr webSocketPtr; + + { + int port = getFreePort(); + WebSocketServer server(port); + + server.setOnConnectionCallback( + [&webSocketPtr](std::shared_ptr webSocket, + std::shared_ptr connectionState, + std::unique_ptr connectionInfo) { + // original ptr in WebSocketServer::handleConnection and the callback argument + REQUIRE(webSocket.use_count() == 2); + webSocket->setOnMessageCallback([&webSocketPtr, webSocket, connectionState]( + const ix::WebSocketMessagePtr& msg) { + if (msg->type == ix::WebSocketMessageType::Open) + { + log(std::string("New connection id: ") + connectionState->getId()); + // original ptr in WebSocketServer::handleConnection, captured ptr of + // this callback, and ptr in WebSocketServer::_clients + REQUIRE(webSocket.use_count() == 3); + webSocketPtr = std::shared_ptr(webSocket); + REQUIRE(webSocket.use_count() == 4); + } + else if (msg->type == ix::WebSocketMessageType::Close) + { + log(std::string("Client closed connection id: ") + + connectionState->getId()); + } + else + { + log(std::string(msg->str)); + } + }); + // original ptr in WebSocketServer::handleConnection, argument of this callback, + // and captured ptr in websocket callback + REQUIRE(webSocket.use_count() == 3); + }); + + server.listen(); + server.start(); + + WebSocketClient webSocketClient(port); + webSocketClient.start(); + + while (true) + { + REQUIRE(!webSocketClient.hasConnectionError()); + if (webSocketClient.isReady()) break; + ix::msleep(10); + } + + REQUIRE(server.getClients().size() == 1); + // same value as in Open-handler above + REQUIRE(webSocketPtr.use_count() == 4); + + ix::msleep(500); + webSocketClient.stop(); + ix::msleep(500); + REQUIRE(server.getClients().size() == 0); + + // websocket should only be referenced by webSocketPtr but is still used by the + // websocket callback + REQUIRE(webSocketPtr.use_count() == 1); + webSocketPtr->setOnMessageCallback(nullptr); + // websocket should only be referenced by webSocketPtr + REQUIRE(webSocketPtr.use_count() == 1); + server.stop(); + } + // websocket should only be referenced by webSocketPtr + REQUIRE(webSocketPtr.use_count() == 1); + } +} diff --git a/extern/IXWebSocket-11.3.2/test/IXWebSocketPerMessageDeflateCompressorTest.cpp b/extern/IXWebSocket-11.3.2/test/IXWebSocketPerMessageDeflateCompressorTest.cpp new file mode 100644 index 0000000000..b067b825fc --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXWebSocketPerMessageDeflateCompressorTest.cpp @@ -0,0 +1,76 @@ +/* + * IXWebSocketPerMessageDeflateCodecTest.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2020 Machine Zone. All rights reserved. + * + * make build_test && build/test/ixwebsocket_unittest per-message-deflate-codec + */ + +#include "IXTest.h" +#include "catch.hpp" +#include +#include +#include + +using namespace ix; + +namespace ix +{ + std::string compressAndDecompress(const std::string& a) + { + std::string b, c; + + WebSocketPerMessageDeflateCompressor compressor; + compressor.init(11, true); + compressor.compress(a, b); + + WebSocketPerMessageDeflateDecompressor decompressor; + decompressor.init(11, true); + decompressor.decompress(b, c); + + return c; + } + + std::string compressAndDecompressVector(const std::string& a) + { + std::string b, c; + + std::vector vec(a.begin(), a.end()); + + WebSocketPerMessageDeflateCompressor compressor; + compressor.init(11, true); + compressor.compress(vec, b); + + WebSocketPerMessageDeflateDecompressor decompressor; + decompressor.init(11, true); + decompressor.decompress(b, c); + + return c; + } + + TEST_CASE("per-message-deflate-codec", "[zlib]") + { + SECTION("string api") + { + REQUIRE(compressAndDecompress("") == ""); + REQUIRE(compressAndDecompress("foo") == "foo"); + REQUIRE(compressAndDecompress("bar") == "bar"); + REQUIRE(compressAndDecompress("asdcaseqw`21897dehqwed") == "asdcaseqw`21897dehqwed"); + REQUIRE(compressAndDecompress("/usr/local/include/ixwebsocket/IXSocketAppleSSL.h") == + "/usr/local/include/ixwebsocket/IXSocketAppleSSL.h"); + } + + SECTION("vector api") + { + REQUIRE(compressAndDecompressVector("") == ""); + REQUIRE(compressAndDecompressVector("foo") == "foo"); + REQUIRE(compressAndDecompressVector("bar") == "bar"); + REQUIRE(compressAndDecompressVector("asdcaseqw`21897dehqwed") == + "asdcaseqw`21897dehqwed"); + REQUIRE( + compressAndDecompressVector("/usr/local/include/ixwebsocket/IXSocketAppleSSL.h") == + "/usr/local/include/ixwebsocket/IXSocketAppleSSL.h"); + } + } + +} // namespace ix diff --git a/extern/IXWebSocket-11.3.2/test/IXWebSocketPingTest.cpp b/extern/IXWebSocket-11.3.2/test/IXWebSocketPingTest.cpp new file mode 100644 index 0000000000..b484f9e365 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXWebSocketPingTest.cpp @@ -0,0 +1,451 @@ +/* + * IXWebSocketPingTest.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone. All rights reserved. + */ + +#include "IXTest.h" +#include "catch.hpp" +#include +#include +#include +#include +#include + +using namespace ix; + +namespace +{ + class WebSocketClient + { + public: + WebSocketClient(int port); + + void start(); + void stop(); + bool isReady() const; + void sendMessage(const std::string& text); + + private: + ix::WebSocket _webSocket; + int _port; + }; + + WebSocketClient::WebSocketClient(int port) + : _port(port) + { + ; + } + + bool WebSocketClient::isReady() const + { + return _webSocket.getReadyState() == ix::ReadyState::Open; + } + + void WebSocketClient::stop() + { + _webSocket.stop(); + } + + void WebSocketClient::start() + { + std::string url; + { + std::stringstream ss; + ss << "ws://127.0.0.1:" << _port << "/"; + + url = ss.str(); + } + + _webSocket.setUrl(url); + + // The important bit for this test. + // Set a 1 second heartbeat with the setter method to test + _webSocket.setPingInterval(1); + + std::stringstream ss; + log(std::string("Connecting to url: ") + url); + + _webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg) { + std::stringstream ss; + if (msg->type == ix::WebSocketMessageType::Open) + { + log("client connected"); + } + else if (msg->type == ix::WebSocketMessageType::Close) + { + log("client disconnected"); + } + else if (msg->type == ix::WebSocketMessageType::Error) + { + ss << "Error ! " << msg->errorInfo.reason; + log(ss.str()); + } + else if (msg->type == ix::WebSocketMessageType::Pong) + { + ss << "Received pong message " << msg->str; + log(ss.str()); + } + else if (msg->type == ix::WebSocketMessageType::Ping) + { + ss << "Received ping message " << msg->str; + log(ss.str()); + } + else if (msg->type == ix::WebSocketMessageType::Message) + { + // too many messages to log + } + else + { + ss << "Invalid ix::WebSocketMessageType"; + log(ss.str()); + } + }); + + _webSocket.start(); + } + + void WebSocketClient::sendMessage(const std::string& text) + { + _webSocket.send(text); + } + + bool startServer(ix::WebSocketServer& server, std::atomic& receivedPingMessages) + { + // A dev/null server + server.setOnConnectionCallback( + [&server, &receivedPingMessages](std::shared_ptr webSocket, + std::shared_ptr connectionState) { + webSocket->setOnMessageCallback( + [webSocket, connectionState, &server, &receivedPingMessages]( + const ix::WebSocketMessagePtr& msg) { + if (msg->type == ix::WebSocketMessageType::Open) + { + TLogger() << "New server connection"; + TLogger() << "id: " << connectionState->getId(); + TLogger() << "Uri: " << msg->openInfo.uri; + TLogger() << "Headers:"; + for (auto it : msg->openInfo.headers) + { + TLogger() << it.first << ": " << it.second; + } + } + else if (msg->type == ix::WebSocketMessageType::Close) + { + log("Server closed connection"); + } + else if (msg->type == ix::WebSocketMessageType::Ping) + { + log("Server received a ping"); + receivedPingMessages++; + } + else if (msg->type == ix::WebSocketMessageType::Message) + { + // to many messages to log + for (auto client : server.getClients()) + { + client->sendText("reply"); + } + } + }); + }); + + auto res = server.listen(); + if (!res.first) + { + log(res.second); + return false; + } + + server.start(); + return true; + } +} // namespace + +TEST_CASE("Websocket_ping_no_data_sent_setPingInterval", "[setPingInterval]") +{ + SECTION("Make sure that ping messages are sent when no other data are sent.") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + std::atomic serverReceivedPingMessages(0); + REQUIRE(startServer(server, serverReceivedPingMessages)); + + std::string session = ix::generateSessionId(); + WebSocketClient webSocketClient(port); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(10); + } + + REQUIRE(server.getClients().size() == 1); + + ix::msleep(2100); + + webSocketClient.stop(); + + + // Here we test ping interval + // -> expected ping messages == 2 as 2100 seconds, 1 ping sent every second + REQUIRE(serverReceivedPingMessages == 2); + + // Give us 1000ms for the server to notice that clients went away + ix::msleep(1000); + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +} + +TEST_CASE("Websocket_ping_data_sent_setPingInterval", "[setPingInterval]") +{ + SECTION("Make sure that ping messages are sent, even if other messages are sent") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + std::atomic serverReceivedPingMessages(0); + REQUIRE(startServer(server, serverReceivedPingMessages)); + + std::string session = ix::generateSessionId(); + WebSocketClient webSocketClient(port); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(10); + } + + REQUIRE(server.getClients().size() == 1); + + ix::msleep(900); + webSocketClient.sendMessage("hello world"); + ix::msleep(900); + webSocketClient.sendMessage("hello world"); + ix::msleep(1300); + + webSocketClient.stop(); + + // Here we test ping interval + // client has sent data, but ping should have been sent no matter what + // -> expected ping messages == 3 as 900+900+1300 = 3100 seconds, 1 ping sent every second + REQUIRE(serverReceivedPingMessages >= 2); + + // Give us 1000ms for the server to notice that clients went away + ix::msleep(1000); + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +} + +TEST_CASE("Websocket_ping_data_sent_setPingInterval_half_full", "[setPingInterval]") +{ + SECTION("Make sure that ping messages are sent, even if other messages are sent continuously " + "during a given time") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + std::atomic serverReceivedPingMessages(0); + REQUIRE(startServer(server, serverReceivedPingMessages)); + + std::string session = ix::generateSessionId(); + WebSocketClient webSocketClient(port); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(10); + } + + REQUIRE(server.getClients().size() == 1); + + // send continuously for 1100ms + auto now = std::chrono::steady_clock::now(); + + while (std::chrono::steady_clock::now() - now <= std::chrono::milliseconds(900)) + { + webSocketClient.sendMessage("message"); + ix::msleep(1); + } + ix::msleep(150); + + // Here we test ping interval + // client has sent data, but ping should have been sent no matter what + // -> expected ping messages == 1, as 900+150 = 1050ms, 1 ping sent every second + REQUIRE(serverReceivedPingMessages == 1); + + ix::msleep(100); + + webSocketClient.stop(); + + // Give us 1000ms for the server to notice that clients went away + ix::msleep(1000); + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +} + +TEST_CASE("Websocket_ping_data_sent_setPingInterval_full", "[setPingInterval]") +{ + SECTION("Make sure that ping messages are sent, even if other messages are sent continuously " + "for longer than ping interval") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + std::atomic serverReceivedPingMessages(0); + REQUIRE(startServer(server, serverReceivedPingMessages)); + + std::string session = ix::generateSessionId(); + WebSocketClient webSocketClient(port); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(1); + } + + REQUIRE(server.getClients().size() == 1); + + // send continuously for 1100ms + auto now = std::chrono::steady_clock::now(); + + while (std::chrono::steady_clock::now() - now <= std::chrono::milliseconds(1100)) + { + webSocketClient.sendMessage("message"); + ix::msleep(1); + } + + // Here we test ping interval + // client has sent data, but ping should have been sent no matter what + // -> expected ping messages == 2, 1 ping sent every second + // The first ping is sent right away on connect + REQUIRE(serverReceivedPingMessages == 2); + + ix::msleep(100); + + webSocketClient.stop(); + + // Give us 1000ms for the server to notice that clients went away + ix::msleep(1000); + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +} + +// Using setPingInterval + +TEST_CASE("Websocket_ping_no_data_sent_setHeartBeatPeriod", "[setPingInterval]") +{ + SECTION("Make sure that ping messages are sent when no other data are sent.") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + std::atomic serverReceivedPingMessages(0); + REQUIRE(startServer(server, serverReceivedPingMessages)); + + std::string session = ix::generateSessionId(); + WebSocketClient webSocketClient(port); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(1); + } + + REQUIRE(server.getClients().size() == 1); + + ix::msleep(2100); + + webSocketClient.stop(); + + // Here we test ping interval + // -> expected ping messages == 2 as 2100 seconds, 1 ping sent every second + REQUIRE(serverReceivedPingMessages == 2); + + // Give us 1000ms for the server to notice that clients went away + ix::msleep(1000); + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + server.stop(); + } +} + +TEST_CASE("Websocket_ping_data_sent_setHeartBeatPeriod", "[setPingInterval]") +{ + SECTION("Make sure that ping messages are sent, even if other messages are sent") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + std::atomic serverReceivedPingMessages(0); + REQUIRE(startServer(server, serverReceivedPingMessages)); + + std::string session = ix::generateSessionId(); + WebSocketClient webSocketClient(port); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(1); + } + + REQUIRE(server.getClients().size() == 1); + + ix::msleep(900); + webSocketClient.sendMessage("hello world"); + ix::msleep(900); + webSocketClient.sendMessage("hello world"); + ix::msleep(1100); + + webSocketClient.stop(); + + // without this sleep test fails on Windows + ix::msleep(100); + + // Here we test ping interval + // client has sent data, but ping should have been sent no matter what + // -> expected ping messages == 2 as 900+900+1100 = 2900 seconds, 1 ping sent every second + REQUIRE(serverReceivedPingMessages >= 2); + + // Give us 1000ms for the server to notice that clients went away + ix::msleep(1000); + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + server.stop(); + } +} diff --git a/extern/IXWebSocket-11.3.2/test/IXWebSocketPingTimeoutTest.cpp b/extern/IXWebSocket-11.3.2/test/IXWebSocketPingTimeoutTest.cpp new file mode 100644 index 0000000000..a5f8301879 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXWebSocketPingTimeoutTest.cpp @@ -0,0 +1,476 @@ +/* + * IXWebSocketHeartBeatNoResponseAutoDisconnectTest.cpp + * Author: Alexandre Konieczny + * Copyright (c) 2019 Machine Zone. All rights reserved. + */ + +#include "IXTest.h" +#include "catch.hpp" +#include +#include +#include +#include +#include + +using namespace ix; + +namespace +{ + class WebSocketClient + { + public: + WebSocketClient(int port, int pingInterval, int pingTimeout); + + void start(); + void stop(); + bool isReady() const; + bool isClosed() const; + void sendMessage(const std::string& text); + int getReceivedPongMessages(); + bool closedDueToPingTimeout(); + + private: + ix::WebSocket _webSocket; + int _port; + int _pingInterval; + int _pingTimeout; + std::atomic _receivedPongMessages; + std::atomic _closedDueToPingTimeout; + }; + + WebSocketClient::WebSocketClient(int port, int pingInterval, int pingTimeout) + : _port(port) + , _receivedPongMessages(0) + , _closedDueToPingTimeout(false) + , _pingInterval(pingInterval) + , _pingTimeout(pingTimeout) + { + ; + } + + bool WebSocketClient::isReady() const + { + return _webSocket.getReadyState() == ix::ReadyState::Open; + } + + bool WebSocketClient::isClosed() const + { + return _webSocket.getReadyState() == ix::ReadyState::Closed; + } + + void WebSocketClient::stop() + { + _webSocket.stop(); + } + + void WebSocketClient::start() + { + std::string url; + { + std::stringstream ss; + ss << "ws://127.0.0.1:" << _port << "/"; + + url = ss.str(); + } + + _webSocket.setUrl(url); + _webSocket.disableAutomaticReconnection(); + + // The important bit for this test. + // Set a ping interval, and a ping timeout + _webSocket.setPingInterval(_pingInterval); + _webSocket.setPingTimeout(_pingTimeout); + + std::stringstream ss; + log(std::string("Connecting to url: ") + url); + + _webSocket.setOnMessageCallback([this](ix::WebSocketMessageType messageType, + const std::string& str, + size_t wireSize, + const ix::WebSocketErrorInfo& error, + const ix::WebSocketOpenInfo& openInfo, + const ix::WebSocketCloseInfo& closeInfo) { + std::stringstream ss; + if (messageType == ix::WebSocketMessageType::Open) + { + log("client connected"); + } + else if (messageType == ix::WebSocketMessageType::Close) + { + log("client disconnected"); + + if (msg->closeInfo.code == 1011) + { + _closedDueToPingTimeout = true; + } + } + else if (messageType == ix::WebSocketMessageType::Error) + { + ss << "Error ! " << error.reason; + log(ss.str()); + } + else if (messageType == ix::WebSocketMessageType::Pong) + { + _receivedPongMessages++; + + ss << "Received pong message " << str; + log(ss.str()); + } + else if (messageType == ix::WebSocketMessageType::Ping) + { + ss << "Received ping message " << str; + log(ss.str()); + } + else if (messageType == ix::WebSocketMessageType::Message) + { + ss << "Received message " << str; + log(ss.str()); + } + else + { + ss << "Invalid ix::WebSocketMessageType"; + log(ss.str()); + } + }); + + _webSocket.start(); + } + + void WebSocketClient::sendMessage(const std::string& text) + { + _webSocket.send(text); + } + + int WebSocketClient::getReceivedPongMessages() + { + return _receivedPongMessages; + } + + bool WebSocketClient::closedDueToPingTimeout() + { + return _closedDueToPingTimeout; + } + + bool startServer(ix::WebSocketServer& server, + std::atomic& receivedPingMessages, + bool enablePong) + { + // A dev/null server + server.setOnConnectionCallback( + [&server, &receivedPingMessages](std::shared_ptr webSocket, + std::shared_ptr connectionState) { + webSocket->setOnMessageCallback( + [webSocket, connectionState, &server, &receivedPingMessages]( + ix::WebSocketMessageType messageType, + const std::string& str, + size_t wireSize, + const ix::WebSocketErrorInfo& error, + const ix::WebSocketOpenInfo& openInfo, + const ix::WebSocketCloseInfo& closeInfo) { + if (messageType == ix::WebSocketMessageType::Open) + { + TLogger() << "New server connection"; + TLogger() << "id: " << connectionState->getId(); + TLogger() << "Uri: " << openInfo.uri; + TLogger() << "Headers:"; + for (auto it : openInfo.headers) + { + TLogger() << it.first << ": " << it.second; + } + } + else if (messageType == ix::WebSocketMessageType::Close) + { + log("Server closed connection"); + } + else if (messageType == ix::WebSocketMessageType::Ping) + { + log("Server received a ping"); + receivedPingMessages++; + } + }); + }); + + if (!enablePong) + { + // USE this to prevent a pong answer, so the ping timeout is raised on client + server.disablePong(); + } + + auto res = server.listen(); + if (!res.first) + { + log(res.second); + return false; + } + + server.start(); + return true; + } +} // namespace + +TEST_CASE("Websocket_ping_timeout_not_checked", "[setPingTimeout]") +{ + SECTION("Make sure that ping messages have a response (PONG).") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + std::atomic serverReceivedPingMessages(0); + bool enablePong = false; // Pong is disabled on Server + REQUIRE(startServer(server, serverReceivedPingMessages, enablePong)); + + std::string session = ix::generateSessionId(); + int pingIntervalSecs = 1; + int pingTimeoutSecs = -1; // ping timeout not checked + WebSocketClient webSocketClient(port, pingIntervalSecs, pingTimeoutSecs); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(10); + } + + REQUIRE(server.getClients().size() == 1); + + ix::msleep(1100); + + // Here we test ping timeout, no timeout + REQUIRE(serverReceivedPingMessages == 1); + REQUIRE(webSocketClient.getReceivedPongMessages() == 0); + + ix::msleep(1000); + + // Here we test ping timeout, no timeout + REQUIRE(serverReceivedPingMessages == 2); + REQUIRE(webSocketClient.getReceivedPongMessages() == 0); + + webSocketClient.stop(); + + // Give us 1000ms for the server to notice that clients went away + ix::msleep(1000); + REQUIRE(server.getClients().size() == 0); + + // Ensure client close was not by ping timeout + REQUIRE(webSocketClient.closedDueToPingTimeout() == false); + + ix::reportWebSocketTraffic(); + } +} + +TEST_CASE("Websocket_ping_no_timeout", "[setPingTimeout]") +{ + SECTION("Make sure that ping messages have a response (PONG).") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + std::atomic serverReceivedPingMessages(0); + bool enablePong = true; // Pong is enabled on Server + REQUIRE(startServer(server, serverReceivedPingMessages, enablePong)); + + std::string session = ix::generateSessionId(); + int pingIntervalSecs = 1; + int pingTimeoutSecs = 2; + WebSocketClient webSocketClient(port, pingIntervalSecs, pingTimeoutSecs); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(10); + } + + REQUIRE(server.getClients().size() == 1); + + ix::msleep(1200); + + // Here we test ping timeout, no timeout + REQUIRE(serverReceivedPingMessages == 1); + REQUIRE(webSocketClient.getReceivedPongMessages() == 1); + + ix::msleep(1000); + + // Here we test ping timeout, no timeout + REQUIRE(serverReceivedPingMessages == 2); + REQUIRE(webSocketClient.getReceivedPongMessages() == 2); + + webSocketClient.stop(); + + // Give us 1000ms for the server to notice that clients went away + ix::msleep(1000); + REQUIRE(server.getClients().size() == 0); + + // Ensure client close was not by ping timeout + REQUIRE(webSocketClient.closedDueToPingTimeout() == false); + + ix::reportWebSocketTraffic(); + } +} + +TEST_CASE("Websocket_no_ping_but_timeout", "[setPingTimeout]") +{ + SECTION("Make sure that ping messages don't have responses (no PONG).") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + std::atomic serverReceivedPingMessages(0); + bool enablePong = false; // Pong is disabled on Server + REQUIRE(startServer(server, serverReceivedPingMessages, enablePong)); + + std::string session = ix::generateSessionId(); + int pingIntervalSecs = -1; // no ping set + int pingTimeoutSecs = 3; + WebSocketClient webSocketClient(port, pingIntervalSecs, pingTimeoutSecs); + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(10); + } + + REQUIRE(server.getClients().size() == 1); + + ix::msleep(2900); + + // Here we test ping timeout, no timeout yet + REQUIRE(serverReceivedPingMessages == 0); + REQUIRE(webSocketClient.getReceivedPongMessages() == 0); + + REQUIRE(webSocketClient.isClosed() == false); + REQUIRE(webSocketClient.closedDueToPingTimeout() == false); + + ix::msleep(300); + + // Here we test ping timeout, timeout + REQUIRE(serverReceivedPingMessages == 0); + REQUIRE(webSocketClient.getReceivedPongMessages() == 0); + // Ensure client close was by ping timeout + ix::msleep(1000); + REQUIRE(webSocketClient.isClosed() == true); + REQUIRE(webSocketClient.closedDueToPingTimeout() == true); + + webSocketClient.stop(); + + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +} + +TEST_CASE("Websocket_ping_timeout", "[setPingTimeout]") +{ + SECTION("Make sure that ping messages don't have responses (no PONG).") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + std::atomic serverReceivedPingMessages(0); + bool enablePong = false; // Pong is disabled on Server + REQUIRE(startServer(server, serverReceivedPingMessages, enablePong)); + + std::string session = ix::generateSessionId(); + int pingIntervalSecs = 1; + int pingTimeoutSecs = 2; + WebSocketClient webSocketClient(port, pingIntervalSecs, pingTimeoutSecs); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(10); + } + + REQUIRE(server.getClients().size() == 1); + + ix::msleep(1100); + + // Here we test ping timeout, no timeout yet + REQUIRE(serverReceivedPingMessages == 1); + REQUIRE(webSocketClient.getReceivedPongMessages() == 0); + + ix::msleep(1100); + + // Here we test ping timeout, timeout + REQUIRE(serverReceivedPingMessages == 1); + REQUIRE(webSocketClient.getReceivedPongMessages() == 0); + // Ensure client close was by ping timeout + ix::msleep(1000); + REQUIRE(webSocketClient.isClosed() == true); + REQUIRE(webSocketClient.closedDueToPingTimeout() == true); + + webSocketClient.stop(); + + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +} + +TEST_CASE("Websocket_ping_long_timeout", "[setPingTimeout]") +{ + SECTION("Make sure that ping messages don't have responses (no PONG).") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + std::atomic serverReceivedPingMessages(0); + bool enablePong = false; // Pong is disabled on Server + REQUIRE(startServer(server, serverReceivedPingMessages, enablePong)); + + std::string session = ix::generateSessionId(); + int pingIntervalSecs = 2; + int pingTimeoutSecs = 6; + WebSocketClient webSocketClient(port, pingIntervalSecs, pingTimeoutSecs); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(10); + } + + REQUIRE(server.getClients().size() == 1); + + ix::msleep(5800); + + // Here we test ping timeout, no timeout yet (2 ping sent at 2s and 4s) + REQUIRE(serverReceivedPingMessages == 2); + REQUIRE(webSocketClient.getReceivedPongMessages() == 0); + + // Ensure client not closed + REQUIRE(webSocketClient.isClosed() == false); + REQUIRE(webSocketClient.closedDueToPingTimeout() == false); + + ix::msleep(600); + + // Here we test ping timeout, timeout (at 6 seconds) + REQUIRE(serverReceivedPingMessages == 2); + REQUIRE(webSocketClient.getReceivedPongMessages() == 0); + // Ensure client close was not by ping timeout + REQUIRE(webSocketClient.isClosed() == true); + REQUIRE(webSocketClient.closedDueToPingTimeout() == true); + + webSocketClient.stop(); + + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +} diff --git a/extern/IXWebSocket-11.3.2/test/IXWebSocketServerTest.cpp b/extern/IXWebSocket-11.3.2/test/IXWebSocketServerTest.cpp new file mode 100644 index 0000000000..5ff58f642f --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXWebSocketServerTest.cpp @@ -0,0 +1,198 @@ +/* + * IXWebSocketServerTest.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone. All rights reserved. + */ + +#include "IXTest.h" +#include "catch.hpp" +#include +#include +#include +#include +#include + +using namespace ix; + +namespace ix +{ + // Test that we can override the connectionState impl to provide our own + class ConnectionStateCustom : public ConnectionState + { + void computeId() + { + // a very boring invariant id that we can test against in the unittest + _id = "foobarConnectionId"; + } + }; + + bool startServer(ix::WebSocketServer& server, std::string& connectionId) + { + auto factory = []() -> std::shared_ptr { + return std::make_shared(); + }; + server.setConnectionStateFactory(factory); + + server.setOnClientMessageCallback( + [&server, &connectionId](std::shared_ptr connectionState, + WebSocket& webSocket, + const ix::WebSocketMessagePtr& msg) { + auto remoteIp = connectionState->getRemoteIp(); + + if (msg->type == ix::WebSocketMessageType::Open) + { + TLogger() << "New connection"; + connectionState->computeId(); + TLogger() << "remote ip: " << remoteIp; + TLogger() << "id: " << connectionState->getId(); + TLogger() << "Uri: " << msg->openInfo.uri; + TLogger() << "Headers:"; + for (auto it : msg->openInfo.headers) + { + TLogger() << it.first << ": " << it.second; + } + + connectionId = connectionState->getId(); + } + else if (msg->type == ix::WebSocketMessageType::Close) + { + TLogger() << "Closed connection"; + } + else if (msg->type == ix::WebSocketMessageType::Message) + { + for (auto&& client : server.getClients()) + { + if (client.get() != &webSocket) + { + client->send(msg->str, msg->binary); + } + } + } + }); + + auto res = server.listen(); + if (!res.first) + { + TLogger() << res.second; + return false; + } + + server.start(); + return true; + } +} // namespace ix + +TEST_CASE("Websocket_server", "[websocket_server]") +{ + SECTION("Connect to the server, do not send anything. Should timeout and return 400") + { + int port = getFreePort(); + ix::WebSocketServer server(port); + std::string connectionId; + REQUIRE(startServer(server, connectionId)); + + std::string errMsg; + bool tls = false; + SocketTLSOptions tlsOptions; + std::shared_ptr socket = createSocket(tls, -1, errMsg, tlsOptions); + std::string host("127.0.0.1"); + auto isCancellationRequested = []() -> bool { return false; }; + bool success = socket->connect(host, port, errMsg, isCancellationRequested); + REQUIRE(success); + + auto lineResult = socket->readLine(isCancellationRequested); + auto lineValid = lineResult.first; + REQUIRE(lineValid); + + auto line = lineResult.second; + + int status = -1; + REQUIRE(sscanf(line.c_str(), "HTTP/1.1 %d", &status) == 1); + REQUIRE(status == 400); + + // FIXME: explicitely set a client timeout larger than the server one (3) + + // Give us 500ms for the server to notice that clients went away + ix::msleep(500); + server.stop(); + REQUIRE(server.getClients().size() == 0); + } + + SECTION("Connect to the server. Send GET request without header. Should return 400") + { + int port = getFreePort(); + ix::WebSocketServer server(port); + std::string connectionId; + REQUIRE(startServer(server, connectionId)); + + std::string errMsg; + bool tls = false; + SocketTLSOptions tlsOptions; + std::shared_ptr socket = createSocket(tls, -1, errMsg, tlsOptions); + std::string host("127.0.0.1"); + auto isCancellationRequested = []() -> bool { return false; }; + bool success = socket->connect(host, port, errMsg, isCancellationRequested); + REQUIRE(success); + + TLogger() << "writeBytes"; + socket->writeBytes("GET /\r\n", isCancellationRequested); + + auto lineResult = socket->readLine(isCancellationRequested); + auto lineValid = lineResult.first; + REQUIRE(lineValid); + + auto line = lineResult.second; + + int status = -1; + REQUIRE(sscanf(line.c_str(), "HTTP/1.1 %d", &status) == 1); + REQUIRE(status == 400); + + // FIXME: explicitely set a client timeout larger than the server one (3) + + // Give us 500ms for the server to notice that clients went away + ix::msleep(500); + server.stop(); + REQUIRE(server.getClients().size() == 0); + } + + SECTION("Connect to the server. Send GET request with correct header") + { + int port = getFreePort(); + ix::WebSocketServer server(port); + std::string connectionId; + REQUIRE(startServer(server, connectionId)); + + std::string errMsg; + bool tls = false; + SocketTLSOptions tlsOptions; + std::shared_ptr socket = createSocket(tls, -1, errMsg, tlsOptions); + std::string host("127.0.0.1"); + auto isCancellationRequested = []() -> bool { return false; }; + bool success = socket->connect(host, port, errMsg, isCancellationRequested); + REQUIRE(success); + + socket->writeBytes("GET / HTTP/1.1\r\n" + "Upgrade: websocket\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Sec-WebSocket-Key: foobar\r\n" + "\r\n", + isCancellationRequested); + + auto lineResult = socket->readLine(isCancellationRequested); + auto lineValid = lineResult.first; + REQUIRE(lineValid); + + auto line = lineResult.second; + + int status = -1; + REQUIRE(sscanf(line.c_str(), "HTTP/1.1 %d", &status) == 1); + REQUIRE(status == 101); + + // Give us 500ms for the server to notice that clients went away + ix::msleep(500); + + server.stop(); + REQUIRE(connectionId == "foobarConnectionId"); + REQUIRE(server.getClients().size() == 0); + } +} diff --git a/extern/IXWebSocket-11.3.2/test/IXWebSocketSubProtocolTest.cpp b/extern/IXWebSocket-11.3.2/test/IXWebSocketSubProtocolTest.cpp new file mode 100644 index 0000000000..8c18f7b2dc --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXWebSocketSubProtocolTest.cpp @@ -0,0 +1,109 @@ +/* + * IXWebSocketServerTest.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone. All rights reserved. + */ + +#include "IXTest.h" +#include "catch.hpp" +#include +#include +#include +#include +#include + +using namespace ix; + +bool startServer(ix::WebSocketServer& server, std::string& subProtocols) +{ + server.setOnClientMessageCallback( + [&server, &subProtocols](std::shared_ptr connectionState, + WebSocket& webSocket, + const ix::WebSocketMessagePtr& msg) { + auto remoteIp = connectionState->getRemoteIp(); + if (msg->type == ix::WebSocketMessageType::Open) + { + TLogger() << "New connection"; + TLogger() << "remote ip: " << remoteIp; + TLogger() << "id: " << connectionState->getId(); + TLogger() << "Uri: " << msg->openInfo.uri; + TLogger() << "Headers:"; + for (auto it : msg->openInfo.headers) + { + TLogger() << it.first << ": " << it.second; + } + + subProtocols = msg->openInfo.headers["Sec-WebSocket-Protocol"]; + } + else if (msg->type == ix::WebSocketMessageType::Close) + { + log("Closed connection"); + } + else if (msg->type == ix::WebSocketMessageType::Message) + { + for (auto&& client : server.getClients()) + { + if (client.get() != &webSocket) + { + client->sendBinary(msg->str); + } + } + } + }); + + auto res = server.listen(); + if (!res.first) + { + log(res.second); + return false; + } + + server.start(); + return true; +} + +TEST_CASE("subprotocol", "[websocket_subprotocol]") +{ + SECTION("Connect to the server") + { + int port = getFreePort(); + ix::WebSocketServer server(port); + + std::string subProtocols; + startServer(server, subProtocols); + + std::atomic connected(false); + ix::WebSocket webSocket; + webSocket.setOnMessageCallback([&connected](const ix::WebSocketMessagePtr& msg) { + if (msg->type == ix::WebSocketMessageType::Open) + { + connected = true; + log("Client connected"); + } + }); + + webSocket.addSubProtocol("json"); + webSocket.addSubProtocol("msgpack"); + + std::string url; + std::stringstream ss; + ss << "ws://127.0.0.1:" << port; + url = ss.str(); + + webSocket.setUrl(url); + webSocket.start(); + + // Give us 3 seconds to connect + int attempts = 0; + while (!connected) + { + REQUIRE(attempts++ < 300); + ix::msleep(10); + } + + webSocket.stop(); + server.stop(); + + REQUIRE(subProtocols == "json,msgpack"); + } +} diff --git a/extern/IXWebSocket-11.3.2/test/IXWebSocketTestConnectionDisconnection.cpp b/extern/IXWebSocket-11.3.2/test/IXWebSocketTestConnectionDisconnection.cpp new file mode 100644 index 0000000000..411cf0194f --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/IXWebSocketTestConnectionDisconnection.cpp @@ -0,0 +1,165 @@ +/* + * IXWebSocketTestConnectionDisconnection.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2017 Machine Zone. All rights reserved. + */ + +#include "IXTest.h" +#include "catch.hpp" +#include +#include +#include +#include + +using namespace ix; + +namespace +{ + const std::string WEBSOCKET_DOT_ORG_URL("wss://echo.websocket.org"); + const std::string GOOGLE_URL("wss://google.com"); + const std::string UNKNOWN_URL("wss://asdcasdcaasdcasdcasdcasdcasdcasdcasassdd.com"); +} // namespace + +namespace +{ + class IXWebSocketTestConnectionDisconnection + { + public: + IXWebSocketTestConnectionDisconnection(); + void start(const std::string& url); + void stop(); + + private: + ix::WebSocket _webSocket; + }; + + IXWebSocketTestConnectionDisconnection::IXWebSocketTestConnectionDisconnection() + { + ; + } + + void IXWebSocketTestConnectionDisconnection::stop() + { + _webSocket.stop(); + } + + void IXWebSocketTestConnectionDisconnection::start(const std::string& url) + { + _webSocket.setUrl(url); + + SocketTLSOptions tlsOptions; + tlsOptions.caFile = "cacert.pem"; + _webSocket.setTLSOptions(tlsOptions); + + std::stringstream ss; + log(std::string("Connecting to url: ") + url); + + _webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg) { + std::stringstream ss; + if (msg->type == ix::WebSocketMessageType::Open) + { + log("TestConnectionDisconnection: connected !"); + } + else if (msg->type == ix::WebSocketMessageType::Close) + { + log("TestConnectionDisconnection: disconnected !"); + } + else if (msg->type == ix::WebSocketMessageType::Error) + { + ss << "TestConnectionDisconnection: Error! "; + ss << msg->errorInfo.reason; + log(ss.str()); + } + else if (msg->type == ix::WebSocketMessageType::Message) + { + log("TestConnectionDisconnection: received message.!"); + } + else if (msg->type == ix::WebSocketMessageType::Ping) + { + log("TestConnectionDisconnection: received ping message.!"); + } + else if (msg->type == ix::WebSocketMessageType::Pong) + { + log("TestConnectionDisconnection: received pong message.!"); + } + else if (msg->type == ix::WebSocketMessageType::Fragment) + { + log("TestConnectionDisconnection: received fragment.!"); + } + else + { + log("Invalid ix::WebSocketMessageType"); + } + }); + + _webSocket.enableAutomaticReconnection(); + REQUIRE(_webSocket.isAutomaticReconnectionEnabled() == true); + + _webSocket.disableAutomaticReconnection(); + REQUIRE(_webSocket.isAutomaticReconnectionEnabled() == false); + + // Start the connection + _webSocket.start(); + } +} // namespace + +// +// We try to connect to different servers, and make sure there are no crashes. +// FIXME: We could do more checks (make sure that we were not able to connect to unknown servers, +// etc...) +// +TEST_CASE("websocket_connections", "[websocket]") +{ + SECTION("Try to connect to invalid servers.") + { + IXWebSocketTestConnectionDisconnection test; + + test.start(GOOGLE_URL); + ix::msleep(1000); + test.stop(); + + test.start(UNKNOWN_URL); + ix::msleep(1000); + test.stop(); + } + + SECTION("Try to connect and disconnect with different timing, not enough time to succesfully " + "connect") + { + IXWebSocketTestConnectionDisconnection test; + log(std::string("50 Runs")); + + for (int i = 0; i < 50; ++i) + { + log(std::string("Run: ") + std::to_string(i)); + test.start(WEBSOCKET_DOT_ORG_URL); + + log(std::string("Sleeping")); + ix::msleep(i); + + log(std::string("Stopping")); + test.stop(); + } + } + + // This test breaks on travis CI - Ubuntu Xenial + gcc + tsan + // We should fix this. + SECTION("Try to connect and disconnect with different timing, from not enough time to " + "successfull connect") + { + IXWebSocketTestConnectionDisconnection test; + log(std::string("20 Runs")); + + for (int i = 0; i < 20; ++i) + { + log(std::string("Run: ") + std::to_string(i)); + test.start(WEBSOCKET_DOT_ORG_URL); + + log(std::string("Sleeping")); + ix::msleep(i * 50); + + log(std::string("Stopping")); + test.stop(); + } + } +} diff --git a/extern/IXWebSocket-11.3.2/test/appsConfig.json b/extern/IXWebSocket-11.3.2/test/appsConfig.json new file mode 100644 index 0000000000..14f8f48b31 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/appsConfig.json @@ -0,0 +1,14 @@ +{ + "apps": { + "FC2F10139A2BAc53BB72D9db967b024f": { + "roles": { + "_sub": { + "secret": "66B1dA3ED5fA074EB5AE84Dd8CE3b5ba" + }, + "_pub": { + "secret": "1c04DB8fFe76A4EeFE3E318C72d771db" + } + } + } + } +} diff --git a/extern/IXWebSocket-11.3.2/test/broadcast-server.js b/extern/IXWebSocket-11.3.2/test/broadcast-server.js new file mode 100644 index 0000000000..9a9fb72116 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/broadcast-server.js @@ -0,0 +1,23 @@ +const WebSocket = require('ws'); + +const wss = new WebSocket.Server({ port: 8080 }); + +// Broadcast to all. +wss.broadcast = function broadcast(data) { + wss.clients.forEach(function each(client) { + if (client.readyState === WebSocket.OPEN) { + client.send(data); + } + }); +}; + +wss.on('connection', function connection(ws) { + ws.on('message', function incoming(data) { + // Broadcast to everyone else. + wss.clients.forEach(function each(client) { + if (client !== ws && client.readyState === WebSocket.OPEN) { + client.send(data); + } + }); + }); +}); diff --git a/extern/IXWebSocket-11.3.2/test/build_linux.sh b/extern/IXWebSocket-11.3.2/test/build_linux.sh new file mode 100644 index 0000000000..c8d9f4e57a --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/build_linux.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# +# Author: Benjamin Sergeant +# Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved. +# + +# 'manual' way of building. You can also use cmake. + +g++ --std=c++11 \ + -DIXWEBSOCKET_USE_TLS \ + -g \ + ../ixwebsocket/IXEventFd.cpp \ + ../ixwebsocket/IXSocket.cpp \ + ../ixwebsocket/IXSetThreadName.cpp \ + ../ixwebsocket/IXWebSocketTransport.cpp \ + ../ixwebsocket/IXWebSocket.cpp \ + ../ixwebsocket/IXWebSocketServer.cpp \ + ../ixwebsocket/IXDNSLookup.cpp \ + ../ixwebsocket/IXSocketConnect.cpp \ + ../ixwebsocket/IXSocketOpenSSL.cpp \ + ../ixwebsocket/IXWebSocketPerMessageDeflate.cpp \ + ../ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp \ + -I ../.. \ + -I Catch2/single_include \ + test_runner.cpp \ + cmd_websocket_chat.cpp \ + IXTest.cpp \ + msgpack11.cpp \ + -o ixwebsocket_unittest \ + -lcrypto -lssl -lz -lpthread diff --git a/extern/IXWebSocket-11.3.2/test/cacert.pem b/extern/IXWebSocket-11.3.2/test/cacert.pem new file mode 100644 index 0000000000..31186dc126 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/cacert.pem @@ -0,0 +1,4430 @@ +## +## Bundle of CA Root Certificates +## +## Certificate data from Mozilla downloaded on: Wed Aug 13 21:49:32 2014 +## +## This is a bundle of X.509 certificates of public Certificate Authorities +## (CA). These were automatically extracted from Mozilla's root certificates +## file (certdata.txt). This file can be found in the mozilla source tree: +## http://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt +## +## It contains the certificates in PEM format and therefore +## can be directly used with curl / libcurl / php_curl, or with +## an Apache+mod_ssl webserver for SSL client authentication. +## Just configure this file as the SSLCACertificateFile. +## +## Conversion done with mk-ca-bundle.pl verison 1.22. +## SHA1: bf2c15b3019e696660321d2227d942936dc50aa7 +## + + +GTE CyberTrust Global Root +========================== +-----BEGIN CERTIFICATE----- +MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUg +Q29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEG +A1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEz +MjM1OTAwWjB1MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQL +Ex5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0 +IEdsb2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrHiM3dFw4u +sJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTSr41tiGeA5u2ylc9yMcql +HHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X404Wqk2kmhXBIgD8SFcd5tB8FLztimQID +AQABMA0GCSqGSIb3DQEBBAUAA4GBAG3rGwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMW +M4ETCJ57NE7fQMh017l93PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OF +NMQkpw0PlZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ +-----END CERTIFICATE----- + +Thawte Server CA +================ +-----BEGIN CERTIFICATE----- +MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs +dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UE +AxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5j +b20wHhcNOTYwODAxMDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNV +BAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29u +c3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcG +A1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0 +ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl +/Kj0R1HahbUgdJSGHg91yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg7 +1CcEJRCXL+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGjEzAR +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG7oWDTSEwjsrZqG9J +GubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6eQNuozDJ0uW8NxuOzRAvZim+aKZuZ +GCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZqdq5snUb9kLy78fyGPmJvKP/iiMucEc= +-----END CERTIFICATE----- + +Thawte Premium Server CA +======================== +-----BEGIN CERTIFICATE----- +MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs +dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UE +AxMYVGhhd3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZl +ckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYT +AlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsGA1UEChMU +VGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2 +aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3DQEJARYZ +cHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2 +aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIh +Udib0GfQug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMRuHM/ +qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQAm +SCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUIhfzJATj/Tb7yFkJD57taRvvBxhEf +8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JMpAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7t +UCemDaYj+bvLpgcUQg== +-----END CERTIFICATE----- + +Equifax Secure CA +================= +-----BEGIN CERTIFICATE----- +MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEQMA4GA1UE +ChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoT +B0VxdWlmYXgxLTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCB +nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPR +fM6fBeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+AcJkVV5MW +8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kCAwEAAaOCAQkwggEFMHAG +A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UE +CxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoG +A1UdEAQTMBGBDzIwMTgwODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvS +spXXR9gjIBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQFMAMB +Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAFjOKer89961 +zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y7qj/WsjTVbJmcVfewCHrPSqnI0kB +BIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee95 +70+sB3c4 +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority +======================================================= +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMx +FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5 +IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVow +XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz +IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94 +f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol +hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBAgUAA4GBALtMEivPLCYA +TxQT3ab7/AoRhIzzKBxnki98tsX63/Dolbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59Ah +WM1pF+NEHJwZRDmJXNycAA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2Omuf +Tqj/ZA1k +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority - G2 +============================================================ +-----BEGIN CERTIFICATE----- +MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJBgNVBAYTAlVT +MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz +dCBOZXR3b3JrMB4XDTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVT +MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz +dCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCO +FoUgRm1HP9SFIIThbbP4pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71 +lSk8UOg013gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwIDAQAB +MA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSkU01UbSuvDV1Ai2TT +1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7iF6YM40AIOw7n60RzKprxaZLvcRTD +Oaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpYoJ2daZH9 +-----END CERTIFICATE----- + +GlobalSign Root CA +================== +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx +GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds +b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD +VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa +DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc +THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb +Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP +c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX +gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF +AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj +Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG +j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH +hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC +X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- + +GlobalSign Root CA - R2 +======================= +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xv +YmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh +bFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT +aWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln +bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6 +ErPLv4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8eoLrvozp +s6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklqtTleiDTsvHgMCJiEbKjN +S7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzdC9XZzPnqJworc5HGnRusyMvo4KD0L5CL +TfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pazq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6C +ygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQUm+IHV2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5nbG9i +YWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGLjAN +BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp +9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu +01yiPqFbQfXf5WRDLenVOavSot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG7 +9G+dwfCMNYxdAfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 +TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== +-----END CERTIFICATE----- + +ValiCert Class 1 VA +=================== +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp +b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh +bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIy +MjM0OFoXDTE5MDYyNTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 +d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEg +UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 +LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9YLqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIi +GQj4/xEjm84H9b9pGib+TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCm +DuJWBQ8YTfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0LBwG +lN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLWI8sogTLDAHkY7FkX +icnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPwnXS3qT6gpf+2SQMT2iLM7XGCK5nP +Orf1LXLI +-----END CERTIFICATE----- + +ValiCert Class 2 VA +=================== +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp +b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh +bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw +MTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 +d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIg +UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 +LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVC +CSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7Rf +ZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZ +SWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbV +UjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8 +W9ViH0Pd +-----END CERTIFICATE----- + +RSA Root Certificate 1 +====================== +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp +b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh +bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw +MjIzM1oXDTE5MDYyNjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 +d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMg +UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 +LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDjmFGWHOjVsQaBalfDcnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td +3zZxFJmP3MKS8edgkpfs2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89H +BFx1cQqYJJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliEZwgs +3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJn0WuPIqpsHEzXcjF +V9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/APhmcGcwTTYJBtYze4D1gCCAPRX5r +on+jjBXu +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority - G3 +============================================================ +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv +cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy +dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAMu6nFL8eB8aHm8bN3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1 +EUGO+i2tKmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGukxUc +cLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBmCC+Vk7+qRy+oRpfw +EuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJXwzw3sJ2zq/3avL6QaaiMxTJ5Xpj +055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWuimi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA +ERSWwauSCPc/L8my/uRan2Te2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5f +j267Cz3qWhMeDGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC +/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565pF4ErWjfJXir0 +xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGtTxzhT5yvDwyd93gN2PQ1VoDa +t20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== +-----END CERTIFICATE----- + +Verisign Class 4 Public Primary Certification Authority - G3 +============================================================ +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv +cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy +dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAK3LpRFpxlmr8Y+1GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaS +tBO3IFsJ+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0GbdU6LM +8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLmNxdLMEYH5IBtptiW +Lugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XYufTsgsbSPZUd5cBPhMnZo0QoBmrX +Razwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA +j/ola09b5KROJ1WrIhVZPMq1CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXtt +mhwwjIDLk5Mqg6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm +fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c2NU8Qh0XwRJd +RTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/bLvSHgCwIe34QWKCudiyxLtG +UPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== +-----END CERTIFICATE----- + +Entrust.net Secure Server CA +============================ +-----BEGIN CERTIFICATE----- +MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMCVVMxFDASBgNV +BAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5uZXQvQ1BTIGluY29ycC4gYnkg +cmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRl +ZDE6MDgGA1UEAxMxRW50cnVzdC5uZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eTAeFw05OTA1MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIG +A1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBi +eSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1p +dGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQ +aO2f55M28Qpku0f1BBc/I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5 +gXpa0zf3wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OCAdcw +ggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHboIHYpIHVMIHSMQsw +CQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5l +dC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF +bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu +dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0MFqBDzIwMTkw +NTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7UISX8+1i0Bow +HQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAaMAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EA +BAwwChsEVjQuMAMCBJAwDQYJKoZIhvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyN +Ewr75Ji174z4xRAN95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9 +n9cd2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= +-----END CERTIFICATE----- + +Entrust.net Premium 2048 Secure Server CA +========================================= +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u +ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp +bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV +BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx +NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3 +d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl +MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u +ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL +Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr +hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW +nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi +VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ +KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy +T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT +J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e +nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- + +Baltimore CyberTrust Root +========================= +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE +ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li +ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC +SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs +dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME +uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB +UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C +G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9 +XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr +l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI +VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB +BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh +cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5 +hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa +Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H +RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +Equifax Secure Global eBusiness CA +================================== +-----BEGIN CERTIFICATE----- +MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT +RXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBTZWN1cmUgR2xvYmFsIGVCdXNp +bmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIwMDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMx +HDAaBgNVBAoTE0VxdWlmYXggU2VjdXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEds +b2JhbCBlQnVzaW5lc3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRV +PEnCUdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc58O/gGzN +qfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/o5brhTMhHD4ePmBudpxn +hcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAHMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j +BBgwFoAUvqigdHJQa0S3ySPY+6j/s1draGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hs +MA0GCSqGSIb3DQEBBAUAA4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okEN +I7SS+RkAZ70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv8qIY +NMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV +-----END CERTIFICATE----- + +Equifax Secure eBusiness CA 1 +============================= +-----BEGIN CERTIFICATE----- +MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT +RXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENB +LTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQwMDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UE +ChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNz +IENBLTEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ +1MRoRvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBuWqDZQu4a +IZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKwEnv+j6YDAgMBAAGjZjBk +MBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEp4MlIR21kW +Nl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRKeDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQF +AAOBgQB1W6ibAxHm6VZMzfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5 +lSE/9dR+WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN/Bf+ +KpYrtWKmpj29f5JZzVoqgrI3eQ== +-----END CERTIFICATE----- + +AddTrust Low-Value Services Root +================================ +-----BEGIN CERTIFICATE----- +MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRU +cnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMwMTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQsw +CQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBO +ZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ulCDtbKRY6 +54eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6ntGO0/7Gcrjyvd7ZWxbWr +oulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyldI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1 +Zmne3yzxbrww2ywkEtvrNTVokMsAsJchPXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJui +GMx1I4S+6+JNM3GOGvDC+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8w +HQYDVR0OBBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBlMQswCQYDVQQGEwJT +RTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEw +HwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxt +ZBsfzQ3duQH6lmM0MkhHma6X7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0Ph +iVYrqW9yTkkz43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY +eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJlpz/+0WatC7xr +mYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOAWiFeIc9TVPC6b4nbqKqVz4vj +ccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk= +-----END CERTIFICATE----- + +AddTrust External Root +====================== +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYD +VQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEw +NDgzOFowbzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRU +cnVzdCBFeHRlcm5hbCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0Eg +Um9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvtH7xsD821 ++iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9uMq/NzgtHj6RQa1wVsfw +Tz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzXmk6vBbOmcZSccbNQYArHE504B4YCqOmo +aSYYkKtMsE8jqzpPhNjfzp/haW+710LXa0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy +2xSoRcRdKn23tNbE7qzNE0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv7 +7+ldU9U0WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYDVR0P +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0Jvf6xCZU7wO94CTL +VBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRk +VHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENB +IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZl +j7DYd7usQWxHYINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 +6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvCNr4TDea9Y355 +e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEXc4g/VhsxOBi0cQ+azcgOno4u +G+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5amnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= +-----END CERTIFICATE----- + +AddTrust Public Services Root +============================= +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSAwHgYDVQQDExdBZGRU +cnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAxMDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJ +BgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5l +dHdvcmsxIDAeBgNVBAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV6tsfSlbu +nyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nXGCwwfQ56HmIexkvA/X1i +d9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnPdzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSG +Aa2Il+tmzV7R/9x98oTaunet3IAIx6eH1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAw +HM+A+WD+eeSI8t0A65RF62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0G +A1UdDgQWBBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDELMAkGA1UEBhMCU0Ux +FDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29yazEgMB4G +A1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4 +JNojVhaTdt02KLmuG7jD8WS6IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL ++YPoRNWyQSW/iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao +GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh4SINhwBk/ox9 +Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQmXiLsks3/QppEIW1cxeMiHV9H +EufOX1362KqxMy3ZdvJOOjMMK7MtkAY= +-----END CERTIFICATE----- + +AddTrust Qualified Certificates Root +==================================== +-----BEGIN CERTIFICATE----- +MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSMwIQYDVQQDExpBZGRU +cnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcx +CzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQ +IE5ldHdvcmsxIzAhBgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwqxBb/4Oxx +64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G87B4pfYOQnrjfxvM0PC3 +KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i2O+tCBGaKZnhqkRFmhJePp1tUvznoD1o +L/BLcHwTOK28FSXx1s6rosAx1i+f4P8UWfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GR +wVY18BTcZTYJbqukB8c10cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HU +MIHRMB0GA1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6FrpGkwZzELMAkGA1UE +BhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29y +azEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlmaWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQAD +ggEBABmrder4i2VhlRO6aQTvhsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxG +GuoYQ992zPlmhpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X +dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3P6CxB9bpT9ze +RXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9YiQBCYz95OdBEsIJuQRno3eDB +iFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5noxqE= +-----END CERTIFICATE----- + +Entrust Root Certification Authority +==================================== +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw +b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG +A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0 +MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu +MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu +Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v +dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz +A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww +Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68 +j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN +rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1 +MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH +hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM +Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa +v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS +W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0 +tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +RSA Security 2048 v3 +==================== +-----BEGIN CERTIFICATE----- +MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6MRkwFwYDVQQK +ExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJpdHkgMjA0OCBWMzAeFw0wMTAy +MjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAXBgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAb +BgNVBAsTFFJTQSBTZWN1cml0eSAyMDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAt49VcdKA3XtpeafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7 +Jylg/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGlwSMiuLgb +WhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnhAMFRD0xS+ARaqn1y07iH +KrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP ++Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpuAWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/ +MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4E +FgQUB8NRMKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYcHnmY +v/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/Zb5gEydxiKRz44Rj +0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+f00/FGj1EVDVwfSQpQgdMWD/YIwj +VAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVOrSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395 +nzIlQnQFgCi/vcEkllgVsRch6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kA +pKnXwiJPZ9d37CAFYd4= +-----END CERTIFICATE----- + +GeoTrust Global CA +================== +-----BEGIN CERTIFICATE----- +MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQK +Ew1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0EwHhcNMDIwNTIxMDQw +MDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j +LjEbMBkGA1UEAxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjo +BbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDviS2Aelet +8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU1XupGc1V3sjs0l44U+Vc +T4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagU +vTLrGAMoUgRx5aszPeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVk +DBF9qn1luMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKInZ57Q +zxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfStQWVYrmm3ok9Nns4 +d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcFPseKUgzbFbS9bZvlxrFUaKnjaZC2 +mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Unhw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6p +XE0zX5IJL4hmXXeXxx12E6nV5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvm +Mw== +-----END CERTIFICATE----- + +GeoTrust Global CA 2 +==================== +-----BEGIN CERTIFICATE----- +MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN +R2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwHhcNMDQwMzA0MDUw +MDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j +LjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDvPE1APRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/ +NTL8Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hLTytCOb1k +LUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL5mkWRxHCJ1kDs6ZgwiFA +Vvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7S4wMcoKK+xfNAGw6EzywhIdLFnopsk/b +HdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNH +K266ZUapEBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6tdEPx7 +srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv/NgdRN3ggX+d6Yvh +ZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywNA0ZF66D0f0hExghAzN4bcLUprbqL +OzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkC +x1YAzUm5s2x7UwQa4qjJqhIFI8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqF +H4z1Ir+rzoPz4iIprn2DQKi6bA== +-----END CERTIFICATE----- + +GeoTrust Universal CA +===================== +-----BEGIN CERTIFICATE----- +MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN +R2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVyc2FsIENBMB4XDTA0MDMwNDA1 +MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu +Yy4xHjAcBgNVBAMTFUdlb1RydXN0IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAKYVVaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9t +JPi8cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTTQjOgNB0e +RXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFhF7em6fgemdtzbvQKoiFs +7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2vc7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d +8Lsrlh/eezJS/R27tQahsiFepdaVaH/wmZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7V +qnJNk22CDtucvc+081xdVHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3Cga +Rr0BHdCXteGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZf9hB +Z3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfReBi9Fi1jUIxaS5BZu +KGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+nhutxx9z3SxPGWX9f5NAEC7S8O08 +ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0 +XG0D08DYj3rWMB8GA1UdIwQYMBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIB +hjANBgkqhkiG9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc +aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fXIwjhmF7DWgh2 +qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzynANXH/KttgCJwpQzgXQQpAvvL +oJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0zuzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsK +xr2EoyNB3tZ3b4XUhRxQ4K5RirqNPnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxF +KyDuSN/n3QmOGKjaQI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2 +DFKWkoRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9ER/frslK +xfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQtDF4JbAiXfKM9fJP/P6EU +p8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/SfuvmbJxPgWp6ZKy7PtXny3YuxadIwVyQD8vI +P/rmMuGNG2+k5o7Y+SlIis5z/iw= +-----END CERTIFICATE----- + +GeoTrust Universal CA 2 +======================= +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN +R2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwHhcNMDQwMzA0 +MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3Qg +SW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0 +DE81WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUGFF+3Qs17 +j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdqXbboW0W63MOhBW9Wjo8Q +JqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxLse4YuU6W3Nx2/zu+z18DwPw76L5GG//a +QMJS9/7jOvdqdzXQ2o3rXhhqMcceujwbKNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2 +WP0+GfPtDCapkzj4T8FdIgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP +20gaXT73y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRthAAn +ZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgocQIgfksILAAX/8sgC +SqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4Lt1ZrtmhN79UNdxzMk+MBB4zsslG +8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2 ++/CfXGJx7Tz0RzgQKzAfBgNVHSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8E +BAMCAYYwDQYJKoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z +dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQL1EuxBRa3ugZ +4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgrFg5fNuH8KrUwJM/gYwx7WBr+ +mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSoag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpq +A1Ihn0CoZ1Dy81of398j9tx4TuaYT1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpg +Y+RdM4kX2TGq2tbzGDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiP +pm8m1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJVOCiNUW7d +FGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH6aLcr34YEoP9VhdBLtUp +gn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwXQMAJKOSLakhT2+zNVVXxxvjpoixMptEm +X36vWkzaH6byHCx+rgIW0lbQL1dTR+iS +-----END CERTIFICATE----- + +America Online Root Certification Authority 1 +============================================= +-----BEGIN CERTIFICATE----- +MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT +QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkG +A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg +T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lkhsmj76CG +v2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym1BW32J/X3HGrfpq/m44z +DyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsWOqMFf6Dch9Wc/HKpoH145LcxVR5lu9Rh +sCFg7RAycsWSJR74kEoYeEfffjA3PlAb2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP +8c9GsEsPPt2IYriMqQkoO3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAUAK3Z +o/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBBQUAA4IBAQB8itEf +GDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkFZu90821fnZmv9ov761KyBZiibyrF +VL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAbLjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft +3OJvx8Fi8eNy1gTIdGcL+oiroQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43g +Kd8hdIaC2y+CMMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds +sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7 +-----END CERTIFICATE----- + +America Online Root Certification Authority 2 +============================================= +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT +QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkG +A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg +T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC206B89en +fHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFciKtZHgVdEglZTvYYUAQv8 +f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2JxhP7JsowtS013wMPgwr38oE18aO6lhO +qKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JN +RvCAOVIyD+OEsnpD8l7eXz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0 +gBe4lL8BPeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67Xnfn +6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEqZ8A9W6Wa6897Gqid +FEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZo2C7HK2JNDJiuEMhBnIMoVxtRsX6 +Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnj +B453cMor9H124HhnAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3Op +aaEg5+31IqEjFNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmnxPBUlgtk87FY +T15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2LHo1YGwRgJfMqZJS5ivmae2p ++DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzcccobGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXg +JXUjhx5c3LqdsKyzadsXg8n33gy8CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//Zoy +zH1kUQ7rVyZ2OuMeIjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgO +ZtMADjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2FAjgQ5ANh +1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUXOm/9riW99XJZZLF0Kjhf +GEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPbAZO1XB4Y3WRayhgoPmMEEf0cjQAPuDff +Z4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQlZvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuP +cX/9XhmgD0uRuMRUvAawRY8mkaKO/qk= +-----END CERTIFICATE----- + +Visa eCommerce Root +=================== +-----BEGIN CERTIFICATE----- +MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQG +EwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2Ug +QXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2 +WhcNMjIwNjI0MDAxNjEyWjBrMQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMm +VmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv +bW1lcmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h2mCxlCfL +F9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4ElpF7sDPwsRROEW+1QK8b +RaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdVZqW1LS7YgFmypw23RuwhY/81q6UCzyr0 +TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI +/k4+oKsGGelT84ATB+0tvz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzs +GHxBvfaLdXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG +MB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUFAAOCAQEAX/FBfXxc +CLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcRzCSs00Rsca4BIGsDoo8Ytyk6feUW +YFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pz +zkWKsKZJ/0x9nXGIxHYdkFsd7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBu +YQa7FkKMcPcw++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt +398znM/jra6O1I7mT1GvFpLgXPYHDw== +-----END CERTIFICATE----- + +Certum Root CA +============== +-----BEGIN CERTIFICATE----- +MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQK +ExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQTAeFw0wMjA2MTExMDQ2Mzla +Fw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8u +by4xEjAQBgNVBAMTCUNlcnR1bSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6x +wS7TT3zNJc4YPk/EjG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdL +kKWoePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GIULdtlkIJ +89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapuOb7kky/ZR6By6/qmW6/K +Uz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUgAKpoC6EahQGcxEZjgoi2IrHu/qpGWX7P +NSzVttpd90gzFFS269lvzs2I1qsb2pY7HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkq +hkiG9w0BAQUFAAOCAQEAuI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+ +GXYkHAQaTOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTgxSvg +GrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1qCjqTE5s7FCMTY5w/ +0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5xO/fIR/RpbxXyEV6DHpx8Uq79AtoS +qFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs6GAqm4VKQPNriiTsBhYscw== +-----END CERTIFICATE----- + +Comodo AAA Services root +======================== +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw +MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl +c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG +C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs +i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW +Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH +Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK +Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f +BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl +cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz +LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm +7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z +8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C +12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +Comodo Secure Services root +=========================== +-----BEGIN CERTIFICATE----- +MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAw +MDAwMFoXDTI4MTIzMTIzNTk1OVowfjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFu +Y2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAi +BgNVBAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPMcm3ye5drswfxdySRXyWP +9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3SHpR7LZQdqnXXs5jLrLxkU0C8j6ysNstc +rbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rC +oznl2yY4rYsK7hljxxwk3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3V +p6ea5EQz6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNVHQ4E +FgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w +gYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL1NlY3VyZUNlcnRpZmlj +YXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRwOi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlm +aWNhdGVTZXJ2aWNlcy5jcmwwDQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm +4J4oqF7Tt/Q05qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj +Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtIgKvcnDe4IRRL +DXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJaD61JlfutuC23bkpgHl9j6Pw +pCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDlizeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1H +RR3B7Hzs/Sk= +-----END CERTIFICATE----- + +Comodo Trusted Services root +============================ +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEw +MDAwMDBaFw0yODEyMzEyMzU5NTlaMH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1h +bmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUw +IwYDVQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWWfnJSoBVC21ndZHoa0Lh7 +3TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMtTGo87IvDktJTdyR0nAducPy9C1t2ul/y +/9c3S0pgePfw+spwtOpZqqPOSC+pw7ILfhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6 +juljatEPmsbS9Is6FARW1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsS +ivnkBbA7kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0GA1Ud +DgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21vZG9jYS5jb20vVHJ1c3RlZENlcnRp +ZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRodHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENl +cnRpZmljYXRlU2VydmljZXMuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8Ntw +uleGFTQQuS9/HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32 +pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxISjBc/lDb+XbDA +BHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+xqFx7D+gIIxmOom0jtTYsU0l +R+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/AtyjcndBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O +9y5Xt5hwXsjEeLBi +-----END CERTIFICATE----- + +QuoVadis Root CA +================ +-----BEGIN CERTIFICATE----- +MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJCTTEZMBcGA1UE +ChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 +eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAz +MTkxODMzMzNaFw0yMTAzMTcxODMzMzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRp +cyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQD +EyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Ypli4kVEAkOPcahdxYTMuk +J0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2DrOpm2RgbaIr1VxqYuvXtdj182d6UajtL +F8HVj71lODqV0D1VNk7feVcxKh7YWWVJWCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeL +YzcS19Dsw3sgQUSj7cugF+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWen +AScOospUxbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCCAk4w +PQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVvdmFkaXNvZmZzaG9y +ZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREwggENMIIBCQYJKwYBBAG+WAABMIH7 +MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNlIG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmlj +YXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJs +ZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh +Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYIKwYBBQUHAgEW +Fmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3TKbkGGew5Oanwl4Rqy+/fMIGu +BgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rqy+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkw +FwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6 +tlCLMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSkfnIYj9lo +fFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf87C9TqnN7Az10buYWnuul +LsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1RcHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2x +gI4JVrmcGmD+XcHXetwReNDWXcG31a0ymQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi +5upZIof4l/UO/erMkqQWxFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi +5nrQNiOKSnQ2+Q== +-----END CERTIFICATE----- + +QuoVadis Root CA 2 +================== +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx +ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6 +XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk +lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB +lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy +lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt +66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn +wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh +D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy +BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie +J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud +DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU +a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv +Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3 +UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm +VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK ++JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW +IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1 +WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X +f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II +4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8 +VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +QuoVadis Root CA 3 +================== +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx +OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg +DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij +KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K +DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv +BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp +p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8 +nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX +MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM +Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz +uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT +BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj +YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB +BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD +VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4 +ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE +AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV +qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s +hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z +POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2 +Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp +8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC +bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu +g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p +vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr +qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +Security Communication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP +U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw +HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP +U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw +8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM +DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX +5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd +DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2 +JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g +0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a +mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ +s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ +6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi +FL39vmwLAw== +-----END CERTIFICATE----- + +Sonera Class 2 Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEPMA0GA1UEChMG +U29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAxMDQwNjA3Mjk0MFoXDTIxMDQw +NjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNVBAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJh +IENsYXNzMiBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3 +/Ei9vX+ALTU74W+oZ6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybT +dXnt5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s3TmVToMG +f+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2EjvOr7nQKV0ba5cTppCD8P +tOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu8nYybieDwnPz3BjotJPqdURrBGAgcVeH +nfO+oJAjPYok4doh28MCAwEAAaMzMDEwDwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITT +XjwwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt +0jSv9zilzqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/3DEI +cbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvDFNr450kkkdAdavph +Oe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6Tk6ezAyNlNzZRZxe7EJQY670XcSx +EtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLH +llpwrN9M +-----END CERTIFICATE----- + +Staat der Nederlanden Root CA +============================= +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJOTDEeMBwGA1UE +ChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFhdCBkZXIgTmVkZXJsYW5kZW4g +Um9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEyMTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4w +HAYDVQQKExVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxh +bmRlbiBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFt +vsznExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw719tV2U02P +jLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MOhXeiD+EwR+4A5zN9RGca +C1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+UtFE5A3+y3qcym7RHjm+0Sq7lr7HcsBth +vJly3uSJt3omXdozSVtSnA71iq3DuD3oBmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn6 +22r+I/q85Ej0ZytqERAhSQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRV +HSAAMDwwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMvcm9v +dC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA7Jbg0zTBLL9s+DAN +BgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k/rvuFbQvBgwp8qiSpGEN/KtcCFtR +EytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzmeafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbw +MVcoEoJz6TMvplW0C5GUR5z6u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3y +nGQI0DvDKcWy7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR +iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw== +-----END CERTIFICATE----- + +TDC Internet Root CA +==================== +-----BEGIN CERTIFICATE----- +MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJESzEVMBMGA1UE +ChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTAeFw0wMTA0MDUx +NjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNVBAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJu +ZXQxHTAbBgNVBAsTFFREQyBJbnRlcm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxLhAvJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20j +xsNuZp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a0vnRrEvL +znWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc14izbSysseLlJ28TQx5yc +5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGNeGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6 +otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcDR0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZI +AYb4QgEBBAQDAgAHMGUGA1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMM +VERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxMEQ1JM +MTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3WjALBgNVHQ8EBAMC +AQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAwHQYDVR0OBBYEFGxkAcf9hW2syNqe +UAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJKoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0G +CSqGSIb3DQEBBQUAA4IBAQBOQ8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540m +gwV5dOy0uaOXwTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+ +2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm899qNLPg7kbWzb +O0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0jUNAE4z9mQNUecYu6oah9jrU +Cbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38aQNiuJkFBT1reBK9sG9l +-----END CERTIFICATE----- + +UTN DATACorp SGC Root CA +======================== +-----BEGIN CERTIFICATE----- +MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UE +BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl +IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZ +BgNVBAMTElVUTiAtIERBVEFDb3JwIFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBa +MIGTMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4w +HAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRy +dXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ys +raP6LnD43m77VkIVni5c7yPeIbkFdicZD0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlo +wHDyUwDAXlCCpVZvNvlK4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA +9P4yPykqlXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulWbfXv +33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQABo4GrMIGoMAsGA1Ud +DwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRTMtGzz3/64PGgXYVOktKeRR20TzA9 +BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dD +LmNybDAqBgNVHSUEIzAhBggrBgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3 +DQEBBQUAA4IBAQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft +Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyjj98C5OBxOvG0 +I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVHKWss5nbZqSl9Mt3JNjy9rjXx +EZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwP +DPafepE39peC4N1xaf92P2BNPM/3mfnGV/TJVTl4uix5yaaIK/QI +-----END CERTIFICATE----- + +UTN USERFirst Hardware Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UE +BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl +IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAd +BgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgx +OTIyWjCBlzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0 +eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVz +ZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlI +wrthdBKWHTxqctU8EGc6Oe0rE81m65UJM6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFd +tqdt++BxF2uiiPsA3/4aMXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8 +i4fDidNdoI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqIDsjf +Pe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9KsyoUhbAgMBAAGjgbkw +gbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKFyXyYbKJhDlV0HN9WF +lp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNF +UkZpcnN0LUhhcmR3YXJlLmNybDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUF +BwMGBggrBgEFBQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM +//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28GpgoiskliCE7/yMgUsogW +XecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gECJChicsZUN/KHAG8HQQZexB2 +lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kn +iCrVWFCVH/A7HFe7fRQ5YiuayZSSKqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67 +nfhmqA== +-----END CERTIFICATE----- + +Camerfirma Chambers of Commerce Root +==================================== +-----BEGIN CERTIFICATE----- +MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe +QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i +ZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAx +NjEzNDNaFw0zNzA5MzAxNjEzNDRaMH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZp +cm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3Jn +MSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0BAQEFAAOC +AQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtbunXF/KGIJPov7coISjlU +xFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0dBmpAPrMMhe5cG3nCYsS4No41XQEMIwRH +NaqbYE6gZj3LJgqcQKH0XZi/caulAGgq7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jW +DA+wWFjbw2Y3npuRVDM30pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFV +d9oKDMyXroDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIGA1Ud +EwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5jaGFtYmVyc2lnbi5v +cmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p26EpW1eLTXYGduHRooowDgYDVR0P +AQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hh +bWJlcnNpZ24ub3JnMCcGA1UdEgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYD +VR0gBFEwTzBNBgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz +aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEBAAxBl8IahsAi +fJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZdp0AJPaxJRUXcLo0waLIJuvvD +L8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wN +UPf6s+xCX6ndbcj0dc97wXImsQEcXCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/n +ADydb47kMgkdTXg0eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1 +erfutGWaIZDgqtCYvDi1czyL+Nw= +-----END CERTIFICATE----- + +Camerfirma Global Chambersign Root +================================== +-----BEGIN CERTIFICATE----- +MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe +QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i +ZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYx +NDE4WhcNMzcwOTMwMTYxNDE4WjB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJt +YSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEg +MB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAw +ggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0Mi+ITaFgCPS3CU6gSS9J +1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/sQJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8O +by4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpVeAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl +6DJWk0aJqCWKZQbua795B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c +8lCrEqWhz0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0TAQH/ +BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1iZXJzaWduLm9yZy9j +aGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4wTcbOX60Qq+UDpfqpFDAOBgNVHQ8B +Af8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAHMCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBj +aGFtYmVyc2lnbi5vcmcwKgYDVR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9y +ZzBbBgNVHSAEVDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh +bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0BAQUFAAOCAQEA +PDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUMbKGKfKX0j//U2K0X1S0E0T9Y +gOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXiryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJ +PJ7oKXqJ1/6v/2j1pReQvayZzKWGVwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4 +IBHNfTIzSJRUTN3cecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREes +t2d/AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A== +-----END CERTIFICATE----- + +NetLock Notary (Class A) Root +============================= +-----BEGIN CERTIFICATE----- +MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQI +EwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6 +dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9j +ayBLb3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oX +DTE5MDIxOTIzMTQ0N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQH +EwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYD +VQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFz +cyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSM +D7tM9DceqQWC2ObhbHDqeLVu0ThEDaiDzl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZ +z+qMkjvN9wfcZnSX9EUi3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC +/tmwqcm8WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LYOph7 +tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2EsiNCubMvJIH5+hCoR6 +4sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCCApswDgYDVR0PAQH/BAQDAgAGMBIG +A1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaC +Ak1GSUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pv +bGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu +IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2Vn +LWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0 +ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFz +IGxlaXJhc2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBh +IGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVu +b3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBh +bmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sg +Q1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFp +bCBhdCBjcHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5 +ayZrU3/b39/zcT0mwBQOxmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjP +ytoUMaFP0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQQeJB +CWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxkf1qbFFgBJ34TUMdr +KuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK8CtmdWOMovsEPoMOmzbwGOQmIMOM +8CgHrTwXZoi1/baI +-----END CERTIFICATE----- + +NetLock Business (Class B) Root +=============================== +-----BEGIN CERTIFICATE----- +MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUxETAPBgNVBAcT +CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV +BAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQDEylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikg +VGFudXNpdHZhbnlraWFkbzAeFw05OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYD +VQQGEwJIVTERMA8GA1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRv +bnNhZ2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5ldExvY2sg +VXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xKgZjupNTKihe5In+DCnVMm8Bp2GQ5o+2S +o/1bXHQawEfKOml2mrriRBf8TKPV/riXiK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr +1nGTLbO/CVRY7QbrqHvcQ7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNV +HQ8BAf8EBAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZ +RUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRh +dGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQuIEEgaGl0 +ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRv +c2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUg +YXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh +c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBz +Oi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6ZXNA +bmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhl +IHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2 +YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBj +cHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06sPgzTEdM +43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXan3BukxowOR0w2y7jfLKR +stE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKSNitjrFgBazMpUIaD8QFI +-----END CERTIFICATE----- + +NetLock Express (Class C) Root +============================== +-----BEGIN CERTIFICATE----- +MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUxETAPBgNVBAcT +CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV +BAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQDEytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBD +KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJ +BgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6 +dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMrTmV0TG9j +ayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzANBgkqhkiG9w0BAQEFAAOB +jQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNAOoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3Z +W3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63 +euyucYT2BDMIJTLrdKwWRMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQw +DgYDVR0PAQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEWggJN +RklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0YWxhbm9zIFN6b2xn +YWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFzb2sgYWxhcGphbiBrZXN6dWx0LiBB +IGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBOZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1i +aXp0b3NpdGFzYSB2ZWRpLiBBIGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0 +ZWxlIGF6IGVsb2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs +ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25sYXBqYW4gYSBo +dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kga2VyaGV0byBheiBlbGxlbm9y +emVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4gSU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5k +IHRoZSB1c2Ugb2YgdGhpcyBjZXJ0aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQ +UyBhdmFpbGFibGUgYXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwg +YXQgY3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmYta3UzbM2 +xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2gpO0u9f38vf5NNwgMvOOW +gyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4Fp1hBWeAyNDYpQcCNJgEjTME1A== +-----END CERTIFICATE----- + +XRamp Global CA Root +==================== +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE +BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj +dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx +HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg +U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu +IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx +foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE +zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs +AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry +xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap +oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC +AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc +/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n +nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz +8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +Go Daddy Class 2 CA +=================== +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY +VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG +A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g +RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD +ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv +2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32 +qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j +YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY +vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O +BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o +atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu +MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim +PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt +I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI +Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b +vZ8= +-----END CERTIFICATE----- + +Starfield Class 2 CA +==================== +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc +U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo +MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG +A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG +SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY +bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ +JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm +epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN +F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF +MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f +hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo +bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g +QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs +afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM +PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD +KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3 +QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +StartCom Certification Authority +================================ +-----BEGIN CERTIFICATE----- +MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN +U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu +ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0 +NjM2WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk +LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg +U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y +o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/ +Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d +eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt +2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z +6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ +osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/ +untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc +UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT +37uMdBNSSwIDAQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE +FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9jZXJ0LnN0YXJ0 +Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3JsLnN0YXJ0Y29tLm9yZy9zZnNj +YS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFMBgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUH +AgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRw +Oi8vY2VydC5zdGFydGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYg +U3RhcnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlhYmlsaXR5 +LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2YgdGhlIFN0YXJ0Q29tIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFpbGFibGUgYXQgaHR0cDovL2NlcnQuc3Rh +cnRjb20ub3JnL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilT +dGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOC +AgEAFmyZ9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8jhvh +3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUWFjgKXlf2Ysd6AgXm +vB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJzewT4F+irsfMuXGRuczE6Eri8sxHk +fY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3 +fsNrarnDy0RLrHiQi+fHLB5LEUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZ +EoalHmdkrQYuL6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq +yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuCO3NJo2pXh5Tl +1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6Vum0ABj6y6koQOdjQK/W/7HW/ +lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkyShNOsF/5oirpt9P/FlUQqmMGqz9IgcgA38coro +g14= +-----END CERTIFICATE----- + +Taiwan GRCA +=========== +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/MQswCQYDVQQG +EwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4X +DTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1owPzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dv +dmVybm1lbnQgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qN +w8XRIePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1qgQdW8or5 +BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKyyhwOeYHWtXBiCAEuTk8O +1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAtsF/tnyMKtsc2AtJfcdgEWFelq16TheEfO +htX7MfP6Mb40qij7cEwdScevLJ1tZqa2jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wov +J5pGfaENda1UhhXcSTvxls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7 +Q3hub/FCVGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHKYS1t +B6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoHEgKXTiCQ8P8NHuJB +O9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThNXo+EHWbNxWCWtFJaBYmOlXqYwZE8 +lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1UdDgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNV +HRMEBTADAQH/MDkGBGcqBwAEMTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg2 +09yewDL7MTqKUWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ +TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyfqzvS/3WXy6Tj +Zwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaKZEk9GhiHkASfQlK3T8v+R0F2 +Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFEJPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlU +D7gsL0u8qV1bYH+Mh6XgUmMqvtg7hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6Qz +DxARvBMB1uUO07+1EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+Hbk +Z6MmnD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WXudpVBrkk +7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44VbnzssQwmSNOXfJIoRIM3BKQ +CZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDeLMDDav7v3Aun+kbfYNucpllQdSNpc5Oy ++fwC00fmcc4QAu4njIT/rEUNE1yDMuAlpYYsfPQS +-----END CERTIFICATE----- + +Swisscom Root CA 1 +================== +-----BEGIN CERTIFICATE----- +MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQG +EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy +dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4 +MTgyMjA2MjBaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln +aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIIC +IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9m2BtRsiM +MW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdihFvkcxC7mlSpnzNApbjyF +NDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/TilftKaNXXsLmREDA/7n29uj/x2lzZAe +AR81sH8A25Bvxn570e56eqeqDFdvpG3FEzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkC +b6dJtDZd0KTeByy2dbcokdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn +7uHbHaBuHYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNFvJbN +cA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo19AOeCMgkckkKmUp +WyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjCL3UcPX7ape8eYIVpQtPM+GP+HkM5 +haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJWbjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNY +MUJDLXT5xp6mig/p/r+D5kNXJLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw +HQYDVR0hBBYwFDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j +BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzcK6FptWfUjNP9 +MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzfky9NfEBWMXrrpA9gzXrzvsMn +jgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7IkVh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQ +MbFamIp1TpBcahQq4FJHgmDmHtqBsfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4H +VtA4oJVwIHaM190e3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtl +vrsRls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ipmXeascCl +OS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HHb6D0jqTsNFFbjCYDcKF3 +1QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksfrK/7DZBaZmBwXarNeNQk7shBoJMBkpxq +nvy5JMWzFYJ+vq6VK+uxwNrjAWALXmmshFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCy +x/yP2FS1k2Kdzs9Z+z0YzirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMW +NY6E0F/6MBr1mmz0DlP5OlvRHA== +-----END CERTIFICATE----- + +DigiCert Assured ID Root CA +=========================== +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx +MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO +9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy +UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW +/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy +oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf +GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF +66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq +hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc +EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn +SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i +8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +DigiCert Global Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw +MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn +TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5 +BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H +4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y +7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB +o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm +8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF +BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr +EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt +tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886 +UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +DigiCert High Assurance EV Root CA +================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw +KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw +MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ +MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu +Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t +Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS +OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 +MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ +NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe +h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY +JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ +V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp +myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK +mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K +-----END CERTIFICATE----- + +Certplus Class 2 Primary CA +=========================== +-----BEGIN CERTIFICATE----- +MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAwPTELMAkGA1UE +BhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFzcyAyIFByaW1hcnkgQ0EwHhcN +OTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2Vy +dHBsdXMxGzAZBgNVBAMTEkNsYXNzIDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBANxQltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR +5aiRVhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyLkcAbmXuZ +Vg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCdEgETjdyAYveVqUSISnFO +YFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yasH7WLO7dDWWuwJKZtkIvEcupdM5i3y95e +e++U8Rs+yskhwcWYAqqi9lt3m/V+llU0HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRME +CDAGAQH/AgEKMAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJ +YIZIAYb4QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMuY29t +L0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/AN9WM2K191EBkOvD +P9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8yfFC82x/xXp8HVGIutIKPidd3i1R +TtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMRFcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+ +7UCmnYR0ObncHoUW2ikbhiMAybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW +//1IMwrh3KWBkJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7 +l7+ijrRU +-----END CERTIFICATE----- + +DST Root CA X3 +============== +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYDVQQK +ExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4X +DTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1 +cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmT +rE4Orz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEqOLl5CjH9 +UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9bxiqKqy69cK3FCxolkHRy +xXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40d +utolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQ +MA0GCSqGSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69ikug +dB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjE +GB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bw +RLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubS +fZGL+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- + +DST ACES CA X6 +============== +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBbMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QxETAPBgNVBAsTCERTVCBBQ0VT +MRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0wMzExMjAyMTE5NThaFw0xNzExMjAyMTE5NTha +MFsxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UE +CxMIRFNUIEFDRVMxFzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPuktKe1jzI +DZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7gLFViYsx+tC3dr5BPTCa +pCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZHfAjIgrrep4c9oW24MFbCswKBXy314pow +GCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4aahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPy +MjwmR/onJALJfh1biEITajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rkc3Qu +Y29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjtodHRwOi8vd3d3LnRy +dXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMtaW5kZXguaHRtbDAdBgNVHQ4EFgQU +CXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZIhvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V2 +5FYrnJmQ6AgwbN99Pe7lv7UkQIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6t +Fr8hlxCBPeP/h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq +nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpRrscL9yuwNwXs +vFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf29w4LTJxoeHtxMcfrHuBnQfO3 +oKfN5XozNmr6mis= +-----END CERTIFICATE----- + +TURKTRUST Certificate Services Provider Root 1 +============================================== +-----BEGIN CERTIFICATE----- +MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGDAJUUjEP +MA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykgMjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0 +acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMx +MDI3MTdaFw0xNTAzMjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsg +U2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYDVQQHDAZB +TktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kgxLBsZXRpxZ9pbSB2ZSBC +aWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEuxZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GX +yGl8hMW0kWxsE2qkVa2kheiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8i +Si9BB35JYbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5CurKZ +8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1JuTm5Rh8i27fbMx4 +W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51b0dewQIDAQABoxAwDjAMBgNVHRME +BTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46 +sWrv7/hg0Uw2ZkUd82YCdAR7kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxE +q8Sn5RTOPEFhfEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy +B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdAaLX/7KfS0zgY +nNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKSRGQDJereW26fyfJOrN3H +-----END CERTIFICATE----- + +TURKTRUST Certificate Services Provider Root 2 +============================================== +-----BEGIN CERTIFICATE----- +MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP +MA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg +QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcN +MDUxMTA3MTAwNzU3WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVr +dHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEPMA0G +A1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmls +acWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqe +LCDe2JAOCtFp0if7qnefJ1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKI +x+XlZEdhR3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJQv2g +QrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGXJHpsmxcPbe9TmJEr +5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1pzpwACPI2/z7woQ8arBT9pmAPAgMB +AAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58SFq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/ntt +Rbj2hWyfIvwqECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4 +Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFzgw2lGh1uEpJ+ +hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotHuFEJjOp9zYhys2AzsfAKRO8P +9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LSy3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5 +UrbnBEI= +-----END CERTIFICATE----- + +SwissSign Gold CA - G2 +====================== +-----BEGIN CERTIFICATE----- +MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw +EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN +MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp +c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq +t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C +jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg +vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF +ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR +AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend +jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO +peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR +7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi +GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64 +OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov +L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm +5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr +44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf +Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m +Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp +mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk +vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf +KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br +NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj +viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ +-----END CERTIFICATE----- + +SwissSign Silver CA - G2 +======================== +-----BEGIN CERTIFICATE----- +MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT +BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X +DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3 +aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644 +N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm ++/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH +6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu +MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h +qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5 +FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs +ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc +celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X +CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB +tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 +cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P +4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F +kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L +3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx +/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa +DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP +e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu +WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ +DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub +DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u +-----END CERTIFICATE----- + +GeoTrust Primary Certification Authority +======================================== +-----BEGIN CERTIFICATE----- +MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQG +EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMoR2VvVHJ1c3QgUHJpbWFyeSBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgx +CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQ +cmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9AWbK7hWN +b6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjAZIVcFU2Ix7e64HXprQU9 +nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE07e9GceBrAqg1cmuXm2bgyxx5X9gaBGge +RwLmnWDiNpcB3841kt++Z8dtd1k7j53WkBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGt +tm/81w7a4DSwDRp35+MImO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJKoZI +hvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ16CePbJC/kRYkRj5K +Ts4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl4b7UVXGYNTq+k+qurUKykG/g/CFN +NWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6KoKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHa +Floxt/m0cYASSJlyc1pZU8FjUjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG +1riR/aYNKxoUAT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= +-----END CERTIFICATE----- + +thawte Primary Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCBqTELMAkGA1UE +BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2 +aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3 +MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwg +SW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMv +KGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMT +FnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCs +oPD7gFnUnMekz52hWXMJEEUMDSxuaPFsW0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ +1CRfBsDMRJSUjQJib+ta3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGc +q/gcfomk6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6Sk/K +aAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94JNqR32HuHUETVPm4p +afs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD +VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XPr87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUF +AAOCAQEAeRHAS7ORtvzw6WfUDW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeE +uzLlQRHAd9mzYJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX +xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2/qxAeeWsEG89 +jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/LHbTY5xZ3Y+m4Q6gLkH3LpVH +z7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7jVaMaA== +-----END CERTIFICATE----- + +VeriSign Class 3 Public Primary Certification Authority - G5 +============================================================ +-----BEGIN CERTIFICATE----- +MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE +BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO +ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk +IHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCB +yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln +biBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBh +dXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKz +j/i5Vbext0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhD +Y2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/ +Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNHiDxpg8v+R70r +fk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/ +BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2Uv +Z2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy +aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqG +SIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzEp6B4Eq1iDkVwZMXnl2YtmAl+ +X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKE +KQsTb47bDN0lAtukixlE0kF6BWlKWE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiC +Km0oHw0LxOXnGiYZ4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vE +ZV8NhnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq +-----END CERTIFICATE----- + +SecureTrust CA +============== +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy +dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe +BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX +OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t +DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH +GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b +01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH +ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj +aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ +KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu +SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf +mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ +nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +Secure Global CA +================ +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH +bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg +MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg +Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx +YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ +bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g +8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV +HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi +0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn +oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA +MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+ +OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn +CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5 +3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +COMODO Certification Authority +============================== +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb +MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD +T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH ++7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww +xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV +4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA +1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI +rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k +b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC +AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP +OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc +IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN ++8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ== +-----END CERTIFICATE----- + +Network Solutions Certificate Authority +======================================= +-----BEGIN CERTIFICATE----- +MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQG +EwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydOZXR3b3Jr +IFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMx +MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu +MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwzc7MEL7xx +jOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPPOCwGJgl6cvf6UDL4wpPT +aaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rlmGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXT +crA/vGp97Eh/jcOrqnErU2lBUzS1sLnFBgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc +/Qzpf14Dl847ABSHJ3A4qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMB +AAGjgZcwgZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwubmV0c29sc3NsLmNv +bS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3JpdHkuY3JsMA0GCSqGSIb3DQEBBQUA +A4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc86fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q +4LqILPxFzBiwmZVRDuwduIj/h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/ +GGUsyfJj4akH/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv +wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHNpGxlaKFJdlxD +ydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey +-----END CERTIFICATE----- + +WellsSecure Public Root Certificate Authority +============================================= +-----BEGIN CERTIFICATE----- +MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoM +F1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYw +NAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN +MDcxMjEzMTcwNzU0WhcNMjIxMjE0MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dl +bGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYD +VQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+rWxxTkqxtnt3CxC5FlAM1 +iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjUDk/41itMpBb570OYj7OeUt9tkTmPOL13 +i0Nj67eT/DBMHAGTthP796EfvyXhdDcsHqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8 +bJVhHlfXBIEyg1J55oNjz7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiB +K0HmOFafSZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/SlwxlAgMB +AAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqGKGh0dHA6Ly9jcmwu +cGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0PAQH/BAQDAgHGMB0GA1UdDgQWBBQm +lRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0jBIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGB +i6SBiDCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRww +GgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEBALkVsUSRzCPI +K0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd/ZDJPHV3V3p9+N701NX3leZ0 +bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pBA4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSlj +qHyita04pO2t/caaH/+Xc/77szWnk4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+es +E2fDbbFwRnzVlhE9iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJ +tylv2G0xffX8oRAHh84vWdw+WNs= +-----END CERTIFICATE----- + +COMODO ECC Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix +GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X +4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni +wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG +FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA +U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +IGC/A +===== +-----BEGIN CERTIFICATE----- +MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYTAkZSMQ8wDQYD +VQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVE +Q1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZy +MB4XDTAyMTIxMzE0MjkyM1oXDTIwMTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQI +EwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NT +STEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaIs9z4iPf930Pfeo2aSVz2 +TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCW +So7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYy +HF2fYPepraX/z9E0+X1bF8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNd +frGoRpAxVs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGdPDPQ +tQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNVHSAEDjAMMAoGCCqB +egF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAxNjAfBgNVHSMEGDAWgBSjBS8YYFDC +iQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUFAAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RK +q89toB9RlPhJy3Q2FLwV3duJL92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3Q +MZsyK10XZZOYYLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg +Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2aNjSaTFR+FwNI +lQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R0982gaEbeC9xs/FZTEYYKKuF +0mBWWg== +-----END CERTIFICATE----- + +Security Communication EV RootCA1 +================================= +-----BEGIN CERTIFICATE----- +MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMhU2VjdXJpdHkgQ29tbXVuaWNh +dGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIzMloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UE +BhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNl +Y3VyaXR5IENvbW11bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSERMqm4miO +/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gOzXppFodEtZDkBp2uoQSX +WHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4z +ZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDFMxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4 +bepJz11sS6/vmsJWXMY1VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK +9U2vP9eCOKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HWtWS3irO4G8za+6xm +iEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZq51ihPZRwSzJIxXYKLerJRO1RuGG +Av8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDbEJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnW +mHyojf6GPgcWkuF75x3sM3Z+Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEW +T1MKZPlO9L9OVL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490 +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GA CA +=============================== +-----BEGIN CERTIFICATE----- +MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCBijELMAkGA1UE +BhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHlyaWdodCAoYykgMjAwNTEiMCAG +A1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBH +bG9iYWwgUm9vdCBHQSBDQTAeFw0wNTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYD +VQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIw +IAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5 +IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy0+zAJs9 +Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxRVVuuk+g3/ytr6dTqvirdqFEr12bDYVxg +Asj1znJ7O7jyTmUIms2kahnBAbtzptf2w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbD +d50kc3vkDIzh2TbhmYsFmQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ +/yxViJGg4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t94B3R +LoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ +KoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOxSPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vIm +MMkQyh2I+3QZH4VFvbBsUfk2ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4 ++vg1YFkCExh8vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa +hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZiFj4A4xylNoEY +okxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ/L7fCg0= +-----END CERTIFICATE----- + +Microsec e-Szigno Root CA +========================= +-----BEGIN CERTIFICATE----- +MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAwcjELMAkGA1UE +BhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNyb3NlYyBMdGQuMRQwEgYDVQQL +EwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9zZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0 +MDYxMjI4NDRaFw0xNzA0MDYxMjI4NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVz +dDEWMBQGA1UEChMNTWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMT +GU1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2uuO/TEdyB5s87lozWbxXG +d36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/N +oqdNAoI/gqyFxuEPkEeZlApxcpMqyabAvjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjc +QR/Ji3HWVBTji1R4P770Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJ +PqW+jqpx62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcBAQRb +MFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3AwLQYIKwYBBQUHMAKG +IWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAPBgNVHRMBAf8EBTADAQH/MIIBcwYD +VR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIBAQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3 +LmUtc3ppZ25vLmh1L1NaU1ovMIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0A +dAB2AOEAbgB5ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn +AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABTAHoAbwBsAGcA +4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABhACAAcwB6AGUAcgBpAG4AdAAg +AGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABoAHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMA +egBpAGcAbgBvAC4AaAB1AC8AUwBaAFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6 +Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NO +PU1pY3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxPPU1pY3Jv +c2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDtiaW5h +cnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuBEGluZm9AZS1zemlnbm8uaHWkdzB1MSMw +IQYDVQQDDBpNaWNyb3NlYyBlLVN6aWduw7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhT +WjEWMBQGA1UEChMNTWljcm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhV +MIGsBgNVHSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJIVTER +MA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDASBgNVBAsTC2UtU3pp +Z25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBSb290IENBghEAzLjnv04pGv2i3Gal +HCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMT +nGZjWS7KXHAM/IO8VbH0jgdsZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FE +aGAHQzAxQmHl7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a +86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfRhUZLphK3dehK +yVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/MPMMNz7UwiiAc7EBt51alhQB +S6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU= +-----END CERTIFICATE----- + +Certigna +======== +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw +EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3 +MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI +Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q +XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH +GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p +ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg +DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf +Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ +tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ +BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J +SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA +hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+ +ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu +PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY +1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +AC Ra\xC3\xADz Certic\xC3\xA1mara S.A. +====================================== +-----BEGIN CERTIFICATE----- +MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNVBAYT +AkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRpZmljYWNpw7NuIERpZ2l0YWwg +LSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwaQUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4w +HhcNMDYxMTI3MjA0NjI5WhcNMzAwNDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+ +U29jaWVkYWQgQ2FtZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJh +IFMuQS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeGqentLhM0R7LQcNzJPNCN +yu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzLfDe3fezTf3MZsGqy2IiKLUV0qPezuMDU +2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQY5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU3 +4ojC2I+GdV75LaeHM/J4Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP +2yYe68yQ54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+bMMCm +8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48jilSH5L887uvDdUhf +HjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++EjYfDIJss2yKHzMI+ko6Kh3VOz3vCa +Mh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/ztA/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK +5lw1omdMEWux+IBkAC1vImHFrEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1b +czwmPS9KvqfJpxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE +AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCBlTCBkgYEVR0g +ADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFyYS5jb20vZHBjLzBaBggrBgEF +BQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW507WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2Ug +cHVlZGVuIGVuY29udHJhciBlbiBsYSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEf +AygPU3zmpFmps4p6xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuX +EpBcunvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/Jre7Ir5v +/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dpezy4ydV/NgIlqmjCMRW3 +MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42gzmRkBDI8ck1fj+404HGIGQatlDCIaR4 +3NAvO2STdPCWkPHv+wlaNECW8DYSwaN0jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wk +eZBWN7PGKX6jD/EpOe9+XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f +/RWmnkJDW2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/RL5h +RqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35rMDOhYil/SrnhLecU +Iw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxkBYn8eNZcLCZDqQ== +-----END CERTIFICATE----- + +TC TrustCenter Class 2 CA II +============================ +-----BEGIN CERTIFICATE----- +MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC +REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy +IENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYw +MTEyMTQzODQzWhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1 +c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UE +AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jftMjWQ+nEdVl//OEd+DFw +IxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKguNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2 +xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2JXjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQ +Xa7pIXSSTYtZgo+U4+lK8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7u +SNQZu+995OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3kUrL84J6E1wIqzCB +7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90 +Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU +cnVzdENlbnRlciUyMENsYXNzJTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i +SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u +TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iSGNn3Bzn1LL4G +dXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprtZjluS5TmVfwLG4t3wVMTZonZ +KNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8au0WOB9/WIFaGusyiC2y8zl3gK9etmF1Kdsj +TYjKUCjLhdLTEKJZbtOTVAB6okaVhgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kP +JOzHdiEoZa5X6AeIdUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfk +vQ== +-----END CERTIFICATE----- + +TC TrustCenter Class 3 CA II +============================ +-----BEGIN CERTIFICATE----- +MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC +REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy +IENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYw +MTEyMTQ0MTU3WhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1 +c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UE +AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJWHt4bNwcwIi9v8Qbxq63W +yKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+QVl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo +6SI7dYnWRBpl8huXJh0obazovVkdKyT21oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZ +uV3bOx4a+9P/FRQI2AlqukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk +2ZyqBwi1Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NXXAek0CSnwPIA1DCB +7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90 +Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU +cnVzdENlbnRlciUyMENsYXNzJTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i +SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u +TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlNirTzwppVMXzE +O2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8TtXqluJucsG7Kv5sbviRmEb8 +yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9 +IJqDnxrcOfHFcqMRA/07QlIp2+gB95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal +092Y+tTmBvTwtiBjS+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc +5A== +-----END CERTIFICATE----- + +TC TrustCenter Universal CA I +============================= +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTELMAkGA1UEBhMC +REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNVBAsTG1RDIFRydXN0Q2VudGVy +IFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcN +MDYwMzIyMTU1NDI4WhcNMjUxMjMxMjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMg +VHJ1c3RDZW50ZXIgR21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYw +JAYDVQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSRJJZ4Hgmgm5qVSkr1YnwC +qMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3TfCZdzHd55yx4Oagmcw6iXSVphU9VDprv +xrlE4Vc93x9UIuVvZaozhDrzznq+VZeujRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtw +ag+1m7Z3W0hZneTvWq3zwZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9O +gdwZu5GQfezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYDVR0j +BBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0GCSqGSIb3DQEBBQUAA4IBAQAo0uCG +1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X17caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/Cy +vwbZ71q+s2IhtNerNXxTPqYn8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3 +ghUJGooWMNjsydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT +ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/2TYcuiUaUj0a +7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY +-----END CERTIFICATE----- + +Deutsche Telekom Root CA 2 +========================== +-----BEGIN CERTIFICATE----- +MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMT +RGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEG +A1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENBIDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5 +MjM1OTAwWjBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0G +A1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBS +b290IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEUha88EOQ5 +bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhCQN/Po7qCWWqSG6wcmtoI +KyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1MjwrrFDa1sPeg5TKqAyZMg4ISFZbavva4VhY +AUlfckE8FQYBjl2tqriTtM2e66foai1SNNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aK +Se5TBY8ZTNXeWHmb0mocQqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTV +jlsB9WoHtxa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAPBgNV +HRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAlGRZrTlk5ynr +E/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756AbrsptJh6sTtU6zkXR34ajgv8HzFZMQSy +zhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpaIzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8 +rZ7/gFnkm0W09juwzTkZmDLl6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4G +dyd1Lx+4ivn+xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU +Cm26OWMohpLzGITY+9HPBVZkVw== +-----END CERTIFICATE----- + +ComSign Secured CA +================== +-----BEGIN CERTIFICATE----- +MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAwPDEbMBkGA1UE +AxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWduMQswCQYDVQQGEwJJTDAeFw0w +NDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBD +QTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDGtWhfHZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs +49ohgHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sWv+bznkqH +7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ueMv5WJDmyVIRD9YTC2LxB +kMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d1 +9guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUw +AwEB/zBEBgNVHR8EPTA7MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29t +U2lnblNlY3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58ADsA +j8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkqhkiG9w0BAQUFAAOC +AQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7piL1DRYHjZiM/EoZNGeQFsOY3wo3a +BijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtCdsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtp +FhpFfTMDZflScZAmlaxMDPWLkz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP +51qJThRv4zdLhfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz +OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw== +-----END CERTIFICATE----- + +Cybertrust Global Root +====================== +-----BEGIN CERTIFICATE----- +MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYGA1UEChMPQ3li +ZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBSb290MB4XDTA2MTIxNTA4 +MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQD +ExZDeWJlcnRydXN0IEdsb2JhbCBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA ++Mi8vRRQZhP/8NN57CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW +0ozSJ8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2yHLtgwEZL +AfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iPt3sMpTjr3kfb1V05/Iin +89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNzFtApD0mpSPCzqrdsxacwOUBdrsTiXSZT +8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAYXSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2 +MDSgMqAwhi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3JsMB8G +A1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUAA4IBAQBW7wojoFRO +lZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMjWqd8BfP9IjsO0QbE2zZMcwSO5bAi +5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUxXOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2 +hO0j9n0Hq0V+09+zv+mKts2oomcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+T +X3EJIrduPuocA06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW +WL1WMRJOEcgh4LMRkWXbtKaIOM5V +-----END CERTIFICATE----- + +ePKI Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx +MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq +MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs +IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi +lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv +qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX +12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O +WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+ +ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao +lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/ +vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi +Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi +MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0 +1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq +KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV +xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP +NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r +GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE +xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx +gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy +sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD +BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +T\xc3\x9c\x42\xC4\xB0TAK UEKAE K\xC3\xB6k Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 - S\xC3\xBCr\xC3\xBCm 3 +============================================================================================================================= +-----BEGIN CERTIFICATE----- +MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRSMRgwFgYDVQQH +DA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJpbGltc2VsIHZlIFRla25vbG9q +aWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSwVEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ry +b25payB2ZSBLcmlwdG9sb2ppIEFyYcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNV +BAsMGkthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUg +S8O2ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAeFw0wNzA4 +MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIxGDAWBgNVBAcMD0dlYnpl +IC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmlsaW1zZWwgdmUgVGVrbm9sb2ppayBBcmHF +n3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBUQUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZl +IEtyaXB0b2xvamkgQXJhxZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2Ft +dSBTZXJ0aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7ZrIFNl +cnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4hgb46ezzb8R1Sf1n68yJMlaCQvEhO +Eav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yKO7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1 +xnnRFDDtG1hba+818qEhTsXOfJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR +6Oqeyjh1jmKwlZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL +hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQIDAQABo0IwQDAd +BgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmPNOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4 +N5EY3ATIZJkrGG2AA1nJrvhY0D7twyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLT +y9LQQfMmNkqblWwM7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYh +LBOhgLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5noN+J1q2M +dqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUsyZyQ2uypQjyttgI= +-----END CERTIFICATE----- + +Buypass Class 2 CA 1 +==================== +-----BEGIN CERTIFICATE----- +MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMiBDQSAxMB4XDTA2 +MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh +c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7M +cXA0ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLXl18xoS83 +0r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVBHfCuuCkslFJgNJQ72uA4 +0Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/R +uFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0P +AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLPgcIV +1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+DKhQ7SLHrQVMdvvt +7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKuBctN518fV4bVIJwo+28TOPX2EZL2 +fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHsh7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5w +wDX3OaJdZtB7WZ+oRxKaJyOkLY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho +-----END CERTIFICATE----- + +Buypass Class 3 CA 1 +==================== +-----BEGIN CERTIFICATE----- +MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMyBDQSAxMB4XDTA1 +MDUwOTE0MTMwM1oXDTE1MDUwOTE0MTMwM1owSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh +c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDMgQ0EgMTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAKSO13TZKWTeXx+HgJHqTjnmGcZEC4DVC69TB4sSveZn8AKx +ifZgisRbsELRwCGoy+Gb72RRtqfPFfV0gGgEkKBYouZ0plNTVUhjP5JW3SROjvi6K//zNIqeKNc0 +n6wv1g/xpC+9UrJJhW05NfBEMJNGJPO251P7vGGvqaMU+8IXF4Rs4HyI+MkcVyzwPX6UvCWThOia +AJpFBUJXgPROztmuOfbIUxAMZTpHe2DC1vqRycZxbL2RhzyRhkmr8w+gbCZ2Xhysm3HljbybIR6c +1jh+JIAVMYKWsUnTYjdbiAwKYjT+p0h+mbEwi5A3lRyoH6UsjfRVyNvdWQrCrXig9IsCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUOBTmyPCppAP0Tj4io1vy1uCtQHQwDgYDVR0P +AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQABZ6OMySU9E2NdFm/soT4JXJEVKirZgCFPBdy7 +pYmrEzMqnji3jG8CcmPHc3ceCQa6Oyh7pEfJYWsICCD8igWKH7y6xsL+z27sEzNxZy5p+qksP2bA +EllNC1QCkoS72xLvg3BweMhT+t/Gxv/ciC8HwEmdMldg0/L2mSlf56oBzKwzqBwKu5HEA6BvtjT5 +htOzdlSY9EqBs1OdTUDs5XcTRa9bqh/YL0yCe/4qxFi7T/ye/QNlGioOw6UgFpRreaaiErS7GqQj +el/wroQk5PMr+4okoyeYZdowdXb8GZHo2+ubPzK/QJcHJrrM85SFSnonk8+QQtS4Wxam58tAA915 +-----END CERTIFICATE----- + +EBG Elektronik Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 +========================================================================== +-----BEGIN CERTIFICATE----- +MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNVBAMML0VCRyBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMTcwNQYDVQQKDC5FQkcg +QmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXptZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAe +Fw0wNjA4MTcwMDIxMDlaFw0xNjA4MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25p +ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2lt +IFRla25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h4fuXd7hxlugTlkaDT7by +X3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAktiHq6yOU/im/+4mRDGSaBUorzAzu8T2b +gmmkTPiab+ci2hC6X5L8GCcKqKpE+i4stPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfr +eYteIAbTdgtsApWjluTLdlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZ +TqNGFav4c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8UmTDGy +Y5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z+kI2sSXFCjEmN1Zn +uqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0OLna9XvNRiYuoP1Vzv9s6xiQFlpJI +qkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMWOeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vm +ExH8nYQKE3vwO9D8owrXieqWfo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0 +Nokb+Clsi7n2l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgwFoAU587GT/wW +Z5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+8ygjdsZs93/mQJ7ANtyVDR2t +FcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgm +zJNSroIBk5DKd8pNSe/iWtkqvTDOTLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64k +XPBfrAowzIpAoHMEwfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqT +bCmYIai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJnxk1Gj7sU +RT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4QDgZxGhBM/nV+/x5XOULK +1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9qKd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt +2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11thie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQ +Y9iJSrSq3RZj9W6+YKH47ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9 +AahH3eU7QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT +-----END CERTIFICATE----- + +certSIGN ROOT CA +================ +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD +VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa +Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE +CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I +JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH +rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2 +ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD +0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943 +AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B +Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB +AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8 +SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0 +x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt +vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz +TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +CNNIC ROOT +========== +-----BEGIN CERTIFICATE----- +MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJDTjEOMAwGA1UE +ChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2MDcwOTE0WhcNMjcwNDE2MDcw +OTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1Qw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzD +o+/hn7E7SIX1mlwhIhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tiz +VHa6dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZOV/kbZKKT +VrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrCGHn2emU1z5DrvTOTn1Or +czvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gNv7Sg2Ca+I19zN38m5pIEo3/PIKe38zrK +y5nLAgMBAAGjczBxMBEGCWCGSAGG+EIBAQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscC +wQ7vptU7ETAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991S +lgrHAsEO76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnKOOK5 +Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvHugDnuL8BV8F3RTIM +O/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7HgviyJA/qIYM/PmLXoXLT1tLYhFHxUV8 +BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fLbuXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2 +G8kS1sHNzYDzAgE8yGnLRUhj2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5m +mxE= +-----END CERTIFICATE----- + +ApplicationCA - Japanese Government +=================================== +-----BEGIN CERTIFICATE----- +MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEcMBoGA1UEChMT +SmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRpb25DQTAeFw0wNzEyMTIxNTAw +MDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYTAkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zl +cm5tZW50MRYwFAYDVQQLEw1BcHBsaWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAp23gdE6Hj6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4 +fl+Kf5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55IrmTwcrN +wVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cwFO5cjFW6WY2H/CPek9AE +jP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDihtQWEjdnjDuGWk81quzMKq2edY3rZ+nYVu +nyoKb58DKTCXKB28t89UKU5RMfkntigm/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRU +WssmP3HMlEYNllPqa0jQk/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNV +BAYTAkpQMRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOCseOD +vOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADlqRHZ3ODrs +o2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJhyzjVOGjprIIC8CFqMjSnHH2HZ9g +/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYD +io+nEhEMy/0/ecGc/WLuo89UDNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmW +dupwX3kSa+SjB1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL +rosot4LKGAfmt1t06SAZf7IbiVQ= +-----END CERTIFICATE----- + +GeoTrust Primary Certification Authority - G3 +============================================= +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UE +BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA4IEdlb1RydXN0 +IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIz +NTk1OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAo +YykgMjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMT +LUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5j +K/BGvESyiaHAKAxJcCGVn2TAppMSAmUmhsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdE +c5IiaacDiGydY8hS2pgn5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3C +IShwiP/WJmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exALDmKu +dlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZChuOl1UcCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMR5yo6hTgMdHNxr +2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IBAQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9 +cr5HqQ6XErhK8WTTOd8lNNTBzU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbE +Ap7aDHdlDkQNkv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD +AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUHSJsMC8tJP33s +t/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2Gspki4cErx5z481+oghLrGREt +-----END CERTIFICATE----- + +thawte Primary Root CA - G2 +=========================== +-----BEGIN CERTIFICATE----- +MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDELMAkGA1UEBhMC +VVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMpIDIwMDcgdGhhd3RlLCBJbmMu +IC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3Qg +Q0EgLSBHMjAeFw0wNzExMDUwMDAwMDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEV +MBMGA1UEChMMdGhhd3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBG +b3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAt +IEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/BebfowJPDQfGAFG6DAJS +LSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6papu+7qzcMBniKI11KOasf2twu8x+qi5 +8/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU +mtgAMADna3+FGO6Lts6KDPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUN +G4k8VIZ3KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41oxXZ3K +rr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== +-----END CERTIFICATE----- + +thawte Primary Root CA - G3 +=========================== +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCBrjELMAkGA1UE +BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2 +aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0w +ODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh +d3RlLCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9uMTgwNgYD +VQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIG +A1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAsr8nLPvb2FvdeHsbnndmgcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2At +P0LMqmsywCPLLEHd5N/8YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC ++BsUa0Lfb1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS99irY +7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2SzhkGcuYMXDhpxwTW +vGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUkOQIDAQABo0IwQDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJ +KoZIhvcNAQELBQADggEBABpA2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweK +A3rD6z8KLFIWoCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu +t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7cKUGRIjxpp7sC +8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fMm7v/OeZWYdMKp8RcTGB7BXcm +er/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZuMdRAGmI0Nj81Aa6sY6A= +-----END CERTIFICATE----- + +GeoTrust Primary Certification Authority - G2 +============================================= +-----BEGIN CERTIFICATE----- +MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA3IEdlb1RydXN0IElu +Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1 +OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg +MjAwNyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMTLUdl +b1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjB2MBAGByqGSM49AgEG +BSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcLSo17VDs6bl8VAsBQps8lL33KSLjHUGMc +KiEIfJo22Av+0SbFWDEwKCXzXV2juLaltJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+ +EVXVMAoGCCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGTqQ7m +ndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBuczrD6ogRLQy7rQkgu2 +npaqBA+K +-----END CERTIFICATE----- + +VeriSign Universal Root Certification Authority +=============================================== +-----BEGIN CERTIFICATE----- +MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCBvTELMAkGA1UE +BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO +ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk +IHVzZSBvbmx5MTgwNgYDVQQDEy9WZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTAeFw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv +cmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj +1mCOkdeQmIN65lgZOIzF9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGP +MiJhgsWHH26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+HLL72 +9fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN/BMReYTtXlT2NJ8I +AfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPTrJ9VAMf2CGqUuV/c4DPxhGD5WycR +tPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0G +CCsGAQUFBwEMBGEwX6FdoFswWTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2O +a8PPgGrUSBgsexkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud +DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4sAPmLGd75JR3 +Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+seQxIcaBlVZaDrHC1LGmWazx +Y8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTx +P/jgdFcrGJ2BtMQo2pSXpXDrrB2+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+P +wGZsY6rp2aQW9IHRlRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4 +mJO37M2CYfE45k+XmCpajQ== +-----END CERTIFICATE----- + +VeriSign Class 3 Public Primary Certification Authority - G4 +============================================================ +-----BEGIN CERTIFICATE----- +MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjELMAkGA1UEBhMC +VVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3 +b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVz +ZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBU +cnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRo +b3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5 +IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8 +Utpkmw4tXNherJI9/gHmGUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGz +rl0Bp3vefLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEw +HzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24u +Y29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMWkf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMD +A2gAMGUCMGYhDBgmYFo4e1ZC4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIx +AJw9SDkjOVgaFRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== +-----END CERTIFICATE----- + +NetLock Arany (Class Gold) Főtanúsítvány +============================================ +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G +A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610 +dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB +cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx +MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO +ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6 +c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu +0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw +/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk +H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw +fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1 +neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW +qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta +YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna +NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu +dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +Staat der Nederlanden Root CA - G2 +================================== +-----BEGIN CERTIFICATE----- +MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE +CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g +Um9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oXDTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMC +TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l +ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ +5291qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8SpuOUfiUtn +vWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPUZ5uW6M7XxgpT0GtJlvOj +CwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvEpMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiil +e7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCR +OME4HYYEhLoaJXhena/MUGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpI +CT0ugpTNGmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy5V65 +48r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv6q012iDTiIJh8BIi +trzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEKeN5KzlW/HdXZt1bv8Hb/C3m1r737 +qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMB +AAGjgZcwgZQwDwYDVR0TAQH/BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcC +ARYxaHR0cDovL3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqGSIb3DQEBCwUA +A4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLySCZa59sCrI2AGeYwRTlHSeYAz ++51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwj +f/ST7ZwaUb7dRUG/kSS0H4zpX897IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaN +kqbG9AclVMwWVxJKgnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfk +CpYL+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxLvJxxcypF +URmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkmbEgeqmiSBeGCc1qb3Adb +CG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvkN1trSt8sV4pAWja63XVECDdCcAz+3F4h +oKOKwJCcaNpQ5kUQR3i2TtJlycM33+FCY7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoV +IPVVYpbtbZNQvOSqeK3Zywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm +66+KAQ== +-----END CERTIFICATE----- + +CA Disig +======== +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMK +QnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwHhcNMDYw +MzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlz +bGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgm +GErENx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnXmjxUizkD +Pw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYDXcDtab86wYqg6I7ZuUUo +hwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhWS8+2rT+MitcE5eN4TPWGqvWP+j1scaMt +ymfraHtuM6kMgiioTGohQBUgDCZbg8KpFhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8w +gfwwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0P +AQH/BAQDAgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cuZGlz +aWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5zay9jYS9jcmwvY2Ff +ZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2svY2EvY3JsL2NhX2Rpc2lnLmNybDAa +BgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEwDQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59t +WDYcPQuBDRIrRhCA/ec8J9B6yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3 +mkkp7M5+cTxqEEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/ +CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeBEicTXxChds6K +ezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFNPGO+I++MzVpQuGhU+QqZMxEA +4Z7CRneC9VkGjCFMhwnN5ag= +-----END CERTIFICATE----- + +Juur-SK +======= +-----BEGIN CERTIFICATE----- +MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcNAQkBFglwa2lA +c2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMRAw +DgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMwMVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqG +SIb3DQEJARYJcGtpQHNrLmVlMQswCQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVy +aW1pc2tlc2t1czEQMA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOBSvZiF3tf +TQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkzABpTpyHhOEvWgxutr2TC ++Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvHLCu3GFH+4Hv2qEivbDtPL+/40UceJlfw +UR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMPPbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDa +Tpxt4brNj3pssAki14sL2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQF +MAMBAf8wggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwICMIHD +HoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDkAGwAagBhAHMAdABh +AHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0AHMAZQBlAHIAaQBtAGkAcwBrAGUA +cwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABzAGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABr +AGkAbgBuAGkAdABhAG0AaQBzAGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nw +cy8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE +FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcYP2/v6X2+MA4G +A1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOiCfP+JmeaUOTDBS8rNXiRTHyo +ERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+gkcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyL +abVAyJRld/JXIWY7zoVAtjNjGr95HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678 +IIbsSt4beDI3poHSna9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkh +Mp6qqIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0ZTbvGRNs2 +yyqcjg== +-----END CERTIFICATE----- + +Hongkong Post Root CA 1 +======================= +-----BEGIN CERTIFICATE----- +MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoT +DUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMB4XDTAzMDUx +NTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25n +IFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1 +ApzQjVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEnPzlTCeqr +auh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjhZY4bXSNmO7ilMlHIhqqh +qZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9nnV0ttgCXjqQesBCNnLsak3c78QA3xMY +V18meMjWCnl3v/evt3a5pQuEF10Q6m/hq5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNV +HRMBAf8ECDAGAQH/AgEDMA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7i +h9legYsCmEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI37pio +l7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clBoiMBdDhViw+5Lmei +IAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJsEhTkYY2sEJCehFC78JZvRZ+K88ps +T/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpOfMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilT +c4afU9hDDl3WY4JxHYB0yvbiAmvZWg== +-----END CERTIFICATE----- + +SecureSign RootCA11 +=================== +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi +SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS +b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw +KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1 +cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL +TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO +wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq +g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP +O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA +bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX +t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh +OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r +bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ +Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01 +y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061 +lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I= +-----END CERTIFICATE----- + +ACEDICOM Root +============= +-----BEGIN CERTIFICATE----- +MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UEAwwNQUNFRElD +T00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMB4XDTA4 +MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEWMBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoG +A1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHk +WLn709gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7XBZXehuD +YAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5PGrjm6gSSrj0RuVFCPYew +MYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAKt0SdE3QrwqXrIhWYENiLxQSfHY9g5QYb +m8+5eaA9oiM/Qj9r+hwDezCNzmzAv+YbX79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbk +HQl/Sog4P75n/TSW9R28MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTT +xKJxqvQUfecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI2Sf2 +3EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyHK9caUPgn6C9D4zq9 +2Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEaeZAwUswdbxcJzbPEHXEUkFDWug/Fq +TYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz +4SsrSbbXc6GqlPUB53NlTKxQMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU +9QHnc2VMrFAwRAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv +bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWImfQwng4/F9tqg +aHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3gvoFNTPhNahXwOf9jU8/kzJP +eGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKeI6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1Pwk +zQSulgUV1qzOMPPKC8W64iLgpq0i5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1 +ThCojz2GuHURwCRiipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oI +KiMnMCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZo5NjEFIq +nxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6zqylfDJKZ0DcMDQj3dcE +I2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacNGHk0vFQYXlPKNFHtRQrmjseCNj6nOGOp +MCwXEGCSn1WHElkQwg9naRHMTh5+Spqtr0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3o +tkYNbn5XOmeUwssfnHdKZ05phkOTOPu220+DkdRgfks+KzgHVZhepA== +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority +======================================================= +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkGA1UEBhMCVVMx +FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5 +IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVow +XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz +IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94 +f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol +hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABByUqkFFBky +CEHwxWsKzH4PIRnN5GfcX6kb5sroc50i2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWX +bj9T/UWZYB2oK0z5XqcJ2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/ +D/xwzoiQ +-----END CERTIFICATE----- + +Microsec e-Szigno Root CA 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER +MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv +c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE +BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt +U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA +fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG +0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA +pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm +1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC +AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf +QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE +FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o +lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX +I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02 +yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi +LXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +E-Guven Kok Elektronik Sertifika Hizmet Saglayicisi +=================================================== +-----BEGIN CERTIFICATE----- +MIIDtjCCAp6gAwIBAgIQRJmNPMADJ72cdpW56tustTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG +EwJUUjEoMCYGA1UEChMfRWxla3Ryb25payBCaWxnaSBHdXZlbmxpZ2kgQS5TLjE8MDoGA1UEAxMz +ZS1HdXZlbiBLb2sgRWxla3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhZ2xheWljaXNpMB4XDTA3 +MDEwNDExMzI0OFoXDTE3MDEwNDExMzI0OFowdTELMAkGA1UEBhMCVFIxKDAmBgNVBAoTH0VsZWt0 +cm9uaWsgQmlsZ2kgR3V2ZW5saWdpIEEuUy4xPDA6BgNVBAMTM2UtR3V2ZW4gS29rIEVsZWt0cm9u +aWsgU2VydGlmaWthIEhpem1ldCBTYWdsYXlpY2lzaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAMMSIJ6wXgBljU5Gu4Bc6SwGl9XzcslwuedLZYDBS75+PNdUMZTe1RK6UxYC6lhj71vY +8+0qGqpxSKPcEC1fX+tcS5yWCEIlKBHMilpiAVDV6wlTL/jDj/6z/P2douNffb7tC+Bg62nsM+3Y +jfsSSYMAyYuXjDtzKjKzEve5TfL0TW3H5tYmNwjy2f1rXKPlSFxYvEK+A1qBuhw1DADT9SN+cTAI +JjjcJRFHLfO6IxClv7wC90Nex/6wN1CZew+TzuZDLMN+DfIcQ2Zgy2ExR4ejT669VmxMvLz4Bcpk +9Ok0oSy1c+HCPujIyTQlCFzz7abHlJ+tiEMl1+E5YP6sOVkCAwEAAaNCMEAwDgYDVR0PAQH/BAQD +AgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJ/uRLOU1fqRTy7ZVZoEVtstxNulMA0GCSqG +SIb3DQEBBQUAA4IBAQB/X7lTW2M9dTLn+sR0GstG30ZpHFLPqk/CaOv/gKlR6D1id4k9CnU58W5d +F4dvaAXBlGzZXd/aslnLpRCKysw5zZ/rTt5S/wzw9JKp8mxTq5vSR6AfdPebmvEvFZ96ZDAYBzwq +D2fK/A+JYZ1lpTzlvBNbCNvj/+27BrtqBrF6T2XGgv0enIu1De5Iu7i9qgi0+6N8y5/NkHZchpZ4 +Vwpm+Vganf2XKWDeEaaQHBkc7gGWIjQ0LpH5t8Qn0Xvmv/uARFoW5evg1Ao4vOSR49XrXMGs3xtq +fJ7lddK2l4fbzIcrQzqECK+rPNv3PGYxhrCdU3nt+CPeQuMtgvEP5fqX +-----END CERTIFICATE----- + +GlobalSign Root CA - R3 +======================= +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv +YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh +bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT +aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln +bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt +iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ +0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3 +rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl +OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2 +xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7 +lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8 +EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E +bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18 +YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r +kpeDMdmztcpHWD9f +-----END CERTIFICATE----- + +Autoridad de Certificacion Firmaprofesional CIF A62634068 +========================================================= +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA +BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 +MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw +QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB +NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD +Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P +B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY +7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH +ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI +plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX +MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX +LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK +bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU +vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1Ud +EwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH +DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp +cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA +bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx +ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx +51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk +R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP +T481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f +Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl +osS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR +crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR +saS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD +KCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi +6Et8Vcad+qMUu2WFbm5PEn4KPJ2V +-----END CERTIFICATE----- + +Izenpe.com +========== +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG +EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz +MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu +QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ +03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK +ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU ++zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC +PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT +OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK +F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK +0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+ +0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB +leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID +AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+ +SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG +NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l +Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga +kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q +hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs +g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5 +aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5 +nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC +ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo +Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z +WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- + +Chambers of Commerce Root - 2008 +================================ +-----BEGIN CERTIFICATE----- +MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYDVQQGEwJFVTFD +MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv +bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu +QS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEy +Mjk1MFoXDTM4MDczMTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNl +ZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQF +EwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJl +cnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW928sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKA +XuFixrYp4YFs8r/lfTJqVKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorj +h40G072QDuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR5gN/ +ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfLZEFHcpOrUMPrCXZk +NNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05aSd+pZgvMPMZ4fKecHePOjlO+Bd5g +D2vlGts/4+EhySnB8esHnFIbAURRPHsl18TlUlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331 +lubKgdaX8ZSD6e2wsWsSaR6s+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ +0wlf2eOKNcx5Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj +ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAxhduub+84Mxh2 +EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNVHQ4EFgQU+SSsD7K1+HnA+mCI +G8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJ +BgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNh +bWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENh +bWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDiC +CQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUH +AgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAJASryI1 +wqM58C7e6bXpeHxIvj99RZJe6dqxGfwWPJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH +3qLPaYRgM+gQDROpI9CF5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbU +RWpGqOt1glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaHFoI6 +M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2pSB7+R5KBWIBpih1 +YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MDxvbxrN8y8NmBGuScvfaAFPDRLLmF +9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QGtjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcK +zBIKinmwPQN/aUv0NCB9szTqjktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvG +nrDQWzilm1DefhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg +OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZd0jQ +-----END CERTIFICATE----- + +Global Chambersign Root - 2008 +============================== +-----BEGIN CERTIFICATE----- +MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYDVQQGEwJFVTFD +MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv +bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu +QS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMx +NDBaFw0zODA3MzExMjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUg +Y3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJ +QTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD +aGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDf +VtPkOpt2RbQT2//BthmLN0EYlVJH6xedKYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXf +XjaOcNFccUMd2drvXNL7G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0 +ZJJ0YPP2zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4ddPB +/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyGHoiMvvKRhI9lNNgA +TH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2Id3UwD2ln58fQ1DJu7xsepeY7s2M +H/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3VyJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfe +Ox2YItaswTXbo6Al/3K1dh3ebeksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSF +HTynyQbehP9r6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh +wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsogzCtLkykPAgMB +AAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQWBBS5CcqcHtvTbDprru1U8VuT +BjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDprru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UE +BhMCRVUxQzBBBgNVBAcTOk1hZHJpZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJm +aXJtYS5jb20vYWRkcmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJm +aXJtYSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiCCQDJzdPp +1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0 +dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAICIf3DekijZBZRG +/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZUohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6 +ReAJ3spED8IXDneRRXozX1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/s +dZ7LoR/xfxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVza2Mg +9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yydYhz2rXzdpjEetrHH +foUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMdSqlapskD7+3056huirRXhOukP9Du +qqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9OAP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETr +P3iZ8ntxPjzxmKfFGBI/5rsoM0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVq +c5iJWzouE4gev8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z +09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B +-----END CERTIFICATE----- + +Go Daddy Root Certificate Authority - G2 +======================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu +MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G +A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq +9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD ++qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd +fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl +NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9 +BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac +vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r +5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV +N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1 +-----END CERTIFICATE----- + +Starfield Root Certificate Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0 +eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw +DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg +VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB +dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv +W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs +bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk +N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf +ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU +JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol +TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx +4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw +F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ +c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +Starfield Services Root Certificate Authority - G2 +================================================== +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl +IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT +dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2 +h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa +hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP +LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB +rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG +SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP +E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy +xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza +YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6 +-----END CERTIFICATE----- + +AffirmTrust Commercial +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw +MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb +DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV +C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6 +BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww +MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV +HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG +hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi +qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv +0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh +sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +AffirmTrust Networking +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw +MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE +Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI +dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24 +/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb +h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV +HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu +UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6 +12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23 +WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9 +/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +AffirmTrust Premium +=================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy +OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy +dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn +BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV +5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs ++7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd +GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R +p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI +S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04 +6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5 +/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo ++Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv +MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC +6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S +L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK ++4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV +BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg +IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60 +g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb +zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw== +-----END CERTIFICATE----- + +AffirmTrust Premium ECC +======================= +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV +BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx +MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U +cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ +N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW +BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK +BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X +57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM +eQ== +-----END CERTIFICATE----- + +Certum Trusted Network CA +========================= +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK +ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy +MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU +ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC +l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J +J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4 +fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0 +cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw +DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj +jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1 +mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj +Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +Certinomis - Autorité Racine +============================= +-----BEGIN CERTIFICATE----- +MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjETMBEGA1UEChMK +Q2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAkBgNVBAMMHUNlcnRpbm9taXMg +LSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkG +A1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYw +JAYDVQQDDB1DZXJ0aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jYF1AMnmHa +wE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N8y4oH3DfVS9O7cdxbwly +Lu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWerP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw +2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92N +jMD2AR5vpTESOH2VwnHu7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9q +c1pkIuVC28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6lSTC +lrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1Enn1So2+WLhl+HPNb +xxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB0iSVL1N6aaLwD4ZFjliCK0wi1F6g +530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql095gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna +4NH4+ej9Uji29YnfAgMBAAGjWzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBQNjLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ +KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9sov3/4gbIOZ/x +WqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZMOH8oMDX/nyNTt7buFHAAQCva +R6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40 +nJ+U8/aGH88bc62UeYdocMMzpXDn2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1B +CxMjidPJC+iKunqjo3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjv +JL1vnxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG5ERQL1TE +qkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWqpdEdnV1j6CTmNhTih60b +WfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZbdsLLO7XSAPCjDuGtbkD326C00EauFddE +wk01+dIL8hf2rGbVJLJP0RyZwG71fet0BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/ +vgt2Fl43N+bYdJeimUV5 +-----END CERTIFICATE----- + +Root CA Generalitat Valenciana +============================== +-----BEGIN CERTIFICATE----- +MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJFUzEfMB0GA1UE +ChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290 +IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcNMDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3 +WjBoMQswCQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UE +CxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+WmmmO3I2 +F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKjSgbwJ/BXufjpTjJ3Cj9B +ZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGlu6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQ +D0EbtFpKd71ng+CT516nDOeB0/RSrFOyA8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXte +JajCq+TA81yc477OMUxkHl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMB +AAGjggM7MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBraS5n +dmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIICIwYKKwYBBAG/VQIB +ADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBl +AHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIAYQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIA +YQBsAGkAdABhAHQAIABWAGEAbABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQBy +AGEAYwBpAPMAbgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA +aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMAaQBvAG4AYQBt +AGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQAZQAgAEEAdQB0AG8AcgBpAGQA +YQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBu +AHQAcgBhACAAZQBuACAAbABhACAAZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAA +OgAvAC8AdwB3AHcALgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0 +dHA6Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+yeAT8MIGV +BgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQswCQYDVQQGEwJFUzEfMB0G +A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5S +b290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRh +TvW1yEICKrNcda3FbcrnlD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdz +Ckj+IHLtb8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg9J63 +NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XFducTZnV+ZfsBn5OH +iJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmCIoaZM3Fa6hlXPZHNqcCjbgcTpsnt ++GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM= +-----END CERTIFICATE----- + +A-Trust-nQual-03 +================ +-----BEGIN CERTIFICATE----- +MIIDzzCCAregAwIBAgIDAWweMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJBVDFIMEYGA1UE +Cgw/QS1UcnVzdCBHZXMuIGYuIFNpY2hlcmhlaXRzc3lzdGVtZSBpbSBlbGVrdHIuIERhdGVudmVy +a2VociBHbWJIMRkwFwYDVQQLDBBBLVRydXN0LW5RdWFsLTAzMRkwFwYDVQQDDBBBLVRydXN0LW5R +dWFsLTAzMB4XDTA1MDgxNzIyMDAwMFoXDTE1MDgxNzIyMDAwMFowgY0xCzAJBgNVBAYTAkFUMUgw +RgYDVQQKDD9BLVRydXN0IEdlcy4gZi4gU2ljaGVyaGVpdHNzeXN0ZW1lIGltIGVsZWt0ci4gRGF0 +ZW52ZXJrZWhyIEdtYkgxGTAXBgNVBAsMEEEtVHJ1c3QtblF1YWwtMDMxGTAXBgNVBAMMEEEtVHJ1 +c3QtblF1YWwtMDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtPWFuA/OQO8BBC4SA +zewqo51ru27CQoT3URThoKgtUaNR8t4j8DRE/5TrzAUjlUC5B3ilJfYKvUWG6Nm9wASOhURh73+n +yfrBJcyFLGM/BWBzSQXgYHiVEEvc+RFZznF/QJuKqiTfC0Li21a8StKlDJu3Qz7dg9MmEALP6iPE +SU7l0+m0iKsMrmKS1GWH2WrX9IWf5DMiJaXlyDO6w8dB3F/GaswADm0yqLaHNgBid5seHzTLkDx4 +iHQF63n1k3Flyp3HaxgtPVxO59X4PzF9j4fsCiIvI+n+u33J4PTs63zEsMMtYrWacdaxaujs2e3V +cuy+VwHOBVWf3tFgiBCzAgMBAAGjNjA0MA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0OBAoECERqlWdV +eRFPMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAVdRU0VlIXLOThaq/Yy/kgM40 +ozRiPvbY7meIMQQDbwvUB/tOdQ/TLtPAF8fGKOwGDREkDg6lXb+MshOWcdzUzg4NCmgybLlBMRmr +sQd7TZjTXLDR8KdCoLXEjq/+8T/0709GAHbrAvv5ndJAlseIOrifEXnzgGWovR/TeIGgUUw3tKZd +JXDRZslo+S4RFGjxVJgIrCaSD96JntT6s3kr0qN51OyLrIdTaEJMUVF0HhsnLuP1Hyl0Te2v9+GS +mYHovjrHF1D2t8b8m7CKa9aIA5GPBnc6hQLdmNVDeD/GMBWsm2vLV7eJUYs66MmEDNuxUCAKGkq6 +ahq97BvIxYSazQ== +-----END CERTIFICATE----- + +TWCA Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ +VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG +EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB +IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx +QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC +oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP +4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r +y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG +9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC +mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW +QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY +T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny +Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +Security Communication RootCA2 +============================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh +dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC +SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy +aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++ ++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R +3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV +spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K +EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8 +QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB +CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj +u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk +3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q +tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29 +mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +EC-ACC +====== +-----BEGIN CERTIFICATE----- +MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB8zELMAkGA1UE +BhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2VydGlmaWNhY2lvIChOSUYgUS0w +ODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYD +VQQLEyxWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UE +CxMsSmVyYXJxdWlhIEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMT +BkVDLUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQGEwJFUzE7 +MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYt +SSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZl +Z2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJh +cnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R85iK +w5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm4CgPukLjbo73FCeT +ae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaVHMf5NLWUhdWZXqBIoH7nF2W4onW4 +HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNdQlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0a +E9jD2z3Il3rucO2n5nzbcc8tlGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw +0JDnJwIDAQABo4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4opvpXY0wfwYD +VR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBodHRwczovL3d3dy5jYXRjZXJ0 +Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5l +dC92ZXJhcnJlbCAwDQYJKoZIhvcNAQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJ +lF7W2u++AVtd0x7Y/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNa +Al6kSBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhyRp/7SNVe +l+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOSAgu+TGbrIP65y7WZf+a2 +E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xlnJ2lYJU6Un/10asIbvPuW/mIPX64b24D +5EI= +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions RootCA 2011 +======================================================= +-----BEGIN CERTIFICATE----- +MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1IxRDBCBgNVBAoT +O0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9y +aXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z +IFJvb3RDQSAyMDExMB4XDTExMTIwNjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYT +AkdSMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z +IENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNo +IEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPzdYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI +1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJfel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa +71HFK9+WXesyHgLacEnsbgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u +8yBRQlqD75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSPFEDH +3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNVHRMBAf8EBTADAQH/ +MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp5dgTBCPuQSUwRwYDVR0eBEAwPqA8 +MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQub3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQu +b3JnMA0GCSqGSIb3DQEBBQUAA4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVt +XdMiKahsog2p6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 +TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7dIsXRSZMFpGD +/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8AcysNnq/onN694/BtZqhFLKPM58N +7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXIl7WdmplNsDz4SgCbZN2fOUvRJ9e4 +-----END CERTIFICATE----- + +Actalis Authentication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM +BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE +AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky +MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz +IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ +wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa +by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6 +zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f +YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2 +oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l +EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7 +hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8 +EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5 +jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY +iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI +WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0 +JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx +K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+ +Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC +4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo +2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz +lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem +OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9 +vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +Trustis FPS Root CA +=================== +-----BEGIN CERTIFICATE----- +MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQG +EwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQLExNUcnVzdGlzIEZQUyBSb290 +IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTExMzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNV +BAoTD1RydXN0aXMgTGltaXRlZDEcMBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQ +RUN+AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihHiTHcDnlk +H5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjjvSkCqPoc4Vu5g6hBSLwa +cY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zt +o3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlBOrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEA +AaNTMFEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAd +BgNVHQ4EFgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01GX2c +GE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmWzaD+vkAMXBJV+JOC +yinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP41BIy+Q7DsdwyhEQsb8tGD+pmQQ9P +8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZEf1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHV +l/9D7S3B2l0pKoU/rGXuhg8FjZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYl +iB6XzCGcKQENZetX2fNXlrtIzYE= +-----END CERTIFICATE----- + +StartCom Certification Authority +================================ +-----BEGIN CERTIFICATE----- +MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN +U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu +ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0 +NjM3WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk +LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg +U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y +o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/ +Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d +eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt +2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z +6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ +osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/ +untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc +UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT +37uMdBNSSwIDAQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFulF2mHMMo0aEPQ +Qa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCCATgwLgYIKwYBBQUHAgEWImh0 +dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cu +c3RhcnRzc2wuY29tL2ludGVybWVkaWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENv +bW1lcmNpYWwgKFN0YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0 +aGUgc2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93d3cuc3RhcnRzc2wuY29t +L3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBG +cmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5 +fPGFf59Jb2vKXfuM/gTFwWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWm +N3PH/UvSTa0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst0OcN +Org+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNcpRJvkrKTlMeIFw6T +tn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKlCcWw0bdT82AUuoVpaiF8H3VhFyAX +e2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVFP0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA +2MFrLH9ZXF2RsXAiV+uKa0hK1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBs +HvUwyKMQ5bLmKhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE +JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ8dCAWZvLMdib +D4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnmfyWl8kgAwKQB2j8= +-----END CERTIFICATE----- + +StartCom Certification Authority G2 +=================================== +-----BEGIN CERTIFICATE----- +MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMN +U3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +RzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UE +ChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8O +o1XJJZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsDvfOpL9HG +4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnooD/Uefyf3lLE3PbfHkffi +Aez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/Q0kGi4xDuFby2X8hQxfqp0iVAXV16iul +Q5XqFYSdCI0mblWbq9zSOdIxHWDirMxWRST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbs +O+wmETRIjfaAKxojAuuKHDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8H +vKTlXcxNnw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM0D4L +nMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/iUUjXuG+v+E5+M5iS +FGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9Ha90OrInwMEePnWjFqmveiJdnxMa +z6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHgTuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJ +KoZIhvcNAQELBQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K +2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfXUfEpY9Z1zRbk +J4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl6/2o1PXWT6RbdejF0mCy2wl+ +JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG +/+gyRr61M3Z3qAFdlsHB1b6uJcDJHgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTc +nIhT76IxW1hPkWLIwpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/Xld +blhYXzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5lIxKVCCIc +l85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoohdVddLHRDiBYmxOlsGOm +7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulrso8uBtjRkcfGEvRM/TAXw8HaOFvjqerm +obp573PYtlNXLfbQ4ddI +-----END CERTIFICATE----- + +Buypass Class 2 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X +DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1 +g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn +9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b +/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU +CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff +awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI +zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn +Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX +Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs +M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI +osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S +aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd +DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD +LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0 +oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC +wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS +CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN +rJgWVqA= +-----END CERTIFICATE----- + +Buypass Class 3 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X +DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH +sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR +5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh +7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ +ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH +2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV +/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ +RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA +Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq +j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G +uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG +Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8 +ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2 +KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz +6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug +UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe +eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi +Cp/HuZc= +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 3 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx +MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK +9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU +NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF +iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W +0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr +AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb +fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT +ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h +P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw== +-----END CERTIFICATE----- + +EE Certification Centre Root CA +=============================== +-----BEGIN CERTIFICATE----- +MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG +EwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEoMCYGA1UEAwwfRUUgQ2Vy +dGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIw +MTAxMDMwMTAxMDMwWhgPMjAzMDEyMTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlB +UyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRy +ZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUyeuuOF0+W2Ap7kaJjbMeM +TC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvObntl8jixwKIy72KyaOBhU8E2lf/slLo2 +rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIwWFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw +93X2PaRka9ZP585ArQ/dMtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtN +P2MbRMNE1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/zQas8fElyalL1BSZ +MEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEF +BQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEFBQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+Rj +xY6hUFaTlrg4wCQiZrxTFGGVv9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqM +lIpPnTX/dqQGE5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u +uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIWiAYLtqZLICjU +3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/vGVCJYMzpJJUPwssd8m92kMfM +dcGWxZ0= +-----END CERTIFICATE----- + +TURKTRUST Certificate Services Provider Root 2007 +================================================= +-----BEGIN CERTIFICATE----- +MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP +MA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg +QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgQXJhbMSxayAyMDA3MB4X +DTA3MTIyNTE4MzcxOVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMMNlTDnFJLVFJVU1QgRWxl +a3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTELMAkGA1UEBhMCVFIxDzAN +BgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDEsGxldGnFn2ltIHZlIEJp +bGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7Fni4gKGMpIEFyYWzEsWsgMjAwNzCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKu3PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9N +YvDdE3ePYakqtdTyuTFYKTsvP2qcb3N2Je40IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQv +KUmi8wUG+7RP2qFsmmaf8EMJyupyj+sA1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveGHtya +KhUG9qPw9ODHFNRRf8+0222vR5YXm3dx2KdxnSQM9pQ/hTEST7ruToK4uT6PIzdezKKqdfcYbwnT +rqdUKDT74eA7YH2gvnmJhsifLfkKS8RQouf9eRbHegsYz85M733WB2+Y8a+xwXrXgTW4qhe04MsC +AwEAAaNCMEAwHQYDVR0OBBYEFCnFkKslrxHkYb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/s +Px+EnWVUXKgWAkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+I +aE1KBiY3iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5mxRZNTZPz/OO +Xl0XrRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsaXRik7r4EW5nVcV9VZWRi1aKb +BFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZqxFdyKbjKlhqQgnDvZImZjINXQhVdP+MmNAK +poRq0Tl9 +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe +Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE +LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD +ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA +BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv +KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z +p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC +AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ +4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y +eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw +MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G +PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw +OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm +2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV +dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph +X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 EV 2009 +================================= +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS +egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh +zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T +7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60 +sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35 +11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv +cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v +ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El +MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp +b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh +c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+ +PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX +ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA +NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv +w9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +PSCProcert +========== +-----BEGIN CERTIFICATE----- +MIIJhjCCB26gAwIBAgIBCzANBgkqhkiG9w0BAQsFADCCAR4xPjA8BgNVBAMTNUF1dG9yaWRhZCBk +ZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9sYW5vMQswCQYDVQQGEwJWRTEQ +MA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlzdHJpdG8gQ2FwaXRhbDE2MDQGA1UEChMtU2lz +dGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMwQQYDVQQLEzpTdXBl +cmludGVuZGVuY2lhIGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMSUw +IwYJKoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4XDTEwMTIyODE2NTEwMFoXDTIw +MTIyNTIzNTk1OVowgdExJjAkBgkqhkiG9w0BCQEWF2NvbnRhY3RvQHByb2NlcnQubmV0LnZlMQ8w +DQYDVQQHEwZDaGFjYW8xEDAOBgNVBAgTB01pcmFuZGExKjAoBgNVBAsTIVByb3ZlZWRvciBkZSBD +ZXJ0aWZpY2Fkb3MgUFJPQ0VSVDE2MDQGA1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZp +Y2FjaW9uIEVsZWN0cm9uaWNhMQswCQYDVQQGEwJWRTETMBEGA1UEAxMKUFNDUHJvY2VydDCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANW39KOUM6FGqVVhSQ2oh3NekS1wwQYalNo97BVC +wfWMrmoX8Yqt/ICV6oNEolt6Vc5Pp6XVurgfoCfAUFM+jbnADrgV3NZs+J74BCXfgI8Qhd19L3uA +3VcAZCP4bsm+lU/hdezgfl6VzbHvvnpC2Mks0+saGiKLt38GieU89RLAu9MLmV+QfI4tL3czkkoh +RqipCKzx9hEC2ZUWno0vluYC3XXCFCpa1sl9JcLB/KpnheLsvtF8PPqv1W7/U0HU9TI4seJfxPmO +EO8GqQKJ/+MMbpfg353bIdD0PghpbNjU5Db4g7ayNo+c7zo3Fn2/omnXO1ty0K+qP1xmk6wKImG2 +0qCZyFSTXai20b1dCl53lKItwIKOvMoDKjSuc/HUtQy9vmebVOvh+qBa7Dh+PsHMosdEMXXqP+UH +0quhJZb25uSgXTcYOWEAM11G1ADEtMo88aKjPvM6/2kwLkDd9p+cJsmWN63nOaK/6mnbVSKVUyqU +td+tFjiBdWbjxywbk5yqjKPK2Ww8F22c3HxT4CAnQzb5EuE8XL1mv6JpIzi4mWCZDlZTOpx+FIyw +Bm/xhnaQr/2v/pDGj59/i5IjnOcVdo/Vi5QTcmn7K2FjiO/mpF7moxdqWEfLcU8UC17IAggmosvp +r2uKGcfLFFb14dq12fy/czja+eevbqQ34gcnAgMBAAGjggMXMIIDEzASBgNVHRMBAf8ECDAGAQH/ +AgEBMDcGA1UdEgQwMC6CD3N1c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAz +Ni0wMB0GA1UdDgQWBBRBDxk4qpl/Qguk1yeYVKIXTC1RVDCCAVAGA1UdIwSCAUcwggFDgBStuyId +xuDSAaj9dlBSk+2YwU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0b3JpZGFkIGRlIENlcnRp +ZmljYWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xhbm8xCzAJBgNVBAYTAlZFMRAwDgYDVQQH +EwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0cml0byBDYXBpdGFsMTYwNAYDVQQKEy1TaXN0ZW1hIE5h +Y2lvbmFsIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5k +ZW5jaWEgZGUgU2VydmljaW9zIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExJTAjBgkqhkiG +9w0BCQEWFmFjcmFpekBzdXNjZXJ0ZS5nb2IudmWCAQowDgYDVR0PAQH/BAQDAgEGME0GA1UdEQRG +MESCDnByb2NlcnQubmV0LnZloBUGBWCGXgIBoAwMClBTQy0wMDAwMDKgGwYFYIZeAgKgEgwQUklG +LUotMzE2MzUzNzMtNzB2BgNVHR8EbzBtMEagRKBChkBodHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52 +ZS9sY3IvQ0VSVElGSUNBRE8tUkFJWi1TSEEzODRDUkxERVIuY3JsMCOgIaAfhh1sZGFwOi8vYWNy +YWl6LnN1c2NlcnRlLmdvYi52ZTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9v +Y3NwLnN1c2NlcnRlLmdvYi52ZTBBBgNVHSAEOjA4MDYGBmCGXgMBAjAsMCoGCCsGAQUFBwIBFh5o +dHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9kcGMwDQYJKoZIhvcNAQELBQADggIBACtZ6yKZu4Sq +T96QxtGGcSOeSwORR3C7wJJg7ODU523G0+1ng3dS1fLld6c2suNUvtm7CpsR72H0xpkzmfWvADmN +g7+mvTV+LFwxNG9s2/NkAZiqlCxB3RWGymspThbASfzXg0gTB1GEMVKIu4YXx2sviiCtxQuPcD4q +uxtxj7mkoP3YldmvWb8lK5jpY5MvYB7Eqvh39YtsL+1+LrVPQA3uvFd359m21D+VJzog1eWuq2w1 +n8GhHVnchIHuTQfiSLaeS5UtQbHh6N5+LwUeaO6/u5BlOsju6rEYNxxik6SgMexxbJHmpHmJWhSn +FFAFTKQAVzAswbVhltw+HoSvOULP5dAssSS830DD7X9jSr3hTxJkhpXzsOfIt+FTvZLm8wyWuevo +5pLtp4EJFAv8lXrPj9Y0TzYS3F7RNHXGRoAvlQSMx4bEqCaJqD8Zm4G7UaRKhqsLEQ+xrmNTbSjq +3TNWOByyrYDT13K9mmyZY+gAu0F2BbdbmRiKw7gSXFbPVgx96OLP7bx0R/vu0xdOIk9W/1DzLuY5 +poLWccret9W6aAjtmcz9opLLabid+Qqkpj5PkygqYWwHJgD/ll9ohri4zspV4KuxPX+Y1zMOWj3Y +eMLEYC/HYvBhkdI4sPaeVdtAgAUSM84dkpvRabP/v/GSCmE1P93+hvS84Bpxs2Km +-----END CERTIFICATE----- + +China Internet Network Information Center EV Certificates Root +============================================================== +-----BEGIN CERTIFICATE----- +MIID9zCCAt+gAwIBAgIESJ8AATANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMCQ04xMjAwBgNV +BAoMKUNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyMUcwRQYDVQQDDD5D +aGluYSBJbnRlcm5ldCBOZXR3b3JrIEluZm9ybWF0aW9uIENlbnRlciBFViBDZXJ0aWZpY2F0ZXMg +Um9vdDAeFw0xMDA4MzEwNzExMjVaFw0zMDA4MzEwNzExMjVaMIGKMQswCQYDVQQGEwJDTjEyMDAG +A1UECgwpQ2hpbmEgSW50ZXJuZXQgTmV0d29yayBJbmZvcm1hdGlvbiBDZW50ZXIxRzBFBgNVBAMM +PkNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyIEVWIENlcnRpZmljYXRl +cyBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm35z7r07eKpkQ0H1UN+U8i6y +jUqORlTSIRLIOTJCBumD1Z9S7eVnAztUwYyZmczpwA//DdmEEbK40ctb3B75aDFk4Zv6dOtouSCV +98YPjUesWgbdYavi7NifFy2cyjw1l1VxzUOFsUcW9SxTgHbP0wBkvUCZ3czY28Sf1hNfQYOL+Q2H +klY0bBoQCxfVWhyXWIQ8hBouXJE0bhlffxdpxWXvayHG1VA6v2G5BY3vbzQ6sm8UY78WO5upKv23 +KzhmBsUs4qpnHkWnjQRmQvaPK++IIGmPMowUc9orhpFjIpryp9vOiYurXccUwVswah+xt54ugQEC +7c+WXmPbqOY4twIDAQABo2MwYTAfBgNVHSMEGDAWgBR8cks5x8DbYqVPm6oYNJKiyoOCWTAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUfHJLOcfA22KlT5uqGDSSosqD +glkwDQYJKoZIhvcNAQEFBQADggEBACrDx0M3j92tpLIM7twUbY8opJhJywyA6vPtI2Z1fcXTIWd5 +0XPFtQO3WKwMVC/GVhMPMdoG52U7HW8228gd+f2ABsqjPWYWqJ1MFn3AlUa1UeTiH9fqBk1jjZaM +7+czV0I664zBechNdn3e9rG3geCg+aF4RhcaVpjwTj2rHO3sOdwHSPdj/gauwqRcalsyiMXHM4Ws +ZkJHwlgkmeHlPuV1LI5D1l08eB6olYIpUNHRFrrvwb562bTYzB5MRuF3sTGrvSrIzo9uoV1/A3U0 +5K2JRVRevq4opbs/eHnrc7MKDf2+yfdWrPa37S+bISnHOLaVxATywy39FCqQmbkHzJ8= +-----END CERTIFICATE----- + +Swisscom Root CA 2 +================== +-----BEGIN CERTIFICATE----- +MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQG +EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy +dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMjAeFw0xMTA2MjQwODM4MTRaFw0zMTA2 +MjUwNzM4MTRaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln +aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAyMIIC +IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ6HJaI2nbeHCOFvErjw0DzpPM +LgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J2HzFZ++r0rk0X2s682Q2zsKwzxNo +ysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJ +wDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVPACu/obvLP+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpH +Wrumnf2U5NGKpV+GY3aFy6//SSj8gO1MedK75MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1a +SgJA/MTAtukxGggo5WDDH8SQjhBiYEQN7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL6yxS +NLCK/RJOlrDrcH+eOfdmQrGrrFLadkBXeyq96G4DsguAhYidDMfCd7Camlf0uPoTXGiTOmekl9Ab +mbeGMktg2M7v0Ax/lZ9vh0+Hio5fCHyqW/xavqGRn1V9TrALacywlKinh/LTSlDcX3KwFnUey7QY +Ypqwpzmqm59m2I2mbJYV4+by+PGDYmy7Velhk6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3 +qPyZ7iVNTA6z00yPhOgpD/0QVAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw +HQYDVR0hBBYwFDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0O +BBYEFE0mICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqhb97iEoHF8Twu +MA0GCSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4RfbgZPnm3qKhyN2abGu2sEzsO +v2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv/2BZf82Fo4s9SBwlAjxnffUy6S8w5X2lejjQ +82YqZh6NM4OKb3xuqFp1mrjX2lhIREeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX+Ave5XLz +o9v/tdhZsnPdTSpxsrpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4DooqzgB53MBfGWcs +a0vvaGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/Ps/8XciATwoCqISxx +OQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99nBjx8Oto0QuFmtEYE3saW +mA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5Wt6NlUe07qxS/TFED6F+KBZvuim6c779o ++sjaC+NCydAXFJy3SuCvkychVSa1ZC+N8f+mQAWFBVzKBxlcCxMoTFh/wqXvRdpg065lYZ1Tg3TC +rvJcwhbtkj6EPnNgiLx29CzP0H1907he0ZESEOnN3col49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX +5OfNeOI5wSsSnqaeG8XmDtkx2Q== +-----END CERTIFICATE----- + +Swisscom Root EV CA 2 +===================== +-----BEGIN CERTIFICATE----- +MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAwZzELMAkGA1UE +BhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdpdGFsIENlcnRpZmljYXRlIFNl +cnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290IEVWIENBIDIwHhcNMTEwNjI0MDk0NTA4WhcN +MzEwNjI1MDg0NTA4WjBnMQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsT +HERpZ2l0YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3aXNzY29tIFJvb3QgRVYg +Q0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3HS9X6lds93BdY7BxUglgRCgz +o3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaVylvNwSqD1ycfMQ4jFrclyxy0uYAy +Xhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPHoCE2G3pXKSinLr9xJZDzRINpUKTk4Rti +GZQJo/PDvO/0vezbE53PnUgJUmfANykRHvvSEaeFGHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8Li +qG12W0OfvrSdsyaGOx9/5fLoZigWJdBLlzin5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaH +Za0zKcQvidm5y8kDnftslFGXEBuGCxobP/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHLOdAG +alNgHa/2+2m8atwBz735j9m9W8E6X47aD0upm50qKGsaCnw8qyIL5XctcfaCNYGu+HuB5ur+rPQa +m3Rc6I8k9l2dRsQs0h4rIWqDJ2dVSqTjyDKXZpBy2uPUZC5f46Fq9mDU5zXNysRojddxyNMkM3Ox +bPlq4SjbX8Y96L5V5jcb7STZDxmPX2MYWFCBUWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDi +xzgHcgplwLa7JSnaFp6LNYth7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgED +MB0GA1UdDgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWBbj2ITY1x0kbB +bkUe88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6xXCX5145v9Ydkn+0UjrgEjihL +j6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98TPLr+flaYC/NUn81ETm484T4VvwYmneTwkLbU +wp4wLh/vx3rEUMfqe9pQy3omywC0Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/UOGED1V7 +XwgiG/W9mR4U9s70WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK4/HsGOV1timH +59yLGn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFhJpiTt3tm7JFe3VE/ +23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4Snr8PyQUQ3nqjsTzyP6Wq +J3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VNvBFuIXxZN5nQBrz5Bm0yFqXZaajh3DyA +HmBR3NdUIR7KYndP+tiPsys6DXhyyWhBWkdKwqPrGtcKqzwyVcgKEZzfdNbwQBUdyLmPtTbFr/gi +uMod89a2GQ+fYWVq6nTIfI/DT11lgh/ZDYnadXL77/FHZxOzyNEZiCcmmpl5fx7kLD977vHeTYuW +l8PVP3wbI+2ksx0WckNLIOFZfsLorSa/ovc= +-----END CERTIFICATE----- + +CA Disig Root R1 +================ +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNVBAYTAlNLMRMw +EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp +ZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQyMDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sx +EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp +c2lnIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy +3QRkD2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/oOI7bm+V8 +u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3AfQ+lekLZWnDZv6fXARz2 +m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJeIgpFy4QxTaz+29FHuvlglzmxZcfe+5nk +CiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8noc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTa +YVKvJrT1cU/J19IG32PK/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6 +vpmumwKjrckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD3AjL +LhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE7cderVC6xkGbrPAX +ZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkCyC2fg69naQanMVXVz0tv/wQFx1is +XxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLdqvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ +04IwDQYJKoZIhvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR +xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaASfX8MPWbTx9B +LxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXoHqJPYNcHKfyyo6SdbhWSVhlM +CrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpBemOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5Gfb +VSUZP/3oNn6z4eGBrxEWi1CXYBmCAMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85 +YmLLW1AL14FABZyb7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKS +ds+xDzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvkF7mGnjix +lAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqFa3qdnom2piiZk4hA9z7N +UaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsTQ6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJ +a7+h89n07eLw4+1knj0vllJPgFOL +-----END CERTIFICATE----- + +CA Disig Root R2 +================ +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw +EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp +ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx +EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp +c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC +w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia +xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7 +A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S +GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV +g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa +5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE +koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A +Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i +Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u +Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV +sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je +dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8 +1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx +mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01 +utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0 +sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg +UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV +7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +ACCVRAIZ1 +========= +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB +SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1 +MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH +UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM +jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0 +RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD +aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ +0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG +WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7 +8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR +5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J +9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK +Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw +Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu +Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM +Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA +QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh +AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA +YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj +AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA +IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk +aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0 +dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2 +MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI +hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E +R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN +YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49 +nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ +TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3 +sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg +Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd +3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p +EfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +TWCA Global Root CA +=================== +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT +CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD +QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK +EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C +nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV +r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR +Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV +tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W +KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99 +sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p +yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn +kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI +zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g +cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M +8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg +/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg +lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP +A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m +i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8 +EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3 +zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0= +-----END CERTIFICATE----- + +TeliaSonera Root CA v1 +====================== +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE +CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4 +MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW +VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+ +6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA +3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k +B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn +Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH +oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3 +F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ +oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7 +gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc +TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB +AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW +DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm +zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW +pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV +G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc +c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT +JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2 +qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6 +Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems +WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +E-Tugra Certification Authority +=============================== +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNVBAYTAlRSMQ8w +DQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamls +ZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN +ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMw +NTEyMDk0OFoXDTIzMDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmEx +QDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxl +cmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQD +DB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEA4vU/kwVRHoViVF56C/UYB4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vd +hQd2h8y/L5VMzH2nPbxHD5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5K +CKpbknSFQ9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEoq1+g +ElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3Dk14opz8n8Y4e0ypQ +BaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcHfC425lAcP9tDJMW/hkd5s3kc91r0 +E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsutdEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gz +rt48Ue7LE3wBf4QOXVGUnhMMti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAq +jqFGOjGY5RH8zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn +rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUXU8u3Zg5mTPj5 +dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6Jyr+zE7S6E5UMA8GA1UdEwEB +/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEG +MA0GCSqGSIb3DQEBCwUAA4ICAQAFNzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAK +kEh47U6YA5n+KGCRHTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jO +XKqYGwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c77NCR807 +VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3+GbHeJAAFS6LrVE1Uweo +a2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WKvJUawSg5TB9D0pH0clmKuVb8P7Sd2nCc +dlqMQ1DujjByTd//SffGqWfZbawCEeI6FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEV +KV0jq9BgoRJP3vQXzTLlyb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gT +Dx4JnW2PAJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpDy4Q0 +8ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8dNL/+I5c30jn6PQ0G +C7TbO6Orb1wdtn7os4I07QZcJA== +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 2 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx +MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ +SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F +vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970 +2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV +WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy +YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4 +r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf +vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR +3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg== +-----END CERTIFICATE----- + +Atos TrustedRoot 2011 +===================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU +cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4 +MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG +A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV +hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr +54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+ +DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320 +HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR +z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R +l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ +bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h +k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh +TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9 +61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G +3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +QuoVadis Root CA 1 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE +PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm +PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6 +Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN +ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l +g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV +7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX +9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f +iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg +t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI +hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3 +GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct +Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP ++V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh +3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa +wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6 +O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0 +FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV +hMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +QuoVadis Root CA 2 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh +ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY +NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t +oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o +MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l +V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo +L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ +sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD +6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh +lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI +hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K +pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9 +x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz +dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X +U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw +mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD +zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN +JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr +O3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +QuoVadis Root CA 3 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286 +IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL +Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe +6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3 +I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U +VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7 +5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi +Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM +dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt +rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI +hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS +t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ +TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du +DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib +Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD +hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX +0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW +dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2 +PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +DigiCert Assured ID Root G2 +=========================== +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw +MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH +35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq +bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw +VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP +YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn +lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO +w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv +0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz +d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW +hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M +jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +DigiCert Assured ID Root G3 +=========================== +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD +VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb +RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs +KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF +UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy +YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy +1vUhZscv6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +DigiCert Global Root G2 +======================= +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx +MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ +kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO +3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV +BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM +UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu +5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr +F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U +WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH +QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/ +iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +DigiCert Global Root G3 +======================= +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD +VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw +MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k +aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O +YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp +Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y +3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34 +VOKa5Vt8sycX +-----END CERTIFICATE----- + +DigiCert Trusted Root G4 +======================== +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw +HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp +pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o +k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa +vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY +QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6 +MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm +mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7 +f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH +dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8 +oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY +ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr +yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy +7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah +ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN +5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb +/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa +5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK +G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP +82Z+ +-----END CERTIFICATE----- + +WoSign +====== +-----BEGIN CERTIFICATE----- +MIIFdjCCA16gAwIBAgIQXmjWEXGUY1BWAGjzPsnFkTANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQG +EwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxKjAoBgNVBAMTIUNlcnRpZmljYXRpb24g +QXV0aG9yaXR5IG9mIFdvU2lnbjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgwMTAwMDFaMFUxCzAJ +BgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRlZDEqMCgGA1UEAxMhQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkgb2YgV29TaWduMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +vcqNrLiRFVaXe2tcesLea9mhsMMQI/qnobLMMfo+2aYpbxY94Gv4uEBf2zmoAHqLoE1UfcIiePyO +CbiohdfMlZdLdNiefvAA5A6JrkkoRBoQmTIPJYhTpA2zDxIIFgsDcSccf+Hb0v1naMQFXQoOXXDX +2JegvFNBmpGN9J42Znp+VsGQX+axaCA2pIwkLCxHC1l2ZjC1vt7tj/id07sBMOby8w7gLJKA84X5 +KIq0VC6a7fd2/BVoFutKbOsuEo/Uz/4Mx1wdC34FMr5esAkqQtXJTpCzWQ27en7N1QhatH/YHGkR ++ScPewavVIMYe+HdVHpRaG53/Ma/UkpmRqGyZxq7o093oL5d//xWC0Nyd5DKnvnyOfUNqfTq1+ez +EC8wQjchzDBwyYaYD8xYTYO7feUapTeNtqwylwA6Y3EkHp43xP901DfA4v6IRmAR3Qg/UDaruHqk +lWJqbrDKaiFaafPz+x1wOZXzp26mgYmhiMU7ccqjUu6Du/2gd/Tkb+dC221KmYo0SLwX3OSACCK2 +8jHAPwQ+658geda4BmRkAjHXqc1S+4RFaQkAKtxVi8QGRkvASh0JWzko/amrzgD5LkhLJuYwTKVY +yrREgk/nkR4zw7CT/xH8gdLKH3Ep3XZPkiWvHYG3Dy+MwwbMLyejSuQOmbp8HkUff6oZRZb9/D0C +AwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOFmzw7R +8bNLtwYgFP6HEtX2/vs+MA0GCSqGSIb3DQEBBQUAA4ICAQCoy3JAsnbBfnv8rWTjMnvMPLZdRtP1 +LOJwXcgu2AZ9mNELIaCJWSQBnfmvCX0KI4I01fx8cpm5o9dU9OpScA7F9dY74ToJMuYhOZO9sxXq +T2r09Ys/L3yNWC7F4TmgPsc9SnOeQHrAK2GpZ8nzJLmzbVUsWh2eJXLOC62qx1ViC777Y7NhRCOj +y+EaDveaBk3e1CNOIZZbOVtXHS9dCF4Jef98l7VNg64N1uajeeAz0JmWAjCnPv/So0M/BVoG6kQC +2nz4SNAzqfkHx5Xh9T71XXG68pWpdIhhWeO/yloTunK0jF02h+mmxTwTv97QRCbut+wucPrXnbes +5cVAWubXbHssw1abR80LzvobtCHXt2a49CUwi1wNuepnsvRtrtWhnk/Yn+knArAdBtaP4/tIEp9/ +EaEQPkxROpaw0RPxx9gmrjrKkcRpnd8BKWRRb2jaFOwIQZeQjdCygPLPwj2/kWjFgGcexGATVdVh +mVd8upUPYUk6ynW8yQqTP2cOEvIo4jEbwFcW3wh8GcF+Dx+FHgo2fFt+J7x6v+Db9NpSvd4MVHAx +kUOVyLzwPt0JfjBkUO1/AaQzZ01oT74V77D2AhGiGxMlOtzCWfHjXEa7ZywCRuoeSKbmW9m1vFGi +kpbbqsY3Iqb+zCB0oy2pLmvLwIIRIbWTee5Ehr7XHuQe+w== +-----END CERTIFICATE----- + +WoSign China +============ +-----BEGIN CERTIFICATE----- +MIIFWDCCA0CgAwIBAgIQUHBrzdgT/BtOOzNy0hFIjTANBgkqhkiG9w0BAQsFADBGMQswCQYDVQQG +EwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNVBAMMEkNBIOayg+mAmuagueiv +geS5pjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgwMTAwMDFaMEYxCzAJBgNVBAYTAkNOMRowGAYD +VQQKExFXb1NpZ24gQ0EgTGltaXRlZDEbMBkGA1UEAwwSQ0Eg5rKD6YCa5qC56K+B5LmmMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0EkhHiX8h8EqwqzbdoYGTufQdDTc7WU1/FDWiD+k +8H/rD195L4mx/bxjWDeTmzj4t1up+thxx7S8gJeNbEvxUNUqKaqoGXqW5pWOdO2XCld19AXbbQs5 +uQF/qvbW2mzmBeCkTVL829B0txGMe41P/4eDrv8FAxNXUDf+jJZSEExfv5RxadmWPgxDT74wwJ85 +dE8GRV2j1lY5aAfMh09Qd5Nx2UQIsYo06Yms25tO4dnkUkWMLhQfkWsZHWgpLFbE4h4TV2TwYeO5 +Ed+w4VegG63XX9Gv2ystP9Bojg/qnw+LNVgbExz03jWhCl3W6t8Sb8D7aQdGctyB9gQjF+BNdeFy +b7Ao65vh4YOhn0pdr8yb+gIgthhid5E7o9Vlrdx8kHccREGkSovrlXLp9glk3Kgtn3R46MGiCWOc +76DbT52VqyBPt7D3h1ymoOQ3OMdc4zUPLK2jgKLsLl3Az+2LBcLmc272idX10kaO6m1jGx6KyX2m ++Jzr5dVjhU1zZmkR/sgO9MHHZklTfuQZa/HpelmjbX7FF+Ynxu8b22/8DU0GAbQOXDBGVWCvOGU6 +yke6rCzMRh+yRpY/8+0mBe53oWprfi1tWFxK1I5nuPHa1UaKJ/kR8slC/k7e3x9cxKSGhxYzoacX +GKUN5AXlK8IrC6KVkLn9YDxOiT7nnO4fuwECAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFOBNv9ybQV0T6GTwp+kVpOGBwboxMA0GCSqGSIb3DQEBCwUA +A4ICAQBqinA4WbbaixjIvirTthnVZil6Xc1bL3McJk6jfW+rtylNpumlEYOnOXOvEESS5iVdT2H6 +yAa+Tkvv/vMx/sZ8cApBWNromUuWyXi8mHwCKe0JgOYKOoICKuLJL8hWGSbueBwj/feTZU7n85iY +r83d2Z5AiDEoOqsuC7CsDCT6eiaY8xJhEPRdF/d+4niXVOKM6Cm6jBAyvd0zaziGfjk9DgNyp115 +j0WKWa5bIW4xRtVZjc8VX90xJc/bYNaBRHIpAlf2ltTW/+op2znFuCyKGo3Oy+dCMYYFaA6eFN0A +kLppRQjbbpCBhqcqBT/mhDn4t/lXX0ykeVoQDF7Va/81XwVRHmyjdanPUIPTfPRm94KNPQx96N97 +qA4bLJyuQHCH2u2nFoJavjVsIE4iYdm8UXrNemHcSxH5/mc0zy4EZmFcV5cjjPOGG0jfKq+nwf/Y +jj4Du9gqsPoUJbJRa4ZDhS4HIxaAjUz7tGM7zMN07RujHv41D198HRaG9Q7DlfEvr10lO1Hm13ZB +ONFLAzkopR6RctR9q5czxNM+4Gm2KHmgCY0c0f9BckgG/Jou5yD5m6Leie2uPAmvylezkolwQOQv +T8Jwg0DXJCxr5wkf09XHwQj02w47HAcLQxGEIYbpgNR12KvxAmLBsX5VYc8T1yaw15zLKYs4SgsO +kI26oQ== +-----END CERTIFICATE----- + +COMODO RSA Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMTE5MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR6FSS0gpWsawNJN3Fz0Rn +dJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8Xpz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZ +FGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+ +5eNu/Nio5JIk2kNrYrhV/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pG +x8cgoLEfZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z+pUX +2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7wqP/0uK3pN/u6uPQL +OvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZahSL0896+1DSJMwBGB7FY79tOi4lu3 +sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVICu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+C +GCe01a60y1Dma/RMhnEw6abfFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5 +WdYgGq/yapiqcrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvlwFTPoCWOAvn9sKIN9SCYPBMt +rFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+ +nq6PK7o9mfjYcwlYRm6mnPTXJ9OV2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSg +tZx8jb8uk2IntznaFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwW +sRqZCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiKboHGhfKp +pC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmckejkk9u+UJueBPSZI9FoJA +zMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yLS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHq +ZJx64SIDqZxubw5lT2yHh17zbqD5daWbQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk52 +7RH89elWsn2/x20Kk4yl0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7I +LaZRfyHBNVOFBkpdn627G190 +-----END CERTIFICATE----- + +USERTrust RSA Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz +0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2j +Y0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFn +RghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O ++T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq +/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKE +Y1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJM +lXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8 +yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+ +eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeW +FPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ +7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQ +Eg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM +8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYzeSf7dNXGi +FSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZXHlKYC6SQK5MNyosycdi +yA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9c +J2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGw +sAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gx +Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +USERTrust ECC Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2 +0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez +nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV +HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB +HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu +9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R4 +=========================== +-----BEGIN CERTIFICATE----- +MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprl +OQcJFspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAwDgYDVR0P +AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61FuOJAf/sKbvu+M8k8o4TV +MAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGXkPoUVy0D7O48027KqGx2vKLeuwIgJ6iF +JzWbVsaj8kfSt24bAgAXqmemFZHe+pTsewv4n4Q= +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R5 +=========================== +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6 +SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS +h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx +uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7 +yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +Staat der Nederlanden Root CA - G3 +================================== +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE +CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g +Um9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloXDTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMC +TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l +ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4y +olQPcPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WWIkYFsO2t +x1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqXxz8ecAgwoNzFs21v0IJy +EavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFyKJLZWyNtZrVtB0LrpjPOktvA9mxjeM3K +Tj215VKb8b475lRgsGYeCasH/lSJEULR9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUur +mkVLoR9BvUhTFXFkC4az5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU5 +1nus6+N86U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7Ngzp +07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHPbMk7ccHViLVlvMDo +FxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXtBznaqB16nzaeErAMZRKQFWDZJkBE +41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTtXUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleu +yjWcLhL75LpdINyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD +U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwpLiniyMMB8jPq +KqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8Ipf3YF3qKS9Ysr1YvY2WTxB1 +v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixpgZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA +8KCWAg8zxXHzniN9lLf9OtMJgwYh/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b +8KKaa8MFSu1BYBQw0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0r +mj1AfsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq4BZ+Extq +1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR1VmiiXTTn74eS9fGbbeI +JG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/QFH1T/U67cjF68IeHRaVesd+QnGTbksV +tzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM94B7IWcnMFk= +-----END CERTIFICATE----- + +Staat der Nederlanden EV Root CA +================================ +-----BEGIN CERTIFICATE----- +MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJOTDEeMBwGA1UE +CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFhdCBkZXIgTmVkZXJsYW5kZW4g +RVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0yMjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5M +MR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRl +cmxhbmRlbiBFViBSb290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkk +SzrSM4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nCUiY4iKTW +O0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3dZ//BYY1jTw+bbRcwJu+r +0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46prfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8 +Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13lpJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gV +XJrm0w912fxBmJc+qiXbj5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr +08C+eKxCKFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS/ZbV +0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0XcgOPvZuM5l5Tnrmd +74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH1vI4gnPah1vlPNOePqc7nvQDs/nx +fRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrPpx9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwa +ivsnuL8wbqg7MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI +eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u2dfOWBfoqSmu +c0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHSv4ilf0X8rLiltTMMgsT7B/Zq +5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTCwPTxGfARKbalGAKb12NMcIxHowNDXLldRqAN +b/9Zjr7dn3LDWyvfjFvO5QxGbJKyCqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tN +f1zuacpzEPuKqf2evTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi +5Dp6Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIaGl6I6lD4 +WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeLeG9QgkRQP2YGiqtDhFZK +DyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGy +eUN51q1veieQA6TqJIc/2b3Z6fJfUEkc7uzXLg== +-----END CERTIFICATE----- + +IdenTrust Commercial Root CA 1 +============================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS +b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES +MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB +IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld +hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/ +mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi +1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C +XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl +3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy +NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV +WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg +xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix +uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI +hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg +ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt +ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV +YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX +feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro +kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe +2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz +Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R +cGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +IdenTrust Public Sector Root CA 1 +================================= +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv +ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV +UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS +b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy +P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6 +Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI +rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf +qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS +mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn +ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh +LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v +iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL +4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B +Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw +DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A +mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt +GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt +m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx +NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4 +Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI +ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC +ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ +3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +Entrust Root Certification Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVy +bXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ug +b25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIw +HhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoT +DUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMx +OTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP +/vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXz +HHfV1IWNcCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKU +s/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4y +TGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRx +AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ6 +0B7vfec7aVHUbI2fkBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5Z +iXMRrEPR9RP/jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDgi +nWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P+ +vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xO +e4pIb4tF9g== +-----END CERTIFICATE----- + +Entrust Root Certification Authority - EC1 +========================================== +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn +YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYw +FAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2Fs +LXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQg +dXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt +IEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHy +AsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef +9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3h +vxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8 +kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +CFCA EV ROOT +============ +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE +CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB +IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw +MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD +DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV +BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD +7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN +uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW +ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7 +xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f +py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K +gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol +hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ +tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf +BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q +ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua +4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG +E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX +BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn +aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy +PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX +kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C +ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/cpp/libwebsockets/devnull_client.cpp b/extern/IXWebSocket-11.3.2/test/compatibility/cpp/libwebsockets/devnull_client.cpp new file mode 100644 index 0000000000..604125449c --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/cpp/libwebsockets/devnull_client.cpp @@ -0,0 +1,171 @@ +/* + * lws-minimal-ws-client + * + * Written in 2010-2019 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * This demonstrates the a minimal ws client using lws. + * + * Original programs connects to https://libwebsockets.org/ and makes a + * wss connection to the dumb-increment protocol there. While + * connected, it prints the numbers it is being sent by + * dumb-increment protocol. + * + * This is modified to make a test client which counts how much messages + * per second can be received. + * + * libwebsockets$ make && ./a.out + * g++ --std=c++14 -I/usr/local/opt/openssl/include devnull_client.cpp -lwebsockets + * messages received: 0 per second 0 total + * [2020/08/02 19:22:21:4774] U: LWS minimal ws client rx [-d ] [--h2] + * [2020/08/02 19:22:21:4814] U: callback_dumb_increment: established + * messages received: 0 per second 0 total + * messages received: 180015 per second 180015 total + * messages received: 172866 per second 352881 total + * messages received: 176177 per second 529058 total + * messages received: 174191 per second 703249 total + * messages received: 193397 per second 896646 total + * messages received: 196385 per second 1093031 total + * messages received: 194593 per second 1287624 total + * messages received: 189484 per second 1477108 total + * messages received: 200825 per second 1677933 total + * messages received: 183542 per second 1861475 total + * ^C[2020/08/02 19:22:33:4450] U: Completed OK + * + */ + +#include +#include +#include +#include +#include +#include + +static int interrupted; +static struct lws* client_wsi; + +std::atomic receivedCount(0); + +static int callback_dumb_increment( + struct lws* wsi, enum lws_callback_reasons reason, void* user, void* in, size_t len) +{ + switch (reason) + { + /* because we are protocols[0] ... */ + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + lwsl_err("CLIENT_CONNECTION_ERROR: %s\n", in ? (char*) in : "(null)"); + client_wsi = NULL; + break; + + case LWS_CALLBACK_CLIENT_ESTABLISHED: lwsl_user("%s: established\n", __func__); break; + + case LWS_CALLBACK_CLIENT_RECEIVE: receivedCount++; break; + + case LWS_CALLBACK_CLIENT_CLOSED: client_wsi = NULL; break; + + default: break; + } + + return lws_callback_http_dummy(wsi, reason, user, in, len); +} + +static const struct lws_protocols protocols[] = {{ + "dumb-increment-protocol", + callback_dumb_increment, + 0, + 0, + }, + {NULL, NULL, 0, 0}}; + +static void sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char** argv) +{ + uint64_t receivedCountTotal(0); + uint64_t receivedCountPerSecs(0); + + auto timer = [&receivedCountTotal, &receivedCountPerSecs] { + while (!interrupted) + { + std::cerr << "messages received: " << receivedCountPerSecs << " per second " + << receivedCountTotal << " total" << std::endl; + + receivedCountPerSecs = receivedCount - receivedCountTotal; + receivedCountTotal += receivedCountPerSecs; + + auto duration = std::chrono::seconds(1); + std::this_thread::sleep_for(duration); + } + }; + + std::thread t1(timer); + + struct lws_context_creation_info info; + struct lws_client_connect_info i; + struct lws_context* context; + const char* p; + int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE + /* for LLL_ verbosity above NOTICE to be built into lws, lws + * must have been configured with -DCMAKE_BUILD_TYPE=DEBUG + * instead of =RELEASE */ + /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ + /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ + /* | LLL_DEBUG */; + + signal(SIGINT, sigint_handler); + if ((p = lws_cmdline_option(argc, argv, "-d"))) logs = atoi(p); + + lws_set_log_level(logs, NULL); + lwsl_user("LWS minimal ws client rx [-d ] [--h2]\n"); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ + info.protocols = protocols; + info.timeout_secs = 10; + + /* + * since we know this lws context is only ever going to be used with + * one client wsis / fds / sockets at a time, let lws know it doesn't + * have to use the default allocations for fd tables up to ulimit -n. + * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we + * will use. + */ + info.fd_limit_per_thread = 1 + 1 + 1; + + context = lws_create_context(&info); + if (!context) + { + lwsl_err("lws init failed\n"); + return 1; + } + + memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */ + i.context = context; + i.port = 8008; + i.address = "127.0.0.1"; + i.path = "/"; + i.host = i.address; + i.origin = i.address; + i.protocol = protocols[0].name; /* "dumb-increment-protocol" */ + i.pwsi = &client_wsi; + + if (lws_cmdline_option(argc, argv, "--h2")) i.alpn = "h2"; + + lws_client_connect_via_info(&i); + + while (n >= 0 && client_wsi && !interrupted) + n = lws_service(context, 0); + + lws_context_destroy(context); + + lwsl_user("Completed %s\n", receivedCount > 10 ? "OK" : "Failed"); + + t1.join(); + + return receivedCount > 10; +} diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/csharp/.gitignore b/extern/IXWebSocket-11.3.2/test/compatibility/csharp/.gitignore new file mode 100644 index 0000000000..1746e3269e --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/csharp/.gitignore @@ -0,0 +1,2 @@ +bin +obj diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/csharp/Main.cs b/extern/IXWebSocket-11.3.2/test/compatibility/csharp/Main.cs new file mode 100644 index 0000000000..f6979e940f --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/csharp/Main.cs @@ -0,0 +1,99 @@ +// +// Main.cs +// Author: Benjamin Sergeant +// Copyright (c) 2020 Machine Zone, Inc. All rights reserved. +// +// In a different terminal, start a push server: +// $ ws push_server -q +// +// $ dotnet run +// messages received per second: 145157 +// messages received per second: 141405 +// messages received per second: 152202 +// messages received per second: 157149 +// messages received per second: 157673 +// messages received per second: 153594 +// messages received per second: 157830 +// messages received per second: 158422 +// + +using System; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; + +public class DevNullClientCli +{ + private static int receivedMessage = 0; + + public static async Task ReceiveAsync(ClientWebSocket ws, CancellationToken token) + { + int bufferSize = 8192; // 8K + var buffer = new byte[bufferSize]; + var offset = 0; + var free = buffer.Length; + + while (true) + { + var result = await ws.ReceiveAsync(new ArraySegment(buffer, offset, free), token).ConfigureAwait(false); + + offset += result.Count; + free -= result.Count; + if (result.EndOfMessage) break; + + if (free == 0) + { + // No free space + // Resize the outgoing buffer + var newSize = buffer.Length + bufferSize; + + var newBuffer = new byte[newSize]; + Array.Copy(buffer, 0, newBuffer, 0, offset); + buffer = newBuffer; + free = buffer.Length - offset; + } + } + + return buffer; + } + + private static void OnTimedEvent(object source, EventArgs e) + { + Console.WriteLine($"messages received per second: {receivedMessage}"); + receivedMessage = 0; + } + + public static async Task ReceiveMessagesAsync(string url) + { + var ws = new ClientWebSocket(); + + System.Uri uri = new System.Uri(url); + var cancellationToken = CancellationToken.None; + + try + { + await ws.ConnectAsync(uri, cancellationToken).ConfigureAwait(false); + while (true) + { + var data = await DevNullClientCli.ReceiveAsync(ws, cancellationToken); + receivedMessage += 1; + } + } + catch (System.Net.WebSockets.WebSocketException e) + { + Console.WriteLine($"WebSocket error: {e}"); + return; + } + } + + public static async Task Main() + { + var timer = new System.Timers.Timer(1000); + timer.Elapsed += OnTimedEvent; + timer.Enabled = true; + timer.Start(); + + var url = "ws://localhost:8008"; + await ReceiveMessagesAsync(url); + } +} diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/csharp/devnull_client.csproj b/extern/IXWebSocket-11.3.2/test/compatibility/csharp/devnull_client.csproj new file mode 100644 index 0000000000..afa7bad5e7 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/csharp/devnull_client.csproj @@ -0,0 +1,6 @@ + + + Exe + netcoreapp3.1 + + diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/node/devnull_client.js b/extern/IXWebSocket-11.3.2/test/compatibility/node/devnull_client.js new file mode 100644 index 0000000000..a65b493a6a --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/node/devnull_client.js @@ -0,0 +1,42 @@ +// +// With ws@7.3.1 +// and +// node --version +// v13.11.0 +// +// In a different terminal, start a push server: +// $ ws push_server -q +// +// $ node devnull_client.js +// messages received per second: 16643 +// messages received per second: 28065 +// messages received per second: 28432 +// messages received per second: 22207 +// messages received per second: 28805 +// messages received per second: 28694 +// messages received per second: 28180 +// messages received per second: 28601 +// messages received per second: 28698 +// messages received per second: 28931 +// messages received per second: 27975 +// +const WebSocket = require('ws'); + +const ws = new WebSocket('ws://localhost:8008'); + +ws.on('open', function open() { + ws.send('hello from node'); +}); + +var receivedMessages = 0; + +setInterval(function timeout() { + console.log(`messages received per second: ${receivedMessages}`) + receivedMessages = 0; +}, 1000); + +ws.on('message', function incoming(data) { + receivedMessages += 1; +}); + + diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/node/echo_server.js b/extern/IXWebSocket-11.3.2/test/compatibility/node/echo_server.js new file mode 100644 index 0000000000..e64f754bfe --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/node/echo_server.js @@ -0,0 +1,11 @@ +const WebSocket = require('ws'); + +const wss = new WebSocket.Server({ port: 8080 }); + +wss.on('connection', function connection(ws) { + ws.on('message', function incoming(message) { + console.log('received: %s', message); + }); + + ws.send('something'); +}); diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/node/echo_server_permessagedeflate.js b/extern/IXWebSocket-11.3.2/test/compatibility/node/echo_server_permessagedeflate.js new file mode 100644 index 0000000000..2ca57a5ec4 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/node/echo_server_permessagedeflate.js @@ -0,0 +1,11 @@ +const WebSocket = require('ws'); + +const wss = new WebSocket.Server({ port: 8080, perMessageDeflate: true }); + +wss.on('connection', function connection(ws) { + ws.on('message', function incoming(message) { + console.log('received: %s', message); + }); + + ws.send('something'); +}); diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websocket-client/generated_file b/extern/IXWebSocket-11.3.2/test/compatibility/python/websocket-client/generated_file new file mode 100644 index 0000000000..c8d8940e01 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websocket-client/generated_file @@ -0,0 +1 @@ +czfbgraoaitascdinvlsyeurfbmfnmygurmttbhhuwmpomvaomrnhqmqpljamcyiljyqrkhdentailnogzopcvenqyginndxtseuhkpaluaymrxnlpjygdusrqhukwzfzjupcwnlulukdiwjesbwcfcyiaqscncsabxzzcxminejluqasenbxdjhototjandnhkdspcxiseldanlvclsagspeloorbcojwpsodiparqsxncanovxfcqpnucqinwutqerufsexlzxwthaepakwaflvttiexkmfamrsjtxieyedwlhswedsxwnhbkxuhdzoszbbiwuybgsxkuoerseshpsqvgqikdjleurqphxukudeqrsktprishkhvdmspllaioahkogufilcejwfdnerfxmtckgczderuciawyegtbfvnfgqgcdcqkkokqfjwphfbjwbmfcubknciycvacvplijgwxteamnoovavmybhauywaitblzocxwaaabxsnjjqvcdnienbigyuvzesioxrneufrdjhweboognrawvzsaxdigpbtytfvfanxpprpwjujjlshtjbpzthzsybzjsojqilqxvaynpadzinxvapxuqzxexgncgifozcaouestholmjpszhvobmvbgujwcwzqiyukemtycdccllaslmkprmpmcrexhwmjndcnmqnagrukxqgusjohscbrgnothquoppagwuowndukimhsdzujhxlzkdysfmdzirwtwwxuyvxptyjwqubosnjwitomwlbxjtxypilwngkhenflbcxmmxdjsvvawbhqacjcolzfmhwxlntohcodrgzjfcojmxfdrevthhhzbxnmlnqrrmhtkforqabxyeffgqftjvhjnjepufymitfipzvvhajtwrkfafpmglapxypxywlhgxihaqeagtjoappkcizyyjsnvbhyrkmleuxkunmkorironpblzwlaldmvjtdbhrbtixmwfbbantvdzoxgmeqpqkjqyufvjvxfzrksbvwcepwdpxxmllyjhxcnfzafbphbmmtejxpfbnwdihmcrgxjjldkkbszmbyzlkmzkwmfgswhrhnmvdczlqcgyrvgfpkcnfoxkjhljnhgyuqwqdtxcagltnjpfdrtwpjepiqubjiekvkeukqolvpkmylzbjscxjbevkwgglhfidfaslcfqgehapawzogeshzilxfgkgfefyqzmauworwvabenpcvxrfadllcgkfcityrxpsxtrfnavetbvalqdzcnjjrbinmndtoumcjgxhxerwjhsuibkyxjiuerfgamqjmtibtwjeycrwzsksjphjmxvlaydvchzedobwrvbvbeqzhgxgjwovuiodhrmyjpbnxholdljeypbqtlfncacfqwvghhrlallbvrdvgaujutpkewwmjkdgashvywtkqgkyllkmuroiwxirilqyqgsnaopqhrixkictmyhuiyrhydsnidcvxaqumuoqzygpjohwlakwzesfqylutvgrzdhuhaaybrjkputtmnatksxmoqbgzwvestibutmigjqqcmhhfcmpgvppecumcvvdbqxkollgnutujmxezmvdmtzmsydtysqwgjjaucwuyllvaciwtcrgvjywhxwlkbhxmiigwyjcupxmuynauvrscsraavenztguizbzbhmnggdxiewoadvtcsxqlplrtwvnwkeyxxwdrbcdhlzppmqqvsaqbflgaffmddcxznkstzzaztaecgastzfptzehagruzmjnitxsaanqvybictgdxdwgnbysdcgbjgcxnxgtivysxuemjgeknaydcwfgzpeskwlgmbiwrkgmhrbmoaykjqtcgxlfdzggadokjkklhfkzbjkpkmejkrqdurwjxxplwzovteeqmpctwbnieygwannvmibjxyhmufvxthzrsujoflnbduvqfmelxitrpsbwjqopuheaetnpzaipktnvybbmuriiovcazzjxxdekfeeknriisrlliyjgbcjlraawqmsdxsxyzzrsrkitogzjktnqhbtoubgmezcarrofnkubvcukfnculapsmyvrcxotkvyzwzoiddilfnzlbgfojfxnkuyjyqqwonhtbgxybijnbaxfubaildzunznkwgrgtkzqjnmeyqoehecuogyylejoupxzswsntwphkggkotsihfwtvikturwnmpzbuqwyqmhpjldmvhtikenigqydzpbzklbzuawpiwwsmrdrxbivellixtlqsovrpsgjlmndwmmrwdzbhgaxcsgunjosdtoosqvleymyaomkzqlvecahvcqwgoczinmmwbwnydgimojchglqalhqriybuuyovgjogzgtinrughqpfvnmysqovaxkgzddolfyuprkwwbqrlddmasrtiywisfgzaptqlaycypniyddoxahndozuqhsmaktkvabjtfkxsieysoedvzsnefcowuxrrlvklwnnbsboqcybxjygykvxnpaswjuxowxrbtpdjrlodpyfmyjyynfgbnexcoybaktttrjinifitrtuxpsstnestfugjcxzxxovgscdoyjtrhpnpcscocdxidkncntdzcpkrfpslsroajkoqnzrgymozsrphwtypqoichqgnzbejcdzymkfngcbcglrcutnsdkiilnnxfzbxtitfmgnheewlvyevaaxhdsmxnqnkbqpvhybvawhfyiqsnhoiaqcznzlmdzuvqqdhbqwwjdowcvsbomfpflohgsfewgjwnqsukkzcawfniglolyfozyzmhfwqsuobxnrzwcntxmqwltliuutthdzcslfomrkvyckzpmhklykhutjftdsqavcdogqldghhfybjdrwshecknxwyioykqzenopbisrsvecpshdsnffoshegqksdajmqzaqucdwdnkrmlwrtggxxufbibjxhtpqwcwclcdgfthcdweopiynlplccmpsmzrcnffnimziuyogvdrntngebccqwkdbrdphtgfviubljdciybyhlyzwgtrjxivkuhipattlglwetwafufpprnivwtaluyfftrdaphrivhyqwnovbjwpafuolcycijqqksrqwaqowklowsancofbykotmbskhezyhfsrwsbxsvmawmrgdpyfbedxxrluwqbwbymhhumvfthdnfclwtmmntfienfbmgqansdpadgbbosoyahcvtghkwuurahdhwcitduifhhdomvqgulmuyiycsqwulzwnrxrfcqwdzzlsjjkkyrotlnihgklmzadwcahrvsexqxtaccqtzjepwxsrrkkzspesietkowdabycclearkdkbfclepidpfyfezvmfdacaiytxfgrljhrcsufjxdzwqlvzjxqqmhatzrlrwvgdrwazzarbmcvfbtnpeswbhxfhzvaubgxihijigkbbrkrqgmlvzwtqmpvhdvqomdpiddexcjhcotdnmlvplwjaxfvclrtaecsxhujkrrxcplimdcipwgwhrrgdskdvndtbcfzgigthbozocksrydsbtoddfotpqcbvxxmhrahtvhkswzvaxxoskyysvybfynmfeuksmlabwodyrtsduyhtpcnvkwmnhdyklhgjvurtnpzzwaisttbfgeljwswpxzgcqbtmjwecbbuoarjwlqhwgnpqprbcosaagqshlzdhbwocmpmtpsjckobgvsdvgxploddlxlzlgjskaqrcigifdnpgnzobcgzxludqoccuoxjraebhsabclurxxjtrfajmotaauoozgdapnqedfinkvxbuesvrjuvyjvhtyitiiwnpltbagpxtvrqcqrtgsfxjnepugkjebcrecqghsxkjxwhaimvqtrrmlgmetkxxiuewmnurafezxmuhtnflgnphnypbojxwgkhlbtwmbbuslndwchzdpllzsficdxbrlgsspysyvzzscsjmgkbxyitlthdvbfowmvnvdwhtecspmyfqasicswpbkpmgmpjeebcmfxkujvobxhlpyixuwltktbliaiabxeucdmclfcferyuezvmrgaddligiisriyqvszdiusdpjztqfjlnuddlprndcfnhymwashdtfjmljsykwaxamlmlqkalxvdlolqmgmkbiifjjxprqookgvlnfflflqcygqjthflzqzbwkryevdjlutcadlaktbhyggeeabvbkbrwvwbnyumhsocylhfhajasbgfynblfvyfrxgfilsqdriakgggsokkbsvlqioberusowebwbpghdycyebbdihjvjsmfphfzmabesrhqsnucqrixqdblybrfbwyzjnzjxrinlulgjgvovcxyorrkxieaiukdmqtzmykekwecelzryzvtuljbfvfezdqwhpnqlhybdtcndthgheaugqnuczfnouielqunwnwcunoetmowwxbfewkwtkebnafwghdjtocdkkmlvpanjkceoimtnpqhqunyanbaxuttgtexfzbvltuexmkazpeqowxbgrxccrgkvjfekjvrawrtexzfmkilcancmpxindbluhcbodgayvbrxfwigwzbmpwzawezxjdfrhzkdclkfvuvrpzhcpuirvbkeyqyjeayrsbkhjbeenslcpvrakrzrzraepzsqzaskcngfbtpgpaqjcwhmdnxecjozsptcfdfghdqtzcyqxssjddujxmmiiroichwxepzuutgljexasqsywjltlntwxmwsnuanjshpqnyzhuydlfwspbraolriciwrjmfoonfdhxarxlzngrbowlbayjbdnemswdngpyxsxulfhjdautaaeykdjovapbodwipkbntmjcrpuinnglykiyniusgugklkaaasbiaaqpcuhbcjwzyvjgxfvlhyedlwktzbpatnonxzifuyvoxybfuojbghypkfmspsolsxxqcacbucmknarohnciohdjfltxpxnixqgxzypaesuvznpuigjxhmpxdysuemicppsxfnsvdkfzebgmcbtqsbyhyilqmoxwvgxlkiyknkjxmqrpyuctxislriuvkcfaibymbfktflzlhcnywimmcwbytqnxrmjgmyurwxzzhfenhpnajkjiwdbzdixigfsmjbgihtcnmpsxgsixuavzcumuylaylhvpltpouqxtgkwuqdegfxqqcbxteqicgoylgeyyzvjgntvpwxioyfajlgnnrpmrmvxwsltuyvykqjujgqeosxkauymlxkqsggrcwsdrozusshytoybwcuizrjbcgqasvgjxkohddvswshmddoatzeuukdlhtgisxvumoflxtmgiuinfadufzwmmdisgefexnxkljaytifqqtxqyksbeqyowdgpyyodzvorauosvbjbmoopdwxsotwausaoinscrizfefrakqmrpyfuuckjqphqirjyfpjbgwsngydudhdbtoureqphpmtrubxaczzmhjfcohuznszsycqwtfuoejilgijcgpxneqmaokzidjonlemwyaouikffsngcapcwmncqqqnhvwdbczpbvnrkhpwgsejuhquwxspqphwltbveloldzbaivmrvvrjiwpcpodrqxxlcpcxcnpiirtbeqvmlgcydgsqxihwgrllbclnogdykpkvjsffzhjbuhzvennrrygrzimcfeeafuenmlzucgwfadmmdonbubfklngxxjscuksucnczsznwwugzglucazaeqemonamqjfjdkbbybcokhnzaikgpojeychguuxvvffkudjevfaymrklcnxlkrritgtkapwpafensxkkisrjyvboyaimchwpdrmmpqpchukbeeuosyzbkxyxqyshzqojnrkcqdyabpwcjovyerruvpzloqzmzfwzglzadyuvcacwunxetwslvywptumkgddiombawxbabihfwwgzfakhxmvfxwynhahezaelvlwhoxewnooskrgyyhqepzbebsxikvxhrktdsyplijixrqgsliqplyzjelyzibqactbshcpgnpwcnarlevzmqvpgfjhudsarlwkkjrerhdkqawuonfgikjjzpqjsfsgvfddvefbkotchdduvrmldmlahkiqpnhpkqiczzvwfgznoeqgjppnnvckuxribchtpioglwztnjhrqxamqnnspgqmoebbmsnsulcksphqhwdekmfljdplmvbmuukzxjegfoupjpfmuqpjkbcpcheyqnowdhgywkphiqfuxdeupdhgostxviimqqugnmrdgbjdqbwyeiuwiuqkjfaugbsinmtqbnxzkbiweqhpddycqzladlsysigdmxaltpkajaedlxfbheuxicuqbnnxgkbjogeghxaxypgpgzdjlrndqhxdgjgovbkzidomgkgaevoytuuerrputrtzpazvoakbederqbuabkadelrlwsawhflxyzhizcgcaxugbsmcjoyqlfgrlucnsrcpumggyjhefuoekmmhgygrogjfbvuemxrrzbxrghwxkpgkuazlloujokppgvrjvjyodslukvwczntrwcmdiunczffraaplfdalzdrxkneehuunmwopyinmkantjleqxrjkegkgxuxphavvwxtkeziheiykglcgzzmhspitzseeqzawrmrcnlaygdgqyerrhuhvoapzrormqoahojocjmnftjduzffmprbwthtlarvchfcwztczbpbfgrsqdhtzaxjflmjlzpsyxlquiezczrsnuwkwizuqjhbdbvofelaqbaiunazhfpzquyjjrkgtzknwktkegxbphgghutmjdeswyugfkqkpltrxvscywtgwutrebrksuuctcenyppphwaqitoneqxjxcfotzqjjybwxkmxyahglavgfkvnxzrgqgyavjyjqquvnzmwnyjwisxygcjagcvurxseirhyaossnyzkdeoiedisxvpxredemqazuauinyckvqivqnnyslucffxezagjvpfqphkgmujcfdolezjozulsqwoecnsayzsadvdusnuzwvhskaotgxlbodjwoxjepkqxnzfxrgrqqzrumqpnsibwwvikzmdfyizzlamdlmmtvvywaayflblceotrkpcbihvduywfbhxsaoxnwvqshxfavmzinomquvxhayiajybclzjdqplgnngsdelwccxgppdnbterwlfdeqhdtlvwyifrjcxutidmjgemmdtvwsazhgdcxxmdvojlyjzinvrogjlnzjyujekxigqwqvsspkaykyvxkwgadfxdwwnnrfhesatjxqbjggmpvezgmpgjkgcldqsmfaeluxnkomyqpcmchsqyyxsaavalvnbmjruevnhwaiayncmqfkfoymuuhjkegyggwwshuzijedggmrgdvhnidxaynbetgpxniilhllegsbvpnungcydprtbogtskwfxmpxmussbbmifzztpsizrnnhgwtupdyrpyzuryjhdrmtkqkiixkclgbabtyeapddotsxwwdxalxeyzsitswdsvafkhywfsptwjbhyxpbekibobxqfgnhmfwipempjwpqzwftugijdjpkkdgabqyhormuspfskklkeoggkppzehkoloamqcktevntuzazxiotiyrywpcoxwcttjqakkfrjotamylnliivgqvztiibqjxpzoyhegaogbjkqbulfyxcsxdzxlsuggfodcqhopuejnpmxfvsivfpljnwolajmieegimvaqicobmjymgaibetesqhudyqwrflzqirbhqgjggrphsslahyandvndrccrtjwieobqbdaqpnckzpyztxlrfanmjegbeluadbplrijleypswysvkuxfirlfcrvgjabzxsykfrzkgmcfnyanecvgynucqiubioxajdmfeveepatnxemcmpovojhsllufbelybvdujzmdstqduxxwzcqlesxkhhwuovdxyzotghbjvjxgqzfrbpxjnzxgsyjddjshjbantaufbynpdntmxmofouuwynmlwosjbytzyhgdwbyoebziaeubxgvbgxhxjqirqtpbuazfmchxxfmscqdfafzayrfqdwovhyqybahzhchdevxieenpyaxvdziwcimtofhhlqdvvlqmpvnudatxtxdjwcoxnhiwgkykeghzaorrwqmuowvycxgrmdqiefjyroohgcmxpicapgklkibifkjwyxvsowyqyvsyqwfhfawzlypmjyxhdofqjyugpugsaxvmasledrhihkiqgqrrxzinpzrajypstncoohkiefdiqsxgnexsduckenrqgaajtbcfntgcwtedguphsgpmushraeetymcmqzgkbmgixxegmqftuhterhyplmgzwpwupvyqjykziuyygeyegrikflhfvqcrnvpbvrxetwfcuvqafyaxemzqwrowvdevczfqvsrigthybbmmjvccishuwftfczljvfwnumxkftaaryqqszkqwahfekxmfnpikqmyiajbwyspbxxucfurkltlodvkwkpdituhwjvrzdtyayufkxtyscesyuzzvbmdvgyvdmgrollmolatwbktfvhxqivreftuatpmukokqnmrxjlwxnuvzdlpxjhwvlciahyrvqntfiijmilqnauvsjdptiedbzamwdmkroqzwsnhqklewbfbufwctceqqxpsfrdbzvtmaptywljllvkwnikjdxjtawpsstcqwbzcqhitydsehcvjjbojzfctytpytartwdebhgcqwwctqgtlfpebltflopwcrumirlnigyiejexviaztvdsoynrpnujgomclclcckjovelhkiurvwnpxmphnfdjaxxwuhbevppjpwcyvzdxksgwgxlafnyuqubyrdiwpbgwlhmupwqvsgmtznxpgsqpnfoxvuudifqxewloekhjugxoccrunzoplwuuroxxbyqidsupmldqndpmtjceteijudmofcbejdqztwfofqiyiqofbimamqefobqooaubgqagtoyplvqxibwxuvrlfpoyomaebxgraefqcutvgnbuuutycbrprlsagcfboqlpsilcjhbtuhlaznlvfzzgfqcrwmydpmoqhrdacotzfryysrwoeuddvlrcwsdgfhpodyahfiavzztlwatfieuaarhycgclbwvcscopbijbmljvwoohlxoihycciczlfllpbpcfwqwufvgttwjxwpyebxcphuhgtmpnutblgdkobtdctsxkboytemkpuonghrkzypuslijsdbhlkrtugmefjqtxmhmbytqegvwfriufkhrkwnfkuadxtsugfundchfyhuueniaytzbxfisszclcddbgswzkzayrafaxokljpqsdxxxtglfaykrqerladandredwqqaensptbncdrsjtqnxnyqpxmoansirmwstwiepfazrlqprzvyfbuclelyxfyinyqxophqccocgkmqgvvtqjhhjudurcybrqcavuymtomowpukqzykqwyquvwdnzmvnwurjtfbjzlcnifnqxmdgxopqmlzfsujykecipedavlxweryekshadhzcihvtxctybdwlcymrvmuquvciyllvdcvxeikdeqktopsjrvxvvfkzaskxpqvgmgtqbffsfdfmlnmpvmxgtmqzuefwqkkqganaxxonefahuamxlecjpbtrgzimlnxnxbrgqrzdylqrirgulahkofihvwtdvkfskhzkencnuldknptgxvwxuioxmwxsttptzjodgzcnquzcrjuotlslhsiuryxjbmdzhmrfhlhhzthbyhjrjxgguizwzrrcrnttqgtrfeilwexskqcqorkyvftkpmtsuxvffesvqfuzsknywfnkcvzoxjousuovysxhyenqobhitrpkcqophceyzyscsxcthrphaotpswxbwfvtmlqslrdkzgyrgyybnwwwenguxjjxxjobfdufqtyphuzbiirznwntyzgzlzagyrajpapilswrqunlgafhnyricwuhybrxixezoxfkyorpfrauonewzxxhdavycgloyhxstntfmahxfmlevozpsvjmeksgaxxylomzljjgtzdiubvgbggrngivqjiidmquruzyykvmfnlmhkzfaxaiicouwhczzylvevgtldrtnfuhhrzyqflqljychkdawapjrpylesjpssozhhsdgklwyvojlnohbmwzcabzjsqvkmicltiepspnvdgluyqdcgwqfjgzbvhwvtdrfvjmipwqkqcyiwcxrqxgufmcidzyhyolmaambogqwjosfgutidazqvtdlbwftgsyzviacfhpvvbnlemmofhwnywuqvvmamnzlnjiidzmmpabasrvoeeajyxgwbhyiulxvmykreirbkzgqcprdoykgkwlqccjjejadinnjmptkusvqaajmnescyoblugdgbajvskwvvzprpgqtvofudwppqnnkxsceardwrwclcgwtejleqkfeinskwjdxcsxyywiqvzsalltciewqeivuwtshyfskojumzofjkiphykwsubuidzwnleltnjhuybjwopflnmkwozlzuoihenyhlboqmhqfrjmkezvrormlgiamqimocwyuymlfuytuguixpevmkhjduvweospfdvezflakbrhgpvtyaqauchvlxclaaynwtzysoqczubeoicgzfhwuenphuctsnvyodyctsjmriojgriordejujrzxvnukygzuiaeyvvytebqvxpbifywjukqcdmnosaxtcstennujfqfvlllhtqlqylvdvzzbveuzwnpxbutndvbilxwxpluzqbungihfhgcdpaeoqwcjgbmmbrhiyjoguiqudhciahcyyznwcqbnqrbdhjqdqtddemyfyzrsioktaejbdpegbzfelgsjidylzikhfrlvndoucvjvkevkwdqtmlenfdcgsxaorlkounuwhtlzqckugixzauaskevqgcxkhxoobelfrkzhmhnyvierhjrquzwzegokqrmeljwikvobguclwwwcombthqazkaaqqyofawszmhftypcazamipjeepfldjryitvaxlfzqtguhlawhpmrcfnohiuwonbamgfvjikgucsmnouhwbpixukhixbitekygkkhthdgyeuwzclsnlhvezdxgxgedymwtapvaqertjqpptiawroaknnwpznheqooehvxdqdnvslxgvkmnzrlnmonkexhvpthuxhcjrhthbckzxutprzzkhmejzvhwavwyqdeeixmskbmmbgvdhmixqlvbrpjzszblvodxnkgpffxlvfktydommkaljerutvzikwibzsksmjwohoplatlnkrtmtjrqboowplqvjlobepfvdioxxliohzkyqfkbngyobtfdqfgqyiipogplmsikfhfyarssqryhulbsrffppvsouuwmtvadjepcotwbeabwzmprpshukvxstvjtmgiayitaidkahgwcpmqoegfeseiivhdhpdnlklqpejxqjvecsyexlvweblohopxqtxfsjrmuucdzylocdbwigqorirycbqsolyuqvqqnyotzsxgzxxekxdhowndlujjfaxlollectdqkuaakqrvblibirddtlqekvgshzvptozduiijpltxafyuwxqyedyjseyvpfjgmcawadixlateirsxagxihuvpduguozbvbnzhtkfabcceyfjihzqofvzzrctwxkcsbyzilacnmxvjwscbncqvmoiagqacneapkvwaedxeegfvxffwxgqreumzpdopuvftqmfmpliytcjbsgughjqdnjxkhgqypkyfdideexjhhswwoqgouaglggvmzwpofzoeucitqscedjsrjpjbkntbonnwhlmsndpvkrgcftqftihjpzbkghkfbpiqsirawlcjeetwtlsulpfgvpiayxsynsrgpdyxipmgqfkvedvalisapynbgqgoysttwqtxjhkslslsrqpnvybgtmihmhpmpyvfxjunmexpppijqzlcescahavwuozlpkzqhjbovnoizzqexlmispiioumpbjvdydrrfkoyadzeosfdrxlycchllvofrijvdokqfbmyuejrdehntpptwpjnjslegrqbcupbczcxahgrtgxeqwovyyqxytkpbqmudzvouzyjwadxdmbcqqalywztlsyksuxdngbafqqwgjcwuhxccmaaczddlnpihtuckzaqghhsbqihnwxdwmrzunoswzccsflnbvoafhuidmxzamlxruypkriuyfubgdztdaomxyqdeutlksorxqpehcqabhqddaxiyjhzbiidyxfoyxvrunampsbaiiedecbrcrgifaeixtpzixdznuhcerubzrosfiogwprsmhdnodkfenfujlvaibxqpvfdcyitwnrqtowcetxtrtjgukdbzzijlouifqvetbaixwpypefbfkjhjtcofotbdishawmuhchzfcivwdmatpcvviitrohszhlydsrrrxwukfglembsisxpntrdgdkqvfvazegdzkcxgjfpnnokwrxgrbpcudrojipvauguifrwxwujfytlirkgnhiaxjtoxmvxqdpzyauqilfvizmufygndusgqbxipkgnwgegowmmxwuoebsjdggqfrjlelkopghwnjxvstpqlpvnkawmclojoxwtvzfbgidhvyscrrxnbaikxocueshspfmtfkwdypadmnihlaswdbcnqxaqhamykqhaxplugtkdowsezhovfiomkgmxqwwsqzwelhdfluthjgqonlohnfajrmyviozrqxedfpwvflclxhodqkmxgovqepvgadyfxwlivwivgiggawsxeuneukpijosihavhzchytubnsjfoezcpcmxqalioxnretxhvjnmdgiutmrjszcenjvyfvihzwvyeniisernwrjjbznvrgrtrdpdtcnzovbtilwhadwvtytxltmzhgrqclfqosyevujjkzohgirkbnnzenwiuxjaxiqcdojhpaohxpesplymqmojthvhobbdvbeecdqtddjijkiqmxminhpqwavufbjejtblsqaetudqsfqjieuvmakhklajjhcvtybqmlazoxdbeaneblwwbywuvbhfmhjuvtppkkathkhsrdatexhnotunewojsndpivtsijzbluzhcucqzgpkeoubtbxwwlubtfzjruzmeihhvblypoxmgvyvcpyacktdaxqevapfgeinkhjepdiqxhsgykfuhnynhmcmcyqdefahovkttefebpccyrjbtjopbhgemeswwcnxriesnsvvjujmctjcszxosichlhzopqdballkdocrgwdzttctyivvpsnmiisgxmyztxeorhcohtqpnuiyaccdstuwmqoojzblowterytoujjedzpdcitrkvgbtnctjjpsceupaoabisnlihoofhtehuypnhkccrxtkpjsuwlqxrgppatvwapbuuoprzgbqqsgexhfxbbtvazonineqpkixnvfiarbznlxecgcxqdqpijzunyazqdxetotftieycixbcskubutgwahczdsvkpznqeblklwgpjublnxwkuwtizftqrhchnyyseinbgejrtmywxvqhebxzpqrgcnpsjrgqmdhprfdbgfnfepfyuwooqninekriwmvmruflknkmwpydedqhddwglctchuxqvjeloiephjucpmwinxpxwsjrlfptqpvkpmmmmqtufjizqmsiuajhlonrdbtjrzghctijtnwmpglarxvvvhwoflzeszrfaofghxqtxwanoitzxggaltucmxbmbtmfvroefihgbqfvzjritmhyzhsfuszbbfcsxmoxakihwxisydgthmdpsmqoneahynksbqjdsfyeccwszrzqqnsmjbjqxkkxldsjbpybrqadracyjpbwqaczrjrfbnvhcvrvxxdoeirvezejxrihlfpeavckcikekwrstxmnvogmsixkfpizvrbwmltxhavddobkogdijspqcuruaansndyozkurwurvumgseztpcmccduzlxestesxkjhrcuzzrljbijsgighjeczpezgblzfqtoofmqpazphirsiziiiklnbnmskdwghcmeqhzcnplipjgtxqyrkigjkoykksvqhmttgypltvrofmosetxufksonnqpqeputslewavksknqvdmaltrfiqjksomciumesqmtsrlnnacvmauhdvnioxzqnmcaorivoqqjxgyidmgcyoatmkkkloyhumajnauwtshrmupbnkwywuikhifksgixikfekvzqjohhgkofwrkremryspdeqfzaraipvwiuoiktihapywvgpeajdkrkhlkrgttafprinkrlxcchkfxltsfgbvqbuzzucbvgjninbdaggpnxhkjtzteqfpxwztfzallabqrxgjlnqumxufnahfguujmfugorilgnknjuejpoftjnlzcmiimhcbwkpexxnyxjbalextccnockapgwuqqtstfxatxopzzavhmxtgelvzwzeskhbnypgddpzxhcdeapfdcbioqbjjridrkckincxcwbskzodzkavnckkwtkiwqccnopwiefweyuzfggltvrrokqwxcpshqddtjvjrjoezghdzvlpqhmhankzmqhbuydwmdxktgarexkjbquutpybvhaqpzphkfvaivyhsbujvfbewjqovjcpwehjejswyfeeviqwsuwhaslavfsikmiromemhdubzyekdumsppewsifwhegeiipjvvckfrqctnsocbfgggumgkiwxtrvdupjgmuqapaxpcyvpdqqxriegdkeclxvipdkjxmkhqkspydlcxmhjfwjlxhseeonltzsbxpjtkvdvewbaywasgqfxtznbmjkqkmicccdlniwswzpfmlujytedfvrqyrvpextxjmnxbmpcyskntvouvrlyomihvelqwpclntyzfelzuvkdecwrzdbjlakajpyxjprrbnsojqhukjxtldrucaqqixbruivklddgwhphiohjlbeerupweaazyjcwkenqakxwalxzphwiwitvloooeuudmyhxthmxnptvvfooqebljdhxlacotnirjnafuajtggchbvayybtxsvadlmlkhvefhumpxyhqqwgcncmnvfvgmgrkdylspirvfbuvpopchjxwtjomxmhyberlbzspgulldkttxqltnhpqsxphczlugkfacgsrxwkhdgnthtbqttodxlsurmjyrqhtxfwwjodvrguaodvmxxzgqfgbxucltdzmmwiylxkfuvrbmpeoeeozarparsqpqlninroroxzjljmisemfjfujqgixcpwlbchxedeohapsulclhewwuozxhhqppepppkzjowfjebxnmbxhgigsjmmtqfwmnnhjojryogyruxmljqdxbwdbhwzyzldeslydbilkyrjgofxdmholtchtmjplfuufpqqbtfgklpxksmxhquqxbvwzghuagiymihgyqaimuodzrnqhqwhhmxoplkuhoqbbbhmtwemyzsagxjxbepqhmkgzlrsdypnlvlhsdtjtddyjanyyfqqjupwfellbkdoffjneicrbzwkrxjlmpefptbedidzflowwtscknjmjowgbnwhlqfamiflhdahhotslcsqesoeibilrufvnkovrnbggzaqxiiewhwflbtryefgokbnszqijwquwjwnpetewqbkmlebbihcczzuspexpmmyzqhkwraoqlcthoqnclddrxcbvhzjwdpqucsuzoskdabpnnccqvbhgchezbdcqtndfvdlkppsgvivuhjxjcdrsyigmvxjjqcmgehsnnmbpfohzfvvqjteuwegidvayomwnnzfkzjellwghlweqcjrxgaufqqcgxccocriqnhacmhuiznezrmqsgyczkgrzeyujgeukebtdiygobfetbimpvfotijicikcufubpdsjvbukfmlfiwvqdhgfbebohjnzywrhoybreniflcxegsfuxmjyeqfeorazhduinodjawsmyxytyxzabmnbmizimxxquvtkvxaygeohqdacwokgunjfgtptzhbfhjgvqiwbphwkizawzqpyaacxucganmvksrcwujlsmqceruyexghddqeogwpsunhfrcqcmzscchxfjehhuyhnesmycbcjxmjypnxgyinjpaekkcfylhryjdirpbwteqysxdhwolpuwilwdkmkqesmoxoowvefzemdjxjloobykwxsskzwozyuwayqtrzqwsagksaugsyzbttngaiygewgljbrxjpktyetvwnnocgjjjpzbuzgnyuvwywerbbtrtaeymrlbgbufuubhzpiumsliuibbmywwbgpgzwrvbrmxuszbnqccxntdgukwjoialgyaseaqwfbrzplghrbdudndjbecqbbveyzoceznauzsqtgsuomdhlelfhnfyefhutyeqbtxxbmnkyoptmdhsciuvghmkghlvbibystpparjzscavjvasqgsbqrgaagibszuldcllmsrmloihcnhsdlcicdrfjzdcjvmystiwxysbzevhscjfvuugmwboslfeiakjcqogngvzatvogvehcbjwprrcesaeovrtbpabsylnmblmqahgmjppkxubngzefzamzvmuhocwxexwiuaytzvxewbqetjsgatitgptljogqfgcemuxgadhgozcicezckemamhpkswdsmktyoijpkdslucxedeoebhhdlncgdkbusmkpcoueabyzrjrqykwdsslfubohvryhculxmcbdgfloonyylgvsuugiycewqydddtoxelrzsjtjwbtitzjlqwcdqnyjopkecpqwaurwplphlopnhrrgetyekjdxkxhqfrfbqvbxjqlamyafksvgqtewoijyjpegiecbiskmutqcjxfopaaoybkucwhpwfofzsbwzsvwdspnymzrvfitpcrihqhghklvwjmlwxaletwosqfcanrxfksmqszcssfskgmpqawpwdbbasynkkviubeylminqlrviaintvjmesspqwegxikebtyfygeyorbsdmgqictmhdfugrcydjatqsbfqwxspdbvxajszqechxfrftlrltzvoupvkzqwtduwghileqmtfzwgkrsshefkglsmwhkbufnunjfwgjpjceoofvjwkdvsunvhtkwaijcmsqtqkrtpzyotvihghihjynqputdrtalwojwnapnqnwbyfaxrauousqutdmhlnpbuwqssnxgybliacsbpkedhflpkinxcwhvpdhxmxcmbllftjjthzkmlhqausowxohrunkfruszhmyrvejvevrfvmzjtfssanuehsosnywzopponukzoggxmvtrketgtkcgfhkxqujhrmefcqcprkdtpemhkovqzrylmavtpguaymxzhgoxjowidzsdhgimtluwstgbxisnaoovwqnxvsgmmrzdhudttsifjbsdrwpwlrwkoeipvqeveslnaedkisghkiqpemtfrpoujtrpykrnjihxnbxurtogbjlwrmvavngajyggvlqggcmmejqlpvukdxydyrbqnuiieedokmsdqoptreljtzvksmsgdvjkyihwchapvnpuoknbmwmfimltdbfonxqliwijqoucrwfjmjvmwwtxfgstkzmxuviaptlioswbjdwbkcehwuhzocyqkioooltzcjaxugmajkcfmivikvxncnchrvfecxxlehkmlyxzvwfvamipxvwzyixramiqtyqckqbjsdsikcoksnrlhjkcabeouglcahoxosbyqcdxvcnnnizcsotqsoexkuwarofizhiqquntihbbguhmnjvdfmhzhioyzhyatdkzdzrdehmnjbrolcqwdmmmcvpmakkhpwlmiwoijrrirwmylujfoxqflcfxhuefzccbwyuzapxiehjsjuacesndetjurvpjwhtmduscaikamtjvpwlmuseihwezshgnnfmfxmxmpnvoleeifzpjorqubbyxujzplhwidzxwtzfzrcuusqrtajhdqamvkbagjjlppoxoybepwdmrkztujmhkjtbqgdmouuqdmknyrmulbnkyaemqmgodyovcpbqxfjcvhoutsfwvhkyzbaovkyujwzkeeprzuaihsdwsuiypkgtiesewpajnoqnlznhxprkheqstidbfdbgzhmraofehtapmdaqwhevnoyujjfntqnifrnlgclksuqbbamfcqyhjfazbifkjdqlwypxudjnvwhcecmpgwhijdylhwekpwxdleybobyrzqvqktzibmzrgkpnotrnxhojdhelvphoihmwwcsbjaqmecobncifglaqqrvvyxjghmigovzbrkbltuvdlmwhbtpihqagkmxgspjyuwnaxnptnpzyxhyidtgiihldmhpibttruaocitxhrhtdxcrpjimvchywspmdgfckmqrtktdoffqmbiiwygqipmzmrpckakkmhelodwatwdophxcflmsbxjobwwmdxhrfolkmxjayvhjhthhrnflfxqsihjxjixystxojyasnkeudbknxqfijhuyhhxrgwwuuknarhhpbgeajqkbaasljezyxjgqpcxzfdzmiroeudtnpphhyiyuldhmhpfceotsqktgzpwxzbygxlwiekfvhgshfeubyiqbuvursiehsyekddtchshigbmofnwsyoaoaxjekbyakiyysoqoifonueqopexsxeztrbyiebrqxychmzlxhtsquhryaxunevgcbkhecngwgdzrfluhaypzohfoslqpkjftdzqkhmzbvpebabamlgatjqvhqlyqdzbcclupsvmkwbopbjtjfakxgfrhqwcthvmjqcflyswgzphywjcrwhvnlddyherppldpcxbuwodpjdvdbeotpxxutccrivwffigweuuyxismzywddvcbdixdauemuqqbpjdkhvllkcayfceskqkrabblqysmxkgtdqefedsgtrheqmpzksjyiyxabnuokmlporvlclvtpfyghidbbbtkvjsjxcpjcxmafswqdureesqlfsnmpehrrwbhoghcdxxekacbbearffapdapeimzhstzhahjqccfhlljrmwecttylaiypejzabasbpyrbgcmtsumypchrmkvwhtcpyybueseqsjmiukqenqbqmzmqpfsufhgbxkeyconorhcjgqguigbfxdvmpnzactdcprensuvrsppmnildkpjklpdhtbhqpthshezkbgomjffpxaipguatdqsgivwslnydvfrlkelrgjkyrxbgfohtgcatbceucgympybjgxnqkohxnocwadqsicewhzdsupwxfwhjlflmzxouduitfqphqjjmwnmnxmswplgvnhittpykgliyjxekmifgfkznallynnhozibsqwalewbrctlptxnqwhfncvugutwidytrlysczkmpuxglyhjvybfgwxuhslqylwyhqudlydfgwhfhelooiyvlrvodytzkhlzxigymffpvebwadcfpkrromuiclslgctfjnqwyrrbnmbcgflcxitxqtybtyrnxshvqrqwedkykundckvxhyceypdodzbxqcxuhkuxdkhxtlsodlstigkpxqfvekvzhkirycvytjvwcfobuhrcfeffhxcybwoclxmhmmvxbmcxepthygwidpjcjyypcdcrngauuvxbzujmqcnxnfbippzditbkowuegwlgxbyzepybrwdoptvmgurhdvrbcpzjfpgfwqpzqreurbwkuaennrwoglmkomdavrysulkcrbiydaiqrhlqyztpbihigmqoiutaxmxacngcfudlptizyrqlazupyjehdxgaztjbnnycidbxyvqpemhbydkcnazvatusxlowiodxxlxbwdstcvhunfdslfsgenfvryamqxfttolfhlhvblzmbdhmzczurmpftulawsyssqqqiivodpqumjipvkdvexfkxyuclfntprbhefgjaywohjmnxmzkccqlysmaojcebeyvrftbzxcxqjegkmdxugbyxzqbtrwxrxqzovwnagzalasadywxiawwwgmawuzkcklsdbzivvsddsozqgokbplvasnyeiuflcgejzynfhhemslqecnakysgkzzvrkpbnngdlhuulfpbihrryrpulifjuchonzghgwlsukwhxvwjeyazftwesbnnkdahtzabfjnwgdqwxzzdyrqltlobffezavrpwrtkpeeawmzabipzdajlzemacpjwxdymocbipvyevanhcowzblwptnmpjreqnitbuccqpiyhpkewsbzxjuqztwttkvyfaodibuejuzwiflaepxtbspykabifjyaldbnperptpnztkwacntotcczimmphxjppvogaxxmxifitmftfhajqvyvjyuvtpvcikxieatjhgnilbtcnykvsgozfcvucwfkammkaqdcikqcwcdpdsofyynidmqafotdexkdauygxtoselztfqngzyotubbzzoscrupczbjbfecdsdhdztezpqfmmeunmyfyeqivepsdxzuvbcvjyqbdbnjggchafyetmhrsihembvizgbndrechdacdejuaokyxfkmogdwoxyoxpbadtmgjmioxfjsdbqazufqpxkxkzqchtvefpzyogddbvajlsruzcrccnuyvwikykxommfukuugwmyvlmhbcggmfpibaplbgxvqdseefvfljvjqomzzfyvlgmcoxffroxtofiilfqslmagzfwuqjtywnosjkwuechpfkvddrflgluagzwnybmnflsevmegkbwucsfqrcuphpcumwmopnfsaavctuvzqimjoknyftccwqrhkicbwdwtvxbwwzfdietrmahdybaotoixwdljrlqgryaonyqtkpblrpixcofzmoywcgkabekvxgpucgbmphkankvapfcuernguqnniomiguvalgmyikvaaddblxnsexvqczsnxrrfrbuesubpgubyekcocdwuzjggzbbmrrncqzvgxrofbfopabjrpyykpqvpgzpxjbqsenrtkmyzbfuuuhsccdhqqrnbbioyjtloouvusczxrqqodlsunklhltmisrbntvgfyugjjbnpzqsldkdbhadydlzklrqejspuqxtbmtafqqpeyljwfodbuhccdtqldhwkgtwqqntqwafouvwnmacmtkdksypegbyhwgdhgvtoeyivcurkjzsjzitoxzbzkykqlldmrmvnoxcjzubsehhfglrtvlgciqcxhpbkgfvbaidyjdpwmquvlyijrljwtbbmxurtmiuxhrbcflwzggtwpkzxphhkrzcrehpxcnfmwvknxomxlarsyqgbajjawekljetnhouvxkijyfhhmutkcuwcdmjjbfreygzakxqoyqkrwghmazcveztqpwebolenfzjwwuhcaecxppttswfoitqfgdckhtggxkioiwyayhehztqnsblrjkbhdqqjqilhzpquouctwowdmxayywrutygpwywsdrvxbcxcdqykcuekntpwtyhlrbnpsqybzduastejevwrovqusrafjyxkwumtiukdivqdcwmhavvbtynvxfreihtmlunonqkjuhpkgrjqqmelwpgdctzkqtzfnauxrliwvbyutgtvbtglilpcubijddkwnnhoutexpxzjzafhspvuavwfohvlpsvskjfsmrhifrvkrwghqvxhutranimdnhyzgvwzbfgmjtfpansqgdohqnfdhwflaxbogblwthaarwpkukaoyrqfclytumcmfvmuyaezohetxtpppluwdoaivaydhqleiarwxtifzzhubzigdgitzprnqenygbhpkmijtykcvdhrtzkuuibgpgstnmcbapgrebajpcvjiwfrwipswvptzflbdxggaerochihfzjqkazadcgztomnwzkuqwgqyvrwhimrtdxveivjrjnvxshrlzrvhhxiwijdzwdqgaycnwgqbeuhhnnplyokswwpxoediqrmzerfrsfeeeqngdkagnomqgagrbchaoojfxsmehducevodnldbiskliqytnjckxiqufxonmqmfjijzcnbllcmiyevpnhhcpztpvrpnsjnephczorbzjjgsgfhnnmrfthuhgqrmebpsunpkgfuwefibcmyqwxsnnlkdbkouokjhomvjlojlmfimdbnaqdbanmskqytgbytekaqrlewlkodeeotsgsfzeoilkeecrxfwiroyzhyjxkeylbrltdubdyxjmxustxnbpzwfeidufimxnsrlxubkxiadzoaeupwongllxtwpmtrrrxowovxnejjppdbrftwpbspkwpfvtmrwromfsevpvrinzmdsxvzegeufgepnibzwzqcyvjmeslyzvyqclteavbfqurhvmnhqheegsdynwrkzziaiulhaaiviwqotbwbjihtwdkvdgljnecbtbjnqkuvhccegkerlreithzfadkpgglkmzmvctxflrwojylcryfwlcrtgvahajiljxtgimloareeunvvvparcniraaphqixlijmptpqqkismjlqcmveyrmfcmpnekgwurpitlyloyunxzhoeahvxjfxkxzwnipenyratadekaulnntmnujazpucwwtqdncymhmmzvadzbpfpdseqockdxbyqokejrxfxnvoplysxuvkkkoiiglylftwdwmkysujjxtpsjdtdgvavkptpbnrutaimrsofhbtihwnukgvvcvutwchtkbsrhbutrrwhysnyqrzlpigquhugofufubhcrinjlzmgnullosgcyhwcnldrnvqnbgsmjdbadrkrmfvxgpxezdbnvkidoifririxhdzhktmorpqhdujnyytavdedsrbuwreoqgdsbhwksetbiedaslfytcxlojtwrreddzssirwxcjwkoxnonscrngpnnjgwruzxlzugwqrsqalslsjfqgdbsprnyjvwcsekgroeidwywofezorzdquuxdmuerbplhfikcppxtdclyyjatpygxvuekblbrjiaegmsxopqybhhtegdstxqyxwqqxyrvrmcjorzfnbrmhqvndwdfyhaljjkmrgxveryvmjauhotmixahukkghecnwrbjudnmdfnmoocexmnhycfdlfzciwxjasxyltvoveeparxiybasvxckduqweibjczoisdepkpvucgnadlkpeopwkyumbtdspugfrtcejkhwzaswnibwvjqlxrwmhapliunxnfisjbnmwgrqhourwrhkswazatvntcpxpwvpvqfwempvgxdclxlyavjvlhzvfcnxrcewepfbfsvodtjsqtsgbqrazvwlpkcbyqjwgqsnjicbkblwkckejomcgwqansooqychdseyzffcmqsvndvkixwtmnsojigfmesuaeoqvgsahsfjrqelohhpswbtudthmwodanbkywwqxqswlodlinywafmdovffujryuchknkxrixqipimpeloetgyhrqznokkseixuhgmjtiiqwyeumlegvejituthyazckfnkanpzqszihzlybqkznszjnxkzgbiwtmufidljjfvyvvltmfrbpquewktkcyldqkfxygmvoemeosztxswektosarqyhxffvxipeoenhvphkbidgjjpafzowrcwzgrrobveudcqgmebxyzzycovojifoinlqpkciozewwnlwquyyaxmfxfsawomvzjjhyqgxitflnhcxegstmheyaipslynijwqljjxmnxmbzfoiyvlpsizypyhedjgidrbgisbagrwhqheyuwnpuvimzrnqhahlfaxkewsfmtsxvihcerqdkjstjockstqerellhqrjzwpscketkoesnidwjztxjbjyxwqglefzcovihorlypyxfyhgsnngqqunwkjjtmpmitpfcvsbejlubzlqujsukygugeaxtlrxoitkrustafshhofgpxguflaifbiegufrpswygzvnhhswinvobtwztrilhjbmsxzdwzsyywnwstgsmvkedpelbqzmylozgqmozlhbvpsdmoxfumuvodjokbeikzudrqzoxvttwjrgottbisrgdemvzfrmyjealslzvlprgaolljqltdrgqdgnqvxcjcqgqmucyxfgoqkczdwrkhlhukqwsevjjphfhljdgegecgjzoberqgylcczwltirqmsmozccorxqkusakrwcymbfsgakuvljxgddhrqmdpbaoszfhhcdgvelceozlrqsaobcjhuoxourxilkjotsluigixcmvkfwwrdhwsdensgmymsbqdygzxxmzcognduiaoapxzydpkwsbeuqgpxmnlwjhebbevilkilmyxeeeettczdhcfgcrqxzmdjngagmmserittorrblefswayxuxqssjjxwslyhdmmrgecpoipmoqovdlmgyrrpbgpazndqluscbipunhqzvehvmhvrfbitfdivmnaldckbaiesrisvmxkkanchhisdodiilshzmkrgxbuelvcmhkzfjpzjnctwnhqrhjaaqqjkgajyspeyjyikamwmirnpqhemjunefitjupnztqsijfgpeiqosqddhewcsekpaghbbqlsbfiwbzfvzofriyausioikwlfirsjjwzwithgwkajqgmqhzejpmnpybppdubltalthydnlovcsqrlnwregffzvpuvzbketldivlmkcmijrwlqpabuxpzryyhpvvxkxrhtekwvqmjmxmwxxgpsvupgjyiwcnndzpbtybuktduoschvngpyfwlleoaozujjxiyjqykajxvthjtiapnsykhxwqeukbrenqbfwgroykvbvqeztawkqshwkiclmuwfyfymofyiivlbhkgkrqoquxnaszbwmcuxmrywiyxukdcdkuqgnvcjgrlilqgsshgilmqmcgxcxzymtckatejqabaszmccxpereyokmazqdodeyjvudovvttgrmfvutbltdaadkppkcgghvzfmziffkbgqckquspczgbmjuqnrzezrpuzsrexpgyfsqjnzrfnapsjjndqhamxlrebkhhoyxgzwgiovjsutthtisaayzqnwompliqgqbqwdoexfpwvsmdfloxiznfomwunhihajanmkbbjsdpkwqeklspfazrokdnrmxlrhavkwiztbokwuyinhypzpfhdolehhzxfpjjpazrzywqntdjzvktcwyvtvyeoqdqohlvllfffdpblxxahlaamfeqxllvurengzdhcqwvpcueboudrvregttvncorrjmltdstoivohowxupzdmssradbdeutzfejqgyrtvbznokhqyedjwwhygcybqgbscwmpbfmulxcmcvjfteyiuontiyrlkamvlzwwjnjkpxwgvwahjbhvewvycyenwhswiwpenxhtbsjsffnxogaxettkgnxgohmugkrgwwauhxkmivqsecpnrrvbyjhjgwfkcltxnsypklpfivkzbmuhxpeddreqahnvllhvwjozgwpamxkhlobeanzmhesvzjenarvnymvzfjqazsbrkhntbduztotrkltchoghqoxvptcnrbulyxkhpjusxbyuayeaiaqoiufwuiwlnyzkpbbawgulpweeciabnhcvxxwqaqzhqkndbykcvokblmrlwycnsbszkjcxssilegntnckzpqowpcycrchabgoclujwyozypyvlvqqvguwfpdbqdjqpdghmthvlsnlruoatlvderlergsapiaqwnxcpfrpqwfvosfgbftuikdhidczqwrvxjihwwmbemykwuxhzjpqpnyfnrxwzivynwlcawduxxaygtjmuomxzsuzyzqaahexdstojopgekcqvlzdxnxkkjatyotywfhirwtzcfyzzbxxzkxqqojywzmyqoxwlgdcnaxczzzyeywvhyjnflnvwwehpnashcolnlackrylspwsqjfptyytzsmopybotwcllqotuutxgsvqihmmsmcesksprjpacfmeldurzmyxfolerntlwofaupidrijtgsoaoxtpesjoerzydgnbocqprfhflxejuugmnhutejbmyqefupkobliqxsgejmzgjsdxwmadzxrqejwrxtqldanvpfixtzlalngftyrcczrrcxlivifyglcrfgjcdsacywctaxczsfvihpjhxdtvtqylzhbvxchmxrlofkxzviqnkfxgpsbncshhjrkrkduilrtkhfpzpavnnqwrbbimztkngwfhfawugsnscpsvctifjfobvoegibsxqtabjarlwsedufgfjckilqgzgwelpyqlziqiwoalanlbzcokuepicjpztbyvsfpqskfkabukgutofarljfqwaspaziylsqcmmtskortzfyahscpfkvgthkslrpmxlibpsjpebrexkjsnwpwzzfwpocmkgjrursekqwlufacbkpqpzprzvbxqbaabvjewbvvqwbpejwviidqzxonkvpitbjpxvimtupmfofqxqohuloifqlgenrsqiqwmaeuuqdhrydcdpfjkbaukzpqyxhdfuozrufjdruwjgarpqdyehzjfgjxomihelyfctnmnjuxmxopinvkuzrekhyxcjedumapxoeonkibrsgswwapblrmrgwfhmrzdttzruvwbbojxztixrtkcabpitaszmzvggbdogixefauogmwthfppeqsiugrhhnfjxqqjswohnpzclwifwbwgsmmixekvbgceyjbckdtchhxzvwjnerwqomgtoimxtqosbqubssiaunhufwazvzmrmfdutuldzzlpyazqontitrpqhbbpnozxxgrucjywqspiqjvhxohbjhiwszkhlzcngdlydogdzoiwuidoiaelglkfrvvsnwxxrlaozrarikrgdcelklweekjnbonfusxbuwcbcbktacneqrvkpcmwaantjcbzrcgoakjbcyjbjtubawvcpnblfvviqtmhmoqfcxiyjosteomsgjnurpxrgoeycrqmbcjhmvlatwirfiraapjemgofxlwhkdjteiafohkvuzrgfisdczjhqdlyugdpvptrfwokyhpatfslainoohszsbhdonabtzkhiwyrecemxezgihbtockentivkmmnhnuylhlvphfbcchrbdrjpasmhiulqibcgdjzudhynwottnzvubfivdwbsvrxmardeeexzkkphhrlmwjqqllaphmxosdijsrvokkoiwrzwbcmksfqyxmqgogpiqimknjxgokyymzrzfkmqxrosfanueqnzkuuflmnrolphqsqsorcvafhosjmtrjudeuffcoxgypiapebmwqkxzgkhegztolheckvvoxloupdwusifzlwvoqcgbkbauoabeudpebezkjltxjhxbnboykltlucppsgtzxefofeiluxgjzkhbiiwsyylwkwqqzzkursslfgxbkqkzlfpiqbjbktlwmoervcllfstcoguapsmudunkparfvygxscvqhegnzbpxzsspowrfwjidlapixgdvnwxbhitvkulinulfjsfmhiistnhxfbasiyqoxbbekgpcgaqohbjdbexooloinygqgumgxayfzuoglzylfqywvkfalyylefidhktmqnmufpmccatmmutygiqidgmdfyghqugmyrankcnbslbtprgdiwegznahwgzwlbuyawikcfqztnpceqbluensbxcdhdqixsngvearidtizszcbplgtqspusvipcyxkvtoqlaktmzsykpgrdfznqpyxphmwyrkmirdiiusbrboosjlgepgvhaeasjdgxtddigigvlidqnwmrjdbyawcxuxqqdoxjjxykxhnlhuwnktlqqmlzuttqrcdwefnculkafkcshhcbamxptkjiaebzvybjqaezjscadctekeyehupgltqixslwwmxtdllmvnkeyhbrxmafpzljqcmlwjhuqooqidxnntrcttmlcmulwwxontxkyuvfrjilgxkinpmsuxqovcyefkyczgggztejesjjjgnswdwkebyiwanupircunriaxtefdfvgktsthiztcyexzhwgrpgavbatoqhvucuweywzepgxgjppjywcqktpyucpjhbyfhtwrxymmpashkourddjoxnsebnfhmlogipkutynmrxjkxbyahvibnxkbkeilyndzhzhjdehaiurqscykmikxgnivdyozzhedkmnllblfgxdqddtzqlzvjdsizuhpxcdrqbatiykzbkeswztzlfudicsmgzjqfxgyptkbvbrheapunsgaadywojyzqhlzzbtqtaqcmprnhahxqsgxqmxfhzqghjegmvjazaahjyowtylbiuzlxbkziayezhdevqwtccihheldlfnanmzlboujtkjhhwkvaciwfjwjibvuitdlzpydtdxmhczanwtooznpuvhngqifrtfrzbbnpkzworwirfjwuphnfsrmqgpngvieuioqwxdreanbrtrngauruyhpdewltkopvizspesmkcadwafqgemzkczqkaogciqcgeqgqjqrxwddndjdkpdlbhhyznfoyzonihimztsejqsqadaipgesuqiwxhdqlkbwadfuiltudxndzbjwqeacuapscgcfudvfdxlwvkvfgdbntkqtxairbvcqtozsbnxbiupjolcqmemehpetkkcugbrqsgtofnvuuckdejpyikuntouneqzajqpfiicjjsjnzdcleyanyxwuadkvlnouxmvwebjujhppmyqhytoesoddpwmhhssqtndkcljbqegxbnqqxgodgloaskmdygleadjfmwvsbwzltgzgyxozvngyxpiexubwqjqhahpuyfallnxeaplfwmcaictoyxxoqtsxqkbnsmribfvgynhafkfbgajtkcflygfwrhbqknbcibrixxovfnosthfijnxxxwivcxqgkoltzppdoafmvjebfrrkkjsnnzaglpirrrgfeixmruqucortidgzhneidpnrdpmnfjnxcwqcbcrcxgrtlntcfptfkeujnbwlcvifxrnmqmiobzxpsmcrfbtkfxiywjjsmedaknsbxrglqphghliqegxiipqeezmbuzdnljolsvglkeherdtxvhjkzjkgpelzvsjxmsdcvvrogoqqugdbibsdliifesvsflwrpfjntoyrdhmzolxkglibwokbrkeziutswqbsodszhsugkkbcpdngdggrjpdnpdnteozdxpreujepfdmiuygwbnttqeuwymavsifolrohmnkhbrfezfzpzjgglaqcybhxrzrrfgkpmtfijgrjhsurokeszxtymermmddbnjbqmabuzcgjbtigeaxxlvptlrvkwyhzzkbfcsolgngempjdlwwkwdgbhoyebznrfbsndfjrutprfeikhpqwefwffgfztjlreekxkblzpsjdqwxbqhstllmhplxyxhrazvtkzigsvkumhswkadvlkrwdyifirkqialcfptvoipblfytamamqpkklqivdvzeubwsckhppfnhlpgbwdhgjzqksieeeqbltusdenijueofeacybpvqrwripvlzhywjixbulqgmfaznpvjebtaxcvdtnhmlwavvzhojostcpraerummgvqbrnxxdzbeqsskqbdgflwkbiceshgezuxcibfdllojgmneemempyazphjwpgipjurhaycxctaouftcdyyyulvwitknigiwtzqtyopfyijqnnxizjgdttnphmdpqepgzrdafddjnghegbrzyrqcguzhchanmjeycnnvgpmijyoruqwkooqdxeveccgpflbdprgbxvcxoloorifyfsgdsjbfcvbamirahlnojahcfkbaboxelcobbiuhxbfqprlkvysoxhzbkrdoyljcmzecafvuluxogkqxvhofwowaogwyzagecuxltvxhnstazaumsyvuvqjlemzzhshggxnauiditishtxsujxpkzfibpfwqfarwyietvcirimrjwkjwdgtyuwaqsjsmuigfiewltbtcyufhdqhihovnafzsinzljqmdizvktwrvgojqkvfvflzwdcnvtivloidpkhsfwsrdudlarhsacwaakwsflfovmqwglsdauukmdcggkyjjmrxchrfbkzokjcrzmlfhvubpvnftckteucaahwuvxuomtxzhezpnpxvrsqlbzwbuchlksrdyvcjlmmnsnaqhhrfmgtcpfshqyqbnridxofjulawzsdhnubwpuoxejjruvozijigxfkggtylooyriobaapvhkuvjgcfaafymafiocmfegsaajcnfyxsrsrodhypbopmriftkgyncupyhzzvxkvpvdffbdwrfytdspvfuxgdgukccxntqnabppgkqeqcbxabuaavsthjjloaybufptrfrsdzvyjhqgptgnnsiqxixdmlxrbvwpcesftrfsdeuwtnttpsldbdilzkkjszoqxmsuoihwqjzwxilyugprwtlftdzefgmrzftwpsnpxjeoradticcfwpudhbdbzatbeotgcrnivzxbgxozsezepkyrlkbgerqmoanhxhyvswreeqhaffiwwmttjmkziiejyctzpnzybjhtaaxxhsjtotxfsindtdubnbplpzbdzqqmbigerlvjxqtpwomtwnblnmsvcskzlibowdacnpnxwbrvczdjhzakghmfhijwqgyyzrrepujchblaibwytgiqbzzyyryfykcfhjmmgrfypfnrtmdfwzqonouilpzjmuchmkoxulhatkfeupueuebquqqiatniognyxfkjxiasuqwoodfexjkwocgevoasahrklhlwgjlywigdyjlvxxpaulyarxxhuqbtkglrumwgwkmdkearjmqjtiumnlxsixjxrzdolreudbpkjflrgyhjxqrvanxdhtxvgihpeijbuoqjetgafzflarfhlisnhiygvlqmaddteokzpjnlfmbntlzytswcqiyeetynsmhyabcrshuhpmhzbhhttfnjkbrmfonezmnqqtlwoppcwbktzepaepqysuxagcbvzfvqtojjwfxgsuoncqtclgjwcrsyvmhegekrnrzvtnlluiwpyicxodefpsfwrqvtodueemtzuvooumlhflipusmrwjdgedwezeerxdodzhnpreknzxctcxjeupxssefzxjonjrvebtkfnhljpfdauntldnsvzswfmkpkvzkrxbjyslwrhyfasrwiagcgkdbgejrcqaztwymannjktpddgwlbphjzmaudibllynryqodwidlflvmccickfjechcubjxzkelctnxynxqinqvgyzljxrhpomdzjaauiglqveehzaqudehwslgfkeagvpyuazbzwlljfmvranvpwfhgyuramixxzuusfcgstvtfyhrokezcbahsrahssldxhyvtimibmqhqeulclkskegsukzxazzskcqfyysljmrtqhdaomoqaxfroyxcryyaydjkrdwryxvvddueicvrntsesxrruoeamkdqqrptgymxmwkjvxxyojlbicdlesjyffuwhcakhsevfesdfcdgsdbtouwqjwztospmicswgdfadzyqnbmcayafpngzyfchjdypnlgbayicvoevgowfqcgchvmkcshjhkmrhgcrrzmhiyzimmkatjwbnuedtzazafyvffzkmhjmgwfeqifzrjjeexpzwlybkxzohdejizqlewiktwzxdeipaxjfjbwojfqongasyqagsafrslotkrvwiujivtsxcygbaoiqmpqecaozjglkmvedjgtyqkgnianhcgomzmdapwhtysilguynisqkavjfozwvvesynkoygqziicjwkgdayxlpjlsdkezcyobbvdkadqyqzkwrhegpudybwnsjvabgsnlvflvjbirjucmfnascxeeywcqcubuyvvbisockiujvhxjuzfxorzadtxpwidjmagoscljffidmbchxmbwtvundvntxadychjmekskjyjjfdwioipqehnakqcopzgwqxjmeqtigocyklntzcyetbmogxakgdihojjqheitevspouvxtvqbytksrfqaiciqbgcaburwrvrnbxxhhbkbhdivriqeexjjmskccmcjrupjaapdqhhresapwnvkiyyvkbcqiyygdxkyfuwlrntrvhugxbbltbnprcsigwlzfuicnykqsaqirturjbqapvodnplytcwnwumtfjnnuzaflnqgklyuhdwyqiztrrwnlllxqtuhnnadcl diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websocket-client/ws_send.py b/extern/IXWebSocket-11.3.2/test/compatibility/python/websocket-client/ws_send.py new file mode 100644 index 0000000000..a01d7acf7b --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websocket-client/ws_send.py @@ -0,0 +1,36 @@ +from websocket import * + +import random +import string +import ssl + +def randomString(stringLength=10): + """Generate a random string of fixed length """ + letters = string.ascii_lowercase + return ''.join(random.choice(letters) for i in range(stringLength)) + +st = randomString(32768) + +with open('generated_file', 'w') as f: + f.write(st) + +ws = create_connection("wss://echo.websocket.org/", + sslopt={"cert_reqs": ssl.CERT_NONE}) + +print("Sending") + +frame = ABNF.create_frame(st, ABNF.OPCODE_TEXT, 0) +ws.send_frame(frame) +cont_frame = ABNF.create_frame(st, ABNF.OPCODE_CONT, 0) +ws.send_frame(cont_frame) +cont_frame = ABNF.create_frame(st, ABNF.OPCODE_CONT, 1) +ws.send_frame(cont_frame) + +print("Sent") +print("Receiving...") +result = ws.recv() +if st+st+st == result: + print("Received ") +else: + print("Error") +ws.close() diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/DOCKER_VERSION b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/DOCKER_VERSION new file mode 100644 index 0000000000..8acdd82b76 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/DOCKER_VERSION @@ -0,0 +1 @@ +0.0.1 diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/Dockerfile b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/Dockerfile new file mode 100644 index 0000000000..a7046897ee --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/Dockerfile @@ -0,0 +1,17 @@ +FROM python:3.8.0-alpine3.10 + +RUN pip install websockets + +COPY vendor/protocol.py /usr/local/lib/python3.8/site-packages/websockets/protocol.py + +COPY *.py /usr/bin/ +COPY entrypoint.sh /usr/bin/ +RUN chmod +x /usr/bin/*.py + +RUN mkdir /certs +COPY *.pem /certs/ + +WORKDIR /certs + +EXPOSE 8765 8766 +CMD ["sh", "/usr/bin/entrypoint.sh"] diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/Procfile b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/Procfile new file mode 100644 index 0000000000..53d7acd939 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/Procfile @@ -0,0 +1,3 @@ +nginx: nginx -p . -c nginx.conf +websocket_server: python echo_server.py +send: sleep 1 ; ws send -x --verify_none wss://localhost:8765 /usr/local/bin/ws diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/README.md b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/README.md new file mode 100644 index 0000000000..f1c3eda86f --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/README.md @@ -0,0 +1,846 @@ +# Clients + +## ws + +``` +$ ws connect ws://127.0.0.1:8765 +Type Ctrl-D to exit prompt... +Connecting to url: ws://127.0.0.1:8765 +> ws_connect: connected +Uri: / +Handshake Headers: +Connection: Upgrade +Date: Sat, 08 Jun 2019 16:43:29 GMT +Sec-WebSocket-Accept: kPCNwGa97y+7NWdAvHi/7/rA8AE= +Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15 +Server: Python/3.7 websockets/7.0 +Upgrade: websocket +Received 13 bytes +ws_connect: received message: > Welcome ! +ws_connect: connection closed: code 1006 reason Abnormal closure +``` + +## wscat + +``` +$ ./node_modules/.bin/wscat -c ws://127.0.0.1:8765 +connected (press CTRL+C to quit) +< > Welcome ! +disconnected (code: 1006) +``` + +# Server + +``` +$ honcho start +15:29:52 system | nginx.1 started (pid=75372) +15:29:52 system | send.1 started (pid=75373) +15:29:52 system | websocket_server.1 started (pid=75374) +15:29:53 send.1 | [2020-01-05 15:29:53.414] [info] ws_send: Connecting to url: wss://localhost:8765 +15:29:53 send.1 | [2020-01-05 15:29:53.415] [info] ws_send: Connecting... +15:29:53 send.1 | [2020-01-05 15:29:53.436] [info] ws_send: connected +15:29:53 send.1 | [2020-01-05 15:29:53.436] [info] ws_send: Sending... +15:29:53 send.1 | [2020-01-05 15:29:53.436] [info] Uri: / +15:29:53 send.1 | [2020-01-05 15:29:53.436] [info] Headers: +15:29:53 send.1 | [2020-01-05 15:29:53.436] [info] Connection: upgrade +15:29:53 send.1 | [2020-01-05 15:29:53.436] [info] Date: Sun, 05 Jan 2020 23:29:53 GMT +15:29:53 send.1 | [2020-01-05 15:29:53.436] [info] Sec-WebSocket-Accept: 2+7DV7Q0Ib3fxynZwaNTtsAepIk= +15:29:53 send.1 | [2020-01-05 15:29:53.436] [info] Server: nginx/1.15.9 +15:29:53 send.1 | [2020-01-05 15:29:53.436] [info] Upgrade: websocket +15:29:53 send.1 | [2020-01-05 15:29:53.633] [info] load file from disk completed in 197 +15:29:54 send.1 | [2020-01-05 15:29:54.267] [info] ws_send: Step 0 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.269] [info] ws_send: Step 1 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.271] [info] ws_send: Step 2 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.273] [info] ws_send: Step 3 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.275] [info] ws_send: Step 4 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.277] [info] ws_send: Step 5 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.278] [info] ws_send: Step 6 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.280] [info] ws_send: Step 7 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.282] [info] ws_send: Step 8 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.284] [info] ws_send: Step 9 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.286] [info] ws_send: Step 10 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.288] [info] ws_send: Step 11 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.289] [info] ws_send: Step 12 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.291] [info] ws_send: Step 13 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.293] [info] ws_send: Step 14 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.295] [info] ws_send: Step 15 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.297] [info] ws_send: Step 16 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.298] [info] ws_send: Step 17 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.300] [info] ws_send: Step 18 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.302] [info] ws_send: Step 19 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.304] [info] ws_send: Step 20 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.306] [info] ws_send: Step 21 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.308] [info] ws_send: Step 22 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.309] [info] ws_send: Step 23 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.311] [info] ws_send: Step 24 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.313] [info] ws_send: Step 25 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.315] [info] ws_send: Step 26 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.317] [info] ws_send: Step 27 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.319] [info] ws_send: Step 28 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.320] [info] ws_send: Step 29 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.322] [info] ws_send: Step 30 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.324] [info] ws_send: Step 31 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.326] [info] ws_send: Step 32 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.328] [info] ws_send: Step 33 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.329] [info] ws_send: Step 34 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.331] [info] ws_send: Step 35 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.333] [info] ws_send: Step 36 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.335] [info] ws_send: Step 37 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.337] [info] ws_send: Step 38 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.339] [info] ws_send: Step 39 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.340] [info] ws_send: Step 40 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.342] [info] ws_send: Step 41 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.344] [info] ws_send: Step 42 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.346] [info] ws_send: Step 43 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.348] [info] ws_send: Step 44 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.350] [info] ws_send: Step 45 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.351] [info] ws_send: Step 46 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.353] [info] ws_send: Step 47 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.355] [info] ws_send: Step 48 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.357] [info] ws_send: Step 49 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.359] [info] ws_send: Step 50 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.361] [info] ws_send: Step 51 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.362] [info] ws_send: Step 52 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.364] [info] ws_send: Step 53 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.366] [info] ws_send: Step 54 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.368] [info] ws_send: Step 55 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.370] [info] ws_send: Step 56 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.372] [info] ws_send: Step 57 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.373] [info] ws_send: Step 58 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.375] [info] ws_send: Step 59 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.377] [info] ws_send: Step 60 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.379] [info] ws_send: Step 61 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.381] [info] ws_send: Step 62 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.383] [info] ws_send: Step 63 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.384] [info] ws_send: Step 64 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.386] [info] ws_send: Step 65 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.388] [info] ws_send: Step 66 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.390] [info] ws_send: Step 67 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.392] [info] ws_send: Step 68 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.394] [info] ws_send: Step 69 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.395] [info] ws_send: Step 70 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.397] [info] ws_send: Step 71 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.399] [info] ws_send: Step 72 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.401] [info] ws_send: Step 73 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.403] [info] ws_send: Step 74 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.405] [info] ws_send: Step 75 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.407] [info] ws_send: Step 76 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.409] [info] ws_send: Step 77 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.411] [info] ws_send: Step 78 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.412] [info] ws_send: Step 79 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.414] [info] ws_send: Step 80 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.416] [info] ws_send: Step 81 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.418] [info] ws_send: Step 82 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.420] [info] ws_send: Step 83 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.421] [info] ws_send: Step 84 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.423] [info] ws_send: Step 85 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.425] [info] ws_send: Step 86 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.427] [info] ws_send: Step 87 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.429] [info] ws_send: Step 88 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.431] [info] ws_send: Step 89 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.432] [info] ws_send: Step 90 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.434] [info] ws_send: Step 91 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.436] [info] ws_send: Step 92 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.438] [info] ws_send: Step 93 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.440] [info] ws_send: Step 94 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.442] [info] ws_send: Step 95 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.444] [info] ws_send: Step 96 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.445] [info] ws_send: Step 97 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.447] [info] ws_send: Step 98 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.449] [info] ws_send: Step 99 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.451] [info] ws_send: Step 100 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.453] [info] ws_send: Step 101 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.454] [info] ws_send: Step 102 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.456] [info] ws_send: Step 103 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.458] [info] ws_send: Step 104 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.460] [info] ws_send: Step 105 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.462] [info] ws_send: Step 106 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.464] [info] ws_send: Step 107 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.465] [info] ws_send: Step 108 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.467] [info] ws_send: Step 109 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.469] [info] ws_send: Step 110 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.471] [info] ws_send: Step 111 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.473] [info] ws_send: Step 112 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.475] [info] ws_send: Step 113 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.476] [info] ws_send: Step 114 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.478] [info] ws_send: Step 115 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.480] [info] ws_send: Step 116 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.482] [info] ws_send: Step 117 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.484] [info] ws_send: Step 118 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.486] [info] ws_send: Step 119 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.488] [info] ws_send: Step 120 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.489] [info] ws_send: Step 121 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.491] [info] ws_send: Step 122 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.493] [info] ws_send: Step 123 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.495] [info] ws_send: Step 124 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.497] [info] ws_send: Step 125 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.499] [info] ws_send: Step 126 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.500] [info] ws_send: Step 127 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.502] [info] ws_send: Step 128 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.504] [info] ws_send: Step 129 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.506] [info] ws_send: Step 130 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.508] [info] ws_send: Step 131 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.509] [info] ws_send: Step 132 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.511] [info] ws_send: Step 133 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.513] [info] ws_send: Step 134 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.515] [info] ws_send: Step 135 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.517] [info] ws_send: Step 136 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.519] [info] ws_send: Step 137 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.520] [info] ws_send: Step 138 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.522] [info] ws_send: Step 139 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.524] [info] ws_send: Step 140 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.526] [info] ws_send: Step 141 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.528] [info] ws_send: Step 142 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.530] [info] ws_send: Step 143 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.531] [info] ws_send: Step 144 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.533] [info] ws_send: Step 145 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.535] [info] ws_send: Step 146 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.537] [info] ws_send: Step 147 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.539] [info] ws_send: Step 148 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.541] [info] ws_send: Step 149 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.542] [info] ws_send: Step 150 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.544] [info] ws_send: Step 151 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.546] [info] ws_send: Step 152 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.548] [info] ws_send: Step 153 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.550] [info] ws_send: Step 154 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.551] [info] ws_send: Step 155 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.553] [info] ws_send: Step 156 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.555] [info] ws_send: Step 157 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.557] [info] ws_send: Step 158 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.559] [info] ws_send: Step 159 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.561] [info] ws_send: Step 160 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.562] [info] ws_send: Step 161 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.564] [info] ws_send: Step 162 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.566] [info] ws_send: Step 163 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.568] [info] ws_send: Step 164 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.570] [info] ws_send: Step 165 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.571] [info] ws_send: Step 166 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.573] [info] ws_send: Step 167 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.575] [info] ws_send: Step 168 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.577] [info] ws_send: Step 169 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.579] [info] ws_send: Step 170 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.581] [info] ws_send: Step 171 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.582] [info] ws_send: Step 172 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.584] [info] ws_send: Step 173 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.586] [info] ws_send: Step 174 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.588] [info] ws_send: Step 175 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.590] [info] ws_send: Step 176 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.592] [info] ws_send: Step 177 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.593] [info] ws_send: Step 178 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.595] [info] ws_send: Step 179 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.597] [info] ws_send: Step 180 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.599] [info] ws_send: Step 181 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.601] [info] ws_send: Step 182 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.602] [info] ws_send: Step 183 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.604] [info] ws_send: Step 184 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.606] [info] ws_send: Step 185 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.608] [info] ws_send: Step 186 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.610] [info] ws_send: Step 187 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.612] [info] ws_send: Step 188 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.613] [info] ws_send: Step 189 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.615] [info] ws_send: Step 190 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.617] [info] ws_send: Step 191 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.619] [info] ws_send: Step 192 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.621] [info] ws_send: Step 193 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.622] [info] ws_send: Step 194 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.624] [info] ws_send: Step 195 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.626] [info] ws_send: Step 196 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.628] [info] ws_send: Step 197 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.630] [info] ws_send: Step 198 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.632] [info] ws_send: Step 199 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.633] [info] ws_send: Step 200 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.635] [info] ws_send: Step 201 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.637] [info] ws_send: Step 202 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.639] [info] ws_send: Step 203 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.641] [info] ws_send: Step 204 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.642] [info] ws_send: Step 205 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.644] [info] ws_send: Step 206 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.646] [info] ws_send: Step 207 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.648] [info] ws_send: Step 208 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.650] [info] ws_send: Step 209 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.652] [info] ws_send: Step 210 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.653] [info] ws_send: Step 211 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.655] [info] ws_send: Step 212 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.657] [info] ws_send: Step 213 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.659] [info] ws_send: Step 214 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.661] [info] ws_send: Step 215 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.663] [info] ws_send: Step 216 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.664] [info] ws_send: Step 217 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.666] [info] ws_send: Step 218 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.668] [info] ws_send: Step 219 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.670] [info] ws_send: Step 220 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.672] [info] ws_send: Step 221 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.674] [info] ws_send: Step 222 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.675] [info] ws_send: Step 223 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.677] [info] ws_send: Step 224 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.679] [info] ws_send: Step 225 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.681] [info] ws_send: Step 226 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.683] [info] ws_send: Step 227 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.685] [info] ws_send: Step 228 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.686] [info] ws_send: Step 229 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.688] [info] ws_send: Step 230 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.690] [info] ws_send: Step 231 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.692] [info] ws_send: Step 232 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.694] [info] ws_send: Step 233 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.695] [info] ws_send: Step 234 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.697] [info] ws_send: Step 235 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.699] [info] ws_send: Step 236 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.701] [info] ws_send: Step 237 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.703] [info] ws_send: Step 238 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.705] [info] ws_send: Step 239 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.707] [info] ws_send: Step 240 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.708] [info] ws_send: Step 241 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.710] [info] ws_send: Step 242 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.712] [info] ws_send: Step 243 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.714] [info] ws_send: Step 244 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.716] [info] ws_send: Step 245 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.718] [info] ws_send: Step 246 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.719] [info] ws_send: Step 247 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.721] [info] ws_send: Step 248 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.723] [info] ws_send: Step 249 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.725] [info] ws_send: Step 250 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.727] [info] ws_send: Step 251 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.729] [info] ws_send: Step 252 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.730] [info] ws_send: Step 253 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.732] [info] ws_send: Step 254 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.734] [info] ws_send: Step 255 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.736] [info] ws_send: Step 256 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.738] [info] ws_send: Step 257 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.740] [info] ws_send: Step 258 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.742] [info] ws_send: Step 259 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.743] [info] ws_send: Step 260 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.745] [info] ws_send: Step 261 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.747] [info] ws_send: Step 262 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.749] [info] ws_send: Step 263 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.751] [info] ws_send: Step 264 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.752] [info] ws_send: Step 265 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.754] [info] ws_send: Step 266 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.756] [info] ws_send: Step 267 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.758] [info] ws_send: Step 268 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.760] [info] ws_send: Step 269 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.762] [info] ws_send: Step 270 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.763] [info] ws_send: Step 271 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.765] [info] ws_send: Step 272 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.767] [info] ws_send: Step 273 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.769] [info] ws_send: Step 274 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.771] [info] ws_send: Step 275 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.772] [info] ws_send: Step 276 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.774] [info] ws_send: Step 277 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.776] [info] ws_send: Step 278 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.778] [info] ws_send: Step 279 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.780] [info] ws_send: Step 280 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.782] [info] ws_send: Step 281 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.783] [info] ws_send: Step 282 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.785] [info] ws_send: Step 283 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.787] [info] ws_send: Step 284 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.789] [info] ws_send: Step 285 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.791] [info] ws_send: Step 286 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.793] [info] ws_send: Step 287 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.794] [info] ws_send: Step 288 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.796] [info] ws_send: Step 289 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.798] [info] ws_send: Step 290 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.800] [info] ws_send: Step 291 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.802] [info] ws_send: Step 292 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.804] [info] ws_send: Step 293 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.805] [info] ws_send: Step 294 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.807] [info] ws_send: Step 295 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.809] [info] ws_send: Step 296 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.811] [info] ws_send: Step 297 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.813] [info] ws_send: Step 298 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.814] [info] ws_send: Step 299 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.816] [info] ws_send: Step 300 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.818] [info] ws_send: Step 301 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.820] [info] ws_send: Step 302 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.822] [info] ws_send: Step 303 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.824] [info] ws_send: Step 304 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.825] [info] ws_send: Step 305 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.827] [info] ws_send: Step 306 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.829] [info] ws_send: Step 307 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.831] [info] ws_send: Step 308 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.833] [info] ws_send: Step 309 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.835] [info] ws_send: Step 310 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.836] [info] ws_send: Step 311 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.838] [info] ws_send: Step 312 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.840] [info] ws_send: Step 313 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.842] [info] ws_send: Step 314 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.844] [info] ws_send: Step 315 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.845] [info] ws_send: Step 316 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.847] [info] ws_send: Step 317 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.849] [info] ws_send: Step 318 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.851] [info] ws_send: Step 319 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.853] [info] ws_send: Step 320 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.855] [info] ws_send: Step 321 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.856] [info] ws_send: Step 322 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.858] [info] ws_send: Step 323 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.860] [info] ws_send: Step 324 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.862] [info] ws_send: Step 325 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.864] [info] ws_send: Step 326 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.866] [info] ws_send: Step 327 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.867] [info] ws_send: Step 328 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.869] [info] ws_send: Step 329 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.871] [info] ws_send: Step 330 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.873] [info] ws_send: Step 331 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.875] [info] ws_send: Step 332 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.876] [info] ws_send: Step 333 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.878] [info] ws_send: Step 334 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.880] [info] ws_send: Step 335 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.882] [info] ws_send: Step 336 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.884] [info] ws_send: Step 337 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.886] [info] ws_send: Step 338 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.888] [info] ws_send: Step 339 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.889] [info] ws_send: Step 340 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.891] [info] ws_send: Step 341 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.893] [info] ws_send: Step 342 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.895] [info] ws_send: Step 343 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.897] [info] ws_send: Step 344 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.899] [info] ws_send: Step 345 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.900] [info] ws_send: Step 346 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.902] [info] ws_send: Step 347 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.904] [info] ws_send: Step 348 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.906] [info] ws_send: Step 349 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.908] [info] ws_send: Step 350 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.910] [info] ws_send: Step 351 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.911] [info] ws_send: Step 352 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.913] [info] ws_send: Step 353 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.915] [info] ws_send: Step 354 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.917] [info] ws_send: Step 355 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.919] [info] ws_send: Step 356 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.920] [info] ws_send: Step 357 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.922] [info] ws_send: Step 358 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.924] [info] ws_send: Step 359 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.926] [info] ws_send: Step 360 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.928] [info] ws_send: Step 361 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.930] [info] ws_send: Step 362 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.932] [info] ws_send: Step 363 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.933] [info] ws_send: Step 364 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.935] [info] ws_send: Step 365 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.937] [info] ws_send: Step 366 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.939] [info] ws_send: Step 367 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.941] [info] ws_send: Step 368 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.943] [info] ws_send: Step 369 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.944] [info] ws_send: Step 370 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.946] [info] ws_send: Step 371 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.948] [info] ws_send: Step 372 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.950] [info] ws_send: Step 373 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.952] [info] ws_send: Step 374 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.954] [info] ws_send: Step 375 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.955] [info] ws_send: Step 376 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.957] [info] ws_send: Step 377 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.959] [info] ws_send: Step 378 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.961] [info] ws_send: Step 379 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.963] [info] ws_send: Step 380 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.965] [info] ws_send: Step 381 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.966] [info] ws_send: Step 382 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.968] [info] ws_send: Step 383 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.970] [info] ws_send: Step 384 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.972] [info] ws_send: Step 385 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.974] [info] ws_send: Step 386 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.976] [info] ws_send: Step 387 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.977] [info] ws_send: Step 388 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.979] [info] ws_send: Step 389 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.981] [info] ws_send: Step 390 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.983] [info] ws_send: Step 391 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.985] [info] ws_send: Step 392 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.986] [info] ws_send: Step 393 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.988] [info] ws_send: Step 394 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.990] [info] ws_send: Step 395 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.992] [info] ws_send: Step 396 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.994] [info] ws_send: Step 397 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.996] [info] ws_send: Step 398 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.997] [info] ws_send: Step 399 out of 768 +15:29:54 send.1 | [2020-01-05 15:29:54.999] [info] ws_send: Step 400 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.001] [info] ws_send: Step 401 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.003] [info] ws_send: Step 402 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.005] [info] ws_send: Step 403 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.007] [info] ws_send: Step 404 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.009] [info] ws_send: Step 405 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.010] [info] ws_send: Step 406 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.012] [info] ws_send: Step 407 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.014] [info] ws_send: Step 408 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.016] [info] ws_send: Step 409 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.018] [info] ws_send: Step 410 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.020] [info] ws_send: Step 411 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.021] [info] ws_send: Step 412 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.023] [info] ws_send: Step 413 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.025] [info] ws_send: Step 414 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.027] [info] ws_send: Step 415 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.029] [info] ws_send: Step 416 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.030] [info] ws_send: Step 417 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.032] [info] ws_send: Step 418 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.034] [info] ws_send: Step 419 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.036] [info] ws_send: Step 420 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.038] [info] ws_send: Step 421 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.040] [info] ws_send: Step 422 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.041] [info] ws_send: Step 423 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.043] [info] ws_send: Step 424 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.045] [info] ws_send: Step 425 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.047] [info] ws_send: Step 426 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.049] [info] ws_send: Step 427 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.051] [info] ws_send: Step 428 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.052] [info] ws_send: Step 429 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.054] [info] ws_send: Step 430 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.056] [info] ws_send: Step 431 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.058] [info] ws_send: Step 432 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.060] [info] ws_send: Step 433 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.062] [info] ws_send: Step 434 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.063] [info] ws_send: Step 435 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.065] [info] ws_send: Step 436 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.067] [info] ws_send: Step 437 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.069] [info] ws_send: Step 438 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.071] [info] ws_send: Step 439 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.073] [info] ws_send: Step 440 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.075] [info] ws_send: Step 441 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.077] [info] ws_send: Step 442 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.078] [info] ws_send: Step 443 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.080] [info] ws_send: Step 444 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.082] [info] ws_send: Step 445 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.084] [info] ws_send: Step 446 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.086] [info] ws_send: Step 447 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.088] [info] ws_send: Step 448 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.090] [info] ws_send: Step 449 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.091] [info] ws_send: Step 450 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.093] [info] ws_send: Step 451 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.095] [info] ws_send: Step 452 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.097] [info] ws_send: Step 453 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.099] [info] ws_send: Step 454 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.101] [info] ws_send: Step 455 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.102] [info] ws_send: Step 456 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.104] [info] ws_send: Step 457 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.106] [info] ws_send: Step 458 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.108] [info] ws_send: Step 459 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.110] [info] ws_send: Step 460 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.112] [info] ws_send: Step 461 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.113] [info] ws_send: Step 462 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.115] [info] ws_send: Step 463 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.117] [info] ws_send: Step 464 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.119] [info] ws_send: Step 465 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.121] [info] ws_send: Step 466 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.123] [info] ws_send: Step 467 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.124] [info] ws_send: Step 468 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.126] [info] ws_send: Step 469 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.128] [info] ws_send: Step 470 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.130] [info] ws_send: Step 471 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.132] [info] ws_send: Step 472 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.134] [info] ws_send: Step 473 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.135] [info] ws_send: Step 474 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.137] [info] ws_send: Step 475 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.139] [info] ws_send: Step 476 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.141] [info] ws_send: Step 477 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.143] [info] ws_send: Step 478 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.145] [info] ws_send: Step 479 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.146] [info] ws_send: Step 480 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.148] [info] ws_send: Step 481 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.150] [info] ws_send: Step 482 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.152] [info] ws_send: Step 483 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.154] [info] ws_send: Step 484 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.155] [info] ws_send: Step 485 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.157] [info] ws_send: Step 486 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.159] [info] ws_send: Step 487 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.161] [info] ws_send: Step 488 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.163] [info] ws_send: Step 489 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.165] [info] ws_send: Step 490 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.166] [info] ws_send: Step 491 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.168] [info] ws_send: Step 492 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.170] [info] ws_send: Step 493 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.172] [info] ws_send: Step 494 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.174] [info] ws_send: Step 495 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.176] [info] ws_send: Step 496 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.177] [info] ws_send: Step 497 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.179] [info] ws_send: Step 498 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.181] [info] ws_send: Step 499 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.183] [info] ws_send: Step 500 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.185] [info] ws_send: Step 501 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.186] [info] ws_send: Step 502 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.188] [info] ws_send: Step 503 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.190] [info] ws_send: Step 504 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.192] [info] ws_send: Step 505 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.194] [info] ws_send: Step 506 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.196] [info] ws_send: Step 507 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.198] [info] ws_send: Step 508 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.199] [info] ws_send: Step 509 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.201] [info] ws_send: Step 510 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.203] [info] ws_send: Step 511 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.205] [info] ws_send: Step 512 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.207] [info] ws_send: Step 513 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.209] [info] ws_send: Step 514 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.210] [info] ws_send: Step 515 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.212] [info] ws_send: Step 516 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.214] [info] ws_send: Step 517 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.216] [info] ws_send: Step 518 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.218] [info] ws_send: Step 519 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.220] [info] ws_send: Step 520 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.221] [info] ws_send: Step 521 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.223] [info] ws_send: Step 522 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.225] [info] ws_send: Step 523 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.227] [info] ws_send: Step 524 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.229] [info] ws_send: Step 525 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.230] [info] ws_send: Step 526 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.232] [info] ws_send: Step 527 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.234] [info] ws_send: Step 528 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.236] [info] ws_send: Step 529 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.238] [info] ws_send: Step 530 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.240] [info] ws_send: Step 531 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.241] [info] ws_send: Step 532 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.243] [info] ws_send: Step 533 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.245] [info] ws_send: Step 534 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.247] [info] ws_send: Step 535 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.249] [info] ws_send: Step 536 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.250] [info] ws_send: Step 537 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.252] [info] ws_send: Step 538 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.254] [info] ws_send: Step 539 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.256] [info] ws_send: Step 540 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.258] [info] ws_send: Step 541 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.260] [info] ws_send: Step 542 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.261] [info] ws_send: Step 543 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.263] [info] ws_send: Step 544 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.265] [info] ws_send: Step 545 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.267] [info] ws_send: Step 546 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.269] [info] ws_send: Step 547 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.271] [info] ws_send: Step 548 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.272] [info] ws_send: Step 549 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.274] [info] ws_send: Step 550 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.276] [info] ws_send: Step 551 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.278] [info] ws_send: Step 552 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.280] [info] ws_send: Step 553 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.282] [info] ws_send: Step 554 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.283] [info] ws_send: Step 555 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.285] [info] ws_send: Step 556 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.287] [info] ws_send: Step 557 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.289] [info] ws_send: Step 558 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.291] [info] ws_send: Step 559 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.293] [info] ws_send: Step 560 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.294] [info] ws_send: Step 561 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.296] [info] ws_send: Step 562 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.298] [info] ws_send: Step 563 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.300] [info] ws_send: Step 564 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.302] [info] ws_send: Step 565 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.304] [info] ws_send: Step 566 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.305] [info] ws_send: Step 567 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.307] [info] ws_send: Step 568 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.309] [info] ws_send: Step 569 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.311] [info] ws_send: Step 570 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.313] [info] ws_send: Step 571 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.315] [info] ws_send: Step 572 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.316] [info] ws_send: Step 573 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.318] [info] ws_send: Step 574 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.320] [info] ws_send: Step 575 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.322] [info] ws_send: Step 576 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.324] [info] ws_send: Step 577 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.326] [info] ws_send: Step 578 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.327] [info] ws_send: Step 579 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.329] [info] ws_send: Step 580 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.331] [info] ws_send: Step 581 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.333] [info] ws_send: Step 582 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.335] [info] ws_send: Step 583 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.337] [info] ws_send: Step 584 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.338] [info] ws_send: Step 585 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.340] [info] ws_send: Step 586 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.342] [info] ws_send: Step 587 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.344] [info] ws_send: Step 588 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.346] [info] ws_send: Step 589 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.347] [info] ws_send: Step 590 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.349] [info] ws_send: Step 591 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.351] [info] ws_send: Step 592 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.353] [info] ws_send: Step 593 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.355] [info] ws_send: Step 594 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.357] [info] ws_send: Step 595 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.359] [info] ws_send: Step 596 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.360] [info] ws_send: Step 597 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.362] [info] ws_send: Step 598 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.364] [info] ws_send: Step 599 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.366] [info] ws_send: Step 600 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.368] [info] ws_send: Step 601 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.370] [info] ws_send: Step 602 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.371] [info] ws_send: Step 603 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.373] [info] ws_send: Step 604 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.375] [info] ws_send: Step 605 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.377] [info] ws_send: Step 606 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.379] [info] ws_send: Step 607 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.381] [info] ws_send: Step 608 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.382] [info] ws_send: Step 609 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.384] [info] ws_send: Step 610 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.386] [info] ws_send: Step 611 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.388] [info] ws_send: Step 612 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.390] [info] ws_send: Step 613 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.392] [info] ws_send: Step 614 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.393] [info] ws_send: Step 615 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.395] [info] ws_send: Step 616 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.397] [info] ws_send: Step 617 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.399] [info] ws_send: Step 618 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.401] [info] ws_send: Step 619 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.403] [info] ws_send: Step 620 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.404] [info] ws_send: Step 621 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.406] [info] ws_send: Step 622 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.408] [info] ws_send: Step 623 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.410] [info] ws_send: Step 624 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.412] [info] ws_send: Step 625 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.414] [info] ws_send: Step 626 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.415] [info] ws_send: Step 627 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.417] [info] ws_send: Step 628 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.419] [info] ws_send: Step 629 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.421] [info] ws_send: Step 630 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.423] [info] ws_send: Step 631 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.425] [info] ws_send: Step 632 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.427] [info] ws_send: Step 633 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.428] [info] ws_send: Step 634 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.430] [info] ws_send: Step 635 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.432] [info] ws_send: Step 636 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.434] [info] ws_send: Step 637 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.436] [info] ws_send: Step 638 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.438] [info] ws_send: Step 639 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.439] [info] ws_send: Step 640 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.441] [info] ws_send: Step 641 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.443] [info] ws_send: Step 642 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.445] [info] ws_send: Step 643 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.447] [info] ws_send: Step 644 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.449] [info] ws_send: Step 645 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.450] [info] ws_send: Step 646 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.452] [info] ws_send: Step 647 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.454] [info] ws_send: Step 648 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.456] [info] ws_send: Step 649 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.458] [info] ws_send: Step 650 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.460] [info] ws_send: Step 651 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.461] [info] ws_send: Step 652 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.463] [info] ws_send: Step 653 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.465] [info] ws_send: Step 654 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.467] [info] ws_send: Step 655 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.469] [info] ws_send: Step 656 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.471] [info] ws_send: Step 657 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.472] [info] ws_send: Step 658 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.474] [info] ws_send: Step 659 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.476] [info] ws_send: Step 660 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.478] [info] ws_send: Step 661 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.480] [info] ws_send: Step 662 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.482] [info] ws_send: Step 663 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.483] [info] ws_send: Step 664 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.485] [info] ws_send: Step 665 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.487] [info] ws_send: Step 666 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.489] [info] ws_send: Step 667 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.491] [info] ws_send: Step 668 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.493] [info] ws_send: Step 669 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.494] [info] ws_send: Step 670 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.496] [info] ws_send: Step 671 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.498] [info] ws_send: Step 672 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.500] [info] ws_send: Step 673 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.502] [info] ws_send: Step 674 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.503] [info] ws_send: Step 675 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.505] [info] ws_send: Step 676 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.507] [info] ws_send: Step 677 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.509] [info] ws_send: Step 678 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.511] [info] ws_send: Step 679 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.513] [info] ws_send: Step 680 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.514] [info] ws_send: Step 681 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.516] [info] ws_send: Step 682 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.518] [info] ws_send: Step 683 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.520] [info] ws_send: Step 684 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.522] [info] ws_send: Step 685 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.524] [info] ws_send: Step 686 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.525] [info] ws_send: Step 687 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.527] [info] ws_send: Step 688 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.529] [info] ws_send: Step 689 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.531] [info] ws_send: Step 690 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.533] [info] ws_send: Step 691 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.535] [info] ws_send: Step 692 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.536] [info] ws_send: Step 693 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.538] [info] ws_send: Step 694 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.540] [info] ws_send: Step 695 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.542] [info] ws_send: Step 696 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.544] [info] ws_send: Step 697 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.545] [info] ws_send: Step 698 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.547] [info] ws_send: Step 699 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.549] [info] ws_send: Step 700 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.551] [info] ws_send: Step 701 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.553] [info] ws_send: Step 702 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.555] [info] ws_send: Step 703 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.556] [info] ws_send: Step 704 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.558] [info] ws_send: Step 705 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.560] [info] ws_send: Step 706 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.562] [info] ws_send: Step 707 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.564] [info] ws_send: Step 708 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.566] [info] ws_send: Step 709 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.567] [info] ws_send: Step 710 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.569] [info] ws_send: Step 711 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.571] [info] ws_send: Step 712 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.573] [info] ws_send: Step 713 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.575] [info] ws_send: Step 714 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.576] [info] ws_send: Step 715 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.578] [info] ws_send: Step 716 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.580] [info] ws_send: Step 717 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.582] [info] ws_send: Step 718 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.584] [info] ws_send: Step 719 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.586] [info] ws_send: Step 720 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.587] [info] ws_send: Step 721 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.589] [info] ws_send: Step 722 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.591] [info] ws_send: Step 723 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.593] [info] ws_send: Step 724 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.595] [info] ws_send: Step 725 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.597] [info] ws_send: Step 726 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.598] [info] ws_send: Step 727 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.600] [info] ws_send: Step 728 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.602] [info] ws_send: Step 729 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.604] [info] ws_send: Step 730 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.606] [info] ws_send: Step 731 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.607] [info] ws_send: Step 732 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.609] [info] ws_send: Step 733 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.611] [info] ws_send: Step 734 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.613] [info] ws_send: Step 735 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.615] [info] ws_send: Step 736 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.617] [info] ws_send: Step 737 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.618] [info] ws_send: Step 738 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.620] [info] ws_send: Step 739 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.622] [info] ws_send: Step 740 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.624] [info] ws_send: Step 741 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.626] [info] ws_send: Step 742 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.628] [info] ws_send: Step 743 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.630] [info] ws_send: Step 744 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.632] [info] ws_send: Step 745 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.633] [info] ws_send: Step 746 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.635] [info] ws_send: Step 747 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.637] [info] ws_send: Step 748 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.639] [info] ws_send: Step 749 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.641] [info] ws_send: Step 750 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.642] [info] ws_send: Step 751 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.644] [info] ws_send: Step 752 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.646] [info] ws_send: Step 753 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.648] [info] ws_send: Step 754 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.650] [info] ws_send: Step 755 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.652] [info] ws_send: Step 756 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.653] [info] ws_send: Step 757 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.655] [info] ws_send: Step 758 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.657] [info] ws_send: Step 759 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.659] [info] ws_send: Step 760 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.661] [info] ws_send: Step 761 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.662] [info] ws_send: Step 762 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.664] [info] ws_send: Step 763 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.666] [info] ws_send: Step 764 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.668] [info] ws_send: Step 765 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.670] [info] ws_send: Step 766 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.673] [info] ws_send: Step 767 out of 768 +15:29:55 send.1 | [2020-01-05 15:29:55.676] [info] ws_send: 0 bytes left to be sent +15:29:55 send.1 | [2020-01-05 15:29:55.689] [info] Sending file through websocket completed in 1801 +15:29:55 send.1 | [2020-01-05 15:29:55.689] [info] ws_send: Send transfer rate: 13 MB/s +15:29:56 send.1 | [2020-01-05 15:29:56.142] [info] ws_send: Waiting for ack... +15:29:57 send.1 | [2020-01-05 15:29:57.399] [info] ws_send: received message (25183026 bytes) +15:29:57 send.1 | [2020-01-05 15:29:57.399] [info] ws_send: Done ! +15:29:59 send.1 | [2020-01-05 15:29:59.233] [info] ws_send: connection closed: code 1000 reason Normal closure +15:29:59 send.1 | +15:29:59 system | send.1 stopped (rc=0) +15:29:59 system | sending SIGTERM to nginx.1 (pid 75372) +15:29:59 system | sending SIGTERM to websocket_server.1 (pid 75374) +15:29:59 system | nginx.1 stopped (rc=0) +15:29:59 system | websocket_server.1 stopped (rc=-15) +``` + +## Sending large files over SSL + +Running inside docker + +``` +$ make docker && make server_ssl +``` + +On the client + +``` +$ make ws_mbedtls && cp build/ws/ws /usr/local/bin/ws && ws send --verify_none wss://localhost:8766 /tmp/big_file +``` diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/broadcast_server.py b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/broadcast_server.py new file mode 100644 index 0000000000..17984c4f0e --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/broadcast_server.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + +# WS server example + +import asyncio +import os +import websockets + + +clients = set() + + +async def echo(websocket, path): + clients.add(websocket) + + try: + while True: + msg = await websocket.recv() + + for ws in clients: + if ws != websocket: + print(f'Sending {len(msg)} bytes to {ws}') + await ws.send(msg) + except websockets.exceptions.ConnectionClosedOK: + print('Client terminating') + clients.remove(websocket) + + +host = os.getenv('BIND_HOST', 'localhost') +print(f'Serving on {host}:8766') + +start_server = websockets.serve(echo, host, 8766, max_size=2 ** 30) + +asyncio.get_event_loop().run_until_complete(start_server) +asyncio.get_event_loop().run_forever() diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/broadcast_server_ssl.py b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/broadcast_server_ssl.py new file mode 100644 index 0000000000..1ad658ff88 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/broadcast_server_ssl.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python + +# WS server example + +import asyncio +import os +import pathlib +import ssl +import websockets + + +clients = set() + + +async def echo(websocket, path): + clients.add(websocket) + + try: + while True: + msg = await websocket.recv() + + for ws in clients: + if ws != websocket: + print(f'Sending {len(msg)} bytes to {ws}') + await ws.send(msg) + except websockets.exceptions.ConnectionClosedOK: + print('Client terminating') + clients.remove(websocket) + + +ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) +ssl_context.load_cert_chain('trusted-server-crt.pem', + 'trusted-server-key.pem') + +host = os.getenv('BIND_HOST', 'localhost') +print(f'Serving on {host}:8766') + +start_server = websockets.serve(echo, host, 8766, max_size=2 ** 30, ssl=ssl_context) + +asyncio.get_event_loop().run_until_complete(start_server) +asyncio.get_event_loop().run_forever() diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/devnull_client.py b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/devnull_client.py new file mode 100644 index 0000000000..b4325fe785 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/devnull_client.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 + +# websocket send client + +import argparse +import asyncio +import websockets + +try: + import uvloop + uvloop.install() +except ImportError: + print('uvloop not available') + pass + +msgCount = 0 + +async def timer(): + global msgCount + + while True: + print(f'Received messages: {msgCount}') + msgCount = 0 + + await asyncio.sleep(1) + + +async def client(url): + global msgCount + + asyncio.ensure_future(timer()) + + async with websockets.connect(url) as ws: + async for message in ws: + msgCount += 1 + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='websocket proxy.') + parser.add_argument('--url', help='Remote websocket url', + default='wss://echo.websocket.org') + args = parser.parse_args() + + asyncio.get_event_loop().run_until_complete(client(args.url)) diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/echo_client.py b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/echo_client.py new file mode 100644 index 0000000000..e4083d67f7 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/echo_client.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 + +# websocket send client + +import argparse +import asyncio +import websockets + + +async def send(url): + async with websockets.connect(url) as ws: + while True: + message = input('> ') + print('Sending message...') + await ws.send(message) + print('Message sent.') + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='websocket proxy.') + parser.add_argument('--url', help='Remote websocket url', + default='wss://echo.websocket.org') + args = parser.parse_args() + + asyncio.get_event_loop().run_until_complete(send(args.url)) diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/echo_server.py b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/echo_server.py new file mode 100644 index 0000000000..e609aa8d1a --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/echo_server.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +# WS server example + +import asyncio +import os +import websockets + + +async def echo(websocket, path): + while True: + msg = await websocket.recv() + # print(f'Received {len(msg)} bytes') + await websocket.send(msg) + +host = os.getenv('BIND_HOST', 'localhost') +print(f'Serving on {host}:8766') + +start_server = websockets.serve(echo, host, 8766, max_size=2 ** 30) + +asyncio.get_event_loop().run_until_complete(start_server) +asyncio.get_event_loop().run_forever() diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/echo_server_interactive.py b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/echo_server_interactive.py new file mode 100644 index 0000000000..78c9cfba0b --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/echo_server_interactive.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +# WS server example + +import asyncio +import websockets + +async def hello(websocket, path): + await websocket.send(f"> Welcome !") + + name = await websocket.recv() + print(f"< {name}") + + greeting = f"Hello {name}!" + + await websocket.send(greeting) + print(f"> {greeting}") + +async def echo(websocket, path): + msg = await websocket.recv() + print(f'Received {len(msg)} bytes') + await websocket.send(msg) + +print('Serving on localhost:8766') +start_server = websockets.serve(echo, 'localhost', 8766) + +asyncio.get_event_loop().run_until_complete(start_server) +asyncio.get_event_loop().run_forever() diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/echo_server_serve_once.py b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/echo_server_serve_once.py new file mode 100644 index 0000000000..22a44cc6e2 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/echo_server_serve_once.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +# WS server example + +import asyncio +import os +import websockets + + +async def echo(websocket, path): + while True: + msg = await websocket.recv() + print(f'Received {len(msg)} bytes') + await websocket.send(msg) + +host = os.getenv('BIND_HOST', 'localhost') +print(f'Serving on {host}:8766') + +start_server = websockets.serve(echo, host, 8766, max_size=2 ** 30) + +asyncio.get_event_loop().run_until_complete(start_server) +asyncio.get_event_loop().run_forever() diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/echo_server_ssl.py b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/echo_server_ssl.py new file mode 100644 index 0000000000..a08fe98f12 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/echo_server_ssl.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python + +# WS server example + +import asyncio +import os +import pathlib +import ssl +import websockets + + +async def echo(websocket, path): + msg = await websocket.recv() + print(f'Received {len(msg)} bytes') + await websocket.send(msg) + +ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) +ssl_context.load_cert_chain('trusted-server-crt.pem', + 'trusted-server-key.pem') + +host = os.getenv('BIND_HOST', 'localhost') +print(f'Serving on {host}:8766') + +start_server = websockets.serve(echo, host, 8766, max_size=2 ** 30, ssl=ssl_context) + +asyncio.get_event_loop().run_until_complete(start_server) +asyncio.get_event_loop().run_forever() diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/empty_file b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/empty_file new file mode 100644 index 0000000000..e69de29bb2 diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/entrypoint.sh b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/entrypoint.sh new file mode 100644 index 0000000000..9e148118b8 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/entrypoint.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +case $MODE in + echo_server) + python /usr/bin/echo_server.py + ;; + echo_server_ssl) + python /usr/bin/echo_server_ssl.py + ;; + broadcast_server) + python /usr/bin/broadcast_server.py + ;; + broadcast_server_ssl) + python /usr/bin/broadcast_server_ssl.py + ;; +esac diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/localhost.pem b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/localhost.pem new file mode 120000 index 0000000000..0373726015 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/localhost.pem @@ -0,0 +1 @@ +trusted-client-crt.pem \ No newline at end of file diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/nginx.conf b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/nginx.conf new file mode 100644 index 0000000000..36042846ab --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/nginx.conf @@ -0,0 +1,36 @@ + +error_log stderr warn; + +daemon off; + +events { + worker_connections 32; +} + +http { + + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + + upstream websocket { + server localhost:8766; + } + + server { + listen 8765 ssl; + + ssl_certificate trusted-client-crt.pem; + ssl_certificate_key trusted-client-key.pem; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers HIGH:!aNULL:!MD5; + + location / { + proxy_pass http://websocket; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } + } +} diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/small_file b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/small_file new file mode 100644 index 0000000000..a4cfdfeff7 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/small_file @@ -0,0 +1 @@ +not much in here diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/trusted-client-crt.pem b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/trusted-client-crt.pem new file mode 100644 index 0000000000..6b0a74b60b --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/trusted-client-crt.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDLDCCAhSgAwIBAgIJALyEpMxNH62gMA0GCSqGSIb3DQEBCwUAMEExFDASBgNV +BAoMC21hY2hpbmV6b25lMRQwEgYDVQQKDAtJWFdlYlNvY2tldDETMBEGA1UEAwwK +dHJ1c3RlZC1jYTAeFw0xOTEyMjQwMDM3MzVaFw0yMDEyMjMwMDM3MzVaMEUxFDAS +BgNVBAoMC21hY2hpbmV6b25lMRQwEgYDVQQKDAtJWFdlYlNvY2tldDEXMBUGA1UE +AwwOdHJ1c3RlZC1jbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDFCipQ6OIJX15n2okHxuSkviuzcHzoYEzPhF6QXzKFbKhuzp4g0mMOXPvDKQE+ ++dycGm6l1yg1pUuNKNxYjDWcSqOIqvDaOv9DkJCCNXpAGbh1CUmGdmp4HvwrzSIn ++3s/enC+zatcnwhrOyJk8k/9VqKlt+vB1++UAQV1eSX7adb/BemoyMguAQ8edAls +IiVSRrHRRyHy98j97jxX5lIdoC1FMv7Tj4suJA7wvTHlq3clpLL8t6dw1DAmBybK +ShUg9SC/T07WJ2cOo8wka+p7S/blh8qZwIy7kTgCI+SYgRfEOA94u9A9mDqp295h +DCghN2UdU3hh1k7SChI/owLpAgMBAAGjIzAhMB8GA1UdEQQYMBaCCWxvY2FsaG9z +dIIJMTI3LjAuMC4xMA0GCSqGSIb3DQEBCwUAA4IBAQAtsbBGLUxABNH5yoRbk0o3 +sGFMVkNDKkCE24BVkUfNyKUxLQWMknw3B4bmhrC8ZQPRk069ERV0ZR6eB2/9EG9s +Pzy4JbMwWrP5c0UIMJRk3w8ev9FXrsKwG6VhIPnvAdbJEis+7eDmYgpvmsbsYRmG +cqJcWvDKffki52Gbr9WgxLpqCGc2XMGr1Y1jU73Y4zmOeNLiU6HRKimNtGjqx/Tx +QoGVTNwki4TQTwQIyJ+HOj0c49IDJ93GbW5aymOT/e1IXDe07e9yg1r80bdFn23X +9bmRagT1/qu8lXfOpQA0foYeSJRSN7gITPmo7G2ogGVr6dZwhAHDYYy2pwW32j7o +-----END CERTIFICATE----- diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/trusted-client-key.pem b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/trusted-client-key.pem new file mode 100644 index 0000000000..a14229d41f --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/trusted-client-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAxQoqUOjiCV9eZ9qJB8bkpL4rs3B86GBMz4RekF8yhWyobs6e +INJjDlz7wykBPvncnBpupdcoNaVLjSjcWIw1nEqjiKrw2jr/Q5CQgjV6QBm4dQlJ +hnZqeB78K80iJ/t7P3pwvs2rXJ8IazsiZPJP/VaipbfrwdfvlAEFdXkl+2nW/wXp +qMjILgEPHnQJbCIlUkax0Uch8vfI/e48V+ZSHaAtRTL+04+LLiQO8L0x5at3JaSy +/LencNQwJgcmykoVIPUgv09O1idnDqPMJGvqe0v25YfKmcCMu5E4AiPkmIEXxDgP +eLvQPZg6qdveYQwoITdlHVN4YdZO0goSP6MC6QIDAQABAoIBAGMCJ58+Vg5FmKdw +vThmLY/GaykgVfNiKFaB+g5rd3Rp0/zR3804SkP2Xx+CpDijzsG12nGEupSyOVN1 ++7qWwX2GV8QduSa/THMD2klDW+mHwxM0Fnj1WayATVApJIeYqyaLfMmziO7ijpVr +Qm4dACqZdOL2lwVxXtYs6TRNKtO4SIzmeVS39hmV2zeGmhUzI4hbirLOWBtbsPpl +qi5wyVkHoEVLnY376TOFc8u5+636yh6G2yqa/zsv9BBXG77DKWl659Fsd4DaUcRG +sk6CTH+I99aE0wrzSUuQmDR/IflxT+DP2ceNrCIc1h1oFzrBKh3fpFR3+D6SSGMn +r9Nk7LECgYEA/8CHrh2LLjqsLbqBoMUXthPwPrVGlK+KGb14+S8Pbfa2hDFWhoif +/FBWAD7GSXedjL3kxFSfmFxsDGPSyqqLRuZNaNs8Ar7vage6FYT6Vfh/8TYOToNr +8AHmhgQCg4luC8VGedCeEDVmUgkdJd/baoY8r3LKXaqsLxyBQN1Hzi0CgYEAxTsQ +jMFwACIdZHJKgUAdEA6PJM0HCS45F4116yqum99S4H10O1VdWK/vKeb13PK//25X +liXhjNHqcVLX08meqs561nKBWhbA72UU3oBAF4RNLHkbZMh1HtZGfBCfJ/Kmq12/ +ZmGCwggUHhwnKD02hIGdffc+0eLTeCQL8HKi+S0CgYEA59+MpAXRHDbByCviPvqy +hrgJBzGfLksAsFmihnluScp2q993jT3tnvrPHiXL7OvwAZxg/seicqbIp2sRwAFj +iQJgiILMI8kskzsyMTSBKtTEWtMhoXlxsQZoFHUqOkutZCqVvPexdwyTGil9LcuJ +yUivWHqAku+ccJItdbup0HkCgYEAswkZzdvucoCFU+AX19o+R4wfzpU7FM9bzhCA +gTgehqojzlqzfwTPlqkmHlBk0Oue9BzS7x5172HCQpqkBsGYAY8rnK0W1JOhEe8d +EZk0FOTpNTy+bC83egWiuA5Sm22+dALGswZDLyUsNeTyeqmOapxKPcWJxfb0ZbO7 +DsrRPAUCgYAzJm79VvEeRtwKhm0AcDSikJgNKojm6T6BIi/9QJyMTYlvGpBEwPBt +iqmqCqXGmUYafFApTUPyzmyDUsLfeHRwylvPn4UtYXPJ1UKGCVY3SWiJQi2CHSvC +gGSIifjzyeSjhw1cqzS2jHfu4lu6p2GBv/fyXLRVS7x6xY7OBinmvg== +-----END RSA PRIVATE KEY----- diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/trusted-server-crt.pem b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/trusted-server-crt.pem new file mode 100644 index 0000000000..54bbaf311f --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/trusted-server-crt.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDLDCCAhSgAwIBAgIJALyEpMxNH62fMA0GCSqGSIb3DQEBCwUAMEExFDASBgNV +BAoMC21hY2hpbmV6b25lMRQwEgYDVQQKDAtJWFdlYlNvY2tldDETMBEGA1UEAwwK +dHJ1c3RlZC1jYTAeFw0xOTEyMjQwMDM3MzVaFw0yMDEyMjMwMDM3MzVaMEUxFDAS +BgNVBAoMC21hY2hpbmV6b25lMRQwEgYDVQQKDAtJWFdlYlNvY2tldDEXMBUGA1UE +AwwOdHJ1c3RlZC1zZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCv0T68TZZ7nab+UWPhssGLrInE0egzWn1AF20RkJv1ePIyU0rQbDxuuP+HQbXD +FzF6vo2j+5p+VxxvUOfko9V6xad3cB4T3AoFrT5sYI8gQX1uby6pjqVX16TK5t+c +i56aNhUXdmcWhuUzlIMIauvueohd+pNj6E6weWqCf8QFD6KYPgK3wWCR4VfWA5QY +RJUhv2aI9HrC9P4Mg0mut8LYURRQvGxOhtbAw76FJ6IgBujpgI5GLHgVK5Q1GlXK +8W7RlNKNmxX+mzK2D6nHixCUGvrFk9nZgZiaHI/h5x0IGXu0sbwlTPjqQ4Axpofw +G1FDi/er4FaGCzU4CKmc7rxRAgMBAAGjIzAhMB8GA1UdEQQYMBaCCWxvY2FsaG9z +dIIJMTI3LjAuMC4xMA0GCSqGSIb3DQEBCwUAA4IBAQBkUB6smmApnBfr2eDwKJew +GQUMUAa7TlyANYlwQ6EjbAH7H6oNf7Sm63Go2Y72JjZPw3OvZl3QcvvS14QxYJ71 +kRdvJ1mhEbIaA2QkdZCuDmcQGLfVEyo0j5q03amQKt9QtSv9MsX1Ok2HqGL17Tf1 +QiUqlkzGCqMIsU20X8QzqwYCGzYZeTFtwLYi75za15Uo/6tE2KwzU7oUhuIebOaS +Sa+s2Y1TjpbWyw9usnuQWQ0k1FJR78F1mKJGghmPBoySBHJdLkLYOMhE1u2shgk5 +N0muMcDRTeHLxm1aBPLHtkRbW3QscEQB6GkT2Dt4U66qNV2CY7Gk0F5xxOrGBC/9 +-----END CERTIFICATE----- diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/trusted-server-key.pem b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/trusted-server-key.pem new file mode 100644 index 0000000000..29ae8bc3bb --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/trusted-server-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAr9E+vE2We52m/lFj4bLBi6yJxNHoM1p9QBdtEZCb9XjyMlNK +0Gw8brj/h0G1wxcxer6No/uaflccb1Dn5KPVesWnd3AeE9wKBa0+bGCPIEF9bm8u +qY6lV9ekyubfnIuemjYVF3ZnFoblM5SDCGrr7nqIXfqTY+hOsHlqgn/EBQ+imD4C +t8FgkeFX1gOUGESVIb9miPR6wvT+DINJrrfC2FEUULxsTobWwMO+hSeiIAbo6YCO +Rix4FSuUNRpVyvFu0ZTSjZsV/psytg+px4sQlBr6xZPZ2YGYmhyP4ecdCBl7tLG8 +JUz46kOAMaaH8BtRQ4v3q+BWhgs1OAipnO68UQIDAQABAoIBAG/bIR2uAyJMT7UX +VQN/tbFGKTRmE2Owm2UOQl7dcMvAkd5AraViZyROYIvN23TuKZWc7AI7DbR5eWa8 +w3vsW+JLI9tSImCiKmIoMUHEQOrVn5aF99r6HOmBEZ/hOLyg+1vDMrIFq1pioimp +v5+4XrgPjvizddgnMQEHjiLOZIiOtin+alixN/W41Ij0jOtRycM5wq3Xr/0RAs5A +ziNeQvWdvDwqa6L9upHZpFfYqP/+KflJPlHLfEkBHZtZQF3uy5tQ1VusfVMO3Xvb +Ljk6RBnD9dKayreD9NVzotr36zYEy/V1oGJcP/8AD1xmDA0/2Kb+bfm+WQHG5wp6 +o09zsZECgYEA5Y3d79Nrfi6InVaZ0r5Y+XXqSZFTsgFnxRseVEbuK4jvrh7eC9jW +pWoaXDh8W6RoT59BPSCKtbQ9mjdECh+aJ6MOeCzCiGSppJboOhC1iVFqFuQLDRO7 +w+2NgkhOvNnJJJYmdTwfokTLmaRUiNqwWAtBm+h7py9e5eXujzqt4+UCgYEAxBKL +OO8CWRb0yGlwKoCwYgGCsVauvbZHaELOAKJUB6H+YhZ+SJqd915u8aYs5nbcMyne +5kuewzd+32WpkykI0Fz4SrGvDETKB5/Yynblp9m69LNdgYoVWgQqQocXVw0nD/nA +KQdFSBZZRExXC/aUAa55txFJitMC4FjgTENgR/0CgYAS/OonxVg15sl8Ika1DPO1 +JtDLZw8CQWWBA1494GQhC8GvqHP7jOMsaZtml3GJ7w6Fz4mI8eEnaJJT6FBjefu5 +XZ57yFALEjCKIcVx0CIECsz4ucJEQaadbU/wP+TrcCRYN2dU+TUwqfohaltnupct +oTi7Gb7otF1oLN3P0S3DFQKBgEnVjdXPszunOGBrzBBFS6ZsWTG8qarJBFTPq1Fz +z17ccrWvMLjYeJnZVr/qyseyhLNDlit02IE82ar4VoYTEr2b9Ofzxy5AjS+X0wRT +B6JQjGVvUcvhGq8+GEfbJT/jtQ0ACIuqsD04JT9h2/mmTg/gCveUK/R6B4BCF5zA +VnZlAoGBANOG5T7KsOH+Hbb//dEmfZYMQmkO/+1Pac9eP4uDFNAVF00M6XHf5LrR +gRp5t46drDtLgxdufN0oUItp8y74EuMa9FHlBQVZRaSqYF4bCgf2PieGApbKWG2n +QhnxYfZqf9S2oVK95EHiJxmtumOFBL7YI9NdzNEeoJExS5Bw6kUn +-----END RSA PRIVATE KEY----- diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/vendor/protocol.py b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/vendor/protocol.py new file mode 100644 index 0000000000..ee849b28f6 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/vendor/protocol.py @@ -0,0 +1,1432 @@ +""" +:mod:`websockets.protocol` handles WebSocket control and data frames. + +See `sections 4 to 8 of RFC 6455`_. + +.. _sections 4 to 8 of RFC 6455: http://tools.ietf.org/html/rfc6455#section-4 + +""" + +import asyncio +import codecs +import collections +import enum +import logging +import random +import struct +import sys +import warnings +from typing import ( + Any, + AsyncIterable, + AsyncIterator, + Awaitable, + Deque, + Dict, + Iterable, + List, + Optional, + Union, + cast, +) + +from .exceptions import ( + ConnectionClosed, + ConnectionClosedError, + ConnectionClosedOK, + InvalidState, + PayloadTooBig, + ProtocolError, +) +from .extensions.base import Extension +from .framing import * +from .handshake import * +from .http import Headers +from .typing import Data + + +__all__ = ["WebSocketCommonProtocol"] + +logger = logging.getLogger(__name__) + + +# A WebSocket connection goes through the following four states, in order: + + +class State(enum.IntEnum): + CONNECTING, OPEN, CLOSING, CLOSED = range(4) + + +# In order to ensure consistency, the code always checks the current value of +# WebSocketCommonProtocol.state before assigning a new value and never yields +# between the check and the assignment. + + +class WebSocketCommonProtocol(asyncio.Protocol): + """ + :class:`~asyncio.Protocol` subclass implementing the data transfer phase. + + Once the WebSocket connection is established, during the data transfer + phase, the protocol is almost symmetrical between the server side and the + client side. :class:`WebSocketCommonProtocol` implements logic that's + shared between servers and clients.. + + Subclasses such as :class:`~websockets.server.WebSocketServerProtocol` and + :class:`~websockets.client.WebSocketClientProtocol` implement the opening + handshake, which is different between servers and clients. + + :class:`WebSocketCommonProtocol` performs four functions: + + * It runs a task that stores incoming data frames in a queue and makes + them available with the :meth:`recv` coroutine. + * It sends outgoing data frames with the :meth:`send` coroutine. + * It deals with control frames automatically. + * It performs the closing handshake. + + :class:`WebSocketCommonProtocol` supports asynchronous iteration:: + + async for message in websocket: + await process(message) + + The iterator yields incoming messages. It exits normally when the + connection is closed with the close code 1000 (OK) or 1001 (going away). + It raises a :exc:`~websockets.exceptions.ConnectionClosedError` exception + when the connection is closed with any other code. + + Once the connection is open, a `Ping frame`_ is sent every + ``ping_interval`` seconds. This serves as a keepalive. It helps keeping + the connection open, especially in the presence of proxies with short + timeouts on inactive connections. Set ``ping_interval`` to ``None`` to + disable this behavior. + + .. _Ping frame: https://tools.ietf.org/html/rfc6455#section-5.5.2 + + If the corresponding `Pong frame`_ isn't received within ``ping_timeout`` + seconds, the connection is considered unusable and is closed with + code 1011. This ensures that the remote endpoint remains responsive. Set + ``ping_timeout`` to ``None`` to disable this behavior. + + .. _Pong frame: https://tools.ietf.org/html/rfc6455#section-5.5.3 + + The ``close_timeout`` parameter defines a maximum wait time in seconds for + completing the closing handshake and terminating the TCP connection. + :meth:`close` completes in at most ``4 * close_timeout`` on the server + side and ``5 * close_timeout`` on the client side. + + ``close_timeout`` needs to be a parameter of the protocol because + ``websockets`` usually calls :meth:`close` implicitly: + + - on the server side, when the connection handler terminates, + - on the client side, when exiting the context manager for the connection. + + To apply a timeout to any other API, wrap it in :func:`~asyncio.wait_for`. + + The ``max_size`` parameter enforces the maximum size for incoming messages + in bytes. The default value is 1 MiB. ``None`` disables the limit. If a + message larger than the maximum size is received, :meth:`recv` will + raise :exc:`~websockets.exceptions.ConnectionClosedError` and the + connection will be closed with code 1009. + + The ``max_queue`` parameter sets the maximum length of the queue that + holds incoming messages. The default value is ``32``. ``None`` disables + the limit. Messages are added to an in-memory queue when they're received; + then :meth:`recv` pops from that queue. In order to prevent excessive + memory consumption when messages are received faster than they can be + processed, the queue must be bounded. If the queue fills up, the protocol + stops processing incoming data until :meth:`recv` is called. In this + situation, various receive buffers (at least in ``asyncio`` and in the OS) + will fill up, then the TCP receive window will shrink, slowing down + transmission to avoid packet loss. + + Since Python can use up to 4 bytes of memory to represent a single + character, each connection may use up to ``4 * max_size * max_queue`` + bytes of memory to store incoming messages. By default, this is 128 MiB. + You may want to lower the limits, depending on your application's + requirements. + + The ``read_limit`` argument sets the high-water limit of the buffer for + incoming bytes. The low-water limit is half the high-water limit. The + default value is 64 KiB, half of asyncio's default (based on the current + implementation of :class:`~asyncio.StreamReader`). + + The ``write_limit`` argument sets the high-water limit of the buffer for + outgoing bytes. The low-water limit is a quarter of the high-water limit. + The default value is 64 KiB, equal to asyncio's default (based on the + current implementation of ``FlowControlMixin``). + + As soon as the HTTP request and response in the opening handshake are + processed: + + * the request path is available in the :attr:`path` attribute; + * the request and response HTTP headers are available in the + :attr:`request_headers` and :attr:`response_headers` attributes, + which are :class:`~websockets.http.Headers` instances. + + If a subprotocol was negotiated, it's available in the :attr:`subprotocol` + attribute. + + Once the connection is closed, the code is available in the + :attr:`close_code` attribute and the reason in :attr:`close_reason`. + + All these attributes must be treated as read-only. + + """ + + # There are only two differences between the client-side and server-side + # behavior: masking the payload and closing the underlying TCP connection. + # Set is_client = True/False and side = "client"/"server" to pick a side. + is_client: bool + side: str = "undefined" + + def __init__( + self, + *, + ping_interval: Optional[float] = 20, + ping_timeout: Optional[float] = 20, + close_timeout: Optional[float] = None, + max_size: Optional[int] = 2 ** 20, + max_queue: Optional[int] = 2 ** 5, + read_limit: int = 2 ** 16, + write_limit: int = 2 ** 16, + loop: Optional[asyncio.AbstractEventLoop] = None, + # The following arguments are kept only for backwards compatibility. + host: Optional[str] = None, + port: Optional[int] = None, + secure: Optional[bool] = None, + legacy_recv: bool = False, + timeout: Optional[float] = None, + ) -> None: + # Backwards compatibility: close_timeout used to be called timeout. + if timeout is None: + timeout = 10 + else: + warnings.warn("rename timeout to close_timeout", DeprecationWarning) + # If both are specified, timeout is ignored. + if close_timeout is None: + close_timeout = timeout + + self.ping_interval = ping_interval + self.ping_timeout = ping_timeout + self.close_timeout = close_timeout + self.max_size = max_size + self.max_queue = max_queue + self.read_limit = read_limit + self.write_limit = write_limit + + if loop is None: + loop = asyncio.get_event_loop() + self.loop = loop + + self._host = host + self._port = port + self._secure = secure + self.legacy_recv = legacy_recv + + # Configure read buffer limits. The high-water limit is defined by + # ``self.read_limit``. The ``limit`` argument controls the line length + # limit and half the buffer limit of :class:`~asyncio.StreamReader`. + # That's why it must be set to half of ``self.read_limit``. + self.reader = asyncio.StreamReader(limit=read_limit // 2, loop=loop) + + # Copied from asyncio.FlowControlMixin + self._paused = False + self._drain_waiter: Optional[asyncio.Future[None]] = None + + self._drain_lock = asyncio.Lock( + loop=loop if sys.version_info[:2] < (3, 8) else None + ) + + # This class implements the data transfer and closing handshake, which + # are shared between the client-side and the server-side. + # Subclasses implement the opening handshake and, on success, execute + # :meth:`connection_open` to change the state to OPEN. + self.state = State.CONNECTING + logger.debug("%s - state = CONNECTING", self.side) + + # HTTP protocol parameters. + self.path: str + self.request_headers: Headers + self.response_headers: Headers + + # WebSocket protocol parameters. + self.extensions: List[Extension] = [] + self.subprotocol: Optional[str] = None + + # The close code and reason are set when receiving a close frame or + # losing the TCP connection. + self.close_code: int + self.close_reason: str + + # Completed when the connection state becomes CLOSED. Translates the + # :meth:`connection_lost` callback to a :class:`~asyncio.Future` + # that can be awaited. (Other :class:`~asyncio.Protocol` callbacks are + # translated by ``self.stream_reader``). + self.connection_lost_waiter: asyncio.Future[None] = loop.create_future() + + # Queue of received messages. + self.messages: Deque[Data] = collections.deque() + self._pop_message_waiter: Optional[asyncio.Future[None]] = None + self._put_message_waiter: Optional[asyncio.Future[None]] = None + + # Protect sending fragmented messages. + self._fragmented_message_waiter: Optional[asyncio.Future[None]] = None + + # Mapping of ping IDs to waiters, in chronological order. + self.pings: Dict[bytes, asyncio.Future[None]] = {} + + # Task running the data transfer. + self.transfer_data_task: asyncio.Task[None] + + # Exception that occurred during data transfer, if any. + self.transfer_data_exc: Optional[BaseException] = None + + # Task sending keepalive pings. + self.keepalive_ping_task: asyncio.Task[None] + + # Task closing the TCP connection. + self.close_connection_task: asyncio.Task[None] + + # Copied from asyncio.FlowControlMixin + async def _drain_helper(self) -> None: # pragma: no cover + if self.connection_lost_waiter.done(): + raise ConnectionResetError("Connection lost") + if not self._paused: + return + waiter = self._drain_waiter + assert waiter is None or waiter.cancelled() + waiter = self.loop.create_future() + self._drain_waiter = waiter + await waiter + + # Copied from asyncio.StreamWriter + async def _drain(self) -> None: # pragma: no cover + if self.reader is not None: + exc = self.reader.exception() + if exc is not None: + raise exc + if self.transport is not None: + if self.transport.is_closing(): + # Yield to the event loop so connection_lost() may be + # called. Without this, _drain_helper() would return + # immediately, and code that calls + # write(...); yield from drain() + # in a loop would never call connection_lost(), so it + # would not see an error when the socket is closed. + await asyncio.sleep( + 0, loop=self.loop if sys.version_info[:2] < (3, 8) else None + ) + await self._drain_helper() + + def connection_open(self) -> None: + """ + Callback when the WebSocket opening handshake completes. + + Enter the OPEN state and start the data transfer phase. + + """ + # 4.1. The WebSocket Connection is Established. + assert self.state is State.CONNECTING + self.state = State.OPEN + logger.debug("%s - state = OPEN", self.side) + # Start the task that receives incoming WebSocket messages. + self.transfer_data_task = self.loop.create_task(self.transfer_data()) + # Start the task that sends pings at regular intervals. + self.keepalive_ping_task = self.loop.create_task(self.keepalive_ping()) + # Start the task that eventually closes the TCP connection. + self.close_connection_task = self.loop.create_task(self.close_connection()) + + @property + def host(self) -> Optional[str]: + alternative = "remote_address" if self.is_client else "local_address" + warnings.warn(f"use {alternative}[0] instead of host", DeprecationWarning) + return self._host + + @property + def port(self) -> Optional[int]: + alternative = "remote_address" if self.is_client else "local_address" + warnings.warn(f"use {alternative}[1] instead of port", DeprecationWarning) + return self._port + + @property + def secure(self) -> Optional[bool]: + warnings.warn(f"don't use secure", DeprecationWarning) + return self._secure + + # Public API + + @property + def local_address(self) -> Any: + """ + Local address of the connection. + + This is a ``(host, port)`` tuple or ``None`` if the connection hasn't + been established yet. + + """ + try: + transport = self.transport + except AttributeError: + return None + else: + return transport.get_extra_info("sockname") + + @property + def remote_address(self) -> Any: + """ + Remote address of the connection. + + This is a ``(host, port)`` tuple or ``None`` if the connection hasn't + been established yet. + + """ + try: + transport = self.transport + except AttributeError: + return None + else: + return transport.get_extra_info("peername") + + @property + def open(self) -> bool: + """ + ``True`` when the connection is usable. + + It may be used to detect disconnections. However, this approach is + discouraged per the EAFP_ principle. + + When ``open`` is ``False``, using the connection raises a + :exc:`~websockets.exceptions.ConnectionClosed` exception. + + .. _EAFP: https://docs.python.org/3/glossary.html#term-eafp + + """ + return self.state is State.OPEN and not self.transfer_data_task.done() + + @property + def closed(self) -> bool: + """ + ``True`` once the connection is closed. + + Be aware that both :attr:`open` and :attr:`closed` are ``False`` during + the opening and closing sequences. + + """ + return self.state is State.CLOSED + + async def wait_closed(self) -> None: + """ + Wait until the connection is closed. + + This is identical to :attr:`closed`, except it can be awaited. + + This can make it easier to handle connection termination, regardless + of its cause, in tasks that interact with the WebSocket connection. + + """ + await asyncio.shield(self.connection_lost_waiter) + + async def __aiter__(self) -> AsyncIterator[Data]: + """ + Iterate on received messages. + + Exit normally when the connection is closed with code 1000 or 1001. + + Raise an exception in other cases. + + """ + try: + while True: + yield await self.recv() + except ConnectionClosedOK: + return + + async def recv(self) -> Data: + """ + Receive the next message. + + Return a :class:`str` for a text frame and :class:`bytes` for a binary + frame. + + When the end of the message stream is reached, :meth:`recv` raises + :exc:`~websockets.exceptions.ConnectionClosed`. Specifically, it + raises :exc:`~websockets.exceptions.ConnectionClosedOK` after a normal + connection closure and + :exc:`~websockets.exceptions.ConnectionClosedError` after a protocol + error or a network failure. + + .. versionchanged:: 3.0 + + :meth:`recv` used to return ``None`` instead. Refer to the + changelog for details. + + Canceling :meth:`recv` is safe. There's no risk of losing the next + message. The next invocation of :meth:`recv` will return it. This + makes it possible to enforce a timeout by wrapping :meth:`recv` in + :func:`~asyncio.wait_for`. + + :raises ~websockets.exceptions.ConnectionClosed: when the + connection is closed + :raises RuntimeError: if two coroutines call :meth:`recv` concurrently + + """ + if self._pop_message_waiter is not None: + raise RuntimeError( + "cannot call recv while another coroutine " + "is already waiting for the next message" + ) + + # Don't await self.ensure_open() here: + # - messages could be available in the queue even if the connection + # is closed; + # - messages could be received before the closing frame even if the + # connection is closing. + + # Wait until there's a message in the queue (if necessary) or the + # connection is closed. + while len(self.messages) <= 0: + pop_message_waiter: asyncio.Future[None] = self.loop.create_future() + self._pop_message_waiter = pop_message_waiter + try: + # If asyncio.wait() is canceled, it doesn't cancel + # pop_message_waiter and self.transfer_data_task. + await asyncio.wait( + [pop_message_waiter, self.transfer_data_task], + loop=self.loop if sys.version_info[:2] < (3, 8) else None, + return_when=asyncio.FIRST_COMPLETED, + ) + finally: + self._pop_message_waiter = None + + # If asyncio.wait(...) exited because self.transfer_data_task + # completed before receiving a new message, raise a suitable + # exception (or return None if legacy_recv is enabled). + if not pop_message_waiter.done(): + if self.legacy_recv: + return None # type: ignore + else: + # Wait until the connection is closed to raise + # ConnectionClosed with the correct code and reason. + await self.ensure_open() + + # Pop a message from the queue. + message = self.messages.popleft() + + # Notify transfer_data(). + if self._put_message_waiter is not None: + self._put_message_waiter.set_result(None) + self._put_message_waiter = None + + return message + + async def send( + self, message: Union[Data, Iterable[Data], AsyncIterable[Data]] + ) -> None: + """ + Send a message. + + A string (:class:`str`) is sent as a `Text frame`_. A bytestring or + bytes-like object (:class:`bytes`, :class:`bytearray`, or + :class:`memoryview`) is sent as a `Binary frame`_. + + .. _Text frame: https://tools.ietf.org/html/rfc6455#section-5.6 + .. _Binary frame: https://tools.ietf.org/html/rfc6455#section-5.6 + + :meth:`send` also accepts an iterable or an asynchronous iterable of + strings, bytestrings, or bytes-like objects. In that case the message + is fragmented. Each item is treated as a message fragment and sent in + its own frame. All items must be of the same type, or else + :meth:`send` will raise a :exc:`TypeError` and the connection will be + closed. + + Canceling :meth:`send` is discouraged. Instead, you should close the + connection with :meth:`close`. Indeed, there only two situations where + :meth:`send` yields control to the event loop: + + 1. The write buffer is full. If you don't want to wait until enough + data is sent, your only alternative is to close the connection. + :meth:`close` will likely time out then abort the TCP connection. + 2. ``message`` is an asynchronous iterator. Stopping in the middle of + a fragmented message will cause a protocol error. Closing the + connection has the same effect. + + :raises TypeError: for unsupported inputs + + """ + await self.ensure_open() + + # While sending a fragmented message, prevent sending other messages + # until all fragments are sent. + while self._fragmented_message_waiter is not None: + await asyncio.shield(self._fragmented_message_waiter) + + # Unfragmented message -- this case must be handled first because + # strings and bytes-like objects are iterable. + + if isinstance(message, (str, bytes, bytearray, memoryview)): + opcode, data = prepare_data(message) + await self.write_frame(True, opcode, data) + + # Fragmented message -- regular iterator. + + elif isinstance(message, Iterable): + + # Work around https://github.com/python/mypy/issues/6227 + message = cast(Iterable[Data], message) + + iter_message = iter(message) + try: + message_chunk = next(iter_message) + except StopIteration: + return + opcode, data = prepare_data(message_chunk) + + self._fragmented_message_waiter = asyncio.Future() + try: + # First fragment. + await self.write_frame(False, opcode, data) + + # Other fragments. + for message_chunk in iter_message: + confirm_opcode, data = prepare_data(message_chunk) + if confirm_opcode != opcode: + raise TypeError("data contains inconsistent types") + await self.write_frame(False, OP_CONT, data) + + # Final fragment. + await self.write_frame(True, OP_CONT, b"") + + except Exception: + # We're half-way through a fragmented message and we can't + # complete it. This makes the connection unusable. + self.fail_connection(1011) + raise + + finally: + self._fragmented_message_waiter.set_result(None) + self._fragmented_message_waiter = None + + # Fragmented message -- asynchronous iterator + + elif isinstance(message, AsyncIterable): + # aiter_message = aiter(message) without aiter + # https://github.com/python/mypy/issues/5738 + aiter_message = type(message).__aiter__(message) # type: ignore + try: + # message_chunk = anext(aiter_message) without anext + # https://github.com/python/mypy/issues/5738 + message_chunk = await type(aiter_message).__anext__( # type: ignore + aiter_message + ) + except StopAsyncIteration: + return + opcode, data = prepare_data(message_chunk) + + self._fragmented_message_waiter = asyncio.Future() + try: + # First fragment. + await self.write_frame(False, opcode, data) + + # Other fragments. + # https://github.com/python/mypy/issues/5738 + async for message_chunk in aiter_message: # type: ignore + confirm_opcode, data = prepare_data(message_chunk) + if confirm_opcode != opcode: + raise TypeError("data contains inconsistent types") + await self.write_frame(False, OP_CONT, data) + + # Final fragment. + await self.write_frame(True, OP_CONT, b"") + + except Exception: + # We're half-way through a fragmented message and we can't + # complete it. This makes the connection unusable. + self.fail_connection(1011) + raise + + finally: + self._fragmented_message_waiter.set_result(None) + self._fragmented_message_waiter = None + + else: + raise TypeError("data must be bytes, str, or iterable") + + async def close(self, code: int = 1000, reason: str = "") -> None: + """ + Perform the closing handshake. + + :meth:`close` waits for the other end to complete the handshake and + for the TCP connection to terminate. As a consequence, there's no need + to await :meth:`wait_closed`; :meth:`close` already does it. + + :meth:`close` is idempotent: it doesn't do anything once the + connection is closed. + + Wrapping :func:`close` in :func:`~asyncio.create_task` is safe, given + that errors during connection termination aren't particularly useful. + + Canceling :meth:`close` is discouraged. If it takes too long, you can + set a shorter ``close_timeout``. If you don't want to wait, let the + Python process exit, then the OS will close the TCP connection. + + :param code: WebSocket close code + :param reason: WebSocket close reason + + """ + try: + await asyncio.wait_for( + self.write_close_frame(serialize_close(code, reason)), + self.close_timeout, + loop=self.loop if sys.version_info[:2] < (3, 8) else None, + ) + except asyncio.TimeoutError: + # If the close frame cannot be sent because the send buffers + # are full, the closing handshake won't complete anyway. + # Fail the connection to shut down faster. + self.fail_connection() + + # If no close frame is received within the timeout, wait_for() cancels + # the data transfer task and raises TimeoutError. + + # If close() is called multiple times concurrently and one of these + # calls hits the timeout, the data transfer task will be cancelled. + # Other calls will receive a CancelledError here. + + try: + # If close() is canceled during the wait, self.transfer_data_task + # is canceled before the timeout elapses. + await asyncio.wait_for( + self.transfer_data_task, + self.close_timeout, + loop=self.loop if sys.version_info[:2] < (3, 8) else None, + ) + except (asyncio.TimeoutError, asyncio.CancelledError): + pass + + # Wait for the close connection task to close the TCP connection. + await asyncio.shield(self.close_connection_task) + + async def ping(self, data: Optional[Data] = None) -> Awaitable[None]: + """ + Send a ping. + + Return a :class:`~asyncio.Future` which will be completed when the + corresponding pong is received and which you may ignore if you don't + want to wait. + + A ping may serve as a keepalive or as a check that the remote endpoint + received all messages up to this point:: + + pong_waiter = await ws.ping() + await pong_waiter # only if you want to wait for the pong + + By default, the ping contains four random bytes. This payload may be + overridden with the optional ``data`` argument which must be a string + (which will be encoded to UTF-8) or a bytes-like object. + + Canceling :meth:`ping` is discouraged. If :meth:`ping` doesn't return + immediately, it means the write buffer is full. If you don't want to + wait, you should close the connection. + + Canceling the :class:`~asyncio.Future` returned by :meth:`ping` has no + effect. + + """ + await self.ensure_open() + + if data is not None: + data = encode_data(data) + + # Protect against duplicates if a payload is explicitly set. + if data in self.pings: + raise ValueError("already waiting for a pong with the same data") + + # Generate a unique random payload otherwise. + while data is None or data in self.pings: + data = struct.pack("!I", random.getrandbits(32)) + + self.pings[data] = self.loop.create_future() + + await self.write_frame(True, OP_PING, data) + + return asyncio.shield(self.pings[data]) + + async def pong(self, data: Data = b"") -> None: + """ + Send a pong. + + An unsolicited pong may serve as a unidirectional heartbeat. + + The payload may be set with the optional ``data`` argument which must + be a string (which will be encoded to UTF-8) or a bytes-like object. + + Canceling :meth:`pong` is discouraged for the same reason as + :meth:`ping`. + + """ + await self.ensure_open() + + data = encode_data(data) + + await self.write_frame(True, OP_PONG, data) + + # Private methods - no guarantees. + + def connection_closed_exc(self) -> ConnectionClosed: + exception: ConnectionClosed + if self.close_code == 1000 or self.close_code == 1001: + exception = ConnectionClosedOK(self.close_code, self.close_reason) + else: + exception = ConnectionClosedError(self.close_code, self.close_reason) + # Chain to the exception that terminated data transfer, if any. + exception.__cause__ = self.transfer_data_exc + return exception + + async def ensure_open(self) -> None: + """ + Check that the WebSocket connection is open. + + Raise :exc:`~websockets.exceptions.ConnectionClosed` if it isn't. + + """ + # Handle cases from most common to least common for performance. + if self.state is State.OPEN: + # If self.transfer_data_task exited without a closing handshake, + # self.close_connection_task may be closing the connection, going + # straight from OPEN to CLOSED. + if self.transfer_data_task.done(): + await asyncio.shield(self.close_connection_task) + raise self.connection_closed_exc() + else: + return + + if self.state is State.CLOSED: + raise self.connection_closed_exc() + + if self.state is State.CLOSING: + # If we started the closing handshake, wait for its completion to + # get the proper close code and reason. self.close_connection_task + # will complete within 4 or 5 * close_timeout after close(). The + # CLOSING state also occurs when failing the connection. In that + # case self.close_connection_task will complete even faster. + await asyncio.shield(self.close_connection_task) + raise self.connection_closed_exc() + + # Control may only reach this point in buggy third-party subclasses. + assert self.state is State.CONNECTING + raise InvalidState("WebSocket connection isn't established yet") + + async def transfer_data(self) -> None: + """ + Read incoming messages and put them in a queue. + + This coroutine runs in a task until the closing handshake is started. + + """ + try: + while True: + message = await self.read_message() + + # Exit the loop when receiving a close frame. + if message is None: + break + + # Wait until there's room in the queue (if necessary). + if self.max_queue is not None: + while len(self.messages) >= self.max_queue: + self._put_message_waiter = self.loop.create_future() + try: + await asyncio.shield(self._put_message_waiter) + finally: + self._put_message_waiter = None + + # Put the message in the queue. + self.messages.append(message) + + # Notify recv(). + if self._pop_message_waiter is not None: + self._pop_message_waiter.set_result(None) + self._pop_message_waiter = None + + except asyncio.CancelledError as exc: + self.transfer_data_exc = exc + # If fail_connection() cancels this task, avoid logging the error + # twice and failing the connection again. + raise + + except ProtocolError as exc: + self.transfer_data_exc = exc + self.fail_connection(1002) + + except (ConnectionError, EOFError) as exc: + # Reading data with self.reader.readexactly may raise: + # - most subclasses of ConnectionError if the TCP connection + # breaks, is reset, or is aborted; + # - IncompleteReadError, a subclass of EOFError, if fewer + # bytes are available than requested. + self.transfer_data_exc = exc + self.fail_connection(1006) + + except UnicodeDecodeError as exc: + self.transfer_data_exc = exc + self.fail_connection(1007) + + except PayloadTooBig as exc: + self.transfer_data_exc = exc + self.fail_connection(1009) + + except Exception as exc: + # This shouldn't happen often because exceptions expected under + # regular circumstances are handled above. If it does, consider + # catching and handling more exceptions. + logger.error("Error in data transfer", exc_info=True) + + self.transfer_data_exc = exc + self.fail_connection(1011) + + async def read_message(self) -> Optional[Data]: + """ + Read a single message from the connection. + + Re-assemble data frames if the message is fragmented. + + Return ``None`` when the closing handshake is started. + + """ + frame = await self.read_data_frame(max_size=self.max_size) + + # A close frame was received. + if frame is None: + return None + + if frame.opcode == OP_TEXT: + text = True + elif frame.opcode == OP_BINARY: + text = False + else: # frame.opcode == OP_CONT + raise ProtocolError("unexpected opcode") + + # Shortcut for the common case - no fragmentation + if frame.fin: + return frame.data.decode("utf-8") if text else frame.data + + # 5.4. Fragmentation + chunks: List[Data] = [] + max_size = self.max_size + if text: + decoder_factory = codecs.getincrementaldecoder("utf-8") + decoder = decoder_factory(errors="strict") + if max_size is None: + + def append(frame: Frame) -> None: + nonlocal chunks + chunks.append(decoder.decode(frame.data, frame.fin)) + + else: + + def append(frame: Frame) -> None: + nonlocal chunks, max_size + chunks.append(decoder.decode(frame.data, frame.fin)) + assert isinstance(max_size, int) + max_size -= len(frame.data) + + else: + if max_size is None: + + def append(frame: Frame) -> None: + nonlocal chunks + chunks.append(frame.data) + + else: + + def append(frame: Frame) -> None: + nonlocal chunks, max_size + chunks.append(frame.data) + assert isinstance(max_size, int) + max_size -= len(frame.data) + + append(frame) + + i = 0 + while not frame.fin: + print(f'fragment {i}') + i += 1 + frame = await self.read_data_frame(max_size=max_size) + if frame is None: + raise ProtocolError("incomplete fragmented message") + if frame.opcode != OP_CONT: + raise ProtocolError("unexpected opcode") + append(frame) + + # mypy cannot figure out that chunks have the proper type. + return ("" if text else b"").join(chunks) # type: ignore + + async def read_data_frame(self, max_size: Optional[int]) -> Optional[Frame]: + """ + Read a single data frame from the connection. + + Process control frames received before the next data frame. + + Return ``None`` if a close frame is encountered before any data frame. + + """ + # 6.2. Receiving Data + while True: + frame = await self.read_frame(max_size) + + # 5.5. Control Frames + if frame.opcode == OP_CLOSE: + # 7.1.5. The WebSocket Connection Close Code + # 7.1.6. The WebSocket Connection Close Reason + self.close_code, self.close_reason = parse_close(frame.data) + try: + # Echo the original data instead of re-serializing it with + # serialize_close() because that fails when the close frame + # is empty and parse_close() synthetizes a 1005 close code. + await self.write_close_frame(frame.data) + except ConnectionClosed: + # It doesn't really matter if the connection was closed + # before we could send back a close frame. + pass + return None + + elif frame.opcode == OP_PING: + # Answer pings. + ping_hex = frame.data.hex() or "[empty]" + logger.debug( + "%s - received ping, sending pong: %s", self.side, ping_hex + ) + await self.pong(frame.data) + + elif frame.opcode == OP_PONG: + # Acknowledge pings on solicited pongs. + if frame.data in self.pings: + logger.debug( + "%s - received solicited pong: %s", + self.side, + frame.data.hex() or "[empty]", + ) + # Acknowledge all pings up to the one matching this pong. + ping_id = None + ping_ids = [] + for ping_id, ping in self.pings.items(): + ping_ids.append(ping_id) + if not ping.done(): + ping.set_result(None) + if ping_id == frame.data: + break + else: # pragma: no cover + assert False, "ping_id is in self.pings" + # Remove acknowledged pings from self.pings. + for ping_id in ping_ids: + del self.pings[ping_id] + ping_ids = ping_ids[:-1] + if ping_ids: + pings_hex = ", ".join( + ping_id.hex() or "[empty]" for ping_id in ping_ids + ) + plural = "s" if len(ping_ids) > 1 else "" + logger.debug( + "%s - acknowledged previous ping%s: %s", + self.side, + plural, + pings_hex, + ) + else: + logger.debug( + "%s - received unsolicited pong: %s", + self.side, + frame.data.hex() or "[empty]", + ) + + # 5.6. Data Frames + else: + return frame + + async def read_frame(self, max_size: Optional[int]) -> Frame: + """ + Read a single frame from the connection. + + """ + frame = await Frame.read( + self.reader.readexactly, + mask=not self.is_client, + max_size=max_size, + extensions=self.extensions, + ) + logger.debug("%s < %r", self.side, frame) + return frame + + async def write_frame( + self, fin: bool, opcode: int, data: bytes, *, _expected_state: int = State.OPEN + ) -> None: + # Defensive assertion for protocol compliance. + if self.state is not _expected_state: # pragma: no cover + raise InvalidState( + f"Cannot write to a WebSocket in the {self.state.name} state" + ) + + frame = Frame(fin, opcode, data) + logger.debug("%s > %r", self.side, frame) + frame.write( + self.transport.write, mask=self.is_client, extensions=self.extensions + ) + + try: + # drain() cannot be called concurrently by multiple coroutines: + # http://bugs.python.org/issue29930. Remove this lock when no + # version of Python where this bugs exists is supported anymore. + async with self._drain_lock: + # Handle flow control automatically. + await self._drain() + except ConnectionError: + # Terminate the connection if the socket died. + self.fail_connection() + # Wait until the connection is closed to raise ConnectionClosed + # with the correct code and reason. + await self.ensure_open() + + async def write_close_frame(self, data: bytes = b"") -> None: + """ + Write a close frame if and only if the connection state is OPEN. + + This dedicated coroutine must be used for writing close frames to + ensure that at most one close frame is sent on a given connection. + + """ + # Test and set the connection state before sending the close frame to + # avoid sending two frames in case of concurrent calls. + if self.state is State.OPEN: + # 7.1.3. The WebSocket Closing Handshake is Started + self.state = State.CLOSING + logger.debug("%s - state = CLOSING", self.side) + + # 7.1.2. Start the WebSocket Closing Handshake + await self.write_frame(True, OP_CLOSE, data, _expected_state=State.CLOSING) + + async def keepalive_ping(self) -> None: + """ + Send a Ping frame and wait for a Pong frame at regular intervals. + + This coroutine exits when the connection terminates and one of the + following happens: + + - :meth:`ping` raises :exc:`ConnectionClosed`, or + - :meth:`close_connection` cancels :attr:`keepalive_ping_task`. + + """ + if self.ping_interval is None: + return + + try: + while True: + await asyncio.sleep( + self.ping_interval, + loop=self.loop if sys.version_info[:2] < (3, 8) else None, + ) + + # ping() raises CancelledError if the connection is closed, + # when close_connection() cancels self.keepalive_ping_task. + + # ping() raises ConnectionClosed if the connection is lost, + # when connection_lost() calls abort_pings(). + + ping_waiter = await self.ping() + + if self.ping_timeout is not None: + try: + await asyncio.wait_for( + ping_waiter, + self.ping_timeout, + loop=self.loop if sys.version_info[:2] < (3, 8) else None, + ) + except asyncio.TimeoutError: + logger.debug("%s ! timed out waiting for pong", self.side) + self.fail_connection(1011) + break + + except asyncio.CancelledError: + raise + + except ConnectionClosed: + pass + + except Exception: + logger.warning("Unexpected exception in keepalive ping task", exc_info=True) + + async def close_connection(self) -> None: + """ + 7.1.1. Close the WebSocket Connection + + When the opening handshake succeeds, :meth:`connection_open` starts + this coroutine in a task. It waits for the data transfer phase to + complete then it closes the TCP connection cleanly. + + When the opening handshake fails, :meth:`fail_connection` does the + same. There's no data transfer phase in that case. + + """ + try: + # Wait for the data transfer phase to complete. + if hasattr(self, "transfer_data_task"): + try: + await self.transfer_data_task + except asyncio.CancelledError: + pass + + # Cancel the keepalive ping task. + if hasattr(self, "keepalive_ping_task"): + self.keepalive_ping_task.cancel() + + # A client should wait for a TCP close from the server. + if self.is_client and hasattr(self, "transfer_data_task"): + if await self.wait_for_connection_lost(): + return + logger.debug("%s ! timed out waiting for TCP close", self.side) + + # Half-close the TCP connection if possible (when there's no TLS). + if self.transport.can_write_eof(): + logger.debug("%s x half-closing TCP connection", self.side) + self.transport.write_eof() + + if await self.wait_for_connection_lost(): + return + logger.debug("%s ! timed out waiting for TCP close", self.side) + + finally: + # The try/finally ensures that the transport never remains open, + # even if this coroutine is canceled (for example). + + # If connection_lost() was called, the TCP connection is closed. + # However, if TLS is enabled, the transport still needs closing. + # Else asyncio complains: ResourceWarning: unclosed transport. + if self.connection_lost_waiter.done() and self.transport.is_closing(): + return + + # Close the TCP connection. Buffers are flushed asynchronously. + logger.debug("%s x closing TCP connection", self.side) + self.transport.close() + + if await self.wait_for_connection_lost(): + return + logger.debug("%s ! timed out waiting for TCP close", self.side) + + # Abort the TCP connection. Buffers are discarded. + logger.debug("%s x aborting TCP connection", self.side) + self.transport.abort() + + # connection_lost() is called quickly after aborting. + await self.wait_for_connection_lost() + + async def wait_for_connection_lost(self) -> bool: + """ + Wait until the TCP connection is closed or ``self.close_timeout`` elapses. + + Return ``True`` if the connection is closed and ``False`` otherwise. + + """ + if not self.connection_lost_waiter.done(): + try: + await asyncio.wait_for( + asyncio.shield(self.connection_lost_waiter), + self.close_timeout, + loop=self.loop if sys.version_info[:2] < (3, 8) else None, + ) + except asyncio.TimeoutError: + pass + # Re-check self.connection_lost_waiter.done() synchronously because + # connection_lost() could run between the moment the timeout occurs + # and the moment this coroutine resumes running. + return self.connection_lost_waiter.done() + + def fail_connection(self, code: int = 1006, reason: str = "") -> None: + """ + 7.1.7. Fail the WebSocket Connection + + This requires: + + 1. Stopping all processing of incoming data, which means cancelling + :attr:`transfer_data_task`. The close code will be 1006 unless a + close frame was received earlier. + + 2. Sending a close frame with an appropriate code if the opening + handshake succeeded and the other side is likely to process it. + + 3. Closing the connection. :meth:`close_connection` takes care of + this once :attr:`transfer_data_task` exits after being canceled. + + (The specification describes these steps in the opposite order.) + + """ + logger.debug( + "%s ! failing %s WebSocket connection with code %d", + self.side, + self.state.name, + code, + ) + + # Cancel transfer_data_task if the opening handshake succeeded. + # cancel() is idempotent and ignored if the task is done already. + if hasattr(self, "transfer_data_task"): + self.transfer_data_task.cancel() + + # Send a close frame when the state is OPEN (a close frame was already + # sent if it's CLOSING), except when failing the connection because of + # an error reading from or writing to the network. + # Don't send a close frame if the connection is broken. + if code != 1006 and self.state is State.OPEN: + + frame_data = serialize_close(code, reason) + + # Write the close frame without draining the write buffer. + + # Keeping fail_connection() synchronous guarantees it can't + # get stuck and simplifies the implementation of the callers. + # Not drainig the write buffer is acceptable in this context. + + # This duplicates a few lines of code from write_close_frame() + # and write_frame(). + + self.state = State.CLOSING + logger.debug("%s - state = CLOSING", self.side) + + frame = Frame(True, OP_CLOSE, frame_data) + logger.debug("%s > %r", self.side, frame) + frame.write( + self.transport.write, mask=self.is_client, extensions=self.extensions + ) + + # Start close_connection_task if the opening handshake didn't succeed. + if not hasattr(self, "close_connection_task"): + self.close_connection_task = self.loop.create_task(self.close_connection()) + + def abort_pings(self) -> None: + """ + Raise ConnectionClosed in pending keepalive pings. + + They'll never receive a pong once the connection is closed. + + """ + assert self.state is State.CLOSED + exc = self.connection_closed_exc() + + for ping in self.pings.values(): + ping.set_exception(exc) + # If the exception is never retrieved, it will be logged when ping + # is garbage-collected. This is confusing for users. + # Given that ping is done (with an exception), canceling it does + # nothing, but it prevents logging the exception. + ping.cancel() + + if self.pings: + pings_hex = ", ".join(ping_id.hex() or "[empty]" for ping_id in self.pings) + plural = "s" if len(self.pings) > 1 else "" + logger.debug( + "%s - aborted pending ping%s: %s", self.side, plural, pings_hex + ) + + # asyncio.Protocol methods + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + """ + Configure write buffer limits. + + The high-water limit is defined by ``self.write_limit``. + + The low-water limit currently defaults to ``self.write_limit // 4`` in + :meth:`~asyncio.WriteTransport.set_write_buffer_limits`, which should + be all right for reasonable use cases of this library. + + This is the earliest point where we can get hold of the transport, + which means it's the best point for configuring it. + + """ + logger.debug("%s - event = connection_made(%s)", self.side, transport) + + transport = cast(asyncio.Transport, transport) + transport.set_write_buffer_limits(self.write_limit) + self.transport = transport + + # Copied from asyncio.StreamReaderProtocol + self.reader.set_transport(transport) + + def connection_lost(self, exc: Optional[Exception]) -> None: + """ + 7.1.4. The WebSocket Connection is Closed. + + """ + logger.debug("%s - event = connection_lost(%s)", self.side, exc) + self.state = State.CLOSED + logger.debug("%s - state = CLOSED", self.side) + if not hasattr(self, "close_code"): + self.close_code = 1006 + if not hasattr(self, "close_reason"): + self.close_reason = "" + logger.debug( + "%s x code = %d, reason = %s", + self.side, + self.close_code, + self.close_reason or "[no reason]", + ) + self.abort_pings() + # If self.connection_lost_waiter isn't pending, that's a bug, because: + # - it's set only here in connection_lost() which is called only once; + # - it must never be canceled. + self.connection_lost_waiter.set_result(None) + + if True: # pragma: no cover + + # Copied from asyncio.StreamReaderProtocol + if self.reader is not None: + if exc is None: + self.reader.feed_eof() + else: + self.reader.set_exception(exc) + + # Copied from asyncio.FlowControlMixin + # Wake up the writer if currently paused. + if not self._paused: + return + waiter = self._drain_waiter + if waiter is None: + return + self._drain_waiter = None + if waiter.done(): + return + if exc is None: + waiter.set_result(None) + else: + waiter.set_exception(exc) + + def pause_writing(self) -> None: # pragma: no cover + assert not self._paused + self._paused = True + + def resume_writing(self) -> None: # pragma: no cover + assert self._paused + self._paused = False + + waiter = self._drain_waiter + if waiter is not None: + self._drain_waiter = None + if not waiter.done(): + waiter.set_result(None) + + def data_received(self, data: bytes) -> None: + logger.debug("%s - event = data_received(<%d bytes>)", self.side, len(data)) + self.reader.feed_data(data) + + def eof_received(self) -> None: + """ + Close the transport after receiving EOF. + + The WebSocket protocol has its own closing handshake: endpoints close + the TCP or TLS connection after sending and receiving a close frame. + + As a consequence, they never need to write after receiving EOF, so + there's no reason to keep the transport open by returning ``True``. + + Besides, that doesn't work on TLS connections. + + """ + logger.debug("%s - event = eof_received()", self.side) + self.reader.feed_eof() diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/ws_proxy.py b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/ws_proxy.py new file mode 100644 index 0000000000..9ec78988f3 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/ws_proxy.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 + +# websocket proxy + +import argparse +import asyncio +import websockets + + +async def hello(websocket, path): + '''Called whenever a new connection is made to the server''' + + url = REMOTE_URL + path + async with websockets.connect(url) as ws: + taskA = asyncio.create_task(clientToServer(ws, websocket)) + taskB = asyncio.create_task(serverToClient(ws, websocket)) + + await taskA + await taskB + + +async def clientToServer(ws, websocket): + async for message in ws: + await websocket.send(message) + + +async def serverToClient(ws, websocket): + async for message in websocket: + await ws.send(message) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='websocket proxy.') + parser.add_argument('--host', help='Host to bind to.', + default='localhost') + parser.add_argument('--port', help='Port to bind to.', + default=8765) + parser.add_argument('--remote_url', help='Remote websocket url', + default='ws://localhost:8767') + args = parser.parse_args() + + REMOTE_URL = args.remote_url + + start_server = websockets.serve(hello, args.host, args.port) + + asyncio.get_event_loop().run_until_complete(start_server) + asyncio.get_event_loop().run_forever() diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/ws_send.py b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/ws_send.py new file mode 100644 index 0000000000..f7da1953dd --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/python/websockets/ws_send.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +# websocket send client + +import argparse +import asyncio +import websockets + + +async def send(url, path): + async with websockets.connect(url, ping_timeout=None, ping_interval=None) as ws: + with open(path, 'rb') as f: + message = f.read() + + print('Sending message...') + await ws.send(message) + print('Message sent.') + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='websocket proxy.') + parser.add_argument('--path', help='Path to the file to send.', + default='small_file') + parser.add_argument('--url', help='Remote websocket url', + default='wss://echo.websocket.org') + args = parser.parse_args() + + asyncio.get_event_loop().run_until_complete(send(args.url, args.path)) diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/ruby/README.md b/extern/IXWebSocket-11.3.2/test/compatibility/ruby/README.md new file mode 100644 index 0000000000..bc0ce6a704 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/ruby/README.md @@ -0,0 +1,6 @@ +``` +export GEM_HOME=$HOME/local/gems +bundle install faye-websocket +``` + +https://stackoverflow.com/questions/486995/ruby-equivalent-of-virtualenv diff --git a/extern/IXWebSocket-11.3.2/test/compatibility/ruby/devnull_client.rb b/extern/IXWebSocket-11.3.2/test/compatibility/ruby/devnull_client.rb new file mode 100644 index 0000000000..1037fe34ff --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/compatibility/ruby/devnull_client.rb @@ -0,0 +1,59 @@ +# +# $ ruby --version +# ruby 2.6.3p62 (2019-04-16 revision 67580) [universal.x86_64-darwin19] +# +# Install a gem locally by setting GEM_HOME +# https://stackoverflow.com/questions/486995/ruby-equivalent-of-virtualenv +# export GEM_HOME=$HOME/local/gems +# bundle install faye-websocket +# +# In a different terminal, start a push server: +# $ ws push_server -q +# +# $ ruby devnull_client.rb +# [:open] +# Connected to server +# messages received per second: 115926 +# messages received per second: 119156 +# messages received per second: 119156 +# messages received per second: 119157 +# messages received per second: 119156 +# messages received per second: 119156 +# messages received per second: 119157 +# messages received per second: 119156 +# messages received per second: 119156 +# messages received per second: 119157 +# messages received per second: 119156 +# messages received per second: 119157 +# messages received per second: 119156 +# ^C[:close, 1006, ""] +# +require 'faye/websocket' +require 'eventmachine' + +EM.run { + ws = Faye::WebSocket::Client.new('ws://127.0.0.1:8008') + + counter = 0 + + EM.add_periodic_timer(1) do + print "messages received per second: #{counter}\n" + counter = 0 # reset counter + end + + ws.on :open do |event| + p [:open] + print "Connected to server\n" + end + + ws.on :message do |event| + # Uncomment the next line to validate that we receive something correct + # p [:message, event.data] + counter += 1 + end + + ws.on :close do |event| + p [:close, event.code, event.reason] + ws = nil + end +} diff --git a/extern/IXWebSocket-11.3.2/test/data/foo.txt b/extern/IXWebSocket-11.3.2/test/data/foo.txt new file mode 100644 index 0000000000..802992c422 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/data/foo.txt @@ -0,0 +1 @@ +Hello world diff --git a/extern/IXWebSocket-11.3.2/test/run.py b/extern/IXWebSocket-11.3.2/test/run.py new file mode 100755 index 0000000000..bb9bad2c0a --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/run.py @@ -0,0 +1,521 @@ +#!/usr/bin/env python2.7 + +from __future__ import print_function + +import os +import sys +import platform +import argparse +import multiprocessing +import tempfile +import time +import datetime +import threading +import subprocess +import re +import xml.etree.ElementTree as ET +from xml.dom import minidom + +hasClick = True +try: + import click +except ImportError: + hasClick = False + +BUILD_TYPE = 'Debug' +XML_OUTPUT_FILE = 'ixwebsocket_unittest.xml' +TEST_EXE_PATH = None + +class Command(object): + """Run system commands with timeout + + From http://www.bo-yang.net/2016/12/01/python-run-command-with-timeout + Python3 might have a builtin way to do that. + """ + def __init__(self, cmd): + self.cmd = cmd + self.process = None + + def run_command(self): + self.process = subprocess.Popen(self.cmd, shell=True) + self.process.communicate() + + def run(self, timeout=None): + '''5 minutes default timeout''' + + if timeout is None: + timeout = 5 * 60 + + thread = threading.Thread(target=self.run_command, args=()) + thread.start() + thread.join(timeout) + + if thread.is_alive(): + print('Command timeout, kill it: ' + self.cmd) + self.process.terminate() + thread.join() + return False, 255 + else: + return True, self.process.returncode + + +def runCommand(cmd, abortOnFailure=True, timeout=None): + '''Small wrapper to run a command and make sure it succeed''' + + if timeout is None: + timeout = 30 * 60 # 30 minute default timeout + + print('\nRunning', cmd) + command = Command(cmd) + succeed, ret = command.run(timeout) + + if not succeed or ret != 0: + msg = 'cmd {}\nfailed with error code {}'.format(cmd, ret) + print(msg) + if abortOnFailure: + sys.exit(-1) + + +def runCMake(sanitizer, buildDir): + '''Generate a makefile from CMake. + We do an out of dir build, so that cleaning up is easy + (remove build sub-folder). + ''' + + sanitizersFlags = { + 'asan': '-DSANITIZE_ADDRESS=On', + 'ubsan': '-DSANITIZE_UNDEFINED=On', + 'tsan': '-DSANITIZE_THREAD=On', + 'none': '' + } + sanitizerFlag = sanitizersFlags.get(sanitizer, '') + + # CMake installed via Self Service ends up here. + cmakeExecutable = '/Applications/CMake.app/Contents/bin/cmake' + if not os.path.exists(cmakeExecutable): + cmakeExecutable = 'cmake' + + if platform.system() == 'Windows': + #generator = '"NMake Makefiles"' + #generator = '"Visual Studio 16 2019"' + generator = '"Visual Studio 15 2017"' + USE_VENDORED_THIRD_PARTY = 'ON' + else: + generator = '"Unix Makefiles"' + USE_VENDORED_THIRD_PARTY = 'ON' + + CMAKE_BUILD_TYPE = BUILD_TYPE + + fmt = '{cmakeExecutable} -H. \ + {sanitizerFlag} \ + -B"{buildDir}" \ + -DCMAKE_BUILD_TYPE={CMAKE_BUILD_TYPE} \ + -DUSE_TLS=1 \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ + -DUSE_VENDORED_THIRD_PARTY={USE_VENDORED_THIRD_PARTY} \ + -G{generator}' + + cmakeCmd = fmt.format(**locals()) + runCommand(cmakeCmd) + + +def runTest(args, buildDir, xmlOutput, testRunName): + '''Execute the unittest. + ''' + if args is None: + args = '' + + testCommand = '{} -o {} -n "{}" -r junit "{}"'.format(TEST_EXE_PATH, xmlOutput, testRunName, args) + + runCommand(testCommand, + abortOnFailure=False) + + +def validateTestSuite(xmlOutput): + ''' + Parse the output XML file to validate that all tests passed. + + Assume that the XML file contains only one testsuite. + (which is true when generate by catch2) + ''' + tree = ET.parse(xmlOutput) + root = tree.getroot() + testSuite = root[0] + testSuiteAttributes = testSuite.attrib + + tests = testSuiteAttributes['tests'] + + success = True + + for testcase in testSuite: + if testcase.tag != 'testcase': + continue + + testName = testcase.attrib['name'] + systemOutput = None + + for child in testcase: + if child.tag == 'system-out': + systemOutput = child.text + + if child.tag == 'failure': + success = False + + print("Testcase '{}' failed".format(testName)) + print(' ', systemOutput) + + return success, tests + + +def log(msg, color): + if hasClick: + click.secho(msg, fg=color) + else: + print(msg) + + +def isSuccessFullRun(output): + '''When being run from lldb, we cannot capture the exit code + so we have to parse the output which is produced in a + consistent way. Whenever we'll be on a recent enough version of lldb we + won't have to do this. + ''' + pid = None + matchingPids = False + exitCode = -1 + + # 'Process 279 exited with status = 1 (0x00000001) ', + exitPattern = re.compile('^Process (?P[0-9]+) exited with status = (?P[0-9]+)') + + # "Process 99232 launched: '/Users/bse... + launchedPattern = re.compile('^Process (?P[0-9]+) launched: ') + + for line in output: + match = exitPattern.match(line) + if match: + exitCode = int(match.group('exitCode')) + pid = match.group('pid') + + match = launchedPattern.match(line) + if match: + matchingPids = (pid == match.group('pid')) + + return exitCode == 0 and matchingPids + + +def testLLDBOutput(): + failedOutputWithCrashLines = [ + ' frame #15: 0x00007fff73f4d305 libsystem_pthread.dylib`_pthread_body + 126', + ' frame #16: 0x00007fff73f5026f libsystem_pthread.dylib`_pthread_start + 70', + ' frame #17: 0x00007fff73f4c415 libsystem_pthread.dylib`thread_start + 13', + '(lldb) quit 1' + ] + + failedOutputWithFailedUnittest = [ + '===============================================================================', + 'test cases: 1 | 0 passed | 1 failed', 'assertions: 15 | 14 passed | 1 failed', + '', + 'Process 279 exited with status = 1 (0x00000001) ', + '', + "Process 279 launched: '/Users/bsergeant/src/foss/ixwebsocket/test/build/Darwin/ixwebsocket_unittest' (x86_64)" + ] + + successLines = [ + '...', + '...', + 'All tests passed (16 assertions in 1 test case)', + '', + 'Process 99232 exited with status = 0 (0x00000000) ', + '', + "Process 99232 launched: '/Users/bsergeant/src/foss/ixwebsocket/test/build/Darwin/ixwebsocket_unittest' (x86_64)" + ] + + assert not isSuccessFullRun(failedOutputWithCrashLines) + assert not isSuccessFullRun(failedOutputWithFailedUnittest) + assert isSuccessFullRun(successLines) + + +def executeJob(job): + '''Execute a unittest and capture info about it (runtime, success, etc...)''' + + start = time.time() + + sys.stderr.write('.') + # print('Executing ' + job['cmd'] + '...') + + # 2 minutes of timeout for a single test + timeout = 2 * 60 + command = Command(job['cmd']) + timedout, ret = command.run(timeout) + + job['exit_code'] = ret + job['success'] = ret == 0 + job['runtime'] = time.time() - start + + # Record unittest console output + job['output'] = '' + path = job['output_path'] + + if os.path.exists(path): + with open(path) as f: + output = f.read() + job['output'] = output + + outputLines = output.splitlines() + + if job['use_lldb']: + job['success'] = isSuccessFullRun(outputLines) + + # Cleanup tmp file now that its content was read + os.unlink(path) + + return job + + +def executeJobs(jobs, cpuCount): + '''Execute a list of job concurrently on multiple CPU/cores''' + + print('Using {} cores to execute the unittest'.format(cpuCount)) + + pool = multiprocessing.Pool(cpuCount) + results = pool.map(executeJob, jobs) + pool.close() + pool.join() + + return results + + +def computeAllTestNames(buildDir): + '''Compute all test case names, by executing the unittest in a custom mode''' + + cmd = '"{}" --list-test-names-only'.format(TEST_EXE_PATH) + names = os.popen(cmd).read().splitlines() + names.sort() # Sort test names for execution determinism + return names + + +def prettyPrintXML(root): + '''Pretty print an XML file. Default writer write it on a single line + which makes it hard for human to inspect.''' + + serializedXml = ET.tostring(root, encoding='utf-8') + reparsed = minidom.parseString(serializedXml) + prettyPrinted = reparsed.toprettyxml(indent=" ") + return prettyPrinted + + +def generateXmlOutput(results, xmlOutput, testRunName, runTime): + '''Generate a junit compatible XML file + + We prefer doing this ourself instead of letting Catch2 do it. + When the test is crashing (as has happened on Jenkins), an invalid file + with no trailer can be created which trigger an XML reading error in validateTestSuite. + + Something like that: + ``` + + + ``` + ''' + + root = ET.Element('testsuites') + testSuite = ET.Element('testsuite', { + 'name': testRunName, + 'tests': str(len(results)), + 'failures': str(sum(1 for result in results if not result['success'])), + 'time': str(runTime), + 'timestamp': datetime.datetime.utcnow().isoformat(), + }) + root.append(testSuite) + + for result in results: + testCase = ET.Element('testcase', { + 'name': result['name'], + 'time': str(result['runtime']) + }) + + systemOut = ET.Element('system-out') + systemOut.text = result['output'].decode('utf-8', 'ignore') + testCase.append(systemOut) + + if not result['success']: + failure = ET.Element('failure') + testCase.append(failure) + + testSuite.append(testCase) + + with open(xmlOutput, 'w') as f: + content = prettyPrintXML(root) + f.write(content.encode('utf-8')) + + +def run(testName, buildDir, sanitizer, xmlOutput, + testRunName, buildOnly, useLLDB, cpuCount, runOnly): + '''Main driver. Run cmake, compiles, execute and validate the testsuite.''' + + # gen build files with CMake + if not runOnly: + runCMake(sanitizer, buildDir) + + if platform.system() == 'Linux': + # build with make -j + runCommand('make -C {} -j 2'.format(buildDir)) + elif platform.system() == 'Darwin': + # build with make + runCommand('make -C {} -j 8'.format(buildDir)) + else: + # build with cmake on recent + runCommand('cmake --build --parallel {}'.format(buildDir)) + + if buildOnly: + return + + # A specific test case can be provided on the command line + if testName: + testNames = [testName] + else: + # Default case + testNames = computeAllTestNames(buildDir) + + # This should be empty. It is useful to have a blacklist during transitions + # We could add something for asan as well. + blackLists = { + 'ubsan': [] + } + blackList = blackLists.get(sanitizer, []) + + # Run through LLDB to capture crashes + lldb = '' + if useLLDB: + lldb = "lldb --batch -o 'run' -k 'thread backtrace all' -k 'quit 1'" + + # Jobs is a list of python dicts + jobs = [] + + for testName in testNames: + outputPath = tempfile.mktemp(suffix=testName + '.log') + + if testName in blackList: + log('Skipping blacklisted test {}'.format(testName), 'yellow') + continue + + # testName can contains spaces, so we enclose them in double quotes + cmd = '{} "{}" "{}" > "{}" 2>&1'.format(lldb, TEST_EXE_PATH, testName, outputPath) + + jobs.append({ + 'name': testName, + 'cmd': cmd, + 'output_path': outputPath, + 'use_lldb': useLLDB + }) + + start = time.time() + results = executeJobs(jobs, cpuCount) + runTime = time.time() - start + generateXmlOutput(results, xmlOutput, testRunName, runTime) + + # Validate and report results + print('\nParsing junit test result file: {}'.format(xmlOutput)) + log('## Results', 'blue') + success, tests = validateTestSuite(xmlOutput) + + if success: + label = 'tests' if int(tests) > 1 else 'test' + msg = 'All test passed (#{} {})'.format(tests, label) + color = 'green' + else: + msg = 'unittest failed' + color = 'red' + + log(msg, color) + log('Execution time: %.2fs' % (runTime), 'blue') + sys.exit(0 if success else 1) + + +def main(): + root = os.path.dirname(os.path.realpath(__file__)) + os.chdir(root) + + buildDir = os.path.join(root, 'build', platform.system()) + if not os.path.exists(buildDir): + os.makedirs(buildDir) + + parser = argparse.ArgumentParser(description='Build and Run the engine unittest') + + sanitizers = ['tsan', 'asan', 'ubsan', 'none'] + + parser.add_argument('--sanitizer', choices=sanitizers, + help='Run a clang sanitizer.') + parser.add_argument('--test', '-t', help='Test name.') + parser.add_argument('--list', '-l', action='store_true', + help='Print test names and exit.') + parser.add_argument('--no_sanitizer', action='store_true', + help='Do not execute a clang sanitizer.') + parser.add_argument('--validate', action='store_true', + help='Validate XML output.') + parser.add_argument('--build_only', '-b', action='store_true', + help='Stop after building. Do not run the unittest.') + parser.add_argument('--run_only', '-r', action='store_true', + help='Only run the test, do not build anything.') + parser.add_argument('--output', '-o', help='Output XML file.') + parser.add_argument('--lldb', action='store_true', + help='Run the test through lldb.') + parser.add_argument('--run_name', '-n', + help='Name of the test run.') + parser.add_argument('--cpu_count', '-j', type=int, default=multiprocessing.cpu_count(), + help='Number of cpus to use for running the tests.') + + args = parser.parse_args() + + # Windows does not play nice with multiple files opened by different processes + # "The process cannot access the file because it is being used by another process" + if platform.system() == 'Windows': + args.cpu_count = 1 + + # Default sanitizer is tsan + sanitizer = args.sanitizer + + if args.no_sanitizer: + sanitizer = 'none' + elif args.sanitizer is None: + sanitizer = 'tsan' + + # Sanitizers display lots of strange errors on Linux on CI, + # which looks like false positives + if platform.system() != 'Darwin': + sanitizer = 'none' + + defaultRunName = 'ixengine_{}_{}'.format(platform.system(), sanitizer) + + xmlOutput = args.output or XML_OUTPUT_FILE + testRunName = args.run_name or os.getenv('IXENGINE_TEST_RUN_NAME') or defaultRunName + + global TEST_EXE_PATH + + if platform.system() == 'Windows': + TEST_EXE_PATH = os.path.join(buildDir, BUILD_TYPE, 'ixwebsocket_unittest.exe') + else: + TEST_EXE_PATH = '../build/test/ixwebsocket_unittest' + + if args.list: + # catch2 exit with a different error code when requesting the list of files + try: + runTest('--list-test-names-only', buildDir, xmlOutput, testRunName) + except AssertionError: + pass + return + + if args.validate: + validateTestSuite(xmlOutput) + return + + if platform.system() != 'Darwin' and args.lldb: + print('LLDB is only supported on Apple at this point') + args.lldb = False + + return run(args.test, buildDir, sanitizer, xmlOutput, + testRunName, args.build_only, args.lldb, args.cpu_count, args.run_only) + + +if __name__ == '__main__': + main() diff --git a/extern/IXWebSocket-11.3.2/test/run.sh b/extern/IXWebSocket-11.3.2/test/run.sh new file mode 100644 index 0000000000..6a27327986 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/run.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +mkdir build +cd build +cmake -DCMAKE_BUILD_TYPE=Debug .. || exit 1 +make || exit 1 + +./ixwebsocket_unittest ${TEST} diff --git a/extern/IXWebSocket-11.3.2/test/test_runner.cpp b/extern/IXWebSocket-11.3.2/test/test_runner.cpp new file mode 100644 index 0000000000..5c000e1eb3 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/test/test_runner.cpp @@ -0,0 +1,29 @@ +/* + * test_runner.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone. All rights reserved. + */ + +#define CATCH_CONFIG_RUNNER +#include "catch.hpp" +#include +#include + +#ifndef _WIN32 +#include +#endif + +int main(int argc, char* argv[]) +{ + ix::initNetSystem(); + +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); +#endif + spdlog::set_level(spdlog::level::debug); + + int result = Catch::Session().run(argc, argv); + + ix::uninitNetSystem(); + return result; +} diff --git a/extern/IXWebSocket-11.3.2/third_party/.clang-format b/extern/IXWebSocket-11.3.2/third_party/.clang-format new file mode 100644 index 0000000000..9d159247d5 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/third_party/.clang-format @@ -0,0 +1,2 @@ +DisableFormat: true +SortIncludes: false diff --git a/extern/IXWebSocket-11.3.2/third_party/README.md b/extern/IXWebSocket-11.3.2/third_party/README.md new file mode 100644 index 0000000000..d2a91157c5 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/third_party/README.md @@ -0,0 +1,7 @@ +# Note + +Except *zlib* and *mbedtls* on Windows, all dependencies here are for the ws command line tools, not for the IXWebSockets library which is standalone. + +## MbedTLS + +A small CMakeLists.txt fix had to be done so that the library can be included with "include_directory" in the top level CMakeLists.txt file. See https://github.com/ARMmbed/mbedtls/issues/2609 diff --git a/extern/IXWebSocket-11.3.2/third_party/cli11/CLI11.hpp b/extern/IXWebSocket-11.3.2/third_party/cli11/CLI11.hpp new file mode 100644 index 0000000000..ce1d06e3f2 --- /dev/null +++ b/extern/IXWebSocket-11.3.2/third_party/cli11/CLI11.hpp @@ -0,0 +1,8938 @@ +// CLI11: Version 2.0.0 +// Originally designed by Henry Schreiner +// https://github.com/CLIUtils/CLI11 +// +// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts +// from: v2.0.0 (added include gaurd) +// +// CLI11 2.0.0 Copyright (c) 2017-2020 University of Cincinnati, developed by Henry +// Schreiner under NSF AWARD 1414736. All rights reserved. +// +// Redistribution and use in source and binary forms of CLI11, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. 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. +// 3. Neither the name of the copyright holder 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. + +#pragma once + +// Standard combined includes: +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define CLI11_VERSION_MAJOR 2 +#define CLI11_VERSION_MINOR 0 +#define CLI11_VERSION_PATCH 0 +#define CLI11_VERSION "2.0.0" + + + + +// The following version macro is very similar to the one in pybind11 +#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) +#if __cplusplus >= 201402L +#define CLI11_CPP14 +#if __cplusplus >= 201703L +#define CLI11_CPP17 +#if __cplusplus > 201703L +#define CLI11_CPP20 +#endif +#endif +#endif +#elif defined(_MSC_VER) && __cplusplus == 199711L +// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented) +// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer +#if _MSVC_LANG >= 201402L +#define CLI11_CPP14 +#if _MSVC_LANG > 201402L && _MSC_VER >= 1910 +#define CLI11_CPP17 +#if __MSVC_LANG > 201703L && _MSC_VER >= 1910 +#define CLI11_CPP20 +#endif +#endif +#endif +#endif + +#if defined(CLI11_CPP14) +#define CLI11_DEPRECATED(reason) [[deprecated(reason)]] +#elif defined(_MSC_VER) +#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) +#else +#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) +#endif + + + + +// C standard library +// Only needed for existence checking +#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM +#if __has_include() +// Filesystem cannot be used if targeting macOS < 10.15 +#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 +#define CLI11_HAS_FILESYSTEM 0 +#else +#include +#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703 +#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9 +#define CLI11_HAS_FILESYSTEM 1 +#elif defined(__GLIBCXX__) +// if we are using gcc and Version <9 default to no filesystem +#define CLI11_HAS_FILESYSTEM 0 +#else +#define CLI11_HAS_FILESYSTEM 1 +#endif +#else +#define CLI11_HAS_FILESYSTEM 0 +#endif +#endif +#endif +#endif + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include // NOLINT(build/include) +#else +#include +#include +#endif + + + +namespace CLI { + + +/// Include the items in this namespace to get free conversion of enums to/from streams. +/// (This is available inside CLI as well, so CLI11 will use this without a using statement). +namespace enums { + +/// output streaming for enumerations +template ::value>::type> +std::ostream &operator<<(std::ostream &in, const T &item) { + // make sure this is out of the detail namespace otherwise it won't be found when needed + return in << static_cast::type>(item); +} + +} // namespace enums + +/// Export to CLI namespace +using enums::operator<<; + +namespace detail { +/// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not +/// produce overflow for some expected uses +constexpr int expected_max_vector_size{1 << 29}; +// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c +/// Split a string by a delim +inline std::vector split(const std::string &s, char delim) { + std::vector elems; + // Check to see if empty string, give consistent result + if(s.empty()) { + elems.emplace_back(); + } else { + std::stringstream ss; + ss.str(s); + std::string item; + while(std::getline(ss, item, delim)) { + elems.push_back(item); + } + } + return elems; +} + +/// Simple function to join a string +template std::string join(const T &v, std::string delim = ",") { + std::ostringstream s; + auto beg = std::begin(v); + auto end = std::end(v); + if(beg != end) + s << *beg++; + while(beg != end) { + s << delim << *beg++; + } + return s.str(); +} + +/// Simple function to join a string from processed elements +template ::value>::type> +std::string join(const T &v, Callable func, std::string delim = ",") { + std::ostringstream s; + auto beg = std::begin(v); + auto end = std::end(v); + auto loc = s.tellp(); + while(beg != end) { + auto nloc = s.tellp(); + if(nloc > loc) { + s << delim; + loc = nloc; + } + s << func(*beg++); + } + return s.str(); +} + +/// Join a string in reverse order +template std::string rjoin(const T &v, std::string delim = ",") { + std::ostringstream s; + for(std::size_t start = 0; start < v.size(); start++) { + if(start > 0) + s << delim; + s << v[v.size() - start - 1]; + } + return s.str(); +} + +// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string + +/// Trim whitespace from left of string +inline std::string <rim(std::string &str) { + auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(str.begin(), it); + return str; +} + +/// Trim anything from left of string +inline std::string <rim(std::string &str, const std::string &filter) { + auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(str.begin(), it); + return str; +} + +/// Trim whitespace from right of string +inline std::string &rtrim(std::string &str) { + auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(it.base(), str.end()); + return str; +} + +/// Trim anything from right of string +inline std::string &rtrim(std::string &str, const std::string &filter) { + auto it = + std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(it.base(), str.end()); + return str; +} + +/// Trim whitespace from string +inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); } + +/// Trim anything from string +inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); } + +/// Make a copy of the string and then trim it +inline std::string trim_copy(const std::string &str) { + std::string s = str; + return trim(s); +} + +/// remove quotes at the front and back of a string either '"' or '\'' +inline std::string &remove_quotes(std::string &str) { + if(str.length() > 1 && (str.front() == '"' || str.front() == '\'')) { + if(str.front() == str.back()) { + str.pop_back(); + str.erase(str.begin(), str.begin() + 1); + } + } + return str; +} + +/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered) +inline std::string trim_copy(const std::string &str, const std::string &filter) { + std::string s = str; + return trim(s, filter); +} +/// Print a two part "help" string +inline std::ostream &format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid) { + name = " " + name; + out << std::setw(static_cast(wid)) << std::left << name; + if(!description.empty()) { + if(name.length() >= wid) + out << "\n" << std::setw(static_cast(wid)) << ""; + for(const char c : description) { + out.put(c); + if(c == '\n') { + out << std::setw(static_cast(wid)) << ""; + } + } + } + out << "\n"; + return out; +} + +/// Print subcommand aliases +inline std::ostream &format_aliases(std::ostream &out, const std::vector &aliases, std::size_t wid) { + if(!aliases.empty()) { + out << std::setw(static_cast(wid)) << " aliases: "; + bool front = true; + for(const auto &alias : aliases) { + if(!front) { + out << ", "; + } else { + front = false; + } + out << alias; + } + out << "\n"; + } + return out; +} + +/// Verify the first character of an option +template bool valid_first_char(T c) { + return std::isalnum(c, std::locale()) || c == '_' || c == '?' || c == '@'; +} + +/// Verify following characters of an option +template bool valid_later_char(T c) { return valid_first_char(c) || c == '.' || c == '-'; } + +/// Verify an option name +inline bool valid_name_string(const std::string &str) { + if(str.empty() || !valid_first_char(str[0])) + return false; + for(auto c : str.substr(1)) + if(!valid_later_char(c)) + return false; + return true; +} + +/// check if a string is a container segment separator (empty or "%%") +inline bool is_separator(const std::string &str) { + static const std::string sep("%%"); + return (str.empty() || str == sep); +} + +/// Verify that str consists of letters only +inline bool isalpha(const std::string &str) { + return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); }); +} + +/// Return a lower case version of a string +inline std::string to_lower(std::string str) { + std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) { + return std::tolower(x, std::locale()); + }); + return str; +} + +/// remove underscores from a string +inline std::string remove_underscore(std::string str) { + str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str)); + return str; +} + +/// Find and replace a substring with another substring +inline std::string find_and_replace(std::string str, std::string from, std::string to) { + + std::size_t start_pos = 0; + + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + + return str; +} + +/// check if the flag definitions has possible false flags +inline bool has_default_flag_values(const std::string &flags) { + return (flags.find_first_of("{!") != std::string::npos); +} + +inline void remove_default_flag_values(std::string &flags) { + auto loc = flags.find_first_of('{'); + while(loc != std::string::npos) { + auto finish = flags.find_first_of("},", loc + 1); + if((finish != std::string::npos) && (flags[finish] == '}')) { + flags.erase(flags.begin() + static_cast(loc), + flags.begin() + static_cast(finish) + 1); + } + loc = flags.find_first_of('{', loc + 1); + } + flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end()); +} + +/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores +inline std::ptrdiff_t find_member(std::string name, + const std::vector names, + bool ignore_case = false, + bool ignore_underscore = false) { + auto it = std::end(names); + if(ignore_case) { + if(ignore_underscore) { + name = detail::to_lower(detail::remove_underscore(name)); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::to_lower(detail::remove_underscore(local_name)) == name; + }); + } else { + name = detail::to_lower(name); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::to_lower(local_name) == name; + }); + } + + } else if(ignore_underscore) { + name = detail::remove_underscore(name); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::remove_underscore(local_name) == name; + }); + } else { + it = std::find(std::begin(names), std::end(names), name); + } + + return (it != std::end(names)) ? (it - std::begin(names)) : (-1); +} + +/// Find a trigger string and call a modify callable function that takes the current string and starting position of the +/// trigger and returns the position in the string to search for the next trigger string +template inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) { + std::size_t start_pos = 0; + while((start_pos = str.find(trigger, start_pos)) != std::string::npos) { + start_pos = modify(str, start_pos); + } + return str; +} + +/// Split a string '"one two" "three"' into 'one two', 'three' +/// Quote characters can be ` ' or " +inline std::vector split_up(std::string str, char delimiter = '\0') { + + const std::string delims("\'\"`"); + auto find_ws = [delimiter](char ch) { + return (delimiter == '\0') ? (std::isspace(ch, std::locale()) != 0) : (ch == delimiter); + }; + trim(str); + + std::vector output; + bool embeddedQuote = false; + char keyChar = ' '; + while(!str.empty()) { + if(delims.find_first_of(str[0]) != std::string::npos) { + keyChar = str[0]; + auto end = str.find_first_of(keyChar, 1); + while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes + end = str.find_first_of(keyChar, end + 1); + embeddedQuote = true; + } + if(end != std::string::npos) { + output.push_back(str.substr(1, end - 1)); + if(end + 2 < str.size()) { + str = str.substr(end + 2); + } else { + str.clear(); + } + + } else { + output.push_back(str.substr(1)); + str = ""; + } + } else { + auto it = std::find_if(std::begin(str), std::end(str), find_ws); + if(it != std::end(str)) { + std::string value = std::string(str.begin(), it); + output.push_back(value); + str = std::string(it + 1, str.end()); + } else { + output.push_back(str); + str = ""; + } + } + // transform any embedded quotes into the regular character + if(embeddedQuote) { + output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar)); + embeddedQuote = false; + } + trim(str); + } + return output; +} + +/// Add a leader to the beginning of all new lines (nothing is added +/// at the start of the first line). `"; "` would be for ini files +/// +/// Can't use Regex, or this would be a subs. +inline std::string fix_newlines(const std::string &leader, std::string input) { + std::string::size_type n = 0; + while(n != std::string::npos && n < input.size()) { + n = input.find('\n', n); + if(n != std::string::npos) { + input = input.substr(0, n + 1) + leader + input.substr(n + 1); + n += leader.size(); + } + } + return input; +} + +/// This function detects an equal or colon followed by an escaped quote after an argument +/// then modifies the string to replace the equality with a space. This is needed +/// to allow the split up function to work properly and is intended to be used with the find_and_modify function +/// the return value is the offset+1 which is required by the find_and_modify function. +inline std::size_t escape_detect(std::string &str, std::size_t offset) { + auto next = str[offset + 1]; + if((next == '\"') || (next == '\'') || (next == '`')) { + auto astart = str.find_last_of("-/ \"\'`", offset - 1); + if(astart != std::string::npos) { + if(str[astart] == ((str[offset] == '=') ? '-' : '/')) + str[offset] = ' '; // interpret this as a space so the split_up works properly + } + } + return offset + 1; +} + +/// Add quotes if the string contains spaces +inline std::string &add_quotes_if_needed(std::string &str) { + if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) { + char quote = str.find('"') < str.find('\'') ? '\'' : '"'; + if(str.find(' ') != std::string::npos) { + str.insert(0, 1, quote); + str.append(1, quote); + } + } + return str; +} + +} // namespace detail + + + + +// Use one of these on all error classes. +// These are temporary and are undef'd at the end of this file. +#define CLI11_ERROR_DEF(parent, name) \ + protected: \ + name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {} \ + name(std::string ename, std::string msg, ExitCodes exit_code) \ + : parent(std::move(ename), std::move(msg), exit_code) {} \ + \ + public: \ + name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \ + name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {} + +// This is added after the one above if a class is used directly and builds its own message +#define CLI11_ERROR_SIMPLE(name) \ + explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {} + +/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, +/// int values from e.get_error_code(). +enum class ExitCodes { + Success = 0, + IncorrectConstruction = 100, + BadNameString, + OptionAlreadyAdded, + FileError, + ConversionError, + ValidationError, + RequiredError, + RequiresError, + ExcludesError, + ExtrasError, + ConfigError, + InvalidError, + HorribleError, + OptionNotFound, + ArgumentMismatch, + BaseClass = 127 +}; + +// Error definitions + +/// @defgroup error_group Errors +/// @brief Errors thrown by CLI11 +/// +/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors. +/// @{ + +/// All errors derive from this one +class Error : public std::runtime_error { + int actual_exit_code; + std::string error_name{"Error"}; + + public: + int get_exit_code() const { return actual_exit_code; } + + std::string get_name() const { return error_name; } + + Error(std::string name, std::string msg, int exit_code = static_cast(ExitCodes::BaseClass)) + : runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {} + + Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast(exit_code)) {} +}; + +// Note: Using Error::Error constructors does not work on GCC 4.7 + +/// Construction errors (not in parsing) +class ConstructionError : public Error { + CLI11_ERROR_DEF(Error, ConstructionError) +}; + +/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) +class IncorrectConstruction : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) + CLI11_ERROR_SIMPLE(IncorrectConstruction) + static IncorrectConstruction PositionalFlag(std::string name) { + return IncorrectConstruction(name + ": Flags cannot be positional"); + } + static IncorrectConstruction Set0Opt(std::string name) { + return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); + } + static IncorrectConstruction SetFlag(std::string name) { + return IncorrectConstruction(name + ": Cannot set an expected number for flags"); + } + static IncorrectConstruction ChangeNotVector(std::string name) { + return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); + } + static IncorrectConstruction AfterMultiOpt(std::string name) { + return IncorrectConstruction( + name + ": You can't change expected arguments after you've changed the multi option policy!"); + } + static IncorrectConstruction MissingOption(std::string name) { + return IncorrectConstruction("Option " + name + " is not defined"); + } + static IncorrectConstruction MultiOptionPolicy(std::string name) { + return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options"); + } +}; + +/// Thrown on construction of a bad name +class BadNameString : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, BadNameString) + CLI11_ERROR_SIMPLE(BadNameString) + static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } + static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } + static BadNameString DashesOnly(std::string name) { + return BadNameString("Must have a name, not just dashes: " + name); + } + static BadNameString MultiPositionalNames(std::string name) { + return BadNameString("Only one positional name allowed, remove: " + name); + } +}; + +/// Thrown when an option already exists +class OptionAlreadyAdded : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) + explicit OptionAlreadyAdded(std::string name) + : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {} + static OptionAlreadyAdded Requires(std::string name, std::string other) { + return OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded); + } + static OptionAlreadyAdded Excludes(std::string name, std::string other) { + return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded); + } +}; + +// Parsing errors + +/// Anything that can error in Parse +class ParseError : public Error { + CLI11_ERROR_DEF(Error, ParseError) +}; + +// Not really "errors" + +/// This is a successful completion on parsing, supposed to exit +class Success : public ParseError { + CLI11_ERROR_DEF(ParseError, Success) + Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {} +}; + +/// -h or --help on command line +class CallForHelp : public Success { + CLI11_ERROR_DEF(Success, CallForHelp) + CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Usually something like --help-all on command line +class CallForAllHelp : public Success { + CLI11_ERROR_DEF(Success, CallForAllHelp) + CallForAllHelp() + : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// -v or --version on command line +class CallForVersion : public Success { + CLI11_ERROR_DEF(Success, CallForVersion) + CallForVersion() + : CallForVersion("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Does not output a diagnostic in CLI11_PARSE, but allows main() to return with a specific error code. +class RuntimeError : public ParseError { + CLI11_ERROR_DEF(ParseError, RuntimeError) + explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} +}; + +/// Thrown when parsing an INI file and it is missing +class FileError : public ParseError { + CLI11_ERROR_DEF(ParseError, FileError) + CLI11_ERROR_SIMPLE(FileError) + static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); } +}; + +/// Thrown when conversion call back fails, such as when an int fails to coerce to a string +class ConversionError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConversionError) + CLI11_ERROR_SIMPLE(ConversionError) + ConversionError(std::string member, std::string name) + : ConversionError("The value " + member + " is not an allowed value for " + name) {} + ConversionError(std::string name, std::vector results) + : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {} + static ConversionError TooManyInputsFlag(std::string name) { + return ConversionError(name + ": too many inputs for a flag"); + } + static ConversionError TrueFalse(std::string name) { + return ConversionError(name + ": Should be true/false or a number"); + } +}; + +/// Thrown when validation of results fails +class ValidationError : public ParseError { + CLI11_ERROR_DEF(ParseError, ValidationError) + CLI11_ERROR_SIMPLE(ValidationError) + explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {} +}; + +/// Thrown when a required option is missing +class RequiredError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiredError) + explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} + static RequiredError Subcommand(std::size_t min_subcom) { + if(min_subcom == 1) { + return RequiredError("A subcommand"); + } + return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands", + ExitCodes::RequiredError); + } + static RequiredError + Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string &option_list) { + if((min_option == 1) && (max_option == 1) && (used == 0)) + return RequiredError("Exactly 1 option from [" + option_list + "]"); + if((min_option == 1) && (max_option == 1) && (used > 1)) { + return RequiredError("Exactly 1 option from [" + option_list + "] is required and " + std::to_string(used) + + " were given", + ExitCodes::RequiredError); + } + if((min_option == 1) && (used == 0)) + return RequiredError("At least 1 option from [" + option_list + "]"); + if(used < min_option) { + return RequiredError("Requires at least " + std::to_string(min_option) + " options used and only " + + std::to_string(used) + "were given from [" + option_list + "]", + ExitCodes::RequiredError); + } + if(max_option == 1) + return RequiredError("Requires at most 1 options be given from [" + option_list + "]", + ExitCodes::RequiredError); + + return RequiredError("Requires at most " + std::to_string(max_option) + " options be used and " + + std::to_string(used) + "were given from [" + option_list + "]", + ExitCodes::RequiredError); + } +}; + +/// Thrown when the wrong number of arguments has been received +class ArgumentMismatch : public ParseError { + CLI11_ERROR_DEF(ParseError, ArgumentMismatch) + CLI11_ERROR_SIMPLE(ArgumentMismatch) + ArgumentMismatch(std::string name, int expected, std::size_t received) + : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + + ", got " + std::to_string(received)) + : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + + ", got " + std::to_string(received)), + ExitCodes::ArgumentMismatch) {} + + static ArgumentMismatch AtLeast(std::string name, int num, std::size_t received) { + return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required but received " + + std::to_string(received)); + } + static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) { + return ArgumentMismatch(name + ": At Most " + std::to_string(num) + " required but received " + + std::to_string(received)); + } + static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); + } + static ArgumentMismatch FlagOverride(std::string name) { + return ArgumentMismatch(name + " was given a disallowed flag override"); + } +}; + +/// Thrown when a requires option is missing +class RequiresError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiresError) + RequiresError(std::string curname, std::string subname) + : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {} +}; + +/// Thrown when an excludes option is present +class ExcludesError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExcludesError) + ExcludesError(std::string curname, std::string subname) + : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {} +}; + +/// Thrown when too many positionals or options are found +class ExtrasError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExtrasError) + explicit ExtrasError(std::vector args) + : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::rjoin(args, " "), + ExitCodes::ExtrasError) {} + ExtrasError(const std::string &name, std::vector args) + : ExtrasError(name, + (args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::rjoin(args, " "), + ExitCodes::ExtrasError) {} +}; + +/// Thrown when extra values are found in an INI file +class ConfigError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConfigError) + CLI11_ERROR_SIMPLE(ConfigError) + static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); } + static ConfigError NotConfigurable(std::string item) { + return ConfigError(item + ": This option is not allowed in a configuration file"); + } +}; + +/// Thrown when validation fails before parsing +class InvalidError : public ParseError { + CLI11_ERROR_DEF(ParseError, InvalidError) + explicit InvalidError(std::string name) + : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) { + } +}; + +/// This is just a safety check to verify selection and parsing match - you should not ever see it +/// Strings are directly added to this error, but again, it should never be seen. +class HorribleError : public ParseError { + CLI11_ERROR_DEF(ParseError, HorribleError) + CLI11_ERROR_SIMPLE(HorribleError) +}; + +// After parsing + +/// Thrown when counting a non-existent option +class OptionNotFound : public Error { + CLI11_ERROR_DEF(Error, OptionNotFound) + explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} +}; + +#undef CLI11_ERROR_DEF +#undef CLI11_ERROR_SIMPLE + +/// @} + + + + +// Type tools + +// Utilities for type enabling +namespace detail { +// Based generally on https://rmf.io/cxx11/almost-static-if +/// Simple empty scoped class +enum class enabler {}; + +/// An instance to use in EnableIf +constexpr enabler dummy = {}; +} // namespace detail + +/// A copy of enable_if_t from C++14, compatible with C++11. +/// +/// We could check to see if C++14 is being used, but it does not hurt to redefine this +/// (even Google does this: https://github.com/google/skia/blob/master/include/private/SkTLogic.h) +/// It is not in the std namespace anyway, so no harm done. +template using enable_if_t = typename std::enable_if::type; + +/// A copy of std::void_t from C++17 (helper for C++11 and C++14) +template struct make_void { using type = void; }; + +/// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine +template using void_t = typename make_void::type; + +/// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine +template using conditional_t = typename std::conditional::type; + +/// Check to see if something is bool (fail check by default) +template struct is_bool : std::false_type {}; + +/// Check to see if something is bool (true if actually a bool) +template <> struct is_bool : std::true_type {}; + +/// Check to see if something is a shared pointer +template struct is_shared_ptr : std::false_type {}; + +/// Check to see if something is a shared pointer (True if really a shared pointer) +template struct is_shared_ptr> : std::true_type {}; + +/// Check to see if something is a shared pointer (True if really a shared pointer) +template struct is_shared_ptr> : std::true_type {}; + +/// Check to see if something is copyable pointer +template struct is_copyable_ptr { + static bool const value = is_shared_ptr::value || std::is_pointer::value; +}; + +/// This can be specialized to override the type deduction for IsMember. +template struct IsMemberType { using type = T; }; + +/// The main custom type needed here is const char * should be a string. +template <> struct IsMemberType { using type = std::string; }; + +namespace detail { + +// These are utilities for IsMember and other transforming objects + +/// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that +/// pointer_traits be valid. + +/// not a pointer +template struct element_type { using type = T; }; + +template struct element_type::value>::type> { + using type = typename std::pointer_traits::element_type; +}; + +/// Combination of the element type and value type - remove pointer (including smart pointers) and get the value_type of +/// the container +template struct element_value_type { using type = typename element_type::type::value_type; }; + +/// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing. +template struct pair_adaptor : std::false_type { + using value_type = typename T::value_type; + using first_type = typename std::remove_const::type; + using second_type = typename std::remove_const::type; + + /// Get the first value (really just the underlying value) + template static auto first(Q &&pair_value) -> decltype(std::forward(pair_value)) { + return std::forward(pair_value); + } + /// Get the second value (really just the underlying value) + template static auto second(Q &&pair_value) -> decltype(std::forward(pair_value)) { + return std::forward(pair_value); + } +}; + +/// Adaptor for map-like structure (true version, must have key_type and mapped_type). +/// This wraps a mapped container in a few utilities access it in a general way. +template +struct pair_adaptor< + T, + conditional_t, void>> + : std::true_type { + using value_type = typename T::value_type; + using first_type = typename std::remove_const::type; + using second_type = typename std::remove_const::type; + + /// Get the first value (really just the underlying value) + template static auto first(Q &&pair_value) -> decltype(std::get<0>(std::forward(pair_value))) { + return std::get<0>(std::forward(pair_value)); + } + /// Get the second value (really just the underlying value) + template static auto second(Q &&pair_value) -> decltype(std::get<1>(std::forward(pair_value))) { + return std::get<1>(std::forward(pair_value)); + } +}; + +// Warning is suppressed due to "bug" in gcc<5.0 and gcc 7.0 with c++17 enabled that generates a Wnarrowing warning +// in the unevaluated context even if the function that was using this wasn't used. The standard says narrowing in +// brace initialization shouldn't be allowed but for backwards compatibility gcc allows it in some contexts. It is a +// little fuzzy what happens in template constructs and I think that was something GCC took a little while to work out. +// But regardless some versions of gcc generate a warning when they shouldn't from the following code so that should be +// suppressed +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnarrowing" +#endif +// check for constructibility from a specific type and copy assignable used in the parse detection +template class is_direct_constructible { + template + static auto test(int, std::true_type) -> decltype( +// NVCC warns about narrowing conversions here +#ifdef __CUDACC__ +#pragma diag_suppress 2361 +#endif + TT { std::declval() } +#ifdef __CUDACC__ +#pragma diag_default 2361 +#endif + , + std::is_move_assignable()); + + template static auto test(int, std::false_type) -> std::false_type; + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0, typename std::is_constructible::type()))::value; +}; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +// Check for output streamability +// Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream + +template class is_ostreamable { + template + static auto test(int) -> decltype(std::declval() << std::declval(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Check for input streamability +template class is_istreamable { + template + static auto test(int) -> decltype(std::declval() >> std::declval(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Check for complex +template class is_complex { + template + static auto test(int) -> decltype(std::declval().real(), std::declval().imag(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Templated operation to get a value from a stream +template ::value, detail::enabler> = detail::dummy> +bool from_stream(const std::string &istring, T &obj) { + std::istringstream is; + is.str(istring); + is >> obj; + return !is.fail() && !is.rdbuf()->in_avail(); +} + +template ::value, detail::enabler> = detail::dummy> +bool from_stream(const std::string & /*istring*/, T & /*obj*/) { + return false; +} + +// check to see if an object is a mutable container (fail by default) +template struct is_mutable_container : std::false_type {}; + +/// type trait to test if a type is a mutable container meaning it has a value_type, it has an iterator, a clear, and +/// end methods and an insert function. And for our purposes we exclude std::string and types that can be constructed +/// from a std::string +template +struct is_mutable_container< + T, + conditional_t().end()), + decltype(std::declval().clear()), + decltype(std::declval().insert(std::declval().end())>(), + std::declval()))>, + void>> + : public conditional_t::value, std::false_type, std::true_type> {}; + +// check to see if an object is a mutable container (fail by default) +template struct is_readable_container : std::false_type {}; + +/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, a clear, and an end +/// methods and an insert function. And for our purposes we exclude std::string and types that can be constructed from +/// a std::string +template +struct is_readable_container< + T, + conditional_t().end()), decltype(std::declval().begin())>, void>> + : public std::true_type {}; + +// check to see if an object is a wrapper (fail by default) +template struct is_wrapper : std::false_type {}; + +// check if an object is a wrapper (it has a value_type defined) +template +struct is_wrapper, void>> : public std::true_type {}; + +// Check for tuple like types, as in classes with a tuple_size type trait +template class is_tuple_like { + template + // static auto test(int) + // -> decltype(std::conditional<(std::tuple_size::value > 0), std::true_type, std::false_type>::type()); + static auto test(int) -> decltype(std::tuple_size::type>::value, std::true_type{}); + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Convert an object to a string (directly forward if this can become a string) +template ::value, detail::enabler> = detail::dummy> +auto to_string(T &&value) -> decltype(std::forward(value)) { + return std::forward(value); +} + +/// Construct a string from the object +template ::value && !std::is_convertible::value, + detail::enabler> = detail::dummy> +std::string to_string(const T &value) { + return std::string(value); +} + +/// Convert an object to a string (streaming must be supported for that type) +template ::value && !std::is_constructible::value && + is_ostreamable::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&value) { + std::stringstream stream; + stream << value; + return stream.str(); +} + +/// If conversion is not supported, return an empty string (streaming is not supported for that type) +template ::value && !is_ostreamable::value && + !is_readable_container::type>::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&) { + return std::string{}; +} + +/// convert a readable container to a string +template ::value && !is_ostreamable::value && + is_readable_container::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&variable) { + std::vector defaults; + auto cval = variable.begin(); + auto end = variable.end(); + while(cval != end) { + defaults.emplace_back(CLI::detail::to_string(*cval)); + ++cval; + } + return std::string("[" + detail::join(defaults) + "]"); +} + +/// special template overload +template ::value, detail::enabler> = detail::dummy> +auto checked_to_string(T &&value) -> decltype(to_string(std::forward(value))) { + return to_string(std::forward(value)); +} + +/// special template overload +template ::value, detail::enabler> = detail::dummy> +std::string checked_to_string(T &&) { + return std::string{}; +} +/// get a string as a convertible value for arithmetic types +template ::value, detail::enabler> = detail::dummy> +std::string value_string(const T &value) { + return std::to_string(value); +} +/// get a string as a convertible value for enumerations +template ::value, detail::enabler> = detail::dummy> +std::string value_string(const T &value) { + return std::to_string(static_cast::type>(value)); +} +/// for other types just use the regular to_string function +template ::value && !std::is_arithmetic::value, detail::enabler> = detail::dummy> +auto value_string(const T &value) -> decltype(to_string(value)) { + return to_string(value); +} + +/// template to get the underlying value type if it exists or use a default +template struct wrapped_type { using type = def; }; + +/// Type size for regular object types that do not look like a tuple +template struct wrapped_type::value>::type> { + using type = typename T::value_type; +}; + +/// This will only trigger for actual void type +template struct type_count_base { static const int value{0}; }; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count_base::value && !is_mutable_container::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// the base tuple size +template +struct type_count_base::value && !is_mutable_container::value>::type> { + static constexpr int value{std::tuple_size::value}; +}; + +/// Type count base for containers is the type_count_base of the individual element +template struct type_count_base::value>::type> { + static constexpr int value{type_count_base::value}; +}; + +/// Set of overloads to get the type size of an object + +/// forward declare the subtype_count structure +template struct subtype_count; + +/// forward declare the subtype_count_min structure +template struct subtype_count_min; + +/// This will only trigger for actual void type +template struct type_count { static const int value{0}; }; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count::value && !is_tuple_like::value && !is_complex::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// Type size for complex since it sometimes looks like a wrapper +template struct type_count::value>::type> { + static constexpr int value{2}; +}; + +/// Type size of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) +template struct type_count::value>::type> { + static constexpr int value{subtype_count::value}; +}; + +/// Type size of types that are wrappers,except containers complex and tuples(which can also be wrappers sometimes) +template +struct type_count::value && !is_complex::value && !is_tuple_like::value && + !is_mutable_container::value>::type> { + static constexpr int value{type_count::value}; +}; + +/// 0 if the index > tuple size +template +constexpr typename std::enable_if::value, int>::type tuple_type_size() { + return 0; +} + +/// Recursively generate the tuple type name +template + constexpr typename std::enable_if < I::value, int>::type tuple_type_size() { + return subtype_count::type>::value + tuple_type_size(); +} + +/// Get the type size of the sum of type sizes for all the individual tuple types +template struct type_count::value>::type> { + static constexpr int value{tuple_type_size()}; +}; + +/// definition of subtype count +template struct subtype_count { + static constexpr int value{is_mutable_container::value ? expected_max_vector_size : type_count::value}; +}; + +/// This will only trigger for actual void type +template struct type_count_min { static const int value{0}; }; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count_min< + T, + typename std::enable_if::value && !is_tuple_like::value && !is_wrapper::value && + !is_complex::value && !std::is_void::value>::type> { + static constexpr int value{type_count::value}; +}; + +/// Type size for complex since it sometimes looks like a wrapper +template struct type_count_min::value>::type> { + static constexpr int value{1}; +}; + +/// Type size min of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) +template +struct type_count_min< + T, + typename std::enable_if::value && !is_complex::value && !is_tuple_like::value>::type> { + static constexpr int value{subtype_count_min::value}; +}; + +/// 0 if the index > tuple size +template +constexpr typename std::enable_if::value, int>::type tuple_type_size_min() { + return 0; +} + +/// Recursively generate the tuple type name +template + constexpr typename std::enable_if < I::value, int>::type tuple_type_size_min() { + return subtype_count_min::type>::value + tuple_type_size_min(); +} + +/// Get the type size of the sum of type sizes for all the individual tuple types +template struct type_count_min::value>::type> { + static constexpr int value{tuple_type_size_min()}; +}; + +/// definition of subtype count +template struct subtype_count_min { + static constexpr int value{is_mutable_container::value + ? ((type_count::value < expected_max_vector_size) ? type_count::value : 0) + : type_count_min::value}; +}; + +/// This will only trigger for actual void type +template struct expected_count { static const int value{0}; }; + +/// For most types the number of expected items is 1 +template +struct expected_count::value && !is_wrapper::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; +/// number of expected items in a vector +template struct expected_count::value>::type> { + static constexpr int value{expected_max_vector_size}; +}; + +/// number of expected items in a vector +template +struct expected_count::value && is_wrapper::value>::type> { + static constexpr int value{expected_count::value}; +}; + +// Enumeration of the different supported categorizations of objects +enum class object_category : int { + char_value = 1, + integral_value = 2, + unsigned_integral = 4, + enumeration = 6, + boolean_value = 8, + floating_point = 10, + number_constructible = 12, + double_constructible = 14, + integer_constructible = 16, + // string like types + string_assignable = 23, + string_constructible = 24, + other = 45, + // special wrapper or container types + wrapper_value = 50, + complex_number = 60, + tuple_value = 70, + container_value = 80, + +}; + +/// Set of overloads to classify an object according to type + +/// some type that is not otherwise recognized +template struct classify_object { + static constexpr object_category value{object_category::other}; +}; + +/// Signed integers +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_same::value && std::is_signed::value && + !is_bool::value && !std::is_enum::value>::type> { + static constexpr object_category value{object_category::integral_value}; +}; + +/// Unsigned integers +template +struct classify_object::value && std::is_unsigned::value && + !std::is_same::value && !is_bool::value>::type> { + static constexpr object_category value{object_category::unsigned_integral}; +}; + +/// single character values +template +struct classify_object::value && !std::is_enum::value>::type> { + static constexpr object_category value{object_category::char_value}; +}; + +/// Boolean values +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::boolean_value}; +}; + +/// Floats +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::floating_point}; +}; + +/// String and similar direct assignment +template +struct classify_object::value && !std::is_integral::value && + std::is_assignable::value>::type> { + static constexpr object_category value{object_category::string_assignable}; +}; + +/// String and similar constructible and copy assignment +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && + !std::is_assignable::value && (type_count::value == 1) && + std::is_constructible::value>::type> { + static constexpr object_category value{object_category::string_constructible}; +}; + +/// Enumerations +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::enumeration}; +}; + +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::complex_number}; +}; + +/// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, +/// vectors, and enumerations +template struct uncommon_type { + using type = typename std::conditional::value && !std::is_integral::value && + !std::is_assignable::value && + !std::is_constructible::value && !is_complex::value && + !is_mutable_container::value && !std::is_enum::value, + std::true_type, + std::false_type>::type; + static constexpr bool value = type::value; +}; + +/// wrapper type +template +struct classify_object::value && is_wrapper::value && + !is_tuple_like::value && uncommon_type::value)>::type> { + static constexpr object_category value{object_category::wrapper_value}; +}; + +/// Assignable from double or int +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && is_direct_constructible::value && + is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::number_constructible}; +}; + +/// Assignable from int +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && !is_direct_constructible::value && + is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::integer_constructible}; +}; + +/// Assignable from double +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && is_direct_constructible::value && + !is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::double_constructible}; +}; + +/// Tuple type +template +struct classify_object< + T, + typename std::enable_if::value && + ((type_count::value >= 2 && !is_wrapper::value) || + (uncommon_type::value && !is_direct_constructible::value && + !is_direct_constructible::value))>::type> { + static constexpr object_category value{object_category::tuple_value}; + // the condition on this class requires it be like a tuple, but on some compilers (like Xcode) tuples can be + // constructed from just the first element so tuples of can be constructed from a string, which + // could lead to issues so there are two variants of the condition, the first isolates things with a type size >=2 + // mainly to get tuples on Xcode with the exception of wrappers, the second is the main one and just separating out + // those cases that are caught by other object classifications +}; + +/// container type +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::container_value}; +}; + +// Type name print + +/// Was going to be based on +/// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template +/// But this is cleaner and works better in this case + +template ::value == object_category::char_value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "CHAR"; +} + +template ::value == object_category::integral_value || + classify_object::value == object_category::integer_constructible, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "INT"; +} + +template ::value == object_category::unsigned_integral, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "UINT"; +} + +template ::value == object_category::floating_point || + classify_object::value == object_category::number_constructible || + classify_object::value == object_category::double_constructible, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "FLOAT"; +} + +/// Print name for enumeration types +template ::value == object_category::enumeration, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "ENUM"; +} + +/// Print name for enumeration types +template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "BOOLEAN"; +} + +/// Print name for enumeration types +template ::value == object_category::complex_number, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "COMPLEX"; +} + +/// Print for all other types +template ::value >= object_category::string_assignable && + classify_object::value <= object_category::other, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "TEXT"; +} +/// typename for tuple value +template ::value == object_category::tuple_value && type_count_base::value >= 2, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration + +/// Generate type name for a wrapper or container value +template ::value == object_category::container_value || + classify_object::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration + +/// Print name for single element tuple types +template ::value == object_category::tuple_value && type_count_base::value == 1, + detail::enabler> = detail::dummy> +inline std::string type_name() { + return type_name::type>::type>(); +} + +/// Empty string if the index > tuple size +template +inline typename std::enable_if::value, std::string>::type tuple_name() { + return std::string{}; +} + +/// Recursively generate the tuple type name +template +inline typename std::enable_if<(I < type_count_base::value), std::string>::type tuple_name() { + std::string str = std::string(type_name::type>::type>()) + + ',' + tuple_name(); + if(str.back() == ',') + str.pop_back(); + return str; +} + +/// Print type name for tuples with 2 or more elements +template ::value == object_category::tuple_value && type_count_base::value >= 2, + detail::enabler>> +inline std::string type_name() { + auto tname = std::string(1, '[') + tuple_name(); + tname.push_back(']'); + return tname; +} + +/// get the type name for a type that has a value_type member +template ::value == object_category::container_value || + classify_object::value == object_category::wrapper_value, + detail::enabler>> +inline std::string type_name() { + return type_name(); +} + +// Lexical cast + +/// Convert to an unsigned integral +template ::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty()) { + return false; + } + char *val = nullptr; + std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); + output = static_cast(output_ll); + return val == (input.c_str() + input.size()) && static_cast(output) == output_ll; +} + +/// Convert to a signed integral +template ::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty()) { + return false; + } + char *val = nullptr; + std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0); + output = static_cast(output_ll); + return val == (input.c_str() + input.size()) && static_cast(output) == output_ll; +} + +/// Convert a flag into an integer value typically binary flags +inline std::int64_t to_flag_value(std::string val) { + static const std::string trueString("true"); + static const std::string falseString("false"); + if(val == trueString) { + return 1; + } + if(val == falseString) { + return -1; + } + val = detail::to_lower(val); + std::int64_t ret; + if(val.size() == 1) { + if(val[0] >= '1' && val[0] <= '9') { + return (static_cast(val[0]) - '0'); + } + switch(val[0]) { + case '0': + case 'f': + case 'n': + case '-': + ret = -1; + break; + case 't': + case 'y': + case '+': + ret = 1; + break; + default: + throw std::invalid_argument("unrecognized character"); + } + return ret; + } + if(val == trueString || val == "on" || val == "yes" || val == "enable") { + ret = 1; + } else if(val == falseString || val == "off" || val == "no" || val == "disable") { + ret = -1; + } else { + ret = std::stoll(val); + } + return ret; +} + +/// Integer conversion +template ::value == object_category::integral_value || + classify_object::value == object_category::unsigned_integral, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + return integral_conversion(input, output); +} + +/// char values +template ::value == object_category::char_value, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + if(input.size() == 1) { + output = static_cast(input[0]); + return true; + } + return integral_conversion(input, output); +} + +/// Boolean values +template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + try { + auto out = to_flag_value(input); + output = (out > 0); + return true; + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + // if the number is out of the range of a 64 bit value then it is still a number and for this purpose is still + // valid all we care about the sign + output = (input[0] != '-'); + return true; + } +} + +/// Floats +template ::value == object_category::floating_point, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + if(input.empty()) { + return false; + } + char *val = nullptr; + auto output_ld = std::strtold(input.c_str(), &val); + output = static_cast(output_ld); + return val == (input.c_str() + input.size()); +} + +/// complex +template ::value == object_category::complex_number, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + using XC = typename wrapped_type::type; + XC x{0.0}, y{0.0}; + auto str1 = input; + bool worked = false; + auto nloc = str1.find_last_of("+-"); + if(nloc != std::string::npos && nloc > 0) { + worked = detail::lexical_cast(str1.substr(0, nloc), x); + str1 = str1.substr(nloc); + if(str1.back() == 'i' || str1.back() == 'j') + str1.pop_back(); + worked = worked && detail::lexical_cast(str1, y); + } else { + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + worked = detail::lexical_cast(str1, y); + x = XC{0}; + } else { + worked = detail::lexical_cast(str1, x); + y = XC{0}; + } + } + if(worked) { + output = T{x, y}; + return worked; + } + return from_stream(input, output); +} + +/// String and similar direct assignment +template ::value == object_category::string_assignable, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = input; + return true; +} + +/// String and similar constructible and copy assignment +template < + typename T, + enable_if_t::value == object_category::string_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = T(input); + return true; +} + +/// Enumerations +template ::value == object_category::enumeration, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename std::underlying_type::type val; + if(!integral_conversion(input, val)) { + return false; + } + output = static_cast(val); + return true; +} + +/// wrapper types +template ::value == object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = val; + return true; + } + return from_stream(input, output); +} + +template ::value == object_category::wrapper_value && + !std::is_assignable::value && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = T{val}; + return true; + } + return from_stream(input, output); +} + +/// Assignable from double or int +template < + typename T, + enable_if_t::value == object_category::number_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val; + if(integral_conversion(input, val)) { + output = T(val); + return true; + } else { + double dval; + if(lexical_cast(input, dval)) { + output = T{dval}; + return true; + } + } + return from_stream(input, output); +} + +/// Assignable from int +template < + typename T, + enable_if_t::value == object_category::integer_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val; + if(integral_conversion(input, val)) { + output = T(val); + return true; + } + return from_stream(input, output); +} + +/// Assignable from double +template < + typename T, + enable_if_t::value == object_category::double_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + double val; + if(lexical_cast(input, val)) { + output = T{val}; + return true; + } + return from_stream(input, output); +} + +/// Non-string convertible from an int +template ::value == object_category::other && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val; + if(integral_conversion(input, val)) { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4800) +#endif + // with Atomic this could produce a warning due to the conversion but if atomic gets here it is an old style + // so will most likely still work + output = val; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + return true; + } + // LCOV_EXCL_START + // This version of cast is only used for odd cases in an older compilers the fail over + // from_stream is tested elsewhere an not relevant for coverage here + return from_stream(input, output); + // LCOV_EXCL_STOP +} + +/// Non-string parsable by a stream +template ::value == object_category::other && !std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + static_assert(is_istreamable::value, + "option object type must have a lexical cast overload or streaming input operator(>>) defined, if it " + "is convertible from another type use the add_option(...) with XC being the known type"); + return from_stream(input, output); +} + +/// Assign a value through lexical cast operations +/// Strings can be empty so we need to do a little different +template ::value && + (classify_object::value == object_category::string_assignable || + classify_object::value == object_category::string_constructible), + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations +template ::value && std::is_assignable::value && + classify_object::value != object_category::string_assignable && + classify_object::value != object_category::string_constructible, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + output = AssignTo{}; + return true; + } + + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations +template ::value && !std::is_assignable::value && + classify_object::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + typename AssignTo::value_type emptyVal{}; + output = emptyVal; + return true; + } + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations for int compatible values +/// mainly for atomic operations on some compilers +template ::value && !std::is_assignable::value && + classify_object::value != object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + output = 0; + return true; + } + int val; + if(lexical_cast(input, val)) { + output = val; + return true; + } + return false; +} + +/// Assign a value converted from a string in lexical cast to the output value directly +template ::value && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo val{}; + bool parse_result = (!input.empty()) ? lexical_cast(input, val) : true; + if(parse_result) { + output = val; + } + return parse_result; +} + +/// Assign a value from a lexical cast through constructing a value and move assigning it +template < + typename AssignTo, + typename ConvertTo, + enable_if_t::value && !std::is_assignable::value && + std::is_move_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo val{}; + bool parse_result = input.empty() ? true : lexical_cast(input, val); + if(parse_result) { + output = AssignTo(val); // use () form of constructor to allow some implicit conversions + } + return parse_result; +} + +/// primary lexical conversion operation, 1 string to 1 type of some kind +template ::value <= object_category::other && + classify_object::value <= object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + return lexical_assign(strings[0], output); +} + +/// Lexical conversion if there is only one element but the conversion type is for two, then call a two element +/// constructor +template ::value <= 2) && expected_count::value == 1 && + is_tuple_like::value && type_count_base::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + // the remove const is to handle pair types coming from a container + typename std::remove_const::type>::type v1; + typename std::tuple_element<1, ConvertTo>::type v2; + bool retval = lexical_assign(strings[0], v1); + if(strings.size() > 1) { + retval = retval && lexical_assign(strings[1], v2); + } + if(retval) { + output = AssignTo{v1, v2}; + } + return retval; +} + +/// Lexical conversion of a container types of single elements +template ::value && is_mutable_container::value && + type_count::value == 1, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + output.erase(output.begin(), output.end()); + for(const auto &elem : strings) { + typename AssignTo::value_type out; + bool retval = lexical_assign(elem, out); + if(!retval) { + return false; + } + output.insert(output.end(), std::move(out)); + } + return (!output.empty()); +} + +/// Lexical conversion for complex types +template ::value, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + + if(strings.size() >= 2 && !strings[1].empty()) { + using XC2 = typename wrapped_type::type; + XC2 x{0.0}, y{0.0}; + auto str1 = strings[1]; + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + } + auto worked = detail::lexical_cast(strings[0], x) && detail::lexical_cast(str1, y); + if(worked) { + output = ConvertTo{x, y}; + } + return worked; + } else { + return lexical_assign(strings[0], output); + } +} + +/// Conversion to a vector type using a particular single type as the conversion type +template ::value && (expected_count::value == 1) && + (type_count::value == 1), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + bool retval = true; + output.clear(); + output.reserve(strings.size()); + for(const auto &elem : strings) { + + output.emplace_back(); + retval = retval && lexical_assign(elem, output.back()); + } + return (!output.empty()) && retval; +} + +// forward declaration + +/// Lexical conversion of a container types with conversion type of two elements +template ::value && is_mutable_container::value && + type_count_base::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(std::vector strings, AssignTo &output); + +/// Lexical conversion of a vector types with type_size >2 forward declaration +template ::value && is_mutable_container::value && + type_count_base::value != 2 && + ((type_count::value > 2) || + (type_count::value > type_count_base::value)), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output); + +/// Conversion for tuples +template ::value && is_tuple_like::value && + (type_count_base::value != type_count::value || + type_count::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output); // forward declaration + +/// Conversion for operations where the assigned type is some class but the conversion is a mutable container or large +/// tuple +template ::value && !is_mutable_container::value && + classify_object::value != object_category::wrapper_value && + (is_mutable_container::value || type_count::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + + if(strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) { + ConvertTo val; + auto retval = lexical_conversion(strings, val); + output = AssignTo{val}; + return retval; + } + output = AssignTo{}; + return true; +} + +/// function template for converting tuples if the static Index is greater than the tuple size +template +inline typename std::enable_if<(I >= type_count_base::value), bool>::type +tuple_conversion(const std::vector &, AssignTo &) { + return true; +} + +/// Conversion of a tuple element where the type size ==1 and not a mutable container +template +inline typename std::enable_if::value && type_count::value == 1, bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + auto retval = lexical_assign(strings[0], output); + strings.erase(strings.begin()); + return retval; +} + +/// Conversion of a tuple element where the type size !=1 but the size is fixed and not a mutable container +template +inline typename std::enable_if::value && (type_count::value > 1) && + type_count::value == type_count_min::value, + bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + auto retval = lexical_conversion(strings, output); + strings.erase(strings.begin(), strings.begin() + type_count::value); + return retval; +} + +/// Conversion of a tuple element where the type is a mutable container or a type with different min and max type sizes +template +inline typename std::enable_if::value || + type_count::value != type_count_min::value, + bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + + std::size_t index{subtype_count_min::value}; + const std::size_t mx_count{subtype_count::value}; + const std::size_t mx{(std::max)(mx_count, strings.size())}; + + while(index < mx) { + if(is_separator(strings[index])) { + break; + } + ++index; + } + bool retval = lexical_conversion( + std::vector(strings.begin(), strings.begin() + static_cast(index)), output); + strings.erase(strings.begin(), strings.begin() + static_cast(index) + 1); + return retval; +} + +/// Tuple conversion operation +template +inline typename std::enable_if<(I < type_count_base::value), bool>::type +tuple_conversion(std::vector strings, AssignTo &output) { + bool retval = true; + using ConvertToElement = typename std:: + conditional::value, typename std::tuple_element::type, ConvertTo>::type; + if(!strings.empty()) { + retval = retval && tuple_type_conversion::type, ConvertToElement>( + strings, std::get(output)); + } + retval = retval && tuple_conversion(std::move(strings), output); + return retval; +} + +/// Lexical conversion of a container types with tuple elements of size 2 +template ::value && is_mutable_container::value && + type_count_base::value == 2, + detail::enabler>> +bool lexical_conversion(std::vector strings, AssignTo &output) { + output.clear(); + while(!strings.empty()) { + + typename std::remove_const::type>::type v1; + typename std::tuple_element<1, typename ConvertTo::value_type>::type v2; + bool retval = tuple_type_conversion(strings, v1); + if(!strings.empty()) { + retval = retval && tuple_type_conversion(strings, v2); + } + if(retval) { + output.insert(output.end(), typename AssignTo::value_type{v1, v2}); + } else { + return false; + } + } + return (!output.empty()); +} + +/// lexical conversion of tuples with type count>2 or tuples of types of some element with a type size>=2 +template ::value && is_tuple_like::value && + (type_count_base::value != type_count::value || + type_count::value > 2), + detail::enabler>> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + static_assert( + !is_tuple_like::value || type_count_base::value == type_count_base::value, + "if the conversion type is defined as a tuple it must be the same size as the type you are converting to"); + return tuple_conversion(strings, output); +} + +/// Lexical conversion of a vector types for everything but tuples of two elements and types of size 1 +template ::value && is_mutable_container::value && + type_count_base::value != 2 && + ((type_count::value > 2) || + (type_count::value > type_count_base::value)), + detail::enabler>> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + bool retval = true; + output.clear(); + std::vector temp; + std::size_t ii{0}; + std::size_t icount{0}; + std::size_t xcm{type_count::value}; + auto ii_max = strings.size(); + while(ii < ii_max) { + temp.push_back(strings[ii]); + ++ii; + ++icount; + if(icount == xcm || is_separator(temp.back()) || ii == ii_max) { + if(static_cast(xcm) > type_count_min::value && is_separator(temp.back())) { + temp.pop_back(); + } + typename AssignTo::value_type temp_out; + retval = retval && + lexical_conversion(temp, temp_out); + temp.clear(); + if(!retval) { + return false; + } + output.insert(output.end(), std::move(temp_out)); + icount = 0; + } + } + return retval; +} + +/// conversion for wrapper types +template ::value == object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + if(strings.empty() || strings.front().empty()) { + output = ConvertTo{}; + return true; + } + typename ConvertTo::value_type val; + if(lexical_conversion(strings, val)) { + output = ConvertTo{val}; + return true; + } + return false; +} + +/// conversion for wrapper types +template ::value == object_category::wrapper_value && + !std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + using ConvertType = typename ConvertTo::value_type; + if(strings.empty() || strings.front().empty()) { + output = ConvertType{}; + return true; + } + ConvertType val; + if(lexical_conversion(strings, val)) { + output = val; + return true; + } + return false; +} + +/// Sum a vector of flag representations +/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is +/// by +/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most +/// common true and false strings then uses stoll to convert the rest for summing +template ::value, detail::enabler> = detail::dummy> +void sum_flag_vector(const std::vector &flags, T &output) { + std::int64_t count{0}; + for(auto &flag : flags) { + count += detail::to_flag_value(flag); + } + output = (count > 0) ? static_cast(count) : T{0}; +} + +/// Sum a vector of flag representations +/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is +/// by +/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most +/// common true and false strings then uses stoll to convert the rest for summing +template ::value, detail::enabler> = detail::dummy> +void sum_flag_vector(const std::vector &flags, T &output) { + std::int64_t count{0}; + for(auto &flag : flags) { + count += detail::to_flag_value(flag); + } + output = static_cast(count); +} + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4800) +#endif +// with Atomic this could produce a warning due to the conversion but if atomic gets here it is an old style so will +// most likely still work + +/// Sum a vector of flag representations +/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is +/// by +/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most +/// common true and false strings then uses stoll to convert the rest for summing +template ::value && !std::is_unsigned::value, detail::enabler> = detail::dummy> +void sum_flag_vector(const std::vector &flags, T &output) { + std::int64_t count{0}; + for(auto &flag : flags) { + count += detail::to_flag_value(flag); + } + std::string out = detail::to_string(count); + lexical_cast(out, output); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +} // namespace detail + + + +namespace detail { + +// Returns false if not a short option. Otherwise, sets opt name and rest and returns true +inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) { + if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { + name = current.substr(1, 1); + rest = current.substr(2); + return true; + } + return false; +} + +// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true +inline bool split_long(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) { + auto loc = current.find_first_of('='); + if(loc != std::string::npos) { + name = current.substr(2, loc - 2); + value = current.substr(loc + 1); + } else { + name = current.substr(2); + value = ""; + } + return true; + } + return false; +} + +// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true +inline bool split_windows_style(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) { + auto loc = current.find_first_of(':'); + if(loc != std::string::npos) { + name = current.substr(1, loc - 1); + value = current.substr(loc + 1); + } else { + name = current.substr(1); + value = ""; + } + return true; + } + return false; +} + +// Splits a string into multiple long and short names +inline std::vector split_names(std::string current) { + std::vector output; + std::size_t val; + while((val = current.find(",")) != std::string::npos) { + output.push_back(trim_copy(current.substr(0, val))); + current = current.substr(val + 1); + } + output.push_back(trim_copy(current)); + return output; +} + +/// extract default flag values either {def} or starting with a ! +inline std::vector> get_default_flag_values(const std::string &str) { + std::vector flags = split_names(str); + flags.erase(std::remove_if(flags.begin(), + flags.end(), + [](const std::string &name) { + return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) && + (name.back() == '}')) || + (name[0] == '!')))); + }), + flags.end()); + std::vector> output; + output.reserve(flags.size()); + for(auto &flag : flags) { + auto def_start = flag.find_first_of('{'); + std::string defval = "false"; + if((def_start != std::string::npos) && (flag.back() == '}')) { + defval = flag.substr(def_start + 1); + defval.pop_back(); + flag.erase(def_start, std::string::npos); + } + flag.erase(0, flag.find_first_not_of("-!")); + output.emplace_back(flag, defval); + } + return output; +} + +/// Get a vector of short names, one of long names, and a single name +inline std::tuple, std::vector, std::string> +get_names(const std::vector &input) { + + std::vector short_names; + std::vector long_names; + std::string pos_name; + + for(std::string name : input) { + if(name.length() == 0) { + continue; + } + if(name.length() > 1 && name[0] == '-' && name[1] != '-') { + if(name.length() == 2 && valid_first_char(name[1])) + short_names.emplace_back(1, name[1]); + else + throw BadNameString::OneCharName(name); + } else if(name.length() > 2 && name.substr(0, 2) == "--") { + name = name.substr(2); + if(valid_name_string(name)) + long_names.push_back(name); + else + throw BadNameString::BadLongName(name); + } else if(name == "-" || name == "--") { + throw BadNameString::DashesOnly(name); + } else { + if(pos_name.length() > 0) + throw BadNameString::MultiPositionalNames(name); + pos_name = name; + } + } + + return std::tuple, std::vector, std::string>( + short_names, long_names, pos_name); +} + +} // namespace detail + + + +class App; + +/// Holds values to load into Options +struct ConfigItem { + /// This is the list of parents + std::vector parents{}; + + /// This is the name + std::string name{}; + + /// Listing of inputs + std::vector inputs{}; + + /// The list of parents and name joined by "." + std::string fullname() const { + std::vector tmp = parents; + tmp.emplace_back(name); + return detail::join(tmp, "."); + } +}; + +/// This class provides a converter for configuration files. +class Config { + protected: + std::vector items{}; + + public: + /// Convert an app into a configuration + virtual std::string to_config(const App *, bool, bool, std::string) const = 0; + + /// Convert a configuration into an app + virtual std::vector from_config(std::istream &) const = 0; + + /// Get a flag value + virtual std::string to_flag(const ConfigItem &item) const { + if(item.inputs.size() == 1) { + return item.inputs.at(0); + } + throw ConversionError::TooManyInputsFlag(item.fullname()); + } + + /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure + std::vector from_file(const std::string &name) { + std::ifstream input{name}; + if(!input.good()) + throw FileError::Missing(name); + + return from_config(input); + } + + /// Virtual destructor + virtual ~Config() = default; +}; + +/// This converter works with INI/TOML files; to write INI files use ConfigINI +class ConfigBase : public Config { + protected: + /// the character used for comments + char commentChar = '#'; + /// the character used to start an array '\0' is a default to not use + char arrayStart = '['; + /// the character used to end an array '\0' is a default to not use + char arrayEnd = ']'; + /// the character used to separate elements in an array + char arraySeparator = ','; + /// the character used separate the name from the value + char valueDelimiter = '='; + /// the character to use around strings + char stringQuote = '"'; + /// the character to use around single characters + char characterQuote = '\''; + + public: + std::string + to_config(const App * /*app*/, bool default_also, bool write_description, std::string prefix) const override; + + std::vector from_config(std::istream &input) const override; + /// Specify the configuration for comment characters + ConfigBase *comment(char cchar) { + commentChar = cchar; + return this; + } + /// Specify the start and end characters for an array + ConfigBase *arrayBounds(char aStart, char aEnd) { + arrayStart = aStart; + arrayEnd = aEnd; + return this; + } + /// Specify the delimiter character for an array + ConfigBase *arrayDelimiter(char aSep) { + arraySeparator = aSep; + return this; + } + /// Specify the delimiter between a name and value + ConfigBase *valueSeparator(char vSep) { + valueDelimiter = vSep; + return this; + } + /// Specify the quote characters used around strings and characters + ConfigBase *quoteCharacter(char qString, char qChar) { + stringQuote = qString; + characterQuote = qChar; + return this; + } +}; + +/// the default Config is the TOML file format +using ConfigTOML = ConfigBase; + +/// ConfigINI generates a "standard" INI compliant output +class ConfigINI : public ConfigTOML { + + public: + ConfigINI() { + commentChar = ';'; + arrayStart = '\0'; + arrayEnd = '\0'; + arraySeparator = ' '; + valueDelimiter = '='; + } +}; + + + +class Option; + +/// @defgroup validator_group Validators + +/// @brief Some validators that are provided +/// +/// These are simple `std::string(const std::string&)` validators that are useful. They return +/// a string if the validation fails. A custom struct is provided, as well, with the same user +/// semantics, but with the ability to provide a new type name. +/// @{ + +/// +class Validator { + protected: + /// This is the description function, if empty the description_ will be used + std::function desc_function_{[]() { return std::string{}; }}; + + /// This is the base function that is to be called. + /// Returns a string error message if validation fails. + std::function func_{[](std::string &) { return std::string{}; }}; + /// The name for search purposes of the Validator + std::string name_{}; + /// A Validator will only apply to an indexed value (-1 is all elements) + int application_index_ = -1; + /// Enable for Validator to allow it to be disabled if need be + bool active_{true}; + /// specify that a validator should not modify the input + bool non_modifying_{false}; + + public: + Validator() = default; + /// Construct a Validator with just the description string + explicit Validator(std::string validator_desc) : desc_function_([validator_desc]() { return validator_desc; }) {} + /// Construct Validator from basic information + Validator(std::function op, std::string validator_desc, std::string validator_name = "") + : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(op)), + name_(std::move(validator_name)) {} + /// Set the Validator operation function + Validator &operation(std::function op) { + func_ = std::move(op); + return *this; + } + /// This is the required operator for a Validator - provided to help + /// users (CLI11 uses the member `func` directly) + std::string operator()(std::string &str) const { + std::string retstring; + if(active_) { + if(non_modifying_) { + std::string value = str; + retstring = func_(value); + } else { + retstring = func_(str); + } + } + return retstring; + } + + /// This is the required operator for a Validator - provided to help + /// users (CLI11 uses the member `func` directly) + std::string operator()(const std::string &str) const { + std::string value = str; + return (active_) ? func_(value) : std::string{}; + } + + /// Specify the type string + Validator &description(std::string validator_desc) { + desc_function_ = [validator_desc]() { return validator_desc; }; + return *this; + } + /// Specify the type string + Validator description(std::string validator_desc) const { + Validator newval(*this); + newval.desc_function_ = [validator_desc]() { return validator_desc; }; + return newval; + } + /// Generate type description information for the Validator + std::string get_description() const { + if(active_) { + return desc_function_(); + } + return std::string{}; + } + /// Specify the type string + Validator &name(std::string validator_name) { + name_ = std::move(validator_name); + return *this; + } + /// Specify the type string + Validator name(std::string validator_name) const { + Validator newval(*this); + newval.name_ = std::move(validator_name); + return newval; + } + /// Get the name of the Validator + const std::string &get_name() const { return name_; } + /// Specify whether the Validator is active or not + Validator &active(bool active_val = true) { + active_ = active_val; + return *this; + } + /// Specify whether the Validator is active or not + Validator active(bool active_val = true) const { + Validator newval(*this); + newval.active_ = active_val; + return newval; + } + + /// Specify whether the Validator can be modifying or not + Validator &non_modifying(bool no_modify = true) { + non_modifying_ = no_modify; + return *this; + } + /// Specify the application index of a validator + Validator &application_index(int app_index) { + application_index_ = app_index; + return *this; + } + /// Specify the application index of a validator + Validator application_index(int app_index) const { + Validator newval(*this); + newval.application_index_ = app_index; + return newval; + } + /// Get the current value of the application index + int get_application_index() const { return application_index_; } + /// Get a boolean if the validator is active + bool get_active() const { return active_; } + + /// Get a boolean if the validator is allowed to modify the input returns true if it can modify the input + bool get_modifying() const { return !non_modifying_; } + + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the + /// same. + Validator operator&(const Validator &other) const { + Validator newval; + + newval._merge_description(*this, other, " AND "); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(!s1.empty() && !s2.empty()) + return std::string("(") + s1 + ") AND (" + s2 + ")"; + else + return s1 + s2; + }; + + newval.active_ = (active_ & other.active_); + newval.application_index_ = application_index_; + return newval; + } + + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the + /// same. + Validator operator|(const Validator &other) const { + Validator newval; + + newval._merge_description(*this, other, " OR "); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(s1.empty() || s2.empty()) + return std::string(); + + return std::string("(") + s1 + ") OR (" + s2 + ")"; + }; + newval.active_ = (active_ & other.active_); + newval.application_index_ = application_index_; + return newval; + } + + /// Create a validator that fails when a given validator succeeds + Validator operator!() const { + Validator newval; + const std::function &dfunc1 = desc_function_; + newval.desc_function_ = [dfunc1]() { + auto str = dfunc1(); + return (!str.empty()) ? std::string("NOT ") + str : std::string{}; + }; + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + + newval.func_ = [f1, dfunc1](std::string &test) -> std::string { + std::string s1 = f1(test); + if(s1.empty()) { + return std::string("check ") + dfunc1() + " succeeded improperly"; + } + return std::string{}; + }; + newval.active_ = active_; + newval.application_index_ = application_index_; + return newval; + } + + private: + void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger) { + + const std::function &dfunc1 = val1.desc_function_; + const std::function &dfunc2 = val2.desc_function_; + + desc_function_ = [=]() { + std::string f1 = dfunc1(); + std::string f2 = dfunc2(); + if((f1.empty()) || (f2.empty())) { + return f1 + f2; + } + return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')'; + }; + } +}; // namespace CLI + +/// Class wrapping some of the accessors of Validator +class CustomValidator : public Validator { + public: +}; +// The implementation of the built in validators is using the Validator class; +// the user is only expected to use the const (static) versions (since there's no setup). +// Therefore, this is in detail. +namespace detail { + +/// CLI enumeration of different file types +enum class path_type { nonexistent, file, directory }; + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +/// get the type of the path from a file name +inline path_type check_path(const char *file) noexcept { + std::error_code ec; + auto stat = std::filesystem::status(file, ec); + if(ec) { + return path_type::nonexistent; + } + switch(stat.type()) { + case std::filesystem::file_type::none: + case std::filesystem::file_type::not_found: + return path_type::nonexistent; + case std::filesystem::file_type::directory: + return path_type::directory; + case std::filesystem::file_type::symlink: + case std::filesystem::file_type::block: + case std::filesystem::file_type::character: + case std::filesystem::file_type::fifo: + case std::filesystem::file_type::socket: + case std::filesystem::file_type::regular: + case std::filesystem::file_type::unknown: + default: + return path_type::file; + } +} +#else +/// get the type of the path from a file name +inline path_type check_path(const char *file) noexcept { +#if defined(_MSC_VER) + struct __stat64 buffer; + if(_stat64(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#else + struct stat buffer; + if(stat(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#endif + return path_type::nonexistent; +} +#endif +/// Check for an existing file (returns error message if check fails) +class ExistingFileValidator : public Validator { + public: + ExistingFileValidator() : Validator("FILE") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "File does not exist: " + filename; + } + if(path_result == path_type::directory) { + return "File is actually a directory: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an existing directory (returns error message if check fails) +class ExistingDirectoryValidator : public Validator { + public: + ExistingDirectoryValidator() : Validator("DIR") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Directory does not exist: " + filename; + } + if(path_result == path_type::file) { + return "Directory is actually a file: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an existing path +class ExistingPathValidator : public Validator { + public: + ExistingPathValidator() : Validator("PATH(existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Path does not exist: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an non-existing path +class NonexistentPathValidator : public Validator { + public: + NonexistentPathValidator() : Validator("PATH(non-existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result != path_type::nonexistent) { + return "Path already exists: " + filename; + } + return std::string(); + }; + } +}; + +/// Validate the given string is a legal ipv4 address +class IPV4Validator : public Validator { + public: + IPV4Validator() : Validator("IPV4") { + func_ = [](std::string &ip_addr) { + auto result = CLI::detail::split(ip_addr, '.'); + if(result.size() != 4) { + return std::string("Invalid IPV4 address must have four parts (") + ip_addr + ')'; + } + int num; + for(const auto &var : result) { + bool retval = detail::lexical_cast(var, num); + if(!retval) { + return std::string("Failed parsing number (") + var + ')'; + } + if(num < 0 || num > 255) { + return std::string("Each IP number must be between 0 and 255 ") + var; + } + } + return std::string(); + }; + } +}; + +} // namespace detail + +// Static is not needed here, because global const implies static. + +/// Check for existing file (returns error message if check fails) +const detail::ExistingFileValidator ExistingFile; + +/// Check for an existing directory (returns error message if check fails) +const detail::ExistingDirectoryValidator ExistingDirectory; + +/// Check for an existing path +const detail::ExistingPathValidator ExistingPath; + +/// Check for an non-existing path +const detail::NonexistentPathValidator NonexistentPath; + +/// Check for an IP4 address +const detail::IPV4Validator ValidIPV4; + +/// Validate the input as a particular type +template class TypeValidator : public Validator { + public: + explicit TypeValidator(const std::string &validator_name) : Validator(validator_name) { + func_ = [](std::string &input_string) { + auto val = DesiredType(); + if(!detail::lexical_cast(input_string, val)) { + return std::string("Failed parsing ") + input_string + " as a " + detail::type_name(); + } + return std::string(); + }; + } + TypeValidator() : TypeValidator(detail::type_name()) {} +}; + +/// Check for a number +const TypeValidator Number("NUMBER"); + +/// Produce a range (factory). Min and max are inclusive. +class Range : public Validator { + public: + /// This produces a range with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template + Range(T min, T max, const std::string &validator_name = std::string{}) : Validator(validator_name) { + if(validator_name.empty()) { + std::stringstream out; + out << detail::type_name() << " in [" << min << " - " << max << "]"; + description(out.str()); + } + + func_ = [min, max](std::string &input) { + T val; + bool converted = detail::lexical_cast(input, val); + if((!converted) || (val < min || val > max)) + return std::string("Value ") + input + " not in range " + std::to_string(min) + " to " + + std::to_string(max); + + return std::string(); + }; + } + + /// Range of one value is 0 to value + template + explicit Range(T max, const std::string &validator_name = std::string{}) + : Range(static_cast(0), max, validator_name) {} +}; + +/// Check for a non negative number +const Range NonNegativeNumber(std::numeric_limits::max(), "NONNEGATIVE"); + +/// Check for a positive valued number (val>0.0), min() her is the smallest positive number +const Range PositiveNumber(std::numeric_limits::min(), std::numeric_limits::max(), "POSITIVE"); + +/// Produce a bounded range (factory). Min and max are inclusive. +class Bound : public Validator { + public: + /// This bounds a value with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template Bound(T min, T max) { + std::stringstream out; + out << detail::type_name() << " bounded to [" << min << " - " << max << "]"; + description(out.str()); + + func_ = [min, max](std::string &input) { + T val; + bool converted = detail::lexical_cast(input, val); + if(!converted) { + return std::string("Value ") + input + " could not be converted"; + } + if(val < min) + input = detail::to_string(min); + else if(val > max) + input = detail::to_string(max); + + return std::string{}; + }; + } + + /// Range of one value is 0 to value + template explicit Bound(T max) : Bound(static_cast(0), max) {} +}; + +namespace detail { +template ::type>::value, detail::enabler> = detail::dummy> +auto smart_deref(T value) -> decltype(*value) { + return *value; +} + +template < + typename T, + enable_if_t::type>::value, detail::enabler> = detail::dummy> +typename std::remove_reference::type &smart_deref(T &value) { + return value; +} +/// Generate a string representation of a set +template std::string generate_set(const T &set) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(set), + [](const iteration_type_t &v) { return detail::pair_adaptor::first(v); }, + ",")); + out.push_back('}'); + return out; +} + +/// Generate a string representation of a map +template std::string generate_map(const T &map, bool key_only = false) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(map), + [key_only](const iteration_type_t &v) { + std::string res{detail::to_string(detail::pair_adaptor::first(v))}; + + if(!key_only) { + res.append("->"); + res += detail::to_string(detail::pair_adaptor::second(v)); + } + return res; + }, + ",")); + out.push_back('}'); + return out; +} + +template struct has_find { + template + static auto test(int) -> decltype(std::declval().find(std::declval()), std::true_type()); + template static auto test(...) -> decltype(std::false_type()); + + static const auto value = decltype(test(0))::value; + using type = std::integral_constant; +}; + +/// A search function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + using element_t = typename detail::element_type::type; + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) { + return (detail::pair_adaptor::first(v) == val); + }); + return {(it != std::end(setref)), it}; +} + +/// A search function that uses the built in find function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + auto &setref = detail::smart_deref(set); + auto it = setref.find(val); + return {(it != std::end(setref)), it}; +} + +/// A search function with a filter function +template +auto search(const T &set, const V &val, const std::function &filter_function) + -> std::pair { + using element_t = typename detail::element_type::type; + // do the potentially faster first search + auto res = search(set, val); + if((res.first) || (!(filter_function))) { + return res; + } + // if we haven't found it do the longer linear search with all the element translations + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) { + V a{detail::pair_adaptor::first(v)}; + a = filter_function(a); + return (a == val); + }); + return {(it != std::end(setref)), it}; +} + +// the following suggestion was made by Nikita Ofitserov(@himikof) +// done in templates to prevent compiler warnings on negation of unsigned numbers + +/// Do a check for overflow on signed numbers +template +inline typename std::enable_if::value, T>::type overflowCheck(const T &a, const T &b) { + if((a > 0) == (b > 0)) { + return ((std::numeric_limits::max)() / (std::abs)(a) < (std::abs)(b)); + } else { + return ((std::numeric_limits::min)() / (std::abs)(a) > -(std::abs)(b)); + } +} +/// Do a check for overflow on unsigned numbers +template +inline typename std::enable_if::value, T>::type overflowCheck(const T &a, const T &b) { + return ((std::numeric_limits::max)() / a < b); +} + +/// Performs a *= b; if it doesn't cause integer overflow. Returns false otherwise. +template typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { + if(a == 0 || b == 0 || a == 1 || b == 1) { + a *= b; + return true; + } + if(a == (std::numeric_limits::min)() || b == (std::numeric_limits::min)()) { + return false; + } + if(overflowCheck(a, b)) { + return false; + } + a *= b; + return true; +} + +/// Performs a *= b; if it doesn't equal infinity. Returns false otherwise. +template +typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { + T c = a * b; + if(std::isinf(c) && !std::isinf(a) && !std::isinf(b)) { + return false; + } + a = c; + return true; +} + +} // namespace detail +/// Verify items are in a set +class IsMember : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction using an initializer list + template + IsMember(std::initializer_list values, Args &&... args) + : IsMember(std::vector(values), std::forward(args)...) {} + + /// This checks to see if an item is in a set (empty function) + template explicit IsMember(T &&set) : IsMember(std::forward(set), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit IsMember(T set, F filter_function) { + + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + + using local_item_t = typename IsMemberType::type; // This will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); }; + + // This is the function that validates + // It stores a copy of the set pointer-like, so shared_ptr will stay alive + func_ = [set, filter_fn](std::string &input) { + local_item_t b; + if(!detail::lexical_cast(input, b)) { + throw ValidationError(input); // name is added later + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(set, b, filter_fn); + if(res.first) { + // Make sure the version in the input string is identical to the one in the set + if(filter_fn) { + input = detail::value_string(detail::pair_adaptor::first(*(res.second))); + } + + // Return empty error string (success) + return std::string{}; + } + + // If you reach this point, the result was not found + return input + " not in " + detail::generate_set(detail::smart_deref(set)); + }; + } + + /// You can pass in as many filter functions as you like, they nest (string only currently) + template + IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) + : IsMember( + std::forward(set), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// definition of the default transformation object +template using TransformPairs = std::vector>; + +/// Translate named items to other or a value set +class Transformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + Transformer(std::initializer_list> values, Args &&... args) + : Transformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit Transformer(T &&mapping) : Transformer(std::forward(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit Transformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); }; + + func_ = [mapping, filter_fn](std::string &input) { + local_item_t b; + if(!detail::lexical_cast(input, b)) { + return std::string(); + // there is no possible way we can match anything in the mapping if we can't convert so just return + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + } + return std::string{}; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) + : Transformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// translate named items to other or a value set +class CheckedTransformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + CheckedTransformer(std::initializer_list> values, Args &&... args) + : CheckedTransformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit CheckedTransformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + auto tfunc = [mapping]() { + std::string out("value in "); + out += detail::generate_map(detail::smart_deref(mapping)) + " OR {"; + out += detail::join( + detail::smart_deref(mapping), + [](const iteration_type_t &v) { return detail::to_string(detail::pair_adaptor::second(v)); }, + ","); + out.push_back('}'); + return out; + }; + + desc_function_ = tfunc; + + func_ = [mapping, tfunc, filter_fn](std::string &input) { + local_item_t b; + bool converted = detail::lexical_cast(input, b); + if(converted) { + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + return std::string{}; + } + } + for(const auto &v : detail::smart_deref(mapping)) { + auto output_string = detail::value_string(detail::pair_adaptor::second(v)); + if(output_string == input) { + return std::string(); + } + } + + return "Check " + input + " " + tfunc() + " FAILED"; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) + : CheckedTransformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// Helper function to allow ignore_case to be passed to IsMember or Transform +inline std::string ignore_case(std::string item) { return detail::to_lower(item); } + +/// Helper function to allow ignore_underscore to be passed to IsMember or Transform +inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); } + +/// Helper function to allow checks to ignore spaces to be passed to IsMember or Transform +inline std::string ignore_space(std::string item) { + item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item)); + item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item)); + return item; +} + +/// Multiply a number by a factor using given mapping. +/// Can be used to write transforms for SIZE or DURATION inputs. +/// +/// Example: +/// With mapping = `{"b"->1, "kb"->1024, "mb"->1024*1024}` +/// one can recognize inputs like "100", "12kb", "100 MB", +/// that will be automatically transformed to 100, 14448, 104857600. +/// +/// Output number type matches the type in the provided mapping. +/// Therefore, if it is required to interpret real inputs like "0.42 s", +/// the mapping should be of a type or . +class AsNumberWithUnit : public Validator { + public: + /// Adjust AsNumberWithUnit behavior. + /// CASE_SENSITIVE/CASE_INSENSITIVE controls how units are matched. + /// UNIT_OPTIONAL/UNIT_REQUIRED throws ValidationError + /// if UNIT_REQUIRED is set and unit literal is not found. + enum Options { + CASE_SENSITIVE = 0, + CASE_INSENSITIVE = 1, + UNIT_OPTIONAL = 0, + UNIT_REQUIRED = 2, + DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL + }; + + template + explicit AsNumberWithUnit(std::map mapping, + Options opts = DEFAULT, + const std::string &unit_name = "UNIT") { + description(generate_description(unit_name, opts)); + validate_mapping(mapping, opts); + + // transform function + func_ = [mapping, opts](std::string &input) -> std::string { + Number num; + + detail::rtrim(input); + if(input.empty()) { + throw ValidationError("Input is empty"); + } + + // Find split position between number and prefix + auto unit_begin = input.end(); + while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) { + --unit_begin; + } + + std::string unit{unit_begin, input.end()}; + input.resize(static_cast(std::distance(input.begin(), unit_begin))); + detail::trim(input); + + if(opts & UNIT_REQUIRED && unit.empty()) { + throw ValidationError("Missing mandatory unit"); + } + if(opts & CASE_INSENSITIVE) { + unit = detail::to_lower(unit); + } + if(unit.empty()) { + if(!detail::lexical_cast(input, num)) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // No need to modify input if no unit passed + return {}; + } + + // find corresponding factor + auto it = mapping.find(unit); + if(it == mapping.end()) { + throw ValidationError(unit + + " unit not recognized. " + "Allowed values: " + + detail::generate_map(mapping, true)); + } + + if(!input.empty()) { + bool converted = detail::lexical_cast(input, num); + if(!converted) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // perform safe multiplication + bool ok = detail::checked_multiply(num, it->second); + if(!ok) { + throw ValidationError(detail::to_string(num) + " multiplied by " + unit + + " factor would cause number overflow. Use smaller value."); + } + } else { + num = static_cast(it->second); + } + + input = detail::to_string(num); + + return {}; + }; + } + + private: + /// Check that mapping contains valid units. + /// Update mapping for CASE_INSENSITIVE mode. + template static void validate_mapping(std::map &mapping, Options opts) { + for(auto &kv : mapping) { + if(kv.first.empty()) { + throw ValidationError("Unit must not be empty."); + } + if(!detail::isalpha(kv.first)) { + throw ValidationError("Unit must contain only letters."); + } + } + + // make all units lowercase if CASE_INSENSITIVE + if(opts & CASE_INSENSITIVE) { + std::map lower_mapping; + for(auto &kv : mapping) { + auto s = detail::to_lower(kv.first); + if(lower_mapping.count(s)) { + throw ValidationError(std::string("Several matching lowercase unit representations are found: ") + + s); + } + lower_mapping[detail::to_lower(kv.first)] = kv.second; + } + mapping = std::move(lower_mapping); + } + } + + /// Generate description like this: NUMBER [UNIT] + template static std::string generate_description(const std::string &name, Options opts) { + std::stringstream out; + out << detail::type_name() << ' '; + if(opts & UNIT_REQUIRED) { + out << name; + } else { + out << '[' << name << ']'; + } + return out.str(); + } +}; + +/// Converts a human-readable size string (with unit literal) to uin64_t size. +/// Example: +/// "100" => 100 +/// "1 b" => 100 +/// "10Kb" => 10240 // you can configure this to be interpreted as kilobyte (*1000) or kibibyte (*1024) +/// "10 KB" => 10240 +/// "10 kb" => 10240 +/// "10 kib" => 10240 // *i, *ib are always interpreted as *bibyte (*1024) +/// "10kb" => 10240 +/// "2 MB" => 2097152 +/// "2 EiB" => 2^61 // Units up to exibyte are supported +class AsSizeValue : public AsNumberWithUnit { + public: + using result_t = std::uint64_t; + + /// If kb_is_1000 is true, + /// interpret 'kb', 'k' as 1000 and 'kib', 'ki' as 1024 + /// (same applies to higher order units as well). + /// Otherwise, interpret all literals as factors of 1024. + /// The first option is formally correct, but + /// the second interpretation is more wide-spread + /// (see https://en.wikipedia.org/wiki/Binary_prefix). + explicit AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) { + if(kb_is_1000) { + description("SIZE [b, kb(=1000b), kib(=1024b), ...]"); + } else { + description("SIZE [b, kb(=1024b), ...]"); + } + } + + private: + /// Get mapping + static std::map init_mapping(bool kb_is_1000) { + std::map m; + result_t k_factor = kb_is_1000 ? 1000 : 1024; + result_t ki_factor = 1024; + result_t k = 1; + result_t ki = 1; + m["b"] = 1; + for(std::string p : {"k", "m", "g", "t", "p", "e"}) { + k *= k_factor; + ki *= ki_factor; + m[p] = k; + m[p + "b"] = k; + m[p + "i"] = ki; + m[p + "ib"] = ki; + } + return m; + } + + /// Cache calculated mapping + static std::map get_mapping(bool kb_is_1000) { + if(kb_is_1000) { + static auto m = init_mapping(true); + return m; + } else { + static auto m = init_mapping(false); + return m; + } + } +}; + +namespace detail { +/// Split a string into a program name and command line arguments +/// the string is assumed to contain a file name followed by other arguments +/// the return value contains is a pair with the first argument containing the program name and the second +/// everything else. +inline std::pair split_program_name(std::string commandline) { + // try to determine the programName + std::pair vals; + trim(commandline); + auto esp = commandline.find_first_of(' ', 1); + while(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) { + esp = commandline.find_first_of(' ', esp + 1); + if(esp == std::string::npos) { + // if we have reached the end and haven't found a valid file just assume the first argument is the + // program name + if(commandline[0] == '"' || commandline[0] == '\'' || commandline[0] == '`') { + bool embeddedQuote = false; + auto keyChar = commandline[0]; + auto end = commandline.find_first_of(keyChar, 1); + while((end != std::string::npos) && (commandline[end - 1] == '\\')) { // deal with escaped quotes + end = commandline.find_first_of(keyChar, end + 1); + embeddedQuote = true; + } + if(end != std::string::npos) { + vals.first = commandline.substr(1, end - 1); + esp = end + 1; + if(embeddedQuote) { + vals.first = find_and_replace(vals.first, std::string("\\") + keyChar, std::string(1, keyChar)); + embeddedQuote = false; + } + } else { + esp = commandline.find_first_of(' ', 1); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + + break; + } + } + if(vals.first.empty()) { + vals.first = commandline.substr(0, esp); + rtrim(vals.first); + } + + // strip the program name + vals.second = (esp != std::string::npos) ? commandline.substr(esp + 1) : std::string{}; + ltrim(vals.second); + return vals; +} + +} // namespace detail +/// @} + + + + +class Option; +class App; + +/// This enum signifies the type of help requested +/// +/// This is passed in by App; all user classes must accept this as +/// the second argument. + +enum class AppFormatMode { + Normal, ///< The normal, detailed help + All, ///< A fully expanded help + Sub, ///< Used when printed as part of expanded subcommand +}; + +/// This is the minimum requirements to run a formatter. +/// +/// A user can subclass this is if they do not care at all +/// about the structure in CLI::Formatter. +class FormatterBase { + protected: + /// @name Options + ///@{ + + /// The width of the first column + std::size_t column_width_{30}; + + /// @brief The required help printout labels (user changeable) + /// Values are Needs, Excludes, etc. + std::map labels_{}; + + ///@} + /// @name Basic + ///@{ + + public: + FormatterBase() = default; + FormatterBase(const FormatterBase &) = default; + FormatterBase(FormatterBase &&) = default; + + /// Adding a destructor in this form to work around bug in GCC 4.7 + virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default) + + /// This is the key method that puts together help + virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0; + + ///@} + /// @name Setters + ///@{ + + /// Set the "REQUIRED" label + void label(std::string key, std::string val) { labels_[key] = val; } + + /// Set the column width + void column_width(std::size_t val) { column_width_ = val; } + + ///@} + /// @name Getters + ///@{ + + /// Get the current value of a name (REQUIRED, etc.) + std::string get_label(std::string key) const { + if(labels_.find(key) == labels_.end()) + return key; + else + return labels_.at(key); + } + + /// Get the current column width + std::size_t get_column_width() const { return column_width_; } + + ///@} +}; + +/// This is a specialty override for lambda functions +class FormatterLambda final : public FormatterBase { + using funct_t = std::function; + + /// The lambda to hold and run + funct_t lambda_; + + public: + /// Create a FormatterLambda with a lambda function + explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {} + + /// Adding a destructor (mostly to make GCC 4.7 happy) + ~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default) + + /// This will simply call the lambda function + std::string make_help(const App *app, std::string name, AppFormatMode mode) const override { + return lambda_(app, name, mode); + } +}; + +/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few +/// overridable methods, to be highly customizable with minimal effort. +class Formatter : public FormatterBase { + public: + Formatter() = default; + Formatter(const Formatter &) = default; + Formatter(Formatter &&) = default; + + /// @name Overridables + ///@{ + + /// This prints out a group of options with title + /// + virtual std::string make_group(std::string group, bool is_positional, std::vector opts) const; + + /// This prints out just the positionals "group" + virtual std::string make_positionals(const App *app) const; + + /// This prints out all the groups of options + std::string make_groups(const App *app, AppFormatMode mode) const; + + /// This prints out all the subcommands + virtual std::string make_subcommands(const App *app, AppFormatMode mode) const; + + /// This prints out a subcommand + virtual std::string make_subcommand(const App *sub) const; + + /// This prints out a subcommand in help-all + virtual std::string make_expanded(const App *sub) const; + + /// This prints out all the groups of options + virtual std::string make_footer(const App *app) const; + + /// This displays the description line + virtual std::string make_description(const App *app) const; + + /// This displays the usage line + virtual std::string make_usage(const App *app, std::string name) const; + + /// This puts everything together + std::string make_help(const App * /*app*/, std::string, AppFormatMode) const override; + + ///@} + /// @name Options + ///@{ + + /// This prints out an option help line, either positional or optional form + virtual std::string make_option(const Option *opt, bool is_positional) const { + std::stringstream out; + detail::format_help( + out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_); + return out.str(); + } + + /// @brief This is the name part of an option, Default: left column + virtual std::string make_option_name(const Option *, bool) const; + + /// @brief This is the options part of the name, Default: combined into left column + virtual std::string make_option_opts(const Option *) const; + + /// @brief This is the description. Default: Right column, on new line if left column too large + virtual std::string make_option_desc(const Option *) const; + + /// @brief This is used to print the name on the USAGE line + virtual std::string make_option_usage(const Option *opt) const; + + ///@} +}; + + + + +using results_t = std::vector; +/// callback function definition +using callback_t = std::function; + +class Option; +class App; + +using Option_p = std::unique_ptr