Compare commits

..
Author SHA1 Message Date
Benjamin Sergeant 067c128474 try on Windows 2019-05-22 09:29:27 -07:00
Benjamin Sergeant a127d9ef0d activate IXWebSocketMessageQTest on mac only 2019-05-22 09:22:50 -07:00
Benjamin Sergeant 4a845e3cc4 stop server at the end of unittest 2019-05-21 19:53:55 -07:00
Benjamin Sergeant 633afa5bed try to enable IXWebSocketMessageQTest.cpp 2019-05-21 19:45:55 -07:00
Kumamon38 49fd2a9e53 Clean (#82)
Thanks
2019-05-21 12:14:58 -07:00
Kumamon38 6264a8b41d Fix ping (#80)
* let poll do his job when closing

* try fix test

* try fix test

* Update IXWebSocketTransport.cpp

* add log to find issue on CI

* add log to find issue on CI

* add log to find issue on CI

* add log to find issue on CI

* add log to find issue on CI

* change state immediately, and send close frame after

* add immediate close test

* disable test for windows

* reenable ping / ping timeout tests

* add time to let windows close client

* reenable ping timeout test

* add 100ms more

* disable test for windows
2019-05-21 09:35:41 -07:00
Kumamon38 3990d3bcbf fix close bug and tests : let poll do his job when closing (#79)
* let poll do his job when closing

* try fix test

* try fix test

* Update IXWebSocketTransport.cpp

* add log to find issue on CI

* add log to find issue on CI

* add log to find issue on CI

* add log to find issue on CI

* add log to find issue on CI

* change state immediately, and send close frame after

* add immediate close test

* disable test for windows
2019-05-21 09:34:08 -07:00
Benjamin Sergeant aa3f201ced one cpu on windows for executing tests 2019-05-17 15:45:31 -07:00
Benjamin Sergeant 83c261977d add back IXWebSocketMessageQueue, with its unittest disabled 2019-05-16 22:41:39 -07:00
Benjamin Sergeant 6ca28d96bf Linux build fix: strncpy needs <string.h> 2019-05-16 22:21:15 -07:00
Benjamin Sergeant c4a5647b62 Revert "Merge branch 'Dimon4eg-message-queue'"
This reverts commit 13fa325134, reversing
changes made to aecd5e9c94.
2019-05-16 22:15:17 -07:00
Benjamin Sergeant 720d5593a5 Fix Address Sanitizer heap-buffer-overflow in WebSocketHandshakeKeyGen::generate
=================================================================
==5077==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6070000077e0 at pc 0x00010ba18c54 bp 0x70000dd45b10 sp 0x70000dd45b08
READ of size 1 at 0x6070000077e0 thread T12
    #0 0x10ba18c53 in WebSocketHandshakeKeyGen::generate(char const*, char*) libwshandshake.hpp:113
    #1 0x10ba2065a in ix::WebSocketHandshake::serverHandshake(int, int) IXWebSocketHandshake.cpp:356
    #2 0x10b9c4952 in ix::WebSocketTransport::connectToSocket(int, int) IXWebSocketTransport.cpp:190
    #3 0x10b97e4c2 in ix::WebSocket::connectToSocket(int, int) IXWebSocket.cpp:193
2019-05-16 21:59:03 -07:00
Benjamin Sergeant 13fa325134 Merge branch 'Dimon4eg-message-queue' 2019-05-16 19:26:45 -07:00
Benjamin Sergeant 773cbb4907 bring back socket mutex which is needed, some CI failures are happening without it 2019-05-16 19:23:32 -07:00
Benjamin Sergeant a696264b48 disable socket mutex usage in WebSocketTransport 2019-05-16 19:23:32 -07:00
Benjamin Sergeant b7db5f77fb remove dead code 2019-05-16 19:23:32 -07:00
Benjamin Sergeant b11678e636 refactor connect unittest so that it hits a local server instead of a remote server 2019-05-16 19:23:32 -07:00
Benjamin Sergeant f746070944 travis makefile fix 2019-05-16 19:23:32 -07:00
Benjamin Sergeant 3323a51ab5 try to run ws test on linux + macOS on travis 2019-05-16 19:23:32 -07:00
Benjamin Sergeant 0e59927384 Add constants for closing code and messages 2019-05-16 19:23:32 -07:00
Benjamin Sergeant 5c4840f129 first socket test hit a local server instead of a remote server / this can help with a windows intermittent failure 2019-05-16 19:23:32 -07:00
Benjamin Sergeant 9ac02323ad build ws on travis (mac + linux) 2019-05-16 19:23:32 -07:00
Benjamin Sergeant cdbed26d1f use a regular mutex instead of a recursive one + stop properly 2019-05-16 19:23:32 -07:00
Benjamin Sergeant 23f171f34d adding logging to IXWebSocketTestConnectionDisconnection makes it fails reliably 2019-05-16 19:23:32 -07:00
Benjamin Sergeant 20b625e483 Update README.md 2019-05-16 19:23:32 -07:00
Benjamin Sergeant f1604c6460 Update README.md 2019-05-16 19:23:32 -07:00
Benjamin Sergeant ba0e007c05 -j option actually work ... 2019-05-16 19:23:32 -07:00
Benjamin Sergeant 643e1bf20f unittest / add options to set the number of jobs 2019-05-16 19:23:32 -07:00
Benjamin Sergeant 24a32a0603 enum class HttpErrorCode derives from int 2019-05-16 19:23:32 -07:00
Benjamin Sergeant c5caf32b77 try to re-enable some tests 2019-05-16 19:23:32 -07:00
Benjamin Sergeant 09956d7500 recursive mutex + enable test that was breaking on Ubuntu Xenial + gcc + tsan 2019-05-16 19:23:32 -07:00
Benjamin Sergeant d91c896e46 comment failing test 2019-05-16 19:23:32 -07:00
Benjamin Sergeant 042e6a22b8 comment failing test 2019-05-16 19:23:32 -07:00
Benjamin Sergeant 14ec12d1f0 do not build ws for now on travis 2019-05-16 19:23:32 -07:00
Benjamin Sergeant 288b05a048 more protection against socket when closing 2019-05-16 19:23:32 -07:00
Benjamin Sergeant 5af3096070 fix compile errors with C++ enum class 2019-05-16 19:23:32 -07:00
Benjamin Sergeant 570fa01c04 close and stop with code and reason + docker = ubuntu xenial 2019-05-16 19:23:32 -07:00
Dimon4eg 2a69038c4c add isEnabledAutomaticReconnection (#75)
* add isEnabledAutomaticReconnection

* test isEnabledAutomaticReconnection

* rename
2019-05-16 19:23:32 -07:00
Benjamin Sergeant 0ba127e447 Revert "Revert "fix cast warning caught on windows""
This reverts commit 25eaf730bc.
2019-05-16 19:23:32 -07:00
Benjamin Sergeant 7714bdf7e0 Revert "fix cast warning caught on windows"
This reverts commit 4edb7447df.
2019-05-16 19:23:32 -07:00
Benjamin Sergeant 4e5e7ae50a fix cast warning caught on windows 2019-05-16 19:23:32 -07:00
Kumamon38 5741b2f6c1 add more time to let client close (#73) 2019-05-16 19:23:32 -07:00
Benjamin Sergeant 76172f92e9 build with gcc on Linux 2019-05-16 19:23:32 -07:00
Benjamin Sergeant f8b547c028 use spdlog for logging in the unittest 2019-05-16 19:23:32 -07:00
Benjamin Sergeant 7ccd9e1709 fix inverted conditional 2019-05-16 19:23:31 -07:00
Benjamin Sergeant 9217b27d40 server code / add dedicated thread to close/join terminated connection threads 2019-05-16 19:23:31 -07:00
Benjamin Sergeant 819e9025b1 travis cmake version on macOS does not know --parallel option 2019-05-16 19:23:31 -07:00
Benjamin Sergeant 53ceab9f91 build in parallel + stop building linux + clang 2019-05-16 19:23:31 -07:00
Benjamin Sergeant a7ed4fe5c3 disable ping tests for now as they are not super reliable 2019-05-16 19:23:31 -07:00
Benjamin Sergeant 3190cd322d Feature/windows ci (#76)
* close with params

* ...

* different generator

* core size = 1

* disable more tests to get something working on windows

* try to enable another test on windows

* enable all OS

* set proper version of linux

* another try

* try again with just env variables

* Revert "core size = 1"

This reverts commit 29af74bba6.

* add windows and mac

* Revert "close with params"

This reverts commit 6bb00b6788.
2019-05-16 19:23:31 -07:00
Kumamon38 dad2b64e15 save timepoints after connect and not in contructor, adjusted tests (#72)
* save timepoints after connect and not in contructor, adjusted tests

* move call into setReadyState

* more time to detect client close in test
2019-05-16 19:20:29 -07:00
Dimon4eg e527ab1613 fix for Windows (#69)
* fix for Windows

* fix condition

* make condition only on Windows
2019-05-16 19:20:29 -07:00
Dimon4eg d7a0bc212d Fix run.py (#71)
* fix run.py

* run.py: fix Windows support

* fix test listing
2019-05-16 19:20:29 -07:00
Benjamin Sergeant aecd5e9c94 bring back socket mutex which is needed, some CI failures are happening without it 2019-05-16 15:58:20 -07:00
Benjamin Sergeant e0edca43d5 disable socket mutex usage in WebSocketTransport 2019-05-16 15:46:32 -07:00
Benjamin Sergeant ce70d3d728 remove dead code 2019-05-16 15:05:20 -07:00
Benjamin Sergeant d9be40a0de refactor connect unittest so that it hits a local server instead of a remote server 2019-05-16 14:25:31 -07:00
Benjamin Sergeant e469f04c39 travis makefile fix 2019-05-16 14:02:24 -07:00
Benjamin Sergeant 11774e6825 try to run ws test on linux + macOS on travis 2019-05-16 13:57:33 -07:00
Benjamin Sergeant 42bdfb51c3 Add constants for closing code and messages 2019-05-16 12:46:53 -07:00
Benjamin Sergeant fd637bf1e1 first socket test hit a local server instead of a remote server / this can help with a windows intermittent failure 2019-05-16 12:24:58 -07:00
Benjamin Sergeant 8085e1416c build ws on travis (mac + linux) 2019-05-16 07:01:15 -07:00
Benjamin Sergeant 671c9f805f use a regular mutex instead of a recursive one + stop properly 2019-05-15 19:26:02 -07:00
Benjamin Sergeant ace7a7ccae adding logging to IXWebSocketTestConnectionDisconnection makes it fails reliably 2019-05-15 19:26:02 -07:00
Benjamin Sergeant 9c3bdf1a77 Update README.md 2019-05-15 19:22:05 -07:00
Benjamin Sergeant f5242b3102 Update README.md 2019-05-15 18:57:17 -07:00
Benjamin Sergeant f1272f059a -j option actually work ... 2019-05-15 18:15:45 -07:00
Benjamin Sergeant 91595ff4c2 unittest / add options to set the number of jobs 2019-05-15 17:52:03 -07:00
Benjamin Sergeant 3755d29a45 enum class HttpErrorCode derives from int 2019-05-15 16:50:00 -07:00
Benjamin Sergeant c2b75399ae try to re-enable some tests 2019-05-15 16:28:29 -07:00
Benjamin Sergeant a33ecd1338 recursive mutex + enable test that was breaking on Ubuntu Xenial + gcc + tsan 2019-05-15 16:01:05 -07:00
Benjamin Sergeant a7e29a9f36 comment failing test 2019-05-15 15:44:14 -07:00
Benjamin Sergeant 02399dfa5c comment failing test 2019-05-15 15:37:30 -07:00
Benjamin Sergeant aec2941bac do not build ws for now on travis 2019-05-15 15:26:49 -07:00
Benjamin Sergeant 9315eb5289 more protection against socket when closing 2019-05-15 15:18:46 -07:00
Benjamin Sergeant 5b2b2ea7b0 fix compile errors with C++ enum class 2019-05-15 15:18:46 -07:00
Benjamin Sergeant d90b634e80 close and stop with code and reason + docker = ubuntu xenial 2019-05-15 15:18:46 -07:00
Dimon4eg 6dd8cda074 add isEnabledAutomaticReconnection (#75)
* add isEnabledAutomaticReconnection

* test isEnabledAutomaticReconnection

* rename
2019-05-14 11:26:37 -07:00
Benjamin Sergeant 701be31554 Revert "Revert "fix cast warning caught on windows""
This reverts commit 25eaf730bc.
2019-05-13 22:16:49 -07:00
Benjamin Sergeant 25eaf730bc Revert "fix cast warning caught on windows"
This reverts commit 4edb7447df.
2019-05-13 21:35:34 -07:00
Benjamin Sergeant 4edb7447df fix cast warning caught on windows 2019-05-13 21:29:47 -07:00
Kumamon38 5f3de60962 add more time to let client close (#73) 2019-05-13 21:26:34 -07:00
Benjamin Sergeant 79c17aba49 build with gcc on Linux 2019-05-13 17:35:21 -07:00
Benjamin Sergeant 80a90496d9 use spdlog for logging in the unittest 2019-05-13 17:32:57 -07:00
Benjamin Sergeant bbca803840 fix inverted conditional 2019-05-13 17:18:07 -07:00
Benjamin Sergeant 160d3869a9 server code / add dedicated thread to close/join terminated connection threads 2019-05-13 17:17:35 -07:00
Benjamin Sergeant afd8f64da8 travis cmake version on macOS does not know --parallel option 2019-05-13 17:17:35 -07:00
Benjamin Sergeant 6d2548b823 build in parallel + stop building linux + clang 2019-05-13 17:06:56 -07:00
Benjamin Sergeant 642356d353 disable ping tests for now as they are not super reliable 2019-05-13 17:01:22 -07:00
Benjamin Sergeant ba0fa36c2a Feature/windows ci (#76)
* close with params

* ...

* different generator

* core size = 1

* disable more tests to get something working on windows

* try to enable another test on windows

* enable all OS

* set proper version of linux

* another try

* try again with just env variables

* Revert "core size = 1"

This reverts commit 29af74bba6.

* add windows and mac

* Revert "close with params"

This reverts commit 6bb00b6788.
2019-05-13 16:51:58 -07:00
Kumamon38 12f6cd878d save timepoints after connect and not in contructor, adjusted tests (#72)
* save timepoints after connect and not in contructor, adjusted tests

* move call into setReadyState

* more time to detect client close in test
2019-05-13 09:08:46 -07:00
Dimon4eg 9aacebbbaf fix for Windows (#69)
* fix for Windows

* fix condition

* make condition only on Windows
2019-05-12 22:21:56 -07:00
Dimon4eg 701c3745c2 Fix run.py (#71)
* fix run.py

* run.py: fix Windows support

* fix test listing
2019-05-12 18:37:22 -07:00
dimon4eg a41d08343c Merge branch 'master' into message-queue 2019-05-12 22:00:10 +03:00
Benjamin Sergeant 156288b17b all derived class use final keyword 2019-05-12 11:43:21 -07:00
dimon4eg 6467f98241 add setOnMessageCallback with r-value 2019-05-12 20:59:18 +03:00
dimon4eg b24e4334f6 correct style 2019-05-12 20:16:02 +03:00
dimon4eg bf8abcbf4a fix warnings 2019-05-12 20:05:28 +03:00
dimon4eg bb484414b1 update comment 2019-05-12 20:00:15 +03:00
dimon4eg fc75b13fae update test 2019-05-12 19:57:31 +03:00
dimon4eg 78f59b4207 added message queue test 2019-05-12 01:50:41 +03:00
dimon4eg 7c5567db56 Added WebSocketMessageQueue 2019-05-12 01:49:06 +03:00
Benjamin Sergeant ed0e23e8a5 bump version to 2.0.0 2019-05-11 14:22:41 -07:00
Dimon4eg 4c4f99606e use C++11 enums (#67)
* use C++11 enums

* small rename

* update tests

* update tests

* update ws

* update ws

* update README.md
2019-05-11 14:22:06 -07:00
Benjamin Sergeant a61586c846 add comment about why a unittest is disabled 2019-05-11 12:25:40 -07:00
Benjamin Sergeant d64d50c978 remove irrelevant comment 2019-05-11 12:24:11 -07:00
Dimon4eg a64b7b0c4a minor improvements (#66)
* minor improvements

* fix build

* improve tests code
2019-05-11 12:20:58 -07:00
Benjamin Sergeant 0caeb81327 minor tweaks to have full feature parity before unittest broke 2019-05-11 11:54:21 -07:00
Benjamin Sergeant edac7a0171 fix race condition in SelectInteruptPipe, where _fildes are not protected (caught by fedora tsan) 2019-05-11 11:45:26 -07:00
Dimon4eg abfadad2e9 remove more iostream includes (#65) 2019-05-11 11:27:58 -07:00
Benjamin Sergeant 2dc1547bbd rename some variables, minor cleanup 2019-05-11 10:24:28 -07:00
dimon4eg 5eb23c9764 uncomment test 2019-05-11 10:15:22 -07:00
dimon4eg 9f4b2856b0 fix crash on close 2019-05-11 10:15:22 -07:00
Dimon4eg b5fc10326e fix crash on close 2019-05-11 10:12:33 -07:00
Dimon4eg 8d3a47a873 Fix crash during closing on Windows (#64)
* fix crash on close

* Improve calculateRetryWaitMilliseconds (#63)

* improve calculateRetryWaitMilliseconds

* update comment

* cout -> spdlog

* fix crash on close

* uncomment test

* Revert "uncomment test"

This reverts commit 27df86ee8f.
2019-05-11 09:51:26 -07:00
Benjamin Sergeant 4df58f3059 fix warning in statsd_client about %m gnu only printf special char 2019-05-11 09:22:29 -07:00
Benjamin Sergeant 06b8cb8d3b fix overflow warning in msgpack11.cpp 2019-05-10 21:17:05 -07:00
Benjamin Sergeant ff81f5b496 add env var to display the ws command typed in 2019-05-10 16:27:23 -07:00
Benjamin Sergeant c89f73006e cout -> spdlog 2019-05-10 12:33:34 -07:00
Dimon4eg c28951f049 Improve calculateRetryWaitMilliseconds (#63)
* improve calculateRetryWaitMilliseconds

* update comment
2019-05-10 12:31:21 -07:00
Benjamin Sergeant dfaaaca223 fix static analyzer thing with un-used variable 2019-05-09 16:57:58 -07:00
Benjamin Sergeant c7f0bf3d64 use spdlog for logging in ws + unittest + remove un-needed mutex 2019-05-09 15:30:44 -07:00
Benjamin Sergeant 234ce4c173 cout -> cerr 2019-05-09 15:06:42 -07:00
tiwariashish86 f60293b2e7 Fixed pong synchronization issue (#62)
* Fixed pong synchronization issue

* Minor optimization in lock by scoping it to necessary changes.

* Fixing compilation issues
2019-05-09 15:06:05 -07:00
Benjamin Sergeant 9441095637 tweak unittest sleep duration to fix gcc+linux CI 2019-05-09 11:10:39 -07:00
Kumamon38 f82d38f758 Fail test GCC TSAN (#61)
* test that fails on Run 8

* commented the failing test
2019-05-09 09:33:18 -07:00
Benjamin Sergeant a7f42f35db warning police 2019-05-09 09:25:56 -07:00
Kumamon38 cb1d1bfd85 fix ping, fix send frame close (#49)
* fix ping, fix send frame close

* fixes for data race on _closeCode etc. and fix test

* fixing one TC

* fix waiting forever if no time to change of readyState, and poll never end

* add 1005 code if no status code received

* fixes for 1005 code

* fix test issue

* fix macOS issue

* revert to master tests and renaming
2019-05-09 09:21:05 -07:00
Benjamin Sergeant 28c3f2ea26 IXCobraMetricsThreadedPublisher.cpp uses a lambda to log instead of std::cerr 2019-05-08 18:53:32 -07:00
dimon4eg 7ecaf1f982 rename ptr 2019-05-09 01:05:47 +03:00
dimon4eg d0a41f3894 simplify bindWebsocket 2019-05-09 00:23:16 +03:00
dimon4eg 57562b234f use lock_guard 2019-05-09 00:20:26 +03:00
dimon4eg 469d127d61 update comments 2019-05-09 00:16:37 +03:00
dimon4eg d6e9b61c8e Rename to WebSocketMessageQueue 2019-05-09 00:09:51 +03:00
Benjamin Sergeant 8dc132dbd3 change default ports for the ws command line tool 2019-05-08 13:56:42 -07:00
Benjamin Sergeant 98e2fbca6a ws connect display more accurate messages for incoming messages 2019-05-08 13:56:42 -07:00
Dimon4eg fa7f0fadde Remove redundant iostream includes (#60) 2019-05-08 13:33:21 -07:00
Dimon4eg 7fb1b65ddd qf 2019-05-08 22:24:39 +03:00
Dimon4eg 77c7fdc636 Added IXWebSocketPoll class 2019-05-08 22:02:56 +03:00
Dimon4eg 2732dfd0f1 set thread name for Windows (#57) 2019-05-08 07:43:43 -07:00
Benjamin Sergeant 2e4c4b72b6 update appveyor windows build file 2019-05-06 17:50:55 -07:00
Dimon4eg fc21ad519b update README.md (#54)
Yeah !
2019-05-06 15:02:16 -07:00
Dimon4eg c65cfd3d26 Use LUrlParser to fix issue of Windows (#53)
LGTM
2019-05-06 14:45:02 -07:00
Dimon4eg 8955462f73 added tests for IXUrlParser (#52)
* added tests for IXUrlParser

* add me as author
2019-05-06 12:47:15 -07:00
Benjamin Sergeant 205c8c15bd socket server / used wrong mutex to protect _connectionsThreads 2019-05-06 12:24:20 -07:00
Dimon4eg 78198a0147 Fix windows (#51)
* More fixes for Windows

* fix tests for windows

* qf for linux

* clean up
2019-05-06 12:22:57 -07:00
Benjamin Sergeant d561e1141e Update README.md 2019-05-06 09:22:52 -07:00
Dimon4eg 753fc845ac Fix for windows (#50) 2019-05-06 09:13:42 -07:00
Benjamin Sergeant 5dbc00bbfe doc: add reference to the conan file built at https://github.com/Zinnion/conan-IXWebSocket 2019-05-01 21:31:32 -07:00
Benjamin Sergeant 14ec8522ef remove un-needed _backgroundThreadRunning variable 2019-05-01 11:09:25 -07:00
Benjamin Sergeant 0c2d1c22bc Make AutomaticReconnection optional (#47)
* unittest pass + commands behave as expected

* cleanup
2019-04-29 21:12:34 -07:00
Benjamin Sergeant 1d39a9c9a9 build fix 2019-04-29 20:54:00 -07:00
Benjamin Sergeant b588ed0fa1 tsan fixes on ubuntu xenial (what travis run) 2019-04-29 20:48:16 -07:00
Benjamin Sergeant d9f7a138b8 dns lookup: fix race condition accessing _errMsg 2019-04-29 19:29:27 -07:00
Benjamin Sergeant d3e04ff619 tsan linux tentative fix / copy string instead of passing a const reference 2019-04-29 17:27:53 -07:00
Benjamin Sergeant 372dd24cc7 rename _blocking to _backgroundThreadRunning and invert the naming 2019-04-29 16:54:08 -07:00
Alexandre Konieczny a9422cf34d fix data race on _thread 2019-04-29 16:46:16 -07:00
Alexandre Konieczny c7e52e6fcd fix data race on _useMask 2019-04-29 16:41:34 -07:00
Benjamin Sergeant 705e0823cb ws connect mode / add a flag to disable automatic reconnection, not hooked up yet 2019-04-29 14:31:29 -07:00
Benjamin Sergeant 8e4cf74974 enable tsan on travis for all configs 2019-04-29 09:11:16 -07:00
Benjamin Sergeant 0a7157655b initialize netSystem (aka winsock on windows) explicitely 2019-04-25 16:38:15 -07:00
Dimon4eg 58d65926bb Fixes for windows (#45)
* init Net system on Windows

* propagate DNS error

* Add zlib 1.2.11 sources

* link zlib statically for windows

* remove not implemented function declaration

* fix connect on Windows
2019-04-25 16:26:53 -07:00
Benjamin Sergeant b178ba16af fix indentation of greatestCommonDivisor 2019-04-25 16:21:36 -07:00
Benjamin Sergeant e4c09284b5 Remove commented code 2019-04-25 16:16:52 -07:00
Benjamin Sergeant 9367a1feff Fix data race in WebSocket where _url is accessed without protection in setThreadName
Also fix with url usage + docker container uses fedora and works with tsan
2019-04-25 16:11:46 -07:00
Benjamin Sergeant d37ed300e2 disable failing unittest temporarily 2019-04-25 09:04:35 -07:00
Dimon4eg 3207ce37b6 Speedup build for Windows (#43)
* Speedup build for Windows

* add space :)
2019-04-25 07:41:01 -07:00
Benjamin Sergeant d036ad7138 tsan fix for the IXWebSocketServerTest test, where there's a data race for connectionId 2019-04-24 22:11:14 -07:00
Benjamin Sergeant 4fe07579b9 Fix data races in DNSLookup (tsan) 2019-04-24 21:53:31 -07:00
Benjamin Sergeant f563d14134 better server termination / another try at preventing thread join failures 2019-04-24 09:45:53 -07:00
Benjamin Sergeant f1b3ecc738 compiler warning police 2019-04-24 09:45:03 -07:00
Benjamin Sergeant 8387f89115 Fix #38 Add some docker doc in the README 2019-04-23 20:51:58 -07:00
Benjamin Sergeant 773f92347f ws cobra publish stress mode fix 2019-04-23 20:51:58 -07:00
Kumamon38 8ff1339b80 add boolean and add missing protocol error close constant (#41) 2019-04-23 04:31:55 -07:00
Benjamin Sergeant c85d5da111 add example websocket C++ server snake which supports basic cobra ops (publish and subscribe without stream sql 2019-04-22 17:33:45 -07:00
Benjamin Sergeant 9ab7bc652a (server) attempt to fix broken macOS unittest on travis CI 2019-04-22 09:36:16 -07:00
Benjamin Sergeant e5c724eb05 Expand build section in the main README 2019-04-21 21:11:08 -07:00
Benjamin Sergeant e0300903d9 Merge branch 'dhruvkakadiya-readme/fix' 2019-04-21 16:14:35 -07:00
dhruvkakadiya 1ef38afcf7 For #39, fixed setOnMessageCallback() in README. 2019-04-21 14:56:02 -07:00
Benjamin Sergeant 210d19c8a0 doc cobra 2019-04-21 11:52:38 -07:00
Benjamin Sergeant 6d24cc44b2 new target to uninstall files 2019-04-21 11:47:57 -07:00
Benjamin Sergeant 768e8eb074 Fix #37 / add directives to install headers and library 2019-04-21 11:42:37 -07:00
Benjamin Sergeant 3dd902e1f9 move cobra files to their own subfolder 2019-04-21 11:20:17 -07:00
Benjamin Sergeant f85c5002b7 add cobra metrics publisher 2019-04-21 11:16:33 -07:00
Benjamin Sergeant d48bf9249b indentation / comestic changes 2019-04-19 16:57:38 -07:00
Benjamin Sergeant 0dfc66f1c7 (test) / use a random number generator to get a free port, when the bind to port 0 strategy does not work out 2019-04-19 16:50:04 -07:00
Benjamin Sergeant 4564173b75 (socket server) wait for all connections threads to be terminated before exiting stop method 2019-04-19 16:31:33 -07:00
Benjamin Sergeant b60e5aaf1f default sanitizer choice 2019-04-19 15:13:59 -07:00
Benjamin Sergeant da67f4cb9a disable clang sanitizers in CI on any platforms but Darwin 2019-04-19 15:09:01 -07:00
Benjamin Sergeant b041042473 fix Linux cast warning 2019-04-19 15:03:49 -07:00
Benjamin Sergeant f83263d6a1 (unittest) upgrade to Catch2 version 2.7.1 2019-04-19 14:41:03 -07:00
Benjamin Sergeant b0139c2217 add locks around Socket::send and Socket::recv to see if it helps with thread sanitizer error in Linux CI 2019-04-19 14:28:51 -07:00
Benjamin Sergeant 0ba2e2ce96 uses sh syntax to capture output 2019-04-19 12:40:39 -07:00
Benjamin Sergeant 4a91ad80c8 (ci) verbose mode to figure out Linux build problems on travis 2019-04-19 12:10:43 -07:00
Benjamin Sergeant 4cc715b13d Windows nmake does not have a -j flag 2019-04-19 11:58:02 -07:00
Benjamin Sergeant 0dfd7cd543 Windows + unittest python script fixes 2019-04-19 11:54:58 -07:00
Benjamin Sergeant 56f164ce2b fix warning / ws_cobra_subscribe does not need a verbose flag 2019-04-19 11:45:42 -07:00
Benjamin Sergeant 65db8c9b00 (test) build dir is an absolute path 2019-04-19 11:45:02 -07:00
Benjamin Sergeant 4c4137d9f2 (ws) fix compiler warnings 2019-04-19 09:48:46 -07:00
Benjamin Sergeant e433e8b5e9 fix test execution on travis which was broken / unify running test locally and on travis 2019-04-19 09:46:17 -07:00
Benjamin Sergeant bb442021cf fix bad merge in IXWebSocketTransport.cpp ... 2019-04-19 09:41:16 -07:00
Benjamin Sergeant 91106b7456 Socket::Poll does not need a callback 2019-04-19 09:32:49 -07:00
Kumamon38 309b5ee1b3 Ping timeout use constant (#36)
* use constant for ping timeout

* change close code types
2019-04-19 09:16:25 -07:00
Dimon4eg 4eded01841 Link zlib statically for windows (#35)
* Add zlib 1.2.11 sources

* link zlib statically for windows
2019-04-19 09:14:03 -07:00
Kumamon38 e3d0c899d3 fix close code/reason issue (#34)
* fix close code/reason issue

* added code 1006 for abnormal closure
2019-04-18 10:02:31 -07:00
Kumamon38 d7595b0dd0 Real ping (#32)
* close method change and fix code

* missing mutex

* wip

* renaming and fixes

* renaming, fixes

* added enablePong/disablePong, add tests

* added new test cases

* add 1 test case

* fix gcd name to greatestCommonDivisor

* move ping and ping timeout checks into socket poll, local var in test cases and indent fixes

* indent issue
2019-04-18 09:24:16 -07:00
Benjamin Sergeant f0375e59fa docker container works with SSL + fix compiler warnings in statsd third_party module 2019-04-18 09:11:12 -07:00
Benjamin Sergeant c367435073 docker + linux build fix 2019-04-17 22:52:03 -07:00
Benjamin Sergeant dc812c384e setter method does not need to return anything, make it void 2019-04-17 20:36:26 -07:00
Benjamin Sergeant 10b2d10dbd (doc) Add more doc to SocketServer 2019-04-17 20:36:26 -07:00
Benjamin Sergeant f96babc6a6 websocket server: closed connection threads are joined properly 2019-04-17 20:36:26 -07:00
Benjamin Sergeant 4e2e14fb22 Bug/30 server connection problem (#31)
* use threads instead of std::async, need to cleanup threads

* less buggy server connection per thread system
2019-04-16 22:19:44 -07:00
Benjamin Sergeant bcf2fc1812 make closeWireSize a default parameter of WebSocketTransport::close 2019-04-16 09:55:12 -07:00
Kumamon38 935e6791a3 close method change and fix code (#28)
* close method change and fix code

* missing mutex
2019-04-16 08:58:34 -07:00
Dimon4eg fbb7c012a3 fix windows build (#29)
* fix windows build

* fix for Unix

* Fix build if TLS is OFF

* add OpenSSL req to ws

* Fix build on Mac

* fix tests for windows
2019-04-16 08:51:57 -07:00
Dimon4eg dac18fcabf move security framework linking to ixwebsocket (#26) 2019-04-14 17:13:24 -07:00
Benjamin Sergeant d8e83caffc fix warning 2019-04-13 21:16:04 -07:00
Benjamin Sergeant fbf80b9f50 ws: new command to subscribe to a cobra server and send an event to a sentry server 2019-04-11 16:03:05 -07:00
Benjamin Sergeant c2a9139d41 (ws) add subcommands: cobra subscribe, and cobra subscribe to statsd bridge 2019-04-08 21:56:01 -07:00
Benjamin Sergeant 6e3dff149a linux ci tentative fix 2019-04-03 22:02:10 -07:00
Benjamin Sergeant 1bacbe38f4 better unittest runner / can run through lldb and produce a junit XML artifact 2019-03-29 15:54:05 -07:00
538 changed files with 113236 additions and 13610 deletions

No files matched your search

+2
View File
@@ -1 +1,3 @@
build
CMakeCache.txt
ws/CMakeCache.txt
+1
View File
@@ -1 +1,2 @@
build
*.pyc
+33 -14
View File
@@ -1,17 +1,36 @@
language: cpp
dist: xenial
compiler:
- gcc
- clang
os:
- linux
- osx
language: bash
matrix:
exclude:
# GCC fails on recent Travis OSX images.
- compiler: gcc
os: osx
include:
# macOS
- os: osx
compiler: clang
script:
- python test/run.py
- make ws
script: python test/run.py
# Linux
- os: linux
dist: xenial
script:
- python test/run.py
- make ws
env:
- CC=gcc
- CXX=g++
# Clang + Linux disabled for now
# - os: linux
# dist: xenial
# script: python test/run.py
# env:
# - CC=clang
# - CXX=clang++
# Windows
- os: windows
env:
- CMAKE_PATH="/c/Program Files/CMake/bin"
script:
- export PATH=$CMAKE_PATH:$PATH
- python test/run.py
+46 -20
View File
@@ -26,6 +26,7 @@ set( IXWEBSOCKET_SOURCES
ixwebsocket/IXSocketFactory.cpp
ixwebsocket/IXDNSLookup.cpp
ixwebsocket/IXCancellationRequest.cpp
ixwebsocket/IXNetSystem.cpp
ixwebsocket/IXWebSocket.cpp
ixwebsocket/IXWebSocketServer.cpp
ixwebsocket/IXWebSocketTransport.cpp
@@ -36,10 +37,12 @@ set( IXWEBSOCKET_SOURCES
ixwebsocket/IXWebSocketHttpHeaders.cpp
ixwebsocket/IXHttpClient.cpp
ixwebsocket/IXUrlParser.cpp
ixwebsocket/LUrlParser.cpp
ixwebsocket/IXSelectInterrupt.cpp
ixwebsocket/IXSelectInterruptPipe.cpp
ixwebsocket/IXSelectInterruptFactory.cpp
ixwebsocket/IXConnectionState.cpp
ixwebsocket/IXWebSocketCloseConstants.cpp
ixwebsocket/IXWebSocketMessageQueue.cpp
)
set( IXWEBSOCKET_HEADERS
@@ -50,6 +53,7 @@ set( IXWEBSOCKET_HEADERS
ixwebsocket/IXSetThreadName.h
ixwebsocket/IXDNSLookup.h
ixwebsocket/IXCancellationRequest.h
ixwebsocket/IXNetSystem.h
ixwebsocket/IXProgressCallback.h
ixwebsocket/IXWebSocket.h
ixwebsocket/IXWebSocketServer.h
@@ -64,12 +68,20 @@ set( IXWEBSOCKET_HEADERS
ixwebsocket/libwshandshake.hpp
ixwebsocket/IXHttpClient.h
ixwebsocket/IXUrlParser.h
ixwebsocket/LUrlParser.h
ixwebsocket/IXSelectInterrupt.h
ixwebsocket/IXSelectInterruptPipe.h
ixwebsocket/IXSelectInterruptFactory.h
ixwebsocket/IXConnectionState.h
ixwebsocket/IXWebSocketCloseConstants.h
ixwebsocket/IXWebSocketMessageQueue.h
)
if (UNIX)
# Linux, Mac, iOS, Android
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptPipe.cpp )
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptPipe.h )
endif()
# Platform specific code
if (APPLE)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/apple/IXSetThreadName_apple.cpp)
@@ -81,6 +93,7 @@ else()
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSelectInterruptEventFd.h)
endif()
set(USE_OPEN_SSL FALSE)
if (USE_TLS)
add_definitions(-DIXWEBSOCKET_USE_TLS)
@@ -91,6 +104,7 @@ if (USE_TLS)
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketSChannel.h)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketSChannel.cpp)
else()
set(USE_OPEN_SSL TRUE)
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketOpenSSL.h)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketOpenSSL.cpp)
endif()
@@ -101,38 +115,50 @@ add_library( ixwebsocket STATIC
${IXWEBSOCKET_HEADERS}
)
# gcc/Linux needs -pthread
find_package(Threads)
if (APPLE AND USE_TLS)
target_link_libraries(ixwebsocket "-framework foundation" "-framework security")
endif()
if(UNIX AND NOT APPLE)
if (USE_OPEN_SSL)
find_package(OpenSSL REQUIRED)
add_definitions(${OPENSSL_DEFINITIONS})
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
include_directories(${OPENSSL_INCLUDE_DIR})
target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES})
endif()
if (WIN32)
get_filename_component(libz_path
${PROJECT_SOURCE_DIR}/third_party/ZLIB-Windows/zlib-1.2.11_deploy_v140/release_dynamic/x64/lib/zlib.lib
ABSOLUTE)
add_library(libz STATIC IMPORTED)
set_target_properties(libz PROPERTIES IMPORTED_LOCATION
${libz_path})
include_directories(${PROJECT_SOURCE_DIR}/third_party/ZLIB-Windows/zlib-1.2.11_deploy_v140/include)
target_link_libraries(ixwebsocket libz wsock32 ws2_32)
add_subdirectory(third_party/zlib)
include_directories(third_party/zlib ${CMAKE_CURRENT_BINARY_DIR}/third_party/zlib)
target_link_libraries(ixwebsocket zlibstatic wsock32 ws2_32)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
else()
# gcc/Linux needs -pthread
find_package(Threads)
target_link_libraries(ixwebsocket
z ${OPENSSL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
z ${CMAKE_THREAD_LIBS_INIT})
endif()
set( IXWEBSOCKET_INCLUDE_DIRS
.
../../shared/OpenSSL/include)
)
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
# Build with Multiple Processes
target_compile_options(ixwebsocket PRIVATE /MP)
endif()
target_include_directories( ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS} )
add_subdirectory(ws)
add_subdirectory(third_party/cpp_redis)
set_target_properties(ixwebsocket PROPERTIES PUBLIC_HEADER "${IXWEBSOCKET_HEADERS}")
install(TARGETS ixwebsocket
ARCHIVE DESTINATION ${CMAKE_INSTALL_PREFIX}/lib
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ixwebsocket/
)
if (USE_WS)
add_subdirectory(ws)
endif()
+1 -1
View File
@@ -1 +1 @@
1.3.2
2.0.0
+1 -1
View File
@@ -1 +1 @@
Dockerfile.dev
docker/Dockerfile.ubuntu_xenial
-31
View File
@@ -1,31 +0,0 @@
FROM debian:stretch
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update
RUN apt-get -y install g++
RUN apt-get -y install libssl-dev
RUN apt-get -y install gdb
RUN apt-get -y install screen
RUN apt-get -y install procps
RUN apt-get -y install lsof
RUN apt-get -y install libz-dev
RUN apt-get -y install vim
RUN apt-get -y install make
RUN apt-get -y install cmake
RUN apt-get -y install curl
RUN apt-get -y install python
RUN apt-get -y install netcat
# debian strech cmake is too old for building with Docker
COPY makefile .
RUN ["make", "install_cmake_for_linux"]
COPY . .
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-rc4-Linux-x86_64/bin
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
# RUN ["make"]
EXPOSE 8765
CMD ["/ws/ws", "transfer", "--port", "8765", "--host", "0.0.0.0"]
+50 -26
View File
@@ -10,8 +10,7 @@
* iOS
* Linux
* Android
The code was made to compile once on Windows but support is currently broken on this platform.
* Windows (no TLS)
## Examples
@@ -35,10 +34,10 @@ webSocket.setOnMessageCallback(
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketCloseInfo& closeInfo,
const ix::WebSocketHttpHeaders& headers)
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
{
if (messageType == ix::WebSocket_MessageType_Message)
if (messageType == ix::WebSocketMessageType::Message)
{
std::cout << str << std::endl;
}
@@ -78,7 +77,7 @@ server.setOnConnectionCallback(
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
{
if (messageType == ix::WebSocket_MessageType_Open)
if (messageType == ix::WebSocketMessageType::Open)
{
std::cerr << "New connection" << std::endl;
@@ -97,7 +96,7 @@ server.setOnConnectionCallback(
std::cerr << it.first << ": " << it.second << std::endl;
}
}
else if (messageType == ix::WebSocket_MessageType_Message)
else if (messageType == 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.
@@ -187,11 +186,36 @@ auto downloadSize = std::get<6>(out);
## Build
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.
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.
There is a Dockerfile for running some code on Linux, and a unittest which can be executed by typing `make test`.
```
mkdir build # make a build dir so that you can build out of tree.
cd build
cmake ..
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
```
You can build and install the ws command line tool with Homebrew.
Headers and a static library will be installed to the target dir.
A [conan](https://conan.io/) file is available at [conan-IXWebSocket](https://github.com/Zinnion/conan-IXWebSocket).
There is a unittest which can be executed by typing `make test`.
There is a Dockerfile for running some code on Linux. 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
...
```
Finally you can build and install the `ws command line tool` with Homebrew. The homebrew version might be slightly out of date.
```
brew tap bsergean/IXWebSocket
@@ -277,10 +301,10 @@ If the connection was closed and sending failed, the return value will be set to
`getReadyState()` returns the state of the connection. There are 4 possible states.
1. WebSocket_ReadyState_Connecting - The connection is not yet open.
2. WebSocket_ReadyState_Open - The connection is open and ready to communicate.
3. WebSocket_ReadyState_Closing - The connection is in the process of closing.
4. WebSocket_MessageType_Close - The connection is closed or could not be opened.
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
@@ -292,10 +316,10 @@ webSocket.setOnMessageCallback(
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketCloseInfo& closeInfo,
const ix::WebSocketHttpHeaders& headers)
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
{
if (messageType == ix::WebSocket_MessageType_Open)
if (messageType == ix::WebSocketMessageType::Open)
{
std::cout << "send greetings" << std::endl;
@@ -306,7 +330,7 @@ webSocket.setOnMessageCallback(
std::cout << it.first << ": " << it.second << std::endl;
}
}
else if (messageType == ix::WebSocket_MessageType_Close)
else if (messageType == ix::WebSocketMessageType::Close)
{
std::cout << "disconnected" << std::endl;
@@ -321,7 +345,7 @@ webSocket.setOnMessageCallback(
### Error notification
A message will be fired when there is an error with the connection. The message type will be `ix::WebSocket_MessageType_Error`. Multiple fields will be available on the event to describe the error.
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.
```
webSocket.setOnMessageCallback(
@@ -329,10 +353,10 @@ webSocket.setOnMessageCallback(
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketCloseInfo& closeInfo,
const ix::WebSocketHttpHeaders& headers)
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
{
if (messageType == ix::WebSocket_MessageType_Error)
if (messageType == ix::WebSocketMessageType::Error)
{
std::stringstream ss;
ss << "Error: " << error.reason << std::endl;
@@ -369,11 +393,11 @@ webSocket.setOnMessageCallback(
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketCloseInfo& closeInfo,
const ix::WebSocketHttpHeaders& headers)
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
{
if (messageType == ix::WebSocket_MessageType_Ping ||
messageType == ix::WebSocket_MessageType_Pong)
if (messageType == ix::WebSocketMessageType::Ping ||
messageType == ix::WebSocketMessageType::Pong)
{
std::cout << "pong data: " << str << std::endl;
}
+6 -2
View File
@@ -1,10 +1,14 @@
image:
- Visual Studio 2017
- Ubuntu
install:
- ls -al
- cmd: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
- python test/run.py
- cd test
- mkdir build
- cd build
- cmake -G"NMake Makefiles" ..
- nmake
- ixwebsocket_unittest.exe
build: off
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

-2
View File
@@ -1,2 +0,0 @@
all:
(cd .. ; make docker && make docker_push)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 673 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

-18
View File
@@ -1,18 +0,0 @@
require=function e(t,a,r){function s(i,l){if(!a[i]){if(!t[i]){var o="function"==typeof require&&require;if(!l&&o)return o(i,!0);if(n)return n(i,!0);var c=new Error("Cannot find module '"+i+"'");throw c.code="MODULE_NOT_FOUND",c}var d=a[i]={exports:{}};t[i][0].call(d.exports,function(e){var a=t[i][1][e];return s(a?a:e)},d,d.exports,e,t,a,r)}return a[i].exports}for(var n="function"==typeof require&&require,i=0;i<r.length;i++)s(r[i]);return s}({1:[function(e,t,a){function r(){this._events=this._events||{},this._maxListeners=this._maxListeners||void 0}function s(e){return"function"==typeof e}function n(e){return"number"==typeof e}function i(e){return"object"==typeof e&&null!==e}function l(e){return void 0===e}t.exports=r,r.EventEmitter=r,r.prototype._events=void 0,r.prototype._maxListeners=void 0,r.defaultMaxListeners=10,r.prototype.setMaxListeners=function(e){if(!n(e)||e<0||isNaN(e))throw TypeError("n must be a positive number");return this._maxListeners=e,this},r.prototype.emit=function(e){var t,a,r,n,o,c;if(this._events||(this._events={}),"error"===e&&(!this._events.error||i(this._events.error)&&!this._events.error.length)){if(t=arguments[1],t instanceof Error)throw t;var d=new Error('Uncaught, unspecified "error" event. ('+t+")");throw d.context=t,d}if(a=this._events[e],l(a))return!1;if(s(a))switch(arguments.length){case 1:a.call(this);break;case 2:a.call(this,arguments[1]);break;case 3:a.call(this,arguments[1],arguments[2]);break;default:n=Array.prototype.slice.call(arguments,1),a.apply(this,n)}else if(i(a))for(n=Array.prototype.slice.call(arguments,1),c=a.slice(),r=c.length,o=0;o<r;o++)c[o].apply(this,n);return!0},r.prototype.addListener=function(e,t){var a;if(!s(t))throw TypeError("listener must be a function");return this._events||(this._events={}),this._events.newListener&&this.emit("newListener",e,s(t.listener)?t.listener:t),this._events[e]?i(this._events[e])?this._events[e].push(t):this._events[e]=[this._events[e],t]:this._events[e]=t,i(this._events[e])&&!this._events[e].warned&&(a=l(this._maxListeners)?r.defaultMaxListeners:this._maxListeners,a&&a>0&&this._events[e].length>a&&(this._events[e].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[e].length),"function"==typeof console.trace&&console.trace())),this},r.prototype.on=r.prototype.addListener,r.prototype.once=function(e,t){function a(){this.removeListener(e,a),r||(r=!0,t.apply(this,arguments))}if(!s(t))throw TypeError("listener must be a function");var r=!1;return a.listener=t,this.on(e,a),this},r.prototype.removeListener=function(e,t){var a,r,n,l;if(!s(t))throw TypeError("listener must be a function");if(!this._events||!this._events[e])return this;if(a=this._events[e],n=a.length,r=-1,a===t||s(a.listener)&&a.listener===t)delete this._events[e],this._events.removeListener&&this.emit("removeListener",e,t);else if(i(a)){for(l=n;l-- >0;)if(a[l]===t||a[l].listener&&a[l].listener===t){r=l;break}if(r<0)return this;1===a.length?(a.length=0,delete this._events[e]):a.splice(r,1),this._events.removeListener&&this.emit("removeListener",e,t)}return this},r.prototype.removeAllListeners=function(e){var t,a;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[e]&&delete this._events[e],this;if(0===arguments.length){for(t in this._events)"removeListener"!==t&&this.removeAllListeners(t);return this.removeAllListeners("removeListener"),this._events={},this}if(a=this._events[e],s(a))this.removeListener(e,a);else if(a)for(;a.length;)this.removeListener(e,a[a.length-1]);return delete this._events[e],this},r.prototype.listeners=function(e){var t;return t=this._events&&this._events[e]?s(this._events[e])?[this._events[e]]:this._events[e].slice():[]},r.prototype.listenerCount=function(e){if(this._events){var t=this._events[e];if(s(t))return 1;if(t)return t.length}return 0},r.listenerCount=function(e,t){return e.listenerCount(t)}},{}],2:[function(e,t,a){(function(e){(function(){function e(e){this.tokens=[],this.tokens.links={},this.options=e||m.defaults,this.rules=u.normal,this.options.gfm&&(this.options.tables?this.rules=u.tables:this.rules=u.gfm)}function r(e,t){if(this.options=t||m.defaults,this.links=e,this.rules=h.normal,this.renderer=this.options.renderer||new s,this.renderer.options=this.options,!this.links)throw new Error("Tokens array requires a `links` property.");this.options.gfm?this.options.breaks?this.rules=h.breaks:this.rules=h.gfm:this.options.pedantic&&(this.rules=h.pedantic)}function s(e){this.options=e||{}}function n(e){this.tokens=[],this.token=null,this.options=e||m.defaults,this.options.renderer=this.options.renderer||new s,this.renderer=this.options.renderer,this.renderer.options=this.options}function i(e,t){return e.replace(t?/&/g:/&(?!#?\w+;)/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function l(e){return e.replLine truncated
a.relevance>r.relevance&&(s=r,r=a)}),s.language&&(r.second_best=s),r}function u(e){return N.tabReplace||N.useBR?e.replace(x,function(e,t){return N.useBR&&"\n"===e?"<br>":N.tabReplace?t.replace(/\t/g,N.tabReplace):void 0}):e}function h(e,t,a){var r=t?C[t]:a,s=[e.trim()];return e.match(/\bhljs\b/)||s.push("hljs"),e.indexOf(r)===-1&&s.push(r),s.join(" ").trim()}function p(e){var t,a,r,i,c,p=n(e);s(p)||(N.useBR?(t=document.createElementNS("http://www.w3.org/1999/xhtml","div"),t.innerHTML=e.innerHTML.replace(/\n/g,"").replace(/<br[ \/]*>/g,"\n")):t=e,c=t.textContent,r=p?d(p,c,!0):m(c),a=l(t),a.length&&(i=document.createElementNS("http://www.w3.org/1999/xhtml","div"),i.innerHTML=r.value,r.value=o(a,l(i),c)),r.value=u(r.value),e.innerHTML=r.value,e.className=h(e.className,p,r.language),e.result={language:r.language,re:r.relevance},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.relevance}))}function g(e){N=i(N,e)}function b(){if(!b.called){b.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,p)}}function _(){addEventListener("DOMContentLoaded",b,!1),addEventListener("load",b,!1)}function f(t,a){var r=S[t]=a(e);r.aliases&&r.aliases.forEach(function(e){C[e]=t})}function v(){return y(S)}function j(e){return e=(e||"").toLowerCase(),S[e]||S[C[e]]}var E=[],y=Object.keys,S={},C={},k=/^(no-?highlight|plain|text)$/i,w=/\blang(?:uage)?-([\w-]+)\b/i,x=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,T="</span>",N={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},M={"&":"&amp;","<":"&lt;",">":"&gt;"};return e.highlight=d,e.highlightAuto=m,e.fixMarkup=u,e.highlightBlock=p,e.configure=g,e.initHighlighting=b,e.initHighlightingOnLoad=_,e.registerLanguage=f,e.listLanguages=v,e.getLanguage=j,e.inherit=i,e.IDENT_RE="[a-zA-Z]\\w*",e.UNDERSCORE_IDENT_RE="[a-zA-Z_]\\w*",e.NUMBER_RE="\\b\\d+(\\.\\d+)?",e.C_NUMBER_RE="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BINARY_NUMBER_RE="\\b(0b[01]+)",e.RE_STARTERS_RE="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BACKSLASH_ESCAPE={begin:"\\\\[\\s\\S]",relevance:0},e.APOS_STRING_MODE={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},e.QUOTE_STRING_MODE={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},e.PHRASAL_WORDS_MODE={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|like)\b/},e.COMMENT=function(t,a,r){var s=e.inherit({className:"comment",begin:t,end:a,contains:[]},r||{});return s.contains.push(e.PHRASAL_WORDS_MODE),s.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|XXX):",relevance:0}),s},e.C_LINE_COMMENT_MODE=e.COMMENT("//","$"),e.C_BLOCK_COMMENT_MODE=e.COMMENT("/\\*","\\*/"),e.HASH_COMMENT_MODE=e.COMMENT("#","$"),e.NUMBER_MODE={className:"number",begin:e.NUMBER_RE,relevance:0},e.C_NUMBER_MODE={className:"number",begin:e.C_NUMBER_RE,relevance:0},e.BINARY_NUMBER_MODE={className:"number",begin:e.BINARY_NUMBER_RE,relevance:0},e.CSS_NUMBER_MODE={className:"number",begin:e.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},e.REGEXP_MODE={className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[e.BACKSLASH_ESCAPE,{begin:/\[/,end:/\]/,relevance:0,contains:[e.BACKSLASH_ESCAPE]}]},e.TITLE_MODE={className:"title",begin:e.IDENT_RE,relevance:0},e.UNDERSCORE_TITLE_MODE={className:"title",begin:e.UNDERSCORE_IDENT_RE,relevance:0},e.METHOD_GUARD={begin:"\\.\\s*"+e.UNDERSCORE_IDENT_RE,relevance:0},e}),e}(),s=[{name:"lisp",create:function(e){var t="[a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#!]*",a="\\|[^]*?\\|",r="(\\-|\\+)?\\d+(\\.\\d+|\\/\\d+)?((d|e|f|l|s|D|E|F|L|S)(\\+|\\-)?\\d+)?",s={className:"meta",begin:"^#!",end:"$"},n={className:"literal",begin:"\\b(t{1}|nil)\\b"},i={className:"number",variants:[{begin:r,relevance:0},{begin:"#(b|B)[0-1]+(/[0-1]+)?"},{begin:"#(o|O)[0-7]+(/[0-7]+)?"},{begin:"#(x|X)[0-9a-fA-F]+(/[0-9a-fA-F]+)?"},{begin:"#(c|C)\\("+r+" +"+r,end:"\\)"}]},l=e.inherit(e.QUOTE_STRING_MODE,{illegal:null}),o=e.COMMENT(";","$",{relevance:0}),c={begin:"\\*",end:"\\*"},d={className:"symbol",begin:"[:&]"+t},m={begin:t,relevance:0},u={begin:a},h={begin:"\\(",end:"\\)",contains:["self",n,l,i,m]},p={contains:[i,l,c,d,h,m],variants:[{begin:"['`]\\(",end:"\\)"},{begin:"\\(quote ",end:"\\)",keywords:{name:"quote"}},{begin:"'"+a}]},g={variants:[{begin:"'"+t},{begin:"#'"+t+"(::"+t+")*"}]},b={begin:"\\(\\s*",end:"\\)"},_={endsWithParent:!0,relevance:0};return b.contains=[{className:"name",variants:[{begin:t},{begin:a}]},_],_.contains=[p,g,b,n,i,l,o,c,d,u,m],{illegal:/\S/,contains:[i,s,n,l,o,p,g,b,m]}}},{name:"cpp",create:function(e){var t={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},a={className:"string",variants:[{begin:'(u8?|U)?L?"',end:'"',illegal:"\\n",coLine truncated
}),e.COMMENT("#cs","#ce"),e.COMMENT("#comments-start","#comments-end")]},n={begin:"\\$[A-z0-9_]+"},i={className:"string",variants:[{begin:/"/,end:/"/,contains:[{begin:/""/,relevance:0}]},{begin:/'/,end:/'/,contains:[{begin:/''/,relevance:0}]}]},l={variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]},o={className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"comments include include-once NoTrayIcon OnAutoItStartRegister pragma compile RequireAdmin"},contains:[{begin:/\\\n/,relevance:0},{beginKeywords:"include",keywords:{"meta-keyword":"include"},end:"$",contains:[i,{className:"meta-string",variants:[{begin:"<",end:">"},{begin:/"/,end:/"/,contains:[{begin:/""/,relevance:0}]},{begin:/'/,end:/'/,contains:[{begin:/''/,relevance:0}]}]}]},i,s]},c={className:"symbol",begin:"@[A-z0-9_]+"},d={className:"function",beginKeywords:"Func",end:"$",illegal:"\\$|\\[|%",contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",contains:[n,i,l]}]};return{case_insensitive:!0,illegal:/\/\*/,keywords:{keyword:t,built_in:r,literal:a},contains:[s,n,i,l,o,c,d]}}},{name:"avrasm",create:function(e){return{case_insensitive:!0,lexemes:"\\.?"+e.IDENT_RE,keywords:{keyword:"adc add adiw and andi asr bclr bld brbc brbs brcc brcs break breq brge brhc brhs brid brie brlo brlt brmi brne brpl brsh brtc brts brvc brvs bset bst call cbi cbr clc clh cli cln clr cls clt clv clz com cp cpc cpi cpse dec eicall eijmp elpm eor fmul fmuls fmulsu icall ijmp in inc jmp ld ldd ldi lds lpm lsl lsr mov movw mul muls mulsu neg nop or ori out pop push rcall ret reti rjmp rol ror sbc sbr sbrc sbrs sec seh sbi sbci sbic sbis sbiw sei sen ser ses set sev sez sleep spm st std sts sub subi swap tst wdr",built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 r16 r17 r18 r19 r20 r21 r22 r23 r24 r25 r26 r27 r28 r29 r30 r31 x|0 xh xl y|0 yh yl z|0 zh zl ucsr1c udr1 ucsr1a ucsr1b ubrr1l ubrr1h ucsr0c ubrr0h tccr3c tccr3a tccr3b tcnt3h tcnt3l ocr3ah ocr3al ocr3bh ocr3bl ocr3ch ocr3cl icr3h icr3l etimsk etifr tccr1c ocr1ch ocr1cl twcr twdr twar twsr twbr osccal xmcra xmcrb eicra spmcsr spmcr portg ddrg ping portf ddrf sreg sph spl xdiv rampz eicrb eimsk gimsk gicr eifr gifr timsk tifr mcucr mcucsr tccr0 tcnt0 ocr0 assr tccr1a tccr1b tcnt1h tcnt1l ocr1ah ocr1al ocr1bh ocr1bl icr1h icr1l tccr2 tcnt2 ocr2 ocdr wdtcr sfior eearh eearl eedr eecr porta ddra pina portb ddrb pinb portc ddrc pinc portd ddrd pind spdr spsr spcr udr0 ucsr0a ucsr0b ubrr0l acsr admux adcsr adch adcl porte ddre pine pinf",meta:".byte .cseg .db .def .device .dseg .dw .endmacro .equ .eseg .exit .include .list .listmac .macro .nolist .org .set"},contains:[e.C_BLOCK_COMMENT_MODE,e.COMMENT(";","$",{relevance:0}),e.C_NUMBER_MODE,e.BINARY_NUMBER_MODE,{className:"number",begin:"\\b(\\$[a-zA-Z0-9]+|0o[0-7]+)"},e.QUOTE_STRING_MODE,{className:"string",begin:"'",end:"[^\\\\]'",illegal:"[^\\\\][^']"},{className:"symbol",begin:"^[A-Za-z0-9_.$]+:"},{className:"meta",begin:"#",end:"$"},{className:"subst",begin:"@[0-9]+"}]}}},{name:"awk",create:function(e){var t={className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{(.*?)}/}]},a="BEGIN END if else while do for in break continue delete next nextfile function func exit|10",r={className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/(u|b)?r?'''/,end:/'''/,relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,relevance:10},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,relevance:10},{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]};return{keywords:{keyword:a},contains:[t,r,e.REGEXP_MODE,e.HASH_COMMENT_MODE,e.NUMBER_MODE]}}},{name:"axapta",create:function(e){return{keywords:"false int abstract private char boolean static null if for true while long throw finally protected final return void enum else break new catch byte super case short default double public try this switch continue reverse firstfast firstonly forupdate nofetch sum avg minof maxof count order group by asc desc index hint like dispaly edit client server ttsbegin ttscommit str real date container anytype common div mod",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,{className:"meta",begin:"#",end:"$"},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:":",contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]}]}}},{name:"bash",create:function(e){var t={className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{(.*?)}/}]},a={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,t,{className:"variable",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]}]},r={className:"string",begin:/'/,end:/'/};return{aliases:["sh","zsh"],lexemes:/-?[a-z\._]+/,keywords:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bLine truncated
beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:]/,contains:[e.TITLE_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",end:/[{;=]/,illegal:/[^\s:]/,contains:[e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"new return throw await",relevance:0},{className:"function",begin:"("+d+"\\s+)+"+e.IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:t,contains:[{begin:e.IDENT_RE+"\\s*\\(",returnBegin:!0,contains:[e.TITLE_MODE],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,relevance:0,contains:[c,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]}]}}},{name:"csp",create:function(e){return{case_insensitive:!1,lexemes:"[a-zA-Z][a-zA-Z0-9_-]*",keywords:{keyword:"base-uri child-src connect-src default-src font-src form-action frame-ancestors frame-src img-src media-src object-src plugin-types report-uri sandbox script-src style-src"},contains:[{className:"string",begin:"'",end:"'"},{className:"attribute",begin:"^Content",end:":",excludeEnd:!0}]}}},{name:"css",create:function(e){var t="[a-zA-Z-][a-zA-Z0-9_-]*",a={begin:/[A-Z\_\.\-]+\s*:/,returnBegin:!0,end:";",endsWithParent:!0,contains:[{className:"attribute",begin:/\S/,end:":",excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,contains:[{begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in",begin:/[\w-]+/},{begin:/\(/,end:/\)/,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]}]},e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}]}}]};return{case_insensitive:!0,illegal:/[=\/|'\$]/,contains:[e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:/\.[A-Za-z0-9_-]+/},{className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$"},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"@(font-face|page)",lexemes:"[a-z-]+",keywords:"font-face page"},{begin:"@",end:"[{;]",illegal:/:/,contains:[{className:"keyword",begin:/\w+/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:t,relevance:0},{begin:"{",end:"}",illegal:/\S/,contains:[e.C_BLOCK_COMMENT_MODE,a]}]}}},{name:"d",create:function(e){var t={keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",literal:"false null true"},a="(0|[1-9][\\d_]*)",r="(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)",s="0[bB][01_]+",n="([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)",i="0[xX]"+n,l="([eE][+-]?"+r+")",o="("+r+"(\\.\\d*|"+l+")|\\d+\\."+r+r+"|\\."+a+l+"?)",c="(0[xX]("+n+"\\."+n+"|\\.?"+n+")[pP][+-]?"+r+")",d="("+a+"|"+s+"|"+i+")",m="("+c+"|"+o+")",u="\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};",h={className:"number",begin:"\\b"+d+"(L|u|U|Lu|LU|uL|UL)?",relevance:0},p={className:"number",begin:"\\b("+m+"([fF]|L|i|[fF]i|Li)?|"+d+"(i|[fF]i|Li))",relevance:0},g={className:"string",begin:"'("+u+"|.)",end:"'",illegal:"."},b={begin:u,relevance:0},_={className:"string",begin:'"',contains:[b],end:'"[cwd]?'},f={className:"string",begin:'[rq]"',end:'"[cwd]?',relevance:5},v={className:"string",begin:"`",end:"`[cwd]?"},j={className:"string",begin:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',relevance:10},E={className:"string",begin:'q"\\{',end:'\\}"'},y={className:"meta",begin:"^#!",end:"$",relevance:5},S={className:"meta",begin:"#(line)",end:"$",relevance:5},C={className:"keyword",begin:"@[a-zA-Z_][a-zA-Z_\\d]*"},k=e.COMMENT("\\/\\+","\\+\\/",{contains:["self"],relevance:10});return{lexemes:e.UNDERSCORE_IDENT_RE,keywords:t,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,k,j,_,f,v,E,p,h,g,y,S,C]}}},{name:"dart",create:function(e){var t={className:"subst",begin:"\\$\\{",end:"}",keywords:"true false null this is new super"},a={className:"string",variants:[{begin:"r'''",end:"'''"},{begin:'r"""',end:'"""'},{begin:"r'",end:"'",illegal:"\\n"},{begin:'r"',end:'"',illegal:"\\n"},{begin:"'''",end:"'''",contains:[e.BACKSLASH_ESCAPE,t]},{begin:'"""',end:'"""',contains:Line truncated
},a={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0},r={className:"symbol",variants:[{begin:/\=[lgenxc]=/},{begin:/\$/}]},s={className:"comment",variants:[{begin:"'",end:"'"},{begin:'"',end:'"'}],illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},n={begin:"/",end:"/",keywords:t,contains:[s,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_NUMBER_MODE]},i={begin:/[a-z][a-z0-9_]*(\([a-z0-9_, ]*\))?[ \t]+/,excludeBegin:!0,end:"$",endsWithParent:!0,contains:[s,n,{className:"comment",begin:/([ ]*[a-z0-9&#*=?@>\\<:\-,()$\[\]_.{}!+%^]+)+/,relevance:0}]};return{aliases:["gms"],case_insensitive:!0,keywords:t,contains:[e.COMMENT(/^\$ontext/,/^\$offtext/),{className:"meta",begin:"^\\$[a-z0-9]+",end:"$",returnBegin:!0,contains:[{className:"meta-keyword",begin:"^\\$[a-z0-9]+"}]},e.COMMENT("^\\*","$"),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{beginKeywords:"set sets parameter parameters variable variables scalar scalars equation equations",end:";",contains:[e.COMMENT("^\\*","$"),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,n,i]},{beginKeywords:"table",end:";",returnBegin:!0,contains:[{beginKeywords:"table",end:"$",contains:[i]},e.COMMENT("^\\*","$"),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_NUMBER_MODE]},{className:"function",begin:/^[a-z][a-z0-9_,\-+' ()$]+\.{2}/,returnBegin:!0,contains:[{className:"title",begin:/^[a-z][a-z0-9_]+/},a,r]},e.C_NUMBER_MODE,r]}}},{name:"gauss",create:function(e){var t={keyword:"and bool break call callexe checkinterrupt clear clearg closeall cls comlog compile continue create debug declare delete disable dlibrary dllcall do dos ed edit else elseif enable end endfor endif endp endo errorlog errorlogat expr external fn for format goto gosub graph if keyword let lib library line load loadarray loadexe loadf loadk loadm loadp loads loadx local locate loopnextindex lprint lpwidth lshow matrix msym ndpclex new not open or output outwidth plot plotsym pop prcsn print printdos proc push retp return rndcon rndmod rndmult rndseed run save saveall screen scroll setarray show sparse stop string struct system trace trap threadfor threadendfor threadbegin threadjoin threadstat threadend until use while winprint",built_in:"abs acf aconcat aeye amax amean AmericanBinomCall AmericanBinomCall_Greeks AmericanBinomCall_ImpVol AmericanBinomPut AmericanBinomPut_Greeks AmericanBinomPut_ImpVol AmericanBSCall AmericanBSCall_Greeks AmericanBSCall_ImpVol AmericanBSPut AmericanBSPut_Greeks AmericanBSPut_ImpVol amin amult annotationGetDefaults annotationSetBkd annotationSetFont annotationSetLineColor annotationSetLineStyle annotationSetLineThickness annualTradingDays arccos arcsin areshape arrayalloc arrayindex arrayinit arraytomat asciiload asclabel astd astds asum atan atan2 atranspose axmargin balance band bandchol bandcholsol bandltsol bandrv bandsolpd bar base10 begwind besselj bessely beta box boxcox cdfBeta cdfBetaInv cdfBinomial cdfBinomialInv cdfBvn cdfBvn2 cdfBvn2e cdfCauchy cdfCauchyInv cdfChic cdfChii cdfChinc cdfChincInv cdfExp cdfExpInv cdfFc cdfFnc cdfFncInv cdfGam cdfGenPareto cdfHyperGeo cdfLaplace cdfLaplaceInv cdfLogistic cdfLogisticInv cdfmControlCreate cdfMvn cdfMvn2e cdfMvnce cdfMvne cdfMvt2e cdfMvtce cdfMvte cdfN cdfN2 cdfNc cdfNegBinomial cdfNegBinomialInv cdfNi cdfPoisson cdfPoissonInv cdfRayleigh cdfRayleighInv cdfTc cdfTci cdfTnc cdfTvn cdfWeibull cdfWeibullInv cdir ceil ChangeDir chdir chiBarSquare chol choldn cholsol cholup chrs close code cols colsf combinate combinated complex con cond conj cons ConScore contour conv convertsatostr convertstrtosa corrm corrms corrvc corrx corrxs cos cosh counts countwts crossprd crout croutp csrcol csrlin csvReadM csvReadSA cumprodc cumsumc curve cvtos datacreate datacreatecomplex datalist dataload dataloop dataopen datasave date datestr datestring datestrymd dayinyr dayofweek dbAddDatabase dbClose dbCommit dbCreateQuery dbExecQuery dbGetConnectOptions dbGetDatabaseName dbGetDriverName dbGetDrivers dbGetHostName dbGetLastErrorNum dbGetLastErrorText dbGetNumericalPrecPolicy dbGetPassword dbGetPort dbGetTableHeaders dbGetTables dbGetUserName dbHasFeature dbIsDriverAvailable dbIsOpen dbIsOpenError dbOpen dbQueryBindValue dbQueryClear dbQueryCols dbQueryExecPrepared dbQueryFetchAllM dbQueryFetchAllSA dbQueryFetchOneM dbQueryFetchOneSA dbQueryFinish dbQueryGetBoundValue dbQueryGetBoundValues dbQueryGetField dbQueryGetLastErrorNum dbQueryGetLastErrorText dbQueryGetLastInsertID dbQueryGetLastQuery dbQueryGetPosition dbQueryIsActive dbQueryIsForwardOnly dbQueryIsNull dbQueryIsSelect dbQueryIsValid dbQueryPrepare dbQueryRows dbQuerySeek dbQuerySeekFirst dbQuerySeekLast dbQuerySeekNext dbQuerySeekPrevious dbQuerySetForwardOnly dbRemoveDatabase dbRollback dbSetConnectOptions dbSetDatabaseName dbSetHostName dbSetNumericalPrecPolicy dbSetPort dbSetUserName dbTransaction DeleteFile delif dLine truncated
},{begin:":\\s*"+t}]}]}}},{name:"hsp",create:function(e){return{case_insensitive:!0,lexemes:/[\w\._]+/,keywords:"goto gosub return break repeat loop continue wait await dim sdim foreach dimtype dup dupptr end stop newmod delmod mref run exgoto on mcall assert logmes newlab resume yield onexit onerror onkey onclick oncmd exist delete mkdir chdir dirlist bload bsave bcopy memfile if else poke wpoke lpoke getstr chdpm memexpand memcpy memset notesel noteadd notedel noteload notesave randomize noteunsel noteget split strrep setease button chgdisp exec dialog mmload mmplay mmstop mci pset pget syscolor mes print title pos circle cls font sysfont objsize picload color palcolor palette redraw width gsel gcopy gzoom gmode bmpsave hsvcolor getkey listbox chkbox combox input mesbox buffer screen bgscr mouse objsel groll line clrobj boxf objprm objmode stick grect grotate gsquare gradf objimage objskip objenable celload celdiv celput newcom querycom delcom cnvstow comres axobj winobj sendmsg comevent comevarg sarrayconv callfunc cnvwtos comevdisp libptr system hspstat hspver stat cnt err strsize looplev sublev iparam wparam lparam refstr refdval int rnd strlen length length2 length3 length4 vartype gettime peek wpeek lpeek varptr varuse noteinfo instr abs limit getease str strmid strf getpath strtrim sin cos tan atan sqrt double absf expf logf limitf powf geteasef mousex mousey mousew hwnd hinstance hdc ginfo objinfo dirinfo sysinfo thismod __hspver__ __hsp30__ __date__ __time__ __line__ __file__ _debug __hspdef__ and or xor not screen_normal screen_palette screen_hide screen_fixedsize screen_tool screen_frame gmode_gdi gmode_mem gmode_rgb0 gmode_alpha gmode_rgb0alpha gmode_add gmode_sub gmode_pixela ginfo_mx ginfo_my ginfo_act ginfo_sel ginfo_wx1 ginfo_wy1 ginfo_wx2 ginfo_wy2 ginfo_vx ginfo_vy ginfo_sizex ginfo_sizey ginfo_winx ginfo_winy ginfo_mesx ginfo_mesy ginfo_r ginfo_g ginfo_b ginfo_paluse ginfo_dispx ginfo_dispy ginfo_cx ginfo_cy ginfo_intid ginfo_newid ginfo_sx ginfo_sy objinfo_mode objinfo_bmscr objinfo_hwnd notemax notesize dir_cur dir_exe dir_win dir_sys dir_cmdline dir_desktop dir_mydoc dir_tv font_normal font_bold font_italic font_underline font_strikeout font_antialias objmode_normal objmode_guifont objmode_usefont gsquare_grad msgothic msmincho do until while wend for next _break _continue switch case default swbreak swend ddim ldim alloc m_pi rad2deg deg2rad ease_linear ease_quad_in ease_quad_out ease_quad_inout ease_cubic_in ease_cubic_out ease_cubic_inout ease_quartic_in ease_quartic_out ease_quartic_inout ease_bounce_in ease_bounce_out ease_bounce_inout ease_shake_in ease_shake_out ease_shake_inout ease_loop",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"string",begin:'{"',end:'"}',contains:[e.BACKSLASH_ESCAPE]},e.COMMENT(";","$",{relevance:0}),{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"addion cfunc cmd cmpopt comfunc const defcfunc deffunc define else endif enum epack func global if ifdef ifndef include modcfunc modfunc modinit modterm module pack packopt regcmd runtime undef usecom uselib"},contains:[e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"}),e.NUMBER_MODE,e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"symbol",begin:"^\\*(\\w+|@)"},e.NUMBER_MODE,e.C_NUMBER_MODE]}}},{name:"htmlbars",create:function(e){var t="action collection component concat debugger each each-in else get hash if input link-to loc log mut outlet partial query-params render textarea unbound unless with yield view",a={illegal:/\}\}/,begin:/[a-zA-Z0-9_]+=/,returnBegin:!0,relevance:0,contains:[{className:"attr",begin:/[a-zA-Z0-9_]+/}]},r=({illegal:/\}\}/,begin:/\)/,end:/\)/,contains:[{begin:/[a-zA-Z\.\-]+/,keywords:{built_in:t},starts:{endsWithParent:!0,relevance:0,contains:[e.QUOTE_STRING_MODE]}}]},{endsWithParent:!0,relevance:0,keywords:{keyword:"as",built_in:t},contains:[e.QUOTE_STRING_MODE,a,e.NUMBER_MODE]});return{case_insensitive:!0,subLanguage:"xml",contains:[e.COMMENT("{{!(--)?","(--)?}}"),{className:"template-tag",begin:/\{\{[#\/]/,end:/\}\}/,contains:[{className:"name",begin:/[a-zA-Z\.\-]+/,keywords:{"builtin-name":t},starts:r}]},{className:"template-variable",begin:/\{\{[a-zA-Z][a-zA-Z\-]+/,end:/\}\}/,keywords:{keyword:"as",built_in:t},contains:[e.QUOTE_STRING_MODE]}]}}},{name:"http",create:function(e){var t="HTTP/[0-9\\.]+";return{aliases:["https"],illegal:"\\S",contains:[{begin:"^"+t,end:"$",contains:[{className:"number",begin:"\\b\\d{3}\\b"}]},{begin:"^[A-Z]+ (.*?) "+t+"$",returnBegin:!0,end:"$",contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{begin:t},{className:"keyword",begin:"[A-Z]+"}]},{className:"attribute",begin:"^\\w",end:": ",excludeEnd:!0,illegal:"\\n|\\s|=",starts:{end:"$",relevance:0}},{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}]}}},{name:"inform7",create:function(e){var t="\\[",a="\\]";return{aliases:["i7"],case_insensitive:!0,keywords:{keywLine truncated
},contains:[t,{className:"keyword",begin:"\\bend\\sif\\b"},{className:"function",beginKeywords:"function",end:"$",contains:[t,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE,r]},{className:"function",begin:"\\bend\\s+",end:"$",keywords:"end",contains:[s,r],relevance:0},{beginKeywords:"command on",end:"$",contains:[t,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE,r]},{className:"meta",variants:[{begin:"<\\?(rev|lc|livecode)",relevance:10},{begin:"<\\?"},{begin:"\\?>"}]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE,r].concat(a),illegal:";$|^\\[|^=|&|{"}}},{name:"livescript",create:function(e){var t={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger case default function var with then unless until loop of by when and or is isnt not it that otherwise from to til fallthrough super case default function var void const let enum export import native __hasProp __extends __slice __bind __indexOf",literal:"true false null undefined yes no on off it that void",built_in:"npm require console print module global window document"},a="[A-Za-z$_](?:-[0-9A-Za-z$_]|[0-9A-Za-z$_])*",r=e.inherit(e.TITLE_MODE,{begin:a}),s={className:"subst",begin:/#\{/,end:/}/,keywords:t},n={className:"subst",begin:/#[A-Za-z$_]/,end:/(?:\-[0-9A-Za-z$_]|[0-9A-Za-z$_])*/,keywords:t},i=[e.BINARY_NUMBER_MODE,{className:"number",begin:"(\\b0[xX][a-fA-F0-9_]+)|(\\b\\d(\\d|_\\d)*(\\.(\\d(\\d|_\\d)*)?)?(_*[eE]([-+]\\d(_\\d|\\d)*)?)?[_a-z]*)",relevance:0,starts:{end:"(\\s*/)?",relevance:0}},{className:"string",variants:[{begin:/'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE]},{begin:/'/,end:/'/,contains:[e.BACKSLASH_ESCAPE]},{begin:/"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,s,n]},{begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,n]},{begin:/\\/,end:/(\s|$)/,excludeEnd:!0}]},{className:"regexp",variants:[{begin:"//",end:"//[gim]*",contains:[s,e.HASH_COMMENT_MODE]},{begin:/\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)/}]},{begin:"@"+a},{begin:"``",end:"``",excludeBegin:!0,excludeEnd:!0,subLanguage:"javascript"}];s.contains=i;var l={className:"params",begin:"\\(",returnBegin:!0,contains:[{begin:/\(/,end:/\)/,keywords:t,contains:["self"].concat(i)}]};return{aliases:["ls"],keywords:t,illegal:/\/\*/,contains:i.concat([e.COMMENT("\\/\\*","\\*\\/"),e.HASH_COMMENT_MODE,{className:"function",contains:[r,l],returnBegin:!0,variants:[{begin:"("+a+"\\s*(?:=|:=)\\s*)?(\\(.*\\))?\\s*\\B\\->\\*?",end:"\\->\\*?"},{begin:"("+a+"\\s*(?:=|:=)\\s*)?!?(\\(.*\\))?\\s*\\B[-~]{1,2}>\\*?",end:"[-~]{1,2}>\\*?"},{begin:"("+a+"\\s*(?:=|:=)\\s*)?(\\(.*\\))?\\s*\\B!?[-~]{1,2}>\\*?",end:"!?[-~]{1,2}>\\*?"}]},{className:"class",beginKeywords:"class",end:"$",illegal:/[:="\[\]]/,contains:[{beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[r]},r]},{begin:a+":",end:":",returnBegin:!0,returnEnd:!0,relevance:0}])}}},{name:"lsl",create:function(e){var t={className:"subst",begin:/\\[tn"\\]/},a={className:"string",begin:'"',end:'"',contains:[t]},r={className:"number",begin:e.C_NUMBER_RE},s={className:"literal",variants:[{begin:"\\b(?:PI|TWO_PI|PI_BY_TWO|DEG_TO_RAD|RAD_TO_DEG|SQRT2)\\b"},{begin:"\\b(?:XP_ERROR_(?:EXPERIENCES_DISABLED|EXPERIENCE_(?:DISABLED|SUSPENDED)|INVALID_(?:EXPERIENCE|PARAMETERS)|KEY_NOT_FOUND|MATURITY_EXCEEDED|NONE|NOT_(?:FOUND|PERMITTED(?:_LAND)?)|NO_EXPERIENCE|QUOTA_EXCEEDED|RETRY_UPDATE|STORAGE_EXCEPTION|STORE_DISABLED|THROTTLED|UNKNOWN_ERROR)|JSON_APPEND|STATUS_(?:PHYSICS|ROTATE_[XYZ]|PHANTOM|SANDBOX|BLOCK_GRAB(?:_OBJECT)?|(?:DIE|RETURN)_AT_EDGE|CAST_SHADOWS|OK|MALFORMED_PARAMS|TYPE_MISMATCH|BOUNDS_ERROR|NOT_(?:FOUND|SUPPORTED)|INTERNAL_ERROR|WHITELIST_FAILED)|AGENT(?:_(?:BY_(?:LEGACY_|USER)NAME|FLYING|ATTACHMENTS|SCRIPTED|MOUSELOOK|SITTING|ON_OBJECT|AWAY|WALKING|IN_AIR|TYPING|CROUCHING|BUSY|ALWAYS_RUN|AUTOPILOT|LIST_(?:PARCEL(?:_OWNER)?|REGION)))?|CAMERA_(?:PITCH|DISTANCE|BEHINDNESS_(?:ANGLE|LAG)|(?:FOCUS|POSITION)(?:_(?:THRESHOLD|LOCKED|LAG))?|FOCUS_OFFSET|ACTIVE)|ANIM_ON|LOOP|REVERSE|PING_PONG|SMOOTH|ROTATE|SCALE|ALL_SIDES|LINK_(?:ROOT|SET|ALL_(?:OTHERS|CHILDREN)|THIS)|ACTIVE|PASS(?:IVE|_(?:ALWAYS|IF_NOT_HANDLED|NEVER))|SCRIPTED|CONTROL_(?:FWD|BACK|(?:ROT_)?(?:LEFT|RIGHT)|UP|DOWN|(?:ML_)?LBUTTON)|PERMISSION_(?:RETURN_OBJECTS|DEBIT|OVERRIDE_ANIMATIONS|SILENT_ESTATE_MANAGEMENT|TAKE_CONTROLS|TRIGGER_ANIMATION|ATTACH|CHANGE_LINKS|(?:CONTROL|TRACK)_CAMERA|TELEPORT)|INVENTORY_(?:TEXTURE|SOUND|OBJECT|SCRIPT|LANDMARK|CLOTHING|NOTECARD|BODYPART|ANIMATION|GESTURE|ALL|NONE)|CHANGED_(?:INVENTORY|COLOR|SHAPE|SCALE|TEXTURE|LINK|ALLOWED_DROP|OWNER|REGION(?:_START)?|TELEPORT|MEDIA)|OBJECT_(?:CLICK_ACTION|HOVER_HEIGHT|LAST_OWNER_ID|(?:PHYSICS|SERVER|STREAMING)_COST|UNKNOWN_DETAIL|CHARACTER_TIME|PHANTOM|PHYSICS|TEMP_ON_REZ|NAME|DESC|POS|PRIM_(?:COUNT|EQUIVALENCE)|RETURN_(?:PARCEL(?:_OWNER)?|REGION)|REZZER_KEY|ROO?T|VELOCITY|OMEGA|OWNER|GROUP|CREATOR|ATTACHED_POINT|RENDER_WEIGHT|(?:BODY_SHAPE|PATHFINDING)_TYLine truncated
contains:[{className:"comment",begin:/\(\*/,end:/\*\)/},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,{begin:/\{/,end:/\}/,illegal:/:/}]}}},{name:"matlab",create:function(e){var t=[e.C_NUMBER_MODE,{className:"string",begin:"'",end:"'",contains:[e.BACKSLASH_ESCAPE,{begin:"''"}]}],a={relevance:0,contains:[{begin:/'['\.]*/}]};return{keywords:{keyword:"break case catch classdef continue else elseif end enumerated events for function global if methods otherwise parfor persistent properties return spmd switch try while",built_in:"sin sind sinh asin asind asinh cos cosd cosh acos acosd acosh tan tand tanh atan atand atan2 atanh sec secd sech asec asecd asech csc cscd csch acsc acscd acsch cot cotd coth acot acotd acoth hypot exp expm1 log log1p log10 log2 pow2 realpow reallog realsqrt sqrt nthroot nextpow2 abs angle complex conj imag real unwrap isreal cplxpair fix floor ceil round mod rem sign airy besselj bessely besselh besseli besselk beta betainc betaln ellipj ellipke erf erfc erfcx erfinv expint gamma gammainc gammaln psi legendre cross dot factor isprime primes gcd lcm rat rats perms nchoosek factorial cart2sph cart2pol pol2cart sph2cart hsv2rgb rgb2hsv zeros ones eye repmat rand randn linspace logspace freqspace meshgrid accumarray size length ndims numel disp isempty isequal isequalwithequalnans cat reshape diag blkdiag tril triu fliplr flipud flipdim rot90 find sub2ind ind2sub bsxfun ndgrid permute ipermute shiftdim circshift squeeze isscalar isvector ans eps realmax realmin pi i inf nan isnan isinf isfinite j why compan gallery hadamard hankel hilb invhilb magic pascal rosser toeplitz vander wilkinson"},illegal:'(//|"|#|/\\*|\\s+/\\w+)',contains:[{className:"function",beginKeywords:"function",end:"$",contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",variants:[{begin:"\\(",end:"\\)"},{begin:"\\[",end:"\\]"}]}]},{begin:/[a-zA-Z_][a-zA-Z_0-9]*'['\.]*/,returnBegin:!0,relevance:0,contains:[{begin:/[a-zA-Z_][a-zA-Z_0-9]*/,relevance:0},a.contains[0]]},{begin:"\\[",end:"\\]",contains:t,relevance:0,starts:a},{begin:"\\{",end:/}/,contains:t,relevance:0,starts:a},{begin:/\)/,relevance:0,starts:a},e.COMMENT("^\\s*\\%\\{\\s*$","^\\s*\\%\\}\\s*$"),e.COMMENT("\\%","$")].concat(t)}}},{name:"maxima",create:function(e){var t="if then else elseif for thru do while unless step in and or not",a="true false unknown inf minf ind und %e %i %pi %phi %gamma",r=" abasep abs absint absolute_real_time acos acosh acot acoth acsc acsch activate addcol add_edge add_edges addmatrices addrow add_vertex add_vertices adjacency_matrix adjoin adjoint af agd airy airy_ai airy_bi airy_dai airy_dbi algsys alg_type alias allroots alphacharp alphanumericp amortization %and annuity_fv annuity_pv antid antidiff AntiDifference append appendfile apply apply1 apply2 applyb1 apropos args arit_amortization arithmetic arithsum array arrayapply arrayinfo arraymake arraysetapply ascii asec asech asin asinh askinteger asksign assoc assoc_legendre_p assoc_legendre_q assume assume_external_byte_order asympa at atan atan2 atanh atensimp atom atvalue augcoefmatrix augmented_lagrangian_method av average_degree backtrace bars barsplot barsplot_description base64 base64_decode bashindices batch batchload bc2 bdvac belln benefit_cost bern bernpoly bernstein_approx bernstein_expand bernstein_poly bessel bessel_i bessel_j bessel_k bessel_simplify bessel_y beta beta_incomplete beta_incomplete_generalized beta_incomplete_regularized bezout bfallroots bffac bf_find_root bf_fmin_cobyla bfhzeta bfloat bfloatp bfpsi bfpsi0 bfzeta biconnected_components bimetric binomial bipartition block blockmatrixp bode_gain bode_phase bothcoef box boxplot boxplot_description break bug_report build_info|10 buildq build_sample burn cabs canform canten cardinality carg cartan cartesian_product catch cauchy_matrix cbffac cdf_bernoulli cdf_beta cdf_binomial cdf_cauchy cdf_chi2 cdf_continuous_uniform cdf_discrete_uniform cdf_exp cdf_f cdf_gamma cdf_general_finite_discrete cdf_geometric cdf_gumbel cdf_hypergeometric cdf_laplace cdf_logistic cdf_lognormal cdf_negative_binomial cdf_noncentral_chi2 cdf_noncentral_student_t cdf_normal cdf_pareto cdf_poisson cdf_rank_sum cdf_rayleigh cdf_signed_rank cdf_student_t cdf_weibull cdisplay ceiling central_moment cequal cequalignore cf cfdisrep cfexpand cgeodesic cgreaterp cgreaterpignore changename changevar chaosgame charat charfun charfun2 charlist charp charpoly chdir chebyshev_t chebyshev_u checkdiv check_overlaps chinese cholesky christof chromatic_index chromatic_number cint circulant_graph clear_edge_weight clear_rules clear_vertex_label clebsch_gordan clebsch_graph clessp clesspignore close closefile cmetric coeff coefmatrix cograd col collapse collectterms columnop columnspace columnswap columnvector combination combine comp2pui compare compfile compile compile_file complement_graph complete_bipartite_graph complete_graph complex_number_p components compose_functions concan concat conjugate conmetderiv connected_components connectLine truncated
illegal:"</",contains:[e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string",begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE]},{begin:"[\\$\\%\\@](\\^\\w\\b|#\\w+|[^\\s\\w{]|{\\w+}|\\w+)"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]}}},{name:"mercury",create:function(e){var t={keyword:"module use_module import_module include_module end_module initialise mutable initialize finalize finalise interface implementation pred mode func type inst solver any_pred any_func is semidet det nondet multi erroneous failure cc_nondet cc_multi typeclass instance where pragma promise external trace atomic or_else require_complete_switch require_det require_semidet require_multi require_nondet require_cc_multi require_cc_nondet require_erroneous require_failure",meta:"inline no_inline type_spec source_file fact_table obsolete memo loop_check minimal_model terminates does_not_terminate check_termination promise_equivalent_clauses foreign_proc foreign_decl foreign_code foreign_type foreign_import_module foreign_export_enum foreign_export foreign_enum may_call_mercury will_not_call_mercury thread_safe not_thread_safe maybe_thread_safe promise_pure promise_semipure tabled_for_io local untrailed trailed attach_to_io_state can_pass_as_mercury_type stable will_not_throw_exception may_modify_trail will_not_modify_trail may_duplicate may_not_duplicate affects_liveness does_not_affect_liveness doesnt_affect_liveness no_sharing unknown_sharing sharing",built_in:"some all not if then else true fail false try catch catch_any semidet_true semidet_false semidet_fail impure_true impure semipure"},a=e.COMMENT("%","$"),r={className:"number",begin:"0'.\\|0[box][0-9a-fA-F]*"},s=e.inherit(e.APOS_STRING_MODE,{relevance:0}),n=e.inherit(e.QUOTE_STRING_MODE,{relevance:0}),i={className:"subst",begin:"\\\\[abfnrtv]\\|\\\\x[0-9a-fA-F]*\\\\\\|%[-+# *.0-9]*[dioxXucsfeEgGp]",relevance:0};n.contains.push(i);var l={className:"built_in",variants:[{begin:"<=>"},{begin:"<=",relevance:0},{begin:"=>",relevance:0},{begin:"/\\\\"},{begin:"\\\\/"}]},o={className:"built_in",variants:[{begin:":-\\|-->"},{begin:"=",relevance:0}]};return{aliases:["m","moo"],keywords:t,contains:[l,o,a,e.C_BLOCK_COMMENT_MODE,r,e.NUMBER_MODE,s,n,{begin:/:-/}]}}},{name:"mipsasm",create:function(e){return{case_insensitive:!0,aliases:["mips"],lexemes:"\\.?"+e.IDENT_RE,keywords:{meta:".2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .ltorg ",built_in:"$0 $1 $2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 $14 $15 $16 $17 $18 $19 $20 $21 $22 $23 $24 $25 $26 $27 $28 $29 $30 $31 zero at v0 v1 a0 a1 a2 a3 a4 a5 a6 a7 t0 t1 t2 t3 t4 t5 t6 t7 t8 t9 s0 s1 s2 s3 s4 s5 s6 s7 s8 k0 k1 gp sp fp ra $f0 $f1 $f2 $f2 $f4 $f5 $f6 $f7 $f8 $f9 $f10 $f11 $f12 $f13 $f14 $f15 $f16 $f17 $f18 $f19 $f20 $f21 $f22 $f23 $f24 $f25 $f26 $f27 $f28 $f29 $f30 $f31 Context Random EntryLo0 EntryLo1 Context PageMask Wired EntryHi HWREna BadVAddr Count Compare SR IntCtl SRSCtl SRSMap Cause EPC PRId EBase Config Config1 Config2 Config3 LLAddr Debug DEPC DESAVE CacheErr ECC ErrorEPC TagLo DataLo TagHi DataHi WatchLo WatchHi PerfCtl PerfCnt "},contains:[{className:"keyword",begin:"\\b(addi?u?|andi?|b(al)?|beql?|bgez(al)?l?|bgtzl?|blezl?|bltz(al)?l?|bnel?|cl[oz]|divu?|ext|ins|j(al)?|jalr(.hb)?|jr(.hb)?|lbu?|lhu?|ll|lui|lw[lr]?|maddu?|mfhi|mflo|movn|movz|move|msubu?|mthi|mtlo|mul|multu?|nop|nor|ori?|rotrv?|sb|sc|se[bh]|sh|sllv?|slti?u?|srav?|srlv?|subu?|sw[lr]?|xori?|wsbh|abs.[sd]|add.[sd]|alnv.ps|bc1[ft]l?|c.(s?f|un|u?eq|[ou]lt|[ou]le|ngle?|seq|l[et]|ng[et]).[sd]|(ceil|floor|round|trunc).[lw].[sd]|cfc1|cvt.d.[lsw]|cvt.l.[dsw]|cvt.ps.s|cvt.s.[dlw]|cvt.s.p[lu]|cvt.w.[dls]|div.[ds]|ldx?c1|luxc1|lwx?c1|madd.[sd]|mfc1|mov[fntz]?.[ds]|msub.[sd]|mth?c1|mul.[ds]|neg.[ds]|nmadd.[ds]|nmsub.[ds]|p[lu][lu].ps|recip.fmt|r?sqrt.[ds]|sdx?c1|sub.[ds]|suxc1|swx?c1|break|cache|d?eret|[de]i|ehb|mfc0|mtc0|pause|prefx?|rdhwr|rdpgpr|sdbbp|ssnop|synci?|syscall|teqi?|tgei?u?|tlb(p|r|w[ir])|tlti?u?|tnei?|wait|wrpgpr)",end:"\\s"},e.COMMENT("[;#]","$"),e.C_BLOCK_COMMENT_MODE,e.QUOTE_STRING_MODE,{className:"string",begin:"'",end:"[^\\\\]'",relevance:0},{className:"title",begin:"\\|",end:"\\|",illegal:"\\n",relevance:0},{className:"number",variants:[{begin:"0x[0-9a-f]+"},{begin:"\\b-?\\d+"}],relevance:0},{className:"symbol",variants:[{begin:"^\\s*[a-z_\\.\\$][a-z0-9_\\.\\$]+:"},{begin:"^\\s*[0-9]+:"},{begin:"[0-9]+[bf]"}],relevance:0}],illegal:"/"}}},{name:"mizar",create:function(e){return{keywords:"environ vocabularies notations constructors definitions registrations theorems schemes requirements begin end definition registration cluster existence pred func defpred deffunc theorem proof let take assume then thus hence ex for st holds consider reconsider such that and in provided of as from be being by means equals implies iff redefine define now not or attr is mode suppoLine truncated
contains:[i]});return{aliases:["ps"],lexemes:/-?[A-z\.\-]+/,case_insensitive:!0,keywords:{keyword:"if else foreach return function do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch",built_in:"Add-Computer Add-Content Add-History Add-JobTrigger Add-Member Add-PSSnapin Add-Type Checkpoint-Computer Clear-Content Clear-EventLog Clear-History Clear-Host Clear-Item Clear-ItemProperty Clear-Variable Compare-Object Complete-Transaction Connect-PSSession Connect-WSMan Convert-Path ConvertFrom-Csv ConvertFrom-Json ConvertFrom-SecureString ConvertFrom-StringData ConvertTo-Csv ConvertTo-Html ConvertTo-Json ConvertTo-SecureString ConvertTo-Xml Copy-Item Copy-ItemProperty Debug-Process Disable-ComputerRestore Disable-JobTrigger Disable-PSBreakpoint Disable-PSRemoting Disable-PSSessionConfiguration Disable-WSManCredSSP Disconnect-PSSession Disconnect-WSMan Disable-ScheduledJob Enable-ComputerRestore Enable-JobTrigger Enable-PSBreakpoint Enable-PSRemoting Enable-PSSessionConfiguration Enable-ScheduledJob Enable-WSManCredSSP Enter-PSSession Exit-PSSession Export-Alias Export-Clixml Export-Console Export-Counter Export-Csv Export-FormatData Export-ModuleMember Export-PSSession ForEach-Object Format-Custom Format-List Format-Table Format-Wide Get-Acl Get-Alias Get-AuthenticodeSignature Get-ChildItem Get-Command Get-ComputerRestorePoint Get-Content Get-ControlPanelItem Get-Counter Get-Credential Get-Culture Get-Date Get-Event Get-EventLog Get-EventSubscriber Get-ExecutionPolicy Get-FormatData Get-Host Get-HotFix Get-Help Get-History Get-IseSnippet Get-Item Get-ItemProperty Get-Job Get-JobTrigger Get-Location Get-Member Get-Module Get-PfxCertificate Get-Process Get-PSBreakpoint Get-PSCallStack Get-PSDrive Get-PSProvider Get-PSSession Get-PSSessionConfiguration Get-PSSnapin Get-Random Get-ScheduledJob Get-ScheduledJobOption Get-Service Get-TraceSource Get-Transaction Get-TypeData Get-UICulture Get-Unique Get-Variable Get-Verb Get-WinEvent Get-WmiObject Get-WSManCredSSP Get-WSManInstance Group-Object Import-Alias Import-Clixml Import-Counter Import-Csv Import-IseSnippet Import-LocalizedData Import-PSSession Import-Module Invoke-AsWorkflow Invoke-Command Invoke-Expression Invoke-History Invoke-Item Invoke-RestMethod Invoke-WebRequest Invoke-WmiMethod Invoke-WSManAction Join-Path Limit-EventLog Measure-Command Measure-Object Move-Item Move-ItemProperty New-Alias New-Event New-EventLog New-IseSnippet New-Item New-ItemProperty New-JobTrigger New-Object New-Module New-ModuleManifest New-PSDrive New-PSSession New-PSSessionConfigurationFile New-PSSessionOption New-PSTransportOption New-PSWorkflowExecutionOption New-PSWorkflowSession New-ScheduledJobOption New-Service New-TimeSpan New-Variable New-WebServiceProxy New-WinEvent New-WSManInstance New-WSManSessionOption Out-Default Out-File Out-GridView Out-Host Out-Null Out-Printer Out-String Pop-Location Push-Location Read-Host Receive-Job Register-EngineEvent Register-ObjectEvent Register-PSSessionConfiguration Register-ScheduledJob Register-WmiEvent Remove-Computer Remove-Event Remove-EventLog Remove-Item Remove-ItemProperty Remove-Job Remove-JobTrigger Remove-Module Remove-PSBreakpoint Remove-PSDrive Remove-PSSession Remove-PSSnapin Remove-TypeData Remove-Variable Remove-WmiObject Remove-WSManInstance Rename-Computer Rename-Item Rename-ItemProperty Reset-ComputerMachinePassword Resolve-Path Restart-Computer Restart-Service Restore-Computer Resume-Job Resume-Service Save-Help Select-Object Select-String Select-Xml Send-MailMessage Set-Acl Set-Alias Set-AuthenticodeSignature Set-Content Set-Date Set-ExecutionPolicy Set-Item Set-ItemProperty Set-JobTrigger Set-Location Set-PSBreakpoint Set-PSDebug Set-PSSessionConfiguration Set-ScheduledJob Set-ScheduledJobOption Set-Service Set-StrictMode Set-TraceSource Set-Variable Set-WmiInstance Set-WSManInstance Set-WSManQuickConfig Show-Command Show-ControlPanelItem Show-EventLog Sort-Object Split-Path Start-Job Start-Process Start-Service Start-Sleep Start-Transaction Start-Transcript Stop-Computer Stop-Job Stop-Process Stop-Service Stop-Transcript Suspend-Job Suspend-Service Tee-Object Test-ComputerSecureChannel Test-Connection Test-ModuleManifest Test-Path Test-PSSessionConfigurationFile Trace-Command Unblock-File Undo-Transaction Unregister-Event Unregister-PSSessionConfiguration Unregister-ScheduledJob Update-FormatData Update-Help Update-List Update-TypeData Use-Transaction Wait-Event Wait-Job Wait-Process Where-Object Write-Debug Write-Error Write-EventLog Write-Host Write-Output Write-Progress Write-Verbose Write-Warning",nomarkup:"-ne -eq -lt -gt -ge -le -not -like -notlike -match -notmatch -contains -notcontains -in -notin -replace"},contains:[t,e.NUMBER_MODE,s,n,r,a,l]}}},{name:"processing",create:function(e){return{keywords:{keyword:"BufferedReader PVector PFont PImage PGraphics HashMap boolean byte char color double float int long String Array FloatDictLine truncated
},{className:"meta",begin:"#\\!?\\[",end:"\\]",contains:[{className:"meta-string",begin:/"/,end:/"/}]},{className:"class",beginKeywords:"type",end:";",contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{endsParent:!0})],illegal:"\\S"},{className:"class",beginKeywords:"trait enum struct",end:"{",contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{endsParent:!0})],illegal:"[\\w\\d]"},{begin:e.IDENT_RE+"::",keywords:{built_in:s}},{begin:"->"}]}}},{name:"scala",create:function(e){var t={className:"meta",begin:"@[A-Za-z]+"},a={className:"subst",variants:[{begin:"\\$[A-Za-z0-9_]+"},{begin:"\\${",end:"}"}]},r={className:"string",variants:[{begin:'"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:'"""',end:'"""',relevance:10},{begin:'[a-z]+"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE,a]},{className:"string",begin:'[a-z]+"""',end:'"""',contains:[a],relevance:10}]},s={className:"symbol",begin:"'\\w[\\w\\d_]*(?!')"},n={className:"type",begin:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},i={className:"title",begin:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,relevance:0},l={className:"class",beginKeywords:"class object trait type",end:/[:={\[\n;]/,excludeEnd:!0,contains:[{beginKeywords:"extends with",relevance:10},{begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[n]},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[n]},i]},o={className:"function",beginKeywords:"def",end:/[:={\[(\n;]/,excludeEnd:!0,contains:[i]};return{keywords:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,r,s,n,o,l,e.C_NUMBER_MODE,t]}}},{name:"scheme",create:function(e){var t="[^\\(\\)\\[\\]\\{\\}\",'`;#|\\\\\\s]+",a="(\\-|\\+)?\\d+([./]\\d+)?",r=a+"[+\\-]"+a+"i",s={"builtin-name":"case-lambda call/cc class define-class exit-handler field import inherit init-field interface let*-values let-values let/ec mixin opt-lambda override protect provide public rename require require-for-syntax syntax syntax-case syntax-error unit/sig unless when with-syntax and begin call-with-current-continuation call-with-input-file call-with-output-file case cond define define-syntax delay do dynamic-wind else for-each if lambda let let* let-syntax letrec letrec-syntax map or syntax-rules ' * + , ,@ - ... / ; < <= = => > >= ` abs acos angle append apply asin assoc assq assv atan boolean? caar cadr call-with-input-file call-with-output-file call-with-values car cdddar cddddr cdr ceiling char->integer char-alphabetic? char-ci<=? char-ci<? char-ci=? char-ci>=? char-ci>? char-downcase char-lower-case? char-numeric? char-ready? char-upcase char-upper-case? char-whitespace? char<=? char<? char=? char>=? char>? char? close-input-port close-output-port complex? cons cos current-input-port current-output-port denominator display eof-object? eq? equal? eqv? eval even? exact->inexact exact? exp expt floor force gcd imag-part inexact->exact inexact? input-port? integer->char integer? interaction-environment lcm length list list->string list->vector list-ref list-tail list? load log magnitude make-polar make-rectangular make-string make-vector max member memq memv min modulo negative? newline not null-environment null? number->string number? numerator odd? open-input-file open-output-file output-port? pair? peek-char port? positive? procedure? quasiquote quote quotient rational? rationalize read read-char real-part real? remainder reverse round scheme-report-environment set! set-car! set-cdr! sin sqrt string string->list string->number string->symbol string-append string-ci<=? string-ci<? string-ci=? string-ci>=? string-ci>? string-copy string-fill! string-length string-ref string-set! string<=? string<? string=? string>=? string>? string? substring symbol->string symbol? tan transcript-off transcript-on truncate values vector vector->list vector-fill! vector-length vector-ref vector-set! with-input-from-file with-output-to-file write write-char zero?"},n={className:"meta",begin:"^#!",end:"$"},i={className:"literal",begin:"(#t|#f|#\\\\"+t+"|#\\\\.)"},l={className:"number",variants:[{begin:a,relevance:0},{begin:r,relevance:0},{begin:"#b[0-1]+(/[0-1]+)?"},{begin:"#o[0-7]+(/[0-7]+)?"},{begin:"#x[0-9a-f]+(/[0-9a-f]+)?"}]},o=e.QUOTE_STRING_MODE,c=[e.COMMENT(";","$",{relevance:0}),e.COMMENT("#\\|","\\|#")],d={begin:t,relevance:0},m={className:"symbol",begin:"'"+t},u={endsWithParent:!0,relevance:0},h={begin:/'/,contains:[{begin:"\\(",end:"\\)",contains:["self",i,o,l,d,m]}]},p={className:"name",begin:t,lexemes:t,keywords:s},g={begin:/lambda/,endsWithParent:!0,returnBegin:!0,contains:[p,{begin:/\(/,end:/\)/,endsParent:!0,contains:[d]}]},b={variants:[{begin:"\\(",end:"\\)"},{begin:"\\[",end:"\\]"}],contains:[gLine truncated
literal:"true false nil"},contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.NUMBER_MODE,a,t.preprocessor],illegal:/#/}}},{name:"sql",create:function(e){var t=e.COMMENT("--","$");return{case_insensitive:!0,illegal:/[<>{}*#]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment",end:/;/,endsWithParent:!0,lexemes:/[\w\.]+/,keywords:{keyword:"abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias allocate allow alter always analyze ancillary and any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner inLine truncated
return{aliases:["styl"],case_insensitive:!1,keywords:"if else for in",illegal:"("+o.join("|")+")",contains:[e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,{begin:"\\.[a-zA-Z][a-zA-Z0-9_-]*"+i,returnBegin:!0,contains:[{className:"selector-class",begin:"\\.[a-zA-Z][a-zA-Z0-9_-]*"}]},{begin:"\\#[a-zA-Z][a-zA-Z0-9_-]*"+i,returnBegin:!0,contains:[{className:"selector-id",begin:"\\#[a-zA-Z][a-zA-Z0-9_-]*"}]},{begin:"\\b("+n.join("|")+")"+i,returnBegin:!0,contains:[{className:"selector-tag",begin:"\\b[a-zA-Z][a-zA-Z0-9_-]*"}]},{begin:"&?:?:\\b("+s.join("|")+")"+i},{begin:"@("+r.join("|")+")\\b"},t,e.CSS_NUMBER_MODE,e.NUMBER_MODE,{className:"function",begin:"^[a-zA-Z][a-zA-Z0-9_-]*\\(.*\\)",illegal:"[\\n]",returnBegin:!0,contains:[{className:"title",begin:"\\b[a-zA-Z][a-zA-Z0-9_-]*"},{className:"params",begin:/\(/,end:/\)/,contains:[a,t,e.APOS_STRING_MODE,e.CSS_NUMBER_MODE,e.NUMBER_MODE,e.QUOTE_STRING_MODE]}]},{className:"attribute",begin:"\\b("+l.reverse().join("|")+")\\b",starts:{end:/;|$/,contains:[a,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE,e.NUMBER_MODE,e.C_BLOCK_COMMENT_MODE],illegal:/\./,relevance:0}}]}}},{name:"subunit",create:function(e){var t={className:"string",begin:"\\[\n(multipart)?",end:"\\]\n"},a={className:"string",begin:"\\d{4}-\\d{2}-\\d{2}(\\s+)\\d{2}:\\d{2}:\\d{2}.\\d+Z"},r={className:"string",begin:"(\\+|-)\\d+"},s={className:"keyword",relevance:10,variants:[{begin:"^(test|testing|success|successful|failure|error|skip|xfail|uxsuccess)(:?)\\s+(test)?"},{begin:"^progress(:?)(\\s+)?(pop|push)?"},{begin:"^tags:"},{begin:"^time:"}]};return{case_insensitive:!0,contains:[t,a,r,s]}}},{name:"swift",create:function(e){var t={keyword:"__COLUMN__ __FILE__ __FUNCTION__ __LINE__ as as! as? associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},a={className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},r=e.COMMENT("/\\*","\\*/",{contains:["self"]}),s={className:"subst",begin:/\\\(/,end:"\\)",keywords:t,contains:[]},n={className:"number",begin:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0},i=e.inherit(e.QUOTE_STRING_MODE,{contains:[s,e.BACKSLASH_ESCAPE]});return s.contains=[n],{keywords:t,contains:[i,e.C_LINE_COMMENT_MODE,r,a,n,{className:"function",beginKeywords:"func",end:"{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{begin:/</,end:/>/},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:t,contains:["self",n,i,e.C_BLOCK_COMMENT_MODE,{begin:":"}],illegal:/["']/}],illegal:/\[|%/},{className:"class",beginKeywords:"struct protocol class extension enum",keywords:t,end:"\\{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/})]},{className:"meta",begin:"(@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain)"},{beginKeywords:"import",end:/$/,contains:[e.C_LINE_COMMENT_MODE,r]}]}}},{name:"taggerscript",create:function(e){var t={className:"comment",begin:/\$noop\(/,end:/\)/,contains:[{begin:/\(/,end:/\)/,contains:["self",{begin:/\\./}]}],relevance:10},a={className:"keyword",begin:/\$(?!noop)[a-zA-Z][_a-zA-Z0-9]*/,end:/\(/,excludeEnd:!0},r={classNamLine truncated
built_in:"ip eip rip al ah bl bh cl ch dl dh sil dil bpl spl r8b r9b r10b r11b r12b r13b r14b r15b ax bx cx dx si di bp sp r8w r9w r10w r11w r12w r13w r14w r15w eax ebx ecx edx esi edi ebp esp eip r8d r9d r10d r11d r12d r13d r14d r15d rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 cs ds es fs gs ss st st0 st1 st2 st3 st4 st5 st6 st7 mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7 xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15 xmm16 xmm17 xmm18 xmm19 xmm20 xmm21 xmm22 xmm23 xmm24 xmm25 xmm26 xmm27 xmm28 xmm29 xmm30 xmm31 ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm8 ymm9 ymm10 ymm11 ymm12 ymm13 ymm14 ymm15 ymm16 ymm17 ymm18 ymm19 ymm20 ymm21 ymm22 ymm23 ymm24 ymm25 ymm26 ymm27 ymm28 ymm29 ymm30 ymm31 zmm0 zmm1 zmm2 zmm3 zmm4 zmm5 zmm6 zmm7 zmm8 zmm9 zmm10 zmm11 zmm12 zmm13 zmm14 zmm15 zmm16 zmm17 zmm18 zmm19 zmm20 zmm21 zmm22 zmm23 zmm24 zmm25 zmm26 zmm27 zmm28 zmm29 zmm30 zmm31 k0 k1 k2 k3 k4 k5 k6 k7 bnd0 bnd1 bnd2 bnd3 cr0 cr1 cr2 cr3 cr4 cr8 dr0 dr1 dr2 dr3 dr8 tr3 tr4 tr5 tr6 tr7 r0 r1 r2 r3 r4 r5 r6 r7 r0b r1b r2b r3b r4b r5b r6b r7b r0w r1w r2w r3w r4w r5w r6w r7w r0d r1d r2d r3d r4d r5d r6d r7d r0h r1h r2h r3h r0l r1l r2l r3l r4l r5l r6l r7l r8l r9l r10l r11l r12l r13l r14l r15l db dw dd dq dt ddq do dy dz resb resw resd resq rest resdq reso resy resz incbin equ times byte word dword qword nosplit rel abs seg wrt strict near far a32 ptr",meta:"%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif %if %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep %endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment .nolist __FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ __UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend align alignb sectalign daz nodaz up down zero default option assume public bits use16 use32 use64 default section segment absolute extern global common cpu float __utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ __float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ __Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__"},contains:[e.COMMENT(";","$",{relevance:0}),{className:"number",variants:[{begin:"\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|(0[Xx])?[0-9][0-9_]*\\.?[0-9_]*(?:[pP](?:[+-]?[0-9_]+)?)?)\\b",relevance:0},{begin:"\\$[0-9][0-9A-Fa-f]*",relevance:0},{begin:"\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[Hh]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b"},{begin:"\\b(?:0[Xx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b"}]},e.QUOTE_STRING_MODE,{className:"string",variants:[{begin:"'",end:"[^\\\\]'"},{begin:"`",end:"[^\\\\]`"}],relevance:0},{className:"symbol",variants:[{begin:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)"},{begin:"^\\s*%%[A-Za-z0-9_$#@~.?]*:"}],relevance:0},{className:"subst",begin:"%[0-9]+",relevance:0},{className:"subst",begin:"%!S+",relevance:0},{className:"meta",begin:/^\s*\.[\w_-]+/}]}}},{name:"xl",create:function(e){var t="ObjectLoader Animate MovieCredits Slides Filters Shading Materials LensFlare Mapping VLCAudioVideo StereoDecoder PointCloud NetworkAccess RemoteControl RegExp ChromaKey Snowfall NodeJS Speech Charts",a={keyword:"if then else do while until for loop import with is as where when by data constant integer real text name boolean symbol infix prefix postfix block tree",literal:"true false nil",built_in:"in mod rem and or xor not abs sign floor ceil sqrt sin cos tan asin acos atan exp expm1 log log2 log10 log1p pi at text_length text_range text_find text_replace contains page slide basic_slide title_slide title subtitle fade_in fade_out fade_at clear_color color line_color line_width texture_wrap texture_transform texture scale_?x scale_?y scale_?z? translate_?x translate_?y translate_?z? rotate_?x rotate_?y rotate_?z? rectangle circle ellipse sphere path line_to move_to quad_to curve_to theme background contents locally time mouse_?x mouse_?y mouse_buttons "+t},r={className:"string",begin:'"',end:'"',illegal:"\\n"},s={className:"string",begin:"'",end:"'",illegal:"\\n"},n={className:"string",begin:"<<",end:">>"},i={className:"number",begin:"[0-9]+#[0-9A-Z_]+(\\.[0-9-A-Z_]+)?#?([Ee][+-]?[0-9]+)?"},l={beginKeywords:"import",end:"$",keywords:a,contains:[r]},o={className:"function",begin:/[a-z][^\n]*->/,returnBegin:!0,end:/->/,contains:[e.inherit(e.TITLE_MODE,{starts:{endsWithParent:!0,keywords:a}})]};return{aliases:["tao"],lexemes:/[a-zA-Z][a-zA-Z0-9_?]*/,keywords:a,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,r,s,n,o,l,i,e.NUMBER_MODE]}}},{name:"xml",create:function(e){var t="[A-Za-z0-9\\._:-]+",a={endsWithParent:!0,illegal:/</,relevLine truncated
"atelier-lakeside-light":".hljs-atelier-lakeside-light .hljs-comment,.hljs-atelier-lakeside-light .hljs-quote{color:#5a7b8c}.hljs-atelier-lakeside-light .hljs-variable,.hljs-atelier-lakeside-light .hljs-template-variable,.hljs-atelier-lakeside-light .hljs-attribute,.hljs-atelier-lakeside-light .hljs-tag,.hljs-atelier-lakeside-light .hljs-name,.hljs-atelier-lakeside-light .hljs-regexp,.hljs-atelier-lakeside-light .hljs-link,.hljs-atelier-lakeside-light .hljs-name,.hljs-atelier-lakeside-light .hljs-selector-id,.hljs-atelier-lakeside-light .hljs-selector-class{color:#d22d72}.hljs-atelier-lakeside-light .hljs-number,.hljs-atelier-lakeside-light .hljs-meta,.hljs-atelier-lakeside-light .hljs-built_in,.hljs-atelier-lakeside-light .hljs-builtin-name,.hljs-atelier-lakeside-light .hljs-literal,.hljs-atelier-lakeside-light .hljs-type,.hljs-atelier-lakeside-light .hljs-params{color:#935c25}.hljs-atelier-lakeside-light .hljs-string,.hljs-atelier-lakeside-light .hljs-symbol,.hljs-atelier-lakeside-light .hljs-bullet{color:#568c3b}.hljs-atelier-lakeside-light .hljs-title,.hljs-atelier-lakeside-light .hljs-section{color:#257fad}.hljs-atelier-lakeside-light .hljs-keyword,.hljs-atelier-lakeside-light .hljs-selector-tag{color:#6b6bb8}.hljs-atelier-lakeside-light .hljs{display:block;overflow-x:auto;background:#ebf8ff;color:#516d7b;padding:.5em}.hljs-atelier-lakeside-light .hljs-emphasis{font-style:italic}.hljs-atelier-lakeside-light .hljs-strong{font-weight:bold}","atelier-plateau-dark":".hljs-atelier-plateau-dark .hljs-comment,.hljs-atelier-plateau-dark .hljs-quote{color:#7e7777}.hljs-atelier-plateau-dark .hljs-variable,.hljs-atelier-plateau-dark .hljs-template-variable,.hljs-atelier-plateau-dark .hljs-attribute,.hljs-atelier-plateau-dark .hljs-tag,.hljs-atelier-plateau-dark .hljs-name,.hljs-atelier-plateau-dark .hljs-regexp,.hljs-atelier-plateau-dark .hljs-link,.hljs-atelier-plateau-dark .hljs-name,.hljs-atelier-plateau-dark .hljs-selector-id,.hljs-atelier-plateau-dark .hljs-selector-class{color:#ca4949}.hljs-atelier-plateau-dark .hljs-number,.hljs-atelier-plateau-dark .hljs-meta,.hljs-atelier-plateau-dark .hljs-built_in,.hljs-atelier-plateau-dark .hljs-builtin-name,.hljs-atelier-plateau-dark .hljs-literal,.hljs-atelier-plateau-dark .hljs-type,.hljs-atelier-plateau-dark .hljs-params{color:#b45a3c}.hljs-atelier-plateau-dark .hljs-string,.hljs-atelier-plateau-dark .hljs-symbol,.hljs-atelier-plateau-dark .hljs-bullet{color:#4b8b8b}.hljs-atelier-plateau-dark .hljs-title,.hljs-atelier-plateau-dark .hljs-section{color:#7272ca}.hljs-atelier-plateau-dark .hljs-keyword,.hljs-atelier-plateau-dark .hljs-selector-tag{color:#8464c4}.hljs-atelier-plateau-dark .hljs-deletion,.hljs-atelier-plateau-dark .hljs-addition{color:#1b1818;display:inline-block;width:100%}.hljs-atelier-plateau-dark .hljs-deletion{background-color:#ca4949}.hljs-atelier-plateau-dark .hljs-addition{background-color:#4b8b8b}.hljs-atelier-plateau-dark .hljs{display:block;overflow-x:auto;background:#1b1818;color:#8a8585;padding:.5em}.hljs-atelier-plateau-dark .hljs-emphasis{font-style:italic}.hljs-atelier-plateau-dark .hljs-strong{font-weight:bold}","atelier-plateau-light":".hljs-atelier-plateau-light .hljs-comment,.hljs-atelier-plateau-light .hljs-quote{color:#655d5d}.hljs-atelier-plateau-light .hljs-variable,.hljs-atelier-plateau-light .hljs-template-variable,.hljs-atelier-plateau-light .hljs-attribute,.hljs-atelier-plateau-light .hljs-tag,.hljs-atelier-plateau-light .hljs-name,.hljs-atelier-plateau-light .hljs-regexp,.hljs-atelier-plateau-light .hljs-link,.hljs-atelier-plateau-light .hljs-name,.hljs-atelier-plateau-light .hljs-selector-id,.hljs-atelier-plateau-light .hljs-selector-class{color:#ca4949}.hljs-atelier-plateau-light .hljs-number,.hljs-atelier-plateau-light .hljs-meta,.hljs-atelier-plateau-light .hljs-built_in,.hljs-atelier-plateau-light .hljs-builtin-name,.hljs-atelier-plateau-light .hljs-literal,.hljs-atelier-plateau-light .hljs-type,.hljs-atelier-plateau-light .hljs-params{color:#b45a3c}.hljs-atelier-plateau-light .hljs-string,.hljs-atelier-plateau-light .hljs-symbol,.hljs-atelier-plateau-light .hljs-bullet{color:#4b8b8b}.hljs-atelier-plateau-light .hljs-title,.hljs-atelier-plateau-light .hljs-section{color:#7272ca}.hljs-atelier-plateau-light .hljs-keyword,.hljs-atelier-plateau-light .hljs-selector-tag{color:#8464c4}.hljs-atelier-plateau-light .hljs-deletion,.hljs-atelier-plateau-light .hljs-addition{color:#1b1818;display:inline-block;width:100%}.hljs-atelier-plateau-light .hljs-deletion{background-color:#ca4949}.hljs-atelier-plateau-light .hljs-addition{background-color:#4b8b8b}.hljs-atelier-plateau-light .hljs{display:block;overflow-x:auto;background:#f4ecec;color:#585050;padding:.5em}.hljs-atelier-plateau-light .hljs-emphasis{font-style:italic}.hljs-atelier-plateau-light .hljs-strong{font-weight:bold}","atelier-savanna-dark":".hljs-atelier-savanna-dark .hljs-comment,.hljs-atelier-savanna-dark .hljs-quote{color:#78877d}.hljs-atelier-savanna-dark .hljs-vaLine truncated
grayscale:".hljs-grayscale .hljs{display:block;overflow-x:auto;padding:.5em;color:#333;background:#fff}.hljs-grayscale .hljs-comment,.hljs-grayscale .hljs-quote{color:#777;font-style:italic}.hljs-grayscale .hljs-keyword,.hljs-grayscale .hljs-selector-tag,.hljs-grayscale .hljs-subst{color:#333;font-weight:bold}.hljs-grayscale .hljs-number,.hljs-grayscale .hljs-literal{color:#777}.hljs-grayscale .hljs-string,.hljs-grayscale .hljs-doctag,.hljs-grayscale .hljs-formula{color:#333;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAJ0lEQVQIW2O8e/fufwYGBgZBQUEQxcCIIfDu3Tuwivfv30NUoAsAALHpFMMLqZlPAAAAAElFTkSuQmCC) repeat}.hljs-grayscale .hljs-title,.hljs-grayscale .hljs-section,.hljs-grayscale .hljs-selector-id{color:#000;font-weight:bold}.hljs-grayscale .hljs-subst{font-weight:normal}.hljs-grayscale .hljs-class .hljs-title,.hljs-grayscale .hljs-type,.hljs-grayscale .hljs-name{color:#333;font-weight:bold}.hljs-grayscale .hljs-tag{color:#333}.hljs-grayscale .hljs-regexp{color:#333;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAYAAADA+m62AAAAPUlEQVQYV2NkQAN37979r6yszIgujiIAU4RNMVwhuiQ6H6wQl3XI4oy4FMHcCJPHcDS6J2A2EqUQpJhohQDexSef15DBCwAAAABJRU5ErkJggg==) repeat}.hljs-grayscale .hljs-symbol,.hljs-grayscale .hljs-bullet,.hljs-grayscale .hljs-link{color:#000;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAKElEQVQIW2NkQAO7d+/+z4gsBhJwdXVlhAvCBECKwIIwAbhKZBUwBQA6hBpm5efZsgAAAABJRU5ErkJggg==) repeat}.hljs-grayscale .hljs-built_in,.hljs-grayscale .hljs-builtin-name{color:#000;text-decoration:underline}.hljs-grayscale .hljs-meta{color:#999;font-weight:bold}.hljs-grayscale .hljs-deletion{color:#fff;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAADCAYAAABS3WWCAAAAE0lEQVQIW2MMDQ39zzhz5kwIAQAyxweWgUHd1AAAAABJRU5ErkJggg==) repeat}.hljs-grayscale .hljs-addition{color:#000;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAALUlEQVQYV2N89+7dfwYk8P79ewZBQUFkIQZGOiu6e/cuiptQHAPl0NtNxAQBAM97Oejj3Dg7AAAAAElFTkSuQmCC) repeat}.hljs-grayscale .hljs-emphasis{font-style:italic}.hljs-grayscale .hljs-strong{font-weight:bold}","gruvbox-dark":".hljs-gruvbox-dark .hljs{display:block;overflow-x:auto;padding:.5em;background:#282828}.hljs-gruvbox-dark .hljs,.hljs-gruvbox-dark .hljs-subst{color:#ebdbb2}.hljs-gruvbox-dark .hljs-deletion,.hljs-gruvbox-dark .hljs-formula,.hljs-gruvbox-dark .hljs-keyword,.hljs-gruvbox-dark .hljs-link,.hljs-gruvbox-dark .hljs-selector-tag{color:#fb4934}.hljs-gruvbox-dark .hljs-built_in,.hljs-gruvbox-dark .hljs-emphasis,.hljs-gruvbox-dark .hljs-name,.hljs-gruvbox-dark .hljs-quote,.hljs-gruvbox-dark .hljs-strong,.hljs-gruvbox-dark .hljs-title,.hljs-gruvbox-dark .hljs-variable{color:#83a598}.hljs-gruvbox-dark .hljs-attr,.hljs-gruvbox-dark .hljs-params,.hljs-gruvbox-dark .hljs-template-tag,.hljs-gruvbox-dark .hljs-type{color:#fabd2f}.hljs-gruvbox-dark .hljs-builtin-name,.hljs-gruvbox-dark .hljs-doctag,.hljs-gruvbox-dark .hljs-literal,.hljs-gruvbox-dark .hljs-number{color:#8f3f71}.hljs-gruvbox-dark .hljs-code,.hljs-gruvbox-dark .hljs-meta,.hljs-gruvbox-dark .hljs-regexp,.hljs-gruvbox-dark .hljs-selector-id,.hljs-gruvbox-dark .hljs-template-variable{color:#fe8019}.hljs-gruvbox-dark .hljs-addition,.hljs-gruvbox-dark .hljs-meta-string,.hljs-gruvbox-dark .hljs-section,.hljs-gruvbox-dark .hljs-selector-attr,.hljs-gruvbox-dark .hljs-selector-class,.hljs-gruvbox-dark .hljs-string,.hljs-gruvbox-dark .hljs-symbol{color:#b8bb26}.hljs-gruvbox-dark .hljs-attribute,.hljs-gruvbox-dark .hljs-bullet,.hljs-gruvbox-dark .hljs-class,.hljs-gruvbox-dark .hljs-function,.hljs-gruvbox-dark .hljs-function .hljs-keyword,.hljs-gruvbox-dark .hljs-meta-keyword,.hljs-gruvbox-dark .hljs-selector-pseudo,.hljs-gruvbox-dark .hljs-tag{color:#8ec07c}.hljs-gruvbox-dark .hljs-comment{color:#928374}.hljs-gruvbox-dark .hljs-link_label,.hljs-gruvbox-dark .hljs-literal,.hljs-gruvbox-dark .hljs-number{color:#d3869b}.hljs-gruvbox-dark .hljs-comment,.hljs-gruvbox-dark .hljs-emphasis{font-style:italic}.hljs-gruvbox-dark .hljs-section,.hljs-gruvbox-dark .hljs-strong,.hljs-gruvbox-dark .hljs-tag{font-weight:bold}","gruvbox-light":".hljs-gruvbox-light .hljs{display:block;overflow-x:auto;padding:.5em;background:#fbf1c7}.hljs-gruvbox-light .hljs,.hljs-gruvbox-light .hljs-subst{color:#3c3836}.hljs-gruvbox-light .hljs-deletion,.hljs-gruvbox-light .hljs-formula,.hljs-gruvbox-light .hljs-keyword,.hljs-gruvbox-light .hljs-link,.hljs-gruvbox-light .hljs-selector-tag{color:#9d0006}.hljs-gruvbox-light .hljs-built_in,.hljs-gruvbox-light .hljs-emphasis,.hljs-gruvbox-light .hljs-name,.hljs-gruvbox-light .hljs-quote,.hljs-gruvbox-light .hljs-strong,.hljs-gruvbox-light .hljs-title,.hljs-gruvbox-light .hljs-variable{color:#076678}.hljs-gruvbox-light .hljs-attr,.hljs-gruvbox-light .hljs-params,.hljs-gruvbox-light .hljs-template-tag,.hljs-gruvbox-light .hljs-type{color:#b57614}.hljs-gruvbox-light .hljs-builtin-name,.hljs-gruvbox-light .hljs-doctag,Line truncated
"solarized-dark":".hljs-solarized-dark .hljs{display:block;overflow-x:auto;padding:.5em;background:#002b36;color:#839496}.hljs-solarized-dark .hljs-comment,.hljs-solarized-dark .hljs-quote{color:#586e75}.hljs-solarized-dark .hljs-keyword,.hljs-solarized-dark .hljs-selector-tag,.hljs-solarized-dark .hljs-addition{color:#859900}.hljs-solarized-dark .hljs-number,.hljs-solarized-dark .hljs-string,.hljs-solarized-dark .hljs-meta .hljs-meta-string,.hljs-solarized-dark .hljs-literal,.hljs-solarized-dark .hljs-doctag,.hljs-solarized-dark .hljs-regexp{color:#2aa198}.hljs-solarized-dark .hljs-title,.hljs-solarized-dark .hljs-section,.hljs-solarized-dark .hljs-name,.hljs-solarized-dark .hljs-selector-id,.hljs-solarized-dark .hljs-selector-class{color:#268bd2}.hljs-solarized-dark .hljs-attribute,.hljs-solarized-dark .hljs-attr,.hljs-solarized-dark .hljs-variable,.hljs-solarized-dark .hljs-template-variable,.hljs-solarized-dark .hljs-class .hljs-title,.hljs-solarized-dark .hljs-type{color:#b58900}.hljs-solarized-dark .hljs-symbol,.hljs-solarized-dark .hljs-bullet,.hljs-solarized-dark .hljs-subst,.hljs-solarized-dark .hljs-meta,.hljs-solarized-dark .hljs-meta .hljs-keyword,.hljs-solarized-dark .hljs-selector-attr,.hljs-solarized-dark .hljs-selector-pseudo,.hljs-solarized-dark .hljs-link{color:#cb4b16}.hljs-solarized-dark .hljs-built_in,.hljs-solarized-dark .hljs-deletion{color:#dc322f}.hljs-solarized-dark .hljs-formula{background:#073642}.hljs-solarized-dark .hljs-emphasis{font-style:italic}.hljs-solarized-dark .hljs-strong{font-weight:bold}","solarized-light":".hljs-solarized-light .hljs{display:block;overflow-x:auto;padding:.5em;background:#fdf6e3;color:#657b83}.hljs-solarized-light .hljs-comment,.hljs-solarized-light .hljs-quote{color:#93a1a1}.hljs-solarized-light .hljs-keyword,.hljs-solarized-light .hljs-selector-tag,.hljs-solarized-light .hljs-addition{color:#859900}.hljs-solarized-light .hljs-number,.hljs-solarized-light .hljs-string,.hljs-solarized-light .hljs-meta .hljs-meta-string,.hljs-solarized-light .hljs-literal,.hljs-solarized-light .hljs-doctag,.hljs-solarized-light .hljs-regexp{color:#2aa198}.hljs-solarized-light .hljs-title,.hljs-solarized-light .hljs-section,.hljs-solarized-light .hljs-name,.hljs-solarized-light .hljs-selector-id,.hljs-solarized-light .hljs-selector-class{color:#268bd2}.hljs-solarized-light .hljs-attribute,.hljs-solarized-light .hljs-attr,.hljs-solarized-light .hljs-variable,.hljs-solarized-light .hljs-template-variable,.hljs-solarized-light .hljs-class .hljs-title,.hljs-solarized-light .hljs-type{color:#b58900}.hljs-solarized-light .hljs-symbol,.hljs-solarized-light .hljs-bullet,.hljs-solarized-light .hljs-subst,.hljs-solarized-light .hljs-meta,.hljs-solarized-light .hljs-meta .hljs-keyword,.hljs-solarized-light .hljs-selector-attr,.hljs-solarized-light .hljs-selector-pseudo,.hljs-solarized-light .hljs-link{color:#cb4b16}.hljs-solarized-light .hljs-built_in,.hljs-solarized-light .hljs-deletion{color:#dc322f}.hljs-solarized-light .hljs-formula{background:#eee8d5}.hljs-solarized-light .hljs-emphasis{font-style:italic}.hljs-solarized-light .hljs-strong{font-weight:bold}",sunburst:".hljs-sunburst .hljs{display:block;overflow-x:auto;padding:.5em;background:#000;color:#f8f8f8}.hljs-sunburst .hljs-comment,.hljs-sunburst .hljs-quote{color:#aeaeae;font-style:italic}.hljs-sunburst .hljs-keyword,.hljs-sunburst .hljs-selector-tag,.hljs-sunburst .hljs-type{color:#e28964}.hljs-sunburst .hljs-string{color:#65b042}.hljs-sunburst .hljs-subst{color:#daefa3}.hljs-sunburst .hljs-regexp,.hljs-sunburst .hljs-link{color:#e9c062}.hljs-sunburst .hljs-title,.hljs-sunburst .hljs-section,.hljs-sunburst .hljs-tag,.hljs-sunburst .hljs-name{color:#89bdff}.hljs-sunburst .hljs-class .hljs-title,.hljs-sunburst .hljs-doctag{text-decoration:underline}.hljs-sunburst .hljs-symbol,.hljs-sunburst .hljs-bullet,.hljs-sunburst .hljs-number{color:#3387cc}.hljs-sunburst .hljs-params,.hljs-sunburst .hljs-variable,.hljs-sunburst .hljs-template-variable{color:#3e87e3}.hljs-sunburst .hljs-attribute{color:#cda869}.hljs-sunburst .hljs-meta{color:#8996a8}.hljs-sunburst .hljs-formula{background-color:#0e2231;color:#f8f8f8;font-style:italic}.hljs-sunburst .hljs-addition{background-color:#253b22;color:#f8f8f8}.hljs-sunburst .hljs-deletion{background-color:#420e09;color:#f8f8f8}.hljs-sunburst .hljs-selector-class{color:#9b703f}.hljs-sunburst .hljs-selector-id{color:#8b98ab}.hljs-sunburst .hljs-emphasis{font-style:italic}.hljs-sunburst .hljs-strong{font-weight:bold}","tomorrow-night-blue":".hljs-tomorrow-night-blue .hljs-comment,.hljs-tomorrow-night-blue .hljs-quote{color:#7285b7}.hljs-tomorrow-night-blue .hljs-variable,.hljs-tomorrow-night-blue .hljs-template-variable,.hljs-tomorrow-night-blue .hljs-tag,.hljs-tomorrow-night-blue .hljs-name,.hljs-tomorrow-night-blue .hljs-selector-id,.hljs-tomorrow-night-blue .hljs-selector-class,.hljs-tomorrow-night-blue .hljs-regexp,.hljs-tomorrow-night-blue .hljs-deletion{color:#ff9da4}.hljs-tomorrow-nigLine truncated
containerLayout:'<div class="remark-notes-area">\n <div class="remark-top-area">\n <div class="remark-toolbar">\n <a class="remark-toolbar-link" href="#increase">+</a>\n <a class="remark-toolbar-link" href="#decrease">-</a>\n <span class="remark-toolbar-timer"></span>\n </div>\n </div>\n <div class="remark-bottom-area">\n <div class="remark-notes-current-area">\n <div class="remark-toggle">Notes for current slide</div>\n <div class="remark-notes"></div>\n </div>\n <div class="remark-notes-preview-area">\n <div class="remark-toggle">Notes for next slide</div>\n <div class="remark-notes-preview"></div>\n </div>\n </div>\n</div>\n<div class="remark-slides-area"></div>\n<div class="remark-preview-area"></div>\n<div class="remark-backdrop"></div>\n<div class="remark-pause">\n <div class="remark-pause-lozenge">\n <span>Paused</span>\n </div>\n</div>\n<div class="remark-help">\n <div class="remark-help-content">\n <h1>Help</h1>\n <p><b>Keyboard shortcuts</b></p>\n <table class="light-keys">\n <tr>\n <td>\n <span class="key"><b>&uarr;</b></span>,\n <span class="key"><b>&larr;</b></span>,\n <span class="key">Pg Up</span>,\n <span class="key">k</span>\n </td>\n <td>Go to previous slide</td>\n </tr>\n <tr>\n <td>\n <span class="key"><b>&darr;</b></span>,\n <span class="key"><b>&rarr;</b></span>,\n <span class="key">Pg Dn</span>,\n <span class="key">Space</span>,\n <span class="key">j</span>\n </td>\n <td>Go to next slide</td>\n </tr>\n <tr>\n <td>\n <span class="key">Home</span>\n </td>\n <td>Go to first slide</td>\n </tr>\n <tr>\n <td>\n <span class="key">End</span>\n </td>\n <td>Go to last slide</td>\n </tr>\n <tr>\n <td>\n Number + <span class="key">Return</span>\n </td>\n <td>Go to specific slide</td>\n </tr>\n <tr>\n <td>\n <span class="key">b</span>&nbsp;/\n <span class="key">m</span>&nbsp;/\n <span class="key">f</span>\n </td>\n <td>Toggle blackout / mirrored / fullscreen mode</td>\n </tr>\n <tr>\n <td>\n <span class="key">c</span>\n </td>\n <td>Clone slideshow</td>\n </tr>\n <tr>\n <td>\n <span class="key">p</span>\n </td>\n <td>Toggle presenter mode</td>\n </tr>\n <tr>\n <td>\n <span class="key">t</span>\n </td>\n <td>Restart the presentation timer</td>\n </tr>\n <tr>\n <td>\n <span class="key">?</span>,\n <span class="key">h</span>\n </td>\n <td>Toggle this help</td>\n </tr>\n </table>\n </div>\n <div class="content dismiss">\n <table class="light-keys">\n <tr>\n <td>\n <span class="key">Esc</span>\n </td>\n <td>Back to slideshow</td>\n </tr>\n </table>\n </div>\n</div>\n'}},{}],23:[function(e,t,a){function r(e,t){var a=this;a.events=e,a.slideshow=t,a.ratio=s(t),a.dimensions=n(a.ratio),a.events.on("propertiesChanged",function(e){e.hasOwnProperty("ratio")&&(a.ratio=s(t),a.dimensions=n(a.ratio))})}function s(e){var t,a=e.getRatio().split(":");return t={width:parseInt(a[0],10),height:parseInt(a[1],10)},t.ratio=t.width/t.height,t}function n(e){return{width:Math.floor(i/o*e.ratio),height:l}}var i=908,l=681,o=i/l;t.exports=r,r.prototype.scaleToFit=function(e,t){var a,r,s,n,i,l=this,o=t.clientHeight,c=t.clientWidth,d=l.ratio,m=l.dimensions;a=c/d.width>o/d.height?o/m.height:c/m.width,r=m.width*a,s=m.height*a,n=(c-r)/2,i=(o-s)/2,e.style["-webkit-transform"]="scale("+a+")",e.style.MozTransform="scale("+a+")",e.style.left=Math.max(n,0)+"px",e.style.top=Math.max(i,0)+"px"}},{}],24:[function(e,t,a){a.addClass=function(e,t){e.className=a.getClasses(e).concat([t]).join(" ")},a.removeClass=function(e,t){e.className=a.getClasses(e).filter(function(e){return e!==t}).join(" ")},a.toggleClass=function(e,t){var r=a.getClasses(e),s=r.indexOf(t);s!==-1?r.splice(s,1):r.push(t),e.className=r.join(" ")},a.getClasses=function(e){return e.className.split(" ").filter(function(e){return""!==e})},a.hasClass=function(e,t){return a.getClasses(e).indexOf(t)!==-1},a.getPrefixedProperty=function(e,t){var a=t[0].toUpperCase()+t.slice(1);return e[t]||e["moz"+a]||e["webkit"+a]}},{}],25:[function(e,t,a){function r(e,t,a){var r=this;r.events=e,r.element=t,r.slideViewsAccessor=a,r.configureElements(),e.on("showSlide",function(e){r.showSlide(e)})}e("../converter");t.exports=r,r.prototype.showSlide=function(e){var t=this,a=t.slideViewsAccessor(),r=a[e],s=a[e+1];t.notesElement.innerHTML=r.notesElement.innerHTML,s?t.notesPreviewElement.innerHTML=s.notesElement.innerHTML:t.notesPreviewElement.innerHTML=""},r.prototype.configureElements=function(){var Line truncated
Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

File diff suppressed because it is too large. Load diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

+14 -2
View File
@@ -1,11 +1,23 @@
version: "3"
services:
snake:
image: bsergean/ws:build
entrypoint: ws snake --port 8765 --host 0.0.0.0 --redis_hosts redis1
ports:
- "8765:8765"
networks:
- ws-net
depends_on:
- redis1
ws:
security_opt:
- seccomp:unconfined
cap_add:
- SYS_PTRACE
stdin_open: true
tty: true
image: bsergean/ws:build
ports:
- "8765:8765"
entrypoint: bash
networks:
- ws-net
+52
View File
@@ -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"]
+43
View File
@@ -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"]
@@ -1,30 +1,24 @@
FROM debian:buster
# Build time
FROM ubuntu:xenial as build
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update
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 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 adduser app
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"]
# Now run in usermode
USER app
EXPOSE 8765
CMD ["bash"]
# RUN ["make"]
RUN ["make", "test"]
+11 -1
View File
@@ -10,7 +10,7 @@ namespace ix
{
std::atomic<uint64_t> ConnectionState::_globalId(0);
ConnectionState::ConnectionState()
ConnectionState::ConnectionState() : _terminated(false)
{
computeId();
}
@@ -29,5 +29,15 @@ namespace ix
{
return std::make_shared<ConnectionState>();
}
bool ConnectionState::isTerminated() const
{
return _terminated;
}
void ConnectionState::setTerminated()
{
_terminated = true;
}
}
+4
View File
@@ -21,9 +21,13 @@ namespace ix
virtual void computeId();
virtual const std::string& getId() const;
void setTerminated();
bool isTerminated() const;
static std::shared_ptr<ConnectionState> createConnectionState();
protected:
std::atomic<bool> _terminated;
std::string _id;
static std::atomic<uint64_t> _globalId;
+48 -11
View File
@@ -19,20 +19,19 @@ namespace ix
std::mutex DNSLookup::_activeJobsMutex;
DNSLookup::DNSLookup(const std::string& hostname, int port, int64_t wait) :
_hostname(hostname),
_port(port),
_wait(wait),
_res(nullptr),
_done(false),
_id(_nextId++)
{
setHostname(hostname);
}
DNSLookup::~DNSLookup()
{
// Remove this job from the active jobs list
std::unique_lock<std::mutex> lock(_activeJobsMutex);
std::lock_guard<std::mutex> lock(_activeJobsMutex);
_activeJobs.erase(_id);
}
@@ -79,7 +78,7 @@ namespace ix
return nullptr;
}
return getAddrInfo(_hostname, _port, errMsg);
return getAddrInfo(getHostname(), _port, errMsg);
}
struct addrinfo* DNSLookup::resolveAsync(std::string& errMsg,
@@ -97,7 +96,7 @@ namespace ix
// Record job in the active Job set
{
std::unique_lock<std::mutex> lock(_activeJobsMutex);
std::lock_guard<std::mutex> lock(_activeJobsMutex);
_activeJobs.insert(_id);
}
@@ -105,7 +104,7 @@ namespace ix
// Good resource on thread forced termination
// https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread
//
_thread = std::thread(&DNSLookup::run, this, _id, _hostname, _port);
_thread = std::thread(&DNSLookup::run, this, _id, getHostname(), _port);
_thread.detach();
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
@@ -135,7 +134,8 @@ namespace ix
return nullptr;
}
return _res;
errMsg = getErrMsg();
return getRes();
}
void DNSLookup::run(uint64_t id, const std::string& hostname, int port) // thread runner
@@ -147,18 +147,55 @@ namespace ix
struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
// if this isn't an active job, and the control thread is gone
// there is not thing to do, and we don't want to touch the defunct
// there is nothing to do, and we don't want to touch the defunct
// object data structure such as _errMsg or _condition
std::unique_lock<std::mutex> lock(_activeJobsMutex);
std::lock_guard<std::mutex> lock(_activeJobsMutex);
if (_activeJobs.count(id) == 0)
{
return;
}
// Copy result into the member variables
_res = res;
_errMsg = errMsg;
setRes(res);
setErrMsg(errMsg);
_condition.notify_one();
_done = true;
}
void DNSLookup::setHostname(const std::string& hostname)
{
std::lock_guard<std::mutex> lock(_hostnameMutex);
_hostname = hostname;
}
const std::string& DNSLookup::getHostname()
{
std::lock_guard<std::mutex> lock(_hostnameMutex);
return _hostname;
}
void DNSLookup::setErrMsg(const std::string& errMsg)
{
std::lock_guard<std::mutex> lock(_errMsgMutex);
_errMsg = errMsg;
}
const std::string& DNSLookup::getErrMsg()
{
std::lock_guard<std::mutex> lock(_errMsgMutex);
return _errMsg;
}
void DNSLookup::setRes(struct addrinfo* addr)
{
std::lock_guard<std::mutex> lock(_resMutex);
_res = addr;
}
struct addrinfo* DNSLookup::getRes()
{
std::lock_guard<std::mutex> lock(_resMutex);
return _res;
}
}
+16 -1
View File
@@ -45,11 +45,26 @@ namespace ix
void run(uint64_t id, const std::string& hostname, int port); // thread runner
void setHostname(const std::string& hostname);
const std::string& getHostname();
void setErrMsg(const std::string& errMsg);
const std::string& getErrMsg();
void setRes(struct addrinfo* addr);
struct addrinfo* getRes();
std::string _hostname;
std::mutex _hostnameMutex;
int _port;
int64_t _wait;
std::string _errMsg;
struct addrinfo* _res;
std::mutex _resMutex;
std::string _errMsg;
std::mutex _errMsgMutex;
std::atomic<bool> _done;
std::thread _thread;
+18 -19
View File
@@ -47,13 +47,12 @@ namespace ix
std::string protocol, host, path, query;
int port;
bool websocket = false;
if (!UrlParser::parse(url, protocol, host, path, query, port, websocket))
if (!UrlParser::parse(url, protocol, host, path, query, port))
{
std::stringstream ss;
ss << "Cannot parse url: " << url;
return std::make_tuple(code, HttpErrorCode_UrlMalformed,
return std::make_tuple(code, HttpErrorCode::UrlMalformed,
headers, payload, ss.str(),
uploadSize, downloadSize);
}
@@ -64,7 +63,7 @@ namespace ix
if (!_socket)
{
return std::make_tuple(code, HttpErrorCode_CannotCreateSocket,
return std::make_tuple(code, HttpErrorCode::CannotCreateSocket,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -117,7 +116,7 @@ namespace ix
{
std::stringstream ss;
ss << "Cannot connect to url: " << url;
return std::make_tuple(code, HttpErrorCode_CannotConnect,
return std::make_tuple(code, HttpErrorCode::CannotConnect,
headers, payload, ss.str(),
uploadSize, downloadSize);
}
@@ -143,7 +142,7 @@ namespace ix
if (!_socket->writeBytes(req, isCancellationRequested))
{
std::string errorMsg("Cannot send request");
return std::make_tuple(code, HttpErrorCode_SendError,
return std::make_tuple(code, HttpErrorCode::SendError,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -157,7 +156,7 @@ namespace ix
if (!lineValid)
{
std::string errorMsg("Cannot retrieve status line");
return std::make_tuple(code, HttpErrorCode_CannotReadStatusLine,
return std::make_tuple(code, HttpErrorCode::CannotReadStatusLine,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -172,7 +171,7 @@ namespace ix
if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1)
{
std::string errorMsg("Cannot parse response code from status line");
return std::make_tuple(code, HttpErrorCode_MissingStatus,
return std::make_tuple(code, HttpErrorCode::MissingStatus,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -184,7 +183,7 @@ namespace ix
if (!headersValid)
{
std::string errorMsg("Cannot parse http headers");
return std::make_tuple(code, HttpErrorCode_HeaderParsingError,
return std::make_tuple(code, HttpErrorCode::HeaderParsingError,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -195,7 +194,7 @@ namespace ix
if (headers.find("Location") == headers.end())
{
std::string errorMsg("Missing location header for redirect");
return std::make_tuple(code, HttpErrorCode_MissingLocation,
return std::make_tuple(code, HttpErrorCode::MissingLocation,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -204,7 +203,7 @@ namespace ix
{
std::stringstream ss;
ss << "Too many redirects: " << redirects;
return std::make_tuple(code, HttpErrorCode_TooManyRedirects,
return std::make_tuple(code, HttpErrorCode::TooManyRedirects,
headers, payload, ss.str(),
uploadSize, downloadSize);
}
@@ -216,7 +215,7 @@ namespace ix
if (verb == "HEAD")
{
return std::make_tuple(code, HttpErrorCode_Ok,
return std::make_tuple(code, HttpErrorCode::Ok,
headers, payload, std::string(),
uploadSize, downloadSize);
}
@@ -237,7 +236,7 @@ namespace ix
if (!chunkResult.first)
{
errorMsg = "Cannot read chunk";
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
return std::make_tuple(code, HttpErrorCode::ChunkReadError,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -255,7 +254,7 @@ namespace ix
if (!lineResult.first)
{
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
return std::make_tuple(code, HttpErrorCode::ChunkReadError,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -282,7 +281,7 @@ namespace ix
if (!chunkResult.first)
{
errorMsg = "Cannot read chunk";
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
return std::make_tuple(code, HttpErrorCode::ChunkReadError,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -293,7 +292,7 @@ namespace ix
if (!lineResult.first)
{
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
return std::make_tuple(code, HttpErrorCode::ChunkReadError,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -308,7 +307,7 @@ namespace ix
else
{
std::string errorMsg("Cannot read http body");
return std::make_tuple(code, HttpErrorCode_CannotReadBody,
return std::make_tuple(code, HttpErrorCode::CannotReadBody,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -322,14 +321,14 @@ namespace ix
if (!gzipInflate(payload, decompressedPayload))
{
std::string errorMsg("Error decompressing payload");
return std::make_tuple(code, HttpErrorCode_Gzip,
return std::make_tuple(code, HttpErrorCode::Gzip,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
payload = decompressedPayload;
}
return std::make_tuple(code, HttpErrorCode_Ok,
return std::make_tuple(code, HttpErrorCode::Ok,
headers, payload, std::string(),
uploadSize, downloadSize);
}
+16 -16
View File
@@ -19,23 +19,23 @@
namespace ix
{
enum HttpErrorCode
enum class HttpErrorCode : int
{
HttpErrorCode_Ok = 0,
HttpErrorCode_CannotConnect = 1,
HttpErrorCode_Timeout = 2,
HttpErrorCode_Gzip = 3,
HttpErrorCode_UrlMalformed = 4,
HttpErrorCode_CannotCreateSocket = 5,
HttpErrorCode_SendError = 6,
HttpErrorCode_ReadError = 7,
HttpErrorCode_CannotReadStatusLine = 8,
HttpErrorCode_MissingStatus = 9,
HttpErrorCode_HeaderParsingError = 10,
HttpErrorCode_MissingLocation = 11,
HttpErrorCode_TooManyRedirects = 12,
HttpErrorCode_ChunkReadError = 13,
HttpErrorCode_CannotReadBody = 14
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
};
using HttpResponse = std::tuple<int, // status
+39
View File
@@ -0,0 +1,39 @@
/*
* IXNetSystem.cpp
* Author: Korchynskyi Dmytro
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#include "IXNetSystem.h"
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
}
}
+6
View File
@@ -23,3 +23,9 @@
# include <sys/time.h>
# include <unistd.h>
#endif
namespace ix
{
bool initNetSystem();
bool uninitNetSystem();
}
+1 -1
View File
@@ -13,7 +13,7 @@
namespace ix
{
class SelectInterruptEventFd : public SelectInterrupt {
class SelectInterruptEventFd final : public SelectInterrupt {
public:
SelectInterruptEventFd();
virtual ~SelectInterruptEventFd();
+8
View File
@@ -40,6 +40,8 @@ namespace ix
bool SelectInterruptPipe::init(std::string& errorMsg)
{
std::lock_guard<std::mutex> lock(_fildesMutex);
// calling init twice is a programming error
assert(_fildes[kPipeReadIndex] == -1);
assert(_fildes[kPipeWriteIndex] == -1);
@@ -108,6 +110,8 @@ namespace ix
bool SelectInterruptPipe::notify(uint64_t value)
{
std::lock_guard<std::mutex> lock(_fildesMutex);
int fd = _fildes[kPipeWriteIndex];
if (fd == -1) return false;
@@ -118,6 +122,8 @@ namespace ix
// TODO: return max uint64_t for errors ?
uint64_t SelectInterruptPipe::read()
{
std::lock_guard<std::mutex> lock(_fildesMutex);
int fd = _fildes[kPipeReadIndex];
uint64_t value = 0;
@@ -133,6 +139,8 @@ namespace ix
int SelectInterruptPipe::getFd() const
{
std::lock_guard<std::mutex> lock(_fildesMutex);
return _fildes[kPipeReadIndex];
}
}
+3 -1
View File
@@ -10,10 +10,11 @@
#include <stdint.h>
#include <string>
#include <mutex>
namespace ix
{
class SelectInterruptPipe : public SelectInterrupt {
class SelectInterruptPipe final : public SelectInterrupt {
public:
SelectInterruptPipe();
virtual ~SelectInterruptPipe();
@@ -30,6 +31,7 @@ namespace ix
// 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;
+31 -18
View File
@@ -19,7 +19,10 @@
#include <sys/types.h>
#include <algorithm>
#include <iostream>
#ifdef min
#undef min
#endif
namespace ix
{
@@ -41,17 +44,14 @@ namespace ix
close();
}
void Socket::poll(const OnPollCallback& onPollCallback, int timeoutSecs)
PollResultType Socket::poll(int timeoutMs)
{
if (_sockfd == -1)
{
if (onPollCallback) onPollCallback(PollResultType::Error);
return;
return PollResultType::Error;
}
PollResultType pollResult = isReadyToRead(1000 * timeoutSecs);
if (onPollCallback) onPollCallback(pollResult);
return isReadyToRead(timeoutMs);
}
PollResultType Socket::select(bool readyToRead, int timeoutMs)
@@ -73,7 +73,7 @@ namespace ix
struct timeval timeout;
timeout.tv_sec = timeoutMs / 1000;
timeout.tv_usec = (timeoutMs < 1000) ? 0 : 1000 * (timeoutMs % 1000);
timeout.tv_usec = 1000 * (timeoutMs % 1000);
// Compute the highest fd.
int sockfd = _sockfd;
@@ -129,7 +129,7 @@ namespace ix
}
// Wake up from poll/select by writing to the pipe which is watched by select
bool Socket::wakeUpFromPoll(uint8_t wakeUpCode)
bool Socket::wakeUpFromPoll(uint64_t wakeUpCode)
{
return _selectInterrupt->notify(wakeUpCode);
}
@@ -184,11 +184,27 @@ namespace ix
int Socket::getErrno()
{
int err;
#ifdef _WIN32
return WSAGetLastError();
err = WSAGetLastError();
#else
return errno;
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)
@@ -223,8 +239,7 @@ namespace ix
return ret == len;
}
// There is possibly something to be writen, try again
else if (ret < 0 && (getErrno() == EWOULDBLOCK ||
getErrno() == EAGAIN))
else if (ret < 0 && Socket::isWaitNeeded())
{
continue;
}
@@ -252,8 +267,7 @@ namespace ix
return true;
}
// There is possibly something to be read, try again
else if (ret < 0 && (getErrno() == EWOULDBLOCK ||
getErrno() == EAGAIN))
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
@@ -312,13 +326,12 @@ namespace ix
size_t size = std::min(kChunkSize, length - output.size());
ssize_t ret = recv((char*)&_readBuffer[0], size);
if (ret <= 0 && (getErrno() != EWOULDBLOCK &&
getErrno() != EAGAIN))
if (ret <= 0 && !Socket::isWaitNeeded())
{
// Error
return std::make_pair(false, std::string());
}
else if (ret > 0)
else
{
output.insert(output.end(),
_readBuffer.begin(),
+24 -15
View File
@@ -16,6 +16,20 @@
#ifdef _WIN32
#include <BaseTsd.h>
typedef SSIZE_T ssize_t;
#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
#endif
#include "IXCancellationRequest.h"
@@ -27,28 +41,23 @@ namespace ix
enum class PollResultType
{
ReadyForRead = 0,
ReadyForWrite = 1,
Timeout = 2,
Error = 3,
SendRequest = 4,
CloseRequest = 5
ReadyForRead = 0,
ReadyForWrite = 1,
Timeout = 2,
Error = 3,
SendRequest = 4,
CloseRequest = 5
};
class Socket {
public:
using OnPollCallback = std::function<void(PollResultType)>;
Socket(int fd = -1);
virtual ~Socket();
bool init(std::string& errorMsg);
void configure();
// Functions to check whether there is activity on the socket
void poll(const OnPollCallback& onPollCallback,
int timeoutSecs = kDefaultPollTimeout);
bool wakeUpFromPoll(uint8_t wakeUpCode);
PollResultType poll(int timeoutMs = kDefaultPollTimeout);
bool wakeUpFromPoll(uint64_t wakeUpCode);
PollResultType isReadyToWrite(int timeoutMs);
PollResultType isReadyToRead(int timeoutMs);
@@ -79,14 +88,14 @@ namespace ix
const CancellationRequest& isCancellationRequested);
static int getErrno();
static bool isWaitNeeded();
static void closeSocket(int fd);
// Used as special codes for pipe communication
static const uint64_t kSendRequest;
static const uint64_t kCloseRequest;
protected:
void closeSocket(int fd);
std::atomic<int> _sockfd;
std::mutex _socketMutex;
-2
View File
@@ -20,8 +20,6 @@
#include <unistd.h>
#include <stdint.h>
#include <iostream>
#include <errno.h>
#define socketerrno errno
+1 -1
View File
@@ -16,7 +16,7 @@
namespace ix
{
class SocketAppleSSL : public Socket
class SocketAppleSSL final : public Socket
{
public:
SocketAppleSSL(int fd = -1);
+18 -24
View File
@@ -7,6 +7,7 @@
#include "IXSocketConnect.h"
#include "IXDNSLookup.h"
#include "IXNetSystem.h"
#include "IXSocket.h"
#include <string.h>
#include <fcntl.h>
@@ -18,18 +19,6 @@
# include <linux/tcp.h>
#endif
namespace
{
void closeSocket(int fd)
{
#ifdef _WIN32
closesocket(fd);
#else
::close(fd);
#endif
}
}
namespace ix
{
//
@@ -56,11 +45,12 @@ namespace ix
// block us for too long
SocketConnect::configure(fd);
if (::connect(fd, address->ai_addr, address->ai_addrlen) == -1
&& errno != EINPROGRESS)
int res = ::connect(fd, address->ai_addr, address->ai_addrlen);
if (res == -1 && !Socket::isWaitNeeded())
{
closeSocket(fd);
errMsg = strerror(errno);
errMsg = strerror(Socket::getErrno());
Socket::closeSocket(fd);
return -1;
}
@@ -68,15 +58,17 @@ namespace ix
{
if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well
{
closeSocket(fd);
Socket::closeSocket(fd);
errMsg = "Cancelled";
return -1;
}
// Use select to check the status of the new connection
// On Linux the timeout needs to be re-initialized everytime
// http://man7.org/linux/man-pages/man2/select.2.html
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 10 * 1000; // 10ms timeout
fd_set wfds;
fd_set efds;
@@ -85,11 +77,13 @@ namespace ix
FD_ZERO(&efds);
FD_SET(fd, &efds);
if (select(fd + 1, nullptr, &wfds, &efds, &timeout) < 0 &&
(errno == EBADF || errno == EINVAL))
// Use select to check the status of the new connection
res = select(fd + 1, nullptr, &wfds, &efds, &timeout);
if (res < 0 && (Socket::getErrno() == EBADF || Socket::getErrno() == EINVAL))
{
closeSocket(fd);
errMsg = std::string("Connect error, select error: ") + strerror(errno);
Socket::closeSocket(fd);
errMsg = std::string("Connect error, select error: ") + strerror(Socket::getErrno());
return -1;
}
@@ -110,7 +104,7 @@ namespace ix
optval != 0)
#endif
{
closeSocket(fd);
Socket::closeSocket(fd);
errMsg = strerror(optval);
return -1;
}
@@ -121,7 +115,7 @@ namespace ix
}
}
closeSocket(fd);
Socket::closeSocket(fd);
errMsg = "connect timed out after 60 seconds";
return -1;
}
+11 -1
View File
@@ -6,12 +6,20 @@
#include "IXSocketFactory.h"
#if defined(__APPLE__) or defined(__linux__)
#ifdef IXWEBSOCKET_USE_TLS
# ifdef __APPLE__
# include <ixwebsocket/IXSocketAppleSSL.h>
# elif defined(_WIN32)
# include <ixwebsocket/IXSocketSChannel.h>
# else
# include <ixwebsocket/IXSocketOpenSSL.h>
# endif
#else
#include <ixwebsocket/IXSocket.h>
#endif
namespace ix
@@ -31,6 +39,8 @@ namespace ix
#ifdef IXWEBSOCKET_USE_TLS
# ifdef __APPLE__
socket = std::make_shared<SocketAppleSSL>();
# elif defined(_WIN32)
socket = std::make_shared<SocketSChannel>();
# else
socket = std::make_shared<SocketOpenSSL>();
# endif
+1
View File
@@ -8,6 +8,7 @@
#pragma once
#include <memory>
#include <string>
namespace ix
{
+2 -3
View File
@@ -10,7 +10,6 @@
#include "IXSocketConnect.h"
#include <cassert>
#include <iostream>
#include <openssl/x509v3.h>
@@ -342,7 +341,7 @@ namespace ix
ERR_clear_error();
ssize_t write_result = SSL_write(_ssl_connection, buf + sent, (int) nbyte);
int reason = SSL_get_error(_ssl_connection, write_result);
int reason = SSL_get_error(_ssl_connection, (int) write_result);
if (reason == SSL_ERROR_NONE) {
nbyte -= write_result;
@@ -382,7 +381,7 @@ namespace ix
return read_result;
}
int reason = SSL_get_error(_ssl_connection, read_result);
int reason = SSL_get_error(_ssl_connection, (int) read_result);
if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE)
{
+1 -1
View File
@@ -19,7 +19,7 @@
namespace ix
{
class SocketOpenSSL : public Socket
class SocketOpenSSL final : public Socket
{
public:
SocketOpenSSL(int fd = -1);
+4 -5
View File
@@ -18,7 +18,6 @@
# include <ws2def.h>
# include <WS2tcpip.h>
# include <schannel.h>
# include <sslsock.h>
# include <io.h>
#define WIN32_LEAN_AND_MEAN
@@ -75,7 +74,7 @@ namespace ix
int port,
std::string& errMsg)
{
return Socket::connect(host, port, errMsg);
return Socket::connect(host, port, errMsg, nullptr);
}
@@ -89,17 +88,17 @@ namespace ix
Socket::close();
}
int SocketSChannel::send(char* buf, size_t nbyte)
ssize_t SocketSChannel::send(char* buf, size_t nbyte)
{
return Socket::send(buf, nbyte);
}
int SocketSChannel::send(const std::string& buffer)
ssize_t SocketSChannel::send(const std::string& buffer)
{
return Socket::send(buffer);
}
int SocketSChannel::recv(void* buf, size_t nbyte)
ssize_t SocketSChannel::recv(void* buf, size_t nbyte)
{
return Socket::recv(buf, nbyte);
}
+4 -4
View File
@@ -10,7 +10,7 @@
namespace ix
{
class SocketSChannel : public Socket
class SocketSChannel final : public Socket
{
public:
SocketSChannel();
@@ -24,9 +24,9 @@ namespace ix
// The important override
virtual void secureSocket() final;
virtual int send(char* buffer, size_t length) final;
virtual int send(const std::string& buffer) final;
virtual int recv(void* buffer, size_t length) final;
virtual ssize_t send(char* buffer, size_t length) final;
virtual ssize_t send(const std::string& buffer) final;
virtual ssize_t recv(void* buffer, size_t length) final;
private:
};
+102 -25
View File
@@ -13,6 +13,7 @@
#include <sstream>
#include <future>
#include <string.h>
#include <assert.h>
namespace ix
{
@@ -29,7 +30,9 @@ namespace ix
_host(host),
_backlog(backlog),
_maxConnections(maxConnections),
_serverFd(-1),
_stop(false),
_stopGc(false),
_connectionStateFactory(&ConnectionState::createConnectionState)
{
@@ -76,7 +79,7 @@ namespace ix
<< "at address " << _host << ":" << _port
<< " : " << strerror(Socket::getErrno());
::close(_serverFd);
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
@@ -100,7 +103,7 @@ namespace ix
<< "at address " << _host << ":" << _port
<< " : " << strerror(Socket::getErrno());
::close(_serverFd);
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
@@ -114,7 +117,7 @@ namespace ix
<< "at address " << _host << ":" << _port
<< " : " << strerror(Socket::getErrno());
::close(_serverFd);
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
@@ -123,9 +126,15 @@ namespace ix
void SocketServer::start()
{
if (_thread.joinable()) return; // we've already been started
if (!_thread.joinable())
{
_thread = std::thread(&SocketServer::run, this);
}
_thread = std::thread(&SocketServer::run, this);
if (!_gcThread.joinable())
{
_gcThread = std::thread(&SocketServer::runGC, this);
}
}
void SocketServer::wait()
@@ -134,16 +143,31 @@ namespace ix
_conditionVariable.wait(lock);
}
void SocketServer::stopAcceptingConnections()
{
_stop = true;
}
void SocketServer::stop()
{
if (!_thread.joinable()) return; // nothing to do
// Stop accepting connections, and close the 'accept' thread
if (_thread.joinable())
{
_stop = true;
_thread.join();
_stop = false;
}
_stop = true;
_thread.join();
_stop = false;
// Join all threads and make sure that all connections are terminated
if (_gcThread.joinable())
{
_stopGc = true;
_gcThread.join();
_stopGc = false;
}
_conditionVariable.notify_one();
::close(_serverFd);
Socket::closeSocket(_serverFd);
}
void SocketServer::setConnectionStateFactory(
@@ -152,14 +176,40 @@ namespace ix
_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<std::mutex> 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);
// Return value of std::async, ignored
std::future<void> f;
for (;;)
{
if (_stop) return;
@@ -192,17 +242,18 @@ namespace ix
// Accept a connection.
struct sockaddr_in client; // client address information
int clientFd; // socket connected to client
socklen_t addressLen = sizeof(socklen_t);
socklen_t addressLen = sizeof(client);
memset(&client, 0, sizeof(client));
if ((clientFd = accept(_serverFd, (struct sockaddr *)&client, &addressLen)) < 0)
{
if (Socket::getErrno() != EWOULDBLOCK)
if (!Socket::isWaitNeeded())
{
// FIXME: that error should be propagated
int err = Socket::getErrno();
std::stringstream ss;
ss << "SocketServer::run() error accepting connection: "
<< strerror(Socket::getErrno());
<< err << ", " << strerror(err);
logError(ss.str());
}
continue;
@@ -216,7 +267,7 @@ namespace ix
<< "Not accepting connection";
logError(ss.str());
::close(clientFd);
Socket::closeSocket(clientFd);
continue;
}
@@ -227,15 +278,41 @@ namespace ix
connectionState = _connectionStateFactory();
}
if (_stop) return;
// Launch the handleConnection work asynchronously in its own thread.
//
// the destructor of a future returned by std::async blocks,
// so we need to declare it outside of this loop
f = std::async(std::launch::async,
&SocketServer::handleConnection,
this,
clientFd,
connectionState);
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
_connectionsThreads.push_back(std::make_pair(
connectionState,
std::thread(&SocketServer::handleConnection,
this,
clientFd,
connectionState)));
}
}
size_t SocketServer::getConnectionsThreadsCount()
{
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
return _connectionsThreads.size();
}
void SocketServer::runGC()
{
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;
}
// Sleep a little bit then keep cleaning up
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
}
+29 -3
View File
@@ -12,6 +12,7 @@
#include <string>
#include <set>
#include <thread>
#include <list>
#include <mutex>
#include <functional>
#include <memory>
@@ -24,6 +25,11 @@ namespace ix
public:
using ConnectionStateFactory = std::function<std::shared_ptr<ConnectionState>()>;
// 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::pair<std::shared_ptr<ConnectionState>,
std::thread>>;
SocketServer(int port = SocketServer::kDefaultPort,
const std::string& host = SocketServer::kDefaultHost,
int backlog = SocketServer::kDefaultTcpBacklog,
@@ -31,6 +37,9 @@ namespace ix
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;
@@ -48,6 +57,8 @@ namespace ix
void logError(const std::string& str);
void logInfo(const std::string& str);
void stopAcceptingConnections();
private:
// Member variables
int _port;
@@ -60,19 +71,34 @@ namespace ix
std::mutex _logMutex;
// background thread to wait for incoming connections
std::atomic<bool> _stop;
std::thread _thread;
void run();
// background thread to cleanup (join) terminated threads
std::atomic<bool> _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;
// Methods
void run();
virtual void handleConnection(int fd,
std::shared_ptr<ConnectionState> connectionState) = 0;
virtual size_t getConnectedClientsCount() = 0;
// Returns true if all connection threads are joined
void closeTerminatedThreads();
size_t getConnectionsThreadsCount();
};
}
+10 -47
View File
@@ -5,43 +5,30 @@
*/
#include "IXUrlParser.h"
#include <iostream>
#include <sstream>
#include "LUrlParser.h"
namespace ix
{
//
// The only difference between those 2 regex is the protocol
//
std::regex UrlParser::_httpRegex("(http|https)://([^/ :]+):?([^/ ]*)(/?[^ #?]*)\\x3f?([^ #]*)#?([^ ]*)");
std::regex UrlParser::_webSocketRegex("(ws|wss)://([^/ :]+):?([^/ ]*)(/?[^ #?]*)\\x3f?([^ #]*)#?([^ ]*)");
bool UrlParser::parse(const std::string& url,
std::string& protocol,
std::string& host,
std::string& path,
std::string& query,
int& port,
bool websocket)
int& port)
{
std::cmatch what;
if (!regex_match(url.c_str(), what,
websocket ? _webSocketRegex : _httpRegex))
LUrlParser::clParseURL res = LUrlParser::clParseURL::ParseURL(url);
if (!res.IsValid())
{
return false;
}
std::string portStr;
protocol = res.m_Scheme;
host = res.m_Host;
path = res.m_Path;
query = res.m_Query;
protocol = std::string(what[1].first, what[1].second);
host = std::string(what[2].first, what[2].second);
portStr = std::string(what[3].first, what[3].second);
path = std::string(what[4].first, what[4].second);
query = std::string(what[5].first, what[5].second);
if (portStr.empty())
if (!res.GetPort(&port))
{
if (protocol == "ws" || protocol == "http")
{
@@ -58,12 +45,6 @@ namespace ix
return false;
}
}
else
{
std::stringstream ss;
ss << portStr;
ss >> port;
}
if (path.empty())
{
@@ -83,22 +64,4 @@ namespace ix
return true;
}
void UrlParser::printUrl(const std::string& url, bool websocket)
{
std::string protocol, host, path, query;
int port {0};
if (!parse(url, protocol, host, path, query, port, websocket))
{
return;
}
std::cout << "[" << url << "]" << std::endl;
std::cout << protocol << std::endl;
std::cout << host << std::endl;
std::cout << port << std::endl;
std::cout << path << std::endl;
std::cout << query << std::endl;
std::cout << "-------------------------------" << std::endl;
}
}
+1 -9
View File
@@ -7,7 +7,6 @@
#pragma once
#include <string>
#include <regex>
namespace ix
{
@@ -19,13 +18,6 @@ namespace ix
std::string& host,
std::string& path,
std::string& query,
int& port,
bool websocket);
static void printUrl(const std::string& url, bool websocket);
private:
static std::regex _httpRegex;
static std::regex _webSocketRegex;
int& port);
};
}
+162 -89
View File
@@ -8,22 +8,26 @@
#include "IXSetThreadName.h"
#include "IXWebSocketHandshake.h"
#include <iostream>
#include <cmath>
#include <cassert>
namespace
{
uint64_t calculateRetryWaitMilliseconds(uint64_t retry_count)
uint64_t calculateRetryWaitMilliseconds(uint32_t retry_count)
{
// This will overflow quite fast for large value of retry_count
// and will become 0, in which case the wait time will be none
// and we'll be constantly retrying to connect.
uint64_t wait_time = ((uint64_t) std::pow(2, retry_count) * 100L);
uint64_t wait_time;
// cap the wait time to 10s, or to retry_count == 10 for which wait_time > 10s
uint64_t tenSeconds = 10 * 1000;
return (wait_time > tenSeconds || retry_count > 10) ? tenSeconds : wait_time;
if (retry_count <= 6)
{
// max wait_time is 6400 ms (2 ^ 6 = 64)
wait_time = ((uint64_t)std::pow(2, retry_count) * 100L);
}
else
{
wait_time = 10 * 1000; // 10 sec
}
return wait_time;
}
}
@@ -31,21 +35,25 @@ namespace ix
{
OnTrafficTrackerCallback WebSocket::_onTrafficTrackerCallback = nullptr;
const int WebSocket::kDefaultHandShakeTimeoutSecs(60);
const int WebSocket::kDefaultHeartBeatPeriod(-1);
const int WebSocket::kDefaultPingIntervalSecs(-1);
const int WebSocket::kDefaultPingTimeoutSecs(-1);
const bool WebSocket::kDefaultEnablePong(true);
WebSocket::WebSocket() :
_onMessageCallback(OnMessageCallback()),
_stop(false),
_automaticReconnection(true),
_handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs),
_heartBeatPeriod(kDefaultHeartBeatPeriod)
_enablePong(kDefaultEnablePong),
_pingIntervalSecs(kDefaultPingIntervalSecs),
_pingTimeoutSecs(kDefaultPingTimeoutSecs)
{
_ws.setOnCloseCallback(
[this](uint16_t code, const std::string& reason, size_t wireSize)
[this](uint16_t code, const std::string& reason, size_t wireSize, bool remote)
{
_onMessageCallback(WebSocket_MessageType_Close, "", wireSize,
_onMessageCallback(WebSocketMessageType::Close, "", wireSize,
WebSocketErrorInfo(), WebSocketOpenInfo(),
WebSocketCloseInfo(code, reason));
WebSocketCloseInfo(code, reason, remote));
}
);
}
@@ -79,16 +87,52 @@ namespace ix
return _perMessageDeflateOptions;
}
void WebSocket::setHeartBeatPeriod(int heartBeatPeriod)
void WebSocket::setHeartBeatPeriod(int heartBeatPeriodSecs)
{
std::lock_guard<std::mutex> lock(_configMutex);
_heartBeatPeriod = heartBeatPeriod;
_pingIntervalSecs = heartBeatPeriodSecs;
}
int WebSocket::getHeartBeatPeriod() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _heartBeatPeriod;
return _pingIntervalSecs;
}
void WebSocket::setPingInterval(int pingIntervalSecs)
{
std::lock_guard<std::mutex> lock(_configMutex);
_pingIntervalSecs = pingIntervalSecs;
}
int WebSocket::getPingInterval() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _pingIntervalSecs;
}
void WebSocket::setPingTimeout(int pingTimeoutSecs)
{
std::lock_guard<std::mutex> lock(_configMutex);
_pingTimeoutSecs = pingTimeoutSecs;
}
int WebSocket::getPingTimeout() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _pingTimeoutSecs;
}
void WebSocket::enablePong()
{
std::lock_guard<std::mutex> lock(_configMutex);
_enablePong = true;
}
void WebSocket::disablePong()
{
std::lock_guard<std::mutex> lock(_configMutex);
_enablePong = false;
}
void WebSocket::start()
@@ -98,26 +142,19 @@ namespace ix
_thread = std::thread(&WebSocket::run, this);
}
void WebSocket::stop()
void WebSocket::stop(uint16_t code,
const std::string& reason)
{
bool automaticReconnection = _automaticReconnection;
close(code, reason);
// This value needs to be forced when shutting down, it is restored later
_automaticReconnection = false;
close();
if (!_thread.joinable())
if (_thread.joinable())
{
_automaticReconnection = automaticReconnection;
return;
// wait until working thread will exit
// it will exit after close operation is finished
_stop = true;
_thread.join();
_stop = false;
}
_stop = true;
_thread.join();
_stop = false;
_automaticReconnection = automaticReconnection;
}
WebSocketInitResult WebSocket::connect(int timeoutSecs)
@@ -125,7 +162,9 @@ namespace ix
{
std::lock_guard<std::mutex> lock(_configMutex);
_ws.configure(_perMessageDeflateOptions,
_heartBeatPeriod);
_enablePong,
_pingIntervalSecs,
_pingTimeoutSecs);
}
WebSocketInitResult status = _ws.connectToUrl(_url, timeoutSecs);
@@ -134,7 +173,7 @@ namespace ix
return status;
}
_onMessageCallback(WebSocket_MessageType_Open, "", 0,
_onMessageCallback(WebSocketMessageType::Open, "", 0,
WebSocketErrorInfo(),
WebSocketOpenInfo(status.uri, status.headers),
WebSocketCloseInfo());
@@ -145,7 +184,10 @@ namespace ix
{
{
std::lock_guard<std::mutex> lock(_configMutex);
_ws.configure(_perMessageDeflateOptions, _heartBeatPeriod);
_ws.configure(_perMessageDeflateOptions,
_enablePong,
_pingIntervalSecs,
_pingTimeoutSecs);
}
WebSocketInitResult status = _ws.connectToSocket(fd, timeoutSecs);
@@ -154,7 +196,7 @@ namespace ix
return status;
}
_onMessageCallback(WebSocket_MessageType_Open, "", 0,
_onMessageCallback(WebSocketMessageType::Open, "", 0,
WebSocketErrorInfo(),
WebSocketOpenInfo(status.uri, status.headers),
WebSocketCloseInfo());
@@ -163,73 +205,103 @@ namespace ix
bool WebSocket::isConnected() const
{
return getReadyState() == WebSocket_ReadyState_Open;
return getReadyState() == ReadyState::Open;
}
bool WebSocket::isClosing() const
{
return getReadyState() == WebSocket_ReadyState_Closing;
return getReadyState() == ReadyState::Closing;
}
void WebSocket::close()
void WebSocket::close(uint16_t code,
const std::string& reason)
{
_ws.close();
_ws.close(code, reason);
}
void WebSocket::reconnectPerpetuallyIfDisconnected()
void WebSocket::checkConnection(bool firstConnectionAttempt)
{
uint64_t retries = 0;
WebSocketErrorInfo connectErr;
ix::WebSocketInitResult status;
using millis = std::chrono::duration<double, std::milli>;
uint32_t retries = 0;
millis duration;
// Try to connect perpertually
while (true)
{
if (isConnected() || isClosing() || _stop || !_automaticReconnection)
if (isConnected() || isClosing() || _stop)
{
break;
}
status = connect(_handshakeTimeoutSecs);
if (!status.success && !_stop)
if (!firstConnectionAttempt && !_automaticReconnection)
{
duration = millis(calculateRetryWaitMilliseconds(retries++));
// Do not attempt to reconnect
break;
}
connectErr.retries = retries;
connectErr.wait_time = duration.count();
connectErr.reason = status.errorStr;
firstConnectionAttempt = false;
// Only sleep if we are retrying
if (duration.count() > 0)
{
// to do: make sleeping conditional
std::this_thread::sleep_for(duration);
}
// Try to connect synchronously
ix::WebSocketInitResult status = connect(_handshakeTimeoutSecs);
if (!status.success)
{
WebSocketErrorInfo connectErr;
if (_automaticReconnection)
{
duration = millis(calculateRetryWaitMilliseconds(retries++));
connectErr.wait_time = duration.count();
connectErr.retries = retries;
}
connectErr.reason = status.errorStr;
connectErr.http_status = status.http_status;
_onMessageCallback(WebSocket_MessageType_Error, "", 0,
_onMessageCallback(WebSocketMessageType::Error, "", 0,
connectErr, WebSocketOpenInfo(),
WebSocketCloseInfo());
std::this_thread::sleep_for(duration);
}
}
}
void WebSocket::run()
{
setThreadName(_url);
setThreadName(getUrl());
bool firstConnectionAttempt = true;
while (true)
{
if (_stop) return;
// 1. Make sure we are always connected
reconnectPerpetuallyIfDisconnected();
checkConnection(firstConnectionAttempt);
if (_stop) return;
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
_ws.poll();
if (_stop) return;
WebSocketTransport::PollResult pollResult = _ws.poll();
// 3. Dispatch the incoming messages
_ws.dispatch(
pollResult,
[this](const std::string& msg,
size_t wireSize,
bool decompressionError,
@@ -238,24 +310,25 @@ namespace ix
WebSocketMessageType webSocketMessageType;
switch (messageKind)
{
case WebSocketTransport::MSG:
default:
case WebSocketTransport::MessageKind::MSG:
{
webSocketMessageType = WebSocket_MessageType_Message;
webSocketMessageType = WebSocketMessageType::Message;
} break;
case WebSocketTransport::PING:
case WebSocketTransport::MessageKind::PING:
{
webSocketMessageType = WebSocket_MessageType_Ping;
webSocketMessageType = WebSocketMessageType::Ping;
} break;
case WebSocketTransport::PONG:
case WebSocketTransport::MessageKind::PONG:
{
webSocketMessageType = WebSocket_MessageType_Pong;
webSocketMessageType = WebSocketMessageType::Pong;
} break;
case WebSocketTransport::FRAGMENT:
case WebSocketTransport::MessageKind::FRAGMENT:
{
webSocketMessageType = WebSocket_MessageType_Fragment;
webSocketMessageType = WebSocketMessageType::Fragment;
} break;
}
@@ -268,11 +341,6 @@ namespace ix
WebSocket::invokeTrafficTrackerCallback(msg.size(), true);
});
// 4. In blocking mode, getting out of this function is triggered by
// an explicit disconnection from the callback, or by the remote end
// closing the connection, ie isConnected() == false.
if (!_thread.joinable() && !isConnected() && !_automaticReconnection) return;
}
}
@@ -299,10 +367,10 @@ namespace ix
}
}
WebSocketSendInfo WebSocket::send(const std::string& text,
WebSocketSendInfo WebSocket::send(const std::string& data,
const OnProgressCallback& onProgressCallback)
{
return sendMessage(text, SendMessageKind::Binary, onProgressCallback);
return sendMessage(data, SendMessageKind::Binary, onProgressCallback);
}
WebSocketSendInfo WebSocket::sendText(const std::string& text,
@@ -365,11 +433,11 @@ namespace ix
{
switch (_ws.getReadyState())
{
case ix::WebSocketTransport::OPEN: return WebSocket_ReadyState_Open;
case ix::WebSocketTransport::CONNECTING: return WebSocket_ReadyState_Connecting;
case ix::WebSocketTransport::CLOSING: return WebSocket_ReadyState_Closing;
case ix::WebSocketTransport::CLOSED: return WebSocket_ReadyState_Closed;
default: return WebSocket_ReadyState_Closed;
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;
}
}
@@ -377,11 +445,11 @@ namespace ix
{
switch (readyState)
{
case WebSocket_ReadyState_Open: return "OPEN";
case WebSocket_ReadyState_Connecting: return "CONNECTING";
case WebSocket_ReadyState_Closing: return "CLOSING";
case WebSocket_ReadyState_Closed: return "CLOSED";
default: return "CLOSED";
case ReadyState::Open : return "OPEN";
case ReadyState::Connecting: return "CONNECTING";
case ReadyState::Closing : return "CLOSING";
case ReadyState::Closed : return "CLOSED";
default: return "UNKNOWN";
}
}
@@ -395,6 +463,11 @@ namespace ix
_automaticReconnection = false;
}
bool WebSocket::isAutomaticReconnectionEnabled() const
{
return _automaticReconnection;
}
size_t WebSocket::bufferedAmount() const
{
return _ws.bufferedAmount();
+47 -25
View File
@@ -19,28 +19,29 @@
#include "IXWebSocketSendInfo.h"
#include "IXWebSocketPerMessageDeflateOptions.h"
#include "IXWebSocketHttpHeaders.h"
#include "IXWebSocketCloseConstants.h"
#include "IXProgressCallback.h"
namespace ix
{
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants
enum ReadyState
enum class ReadyState
{
WebSocket_ReadyState_Connecting = 0,
WebSocket_ReadyState_Open = 1,
WebSocket_ReadyState_Closing = 2,
WebSocket_ReadyState_Closed = 3
Connecting = 0,
Open = 1,
Closing = 2,
Closed = 3
};
enum WebSocketMessageType
enum class WebSocketMessageType
{
WebSocket_MessageType_Message = 0,
WebSocket_MessageType_Open = 1,
WebSocket_MessageType_Close = 2,
WebSocket_MessageType_Error = 3,
WebSocket_MessageType_Ping = 4,
WebSocket_MessageType_Pong = 5,
WebSocket_MessageType_Fragment = 6
Message = 0,
Open = 1,
Close = 2,
Error = 3,
Ping = 4,
Pong = 5,
Fragment = 6
};
struct WebSocketOpenInfo
@@ -61,11 +62,14 @@ namespace ix
{
uint16_t code;
std::string reason;
bool remote;
WebSocketCloseInfo(uint16_t c = 0,
const std::string& r = std::string())
const std::string& r = std::string(),
bool rem = false)
: code(c)
, reason(r)
, remote(rem)
{
;
}
@@ -88,36 +92,50 @@ namespace ix
void setUrl(const std::string& url);
void setPerMessageDeflateOptions(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
void setHandshakeTimeout(int handshakeTimeoutSecs);
void setHeartBeatPeriod(int heartBeatPeriod);
void setHeartBeatPeriod(int heartBeatPeriodSecs);
void setPingInterval(int pingIntervalSecs); // alias of setHeartBeatPeriod
void setPingTimeout(int pingTimeoutSecs);
void enablePong();
void disablePong();
// Run asynchronously, by calling start and stop.
void start();
void stop();
// 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();
WebSocketSendInfo send(const std::string& text,
// send binary data
WebSocketSendInfo send(const std::string& data,
const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo sendText(const std::string& text,
const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo ping(const std::string& text);
void close();
void close(uint16_t code = 1000,
const std::string& reason = "Normal closure");
void setOnMessageCallback(const OnMessageCallback& callback);
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 getHeartBeatPeriod() const;
int getPingInterval() const;
int getPingTimeout() const;
size_t bufferedAmount() const;
void enableAutomaticReconnection();
void disableAutomaticReconnection();
bool isAutomaticReconnectionEnabled() const;
private:
@@ -127,12 +145,10 @@ namespace ix
bool isConnected() const;
bool isClosing() const;
void reconnectPerpetuallyIfDisconnected();
std::string readyStateToString(ReadyState readyState);
void checkConnection(bool firstConnectionAttempt);
static void invokeTrafficTrackerCallback(size_t size, bool incoming);
// Server
void setSocketFileDescriptor(int fd);
WebSocketInitResult connectToSocket(int fd, int timeoutSecs);
WebSocketTransport _ws;
@@ -152,9 +168,15 @@ namespace ix
std::atomic<int> _handshakeTimeoutSecs;
static const int kDefaultHandShakeTimeoutSecs;
// Optional Heartbeat
int _heartBeatPeriod;
static const int kDefaultHeartBeatPeriod;
// enable or disable PONG frame response to received PING frame
bool _enablePong;
static const bool kDefaultEnablePong;
// Optional ping and ping timeout
int _pingIntervalSecs;
int _pingTimeoutSecs;
static const int kDefaultPingIntervalSecs;
static const int kDefaultPingTimeoutSecs;
friend class WebSocketServer;
};
+23
View File
@@ -0,0 +1,23 @@
/*
* 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::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");
}
+29
View File
@@ -0,0 +1,29 @@
/*
* IXWebSocketCloseConstants.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <cstdint>
#include <string>
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 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;
};
}
+4 -4
View File
@@ -12,10 +12,10 @@ namespace ix
{
struct WebSocketErrorInfo
{
uint64_t retries;
double wait_time;
int http_status;
uint32_t retries = 0;
double wait_time = 0;
int http_status = 0;
std::string reason;
bool decompressionError;
bool decompressionError = false;
};
}
+2 -4
View File
@@ -10,9 +10,7 @@
#include "libwshandshake.hpp"
#include <iostream>
#include <sstream>
#include <regex>
#include <random>
#include <algorithm>
@@ -244,7 +242,7 @@ namespace ix
}
char output[29] = {};
WebSocketHandshakeKeyGen::generate(secWebSocketKey.c_str(), output);
WebSocketHandshakeKeyGen::generate(secWebSocketKey, output);
if (std::string(output) != headers["sec-websocket-accept"])
{
std::string errorMsg("Invalid Sec-WebSocket-Accept value");
@@ -350,7 +348,7 @@ namespace ix
}
char output[29] = {};
WebSocketHandshakeKeyGen::generate(headers["sec-websocket-key"].c_str(), output);
WebSocketHandshakeKeyGen::generate(headers["sec-websocket-key"], output);
std::stringstream ss;
ss << "HTTP/1.1 101 Switching Protocols\r\n";
+19 -3
View File
@@ -6,12 +6,28 @@
#include "IXWebSocketHttpHeaders.h"
#include "IXSocket.h"
#include <string>
#include <unordered_map>
#include <algorithm>
#include <locale>
namespace ix
{
bool CaseInsensitiveLess::NocaseCompare::operator()(const unsigned char & c1, const unsigned char & c2) const
{
#ifdef _WIN32
return std::tolower(c1, std::locale()) < std::tolower(c2, std::locale());
#else
return std::tolower(c1) < std::tolower(c2);
#endif
}
bool CaseInsensitiveLess::operator()(const std::string & s1, const std::string & s2) const
{
return std::lexicographical_compare
(s1.begin(), s1.end(), // source range
s2.begin(), s2.end(), // dest range
NocaseCompare()); // comparison
}
std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders(
std::shared_ptr<Socket> socket,
const CancellationRequest& isCancellationRequested)
+2 -12
View File
@@ -11,7 +11,6 @@
#include <string>
#include <map>
#include <memory>
#include <algorithm>
namespace ix
{
@@ -22,19 +21,10 @@ namespace ix
// Case Insensitive compare_less binary function
struct NocaseCompare
{
bool operator() (const unsigned char& c1, const unsigned char& c2) const
{
return std::tolower(c1) < std::tolower(c2);
}
bool operator() (const unsigned char& c1, const unsigned char& c2) const;
};
bool operator() (const std::string & s1, const std::string & s2) const
{
return std::lexicographical_compare
(s1.begin(), s1.end(), // source range
s2.begin(), s2.end(), // dest range
NocaseCompare()); // comparison
}
bool operator() (const std::string & s1, const std::string & s2) const;
};
using WebSocketHttpHeaders = std::map<std::string, std::string, CaseInsensitiveLess>;
+121
View File
@@ -0,0 +1,121 @@
/*
* IXWebSocketMessageQueue.cpp
* Author: Korchynskyi Dmytro
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXWebSocketMessageQueue.h"
namespace ix
{
WebSocketMessageQueue::WebSocketMessageQueue(WebSocket* websocket)
{
bindWebsocket(websocket);
}
WebSocketMessageQueue::~WebSocketMessageQueue()
{
if (!_messages.empty())
{
// not handled all messages
}
bindWebsocket(nullptr);
}
void WebSocketMessageQueue::bindWebsocket(WebSocket * websocket)
{
if (_websocket == websocket) return;
// unbind old
if (_websocket)
{
// set dummy callback just to avoid crash
_websocket->setOnMessageCallback([](
WebSocketMessageType,
const std::string&,
size_t,
const WebSocketErrorInfo&,
const WebSocketOpenInfo&,
const WebSocketCloseInfo&)
{});
}
_websocket = websocket;
// bind new
if (_websocket)
{
_websocket->setOnMessageCallback([this](
WebSocketMessageType type,
const std::string& str,
size_t wireSize,
const WebSocketErrorInfo& errorInfo,
const WebSocketOpenInfo& openInfo,
const WebSocketCloseInfo& closeInfo)
{
MessagePtr message(new Message());
message->type = type;
message->str = str;
message->wireSize = wireSize;
message->errorInfo = errorInfo;
message->openInfo = openInfo;
message->closeInfo = closeInfo;
{
std::lock_guard<std::mutex> lock(_messagesMutex);
_messages.emplace_back(std::move(message));
}
});
}
}
void WebSocketMessageQueue::setOnMessageCallback(const OnMessageCallback& callback)
{
_onMessageUserCallback = callback;
}
void WebSocketMessageQueue::setOnMessageCallback(OnMessageCallback&& callback)
{
_onMessageUserCallback = std::move(callback);
}
WebSocketMessageQueue::MessagePtr WebSocketMessageQueue::popMessage()
{
MessagePtr message;
std::lock_guard<std::mutex> lock(_messagesMutex);
if (!_messages.empty())
{
message = std::move(_messages.front());
_messages.pop_front();
}
return message;
}
void WebSocketMessageQueue::poll(int count)
{
if (!_onMessageUserCallback)
return;
MessagePtr message;
while (count > 0 && (message = popMessage()))
{
_onMessageUserCallback(
message->type,
message->str,
message->wireSize,
message->errorInfo,
message->openInfo,
message->closeInfo
);
--count;
}
}
}
+53
View File
@@ -0,0 +1,53 @@
/*
* IXWebSocketMessageQueue.h
* Author: Korchynskyi Dmytro
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXWebSocket.h"
#include <thread>
#include <list>
#include <memory>
namespace ix
{
//
// A helper class to dispatch websocket message callbacks in your thread.
//
class WebSocketMessageQueue
{
public:
WebSocketMessageQueue(WebSocket* websocket = nullptr);
~WebSocketMessageQueue();
void bindWebsocket(WebSocket* websocket);
void setOnMessageCallback(const OnMessageCallback& callback);
void setOnMessageCallback(OnMessageCallback&& callback);
void poll(int count = 512);
protected:
struct Message
{
WebSocketMessageType type;
std::string str;
size_t wireSize;
WebSocketErrorInfo errorInfo;
WebSocketOpenInfo openInfo;
WebSocketCloseInfo closeInfo;
};
using MessagePtr = std::shared_ptr<Message>;
MessagePtr popMessage();
private:
WebSocket* _websocket = nullptr;
OnMessageCallback _onMessageUserCallback;
std::mutex _messagesMutex;
std::list<MessagePtr> _messages;
};
}
@@ -7,7 +7,6 @@
#include "IXWebSocketPerMessageDeflateCodec.h"
#include "IXWebSocketPerMessageDeflateOptions.h"
#include <iostream>
#include <cassert>
#include <string.h>
@@ -7,7 +7,6 @@
#include "IXWebSocketPerMessageDeflateOptions.h"
#include <sstream>
#include <iostream>
#include <algorithm>
#include <cctype>
@@ -23,7 +23,6 @@ namespace ix
WebSocketPerMessageDeflateOptions(std::string extension);
std::string generateHeader();
std::string parseHeader();
bool enabled() const;
bool getClientNoContextTakeover() const;
bool getServerNoContextTakeover() const;
-3
View File
@@ -6,9 +6,6 @@
#pragma once
#include <string>
#include <iostream>
namespace ix
{
struct WebSocketSendInfo
+21 -1
View File
@@ -17,13 +17,15 @@
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) : SocketServer(port, host, backlog, maxConnections),
_handshakeTimeoutSecs(handshakeTimeoutSecs)
_handshakeTimeoutSecs(handshakeTimeoutSecs),
_enablePong(kDefaultEnablePong)
{
}
@@ -35,6 +37,8 @@ namespace ix
void WebSocketServer::stop()
{
stopAcceptingConnections();
auto clients = getClients();
for (auto client : clients)
{
@@ -44,6 +48,16 @@ namespace ix
SocketServer::stop();
}
void WebSocketServer::enablePong()
{
_enablePong = true;
}
void WebSocketServer::disablePong()
{
_enablePong = false;
}
void WebSocketServer::setOnConnectionCallback(const OnConnectionCallback& callback)
{
_onConnectionCallback = callback;
@@ -58,6 +72,11 @@ namespace ix
webSocket->disableAutomaticReconnection();
if (_enablePong)
webSocket->enablePong();
else
webSocket->disablePong();
// Add this client to our client set
{
std::lock_guard<std::mutex> lock(_clientsMutex);
@@ -91,6 +110,7 @@ namespace ix
}
logInfo("WebSocketServer::handleConnection() done");
connectionState->setTerminated();
}
std::set<std::shared_ptr<WebSocket>> WebSocketServer::getClients()
+6 -1
View File
@@ -23,7 +23,7 @@ namespace ix
using OnConnectionCallback = std::function<void(std::shared_ptr<WebSocket>,
std::shared_ptr<ConnectionState>)>;
class WebSocketServer : public SocketServer {
class WebSocketServer final : public SocketServer {
public:
WebSocketServer(int port = SocketServer::kDefaultPort,
const std::string& host = SocketServer::kDefaultHost,
@@ -33,6 +33,9 @@ namespace ix
virtual ~WebSocketServer();
virtual void stop() final;
void enablePong();
void disablePong();
void setOnConnectionCallback(const OnConnectionCallback& callback);
// Get all the connected clients
@@ -41,6 +44,7 @@ namespace ix
private:
// Member variables
int _handshakeTimeoutSecs;
bool _enablePong;
OnConnectionCallback _onConnectionCallback;
@@ -48,6 +52,7 @@ namespace ix
std::set<std::shared_ptr<WebSocket>> _clients;
const static int kDefaultHandShakeTimeoutSecs;
const static bool kDefaultEnablePong;
// Methods
virtual void handleConnection(int fd,
+413 -160
View File
@@ -45,27 +45,52 @@
#include <vector>
#include <string>
#include <cstdarg>
#include <iostream>
#include <sstream>
#include <chrono>
#include <thread>
namespace
{
int greatestCommonDivisor(int a, int b)
{
while (b != 0)
{
int t = b;
b = a % b;
a = t;
}
return a;
}
}
namespace ix
{
const std::string WebSocketTransport::kHeartBeatPingMessage("ixwebsocket::heartbeat");
const int WebSocketTransport::kDefaultHeartBeatPeriod(-1);
const std::string WebSocketTransport::kPingMessage("ixwebsocket::heartbeat");
const int WebSocketTransport::kDefaultPingIntervalSecs(-1);
const int WebSocketTransport::kDefaultPingTimeoutSecs(-1);
const bool WebSocketTransport::kDefaultEnablePong(true);
const int WebSocketTransport::kClosingMaximumWaitingDelayInMs(300);
constexpr size_t WebSocketTransport::kChunkSize;
WebSocketTransport::WebSocketTransport() :
_useMask(true),
_readyState(CLOSED),
_closeCode(0),
_readyState(ReadyState::CLOSED),
_closeCode(WebSocketCloseConstants::kInternalErrorCode),
_closeReason(WebSocketCloseConstants::kInternalErrorMessage),
_closeWireSize(0),
_closeRemote(false),
_enablePerMessageDeflate(false),
_requestInitCancellation(false),
_heartBeatPeriod(kDefaultHeartBeatPeriod),
_lastSendTimePoint(std::chrono::steady_clock::now())
_closingTimePoint(std::chrono::steady_clock::now()),
_enablePong(kDefaultEnablePong),
_pingIntervalSecs(kDefaultPingIntervalSecs),
_pingTimeoutSecs(kDefaultPingTimeoutSecs),
_pingIntervalOrTimeoutGCDSecs(-1),
_nextGCDTimePoint(std::chrono::steady_clock::now()),
_lastSendPingTimePoint(std::chrono::steady_clock::now()),
_lastReceivePongTimePoint(std::chrono::steady_clock::now())
{
_readbuf.resize(kChunkSize);
}
@@ -76,29 +101,48 @@ namespace ix
}
void WebSocketTransport::configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
int heartBeatPeriod)
bool enablePong,
int pingIntervalSecs,
int pingTimeoutSecs)
{
_perMessageDeflateOptions = perMessageDeflateOptions;
_enablePerMessageDeflate = _perMessageDeflateOptions.enabled();
_heartBeatPeriod = heartBeatPeriod;
_enablePong = enablePong;
_pingIntervalSecs = pingIntervalSecs;
_pingTimeoutSecs = pingTimeoutSecs;
if (pingIntervalSecs > 0 && pingTimeoutSecs > 0)
{
_pingIntervalOrTimeoutGCDSecs = greatestCommonDivisor(pingIntervalSecs,
pingTimeoutSecs);
}
else if (_pingTimeoutSecs > 0)
{
_pingIntervalOrTimeoutGCDSecs = pingTimeoutSecs;
}
else
{
_pingIntervalOrTimeoutGCDSecs = pingIntervalSecs;
}
}
// Client
WebSocketInitResult WebSocketTransport::connectToUrl(const std::string& url,
int timeoutSecs)
{
std::lock_guard<std::mutex> lock(_socketMutex);
std::string protocol, host, path, query;
int port;
bool websocket = true;
if (!UrlParser::parse(url, protocol, host, path, query, port, websocket))
if (!UrlParser::parse(url, protocol, host, path, query, port))
{
return WebSocketInitResult(false, 0,
std::string("Could not parse URL ") + url);
}
bool tls = protocol == "wss";
std::string errorMsg;
bool tls = protocol == "wss";
_socket = createSocket(tls, errorMsg);
if (!_socket)
@@ -116,7 +160,7 @@ namespace ix
timeoutSecs);
if (result.success)
{
setReadyState(OPEN);
setReadyState(ReadyState::OPEN);
}
return result;
}
@@ -124,6 +168,8 @@ namespace ix
// Server
WebSocketInitResult WebSocketTransport::connectToSocket(int fd, int timeoutSecs)
{
std::lock_guard<std::mutex> lock(_socketMutex);
// Server should not mask the data it sends to the client
_useMask = false;
@@ -144,31 +190,36 @@ namespace ix
auto result = webSocketHandshake.serverHandshake(fd, timeoutSecs);
if (result.success)
{
setReadyState(OPEN);
setReadyState(ReadyState::OPEN);
}
return result;
}
WebSocketTransport::ReadyStateValues WebSocketTransport::getReadyState() const
WebSocketTransport::ReadyState WebSocketTransport::getReadyState() const
{
return _readyState;
}
void WebSocketTransport::setReadyState(ReadyStateValues readyStateValue)
void WebSocketTransport::setReadyState(ReadyState readyState)
{
// No state change, return
if (_readyState == readyStateValue) return;
if (_readyState == readyState) return;
if (readyStateValue == CLOSED)
if (readyState == ReadyState::CLOSED)
{
std::lock_guard<std::mutex> lock(_closeDataMutex);
_onCloseCallback(_closeCode, _closeReason, _closeWireSize);
_closeCode = 0;
_closeReason = std::string();
_onCloseCallback(_closeCode, _closeReason, _closeWireSize, _closeRemote);
_closeCode = WebSocketCloseConstants::kInternalErrorCode;
_closeReason = WebSocketCloseConstants::kInternalErrorMessage;
_closeWireSize = 0;
_closeRemote = false;
}
else if (readyState == ReadyState::OPEN)
{
initTimePointsAndGCDAfterConnect();
}
_readyState = readyStateValue;
_readyState = readyState;
}
void WebSocketTransport::setOnCloseCallback(const OnCloseCallback& onCloseCallback)
@@ -176,95 +227,178 @@ namespace ix
_onCloseCallback = onCloseCallback;
}
// Only consider send time points for that computation.
// The receive time points is taken into account in Socket::poll (second parameter).
bool WebSocketTransport::heartBeatPeriodExceeded()
void WebSocketTransport::initTimePointsAndGCDAfterConnect()
{
std::lock_guard<std::mutex> lock(_lastSendTimePointMutex);
auto now = std::chrono::steady_clock::now();
return now - _lastSendTimePoint > std::chrono::seconds(_heartBeatPeriod);
{
std::lock_guard<std::mutex> lock(_lastSendPingTimePointMutex);
_lastSendPingTimePoint = std::chrono::steady_clock::now();
}
{
std::lock_guard<std::mutex> lock(_lastReceivePongTimePointMutex);
_lastReceivePongTimePoint = std::chrono::steady_clock::now();
}
if (_pingIntervalOrTimeoutGCDSecs > 0)
{
_nextGCDTimePoint = std::chrono::steady_clock::now() + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs);
}
}
void WebSocketTransport::poll()
// Only consider send PING time points for that computation.
bool WebSocketTransport::pingIntervalExceeded()
{
_socket->poll(
[this](PollResultType pollResult)
if (_pingIntervalSecs <= 0)
return false;
std::lock_guard<std::mutex> lock(_lastSendPingTimePointMutex);
auto now = std::chrono::steady_clock::now();
return now - _lastSendPingTimePoint > std::chrono::seconds(_pingIntervalSecs);
}
bool WebSocketTransport::pingTimeoutExceeded()
{
if (_pingTimeoutSecs <= 0)
return false;
std::lock_guard<std::mutex> lock(_lastReceivePongTimePointMutex);
auto now = std::chrono::steady_clock::now();
return now - _lastReceivePongTimePoint > std::chrono::seconds(_pingTimeoutSecs);
}
bool WebSocketTransport::closingDelayExceeded()
{
std::lock_guard<std::mutex> 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 (1) ping timeout is enabled and (2) duration since last received
// ping response (PONG) exceeds the maximum delay, then close the connection
if (pingTimeoutExceeded())
{
// If (1) heartbeat is enabled, and (2) no data was received or
// send for a duration exceeding our heart-beat period, send a
// ping to the server.
if (pollResult == PollResultType::Timeout &&
heartBeatPeriodExceeded())
{
std::stringstream ss;
ss << kHeartBeatPingMessage << "::" << _heartBeatPeriod << "s";
sendPing(ss.str());
}
// Make sure we send all the buffered data
// there can be a lot of it for large messages.
else if (pollResult == PollResultType::SendRequest)
{
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);
close(WebSocketCloseConstants::kInternalErrorCode,
WebSocketCloseConstants::kPingTimeoutMessage);
}
// If ping is enabled and no ping has been sent for a duration
// exceeding our ping interval, send a ping to the server.
else if (pingIntervalExceeded())
{
std::stringstream ss;
ss << kPingMessage << "::" << _pingIntervalSecs << "s";
sendPing(ss.str());
}
}
if (result == PollResultType::Error)
{
_socket->close();
setReadyState(CLOSED);
break;
}
else if (result == PollResultType::ReadyForWrite)
{
sendOnSocket();
}
}
}
else if (pollResult == PollResultType::ReadyForRead)
{
while (true)
{
ssize_t ret = _socket->recv((char*)&_readbuf[0], _readbuf.size());
// 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 : _pingIntervalOrTimeoutGCDSecs;
if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
_socket->getErrno() == EAGAIN))
{
break;
}
else if (ret <= 0)
{
_rxbuf.clear();
_socket->close();
setReadyState(CLOSED);
break;
}
else
{
_rxbuf.insert(_rxbuf.end(),
_readbuf.begin(),
_readbuf.begin() + ret);
}
}
}
else if (pollResult == PollResultType::Error)
{
_socket->close();
}
else if (pollResult == PollResultType::CloseRequest)
{
_socket->close();
}
if (_pingIntervalOrTimeoutGCDSecs > 0)
{
// compute lasting delay to wait for next ping / timeout, if at least one set
auto now = std::chrono::steady_clock::now();
// Avoid a race condition where we get stuck in select
// while closing.
if (_readyState == CLOSING)
if (now >= _nextGCDTimePoint)
{
_nextGCDTimePoint = now + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs);
lastingTimeoutDelayInMs = _pingIntervalOrTimeoutGCDSecs * 1000;
}
else
{
lastingTimeoutDelayInMs = (int)std::chrono::duration_cast<std::chrono::milliseconds>(_nextGCDTimePoint - now).count();
}
}
#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->poll(lastingTimeoutDelayInMs);
// Make sure we send all the buffered data
// there can be a lot of it for large messages.
if (pollResult == PollResultType::SendRequest)
{
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)
{
_socket->close();
closeSocket();
setReadyState(ReadyState::CLOSED);
break;
}
},
_heartBeatPeriod);
else if (result == PollResultType::ReadyForWrite)
{
sendOnSocket();
}
}
}
else if (pollResult == PollResultType::ReadyForRead)
{
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 PollResult::AbnormalClose;
}
else
{
_rxbuf.insert(_rxbuf.end(),
_readbuf.begin(),
_readbuf.begin() + ret);
}
}
}
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
@@ -326,12 +460,13 @@ namespace ix
// | Payload Data continued ... |
// +---------------------------------------------------------------+
//
void WebSocketTransport::dispatch(const OnMessageCallback& onMessageCallback)
void WebSocketTransport::dispatch(WebSocketTransport::PollResult pollResult,
const OnMessageCallback& onMessageCallback)
{
while (true)
{
wsheader_type ws;
if (_rxbuf.size() < 2) return; /* Need at least 2 */
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;
@@ -339,7 +474,7 @@ namespace ix
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) return; /* Need: ws.header_size - _rxbuf.size() */
if (_rxbuf.size() < ws.header_size) break; /* Need: ws.header_size - _rxbuf.size() */
//
// Calculate payload length:
@@ -412,7 +547,7 @@ namespace ix
//
if (ws.fin && _chunks.empty())
{
emitMessage(MSG,
emitMessage(MessageKind::MSG,
std::string(_rxbuf.begin()+ws.header_size,
_rxbuf.begin()+ws.header_size+(size_t) ws.N),
ws,
@@ -432,26 +567,30 @@ namespace ix
_rxbuf.begin()+ws.header_size+(size_t)ws.N));
if (ws.fin)
{
emitMessage(MSG, getMergedChunks(), ws, onMessageCallback);
emitMessage(MessageKind::MSG, getMergedChunks(), ws, onMessageCallback);
_chunks.clear();
}
else
{
emitMessage(FRAGMENT, std::string(), ws, onMessageCallback);
emitMessage(MessageKind::FRAGMENT, std::string(), ws, onMessageCallback);
}
}
}
else if (ws.opcode == wsheader_type::PING)
{
unmaskReceiveBuffer(ws);
std::string pingData(_rxbuf.begin()+ws.header_size,
_rxbuf.begin()+ws.header_size + (size_t) ws.N);
// Reply back right away
bool compress = false;
sendData(wsheader_type::PONG, pingData, compress);
if (_enablePong)
{
// Reply back right away
bool compress = false;
sendData(wsheader_type::PONG, pingData, compress);
}
emitMessage(PING, pingData, ws, onMessageCallback);
emitMessage(MessageKind::PING, pingData, ws, onMessageCallback);
}
else if (ws.opcode == wsheader_type::PONG)
{
@@ -459,39 +598,99 @@ namespace ix
std::string pongData(_rxbuf.begin()+ws.header_size,
_rxbuf.begin()+ws.header_size + (size_t) ws.N);
emitMessage(PONG, pongData, ws, onMessageCallback);
std::lock_guard<std::mutex> lck(_lastReceivePongTimePointMutex);
_lastReceivePongTimePoint = std::chrono::steady_clock::now();
emitMessage(MessageKind::PONG, pongData, ws, onMessageCallback);
}
else if (ws.opcode == wsheader_type::CLOSE)
{
std::string reason;
uint16_t code = 0;
unmaskReceiveBuffer(ws);
// Extract the close code first, available as the first 2 bytes
uint16_t code = 0;
code |= ((uint64_t) _rxbuf[ws.header_size]) << 8;
code |= ((uint64_t) _rxbuf[ws.header_size+1]) << 0;
// Get the reason.
std::string reason(_rxbuf.begin()+ws.header_size + 2,
_rxbuf.begin()+ws.header_size + 2 + (size_t) ws.N);
if (ws.N >= 2)
{
std::lock_guard<std::mutex> lock(_closeDataMutex);
_closeCode = code;
_closeReason = reason;
_closeWireSize = _rxbuf.size();
// 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.assign(_rxbuf.begin()+ws.header_size + 2,
_rxbuf.begin()+ws.header_size + (size_t) ws.N);
}
}
else
{
// no close code received
code = WebSocketCloseConstants::kNoStatusCodeErrorCode;
reason = WebSocketCloseConstants::kNoStatusCodeErrorMessage;
}
close();
// 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);
_socket->wakeUpFromPoll(Socket::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;
{
std::lock_guard<std::mutex> lock(_closeDataMutex);
identicalReason = _closeCode == code && _closeReason == reason;
}
if (identicalReason)
{
bool remote = false;
closeSocketAndSwitchToClosedState(code, reason, _rxbuf.size(), remote);
}
}
}
else
{
close();
// 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::AbnormalClose)
{
_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
@@ -522,7 +721,7 @@ namespace ix
size_t wireSize = message.size();
// When the RSV1 bit is 1 it means the message is compressed
if (_enablePerMessageDeflate && ws.rsv1 && messageKind != FRAGMENT)
if (_enablePerMessageDeflate && ws.rsv1 && messageKind != MessageKind::FRAGMENT)
{
std::string decompressedMessage;
bool success = _perMessageDeflate.decompress(message, decompressedMessage);
@@ -549,7 +748,7 @@ namespace ix
bool compress,
const OnProgressCallback& onProgressCallback)
{
if (_readyState == CLOSING || _readyState == CLOSED)
if (_readyState != ReadyState::OPEN && _readyState != ReadyState::CLOSING)
{
return WebSocketSendInfo();
}
@@ -644,7 +843,7 @@ namespace ix
std::string::const_iterator message_end,
bool compress)
{
auto message_size = message_end - message_begin;
uint64_t message_size = static_cast<uint64_t>(message_end - message_begin);
unsigned x = getRandomUnsigned();
uint8_t masking_key[4] = {};
@@ -730,7 +929,15 @@ namespace ix
WebSocketSendInfo WebSocketTransport::sendPing(const std::string& message)
{
bool compress = false;
return sendData(wsheader_type::PING, message, compress);
WebSocketSendInfo info = sendData(wsheader_type::PING, message, compress);
if (info.success)
{
std::lock_guard<std::mutex> lck(_lastSendPingTimePointMutex);
_lastSendPingTimePoint = std::chrono::steady_clock::now();
}
return info;
}
WebSocketSendInfo WebSocketTransport::sendBinary(
@@ -751,24 +958,28 @@ namespace ix
_enablePerMessageDeflate, onProgressCallback);
}
ssize_t WebSocketTransport::send()
{
std::lock_guard<std::mutex> lock(_socketMutex);
return _socket->send((char*)&_txbuf[0], _txbuf.size());
}
void WebSocketTransport::sendOnSocket()
{
std::lock_guard<std::mutex> lock(_txbufMutex);
while (_txbuf.size())
{
ssize_t ret = _socket->send((char*)&_txbuf[0], _txbuf.size());
ssize_t ret = send();
if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
_socket->getErrno() == EAGAIN))
if (ret < 0 && Socket::isWaitNeeded())
{
break;
}
else if (ret <= 0)
{
_socket->close();
setReadyState(CLOSED);
closeSocket();
setReadyState(ReadyState::CLOSED);
break;
}
else
@@ -776,35 +987,77 @@ namespace ix
_txbuf.erase(_txbuf.begin(), _txbuf.begin() + ret);
}
}
std::lock_guard<std::mutex> lck(_lastSendTimePointMutex);
_lastSendTimePoint = std::chrono::steady_clock::now();
}
void WebSocketTransport::close()
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, "", compress);
}
}
void WebSocketTransport::closeSocket()
{
std::lock_guard<std::mutex> lock(_socketMutex);
_socket->close();
}
void WebSocketTransport::closeSocketAndSwitchToClosedState(
uint16_t code, const std::string& reason, size_t closeWireSize, bool remote)
{
closeSocket();
{
std::lock_guard<std::mutex> lock(_closeDataMutex);
_closeCode = code;
_closeReason = reason;
_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 == CLOSING || _readyState == CLOSED) return;
if (_readyState == ReadyState::CLOSING || _readyState == ReadyState::CLOSED) return;
{
std::lock_guard<std::mutex> lock(_closeDataMutex);
_closeCode = code;
_closeReason = reason;
_closeWireSize = closeWireSize;
_closeRemote = remote;
}
{
std::lock_guard<std::mutex> lock(_closingTimePointMutex);
_closingTimePoint = std::chrono::steady_clock::now();
}
setReadyState(ReadyState::CLOSING);
// See list of close events here:
// https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
// We use 1000: normal closure.
//
// >>> struct.pack('!H', 1000)
// b'\x03\xe8'
//
const std::string normalClosure = std::string("\x03\xe8");
bool compress = false;
sendData(wsheader_type::CLOSE, normalClosure, compress);
setReadyState(CLOSING);
_socket->wakeUpFromPoll(Socket::kCloseRequest);
_socket->close();
_closeCode = 1000;
_closeReason = "Normal Closure";
setReadyState(CLOSED);
sendCloseFrame(code, reason);
// wake up the poll, but do not close yet
_socket->wakeUpFromPoll(Socket::kSendRequest);
}
size_t WebSocketTransport::bufferedAmount() const
+82 -23
View File
@@ -25,6 +25,7 @@
#include "IXCancellationRequest.h"
#include "IXWebSocketHandshake.h"
#include "IXProgressCallback.h"
#include "IXWebSocketCloseConstants.h"
namespace ix
{
@@ -40,7 +41,7 @@ namespace ix
class WebSocketTransport
{
public:
enum ReadyStateValues
enum class ReadyState
{
CLOSING,
CLOSED,
@@ -48,7 +49,7 @@ namespace ix
OPEN
};
enum MessageKind
enum class MessageKind
{
MSG,
PING,
@@ -56,36 +57,54 @@ namespace ix
FRAGMENT
};
enum class PollResult
{
Succeeded,
AbnormalClose
};
using OnMessageCallback = std::function<void(const std::string&,
size_t,
bool,
MessageKind)>;
using OnCloseCallback = std::function<void(uint16_t,
const std::string&,
size_t)>;
size_t,
bool)>;
WebSocketTransport();
~WebSocketTransport();
void configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
int heartBeatPeriod);
bool enablePong,
int pingIntervalSecs,
int pingTimeoutSecs);
WebSocketInitResult connectToUrl(const std::string& url, // Client
int timeoutSecs);
WebSocketInitResult connectToSocket(int fd, // Server
int timeoutSecs);
void poll();
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();
ReadyStateValues getReadyState() const;
void setReadyState(ReadyStateValues readyStateValue);
void close(uint16_t code = WebSocketCloseConstants::kNormalClosureCode,
const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage,
size_t closeWireSize = 0,
bool remote = false);
void closeSocket();
ssize_t send();
ReadyState getReadyState() const;
void setReadyState(ReadyState readyState);
void setOnCloseCallback(const OnCloseCallback& onCloseCallback);
void dispatch(const OnMessageCallback& onMessageCallback);
void dispatch(PollResult pollResult,
const OnMessageCallback& onMessageCallback);
size_t bufferedAmount() const;
private:
@@ -98,11 +117,11 @@ namespace ix
bool mask;
enum opcode_type {
CONTINUATION = 0x0,
TEXT_FRAME = 0x1,
TEXT_FRAME = 0x1,
BINARY_FRAME = 0x2,
CLOSE = 8,
PING = 9,
PONG = 0xa,
CLOSE = 8,
PING = 9,
PONG = 0xa,
} opcode;
int N0;
uint64_t N;
@@ -111,7 +130,7 @@ namespace ix
// Tells whether we should mask the data we send.
// client should mask but server should not
bool _useMask;
std::atomic<bool> _useMask;
// Buffer for reading from our socket. That buffer is never resized.
std::vector<uint8_t> _readbuf;
@@ -136,14 +155,16 @@ namespace ix
// Underlying TCP socket
std::shared_ptr<Socket> _socket;
std::mutex _socketMutex;
// Hold the state of the connection (OPEN, CLOSED, etc...)
std::atomic<ReadyStateValues> _readyState;
std::atomic<ReadyState> _readyState;
OnCloseCallback _onCloseCallback;
uint16_t _closeCode;
std::string _closeReason;
size_t _closeWireSize;
bool _closeRemote;
mutable std::mutex _closeDataMutex;
// Data used for Per Message Deflate compression (with zlib)
@@ -154,15 +175,53 @@ namespace ix
// Used to cancel dns lookup + socket connect + http upgrade
std::atomic<bool> _requestInitCancellation;
// Optional Heartbeat
int _heartBeatPeriod;
static const int kDefaultHeartBeatPeriod;
const static std::string kHeartBeatPingMessage;
mutable std::mutex _lastSendTimePointMutex;
std::chrono::time_point<std::chrono::steady_clock> _lastSendTimePoint;
mutable std::mutex _closingTimePointMutex;
std::chrono::time_point<std::chrono::steady_clock>_closingTimePoint;
static const int kClosingMaximumWaitingDelayInMs;
// No data was send through the socket for longer than the heartbeat period
bool heartBeatPeriodExceeded();
// enable auto response to ping
std::atomic<bool> _enablePong;
static const bool kDefaultEnablePong;
// Optional ping and pong timeout
// if both ping interval and timeout are set (> 0),
// then use GCD of these value to wait for the lowest time
int _pingIntervalSecs;
int _pingTimeoutSecs;
int _pingIntervalOrTimeoutGCDSecs;
static const int kDefaultPingIntervalSecs;
static const int kDefaultPingTimeoutSecs;
static const std::string kPingMessage;
// Record time step for ping/ ping timeout to ensure we wait for the right left duration
std::chrono::time_point<std::chrono::steady_clock> _nextGCDTimePoint;
// We record when ping are being sent so that we can know when to send the next one
// We also record when pong are being sent as a reply to pings, to close the connections
// if no pong were received sufficiently fast.
mutable std::mutex _lastSendPingTimePointMutex;
mutable std::mutex _lastReceivePongTimePointMutex;
std::chrono::time_point<std::chrono::steady_clock> _lastSendPingTimePoint;
std::chrono::time_point<std::chrono::steady_clock> _lastReceivePongTimePoint;
// If this function returns true, it is time to send a new ping
bool pingIntervalExceeded();
// No PONG data was received through the socket for longer than ping timeout delay
bool pingTimeoutExceeded();
// after calling close(), if no CLOSE frame answer is received back from the remote, we should close the connexion
bool closingDelayExceeded();
void initTimePointsAndGCDAfterConnect();
void sendCloseFrame(uint16_t code, const std::string& reason);
void closeSocketAndSwitchToClosedState(uint16_t code,
const std::string& reason,
size_t closeWireSize,
bool remote);
void sendOnSocket();
WebSocketSendInfo sendData(wsheader_type::opcode_type type,
+263
View File
@@ -0,0 +1,263 @@
/*
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
* https://github.com/corporateshark/LUrlParser
*
* The MIT License (MIT)
*
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "LUrlParser.h"
#include <algorithm>
#include <cstring>
#include <stdlib.h>
// check if the scheme name is valid
static bool IsSchemeValid( const std::string& SchemeName )
{
for ( auto c : SchemeName )
{
if ( !isalpha( c ) && c != '+' && c != '-' && c != '.' ) return false;
}
return true;
}
bool LUrlParser::clParseURL::GetPort( int* OutPort ) const
{
if ( !IsValid() ) { return false; }
int Port = atoi( m_Port.c_str() );
if ( Port <= 0 || Port > 65535 ) { return false; }
if ( OutPort ) { *OutPort = Port; }
return true;
}
// based on RFC 1738 and RFC 3986
LUrlParser::clParseURL LUrlParser::clParseURL::ParseURL( const std::string& URL )
{
LUrlParser::clParseURL Result;
const char* CurrentString = URL.c_str();
/*
* <scheme>:<scheme-specific-part>
* <scheme> := [a-z\+\-\.]+
* For resiliency, programs interpreting URLs should treat upper case letters as equivalent to lower case in scheme names
*/
// try to read scheme
{
const char* LocalString = strchr( CurrentString, ':' );
if ( !LocalString )
{
return clParseURL( LUrlParserError_NoUrlCharacter );
}
// save the scheme name
Result.m_Scheme = std::string( CurrentString, LocalString - CurrentString );
if ( !IsSchemeValid( Result.m_Scheme ) )
{
return clParseURL( LUrlParserError_InvalidSchemeName );
}
// scheme should be lowercase
std::transform( Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower );
// skip ':'
CurrentString = LocalString+1;
}
/*
* //<user>:<password>@<host>:<port>/<url-path>
* any ":", "@" and "/" must be normalized
*/
// skip "//"
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
// check if the user name and password are specified
bool bHasUserName = false;
const char* LocalString = CurrentString;
while ( *LocalString )
{
if ( *LocalString == '@' )
{
// user name and password are specified
bHasUserName = true;
break;
}
else if ( *LocalString == '/' )
{
// end of <host>:<port> specification
bHasUserName = false;
break;
}
LocalString++;
}
// user name and password
LocalString = CurrentString;
if ( bHasUserName )
{
// read user name
while ( *LocalString && *LocalString != ':' && *LocalString != '@' ) LocalString++;
Result.m_UserName = std::string( CurrentString, LocalString - CurrentString );
// proceed with the current pointer
CurrentString = LocalString;
if ( *CurrentString == ':' )
{
// skip ':'
CurrentString++;
// read password
LocalString = CurrentString;
while ( *LocalString && *LocalString != '@' ) LocalString++;
Result.m_Password = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
// skip '@'
if ( *CurrentString != '@' )
{
return clParseURL( LUrlParserError_NoAtSign );
}
CurrentString++;
}
bool bHasBracket = ( *CurrentString == '[' );
// go ahead, read the host name
LocalString = CurrentString;
while ( *LocalString )
{
if ( bHasBracket && *LocalString == ']' )
{
// end of IPv6 address
LocalString++;
break;
}
else if ( !bHasBracket && ( *LocalString == ':' || *LocalString == '/' ) )
{
// port number is specified
break;
}
LocalString++;
}
Result.m_Host = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
// is port number specified?
if ( *CurrentString == ':' )
{
CurrentString++;
// read port number
LocalString = CurrentString;
while ( *LocalString && *LocalString != '/' ) LocalString++;
Result.m_Port = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
// end of string
if ( !*CurrentString )
{
Result.m_ErrorCode = LUrlParserError_Ok;
return Result;
}
// skip '/'
if ( *CurrentString != '/' )
{
return clParseURL( LUrlParserError_NoSlash );
}
CurrentString++;
// parse the path
LocalString = CurrentString;
while ( *LocalString && *LocalString != '#' && *LocalString != '?' ) LocalString++;
Result.m_Path = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
// check for query
if ( *CurrentString == '?' )
{
// skip '?'
CurrentString++;
// read query
LocalString = CurrentString;
while ( *LocalString && *LocalString != '#' ) LocalString++;
Result.m_Query = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
// check for fragment
if ( *CurrentString == '#' )
{
// skip '#'
CurrentString++;
// read fragment
LocalString = CurrentString;
while ( *LocalString ) LocalString++;
Result.m_Fragment = std::string( CurrentString, LocalString - CurrentString );
}
Result.m_ErrorCode = LUrlParserError_Ok;
return Result;
}
+78
View File
@@ -0,0 +1,78 @@
/*
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
* https://github.com/corporateshark/LUrlParser
*
* The MIT License (MIT)
*
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include <string>
namespace LUrlParser
{
enum LUrlParserError
{
LUrlParserError_Ok = 0,
LUrlParserError_Uninitialized = 1,
LUrlParserError_NoUrlCharacter = 2,
LUrlParserError_InvalidSchemeName = 3,
LUrlParserError_NoDoubleSlash = 4,
LUrlParserError_NoAtSign = 5,
LUrlParserError_UnexpectedEndOfLine = 6,
LUrlParserError_NoSlash = 7,
};
class clParseURL
{
public:
LUrlParserError m_ErrorCode;
std::string m_Scheme;
std::string m_Host;
std::string m_Port;
std::string m_Path;
std::string m_Query;
std::string m_Fragment;
std::string m_UserName;
std::string m_Password;
clParseURL()
: m_ErrorCode( LUrlParserError_Uninitialized )
{}
/// return 'true' if the parsing was successful
bool IsValid() const { return m_ErrorCode == LUrlParserError_Ok; }
/// helper to convert the port number to int, return 'true' if the port is valid (within the 0..65535 range)
bool GetPort( int* OutPort ) const;
/// parse the URL
static clParseURL ParseURL( const std::string& URL );
private:
explicit clParseURL( LUrlParserError ErrorCode )
: m_ErrorCode( ErrorCode )
{}
};
} // namespace LUrlParser
+8 -1
View File
@@ -20,6 +20,8 @@
#include <cstdint>
#include <cstddef>
#include <string>
#include <string.h>
class WebSocketHandshakeKeyGen {
template <int N, typename T>
@@ -100,7 +102,12 @@ class WebSocketHandshakeKeyGen {
}
public:
static inline void generate(const char input[24], char output[28]) {
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
};
@@ -4,13 +4,41 @@
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "../IXSetThreadName.h"
#include <iostream>
#include <Windows.h>
namespace ix
{
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)
{
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)
{
}
}
void setThreadName(const std::string& name)
{
// FIXME
std::cerr << "setThreadName not implemented on Windows yet" << std::endl;
SetThreadName(-1, name.c_str());
}
}
+16 -14
View File
@@ -5,8 +5,17 @@ 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
brew:
mkdir -p build && (cd build ; cmake .. ; make -j install)
mkdir -p build && (cd build ; cmake -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j install)
ws:
mkdir -p build && (cd build ; cmake -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j)
uninstall:
xargs rm -fv < build/install_manifest.txt
.PHONY: docker
@@ -25,20 +34,12 @@ docker_push:
docker push ${LATEST}
run:
docker run --cap-add sys_ptrace -it ws:latest
docker run --cap-add sys_ptrace --entrypoint=bash -it bsergean/ws:build
# this is helpful to remove trailing whitespaces
trail:
sh third_party/remote_trailing_whitespaces.sh
build:
(cd examples/satori_publisher ; mkdir -p build ; cd build ; cmake .. ; make)
(cd examples/chat ; mkdir -p build ; cd build ; cmake .. ; make)
(cd examples/ping_pong ; mkdir -p build ; cd build ; cmake .. ; make)
(cd examples/ws_connect ; mkdir -p build ; cd build ; cmake .. ; make)
(cd examples/echo_server ; mkdir -p build ; cd build ; cmake .. ; make)
(cd examples/broadcast_server ; mkdir -p build ; cd build ; cmake .. ; make)
# 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:
@@ -48,10 +49,10 @@ test_server:
# env TEST=Websocket_chat make test
# env TEST=heartbeat make test
test:
python test/run.py
python2.7 test/run.py
ws_test: all
(cd ws ; bash test_ws.sh)
ws_test: ws
(cd ws ; env DEBUG=1 PATH=../ws/build:$$PATH bash test_ws.sh)
# For the fork that is configured with appveyor
rebase_upstream:
@@ -62,7 +63,8 @@ rebase_upstream:
install_cmake_for_linux:
mkdir -p /tmp/cmake
(cd /tmp/cmake ; curl -L -O https://github.com/Kitware/CMake/releases/download/v3.14.0-rc4/cmake-3.14.0-rc4-Linux-x86_64.tar.gz ; tar zxf cmake-3.14.0-rc4-Linux-x86_64.tar.gz)
(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)
.PHONY: test
.PHONY: build
.PHONY: ws
+4 -3
View File
@@ -1,9 +1,10 @@
CMakeCache.txt
package-lock.json
CMakeFiles
ixwebsocket_unittest
cmake_install.cmake
CMakeFiles
ixwebsocket_unittest
cmake_install.cmake
node_modules
ixwebsocket
Makefile
build
ixwebsocket_unittest.xml
+30 -10
View File
@@ -5,12 +5,13 @@
cmake_minimum_required (VERSION 3.4.1)
project (ixwebsocket_unittest)
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../third_party/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
find_package(Sanitizers)
set (CMAKE_CXX_STANDARD 14)
if (NOT WIN32)
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../third_party/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
find_package(Sanitizers)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
set(CMAKE_LD_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
option(USE_TLS "Add TLS support" ON)
endif()
@@ -19,6 +20,8 @@ add_subdirectory(${PROJECT_SOURCE_DIR}/.. ixwebsocket)
include_directories(
${PROJECT_SOURCE_DIR}/Catch2/single_include
../third_party/msgpack11
../third_party/spdlog/include
../ws
)
# Shared sources
@@ -26,24 +29,41 @@ set (SOURCES
test_runner.cpp
IXTest.cpp
../third_party/msgpack11/msgpack11.cpp
../ws/ixcore/utils/IXCoreLogger.cpp
IXDNSLookupTest.cpp
IXSocketTest.cpp
IXSocketConnectTest.cpp
IXWebSocketServerTest.cpp
IXWebSocketTestConnectionDisconnection.cpp
IXUrlParserTest.cpp
IXWebSocketServerTest.cpp
)
# Some unittest don't work on windows yet
if (NOT WIN32)
list(APPEND SOURCES
IXWebSocketServerTest.cpp
IXWebSocketHeartBeatTest.cpp
if (UNIX)
list(APPEND SOURCES
IXDNSLookupTest.cpp
cmd_websocket_chat.cpp
IXWebSocketTestConnectionDisconnection.cpp
IXWebSocketCloseTest.cpp
IXWebSocketPingTest.cpp
IXWebSocketPingTimeoutTest.cpp
)
endif()
# Some unittest fail for dubious reason on Ubuntu Xenial with TSAN
if (MAC OR WIN32)
list(APPEND SOURCES
IXWebSocketMessageQTest.cpp
)
endif()
# Disable tests for now that are failing or not reliable
add_executable(ixwebsocket_unittest ${SOURCES})
add_sanitizers(ixwebsocket_unittest)
if (NOT WIN32)
add_sanitizers(ixwebsocket_unittest)
endif()
if (APPLE AND USE_TLS)
target_link_libraries(ixwebsocket_unittest "-framework foundation" "-framework security")
File diff suppressed because it is too large. Load diff
+10 -2
View File
@@ -17,8 +17,12 @@ 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("www.google.com", 80, errMsg, [] { return false; });
int fd = SocketConnect::connect("127.0.0.1", port, errMsg, [] { return false; });
std::cerr << "Error message: " << errMsg << std::endl;
REQUIRE(fd != -1);
}
@@ -34,9 +38,13 @@ TEST_CASE("socket_connect", "[net]")
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("www.google.com", 80, errMsg, [] { return true; });
int fd = SocketConnect::connect("127.0.0.1", port, errMsg, [] { return true; });
std::cerr << "Error message: " << errMsg << std::endl;
REQUIRE(fd == -1);
}
+10 -6
View File
@@ -53,13 +53,17 @@ namespace ix
TEST_CASE("socket", "[socket]")
{
SECTION("Connect to google HTTP server. Send GET request without header. Should return 200")
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;
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
std::string host("www.google.com");
int port = 80;
std::string host("127.0.0.1");
std::stringstream ss;
ss << "GET / HTTP/1.1\r\n";
@@ -67,14 +71,14 @@ TEST_CASE("socket", "[socket]")
ss << "\r\n";
std::string request(ss.str());
int expectedStatus = 200;
int expectedStatus = 400;
int timeoutSecs = 3;
testSocket(host, port, request, socket, expectedStatus, timeoutSecs);
}
#if defined(__APPLE__) or defined(__linux__)
SECTION("Connect to google HTTPS server. Send GET request without header. Should return 200")
#if defined(__APPLE__) || defined(__linux__)
SECTION("Connect to google HTTPS server over port 443. Send GET request without header. Should return 200")
{
std::string errMsg;
bool tls = true;
+70 -12
View File
@@ -17,6 +17,8 @@
#include <stdlib.h>
#include <stack>
#include <iomanip>
#include <random>
namespace ix
{
@@ -70,10 +72,12 @@ namespace ix
Logger() << msg;
}
int getAnyFreePortSimple()
int getAnyFreePortRandom()
{
static int defaultPort = 8090;
return defaultPort++;
std::random_device rd;
std::uniform_int_distribution<int> dist(1024 + 1, 65535);
return dist(rd);
}
int getAnyFreePort()
@@ -83,7 +87,7 @@ namespace ix
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
log("Cannot compute a free port. socket error.");
return defaultPort;
return getAnyFreePortRandom();
}
int enable = 1;
@@ -91,7 +95,7 @@ namespace ix
(char*) &enable, sizeof(enable)) < 0)
{
log("Cannot compute a free port. setsockopt error.");
return defaultPort;
return getAnyFreePortRandom();
}
// Bind to port 0. This is the standard way to get a free port.
@@ -104,22 +108,22 @@ namespace ix
{
log("Cannot compute a free port. bind error.");
::close(sockfd);
return defaultPort;
Socket::closeSocket(sockfd);
return getAnyFreePortRandom();
}
struct sockaddr_in sa; // server address information
unsigned int len;
socklen_t len = sizeof(sa);
if (getsockname(sockfd, (struct sockaddr *) &sa, &len) < 0)
{
log("Cannot compute a free port. getsockname error.");
::close(sockfd);
return defaultPort;
Socket::closeSocket(sockfd);
return getAnyFreePortRandom();
}
int port = ntohs(sa.sin_port);
::close(sockfd);
Socket::closeSocket(sockfd);
return port;
}
@@ -130,7 +134,7 @@ namespace ix
{
#if defined(__has_feature)
# if __has_feature(address_sanitizer)
int port = getAnyFreePortSimple();
int port = getAnyFreePortRandom();
# else
int port = getAnyFreePort();
# endif
@@ -166,4 +170,58 @@ namespace ix
std::cout << prefix << ": " << s << " => " << ss.str() << std::endl;
}
bool startWebSocketEchoServer(ix::WebSocketServer& server)
{
server.setOnConnectionCallback(
[&server](std::shared_ptr<ix::WebSocket> webSocket,
std::shared_ptr<ConnectionState> connectionState)
{
webSocket->setOnMessageCallback(
[webSocket, connectionState, &server](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)
{
Logger() << "New connection";
Logger() << "Uri: " << openInfo.uri;
Logger() << "Headers:";
for (auto it : openInfo.headers)
{
Logger() << it.first << ": " << it.second;
}
}
else if (messageType == ix::WebSocketMessageType::Close)
{
Logger() << "Closed connection";
}
else if (messageType == ix::WebSocketMessageType::Message)
{
for (auto&& client : server.getClients())
{
if (client != webSocket)
{
client->send(str);
}
}
}
}
);
}
);
auto res = server.listen();
if (!res.first)
{
Logger() << res.second;
return false;
}
server.start();
return true;
}
}
+7 -12
View File
@@ -11,6 +11,8 @@
#include <sstream>
#include <iostream>
#include <mutex>
#include <spdlog/spdlog.h>
#include <ixwebsocket/IXWebSocketServer.h>
namespace ix
{
@@ -27,22 +29,14 @@ namespace ix
struct Logger
{
public:
Logger& operator<<(const std::string& msg)
{
std::lock_guard<std::mutex> lock(_mutex);
std::cerr << msg;
std::cerr << std::endl;
return *this;
}
template <typename T>
Logger& operator<<(T const& obj)
{
std::lock_guard<std::mutex> lock(_mutex);
std::cerr << obj;
std::cerr << std::endl;
std::stringstream ss;
ss << obj;
spdlog::info(ss.str());
return *this;
}
@@ -52,6 +46,7 @@ namespace ix
void log(const std::string& msg);
bool computeFreePorts(int count);
int getFreePort();
bool startWebSocketEchoServer(ix::WebSocketServer& server);
}
+108
View File
@@ -0,0 +1,108 @@
/*
* IXSocketTest.cpp
* Author: Korchynskyi Dmytro
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#include <iostream>
#include <ixwebsocket/IXUrlParser.h>
#include "IXTest.h"
#include "catch.hpp"
#include <string.h>
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.eyJleHAiOjE1NTcxNzAwNzIsInVpZCI6ImMwZmZjOGE1LTk4OTktNDAwYi1hNGU5LTJjNWM3NjFmNWQxZiIsInVzbiI6InN2YmhOdlNJSmEifQ.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.eyJleHAiOjE1NTcxNzAwNzIsInVpZCI6ImMwZmZjOGE1LTk4OTktNDAwYi1hNGU5LTJjNWM3NjFmNWQxZiIsInVzbiI6InN2YmhOdlNJSmEifQ.5L8BUbpTA4XAHlSrdwhIVlrlIpRtjExepim7Yh5eEO4&status=true&format=protobuf");
REQUIRE(query == "token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NTcxNzAwNzIsInVpZCI6ImMwZmZjOGE1LTk4OTktNDAwYi1hNGU5LTJjNWM3NjFmNWQxZiIsInVzbiI6InN2YmhOdlNJSmEifQ.5L8BUbpTA4XAHlSrdwhIVlrlIpRtjExepim7Yh5eEO4&status=true&format=protobuf");
REQUIRE(port == 7350);
}
}
}
+456
View File
@@ -0,0 +1,456 @@
/*
* IXWebSocketCloseTest.cpp
* Author: Alexandre Konieczny
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#include <iostream>
#include <sstream>
#include <queue>
#include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXWebSocketServer.h>
#include "IXTest.h"
#include "catch.hpp"
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;
void sendMessage(const std::string& text);
uint16_t getCloseCode();
const std::string& getCloseReason();
bool getCloseRemote();
private:
ix::WebSocket _webSocket;
int _port;
mutable std::mutex _mutexCloseData;
uint16_t _closeCode;
std::string _closeReason;
bool _closeRemote;
};
WebSocketClient::WebSocketClient(int port)
: _port(port)
, _closeCode(0)
, _closeReason(std::string(""))
, _closeRemote(false)
{
;
}
bool WebSocketClient::isReady() const
{
return _webSocket.getReadyState() == ix::ReadyState::Open;
}
uint16_t WebSocketClient::getCloseCode()
{
std::lock_guard<std::mutex> lck(_mutexCloseData);
return _closeCode;
}
const std::string& WebSocketClient::getCloseReason()
{
std::lock_guard<std::mutex> lck(_mutexCloseData);
return _closeReason;
}
bool WebSocketClient::getCloseRemote()
{
std::lock_guard<std::mutex> 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](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)
{
std::stringstream ss;
ss << "client disconnected("
<< closeInfo.code
<< ","
<< closeInfo.reason
<< ")";
log(ss.str());
std::lock_guard<std::mutex> lck(_mutexCloseData);
_closeCode = closeInfo.code;
_closeReason = std::string(closeInfo.reason);
_closeRemote = closeInfo.remote;
}
else if (messageType == ix::WebSocketMessageType::Error)
{
ss << "Error ! " << error.reason;
log(ss.str());
}
else if (messageType == ix::WebSocketMessageType::Pong)
{
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);
}
bool startServer(ix::WebSocketServer& server,
uint16_t& receivedCloseCode,
std::string& receivedCloseReason,
bool& receivedCloseRemote,
std::mutex& mutexWrite)
{
// A dev/null server
server.setOnConnectionCallback(
[&server, &receivedCloseCode, &receivedCloseReason, &receivedCloseRemote, &mutexWrite](std::shared_ptr<ix::WebSocket> webSocket,
std::shared_ptr<ConnectionState> connectionState)
{
webSocket->setOnMessageCallback(
[webSocket, connectionState, &server, &receivedCloseCode, &receivedCloseReason, &receivedCloseRemote, &mutexWrite](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)
{
Logger() << "New server connection";
Logger() << "id: " << connectionState->getId();
Logger() << "Uri: " << openInfo.uri;
Logger() << "Headers:";
for (auto it : openInfo.headers)
{
Logger() << it.first << ": " << it.second;
}
}
else if (messageType == ix::WebSocketMessageType::Close)
{
std::stringstream ss;
ss << "Server closed connection("
<< closeInfo.code
<< ","
<< closeInfo.reason
<< ")";
log(ss.str());
std::lock_guard<std::mutex> lck(mutexWrite);
receivedCloseCode = closeInfo.code;
receivedCloseReason = std::string(closeInfo.reason);
receivedCloseRemote = closeInfo.remote;
}
}
);
}
);
auto res = server.listen();
if (!res.first)
{
log(res.second);
return false;
}
server.start();
return true;
}
}
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)
{
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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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();
}
}
-228
View File
@@ -1,228 +0,0 @@
/*
* IXWebSocketHeartBeatTest.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#include <iostream>
#include <sstream>
#include <queue>
#include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXWebSocketServer.h>
#include "IXTest.h"
#include "catch.hpp"
using namespace ix;
namespace
{
class WebSocketClient
{
public:
WebSocketClient(int port);
void subscribe(const std::string& channel);
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::WebSocket_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);
// The important bit for this test.
// Set a 1 second heartbeat ; if no traffic is present on the connection for 1 second
// a ping message will be sent by the client.
_webSocket.setHeartBeatPeriod(1);
std::stringstream ss;
log(std::string("Connecting to url: ") + url);
_webSocket.setOnMessageCallback(
[](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::WebSocket_MessageType_Open)
{
log("client connected");
}
else if (messageType == ix::WebSocket_MessageType_Close)
{
log("client disconnected");
}
else if (messageType == ix::WebSocket_MessageType_Error)
{
ss << "Error ! " << error.reason;
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_Pong)
{
ss << "Received pong message " << str;
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_Ping)
{
ss << "Received ping message " << str;
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_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);
}
bool startServer(ix::WebSocketServer& server, std::atomic<int>& receivedPingMessages)
{
// A dev/null server
server.setOnConnectionCallback(
[&server, &receivedPingMessages](std::shared_ptr<ix::WebSocket> webSocket,
std::shared_ptr<ConnectionState> 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::WebSocket_MessageType_Open)
{
Logger() << "New server connection";
Logger() << "id: " << connectionState->getId();
Logger() << "Uri: " << openInfo.uri;
Logger() << "Headers:";
for (auto it : openInfo.headers)
{
Logger() << it.first << ": " << it.second;
}
}
else if (messageType == ix::WebSocket_MessageType_Close)
{
log("Server closed connection");
}
else if (messageType == ix::WebSocket_MessageType_Ping)
{
log("Server received a ping");
receivedPingMessages++;
}
}
);
}
);
auto res = server.listen();
if (!res.first)
{
log(res.second);
return false;
}
server.start();
return true;
}
}
TEST_CASE("Websocket_heartbeat", "[heartbeat]")
{
SECTION("Make sure that ping messages are sent during heartbeat.")
{
ix::setupWebSocketTrafficTrackerCallback();
int port = getFreePort();
ix::WebSocketServer server(port);
std::atomic<int> serverReceivedPingMessages(0);
REQUIRE(startServer(server, serverReceivedPingMessages));
std::string session = ix::generateSessionId();
WebSocketClient webSocketClientA(port);
WebSocketClient webSocketClientB(port);
webSocketClientA.start();
webSocketClientB.start();
// Wait for all chat instance to be ready
while (true)
{
if (webSocketClientA.isReady() && webSocketClientB.isReady()) break;
ix::msleep(10);
}
REQUIRE(server.getClients().size() == 2);
ix::msleep(900);
webSocketClientB.sendMessage("hello world");
ix::msleep(900);
webSocketClientB.sendMessage("hello world");
ix::msleep(900);
webSocketClientA.stop();
webSocketClientB.stop();
// Here we test heart beat period exceeded for clientA
// but it should not be exceeded for clientB which has sent data.
// -> expected ping messages == 2, but add a small buffer to make this more reliable.
REQUIRE(serverReceivedPingMessages >= 2);
REQUIRE(serverReceivedPingMessages <= 4);
// Give us 500ms for the server to notice that clients went away
ix::msleep(500);
REQUIRE(server.getClients().size() == 0);
ix::reportWebSocketTraffic();
}
}
+193
View File
@@ -0,0 +1,193 @@
/*
* IXWebSocketMessageQTest.cpp
* Author: Korchynskyi Dmytro
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXWebSocketServer.h>
#include <ixwebsocket/IXWebSocketMessageQueue.h>
#include "IXTest.h"
#include "catch.hpp"
#include <thread>
using namespace ix;
namespace
{
bool startServer(ix::WebSocketServer& server)
{
server.setOnConnectionCallback(
[&server](std::shared_ptr<ix::WebSocket> webSocket,
std::shared_ptr<ConnectionState> connectionState)
{
webSocket->setOnMessageCallback(
[connectionState, &server](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)
{
Logger() << "New connection";
connectionState->computeId();
Logger() << "id: " << connectionState->getId();
Logger() << "Uri: " << openInfo.uri;
Logger() << "Headers:";
for (auto it : openInfo.headers)
{
Logger() << it.first << ": " << it.second;
}
}
else if (messageType == ix::WebSocketMessageType::Close)
{
Logger() << "Closed connection";
}
else if (messageType == ix::WebSocketMessageType::Message)
{
Logger() << "Message received: " << str;
for (auto&& client : server.getClients())
{
client->send(str);
}
}
}
);
}
);
auto res = server.listen();
if (!res.first)
{
Logger() << res.second;
return false;
}
server.start();
return true;
}
class MsgQTestClient
{
public:
MsgQTestClient()
{
msgQ.bindWebsocket(&ws);
msgQ.setOnMessageCallback([this](WebSocketMessageType messageType,
const std::string & str,
size_t wireSize,
const WebSocketErrorInfo & error,
const WebSocketOpenInfo & openInfo,
const WebSocketCloseInfo & closeInfo)
{
REQUIRE(mainThreadId == std::this_thread::get_id());
std::stringstream ss;
if (messageType == WebSocketMessageType::Open)
{
log("client connected");
sendNextMessage();
}
else if (messageType == WebSocketMessageType::Close)
{
log("client disconnected");
}
else if (messageType == WebSocketMessageType::Error)
{
ss << "Error ! " << error.reason;
log(ss.str());
testDone = true;
}
else if (messageType == WebSocketMessageType::Pong)
{
ss << "Received pong message " << str;
log(ss.str());
}
else if (messageType == WebSocketMessageType::Ping)
{
ss << "Received ping message " << str;
log(ss.str());
}
else if (messageType == WebSocketMessageType::Message)
{
REQUIRE(str.compare("Hey dude!") == 0);
++receivedCount;
ss << "Received message " << str;
log(ss.str());
sendNextMessage();
}
else
{
ss << "Invalid WebSocketMessageType";
log(ss.str());
testDone = true;
}
});
}
void sendNextMessage()
{
if (receivedCount >= 3)
{
testDone = true;
succeeded = true;
}
else
{
auto info = ws.sendText("Hey dude!");
if (info.success)
log("sent message");
else
log("send failed");
}
}
void run(const std::string& url)
{
mainThreadId = std::this_thread::get_id();
testDone = false;
receivedCount = 0;
ws.setUrl(url);
ws.start();
while (!testDone)
{
msgQ.poll();
msleep(50);
}
}
bool isSucceeded() const { return succeeded; }
private:
WebSocket ws;
WebSocketMessageQueue msgQ;
bool testDone = false;
uint32_t receivedCount = 0;
std::thread::id mainThreadId;
bool succeeded = false;
};
}
TEST_CASE("Websocket_message_queue", "[websocket_message_q]")
{
SECTION("Send several messages")
{
int port = getFreePort();
WebSocketServer server(port);
REQUIRE(startServer(server));
MsgQTestClient testClient;
testClient.run("ws://127.0.0.1:" + std::to_string(port));
REQUIRE(testClient.isSucceeded());
server.stop();
}
}
+481
View File
@@ -0,0 +1,481 @@
/*
* IXWebSocketPingTest.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#include <iostream>
#include <sstream>
#include <queue>
#include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXWebSocketServer.h>
#include "IXTest.h"
#include "catch.hpp"
using namespace ix;
namespace
{
class WebSocketClient
{
public:
WebSocketClient(int port, bool useHeartBeatMethod);
void start();
void stop();
bool isReady() const;
void sendMessage(const std::string& text);
private:
ix::WebSocket _webSocket;
int _port;
bool _useHeartBeatMethod;
};
WebSocketClient::WebSocketClient(int port, bool useHeartBeatMethod)
: _port(port),
_useHeartBeatMethod(useHeartBeatMethod)
{
;
}
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
if (_useHeartBeatMethod)
{
_webSocket.setHeartBeatPeriod(1);
}
else
{
_webSocket.setPingInterval(1);
}
std::stringstream ss;
log(std::string("Connecting to url: ") + url);
_webSocket.setOnMessageCallback(
[](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");
}
else if (messageType == ix::WebSocketMessageType::Error)
{
ss << "Error ! " << error.reason;
log(ss.str());
}
else if (messageType == ix::WebSocketMessageType::Pong)
{
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)
{
// 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<int>& receivedPingMessages)
{
// A dev/null server
server.setOnConnectionCallback(
[&server, &receivedPingMessages](std::shared_ptr<ix::WebSocket> webSocket,
std::shared_ptr<ConnectionState> 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)
{
Logger() << "New server connection";
Logger() << "id: " << connectionState->getId();
Logger() << "Uri: " << openInfo.uri;
Logger() << "Headers:";
for (auto it : openInfo.headers)
{
Logger() << 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++;
}
else if (messageType == 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;
}
}
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<int> serverReceivedPingMessages(0);
REQUIRE(startServer(server, serverReceivedPingMessages));
std::string session = ix::generateSessionId();
bool useSetHeartBeatPeriodMethod = false; // so use setPingInterval
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
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<int> serverReceivedPingMessages(0);
REQUIRE(startServer(server, serverReceivedPingMessages));
std::string session = ix::generateSessionId();
bool useSetHeartBeatPeriodMethod = false; // so use setPingInterval
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
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 == 3);
// 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<int> serverReceivedPingMessages(0);
REQUIRE(startServer(server, serverReceivedPingMessages));
std::string session = ix::generateSessionId();
bool useSetHeartBeatPeriodMethod = false; // so use setPingInterval
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
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<int> serverReceivedPingMessages(0);
REQUIRE(startServer(server, serverReceivedPingMessages));
std::string session = ix::generateSessionId();
bool useSetHeartBeatPeriodMethod = false; // so use setPingInterval
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
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 == 1, 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();
}
}
// Using setHeartBeatPeriod
TEST_CASE("Websocket_ping_no_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]")
{
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<int> serverReceivedPingMessages(0);
REQUIRE(startServer(server, serverReceivedPingMessages));
std::string session = ix::generateSessionId();
bool useSetHeartBeatPeriodMethod = true;
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
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(1900);
webSocketClient.stop();
// Here we test ping interval
// -> expected ping messages == 1 as 1900 seconds, 1 ping sent every second
REQUIRE(serverReceivedPingMessages == 1);
// 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_setHeartBeatPeriod", "[setHeartBeatPeriod]")
{
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<int> serverReceivedPingMessages(0);
REQUIRE(startServer(server, serverReceivedPingMessages));
std::string session = ix::generateSessionId();
bool useSetHeartBeatPeriodMethod = true;
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
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();
}
}
+485
View File
@@ -0,0 +1,485 @@
/*
* IXWebSocketHeartBeatNoResponseAutoDisconnectTest.cpp
* Author: Alexandre Konieczny
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#include <iostream>
#include <sstream>
#include <queue>
#include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXWebSocketServer.h>
#include "IXTest.h"
#include "catch.hpp"
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<int> _receivedPongMessages;
std::atomic<bool> _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 (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<int>& receivedPingMessages, bool enablePong)
{
// A dev/null server
server.setOnConnectionCallback(
[&server, &receivedPingMessages](std::shared_ptr<ix::WebSocket> webSocket,
std::shared_ptr<ConnectionState> 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)
{
Logger() << "New server connection";
Logger() << "id: " << connectionState->getId();
Logger() << "Uri: " << openInfo.uri;
Logger() << "Headers:";
for (auto it : openInfo.headers)
{
Logger() << 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;
}
}
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<int> 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<int> 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<int> 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<int> 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<int> 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();
}
}
+7 -8
View File
@@ -50,7 +50,7 @@ namespace ix
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
{
if (messageType == ix::WebSocket_MessageType_Open)
if (messageType == ix::WebSocketMessageType::Open)
{
Logger() << "New connection";
connectionState->computeId();
@@ -64,11 +64,11 @@ namespace ix
connectionId = connectionState->getId();
}
else if (messageType == ix::WebSocket_MessageType_Close)
else if (messageType == ix::WebSocketMessageType::Close)
{
Logger() << "Closed connection";
}
else if (messageType == ix::WebSocket_MessageType_Message)
else if (messageType == ix::WebSocketMessageType::Message)
{
for (auto&& client : server.getClients())
{
@@ -107,7 +107,7 @@ TEST_CASE("Websocket_server", "[websocket_server]")
std::string errMsg;
bool tls = false;
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
std::string host("localhost");
std::string host("127.0.0.1");
auto isCancellationRequested = []() -> bool
{
return false;
@@ -141,7 +141,7 @@ TEST_CASE("Websocket_server", "[websocket_server]")
std::string errMsg;
bool tls = false;
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
std::string host("localhost");
std::string host("127.0.0.1");
auto isCancellationRequested = []() -> bool
{
return false;
@@ -178,7 +178,7 @@ TEST_CASE("Websocket_server", "[websocket_server]")
std::string errMsg;
bool tls = false;
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
std::string host("localhost");
std::string host("127.0.0.1");
auto isCancellationRequested = []() -> bool
{
return false;
@@ -204,9 +204,8 @@ TEST_CASE("Websocket_server", "[websocket_server]")
// Give us 500ms for the server to notice that clients went away
ix::msleep(500);
REQUIRE(connectionId == "foobarConnectionId");
server.stop();
REQUIRE(connectionId == "foobarConnectionId");
REQUIRE(server.getClients().size() == 0);
}
}
+59 -21
View File
@@ -60,29 +60,35 @@ namespace
const ix::WebSocketCloseInfo& closeInfo)
{
std::stringstream ss;
if (messageType == ix::WebSocket_MessageType_Open)
if (messageType == ix::WebSocketMessageType::Open)
{
log("cmd_websocket_satori_chat: connected !");
log("TestConnectionDisconnection: connected !");
}
else if (messageType == ix::WebSocket_MessageType_Close)
else if (messageType == ix::WebSocketMessageType::Close)
{
log("cmd_websocket_satori_chat: disconnected !");
log("TestConnectionDisconnection: disconnected !");
}
else if (messageType == ix::WebSocket_MessageType_Error)
else if (messageType == ix::WebSocketMessageType::Error)
{
log("cmd_websocket_satori_chat: Error!");
ss << "TestConnectionDisconnection: Error! ";
ss << error.reason;
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_Message)
else if (messageType == ix::WebSocketMessageType::Message)
{
log("cmd_websocket_satori_chat: received message.!");
log("TestConnectionDisconnection: received message.!");
}
else if (messageType == ix::WebSocket_MessageType_Ping)
else if (messageType == ix::WebSocketMessageType::Ping)
{
log("cmd_websocket_satori_chat: received ping message.!");
log("TestConnectionDisconnection: received ping message.!");
}
else if (messageType == ix::WebSocket_MessageType_Pong)
else if (messageType == ix::WebSocketMessageType::Pong)
{
log("cmd_websocket_satori_chat: received pong message.!");
log("TestConnectionDisconnection: received pong message.!");
}
else if (messageType == ix::WebSocketMessageType::Fragment)
{
log("TestConnectionDisconnection: received fragment.!");
}
else
{
@@ -90,6 +96,12 @@ namespace
}
});
_webSocket.enableAutomaticReconnection();
REQUIRE(_webSocket.isAutomaticReconnectionEnabled() == true);
_webSocket.disableAutomaticReconnection();
REQUIRE(_webSocket.isAutomaticReconnectionEnabled() == false);
// Start the connection
_webSocket.start();
}
@@ -103,26 +115,52 @@ TEST_CASE("websocket_connections", "[websocket]")
{
SECTION("Try to connect to invalid servers.")
{
IXWebSocketTestConnectionDisconnection chatA;
IXWebSocketTestConnectionDisconnection test;
chatA.start(GOOGLE_URL);
test.start(GOOGLE_URL);
ix::msleep(1000);
chatA.stop();
test.stop();
chatA.start(UNKNOWN_URL);
test.start(UNKNOWN_URL);
ix::msleep(1000);
chatA.stop();
test.stop();
}
SECTION("Try to connect and disconnect with different timing.")
SECTION("Try to connect and disconnect with different timing, not enough time to succesfully connect")
{
IXWebSocketTestConnectionDisconnection chatA;
IXWebSocketTestConnectionDisconnection test;
log(std::string("50 Runs"));
for (int i = 0; i < 50; ++i)
{
log(std::string("Run: ") + std::to_string(i));
chatA.start(WEBSOCKET_DOT_ORG_URL);
test.start(WEBSOCKET_DOT_ORG_URL);
log(std::string("Sleeping"));
ix::msleep(i);
chatA.stop();
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();
}
}
}
+14 -14
View File
@@ -87,7 +87,7 @@ namespace
bool WebSocketChat::isReady() const
{
return _webSocket.getReadyState() == ix::WebSocket_ReadyState_Open;
return _webSocket.getReadyState() == ix::ReadyState::Open;
}
void WebSocketChat::stop()
@@ -100,7 +100,7 @@ namespace
std::string url;
{
std::stringstream ss;
ss << "ws://localhost:"
ss << "ws://127.0.0.1:"
<< _port
<< "/"
<< _user;
@@ -122,21 +122,21 @@ namespace
const ix::WebSocketCloseInfo& closeInfo)
{
std::stringstream ss;
if (messageType == ix::WebSocket_MessageType_Open)
if (messageType == ix::WebSocketMessageType::Open)
{
ss << "cmd_websocket_chat: user "
<< _user
<< " Connected !";
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_Close)
else if (messageType == ix::WebSocketMessageType::Close)
{
ss << "cmd_websocket_chat: user "
<< _user
<< " disconnected !";
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_Message)
else if (messageType == ix::WebSocketMessageType::Message)
{
auto result = decodeMessage(str);
@@ -159,20 +159,20 @@ namespace
<< _user << " > ";
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_Error)
else if (messageType == ix::WebSocketMessageType::Error)
{
ss << "cmd_websocket_chat: Error ! " << error.reason;
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_Ping)
else if (messageType == ix::WebSocketMessageType::Ping)
{
log("cmd_websocket_chat: received ping message");
}
else if (messageType == ix::WebSocket_MessageType_Pong)
else if (messageType == ix::WebSocketMessageType::Pong)
{
log("cmd_websocket_chat: received pong message");
}
else if (messageType == ix::WebSocket_MessageType_Fragment)
else if (messageType == ix::WebSocketMessageType::Fragment)
{
log("cmd_websocket_chat: received message fragment");
}
@@ -228,7 +228,7 @@ namespace
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
{
if (messageType == ix::WebSocket_MessageType_Open)
if (messageType == ix::WebSocketMessageType::Open)
{
Logger() << "New connection";
Logger() << "id: " << connectionState->getId();
@@ -239,11 +239,11 @@ namespace
Logger() << it.first << ": " << it.second;
}
}
else if (messageType == ix::WebSocket_MessageType_Close)
else if (messageType == ix::WebSocketMessageType::Close)
{
log("Closed connection");
}
else if (messageType == ix::WebSocket_MessageType_Message)
else if (messageType == ix::WebSocketMessageType::Message)
{
for (auto&& client : server.getClients())
{
@@ -336,8 +336,8 @@ TEST_CASE("Websocket_chat", "[websocket_chat]")
REQUIRE(chatA.getReceivedMessages()[1] == "from B2");
REQUIRE(chatA.getReceivedMessages()[2].size() == bigMessage.size());
// Give us 500ms for the server to notice that clients went away
ix::msleep(500);
// Give us 1000ms for the server to notice that clients went away
ix::msleep(1000);
REQUIRE(server.getClients().size() == 0);
ix::reportWebSocketTraffic();
Regular → Executable
+465 -69
View File
@@ -1,10 +1,30 @@
#!/usr/bin/env python2.7
from __future__ import print_function
import os
import sys
import platform
import shutil
import subprocess
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
@@ -16,12 +36,16 @@ class Command(object):
self.cmd = cmd
self.process = None
def run_command(self, capture = False):
def run_command(self):
self.process = subprocess.Popen(self.cmd, shell=True)
self.process.communicate()
def run(self, timeout = 5 * 60):
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)
@@ -35,85 +59,457 @@ class Command(object):
return True, self.process.returncode
osName = platform.system()
print('os name = {}'.format(osName))
def runCommand(cmd, abortOnFailure=True, timeout=None):
'''Small wrapper to run a command and make sure it succeed'''
root = os.path.dirname(os.path.realpath(__file__))
buildDir = os.path.join(root, 'build', osName)
if timeout is None:
timeout = 30 * 60 # 30 minute default timeout
if not os.path.exists(buildDir):
os.makedirs(buildDir)
print('\nRunning', cmd)
command = Command(cmd)
succeed, ret = command.run(timeout)
os.chdir(buildDir)
if not succeed or ret != 0:
msg = 'cmd {}\nfailed with error code {}'.format(cmd, ret)
print(msg)
if abortOnFailure:
sys.exit(-1)
if osName == 'Windows':
generator = '-G"NMake Makefiles"'
make = 'nmake'
testBinary ='ixwebsocket_unittest.exe'
else:
generator = ''
make = 'make -j6'
testBinary ='./ixwebsocket_unittest'
sanitizersFlags = {
'asan': '-DSANITIZE_ADDRESS=On',
'ubsan': '-DSANITIZE_UNDEFINED=On',
'tsan': '-DSANITIZE_THREAD=On',
'none': ''
}
sanitizer = 'tsan'
if osName == 'Linux':
sanitizer = 'none'
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).
'''
sanitizerFlags = sanitizersFlags[sanitizer]
sanitizersFlags = {
'asan': '-DSANITIZE_ADDRESS=On',
'ubsan': '-DSANITIZE_UNDEFINED=On',
'tsan': '-DSANITIZE_THREAD=On',
'none': ''
}
sanitizerFlag = sanitizersFlags.get(sanitizer, '')
# if osName == 'Windows':
# os.environ['CC'] = 'clang-cl'
# os.environ['CXX'] = 'clang-cl'
# CMake installed via Self Service ends up here.
cmakeExecutable = '/Applications/CMake.app/Contents/bin/cmake'
if not os.path.exists(cmakeExecutable):
cmakeExecutable = 'cmake'
cmakeCmd = 'cmake -DCMAKE_BUILD_TYPE=Debug {} {} ../..'.format(generator, sanitizerFlags)
print(cmakeCmd)
ret = os.system(cmakeCmd)
assert ret == 0, 'CMake failed, exiting'
if platform.system() == 'Windows':
#generator = '"NMake Makefiles"'
#generator = '"Visual Studio 16 2019"'
generator = '"Visual Studio 15 2017"'
else:
generator = '"Unix Makefiles"'
ret = os.system(make)
assert ret == 0, 'Make failed, exiting'
CMAKE_BUILD_TYPE = BUILD_TYPE
def findFiles(prefix):
'''Find all files under a given directory'''
fmt = '{cmakeExecutable} -H. \
{sanitizerFlag} \
-B"{buildDir}" \
-DCMAKE_BUILD_TYPE={CMAKE_BUILD_TYPE} \
-DUSE_TLS=1 \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-G{generator}'
paths = []
cmakeCmd = fmt.format(**locals())
runCommand(cmakeCmd)
for root, _, files in os.walk(prefix):
for path in files:
fullPath = os.path.join(root, path)
if os.path.islink(fullPath):
continue
def runTest(args, buildDir, xmlOutput, testRunName):
'''Execute the unittest.
'''
if args is None:
args = ''
paths.append(fullPath)
testCommand = '{} -o {} -n "{}" -r junit "{}"'.format(TEST_EXE_PATH, xmlOutput, testRunName, args)
return paths
runCommand(testCommand,
abortOnFailure=False)
#for path in findFiles('.'):
# print(path)
# We need to copy the zlib DLL in the current work directory
shutil.copy(os.path.join(
'..',
'..',
'..',
'third_party',
'ZLIB-Windows',
'zlib-1.2.11_deploy_v140',
'release_dynamic',
'x64',
'bin',
'zlib.dll'), '.')
def validateTestSuite(xmlOutput):
'''
Parse the output XML file to validate that all tests passed.
# lldb = "lldb --batch -o 'run' -k 'thread backtrace all' -k 'quit 1'"
lldb = "" # Disabled for now
testCommand = '{} {} {}'.format(lldb, testBinary, os.getenv('TEST', ''))
command = Command(testCommand)
timedout, ret = command.run()
assert ret == 0, 'Test command failed'
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<pid>[0-9]+) exited with status = (?P<exitCode>[0-9]+)')
# "Process 99232 launched: '/Users/bse...
launchedPattern = re.compile('^Process (?P<pid>[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:
```
<testsuite>
<foo>
```
'''
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):
'''Main driver. Run cmake, compiles, execute and validate the testsuite.'''
# gen build files with CMake
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('--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 = os.path.join(buildDir, '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)
if __name__ == '__main__':
main()
+14 -1
View File
@@ -7,10 +7,23 @@
#define CATCH_CONFIG_RUNNER
#include "catch.hpp"
#include <ixwebsocket/IXSocket.h>
#include <spdlog/spdlog.h>
#include <ixwebsocket/IXNetSystem.h>
#include <ixcore/utils/IXCoreLogger.h>
int main(int argc, char* argv[])
{
ix::initNetSystem();
ix::IXCoreLogger::LogFunc logFunc = [](const char* msg)
{
spdlog::info(msg);
};
ix::IXCoreLogger::setLogFunction(logFunc);
int result = Catch::Session().run(argc, argv);
ix::uninitNetSystem();
return result;
}
+3 -1
View File
@@ -1 +1,3 @@
Except ZLIB on Windows (whose port is currently broken...) all dependencies here are for the ws command line tools, not for the IXWebSockets library which is standalone.
# Note
Except *zlib* on Windows, all dependencies here are for the ws command line tools, not for the IXWebSockets library which is standalone.
Loaded 100 of 538 files, more files were not shown because too many files have changed in this diff. Show more