From b1bc8aa06349c68bbf3346436000d8f853ece1a4 Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Tue, 12 Dec 2023 20:31:05 -0600 Subject: [PATCH] Added Lua modules: Copas, binaryheap, and timerwheel. --- CHANGELOG | 5 + LICENSES | 3 + assets/Menu.singe | 5 +- build-all.sh | 51 +- src/embedded.h | 16 + src/main.c | 2 +- src/singe.c | 60 +- src/videoPlayer.c | 10 - thirdparty/binaryheap.lua/.busted | 7 + thirdparty/binaryheap.lua/.editorconfig | 13 + thirdparty/binaryheap.lua/.gitignore | 2 + thirdparty/binaryheap.lua/.luacheckrc | 31 + thirdparty/binaryheap.lua/.luacov | 6 + thirdparty/binaryheap.lua/.travis.yml | 29 + thirdparty/binaryheap.lua/LICENSE | 21 + thirdparty/binaryheap.lua/README.md | 53 + .../binaryheap.lua/binaryheap-0.4-1.rockspec | 26 + thirdparty/binaryheap.lua/config.ld | 11 + thirdparty/binaryheap.lua/docs/index.html | 703 ++++++ thirdparty/binaryheap.lua/docs/ldoc.css | 291 +++ .../binaryheap.lua/docs/topics/readme.md.html | 109 + .../examples/knight_dijkstra.lua | 85 + .../binaryheap.lua/spec/binaryheap_spec.lua | 835 +++++++ .../spec/dijkstras_algorithm_spec.lua | 95 + thirdparty/binaryheap.lua/src/binaryheap.lua | 406 ++++ thirdparty/copas/.editorconfig | 14 + thirdparty/copas/.gitignore | 6 + thirdparty/copas/.luacheckrc | 31 + thirdparty/copas/.luacov | 4 + thirdparty/copas/LICENSE | 21 + thirdparty/copas/Makefile | 75 + thirdparty/copas/Makefile.win | 16 + thirdparty/copas/README.md | 36 + thirdparty/copas/bin/copas.lua | 85 + thirdparty/copas/copas-cvs-6.rockspec | 56 + thirdparty/copas/docs/copas.png | 3 + thirdparty/copas/docs/doc.css | 209 ++ thirdparty/copas/docs/index.html | 395 ++++ thirdparty/copas/docs/license.html | 100 + thirdparty/copas/docs/manual.html | 527 +++++ thirdparty/copas/docs/reference.html | 1003 ++++++++ thirdparty/copas/misc/cosocket.lua | 44 + thirdparty/copas/misc/echoserver.lua | 24 + thirdparty/copas/misc/testasyncspeed.lua | 58 + .../copas/rockspec/copas-1.1.2-1.rockspec | 30 + .../copas/rockspec/copas-1.1.3-1.rockspec | 31 + .../copas/rockspec/copas-1.1.4-1.rockspec | 28 + .../copas/rockspec/copas-1.1.5-1.rockspec | 28 + .../copas/rockspec/copas-1.1.6-1.rockspec | 28 + .../copas/rockspec/copas-1.2.0-1.rockspec | 29 + .../copas/rockspec/copas-1.2.1-1.rockspec | 29 + .../copas/rockspec/copas-2.0.0-1.rockspec | 35 + .../copas/rockspec/copas-2.0.0-2.rockspec | 35 + .../copas/rockspec/copas-2.0.1-1.rockspec | 35 + .../copas/rockspec/copas-2.0.2-1.rockspec | 35 + .../copas/rockspec/copas-3.0.0-1.rockspec | 51 + .../copas/rockspec/copas-3.0.0-2.rockspec | 51 + .../copas/rockspec/copas-3.0.0-3.rockspec | 51 + .../copas/rockspec/copas-4.0.0-1.rockspec | 51 + .../copas/rockspec/copas-4.1.0-1.rockspec | 51 + .../copas/rockspec/copas-4.2.0-1.rockspec | 51 + .../copas/rockspec/copas-4.3.0-1.rockspec | 51 + .../copas/rockspec/copas-4.3.1-1.rockspec | 51 + .../copas/rockspec/copas-4.3.2-1.rockspec | 51 + .../copas/rockspec/copas-4.4.0-1.rockspec | 51 + .../copas/rockspec/copas-4.5.0-1.rockspec | 51 + .../copas/rockspec/copas-4.6.0-1.rockspec | 51 + .../copas/rockspec/copas-4.7.0-1.rockspec | 56 + thirdparty/copas/src/copas.lua | 2008 +++++++++++++++++ thirdparty/copas/src/copas/ftp.lua | 95 + thirdparty/copas/src/copas/http.lua | 459 ++++ thirdparty/copas/src/copas/lock.lua | 191 ++ thirdparty/copas/src/copas/queue.lua | 191 ++ thirdparty/copas/src/copas/semaphore.lua | 202 ++ thirdparty/copas/src/copas/smtp.lua | 34 + thirdparty/copas/src/copas/timer.lua | 130 ++ thirdparty/copas/tests/certs/_readme.md | 3 + thirdparty/copas/tests/certs/all.bat | 14 + thirdparty/copas/tests/certs/all.sh | 13 + thirdparty/copas/tests/certs/clientA.bat | 9 + thirdparty/copas/tests/certs/clientA.cnf | 316 +++ thirdparty/copas/tests/certs/clientA.sh | 12 + thirdparty/copas/tests/certs/clientB.bat | 9 + thirdparty/copas/tests/certs/clientB.cnf | 316 +++ thirdparty/copas/tests/certs/clientB.sh | 12 + thirdparty/copas/tests/certs/rootA.bat | 7 + thirdparty/copas/tests/certs/rootA.cnf | 315 +++ thirdparty/copas/tests/certs/rootA.sh | 7 + thirdparty/copas/tests/certs/rootB.bat | 7 + thirdparty/copas/tests/certs/rootB.cnf | 315 +++ thirdparty/copas/tests/certs/rootB.sh | 7 + thirdparty/copas/tests/certs/serverA.bat | 9 + thirdparty/copas/tests/certs/serverA.cnf | 316 +++ thirdparty/copas/tests/certs/serverA.sh | 12 + thirdparty/copas/tests/certs/serverB.bat | 9 + thirdparty/copas/tests/certs/serverB.cnf | 316 +++ thirdparty/copas/tests/certs/serverB.sh | 12 + thirdparty/copas/tests/close.lua | 75 + thirdparty/copas/tests/connecttwice.lua | 36 + thirdparty/copas/tests/errhandlers.lua | 160 ++ thirdparty/copas/tests/exit.lua | 65 + thirdparty/copas/tests/exittest.lua | 74 + thirdparty/copas/tests/http-timeout.lua | 331 +++ thirdparty/copas/tests/httpredirect.lua | 91 + thirdparty/copas/tests/largetransfer.lua | 118 + thirdparty/copas/tests/lock.lua | 107 + thirdparty/copas/tests/loop_starter.lua | 13 + thirdparty/copas/tests/pause.lua | 30 + thirdparty/copas/tests/queue.lua | 138 ++ thirdparty/copas/tests/removeserver.lua | 40 + thirdparty/copas/tests/removethread.lua | 66 + thirdparty/copas/tests/request.lua | 66 + thirdparty/copas/tests/semaphore.lua | 146 ++ thirdparty/copas/tests/starve.lua | 87 + thirdparty/copas/tests/tcptimeout.lua | 169 ++ thirdparty/copas/tests/timeout_errors.lua | 50 + thirdparty/copas/tests/timer.lua | 127 ++ thirdparty/copas/tests/tls-sni.lua | 106 + thirdparty/copas/tests/udptimeout.lua | 107 + thirdparty/timerwheel.lua/.busted | 7 + thirdparty/timerwheel.lua/.editorconfig | 14 + thirdparty/timerwheel.lua/.gitignore | 3 + thirdparty/timerwheel.lua/.luacheckrc | 32 + thirdparty/timerwheel.lua/.luacov | 6 + thirdparty/timerwheel.lua/LICENSE | 22 + thirdparty/timerwheel.lua/README.md | 90 + thirdparty/timerwheel.lua/config.ld | 10 + thirdparty/timerwheel.lua/docs/index.html | 313 +++ thirdparty/timerwheel.lua/docs/ldoc.css | 291 +++ .../timerwheel.lua/docs/topics/readme.md.html | 165 ++ .../rockspecs/timerwheel-0.1.0-1.rockspec | 27 + .../rockspecs/timerwheel-0.1.0-2.rockspec | 28 + .../rockspecs/timerwheel-0.2.0-1.rockspec | 28 + .../rockspecs/timerwheel-0.2.0-2.rockspec | 33 + .../rockspecs/timerwheel-1.0.0-1.rockspec | 36 + .../rockspecs/timerwheel-1.0.1-1.rockspec | 36 + .../rockspecs/timerwheel-1.0.2-1.rockspec | 36 + .../timerwheel.lua/spec/timerwheel_spec.lua | 460 ++++ .../src/timerwheel/timerwheel.lua | 307 +++ .../timerwheel.lua/timerwheel-scm-1.rockspec | 36 + 140 files changed, 16071 insertions(+), 50 deletions(-) create mode 100644 thirdparty/binaryheap.lua/.busted create mode 100644 thirdparty/binaryheap.lua/.editorconfig create mode 100644 thirdparty/binaryheap.lua/.gitignore create mode 100644 thirdparty/binaryheap.lua/.luacheckrc create mode 100644 thirdparty/binaryheap.lua/.luacov create mode 100644 thirdparty/binaryheap.lua/.travis.yml create mode 100644 thirdparty/binaryheap.lua/LICENSE create mode 100644 thirdparty/binaryheap.lua/README.md create mode 100644 thirdparty/binaryheap.lua/binaryheap-0.4-1.rockspec create mode 100644 thirdparty/binaryheap.lua/config.ld create mode 100644 thirdparty/binaryheap.lua/docs/index.html create mode 100644 thirdparty/binaryheap.lua/docs/ldoc.css create mode 100644 thirdparty/binaryheap.lua/docs/topics/readme.md.html create mode 100644 thirdparty/binaryheap.lua/examples/knight_dijkstra.lua create mode 100644 thirdparty/binaryheap.lua/spec/binaryheap_spec.lua create mode 100644 thirdparty/binaryheap.lua/spec/dijkstras_algorithm_spec.lua create mode 100644 thirdparty/binaryheap.lua/src/binaryheap.lua create mode 100644 thirdparty/copas/.editorconfig create mode 100644 thirdparty/copas/.gitignore create mode 100644 thirdparty/copas/.luacheckrc create mode 100644 thirdparty/copas/.luacov create mode 100644 thirdparty/copas/LICENSE create mode 100644 thirdparty/copas/Makefile create mode 100644 thirdparty/copas/Makefile.win create mode 100644 thirdparty/copas/README.md create mode 100755 thirdparty/copas/bin/copas.lua create mode 100644 thirdparty/copas/copas-cvs-6.rockspec create mode 100644 thirdparty/copas/docs/copas.png create mode 100644 thirdparty/copas/docs/doc.css create mode 100644 thirdparty/copas/docs/index.html create mode 100644 thirdparty/copas/docs/license.html create mode 100644 thirdparty/copas/docs/manual.html create mode 100644 thirdparty/copas/docs/reference.html create mode 100644 thirdparty/copas/misc/cosocket.lua create mode 100644 thirdparty/copas/misc/echoserver.lua create mode 100644 thirdparty/copas/misc/testasyncspeed.lua create mode 100644 thirdparty/copas/rockspec/copas-1.1.2-1.rockspec create mode 100644 thirdparty/copas/rockspec/copas-1.1.3-1.rockspec create mode 100644 thirdparty/copas/rockspec/copas-1.1.4-1.rockspec create mode 100644 thirdparty/copas/rockspec/copas-1.1.5-1.rockspec create mode 100644 thirdparty/copas/rockspec/copas-1.1.6-1.rockspec create mode 100644 thirdparty/copas/rockspec/copas-1.2.0-1.rockspec create mode 100644 thirdparty/copas/rockspec/copas-1.2.1-1.rockspec create mode 100644 thirdparty/copas/rockspec/copas-2.0.0-1.rockspec create mode 100644 thirdparty/copas/rockspec/copas-2.0.0-2.rockspec create mode 100644 thirdparty/copas/rockspec/copas-2.0.1-1.rockspec create mode 100644 thirdparty/copas/rockspec/copas-2.0.2-1.rockspec create mode 100644 thirdparty/copas/rockspec/copas-3.0.0-1.rockspec create mode 100644 thirdparty/copas/rockspec/copas-3.0.0-2.rockspec create mode 100644 thirdparty/copas/rockspec/copas-3.0.0-3.rockspec create mode 100644 thirdparty/copas/rockspec/copas-4.0.0-1.rockspec create mode 100644 thirdparty/copas/rockspec/copas-4.1.0-1.rockspec create mode 100644 thirdparty/copas/rockspec/copas-4.2.0-1.rockspec create mode 100644 thirdparty/copas/rockspec/copas-4.3.0-1.rockspec create mode 100644 thirdparty/copas/rockspec/copas-4.3.1-1.rockspec create mode 100644 thirdparty/copas/rockspec/copas-4.3.2-1.rockspec create mode 100644 thirdparty/copas/rockspec/copas-4.4.0-1.rockspec create mode 100644 thirdparty/copas/rockspec/copas-4.5.0-1.rockspec create mode 100644 thirdparty/copas/rockspec/copas-4.6.0-1.rockspec create mode 100644 thirdparty/copas/rockspec/copas-4.7.0-1.rockspec create mode 100644 thirdparty/copas/src/copas.lua create mode 100644 thirdparty/copas/src/copas/ftp.lua create mode 100644 thirdparty/copas/src/copas/http.lua create mode 100644 thirdparty/copas/src/copas/lock.lua create mode 100644 thirdparty/copas/src/copas/queue.lua create mode 100644 thirdparty/copas/src/copas/semaphore.lua create mode 100644 thirdparty/copas/src/copas/smtp.lua create mode 100644 thirdparty/copas/src/copas/timer.lua create mode 100644 thirdparty/copas/tests/certs/_readme.md create mode 100644 thirdparty/copas/tests/certs/all.bat create mode 100755 thirdparty/copas/tests/certs/all.sh create mode 100644 thirdparty/copas/tests/certs/clientA.bat create mode 100644 thirdparty/copas/tests/certs/clientA.cnf create mode 100755 thirdparty/copas/tests/certs/clientA.sh create mode 100644 thirdparty/copas/tests/certs/clientB.bat create mode 100644 thirdparty/copas/tests/certs/clientB.cnf create mode 100755 thirdparty/copas/tests/certs/clientB.sh create mode 100644 thirdparty/copas/tests/certs/rootA.bat create mode 100644 thirdparty/copas/tests/certs/rootA.cnf create mode 100755 thirdparty/copas/tests/certs/rootA.sh create mode 100644 thirdparty/copas/tests/certs/rootB.bat create mode 100644 thirdparty/copas/tests/certs/rootB.cnf create mode 100755 thirdparty/copas/tests/certs/rootB.sh create mode 100644 thirdparty/copas/tests/certs/serverA.bat create mode 100644 thirdparty/copas/tests/certs/serverA.cnf create mode 100755 thirdparty/copas/tests/certs/serverA.sh create mode 100644 thirdparty/copas/tests/certs/serverB.bat create mode 100644 thirdparty/copas/tests/certs/serverB.cnf create mode 100755 thirdparty/copas/tests/certs/serverB.sh create mode 100644 thirdparty/copas/tests/close.lua create mode 100644 thirdparty/copas/tests/connecttwice.lua create mode 100644 thirdparty/copas/tests/errhandlers.lua create mode 100644 thirdparty/copas/tests/exit.lua create mode 100644 thirdparty/copas/tests/exittest.lua create mode 100644 thirdparty/copas/tests/http-timeout.lua create mode 100644 thirdparty/copas/tests/httpredirect.lua create mode 100644 thirdparty/copas/tests/largetransfer.lua create mode 100644 thirdparty/copas/tests/lock.lua create mode 100644 thirdparty/copas/tests/loop_starter.lua create mode 100644 thirdparty/copas/tests/pause.lua create mode 100644 thirdparty/copas/tests/queue.lua create mode 100644 thirdparty/copas/tests/removeserver.lua create mode 100644 thirdparty/copas/tests/removethread.lua create mode 100644 thirdparty/copas/tests/request.lua create mode 100644 thirdparty/copas/tests/semaphore.lua create mode 100644 thirdparty/copas/tests/starve.lua create mode 100644 thirdparty/copas/tests/tcptimeout.lua create mode 100644 thirdparty/copas/tests/timeout_errors.lua create mode 100644 thirdparty/copas/tests/timer.lua create mode 100644 thirdparty/copas/tests/tls-sni.lua create mode 100644 thirdparty/copas/tests/udptimeout.lua create mode 100644 thirdparty/timerwheel.lua/.busted create mode 100644 thirdparty/timerwheel.lua/.editorconfig create mode 100644 thirdparty/timerwheel.lua/.gitignore create mode 100644 thirdparty/timerwheel.lua/.luacheckrc create mode 100644 thirdparty/timerwheel.lua/.luacov create mode 100644 thirdparty/timerwheel.lua/LICENSE create mode 100644 thirdparty/timerwheel.lua/README.md create mode 100644 thirdparty/timerwheel.lua/config.ld create mode 100644 thirdparty/timerwheel.lua/docs/index.html create mode 100644 thirdparty/timerwheel.lua/docs/ldoc.css create mode 100644 thirdparty/timerwheel.lua/docs/topics/readme.md.html create mode 100644 thirdparty/timerwheel.lua/rockspecs/timerwheel-0.1.0-1.rockspec create mode 100644 thirdparty/timerwheel.lua/rockspecs/timerwheel-0.1.0-2.rockspec create mode 100644 thirdparty/timerwheel.lua/rockspecs/timerwheel-0.2.0-1.rockspec create mode 100644 thirdparty/timerwheel.lua/rockspecs/timerwheel-0.2.0-2.rockspec create mode 100644 thirdparty/timerwheel.lua/rockspecs/timerwheel-1.0.0-1.rockspec create mode 100644 thirdparty/timerwheel.lua/rockspecs/timerwheel-1.0.1-1.rockspec create mode 100644 thirdparty/timerwheel.lua/rockspecs/timerwheel-1.0.2-1.rockspec create mode 100644 thirdparty/timerwheel.lua/spec/timerwheel_spec.lua create mode 100644 thirdparty/timerwheel.lua/src/timerwheel/timerwheel.lua create mode 100644 thirdparty/timerwheel.lua/timerwheel-scm-1.rockspec diff --git a/CHANGELOG b/CHANGELOG index b6b240221..3e683e70e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -38,6 +38,9 @@ New Features - LuaSocket - LuaSec - LuaRS232 + - timerwheel + - binaryheap + - Copas - New command line option: -p (or --program). This is similar to the existing -t (or --trace) option. Where "trace" displays and logs script @@ -68,6 +71,8 @@ Fixes - PNG files no longer generate warnings on the console. +- Non-VLDP videos now honor command line volume settings. + - Menu system now performs "natural sorting" of game titles instead of brain dead "computer sorting". It should make a lot more sense paging through games now. diff --git a/LICENSES b/LICENSES index 1fe544ba7..45cd354f8 100644 --- a/LICENSES +++ b/LICENSES @@ -4,7 +4,9 @@ these tools, Singe would not exist. arg_parser BSD-2-Clause http://savannah.nongnu.org/projects/arg-parser +binaryheap.lua MIT http://tieske.github.io/binaryheap.lua bzip2 bzip2-1.0.6 https://sourceware.org/bzip2 +copas MIT https://lunarmodules.github.io/copas ffmpeg LGPL-2.1 https://ffmpeg.org ffms2 GPL-3.0-only https://github.com/FFMS/ffms2 jbigkit GPL-2.0 https://www.cl.cam.ac.uk/~mgk25/jbigkit @@ -21,6 +23,7 @@ SDL2_gfx LGPL-2.1 https://github.com/ferzkopp/SDL_gfx SDL2_image Zlib https://www.libsdl.org SDL2_mixer Zlib https://www.libsdl.org SDL2_ttf Zlib https://www.libsdl.org +timerwheel MIT https://tieske.github.io/timerwheel.lua uthash BSD-1-Clause https://troydhanson.github.io/uthash vlc GPL-2.0 https://www.videolan.org/vlc xz Public-Domain https://github.com/tukaani-project/xz diff --git a/assets/Menu.singe b/assets/Menu.singe index 47592d391..9a76abf80 100644 --- a/assets/Menu.singe +++ b/assets/Menu.singe @@ -48,9 +48,12 @@ function loadGameAssets() SPRITE_CABINET = spriteLoad(GAME_LIST[GAME_SELECTED].CABINET) SPRITE_MARQUEE = spriteLoad(GAME_LIST[GAME_SELECTED].MARQUEE) VIDEO_ATTRACT = videoLoad(GAME_LIST[GAME_SELECTED].ATTRACT) + if GAME_LIST[GAME_SELECTED].AUDIO_TRACK then + videoSetAudioTrack(VIDEO_ATTRACT, GAME_LIST[GAME_SELECTED].AUDIO_TRACK) + end videoPlay(VIDEO_ATTRACT) videoSeek(VIDEO_ATTRACT, GAME_LIST[GAME_SELECTED].ATTRACT_START) - videoSetVolume(VIDEO_ATTRACT, 0, 0) + -- videoSetVolume(VIDEO_ATTRACT, 0, 0) -- Build text sprites local textBox = GAME_LIST[GAME_SELECTED].DESCRIPTION .. WRAP_BREAK .. WRAP_BREAK .. diff --git a/build-all.sh b/build-all.sh index 640706eac..871a60823 100755 --- a/build-all.sh +++ b/build-all.sh @@ -304,15 +304,6 @@ if [[ 0 == 1 ]]; then # === "Indexing" Text === createEmbeddedImage indexing - # === Singe Framework === - createEmbeddedBinary assets/Framework.singe ${G_GENERATED}/Framework_singe.h FRAMEWORK_SINGE_H - - # === Default Config === - createEmbeddedBinary assets/controls.cfg ${G_GENERATED}/controls_cfg.h CONTROLS_CFG_H - - # === Singe Menu App === - createEmbeddedBinary assets/Menu.singe ${G_GENERATED}/Menu_singe.h MENU_SINGE_H - # === Singe Menu Font === createEmbeddedBinary assets/FreeSansBold.ttf ${G_GENERATED}/FreeSansBold_ttf.h FREESANSBOLD_TTF_H @@ -342,6 +333,32 @@ if [[ 0 == 1 ]]; then # === LuaRS232 === createEmbeddedBinary thirdparty/librs232/bindings/lua/rs232.lua ${G_GENERATED}/rs232_lua.h RS232_LUA_H + # === Copas === + createEmbeddedBinary thirdparty/copas/src/copas.lua ${G_GENERATED}/copas_lua.h COPAS_LUA_H + createEmbeddedBinary thirdparty/copas/src/copas/ftp.lua ${G_GENERATED}/copas_ftp_lua.h COPAS_FTP_LUA_H copas + createEmbeddedBinary thirdparty/copas/src/copas/http.lua ${G_GENERATED}/copas_http_lua.h COPAS_HTTP_LUA_H copas + createEmbeddedBinary thirdparty/copas/src/copas/smtp.lua ${G_GENERATED}/copas_smtp_lua.h COPAS_SMTP_LUA_H copas + createEmbeddedBinary thirdparty/copas/src/copas/lock.lua ${G_GENERATED}/copas_lock_lua.h COPAS_LOCK_LUA_H copas + createEmbeddedBinary thirdparty/copas/src/copas/queue.lua ${G_GENERATED}/copas_queue_lua.h COPAS_QUEUE_LUA_H copas + createEmbeddedBinary thirdparty/copas/src/copas/semaphore.lua ${G_GENERATED}/copas_semaphore_lua.h COPAS_SEMAPHORE_LUA_H copas + createEmbeddedBinary thirdparty/copas/src/copas/timer.lua ${G_GENERATED}/copas_timer_lua.h COPAS_TIMER_LUA_H copas + + # === binaryheap.lua === + createEmbeddedBinary thirdparty/binaryheap.lua/src/binaryheap.lua ${G_GENERATED}/binaryheap_lua.h BINARYHEAP_LUA_H + + # === binaryheap.lua === + createEmbeddedBinary thirdparty/timerwheel.lua/src/timerwheel/timerwheel.lua ${G_GENERATED}/timerwheel_lua.h TIMERWHEEL_LUA_H +fi + + # === Singe Framework === + createEmbeddedBinary assets/Framework.singe ${G_GENERATED}/Framework_singe.h FRAMEWORK_SINGE_H + + # === Default Config === + createEmbeddedBinary assets/controls.cfg ${G_GENERATED}/controls_cfg.h CONTROLS_CFG_H + + # === Singe Menu App === + createEmbeddedBinary assets/Menu.singe ${G_GENERATED}/Menu_singe.h MENU_SINGE_H + :<> ${SOURCEFILE} popd + if [[ ! -z ${PREFIX} ]]; then + PREFIX=${PREFIX}_ + sed -i "s/unsigned char /unsigned char ${PREFIX}/" ${SOURCEFILE} + sed -i "s/unsigned int /unsigned int ${PREFIX}/" ${SOURCEFILE} + fi printf "\n#else // EMBED_HERE\n\n" >> ${SOURCEFILE} - printf "extern unsigned char ${FILENAME/\./_}[];\n" >> ${SOURCEFILE} - printf "extern unsigned int ${FILENAME/\./_}_len;\n" >> ${SOURCEFILE} + printf "extern unsigned char ${PREFIX}${FILENAME/\./_}[];\n" >> ${SOURCEFILE} + printf "extern unsigned int ${PREFIX}${FILENAME/\./_}_len;\n" >> ${SOURCEFILE} printf "\n#endif // EMBED_HERE\n\n" >> ${SOURCEFILE} outputFooter ${BLOCKER} >> ${SOURCEFILE} } @@ -469,7 +491,7 @@ function outputLicense() { * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 + * as published by the Free Software Foundation; either version 3 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, @@ -479,7 +501,8 @@ function outputLicense() { * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. * */ LICENSE diff --git a/src/embedded.h b/src/embedded.h index 443ef04dc..783f8bc9e 100644 --- a/src/embedded.h +++ b/src/embedded.h @@ -58,5 +58,21 @@ // LuaRS232 #include "generated/rs232_lua.h" +// binaryheap +#include "generated/binaryheap_lua.h" + +// timerwheel +#include "generated/timerwheel_lua.h" + +// Copas +#include "generated/copas_lua.h" +#include "generated/copas_ftp_lua.h" +#include "generated/copas_http_lua.h" +#include "generated/copas_lock_lua.h" +#include "generated/copas_queue_lua.h" +#include "generated/copas_semaphore_lua.h" +#include "generated/copas_smtp_lua.h" +#include "generated/copas_timer_lua.h" + #endif // EMBEDDED_H diff --git a/src/main.c b/src/main.c index 31522870e..b4ffa7555 100644 --- a/src/main.c +++ b/src/main.c @@ -1012,7 +1012,7 @@ void unpackGames(void) { // Unpack it! if (ok) { - utilSay(">>> Unpacking %s: %s", types[packageType], de->d_name); + utilSay(">>> Installing %s: %s", types[packageType], de->d_name); // https://github.com/libarchive/libarchive/wiki/Examples#user-content-A_Complete_Extractor flags = ARCHIVE_EXTRACT_TIME; flags |= ARCHIVE_EXTRACT_PERM; diff --git a/src/singe.c b/src/singe.c index c4399a8e2..a7481b171 100644 --- a/src/singe.c +++ b/src/singe.c @@ -279,34 +279,47 @@ GlobalT _global; // Lua Modules static const luaModuleT luaModules[] = { // LuaFileSystem - MODC("lfs", luaopen_lfs), + MODC("lfs", luaopen_lfs), // LuaSocket - MODC("mime.core", luaopen_mime_core), - MODC("socket.core", luaopen_socket_core), - MODL("ltn12", ltn12_lua), - MODL("mbox", mbox_lua), - MODL("mime", mime_lua), - MODL("socket", socket_lua), - MODL("socket.ftp", ftp_lua), - MODL("socket.headers", headers_lua), - MODL("socket.http", http_lua), - MODL("socket.smtp", smtp_lua), - MODL("socket.tp", tp_lua), - MODL("socket.url", url_lua), + MODC("mime.core", luaopen_mime_core), + MODC("socket.core", luaopen_socket_core), + MODL("ltn12", ltn12_lua), + MODL("mbox", mbox_lua), + MODL("mime", mime_lua), + MODL("socket", socket_lua), + MODL("socket.ftp", ftp_lua), + MODL("socket.headers", headers_lua), + MODL("socket.http", http_lua), + MODL("socket.smtp", smtp_lua), + MODL("socket.tp", tp_lua), + MODL("socket.url", url_lua), #ifndef _WIN32 - MODC("socket.unix", luaopen_socket_unix), - MODC("socket.serial", luaopen_socket_serial), + MODC("socket.unix", luaopen_socket_unix), + MODC("socket.serial", luaopen_socket_serial), #endif // LuaSec - MODC("ssl.core", luaopen_ssl_core), - MODC("ssl.context", luaopen_ssl_context), - MODC("ssl.x509", luaopen_ssl_x509), - MODC("ssl.config", luaopen_ssl_config), - MODL("ssl.https", https_lua), - MODL("ssl", ssl_lua), + MODC("ssl.core", luaopen_ssl_core), + MODC("ssl.context", luaopen_ssl_context), + MODC("ssl.x509", luaopen_ssl_x509), + MODC("ssl.config", luaopen_ssl_config), + MODL("ssl.https", https_lua), + MODL("ssl", ssl_lua), // LuaRS232 - MODC("rs232.core", luaopen_luars232), - MODL("rs232", rs232_lua), + MODC("rs232.core", luaopen_luars232), + MODL("rs232", rs232_lua), + // binaryheap + MODL("binaryheap", binaryheap_lua), + // timerwheel + MODL("timerwheel", timerwheel_lua), + // Copas + MODL("copas", copas_lua), + MODL("copas.ftp", copas_ftp_lua), + MODL("copas.http", copas_http_lua), + MODL("copas.lock", copas_lock_lua), + MODL("copas.queue", copas_queue_lua), + MODL("copas.semaphore", copas_semaphore_lua), + MODL("copas.smtp", copas_smtp_lua), + MODL("copas.timer", copas_timer_lua), }; @@ -2808,7 +2821,6 @@ int32_t apiVideoGetLanguageDescription(lua_State *L) { if (lua_isstring(L, 1)) { c = (char *)lua_tostring(L, 1); r = videoGetLanguageDescription(c); -// utilSay("[%s] [%s]", c, r); } } diff --git a/src/videoPlayer.c b/src/videoPlayer.c index 864df641e..9954ef806 100644 --- a/src/videoPlayer.c +++ b/src/videoPlayer.c @@ -292,7 +292,6 @@ int32_t _loadVideoAndAudio(char *vFilename, char *aFilename, char *indexPath, bo while ((tag = (AVDictionaryEntry *)av_dict_iterate(fmt_ctx->streams[x]->metadata, tag))) { if (utilStricmp("language", tag->key) == 0) { v->audio[count++].language = strdup(tag->value); - utilSay("Track %d is %s", x, tag->value); break; } } @@ -502,15 +501,6 @@ char *videoGetLanguageDescription(char *languageCode) { int32_t i = 0; while (p_languages[i].psz_eng_name != NULL) { - /* - utilSay("[%s] [%s] [%s] [%s] [%s]", - languageCode, - p_languages[i].psz_eng_name, - p_languages[i].psz_iso639_1, - p_languages[i].psz_iso639_2T, - p_languages[i].psz_iso639_2B - ); - */ if ((utilStricmp(languageCode, (char *)p_languages[i].psz_iso639_1) == 0) || (utilStricmp(languageCode, (char *)p_languages[i].psz_iso639_2T) == 0) || (utilStricmp(languageCode, (char *)p_languages[i].psz_iso639_2B) == 0)) { diff --git a/thirdparty/binaryheap.lua/.busted b/thirdparty/binaryheap.lua/.busted new file mode 100644 index 000000000..313c185cd --- /dev/null +++ b/thirdparty/binaryheap.lua/.busted @@ -0,0 +1,7 @@ +return { + default = { + verbose = true, + coverage = true, + output = "gtest", + }, +} diff --git a/thirdparty/binaryheap.lua/.editorconfig b/thirdparty/binaryheap.lua/.editorconfig new file mode 100644 index 000000000..dac40a4bf --- /dev/null +++ b/thirdparty/binaryheap.lua/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 + +[*.lua] +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab diff --git a/thirdparty/binaryheap.lua/.gitignore b/thirdparty/binaryheap.lua/.gitignore new file mode 100644 index 000000000..4501f8793 --- /dev/null +++ b/thirdparty/binaryheap.lua/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +luacov.* diff --git a/thirdparty/binaryheap.lua/.luacheckrc b/thirdparty/binaryheap.lua/.luacheckrc new file mode 100644 index 000000000..daee25997 --- /dev/null +++ b/thirdparty/binaryheap.lua/.luacheckrc @@ -0,0 +1,31 @@ +std = "ngx_lua+busted" +unused_args = false +redefined = false +max_line_length = false + + +globals = { + --"_KONG", + --"kong", + --"ngx.IS_CLI", +} + + +not_globals = { + "string.len", + "table.getn", +} + + +ignore = { + --"6.", -- ignore whitespace warnings + "211/_ENV", -- unused variable +} + + +exclude_files = { + "here/**" + --"spec/fixtures/invalid-module.lua", + --"spec-old-api/fixtures/invalid-module.lua", +} + diff --git a/thirdparty/binaryheap.lua/.luacov b/thirdparty/binaryheap.lua/.luacov new file mode 100644 index 000000000..ede5a299e --- /dev/null +++ b/thirdparty/binaryheap.lua/.luacov @@ -0,0 +1,6 @@ +modules = { + ["binaryheap"] = "src/binaryheap.lua", + ["binaryheap.*"] = "src" +} +runreport = true +deletestats = false -- file still needed to push to coveralls diff --git a/thirdparty/binaryheap.lua/.travis.yml b/thirdparty/binaryheap.lua/.travis.yml new file mode 100644 index 000000000..41f057f20 --- /dev/null +++ b/thirdparty/binaryheap.lua/.travis.yml @@ -0,0 +1,29 @@ +language: python +sudo: false + +env: + - LUA="lua 5.1" + - LUA="lua 5.2" + - LUA="lua 5.3" + - LUA="luajit 2.0" + - LUA="luajit 2.0 --compat 5.2" + - LUA="luajit 2.1" + - LUA="luajit 2.1 --compat 5.2" + +before_install: + - pip install hererocks + - hererocks here -r^ --$LUA + - source here/bin/activate + - luarocks install luacheck + - luarocks install busted + - luarocks install luacov-coveralls + +install: + - luarocks make + +script: + - luacheck . + - busted + +after_success: + - luacov-coveralls diff --git a/thirdparty/binaryheap.lua/LICENSE b/thirdparty/binaryheap.lua/LICENSE new file mode 100644 index 000000000..36c451d1a --- /dev/null +++ b/thirdparty/binaryheap.lua/LICENSE @@ -0,0 +1,21 @@ +Copyright © 2015-2019 Thijs Schreijer. + +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. diff --git a/thirdparty/binaryheap.lua/README.md b/thirdparty/binaryheap.lua/README.md new file mode 100644 index 000000000..f8204c9e8 --- /dev/null +++ b/thirdparty/binaryheap.lua/README.md @@ -0,0 +1,53 @@ +[![Build Status](https://travis-ci.com/Tieske/binaryheap.lua.svg?branch=master)](https://travis-ci.com/Tieske/binaryheap.lua) +[![Coverage Status](https://coveralls.io/repos/github/Tieske/binaryheap.lua/badge.svg?branch=master)](https://coveralls.io/github/Tieske/binaryheap.lua?branch=master) + +binaryheap.lua +============== + +[Binary heap](http://en.wikipedia.org/wiki/Binary_heap) implementation + +Both the [source code](https://github.com/Tieske/binaryheap.lua) as well as the +[documentation](http://tieske.github.io/binaryheap.lua) are on github + +Based on [original code](http://lua-users.org/lists/lua-l/2015-04/msg00137.html) +by Oliver Kroth, with +[extras](http://lua-users.org/lists/lua-l/2015-04/msg00133.html) +as proposed by Sean Conner. + +Contributions +============= +This library was create by contributions from Oliver Kroth, +Thijs Schreijer, Boris Nagaev + +History +======= + +Version 0.4, 7-Nov-2018 + + - [breaking] added additional tests, mostly on returning errors, minor behaviour changes + - added `size` method + - fixed a lot of linter issues + +Version 0.3, 15-Jul-2018 + + - bugfix `unique:pop` returning wrong order results (by Daurnimator) + - change `unique:peek` returning same order as `pop` + - added `unique:peekValue` returning just the value + +Version 0.2, 21-Apr-2015 + + - bugfix `remove` function (by Boris Nagaev) + - configurable comparison function for the tree + +Version 0.1, 20-Apr-2015 + + - Initial release + + +Copyright +========= +Copyright 2015-2019 Thijs Schreijer + +License +======= +MIT/X11 diff --git a/thirdparty/binaryheap.lua/binaryheap-0.4-1.rockspec b/thirdparty/binaryheap.lua/binaryheap-0.4-1.rockspec new file mode 100644 index 000000000..809629a0d --- /dev/null +++ b/thirdparty/binaryheap.lua/binaryheap-0.4-1.rockspec @@ -0,0 +1,26 @@ +package = "binaryheap" +version = "0.4-1" + +source = { + url = "https://github.com/Tieske/binaryheap.lua/archive/version_0v4.tar.gz", + dir = "binaryheap.lua-version_0v4" +} + +description = { + summary = "Binary heap implementation in pure Lua", + detailed = [[ + Binary heaps are an efficient sorting algorithm. This module + implements a plain binary heap (without reverse lookup) and a + 'unique' binary heap (with unique payloads and reverse lookup). + ]], + license = "MIT/X11", + homepage = "https://github.com/Tieske/binaryheap.lua" +} +dependencies = { + "lua >= 5.1", +} + +build = { + type = "builtin", + modules = { binaryheap = "src/binaryheap.lua" } +} diff --git a/thirdparty/binaryheap.lua/config.ld b/thirdparty/binaryheap.lua/config.ld new file mode 100644 index 000000000..0af92ba19 --- /dev/null +++ b/thirdparty/binaryheap.lua/config.ld @@ -0,0 +1,11 @@ +project='binaryheap.lua' +title='binaryheap' +description='Module to create binary heaps' +format='markdown' +file='./src/' +dir='docs' +readme='readme.md' +sort=true +sort_modules=true +all=false +style='./docs/' diff --git a/thirdparty/binaryheap.lua/docs/index.html b/thirdparty/binaryheap.lua/docs/index.html new file mode 100644 index 000000000..1536bf593 --- /dev/null +++ b/thirdparty/binaryheap.lua/docs/index.html @@ -0,0 +1,703 @@ + + + + + binaryheap + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ +

Module binaryheap

+

Binary heap implementation

+ +

A binary heap (or binary tree) is a sorting algorithm.

+

+ + +

The 'plain binary heap' is managed by positions. Which are hard to get once + an element is inserted. It can be anywhere in the list because it is re-sorted + upon insertion/deletion of items. The array with values is stored in field + values:

+ + +
+peek = heap.values[1]
+
+ +

A 'unique binary heap' is where the payload is unique and the payload itself + also stored (as key) in the heap with the position as value, as in;

+ +
+heap.reverse[payload] = [pos]
+
+ +

Due to this setup the reverse search, based on payload, is now a + much faster operation because instead of traversing the list/heap, + you can do;

+ +
+pos = heap.reverse[payload]
+
+ +

This means that deleting elements from a 'unique binary heap' is + faster than from a plain heap.

+ +

All management functions in the 'unique binary heap' take payload + instead of pos as argument. + Note that the value of the payload must be unique!

+ +

Fields of heap object:

+ +
    +
  • values - array of values
  • +
  • payloads - array of payloads (unique binary heap only)
  • +
  • reverse - map from payloads to indices (unique binary heap only)
  • +
+

+ + +

Basic heap

+ + + + + +
binaryHeap (swap, erase, lt)Creates a new binary heap.
+

Plain heap

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
heap:insert (value)Inserts an element in the heap.
heap:peek ()Returns the element at the top of the heap, without removing it.
heap:pop ()Removes the top of the heap and returns it.
heap:remove (pos)Removes an element from the heap.
heap:size ()Returns the number of elements in the heap.
heap:update (pos, newValue)Updates the value of an element in the heap.
maxHeap (gt)Creates a new max-heap, where the largest value is at the top.
minHeap (lt)Creates a new min-heap, where the smallest value is at the top.
+

Unique heap

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
heap:size ()Returns the number of elements in the heap.
maxUnique (gt)Creates a new max-heap with unique payloads.
minUnique (lt)Creates a new min-heap with unique payloads.
unique:insert (value, payload)Inserts an element in the heap.
unique:peek ()Returns the element at the top of the heap, without removing it.
unique:peekValue ()Returns the element at the top of the heap, without removing it.
unique:pop ()Removes the top of the heap and returns it.
unique:remove (payload)Removes an element from the heap.
unique:update (payload, newValue)Updates the value of an element in the heap.
unique:valueByPayload (payload)Returns the value associated with the payload
+ +
+
+ + +

Basic heap

+ +
+ This is the base implementation of the heap. Under regular circumstances + this should not be used, instead use a Plain heap or Unique heap. +
+
+
+ + binaryHeap (swap, erase, lt) +
+
+ Creates a new binary heap. + This is the core of all heaps, the others + are built upon these sorting functions. + + +

Parameters:

+
    +
  • swap + (function) swap(heap, idx1, idx2) swaps values at + idx1 and idx2 in the heaps heap.values and heap.payloads lists (see + return value below). +
  • +
  • erase + (function) swap(heap, position) raw removal +
  • +
  • lt + (function) in lt(a, b) returns true when a < b (for a min-heap) +
  • +
+ +

Returns:

+
    + + table with two methods; heap:bubbleUp(pos) and heap:sinkDown(pos) + that implement the sorting algorithm and two fields; heap.values and + heap.payloads being lists, holding the values and payloads respectively. +
+ + + + +
+
+

Plain heap

+ +
+ A plain heap carries a single piece of information per entry. This can be + any type (except nil), as long as the comparison function used to create + the heap can handle it. +
+
+
+ + heap:insert (value) +
+
+ Inserts an element in the heap. + + +

Parameters:

+
    +
  • value + the value used for sorting this element +
  • +
+ +

Returns:

+
    + + nothing, or throws an error on bad input +
+ + + + +
+
+ + heap:peek () +
+
+ Returns the element at the top of the heap, without removing it. + + + +

Returns:

+
    + + value at the top, or nil if there is none +
+ + + + +
+
+ + heap:pop () +
+
+ Removes the top of the heap and returns it. + + + +

Returns:

+
    + + value at the top, or nil if there is none +
+ + + + +
+
+ + heap:remove (pos) +
+
+ Removes an element from the heap. + + +

Parameters:

+
    +
  • pos + the position to remove +
  • +
+ +

Returns:

+
    + + value, or nil if a bad pos value was provided +
+ + + + +
+
+ + heap:size () +
+
+ Returns the number of elements in the heap. + + + +

Returns:

+
    + + number of elements +
+ + + + +
+
+ + heap:update (pos, newValue) +
+
+ Updates the value of an element in the heap. + + +

Parameters:

+
    +
  • pos + the position which value to update +
  • +
  • newValue + the new value to use for this payload +
  • +
+ + + + + +
+
+ + maxHeap (gt) +
+
+ Creates a new max-heap, where the largest value is at the top. + + +

Parameters:

+
    +
  • gt + (optional) comparison function (greater-than), see binaryHeap. +
  • +
+ +

Returns:

+
    + + the new heap +
+ + + + +
+
+ + minHeap (lt) +
+
+ Creates a new min-heap, where the smallest value is at the top. + + +

Parameters:

+
    +
  • lt + (optional) comparison function (less-than), see binaryHeap. +
  • +
+ +

Returns:

+
    + + the new heap +
+ + + + +
+
+

Unique heap

+ +
+ A unique heap carries 2 pieces of information per entry.

+ +
    +
  1. The value, this is used for ordering the heap. It can be any type (except + nil), as long as the comparison function used to create the heap can + handle it.
  2. +
  3. The payload, this can be any type (except nil), but it MUST be unique.
  4. +
+ +

With the 'unique heap' it is easier to remove elements from the heap. +

+
+
+ + heap:size () +
+
+ Returns the number of elements in the heap. + + + +

Returns:

+
    + + number of elements +
+ + + + +
+
+ + maxUnique (gt) +
+
+ Creates a new max-heap with unique payloads. + A max-heap is where the largest value is at the top.

+ +

NOTE: All management functions in the 'unique binary heap' + take payload instead of pos as argument. + + +

Parameters:

+
    +
  • gt + (optional) comparison function (greater-than), see binaryHeap. +
  • +
+ +

Returns:

+
    + + the new heap +
+ + + + +
+
+ + minUnique (lt) +
+
+ Creates a new min-heap with unique payloads. + A min-heap is where the smallest value is at the top.

+ +

NOTE: All management functions in the 'unique binary heap' + take payload instead of pos as argument. + + +

Parameters:

+
    +
  • lt + (optional) comparison function (less-than), see binaryHeap. +
  • +
+ +

Returns:

+
    + + the new heap +
+ + + + +
+
+ + unique:insert (value, payload) +
+
+ Inserts an element in the heap. + + +

Parameters:

+
    +
  • value + the value used for sorting this element +
  • +
  • payload + the payload attached to this element +
  • +
+ +

Returns:

+
    + + nothing, or throws an error on bad input +
+ + + + +
+
+ + unique:peek () +
+
+ Returns the element at the top of the heap, without removing it. + + + +

Returns:

+
    + + payload, value, or nil if there is none +
+ + + + +
+
+ + unique:peekValue () +
+
+ Returns the element at the top of the heap, without removing it. + + + +

Returns:

+
    + + value at the top, or nil if there is none +
+ + + +

Usage:

+
    +
    -- simple timer based heap example
    +while true do
    +  sleep(heap:peekValue() - gettime())  -- assume LuaSocket gettime function
    +  coroutine.resume((heap:pop()))       -- assumes payload to be a coroutine,
    +                                       -- double parens to drop extra return value
    +end
    +
+ +
+
+ + unique:pop () +
+
+ Removes the top of the heap and returns it. + When used with timers, pop will return the payload that is due.

+ +

Note: this function returns payload as the first result to prevent + extra locals when retrieving the payload. + + + +

Returns:

+
    + + payload, value, or nil if there is none +
+ + + + +
+
+ + unique:remove (payload) +
+
+ Removes an element from the heap. + + +

Parameters:

+
    +
  • payload + the payload to remove +
  • +
+ +

Returns:

+
    + + value, payload or nil if not found +
+ + + + +
+
+ + unique:update (payload, newValue) +
+
+ Updates the value of an element in the heap. + + +

Parameters:

+
    +
  • payload + the payoad whose value to update +
  • +
  • newValue + the new value to use for this payload +
  • +
+ +

Returns:

+
    + + nothing, or throws an error on bad input +
+ + + + +
+
+ + unique:valueByPayload (payload) +
+
+ Returns the value associated with the payload + + +

Parameters:

+
    +
  • payload + the payload to lookup +
  • +
+ +

Returns:

+
    + + value or nil if no such payload exists +
+ + + + +
+
+ + +
+
+
+generated by LDoc 1.4.6 +Last updated 2018-11-07 17:56:33 +
+
+ + diff --git a/thirdparty/binaryheap.lua/docs/ldoc.css b/thirdparty/binaryheap.lua/docs/ldoc.css new file mode 100644 index 000000000..f6e3d99f9 --- /dev/null +++ b/thirdparty/binaryheap.lua/docs/ldoc.css @@ -0,0 +1,291 @@ +body { + color: #47555c; + font-size: 16px; + font-family: "Open Sans", sans-serif; + margin: 0; + background: #eff4ff; +} + +a:link { color: #008fee; } +a:visited { color: #008fee; } +a:hover { color: #22a7ff; } + +h1 { font-size:26px; font-weight: normal; } +h2 { font-size:22px; font-weight: normal; } +h3 { font-size:18px; font-weight: normal; } +h4 { font-size:16px; font-weight: bold; } + +hr { + height: 1px; + background: #c1cce4; + border: 0px; + margin: 15px 0; +} + +code, tt { + font-family: monospace; +} +span.parameter { + font-family: monospace; + font-weight: bold; + color: rgb(99, 115, 131); +} +span.parameter:after { + content:":"; +} +span.types:before { + content:"("; +} +span.types:after { + content:")"; +} +.type { + font-weight: bold; font-style:italic +} + +p.name { + font-family: "Andale Mono", monospace; +} + +#navigation { + float: left; + background-color: white; + border-right: 1px solid #d3dbec; + border-bottom: 1px solid #d3dbec; + + width: 14em; + vertical-align: top; + overflow: visible; +} + +#navigation br { + display: none; +} + +#navigation h1 { + background-color: white; + border-bottom: 1px solid #d3dbec; + padding: 15px; + margin-top: 0px; + margin-bottom: 0px; +} + +#navigation h2 { + font-size: 18px; + background-color: white; + border-bottom: 1px solid #d3dbec; + padding-left: 15px; + padding-right: 15px; + padding-top: 10px; + padding-bottom: 10px; + margin-top: 30px; + margin-bottom: 0px; +} + +#content h1 { + background-color: #2c3e67; + color: white; + padding: 15px; + margin: 0px; +} + +#content h2 { + background-color: #6c7ea7; + color: white; + padding: 15px; + padding-top: 15px; + padding-bottom: 15px; + margin-top: 0px; +} + +#content h2 a { + background-color: #6c7ea7; + color: white; + text-decoration: none; +} + +#content h2 a:hover { + text-decoration: underline; +} + +#content h3 { + font-style: italic; + padding-top: 15px; + padding-bottom: 4px; + margin-right: 15px; + margin-left: 15px; + margin-bottom: 5px; + border-bottom: solid 1px #bcd; +} + +#content h4 { + margin-right: 15px; + margin-left: 15px; + border-bottom: solid 1px #bcd; +} + +#content pre { + margin: 15px; +} + +pre { + background-color: rgb(50, 55, 68); + color: white; + border-radius: 3px; + /* border: 1px solid #C0C0C0; /* silver */ + padding: 15px; + overflow: auto; + font-family: "Andale Mono", monospace; +} + +#content ul pre.example { + margin-left: 0px; +} + +table.index { +/* border: 1px #00007f; */ +} +table.index td { text-align: left; vertical-align: top; } + +#navigation ul +{ + font-size:1em; + list-style-type: none; + margin: 1px 1px 10px 1px; + padding-left: 20px; +} + +#navigation li { + text-indent: -1em; + display: block; + margin: 3px 0px 0px 22px; +} + +#navigation li li a { + margin: 0px 3px 0px -1em; +} + +#content { + margin-left: 14em; +} + +#content p { + padding-left: 15px; + padding-right: 15px; +} + +#content table { + padding-left: 15px; + padding-right: 15px; + background-color: white; +} + +#content p, #content table, #content ol, #content ul, #content dl { + max-width: 900px; +} + +#about { + padding: 15px; + padding-left: 16em; + background-color: white; + border-top: 1px solid #d3dbec; + border-bottom: 1px solid #d3dbec; +} + +table.module_list, table.function_list { + border-width: 1px; + border-style: solid; + border-color: #cccccc; + border-collapse: collapse; + margin: 15px; +} +table.module_list td, table.function_list td { + border-width: 1px; + padding-left: 10px; + padding-right: 10px; + padding-top: 5px; + padding-bottom: 5px; + border: solid 1px rgb(193, 204, 228); +} +table.module_list td.name, table.function_list td.name { + background-color: white; min-width: 200px; border-right-width: 0px; +} +table.module_list td.summary, table.function_list td.summary { + background-color: white; width: 100%; border-left-width: 0px; +} + +dl.function { + margin-right: 15px; + margin-left: 15px; + border-bottom: solid 1px rgb(193, 204, 228); + border-left: solid 1px rgb(193, 204, 228); + border-right: solid 1px rgb(193, 204, 228); + background-color: white; +} + +dl.function dt { + color: rgb(99, 123, 188); + font-family: monospace; + border-top: solid 1px rgb(193, 204, 228); + padding: 15px; +} + +dl.function dd { + margin-left: 15px; + margin-right: 15px; + margin-top: 5px; + margin-bottom: 15px; +} + +#content dl.function dd h3 { + margin-top: 0px; + margin-left: 0px; + padding-left: 0px; + font-size: 16px; + color: rgb(128, 128, 128); + border-bottom: solid 1px #def; +} + +#content dl.function dd ul, #content dl.function dd ol { + padding: 0px; + padding-left: 15px; + list-style-type: none; +} + +ul.nowrap { + overflow:auto; + white-space:nowrap; +} + +.section-description { + padding-left: 15px; + padding-right: 15px; +} + +/* stop sublists from having initial vertical space */ +ul ul { margin-top: 0px; } +ol ul { margin-top: 0px; } +ol ol { margin-top: 0px; } +ul ol { margin-top: 0px; } + +/* make the target distinct; helps when we're navigating to a function */ +a:target + * { + background-color: #FF9; +} + + +/* styles for prettification of source */ +pre .comment { color: #bbccaa; } +pre .constant { color: #a8660d; } +pre .escape { color: #844631; } +pre .keyword { color: #ffc090; font-weight: bold; } +pre .library { color: #0e7c6b; } +pre .marker { color: #512b1e; background: #fedc56; font-weight: bold; } +pre .string { color: #8080ff; } +pre .number { color: #f8660d; } +pre .operator { color: #2239a8; font-weight: bold; } +pre .preprocessor, pre .prepro { color: #a33243; } +pre .global { color: #c040c0; } +pre .user-keyword { color: #800080; } +pre .prompt { color: #558817; } +pre .url { color: #272fc2; text-decoration: underline; } diff --git a/thirdparty/binaryheap.lua/docs/topics/readme.md.html b/thirdparty/binaryheap.lua/docs/topics/readme.md.html new file mode 100644 index 000000000..5ceb24e47 --- /dev/null +++ b/thirdparty/binaryheap.lua/docs/topics/readme.md.html @@ -0,0 +1,109 @@ + + + + + binaryheap + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ + +

binaryheap.lua

+ +

Binary heap implementation

+ +

Both the source code as well as the +documentation are on github

+ +

Based on original code +by Oliver Kroth, with +extras +as proposed by Sean Conner.

+ +

Contributions

+

This library was create by contributions from Oliver Kroth, +Thijs Schreijer, Boris Nagaev

+ +

History

+ +

Version 0.4, 7-Nov-2018

+ +
    +
  • [breaking] added additional tests, mostly on returning errors, minor behaviour changes
  • +
  • added size method
  • +
  • fixed a lot of linter issues
  • +
+ +

Version 0.3, 15-Jul-2018

+ + + +

Version 0.2, 21-Apr-2015

+ +
    +
  • bugfix remove function (by Boris Nagaev)
  • +
  • configurable comparison function for the tree
  • +
+ +

Version 0.1, 20-Apr-2015

+ +
    +
  • Initial release
  • +
+ + +

Copyright

+

Copyright 2015-2018 Thijs Schreijer

+ +

License

+

MIT/X11

+ + +
+
+
+generated by LDoc 1.4.6 +Last updated 2018-11-07 17:56:33 +
+
+ + diff --git a/thirdparty/binaryheap.lua/examples/knight_dijkstra.lua b/thirdparty/binaryheap.lua/examples/knight_dijkstra.lua new file mode 100644 index 000000000..61d2e3467 --- /dev/null +++ b/thirdparty/binaryheap.lua/examples/knight_dijkstra.lua @@ -0,0 +1,85 @@ +-- Calculates shortest path of Knight from (1, 1) to (x, y). +-- Prints matrix of shortest paths for all cells. +-- Knight can't leave the rectangle from (1, 1) to (x, y). + +-- See https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm +-- See http://stackoverflow.com/questions/2339101 + +-- Usage: +-- $ lua knight_dijkstra.lua X Y + +local binaryheap = require 'binaryheap' + +local ROWS = tonumber(arg[2]) or 8 +local COLS = tonumber(arg[1]) or 8 + +local unvisited = binaryheap.minUnique() + +local function Cell(x, y) + return x .. '_' .. y +end + +local function Coordinates(cell) + local x, y = cell:match('(%d+)_(%d+)') + return x, y +end + +local function neighbours(cell) + local x, y = Coordinates(cell) + local gen = coroutine.wrap(function() + coroutine.yield(x - 1, y - 2) + coroutine.yield(x - 1, y + 2) + coroutine.yield(x + 1, y - 2) + coroutine.yield(x + 1, y + 2) + coroutine.yield(x - 2, y - 1) + coroutine.yield(x - 2, y + 1) + coroutine.yield(x + 2, y - 1) + coroutine.yield(x + 2, y + 1) + end) + return coroutine.wrap(function() + for xx, yy in gen do + if 1 <= xx and xx <= COLS and + 1 <= yy and yy <= ROWS then + coroutine.yield(Cell(xx, yy)) + end + end + end) +end + +for y = 1, ROWS do + for x = 1, COLS do + local cell = Cell(x, y) + unvisited:insert(math.huge, cell) + end +end +unvisited:update('1_1', 0) + +local final_distance = {} + +while unvisited:peek() do + local current_distance, current = unvisited:peek() + assert(not final_distance[current]) + final_distance[current] = current_distance + unvisited:remove(current) + -- update neighbours + local new_distance = current_distance + 1 + for neighbour in neighbours(current) do + if unvisited.reverse[neighbour] then + local pos = unvisited.reverse[neighbour] + local distance = unvisited.value[pos] + if distance > new_distance then + unvisited:update(neighbour, new_distance) + end + end + end +end + +for y = 1, ROWS do + local row = {} + for x = 1, COLS do + local cell = Cell(x, y) + local distance = final_distance[cell] + table.insert(row, distance) + end + print(table.concat(row, ' ')) +end diff --git a/thirdparty/binaryheap.lua/spec/binaryheap_spec.lua b/thirdparty/binaryheap.lua/spec/binaryheap_spec.lua new file mode 100644 index 000000000..107ada81c --- /dev/null +++ b/thirdparty/binaryheap.lua/spec/binaryheap_spec.lua @@ -0,0 +1,835 @@ + +local bh = require('binaryheap') + +local data = { + { value = 98, payload = "pos08" }, -- 1 + { value = 28, payload = "pos05" }, -- 2 + { value = 36, payload = "pos06" }, -- 3 + { value = 48, payload = "pos09" }, -- 4 + { value = 68, payload = "pos10" }, -- 5 + { value = 58, payload = "pos13" }, -- 6 + { value = 80, payload = "pos15" }, -- 7 + { value = 46, payload = "pos04" }, -- 8 + { value = 19, payload = "pos03" }, -- 9 + { value = 66, payload = "pos11" }, -- 10 + { value = 22, payload = "pos02" }, -- 11 + { value = 60, payload = "pos12" }, -- 12 + { value = 15, payload = "pos01" }, -- 13 + { value = 83, payload = "pos14" }, -- 14 + { value = 59, payload = "pos07" }, -- 15 +} + +local sort = function(t) + table.sort(t, function(a,b) return (a.value < b.value) end) + return t +end + +local function check(heap) + for pos = 2, #heap.values do + local parent = math.floor(pos / 2) + assert(not heap.lt(heap.values[pos], heap.values[parent])) + end + if heap.payloads then + for pos in ipairs(heap.values) do + local payload = heap.payloads[pos] + assert(heap.reverse[payload] == pos) + end + end +end + +local function newheap() + -- create a heap with data + local heap = bh.minUnique() + for _, node in ipairs(data) do + heap:insert(node.value,node.payload) + check(heap) + end + + -- create a sorted list with data, sorted by 'value' + local sorted = {} + for k,v in pairs(data) do sorted[k] = v end + sort(sorted) + -- create a reverse list of the sorted table; returns sorted-index, based on 'value' + local sreverse = {} + for i,v in ipairs(sorted) do + sreverse[v.value] = i + end + return heap, sorted, sreverse +end + +local function testheap(heap, sorted) + while sorted[1] do + local value1, payload1 + if heap.reverse then + -- it is a unique heap + payload1, value1 = heap:pop() + else + -- it is a plain heap + value1, payload1 = heap:pop() + end + local value2, payload2 = sorted[1].value, sorted[1].payload + table.remove(sorted, 1) + assert.are.equal(payload1, payload2) + assert.are.equal(value1, value2) + end +end + +describe("[minUnique]", function() + + it("validates order of insertion", function() + local h = newheap() + assert.are.equal(h.payloads[1], data[13].payload) + assert.are.equal(h.payloads[2], data[11].payload) + assert.are.equal(h.payloads[3], data[9].payload) + assert.are.equal(h.payloads[4], data[8].payload) + assert.are.equal(h.payloads[5], data[2].payload) + assert.are.equal(h.payloads[6], data[3].payload) + assert.are.equal(h.payloads[7], data[15].payload) + assert.are.equal(h.payloads[8], data[1].payload) + assert.are.equal(h.payloads[9], data[4].payload) + assert.are.equal(h.payloads[10], data[5].payload) + assert.are.equal(h.payloads[11], data[10].payload) + assert.are.equal(h.payloads[12], data[12].payload) + assert.are.equal(h.payloads[13], data[6].payload) + assert.are.equal(h.payloads[14], data[14].payload) + assert.are.equal(h.payloads[15], data[7].payload) + end) + + it("validates order of popping", function() + testheap(newheap()) + end) + + it("peek()", function() + local heap, sorted = newheap() + local payload, value = heap:peek() + -- correct values? + assert.are.equal(value, sorted[1].value) + assert.are.equal(payload, sorted[1].payload) + -- are they still on the heap? + assert.are.equal(value, heap.values[1]) + assert.are.equal(payload, heap.payloads[1]) + end) + + it("peekValue()", function() + local h = bh.minUnique() + h:insert(1, 11) + assert.equal(1, h:peekValue(11)) + -- try again empty + h:pop() + assert.is_nil(h:peekValue(11)) + end) + + it("pop()", function() + local h = bh.minUnique() + h:insert(3, 13) + h:insert(2, 12) + h:insert(1, 11) + -- try again empty + local pl, v + pl, v = h:pop() + assert.equal(v, 1) + assert.equal(pl, 11) + pl, v = h:pop() + assert.equal(v, 2) + assert.equal(pl, 12) + pl, v = h:pop() + assert.equal(v, 3) + assert.equal(pl, 13) + pl, v = h:pop() + assert.is_nil(v) + assert.is_nil(pl) +end) + + describe("remove()", function() + it("a middle item", function() + local heap, sorted = newheap() + local idx = 4 + local value = sorted[idx].value + local payload = sorted[idx].payload + local v, pl = heap:remove(payload) + check(heap) + -- did we get the right ones? + assert.are.equal(value, v) + assert.are.equal(payload, pl) + assert.is.Nil(heap[payload]) + -- remove from test data and compare + table.remove(sorted, idx) + testheap(heap, sorted) + end) + + it("the last item (of the array)", function() + local heap, sorted = newheap() + local idx = #heap.values + local value = sorted[idx].value + local payload = sorted[idx].payload + local v, pl = heap:remove(payload) + check(heap) + -- did we get the right ones? + assert.are.equal(value, v) + assert.are.equal(payload, pl) + assert.is.Nil(heap.reverse[payload]) + -- remove from test data and compare + table.remove(sorted, idx) + testheap(heap, sorted) + end) + + it("non existing payload returns nil", function() + local heap = newheap() + local v, pl = heap:remove({}) + assert.is_nil(v) + assert.is_nil(pl) + end) + + it("nil payload returns nil", function() + local heap = newheap() + local v, pl = heap:remove(nil) + assert.is_nil(v) + assert.is_nil(pl) + end) + + it("with repeated values", function() + local h = bh.minUnique() + h:insert(1, 11) + check(h) + h:insert(1, 12) + check(h) + local value, payload + value, payload = h:remove(11) + check(h) + assert.equal(1, value) + assert.equal(11, payload) + payload, value = h:peek() + assert.equal(1, value) + assert.equal(12, payload) + assert.same({1}, h.values) + assert.same({12}, h.payloads) + assert.same({[12]=1}, h.reverse) + end) + end) + + describe("insert()", function() + it("a top item", function() + local heap, sorted = newheap() + local nvalue = sorted[1].value - 10 + local npayload = {} + table.insert(sorted, 1, {}) + sorted[1].value = nvalue + sorted[1].payload = npayload + heap:insert(nvalue, npayload) + check(heap) + testheap(heap, sorted) + end) + + it("a middle item", function() + local heap, sorted = newheap() + local nvalue = 57 + local npayload = {} + table.insert(sorted, { value = nvalue, payload = npayload }) + sort(sorted) + heap:insert(nvalue, npayload) + check(heap) + testheap(heap, sorted) + end) + + it("a last item", function() + local heap, sorted = newheap() + local nvalue = sorted[#sorted].value + 10 + local npayload = {} + table.insert(sorted, {}) + sorted[#sorted].value = nvalue + sorted[#sorted].payload = npayload + heap:insert(nvalue, npayload) + check(heap) + testheap(heap, sorted) + end) + + it("a nil value throws an error", function() + local heap = newheap() + assert.has.error(function() + heap:insert(nil, "something") + end) + end) + + it("a nil payload throws an error", function() + local heap = newheap() + assert.has.error(function() + heap:insert(15, nil) + end) + end) + + it("a duplicate payload throws an error", function() + local heap = newheap() + local value = {} + heap:insert(1, value) + assert.has.error(function() + heap:insert(2, value) + end) + end) + end) + + describe("update()", function() + it("a top item", function() + local heap, sorted = newheap() + local idx = 1 + local payload = sorted[idx].payload + local nvalue = sorted[#sorted].value + 1 -- move to end with new value + sorted[idx].value = nvalue + sort(sorted) + heap:update(payload, nvalue) + check(heap) + testheap(heap, sorted) + end) + + it("a middle item", function() + local heap, sorted = newheap() + local idx = 4 + local payload = sorted[idx].payload + local nvalue = sorted[idx].value * 2 + sorted[idx].value = nvalue + sort(sorted) + heap:update(payload, nvalue) + check(heap) + testheap(heap, sorted) + end) + + it("a last item", function() + local heap, sorted = newheap() + local idx = #sorted + local payload = sorted[idx].payload + local nvalue = sorted[1].value - 1 -- move to top with new value + sorted[idx].value = nvalue + sort(sorted) + heap:update(payload, nvalue) + check(heap) + testheap(heap, sorted) + end) + + it("a nil value throws an error", function() + local heap, sorted = newheap() + local idx = #sorted + local payload = sorted[idx].payload + assert.has.error(function() + heap:update(payload, nil) + end) + end) + + it("an unknown payload throws an error", function() + local heap = newheap() + assert.has.error(function() + heap:update({}, 10) + end) + end) + end) + + describe("size()", function() + it("returns number of elements", function() + local h = bh.minUnique() + assert.equal(0, h:size()) + h:insert(1, -1) + assert.equal(1, h:size()) + h:insert(2, -2) + assert.equal(2, h:size()) + h:insert(3, -3) + assert.equal(3, h:size()) + h:insert(4, -4) + assert.equal(4, h:size()) + h:insert(5, -5) + assert.equal(5, h:size()) + h:pop() + assert.equal(4, h:size()) + h:pop() + assert.equal(3, h:size()) + h:pop() + assert.equal(2, h:size()) + h:pop() + assert.equal(1, h:size()) + h:pop() + assert.equal(0, h:size()) + end) + end) + + describe("valueByPayload()", function() + it("gets value by payload", function() + local h = bh.minUnique() + h:insert(1, -1) + h:insert(2, -2) + h:insert(3, -3) + h:insert(4, -4) + h:insert(5, -5) + assert.equal(1, h:valueByPayload((-1))) + assert.equal(2, h:valueByPayload((-2))) + assert.equal(3, h:valueByPayload((-3))) + assert.equal(4, h:valueByPayload((-4))) + assert.equal(5, h:valueByPayload((-5))) + h:remove(-1) + assert.falsy(h:valueByPayload((-1))) + end) + + it("non existing payload returns nil", function() + local h = bh.minUnique() + h:insert(1, -1) + h:insert(2, -2) + h:insert(3, -3) + h:insert(4, -4) + h:insert(5, -5) + assert.is_nil(h:valueByPayload({})) + end) + + it("nil payload returns nil", function() + local h = bh.minUnique() + h:insert(1, -1) + h:insert(2, -2) + h:insert(3, -3) + h:insert(4, -4) + h:insert(5, -5) + assert.is_nil(h:valueByPayload(nil)) + end) + end) + + it("creates minUnique with custom less-than function", function() + local h = bh.minUnique(function (a, b) + return math.abs(a) < math.abs(b) + end) + h:insert(1, -1) + check(h) + h:insert(-2, 2) + check(h) + h:insert(3, -3) + check(h) + h:insert(-4, 4) + check(h) + h:insert(5, -5) + check(h) + local value, payload + payload, value = h:peek() + assert.equal(1, value) + assert.equal(-1, payload) + h:pop() + check(h) + payload, value = h:peek() + assert.equal(-2, value) + assert.equal(2, payload) + end) +end) + +describe("[maxUnique]", function() + it("creates maxUnique with custom less-than function", function() + local h = bh.maxUnique(function (a, b) + return math.abs(a) > math.abs(b) + end) + h:insert(1, -1) + check(h) + h:insert(-2, 2) + check(h) + h:insert(3, -3) + check(h) + h:insert(-4, 4) + check(h) + h:insert(5, -5) + check(h) + local value, payload + payload, value = h:peek() + assert.equal(5, value) + assert.equal(-5, payload) + h:pop() + check(h) + payload, value = h:peek() + assert.equal(-4, value) + assert.equal(4, payload) + end) +end) + +describe("[minHeap]", function() + it("creates minHeap", function() + local h = bh.minHeap() + check(h) + end) + + describe("insert()", function() + it("a number into minHeap", function() + local h = bh.minHeap() + h:insert(42) + check(h) + end) + + it("nil throws an error", function() + local h = bh.minHeap() + assert.has.error(function() + h:insert(nil) + end) + end) + end) + + describe("remove()", function() + it("a position", function() + local h = bh.minHeap() + h:insert(42) + h:insert(43) + assert.equal(43, h:remove(2)) + check(h) + end) + + it("a bad position returns nil", function() + local h = bh.minHeap() + h:insert(42) + h:insert(43) + assert.is_nil(h:remove(0)) + assert.is_nil(h:remove(3)) + check(h) + end) + end) + + describe("size()", function() + it("returns number of elements", function() + local h = bh.minHeap() + assert.equal(0, h:size()) + h:insert(1) + assert.equal(1, h:size()) + h:insert(2) + assert.equal(2, h:size()) + h:insert(3) + assert.equal(3, h:size()) + h:insert(4) + assert.equal(4, h:size()) + h:insert(5) + assert.equal(5, h:size()) + h:pop() + assert.equal(4, h:size()) + h:pop() + assert.equal(3, h:size()) + h:pop() + assert.equal(2, h:size()) + h:pop() + assert.equal(1, h:size()) + h:pop() + assert.equal(0, h:size()) + end) + end) + + describe("peek()", function() + it("return nil in empty minHeap", function() + local h = bh.minHeap() + assert.is_nil(h:peek()) + check(h) + end) + + it("minHeap of one element", function() + local h = bh.minHeap() + h:insert(42) + check(h) + local value, payload = h:peek() + assert.equal(42, value) + assert.falsy(payload) + end) + + it("minHeap of two elements", function() + local h = bh.minHeap() + h:insert(42) + check(h) + h:insert(1) + check(h) + local value, payload = h:peek() + assert.equal(1, value) + assert.falsy(payload) + end) + + it("minHeap of 10 elements", function() + local h = bh.minHeap() + h:insert(10) + h:insert(7) + h:insert(1) + h:insert(5) + h:insert(6) + h:insert(9) + h:insert(8) + h:insert(4) + h:insert(2) + h:insert(3) + check(h) + local value, payload = h:peek() + assert.equal(1, value) + assert.falsy(payload) + end) + + it("removes peek in minHeap of 5 elements", function() + local h = bh.minHeap() + h:insert(1) + h:insert(2) + h:insert(3) + h:insert(4) + h:insert(5) + local value + value = h:pop() + check(h) + assert.equal(1, value) + value = h:peek() + assert.equal(2, value) + end) + end) + + describe("update()", function() + it("in minHeap of 5 elements (pos 2 -> pos 1)", function() + local h = bh.minHeap() + h:insert(1) + h:insert(2) + h:insert(3) + h:insert(4) + h:insert(5) + check(h) + h:update(2, -100) + check(h) + local value = h:peek() + assert.equal(-100, value) + end) + + it("in minHeap of 5 elements (pos 1 -> pos 2)", function() + local h = bh.minHeap() + h:insert(1) + h:insert(2) + h:insert(3) + h:insert(4) + h:insert(5) + check(h) + h:update(1, 100) + check(h) + local value = h:peek() + assert.equal(2, value) + end) + + it("nil throws an error", function() + local h = bh.minHeap() + h:insert(10) + assert.has.error(function() + h:update(nil) + end) + end) + + it("bad position throws an error", function() + local h = bh.minHeap() + h:insert(10) + assert.has.error(function() + h:update(0) + end) + assert.has.error(function() + h:update(2) + end) + end) + end) + + it("creates minHeap with custom less-than function", function() + local h = bh.minHeap(function (a, b) + return math.abs(a) < math.abs(b) + end) + h:insert(1) + check(h) + h:insert(-2) + check(h) + h:insert(3) + check(h) + h:insert(-4) + check(h) + h:insert(5) + check(h) + assert.equal(1, h:peek()) + h:pop() + check(h) + assert.equal(-2, h:peek()) + end) +end) + + +describe("[maxHeap]", function() + it("creates maxHeap", function() + local h = bh.maxHeap() + check(h) + end) + + describe("insert()", function() + it("inserts a number into maxHeap", function() + local h = bh.maxHeap() + h:insert(42) + check(h) + end) + + it("nil throws an error", function() + local h = bh.maxHeap() + assert.has.error(function() + h:insert(nil) + end) + end) + end) + + describe("remove()", function() + it("a position", function() + local h = bh.maxHeap() + h:insert(42) + h:insert(43) + assert.equal(42, h:remove(2)) + check(h) + end) + + it("a bad position returns nil", function() + local h = bh.maxHeap() + h:insert(42) + h:insert(43) + assert.is_nil(h:remove(0)) + assert.is_nil(h:remove(3)) + check(h) + end) + end) + + describe("size()", function() + it("returns number of elements", function() + local h = bh.minHeap() + assert.equal(0, h:size()) + h:insert(1) + assert.equal(1, h:size()) + h:insert(2) + assert.equal(2, h:size()) + h:insert(3) + assert.equal(3, h:size()) + h:insert(4) + assert.equal(4, h:size()) + h:insert(5) + assert.equal(5, h:size()) + h:pop() + assert.equal(4, h:size()) + h:pop() + assert.equal(3, h:size()) + h:pop() + assert.equal(2, h:size()) + h:pop() + assert.equal(1, h:size()) + h:pop() + assert.equal(0, h:size()) + end) + end) + + describe("peek()", function() + it("return nil in empty maxHeap", function() + local h = bh.maxHeap() + assert.is_nil(h:peek()) + check(h) + end) + + it("maxHeap of one element", function() + local h = bh.maxHeap() + h:insert(42) + check(h) + local value = h:peek() + assert.equal(42, value) + end) + + it("maxHeap of two elements", function() + local h = bh.maxHeap() + h:insert(42) + check(h) + h:insert(1) + check(h) + local value = h:peek() + assert.equal(42, value) + end) + + it("maxHeap of 10 elements", function() + local h = bh.maxHeap() + h:insert(10) + h:insert(7) + h:insert(1) + h:insert(5) + h:insert(6) + h:insert(9) + h:insert(8) + h:insert(4) + h:insert(2) + h:insert(3) + check(h) + local value = h:peek() + assert.equal(10, value) + end) + + it("removes peek in maxHeap of 5 elements", function() + local h = bh.maxHeap() + h:insert(1) + h:insert(2) + h:insert(3) + h:insert(4) + h:insert(5) + check(h) + local value + value = h:pop() + check(h) + assert.equal(5, value) + value = h:peek() + assert.equal(4, value) + end) + end) + + describe("update()", function() + it("in maxHeap of 5 elements (pos 2 -> pos 1)", function() + local h = bh.maxHeap() + h:insert(1) + h:insert(2) + h:insert(3) + h:insert(4) + h:insert(5) + check(h) + h:update(2, 100) + check(h) + local value = h:peek() + assert.equal(100, value) + end) + + it("in maxHeap of 5 elements (pos 1 -> pos 2)", function() + local h = bh.maxHeap() + h:insert(1) + h:insert(2) + h:insert(3) + h:insert(4) + h:insert(5) + check(h) + h:update(1, -100) + check(h) + local value = h:peek() + assert.equal(4, value) + end) + + it("nil throws an error", function() + local h = bh.maxHeap() + h:insert(10) + assert.has.error(function() + h:update(nil) + end) + end) + + it("bad position throws an error", function() + local h = bh.maxHeap() + h:insert(10) + assert.has.error(function() + h:update(0) + end) + assert.has.error(function() + h:update(2) + end) + end) +end) + + it("creates maxHeap with custom greater-than function", function() + local h = bh.maxHeap(function (a, b) + return math.abs(a) > math.abs(b) + end) + h:insert(1) + check(h) + h:insert(-2) + check(h) + h:insert(3) + check(h) + h:insert(-4) + check(h) + h:insert(5) + check(h) + assert.equal(5, (h:peek())) + h:pop() + check(h) + assert.equal(-4, (h:peek())) + end) +end) diff --git a/thirdparty/binaryheap.lua/spec/dijkstras_algorithm_spec.lua b/thirdparty/binaryheap.lua/spec/dijkstras_algorithm_spec.lua new file mode 100644 index 000000000..cc63dd8a6 --- /dev/null +++ b/thirdparty/binaryheap.lua/spec/dijkstras_algorithm_spec.lua @@ -0,0 +1,95 @@ +describe("dijkstras algorithm with binaryheap", function() + -- See https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm + + it("calculates knight's shortest path", function() + -- See http://stackoverflow.com/questions/2339101 + + local binaryheap = require 'binaryheap' + + local ROWS = 8 + local COLS = 8 + + local unvisited = binaryheap.minUnique() + + local function Cell(x, y) + return x .. '_' .. y + end + + local function Coordinates(cell) + local x, y = cell:match('(%d+)_(%d+)') + return x, y + end + + local function neighbours(cell) + local x, y = Coordinates(cell) + local gen = coroutine.wrap(function() + coroutine.yield(x - 1, y - 2) + coroutine.yield(x - 1, y + 2) + coroutine.yield(x + 1, y - 2) + coroutine.yield(x + 1, y + 2) + coroutine.yield(x - 2, y - 1) + coroutine.yield(x - 2, y + 1) + coroutine.yield(x + 2, y - 1) + coroutine.yield(x + 2, y + 1) + end) + return coroutine.wrap(function() + for xx, yy in gen do + if 1 <= xx and xx <= COLS and + 1 <= yy and yy <= ROWS then + coroutine.yield(Cell(xx, yy)) + end + end + end) + end + + for y = 1, ROWS do + for x = 1, COLS do + local cell = Cell(x, y) + unvisited:insert(math.huge, cell) + end + end + unvisited:update('1_1', 0) + + local final_distance = {} + + while unvisited:peekValue() do + local current, current_distance = unvisited:peek() + assert(not final_distance[current]) + final_distance[current] = current_distance + unvisited:remove(current) + -- update neighbours + local new_distance = current_distance + 1 + for neighbour in neighbours(current) do + if unvisited.reverse[neighbour] then + local pos = unvisited.reverse[neighbour] + local distance = unvisited.values[pos] + if distance > new_distance then + unvisited:update(neighbour, new_distance) + end + end + end + end + + local rows = {} + for y = 1, ROWS do + local row = {} + for x = 1, COLS do + local cell = Cell(x, y) + local distance = final_distance[cell] + table.insert(row, distance) + end + table.insert(rows, table.concat(row, ' ')) + end + + assert.equal([[ +0 3 2 3 2 3 4 5 +3 4 1 2 3 4 3 4 +2 1 4 3 2 3 4 5 +3 2 3 2 3 4 3 4 +2 3 2 3 4 3 4 5 +3 4 3 4 3 4 5 4 +4 3 4 3 4 5 4 5 +5 4 5 4 5 4 5 6]], + table.concat(rows, '\n')) + end) +end) diff --git a/thirdparty/binaryheap.lua/src/binaryheap.lua b/thirdparty/binaryheap.lua/src/binaryheap.lua new file mode 100644 index 000000000..077f22e78 --- /dev/null +++ b/thirdparty/binaryheap.lua/src/binaryheap.lua @@ -0,0 +1,406 @@ +------------------------------------------------------------------- +-- Binary heap implementation +-- +-- A binary heap (or binary tree) is a [sorting algorithm](http://en.wikipedia.org/wiki/Binary_heap). +-- +-- The 'plain binary heap' is managed by positions. Which are hard to get once +-- an element is inserted. It can be anywhere in the list because it is re-sorted +-- upon insertion/deletion of items. The array with values is stored in field +-- `values`: +-- +-- `peek = heap.values[1]` +-- +-- A 'unique binary heap' is where the payload is unique and the payload itself +-- also stored (as key) in the heap with the position as value, as in; +-- `heap.reverse[payload] = [pos]` +-- +-- Due to this setup the reverse search, based on payload, is now a +-- much faster operation because instead of traversing the list/heap, +-- you can do; +-- `pos = heap.reverse[payload]` +-- +-- This means that deleting elements from a 'unique binary heap' is +-- faster than from a plain heap. +-- +-- All management functions in the 'unique binary heap' take `payload` +-- instead of `pos` as argument. +-- Note that the value of the payload must be unique! +-- +-- Fields of heap object: +-- +-- * values - array of values +-- * payloads - array of payloads (unique binary heap only) +-- * reverse - map from payloads to indices (unique binary heap only) + +local assert = assert +local floor = math.floor +local _ENV = nil + +local M = {} + +--================================================================ +-- basic heap sorting algorithm +--================================================================ + +--- Basic heap. +-- This is the base implementation of the heap. Under regular circumstances +-- this should not be used, instead use a _Plain heap_ or _Unique heap_. +-- @section baseheap + +--- Creates a new binary heap. +-- This is the core of all heaps, the others +-- are built upon these sorting functions. +-- @param swap (function) `swap(heap, idx1, idx2)` swaps values at +-- `idx1` and `idx2` in the heaps `heap.values` and `heap.payloads` lists (see +-- return value below). +-- @param erase (function) `swap(heap, position)` raw removal +-- @param lt (function) in `lt(a, b)` returns `true` when `a < b` (for a min-heap) +-- @return table with two methods; `heap:bubbleUp(pos)` and `heap:sinkDown(pos)` +-- that implement the sorting algorithm and two fields; `heap.values` and +-- `heap.payloads` being lists, holding the values and payloads respectively. +M.binaryHeap = function(swap, erase, lt) + + local heap = { + values = {}, -- list containing values + erase = erase, + swap = swap, + lt = lt, + } + + function heap:bubbleUp(pos) + local values = self.values + while pos>1 do + local parent = floor(pos/2) + if not lt(values[pos], values[parent]) then + break + end + swap(self, parent, pos) + pos = parent + end + end + + function heap:sinkDown(pos) + local values = self.values + local last = #values + while true do + local min = pos + local child = 2 * pos + + for c = child, child + 1 do + if c <= last and lt(values[c], values[min]) then min = c end + end + + if min == pos then break end + + swap(self, pos, min) + pos = min + end + end + + return heap +end + +--================================================================ +-- plain heap management functions +--================================================================ + +--- Plain heap. +-- A plain heap carries a single piece of information per entry. This can be +-- any type (except `nil`), as long as the comparison function used to create +-- the heap can handle it. +-- @section plainheap +do end -- luacheck: ignore +-- the above is to trick ldoc (otherwise `update` below disappears) + +local update +--- Updates the value of an element in the heap. +-- @function heap:update +-- @param pos the position which value to update +-- @param newValue the new value to use for this payload +update = function(self, pos, newValue) + assert(newValue ~= nil, "cannot add 'nil' as value") + assert(pos >= 1 and pos <= #self.values, "illegal position") + self.values[pos] = newValue + if pos > 1 then self:bubbleUp(pos) end + if pos < #self.values then self:sinkDown(pos) end +end + +local remove +--- Removes an element from the heap. +-- @function heap:remove +-- @param pos the position to remove +-- @return value, or nil if a bad `pos` value was provided +remove = function(self, pos) + local last = #self.values + if pos < 1 then + return -- bad pos + + elseif pos < last then + local v = self.values[pos] + self:swap(pos, last) + self:erase(last) + self:bubbleUp(pos) + self:sinkDown(pos) + return v + + elseif pos == last then + local v = self.values[pos] + self:erase(last) + return v + + else + return -- bad pos: pos > last + end +end + +local insert +--- Inserts an element in the heap. +-- @function heap:insert +-- @param value the value used for sorting this element +-- @return nothing, or throws an error on bad input +insert = function(self, value) + assert(value ~= nil, "cannot add 'nil' as value") + local pos = #self.values + 1 + self.values[pos] = value + self:bubbleUp(pos) +end + +local pop +--- Removes the top of the heap and returns it. +-- @function heap:pop +-- @return value at the top, or `nil` if there is none +pop = function(self) + if self.values[1] ~= nil then + return remove(self, 1) + end +end + +local peek +--- Returns the element at the top of the heap, without removing it. +-- @function heap:peek +-- @return value at the top, or `nil` if there is none +peek = function(self) + return self.values[1] +end + +local size +--- Returns the number of elements in the heap. +-- @function heap:size +-- @return number of elements +size = function(self) + return #self.values +end + +local function swap(heap, a, b) + heap.values[a], heap.values[b] = heap.values[b], heap.values[a] +end + +local function erase(heap, pos) + heap.values[pos] = nil +end + +--================================================================ +-- plain heap creation +--================================================================ + +local function plainHeap(lt) + local h = M.binaryHeap(swap, erase, lt) + h.peek = peek + h.pop = pop + h.size = size + h.remove = remove + h.insert = insert + h.update = update + return h +end + +--- Creates a new min-heap, where the smallest value is at the top. +-- @param lt (optional) comparison function (less-than), see `binaryHeap`. +-- @return the new heap +M.minHeap = function(lt) + if not lt then + lt = function(a,b) return (a < b) end + end + return plainHeap(lt) +end + +--- Creates a new max-heap, where the largest value is at the top. +-- @param gt (optional) comparison function (greater-than), see `binaryHeap`. +-- @return the new heap +M.maxHeap = function(gt) + if not gt then + gt = function(a,b) return (a > b) end + end + return plainHeap(gt) +end + +--================================================================ +-- unique heap management functions +--================================================================ + +--- Unique heap. +-- A unique heap carries 2 pieces of information per entry. +-- +-- 1. The `value`, this is used for ordering the heap. It can be any type (except +-- `nil`), as long as the comparison function used to create the heap can +-- handle it. +-- 2. The `payload`, this can be any type (except `nil`), but it MUST be unique. +-- +-- With the 'unique heap' it is easier to remove elements from the heap. +-- @section uniqueheap +do end -- luacheck: ignore +-- the above is to trick ldoc (otherwise `update` below disappears) + +local updateU +--- Updates the value of an element in the heap. +-- @function unique:update +-- @param payload the payoad whose value to update +-- @param newValue the new value to use for this payload +-- @return nothing, or throws an error on bad input +function updateU(self, payload, newValue) + return update(self, self.reverse[payload], newValue) +end + +local insertU +--- Inserts an element in the heap. +-- @function unique:insert +-- @param value the value used for sorting this element +-- @param payload the payload attached to this element +-- @return nothing, or throws an error on bad input +function insertU(self, value, payload) + assert(self.reverse[payload] == nil, "duplicate payload") + local pos = #self.values + 1 + self.reverse[payload] = pos + self.payloads[pos] = payload + return insert(self, value) +end + +local removeU +--- Removes an element from the heap. +-- @function unique:remove +-- @param payload the payload to remove +-- @return value, payload or nil if not found +function removeU(self, payload) + local pos = self.reverse[payload] + if pos ~= nil then + return remove(self, pos), payload + end +end + +local popU +--- Removes the top of the heap and returns it. +-- When used with timers, `pop` will return the payload that is due. +-- +-- Note: this function returns `payload` as the first result to prevent +-- extra locals when retrieving the `payload`. +-- @function unique:pop +-- @return payload, value, or `nil` if there is none +function popU(self) + if self.values[1] then + local payload = self.payloads[1] + local value = remove(self, 1) + return payload, value + end +end + +local peekU +--- Returns the element at the top of the heap, without removing it. +-- @function unique:peek +-- @return payload, value, or `nil` if there is none +peekU = function(self) + return self.payloads[1], self.values[1] +end + +local peekValueU +--- Returns the element at the top of the heap, without removing it. +-- @function unique:peekValue +-- @return value at the top, or `nil` if there is none +-- @usage -- simple timer based heap example +-- while true do +-- sleep(heap:peekValue() - gettime()) -- assume LuaSocket gettime function +-- coroutine.resume((heap:pop())) -- assumes payload to be a coroutine, +-- -- double parens to drop extra return value +-- end +peekValueU = function(self) + return self.values[1] +end + +local valueByPayload +--- Returns the value associated with the payload +-- @function unique:valueByPayload +-- @param payload the payload to lookup +-- @return value or nil if no such payload exists +valueByPayload = function(self, payload) + return self.values[self.reverse[payload]] +end + +local sizeU +--- Returns the number of elements in the heap. +-- @function heap:size +-- @return number of elements +sizeU = function(self) + return #self.values +end + +local function swapU(heap, a, b) + local pla, plb = heap.payloads[a], heap.payloads[b] + heap.reverse[pla], heap.reverse[plb] = b, a + heap.payloads[a], heap.payloads[b] = plb, pla + swap(heap, a, b) +end + +local function eraseU(heap, pos) + local payload = heap.payloads[pos] + heap.reverse[payload] = nil + heap.payloads[pos] = nil + erase(heap, pos) +end + +--================================================================ +-- unique heap creation +--================================================================ + +local function uniqueHeap(lt) + local h = M.binaryHeap(swapU, eraseU, lt) + h.payloads = {} -- list contains payloads + h.reverse = {} -- reverse of the payloads list + h.peek = peekU + h.peekValue = peekValueU + h.valueByPayload = valueByPayload + h.pop = popU + h.size = sizeU + h.remove = removeU + h.insert = insertU + h.update = updateU + return h +end + +--- Creates a new min-heap with unique payloads. +-- A min-heap is where the smallest value is at the top. +-- +-- *NOTE*: All management functions in the 'unique binary heap' +-- take `payload` instead of `pos` as argument. +-- @param lt (optional) comparison function (less-than), see `binaryHeap`. +-- @return the new heap +M.minUnique = function(lt) + if not lt then + lt = function(a,b) return (a < b) end + end + return uniqueHeap(lt) +end + +--- Creates a new max-heap with unique payloads. +-- A max-heap is where the largest value is at the top. +-- +-- *NOTE*: All management functions in the 'unique binary heap' +-- take `payload` instead of `pos` as argument. +-- @param gt (optional) comparison function (greater-than), see `binaryHeap`. +-- @return the new heap +M.maxUnique = function(gt) + if not gt then + gt = function(a,b) return (a > b) end + end + return uniqueHeap(gt) +end + +return M diff --git a/thirdparty/copas/.editorconfig b/thirdparty/copas/.editorconfig new file mode 100644 index 000000000..7c06305e4 --- /dev/null +++ b/thirdparty/copas/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +[*.lua] +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab diff --git a/thirdparty/copas/.gitignore b/thirdparty/copas/.gitignore new file mode 100644 index 000000000..2a7cab831 --- /dev/null +++ b/thirdparty/copas/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +**/*.srl +**/*.pem +*.rock +luacov.report.out +luacov.stats.out diff --git a/thirdparty/copas/.luacheckrc b/thirdparty/copas/.luacheckrc new file mode 100644 index 000000000..06ce2c028 --- /dev/null +++ b/thirdparty/copas/.luacheckrc @@ -0,0 +1,31 @@ +--std = "ngx_lua+busted" +unused_args = false +redefined = false +max_line_length = false + + +globals = { + --"_KONG", + --"kong", + --"ngx.IS_CLI", +} + + +not_globals = { + "string.len", + "table.getn", +} + + +ignore = { + --"6.", -- ignore whitespace warnings +} + + +exclude_files = { + ".install/**", + ".luarocks/**", + --"spec/fixtures/invalid-module.lua", + --"spec-old-api/fixtures/invalid-module.lua", +} + diff --git a/thirdparty/copas/.luacov b/thirdparty/copas/.luacov new file mode 100644 index 000000000..9f4740d1d --- /dev/null +++ b/thirdparty/copas/.luacov @@ -0,0 +1,4 @@ +modules = { + ["copas"] = "src/copas.lua", + ["copas.*"] = "src" +} diff --git a/thirdparty/copas/LICENSE b/thirdparty/copas/LICENSE new file mode 100644 index 000000000..f27ea9318 --- /dev/null +++ b/thirdparty/copas/LICENSE @@ -0,0 +1,21 @@ +Copyright © 2005-2013 Kepler Project, 2015-2023 Thijs Schreijer. + +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. diff --git a/thirdparty/copas/Makefile b/thirdparty/copas/Makefile new file mode 100644 index 000000000..7c0b584cd --- /dev/null +++ b/thirdparty/copas/Makefile @@ -0,0 +1,75 @@ +# $Id: Makefile,v 1.3 2007/10/29 22:50:16 carregal Exp $ + +DESTDIR ?= + +# Default prefix +PREFIX ?= /usr/local + +# System's lua directory (where Lua libraries are installed) +LUA_DIR ?= $(PREFIX)/share/lua/5.1 + +DELIM=-e "print(([[=]]):rep(70))" +PKGPATH=-e "package.path='src/?.lua;'..package.path" + +# Lua interpreter +LUA=lua + +.PHONY: certs + +install: + mkdir -p $(DESTDIR)$(LUA_DIR)/copas + cp src/copas.lua $(DESTDIR)$(LUA_DIR)/copas.lua + cp src/copas/ftp.lua $(DESTDIR)$(LUA_DIR)/copas/ftp.lua + cp src/copas/smtp.lua $(DESTDIR)$(LUA_DIR)/copas/smtp.lua + cp src/copas/http.lua $(DESTDIR)$(LUA_DIR)/copas/http.lua + cp src/copas/timer.lua $(DESTDIR)$(LUA_DIR)/copas/timer.lua + cp src/copas/lock.lua $(DESTDIR)$(LUA_DIR)/copas/lock.lua + cp src/copas/semaphore.lua $(DESTDIR)$(LUA_DIR)/copas/semaphore.lua + cp src/copas/queue.lua $(DESTDIR)$(LUA_DIR)/copas/queue.lua + +tests/certs/clientA.pem: + cd ./tests/certs && \ + ./rootA.sh && \ + ./rootB.sh && \ + ./serverA.sh && \ + ./serverB.sh && \ + ./clientA.sh && \ + ./clientB.sh && \ + cd ../.. + +certs: tests/certs/clientA.pem + +test: certs + $(LUA) $(DELIM) $(PKGPATH) tests/close.lua + $(LUA) $(DELIM) $(PKGPATH) tests/connecttwice.lua + $(LUA) $(DELIM) $(PKGPATH) tests/errhandlers.lua + $(LUA) $(DELIM) $(PKGPATH) tests/exit.lua + $(LUA) $(DELIM) $(PKGPATH) tests/exittest.lua + $(LUA) $(DELIM) $(PKGPATH) tests/http-timeout.lua + $(LUA) $(DELIM) $(PKGPATH) tests/httpredirect.lua + $(LUA) $(DELIM) $(PKGPATH) tests/largetransfer.lua + $(LUA) $(DELIM) $(PKGPATH) tests/lock.lua + $(LUA) $(DELIM) $(PKGPATH) tests/loop_starter.lua + $(LUA) $(DELIM) $(PKGPATH) tests/pause.lua + $(LUA) $(DELIM) $(PKGPATH) tests/queue.lua + $(LUA) $(DELIM) $(PKGPATH) tests/removeserver.lua + $(LUA) $(DELIM) $(PKGPATH) tests/removethread.lua + $(LUA) $(DELIM) $(PKGPATH) tests/request.lua 'http://www.google.com' + $(LUA) $(DELIM) $(PKGPATH) tests/request.lua 'https://www.google.nl' true + $(LUA) $(DELIM) $(PKGPATH) tests/semaphore.lua + $(LUA) $(DELIM) $(PKGPATH) tests/starve.lua + $(LUA) $(DELIM) $(PKGPATH) tests/tcptimeout.lua + $(LUA) $(DELIM) $(PKGPATH) tests/timer.lua + $(LUA) $(DELIM) $(PKGPATH) tests/timeout_errors.lua + $(LUA) $(DELIM) $(PKGPATH) tests/tls-sni.lua + $(LUA) $(DELIM) $(PKGPATH) tests/udptimeout.lua + $(LUA) $(DELIM) + +coverage: + $(RM) luacov.stats.out + $(MAKE) test LUA="$(LUA) -lluacov" + luacov + +clean: + $(RM) luacov.stats.out luacov.report.out + $(RM) tests/certs/*.pem tests/certs/*.srl diff --git a/thirdparty/copas/Makefile.win b/thirdparty/copas/Makefile.win new file mode 100644 index 000000000..33f733df2 --- /dev/null +++ b/thirdparty/copas/Makefile.win @@ -0,0 +1,16 @@ +# $Id: Makefile.win,v 1.5 2008/01/16 18:07:17 mascarenhas Exp $ + +LUA_DIR= c:\lua5.1\lua + +build clean: + +install: + mkdir "$(LUA_DIR)\copas" + copy src\copas.lua "$(LUA_DIR)\copas.lua" + copy src\copas\ftp.lua "$(LUA_DIR)\copas\ftp.lua" + copy src\copas\http.lua "$(LUA_DIR)\copas\http.lua" + copy src\copas\lock.lua "$(LUA_DIR)\copas\lock.lua" + copy src\copas\queue.lua "$(LUA_DIR)\copas\queue.lua" + copy src\copas\semaphore.lua "$(LUA_DIR)\copas\semaphore.lua" + copy src\copas\smtp.lua "$(LUA_DIR)\copas\smtp.lua" + copy src\copas\timer.lua "$(LUA_DIR)\copas\timer.lua" diff --git a/thirdparty/copas/README.md b/thirdparty/copas/README.md new file mode 100644 index 000000000..ef1e2b41a --- /dev/null +++ b/thirdparty/copas/README.md @@ -0,0 +1,36 @@ +# Copas 4.7 + +[![Unix build](https://img.shields.io/github/actions/workflow/status/lunarmodules/copas/unix_build.yml?branch=master&label=Unix%20build&logo=linux)](https://github.com/lunarmodules/copas/actions) +[![Coveralls code coverage](https://img.shields.io/coveralls/github/lunarmodules/copas?logo=coveralls)](https://coveralls.io/github/lunarmodules/copas) +[![Luacheck](https://github.com/lunarmodules/copas/workflows/Luacheck/badge.svg)](https://github.com/lunarmodules/copas/actions) +[![SemVer](https://img.shields.io/github/v/tag/lunarmodules/copas?color=brightgreen&label=SemVer&logo=semver&sort=semver)](CHANGELOG.md) +[![Licence](http://img.shields.io/badge/Licence-MIT-brightgreen.svg)](LICENSE) + +Copas is a dispatcher based on coroutines that can be used for asynchronous networking. For example TCP or UDP based servers. But it also features timers and client support for http(s), ftp and smtp requests. + +It uses [LuaSocket](https://github.com/diegonehab/luasocket) as the interface with the TCP/IP stack and [LuaSec](https://github.com/brunoos/luasec) for ssl support. + +A server or thread registered with Copas should provide a handler for requests and use Copas socket functions to send the response. Copas loops through requests and invokes the corresponding handlers. For a full implementation of a Copas HTTP server you can refer to [Xavante](http://keplerproject.github.io/xavante/) as an example. + +Copas is free software and uses the same license as Lua (MIT), and can be downloaded from [its GitHub page](https://github.com/lunarmodules/copas). + +The easiest way to install Copas is through [LuaRocks](https://luarocks.org/): + +``` +luarocks install copas +``` + +For more details see [the documentation](http://lunarmodules.github.io/copas/). + +### Releasing a new version + + - update changelog in docs (`index.html`, update `history` and `status` sections) + - update version in `copas.lua` + - update version at the top of this README, + - update copyright years if needed + - update rockspec + - commit as `release X.Y.Z` + - tag as `vX_Y_Z` and as `X.Y.Z` + - push commit and tag + - upload to luarocks + - test luarocks installation diff --git a/thirdparty/copas/bin/copas.lua b/thirdparty/copas/bin/copas.lua new file mode 100755 index 000000000..c0da5306f --- /dev/null +++ b/thirdparty/copas/bin/copas.lua @@ -0,0 +1,85 @@ +#!/usr/bin/env lua + +-- luacheck: globals copas +copas = require("copas") + + +-- Error handler that forces an application exit +local function errorhandler(err, co, skt) + io.stderr:write(copas.gettraceback(err, co, skt).."\n") + os.exit(1) +end + + +local function version_info() + print(copas._VERSION, copas._COPYRIGHT) + print(copas._DESCRIPTION) + print("Lua VM:", _G._VERSION) +end + + +local function load_lib(lib_name) + require(lib_name) +end + + +local function run_code(code) + if loadstring then -- deprecated in Lua 5.2 + assert(loadstring(code, "command line"))() + else + assert(load(code, "command line"))() + end +end + + +local function run_stdin() + assert(loadfile())() +end + + +local function run_file(filename, i) + -- shift arguments, such that the Lua file being executed is at index 0. The + -- first argument following the name is at index 1. + local last = #arg + local first = #arg + for idx, v in pairs(arg) do + if idx < first then first = idx end + end + for n = first - i, last do + arg[n] = arg[n+i] -- luacheck: ignore + end + assert(loadfile(filename))() +end + + +local function show_usage() + print([[ +usage: copas [options]... [script [args]...]. +Available options are: + -e chunk Execute string 'chunk'. + -l name Require library 'name'. + -v Show version information. + -- Stop handling options. + - Execute stdin and stop handling options.]]) + os.exit(1) +end + + +copas(function() + copas.seterrorhandler(errorhandler) + local i = 0 + while i < math.max(#arg, 1) do -- if no args, use 1 being 'nil' + i = i + 1 + local handled = false + local opt = arg[i] or "-" -- set default action if no args + -- options to continue handling + if opt == "-v" then version_info() handled = true end + if opt == "-l" then i = i + 1 load_lib(arg[i]) handled = true end + if opt == "-e" then i = i + 1 run_code(arg[i]) handled = true end + -- options that terminate handling + if opt == "--" then return end + if opt == "-" then return run_stdin() end + if opt:sub(1,1) == "-" and not handled then return show_usage() end + if not handled then return run_file(opt, i) end + end +end) diff --git a/thirdparty/copas/copas-cvs-6.rockspec b/thirdparty/copas/copas-cvs-6.rockspec new file mode 100644 index 000000000..7c4cc9c87 --- /dev/null +++ b/thirdparty/copas/copas-cvs-6.rockspec @@ -0,0 +1,56 @@ +local package_name = "copas" +local package_version = "cvs" +local rockspec_revision = "6" +local github_account_name = "lunarmodules" +local github_repo_name = package_name + + +package = package_name +version = package_version.."-"..rockspec_revision +source = { + url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", + branch = (package_version == "cvs") and "master" or nil, + tag = (package_version ~= "cvs") and package_version or nil, +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, +} +dependencies = { + "lua >= 5.1, < 5.5", + "luasocket >= 2.1, <= 3.0rc1-2", + "coxpcall >= 1.14", + "binaryheap >= 0.4", + "timerwheel ~> 1", +} +build = { + type = "builtin", + install = { + bin = { + copas = "bin/copas.lua", + } + }, + modules = { + ["copas"] = "src/copas.lua", + ["copas.http"] = "src/copas/http.lua", + ["copas.ftp"] = "src/copas/ftp.lua", + ["copas.smtp"] = "src/copas/smtp.lua", + ["copas.timer"] = "src/copas/timer.lua", + ["copas.lock"] = "src/copas/lock.lua", + ["copas.semaphore"] = "src/copas/semaphore.lua", + ["copas.queue"] = "src/copas/queue.lua", + }, + copy_directories = { + "docs", + }, +} diff --git a/thirdparty/copas/docs/copas.png b/thirdparty/copas/docs/copas.png new file mode 100644 index 000000000..af8fa59c2 --- /dev/null +++ b/thirdparty/copas/docs/copas.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87a7c297c258fd85a45204d9aa760303f27cb261d565548086af39d4ea9f1e12 +size 11688 diff --git a/thirdparty/copas/docs/doc.css b/thirdparty/copas/docs/doc.css new file mode 100644 index 000000000..f233ce4f0 --- /dev/null +++ b/thirdparty/copas/docs/doc.css @@ -0,0 +1,209 @@ +body { + color: #47555c; + font-size: 16px; + font-family: "Open Sans", sans-serif; + margin: 0; + padding: 0; + background: #eff4ff; +} + +a:link { color: #008fee; } +a:visited { color: #008fee; } +a:hover { color: #22a7ff; } + +h1 { font-size:26px; } +h2 { font-size:24px; } +h3 { font-size:18px; } +h4 { font-size:16px; } + +hr { + height: 1px; + background: #c1cce4; + border: 0px; + margin: 20px 0; +} + +code { + font-family: "Open Sans Mono", "Andale Mono", monospace; +} + +tt { + font-family: "Open Sans Mono", "Andale Mono", monospace; +} + +body, td, th { +} + +textarea, pre, tt { + font-family: "Open Sans Mono", "Andale Mono", monospace; +} + +img { + border-width: 0px; +} + +.example { + background-color: #323744; + color: white; + font-size: 16px; + padding: 16px 24px; + border-radius: 2px; +} + +div.header, div.footer { +} + +#container { +} + +#product { + background-color: white; + padding: 10px; + height: 130px; + border-bottom: solid #d3dbec 1px; +} + +#product big { + font-size: 42px; +} +#product strong { + font-weight: normal; +} + +#product_logo { + float: right; +} + +#product_name { + padding-top: 15px; + padding-left: 30px; + font-size: 42px; + font-weight: normal; +} + +#product_description { + padding-left: 30px; + color: #757779; +} + +#main { + background: #eff4ff; + margin: 0; +} + +#navigation { + width: 100%; + background-color: rgb(44,62,103); + padding: 10px; + margin: 0; +} + +#navigation h1 { + display: none; +} + +#navigation a:hover { + text-decoration: underline; +} + +#navigation ul li a { + color: rgb(136, 208, 255); + font-weight: bold; + text-decoration: none; +} + +#navigation ul li li a { + color: rgb(136, 208, 255); + font-weight: normal; + text-decoration: none; +} + +#navigation ul { + display: inline; + color: white; + padding: 0px; + padding-top: 10px; + padding-bottom: 10px; +} + +#navigation li { + display: inline; + list-style-type: none; + padding-left: 5px; + padding-right: 5px; +} + +#navigation li { + padding: 10px; + padding: 10px; +} + +#navigation li li { +} + +#navigation li:hover a { + color: rgb(166, 238, 255); +} + +#content { + padding: 20px; + width: 800px; + margin-left: auto; + margin-right: auto; +} + +#about { + display: none; +} + +dl.reference { + background-color: white; + padding: 20px; + border: solid #d3dbec 1px; +} + +dl.reference dt { + padding: 5px; + padding-top: 25px; + color: #637bbc; +} + +dl.reference dl dt { + padding-top: 5px; + color: #637383; +} + +dl.reference dd { +} + +@media print { + body { + font: 10pt "Times New Roman", "TimeNR", Times, serif; + } + a { + font-weight:bold; color: #004080; text-decoration: underline; + } + #main { + background-color: #ffffff; border-left: 0px; + } + #container { + margin-left: 2%; margin-right: 2%; background-color: #ffffff; + } + #content { + margin-left: 0px; padding: 1em; border-left: 0px; border-right: 0px; background-color: #ffffff; + } + #navigation { + display: none; + } + #product_logo { + display: none; + } + #about img { + display: none; + } + .example { + font-family: "Andale Mono", monospace; + font-size: 8pt; + page-break-inside: avoid; + } +} diff --git a/thirdparty/copas/docs/index.html b/thirdparty/copas/docs/index.html new file mode 100644 index 000000000..0c96c8d20 --- /dev/null +++ b/thirdparty/copas/docs/index.html @@ -0,0 +1,395 @@ + + + + Copas - Coroutine Oriented Portable Asynchronous Services for Lua + + + + +
+ +
+ +
Copas
+
Coroutine Oriented Portable Asynchronous Services for Lua
+
+ + +
+ + + +
+ +

Overview

+ +

+Copas is a dispatcher based on coroutines that can be used for asynchroneous +networking. For example TCP or UDP based servers. But it also features timers +and client support for http(s), ftp and smtp requests. +

+ +

+It uses LuaSocket +as the interface with the TCP/IP stack and LuaSec for ssl +support. +

+ +

+A server or thread registered with Copas should provide a handler for requests and use +Copas socket functions to send the response. +Copas loops through requests and invokes the corresponding handlers. +For a full implementation of a Copas HTTP server you can refer to +Xavante as an example. +

+ +

Copas is free software and uses the same license +as Lua 5.1 to 5.4

+ + +

Status

+ +

Current version is 4.7.0 and was developed for Lua 5.1 to 5.4.

+ +

Download

+ +

+Copas can be downloaded from its +Github page, in + the "Downloads" tab. +

+ +

You can also install Copas using LuaRocks:

+ +
+luarocks install copas
+
+ + +

Dependencies

+ +

Copas depends on +LuaSocket, Coxpcall (only when using Lua 5.1), and (optionally) LuaSec. +

+ +

History

+ +
+
Copas 4.7.x [unreleased]
+
    +
  • Fix: copas.removethread would not remove a sleeping thread immediately (it would not execute, but + would prevent the Copas loop from exiting until the timer expired).
  • +
+ +
Copas 4.7.0 [15/Jan/2023]
+
    +
  • Fix: windows makefile didn't include all submodules.
  • +
  • Fix: creating a new timer with a bad delay setting would throw a bad error message.
  • +
  • Refactor: submodules are now part of copas (lazily loaded) and do not need to be required anymore
  • +
  • Feat: runtime script added to directly run copas based code
  • +
  • Fix: improved socket wrapper for lacking LuaSec methods (setoption, getoption, getpeername, getsockname)
  • +
  • Feat: added LuaSec methods to wrapper (getalpn, getsniname)
  • +
+ +
Copas 4.6.0 [30/Dec/2022]
+
    +
  • Added: for timeouts in copas, lock, semaphore, and queue, allow math.huge + to specify no timeout/wait forever. Using math.huge over long timeouts will + reduce pressure on the timer-wheel.
  • +
  • Refactor: increase ringsize (timer-wheel) from 1 minute to 1 day to reduce timer-wheel pressure.
  • +
+ +
Copas 4.5.0 [18/Dec/2022]
+
    +
  • Added: copas.status got an extra parameter to track more detailed stats.
  • +
  • Fix: queue workers would not properly handle falsy items in the queue. The worker would + exit instead of handle the item.
  • +
  • Fix: a non-reentrant lock should block instead of returning an error when entered again.
  • +
  • Fix: finishing a queue would not honour the timeout.
  • +
  • Refactor: more manual cleanup instead of relying on weak-tables.
  • +
+ +
Copas 4.4.0 [23/Oct/2022]
+
    +
  • Fix: an error in the timer callback would kill the timer.
  • +
  • Added: copas.geterrorhandler to retrieve the active errorhandler.
  • +
  • Added: option errorhandler for timer objects.
  • +
  • Added: copas.pause and copas.pauseforever to replace copas.sleep. The latter + method can accidentally sleep-forever if time arithmetic returns a negative result.
  • +
  • Added: copas.status which returns an object with number of tasks/timers/sockets.
  • +
  • Change: renamed copas.setErrorHandler to copas.seterrorhandler.
  • +
  • Change: renamed copas.useSocketTimeoutErrors to copas.usesockettimeouterrors.
  • +
+ +
Copas 4.3.2 [03/Oct/2022]
+
    +
  • Fix: error handler for timeouts. Underlying + bug is in coxpcall, and hence this only applies to PuC Lua 5.1.
  • +
+ +
Copas 4.3.1 [21/Sep/2022]
+
    +
  • Fix: with Lua 5.1 the timeouts would resume the wrapped (by coxpcall) + coroutines, instead of the original ones. Causing errors to bubble up one + level too many. +
  • +
+ +
Copas 4.3.0 [19/Sep/2022]
+
    +
  • Fix: when the loop is idle, do an occasional GC to clean out any lingering + non-closed sockets. This could prevent the loop from exiting.
  • +
  • Fix: in debug mode very large data is now truncated when displayed.
  • +
  • Fix: the receive methods could starve other threads on high-throughput.
  • +
  • Change: order of copas.addnamedthread args.
  • +
  • Fix: copas.receivepartial could return early with no data received + if the `prefix` parameter was specified.
  • +
  • Change: renamed copas.receivePartial to copas.receivepartial.
  • +
  • Added: sock:receivepartial to better process streaming TCP data.
  • +
  • fix: copas.receivepartial is now documented.
  • +
  • fix: copas.http was missing some error handling.
  • +
  • fix: Copas timeouts when receiving/sending would not return partial results, + or last bytes sent.
  • +
+ +
Copas 4.2.0 [06/Sep/2022]
+
    +
  • Change: pick up datagram size from newer LuaSocket versions.
  • +
  • Fix: non-recurring timer can now be armed again from its own handler.
  • +
  • Added: calling on the module table now invokes the copas.loop method.
  • +
+ +
Copas 4.1.0 [25/Aug/2022]
+
    +
  • Fix: handle errors thrown by the error handlers themselves.
  • +
  • Deps: Bump timerwheel to 1.0 (no changes, just a small fix)
  • +
  • Added: copas.gettraceback, previously internal to the default error handler, + now exposed to make it easier to write proper error handlers
  • +
  • Added: http-request now takes a timeout setting, previously it would always use the default value + of 30 seconds.
  • +
  • Added: the previously internal function for generating a TCP socket in the http-request module, + is now exported as http.getcreatefunc(). This allows to capture the socket used by the + request. When using streaming responses, for example with server-sent events, this can be used to modify + the timeouts, or for closing the stream.
  • +
  • Fix: empty queues were not destroyed properly and could prevent Copas from exiting
  • +
+ +
Copas 4.0.0 [29/Jul/2022]
+
    +
  • [breaking] Change: removed the "limitset". Its functionality can easily be recreated with + the new "queue" class, which is a better abstraction.
  • +
  • [breaking] Change: threads added through copas.addthread or + copas.addnamedthread will now be "scheduled", instead of immediately started.
  • +
  • Fixed: yielding to the Copas scheduler from user-code now throws a proper error and + no longer breaks the loop. Breaking the loop could also happen if a thread returned with + at least 2 return values.
  • +
  • Fixed: wrongly auto-closing sockets. Upon exiting a coroutine, sockets would be automatically + closed. This should only be the case for accepted TCP connections on a TCP server socket. This caused issues + for sockets shared between threads.
    + [breaking]: this changes behavior, auto-close is now determined when accepting the connection, and no longer when + terminating the handler thread. This will only affect users that dynamically change copas.autoclose + at runtime.
  • +
  • Fixed: http requests would not set SNI defaults. Setting fields protocol, + options, and verify directly on the http options table is now deprecated. Instead + specify sslparams, similar to other SSL/TLS functions.
  • +
  • Added: added sempahore:destroy()
  • +
  • Added: copas.settimeouts, to set separate timeouts for connect, send, receive
  • +
  • Added: queue class, see module "copas.queue"
  • +
  • Added: names for sockets and coroutines: +
      +
    • copas.addserver() has a 4th argument; name, to name the server socket
    • +
    • copas.addnamedthread() is new and identical to copas.addthread(), + but also accepts a name
    • +
    • copas.setsocketname(), copas.getsocketname(), + copas.setthreadname(), copas.getthreadname() added to manage names
    • +
    • copas.debug.start() and copas.debug.end() to enable debug + logging for the scheduler itself.
    • +
    • copas.debug.socket() to enable debug logging for socket methods (experimental).
    • +
    +
  • +
+ +
Copas 3.0.0 [12/Nov/2021]
+
    +
  • [breaking] Change: copas.addserver() now uses the timeout value as a copas timeout, + instead of a luasocket timeout. The client sockets for incoming connections will + inherit the timeout from the server socket.
  • +
  • Added: support for SNI on TLS connections #81 (@amyspark)
  • +
  • Added: copas.settimeout() so Copas can manage its own timeouts instead of spinning forever (Patrick Barrett )
  • +
  • Added: timer class, see module "copas.timer"
  • +
  • Added: lock class, see module "copas.lock"
  • +
  • Added: semaphore class, see module "copas.semaphore"
  • +
  • Added: timeout interface copas.timeout()
  • +
  • Added: option to override the default errorhandler, and fixes to the handler
  • +
  • Added: copas.removethread() added to be able to forcefully remove a previously added thread
  • +
  • Added: copas.loop() now takes an optional initialization function
  • +
  • Fixed: closing sockets from another thread would make the read/write ops hang #104
  • +
  • Fixed: coxpcall dependency in limit.lua #63 (Francois Perrad)
  • +
  • Fixed: CI now generates the certificates for testing, on unix make can be used, on Windows generate them manually
  • +
  • Fixed: type in wrapped udp:setpeername was actually calling udp:getpeername
  • +
  • Fixed: default error handler didn't print the stacktrace
  • +
  • Fixed: small memory leak when sleeping until woken
  • +
  • Fixed: do not wrap udp:sendto() method, since udp send doesn't block
  • +
  • Change: performance improvement in big limit-sets (Francisco Castro)
  • +
  • Change: update deprecated tls default to tls 1.2 in (copas.http)
  • +
+ +
Copas 2.0.2 [2017]
+
    +
  • Added: copas.running flag
  • +
  • Fixed: fix for http request #53 (Peter Melnichenko)
  • +
  • Added: extra parameter keep_open for the removeserver() method (Hisham Muhammad)
  • +
  • Change: tweaked makefile with a DESTDIR variable (Richard Leitner)
  • +
+ +
Copas 2.0.1 [2016]
+
    +
  • Added: support for Lua 5.3 (no code changes, just rockspec update)
  • +
  • Fixed: yield across c boundary error (by Peter Melnichenko)
  • +
  • Fixed: bug in wrappers for setoption() and shutdown() (reported by Rob Probin)
  • +
+ +
Copas 2.0.0 [2015]
+
    +
  • Added: removeserver() function to remove servers from the scheduler (by Paul Kulchenko)
  • +
  • Added: client requests for http(s), ftp, and smtp (like LuaSocket/LuaSec, but async)
  • +
  • Added: transparent async support (handshake, and send/receive) for ssl using LuaSec
  • +
  • Added: handler() as a convenience for full copas and ssl wrapping
  • +
  • [breaking] Change: the loop now exits when there is nothing more to do
  • +
  • [breaking] Change: dummy first argument to new tasks removed
  • +
  • Fixed: completed the socket wrappers, missing functions were added
  • +
  • Fixed: connect issue, step() errorring out instead of returning nil + error
  • +
  • Fixed: UDP sockets being auto closed
  • +
  • Fixed: the receivePartial function for http request support (by Paul Kulchenko)
  • +
+ +
Copas 1.2.1 [2013]
+
    +
  • Fixed bad version constant
  • +
  • Fixed timer issue
  • +
  • updated documentation
  • +
+ +
Copas 1.2.0 [2013]
+
    +
  • Support for Lua 5.2
  • +
  • UDP support
  • +
  • suspending threads
  • +
  • other minor updates
  • +
+ +
Copas 1.1.6 [18/Mar/2010]
+
    +
  • Now checks to see if socket.http was required before copas +
  • +
+ +
Copas 1.1.5 [07/Apr/2009]
+
    +
  • Fixed bug reported by Sam Roberts on the + Kepler list + (found due to Xavante locking up on some POST requests) +
  • +
+ +
Copas 1.1.4 [10/Dec/2008]
+
    +
  • Fixed bug [#5372] + - copas.connect is semi-broken (found by Gary NG)
  • +
+ +
Copas 1.1.3 [19/May/2008]
+
    +
  • Using copcall instead of pcall in socket.protect + (feature request [#5274] by Gary NG)
  • +
+ +
Copas 1.1.2 [15/May/2008]
+
    +
  • Fixed Bug [#4249] + - bugs in copas.receive (found by Gary NG)
  • +
+ +
Copas 1.1.1 [13/Aug/2007]
+
+
    +
  • Compatible with Lua 5.1
  • +
  • Refactored by Thomas Harning Jr. (for more details check + Bug 766)
  • +
  • Patch by Gary NG concerning the handling of stopped sockets
  • +
+ +
+ +
Copas 1.1 [20/Sep/2006]
+
+ +
Copas 1.0 [17/May/2005]
+
+ +
Copas 1.0 Beta[17/Feb/2005]
+
    +
  • First public version
  • +
+
+ +

Credits

+ +

Copas was designed and implemented by André Carregal and +Javier Guerra as part of the +Kepler Project which +holds its copyright. Copas development had significative contributions from Diego Nehab, +Mike Pall, David Burgess, Leonardo Godinho, Thomas Harning Jr. and Gary NG.

+ + +

Contact us

+ +

For more information please +contact us. +Comments are welcome!

+ +

+You can also reach other Kepler developers and users on the Kepler Project +mailing list. +

+ +
+ +
+ + + +
+ + diff --git a/thirdparty/copas/docs/license.html b/thirdparty/copas/docs/license.html new file mode 100644 index 000000000..c9269ae13 --- /dev/null +++ b/thirdparty/copas/docs/license.html @@ -0,0 +1,100 @@ + + + + Copas License + + + + +
+ +
+ +
Copas
+
Coroutine Oriented Portable Asynchronous Services for Lua
+
+ + +
+ + + +
+

License

+

+Copas is free software: it can be used for both academic and +commercial purposes at absolutely no cost.

+

+The spirit of the license is that you are free to use Copas for +any purpose at no cost without having to ask us. The only +requirement is that if you do use Copas, then you should give us +credit by including the appropriate copyright notice somewhere in +your product or its documentation.

+

+Copas was designed and implemented by André Carregal and +Javier Guerra. The implementation is not derived from +licensed software.

+

+ +

+ +
+

Copyright © 2005-2013 Kepler Project, 2015-2023 Thijs Schreijer.

+

+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.

+
+ +
+ +
+

Valid XHTML 1.0!

+

$Id: license.html,v 1.17 2009/03/24 22:04:26 carregal Exp $

+
+ +
+ + + diff --git a/thirdparty/copas/docs/manual.html b/thirdparty/copas/docs/manual.html new file mode 100644 index 000000000..b056496f8 --- /dev/null +++ b/thirdparty/copas/docs/manual.html @@ -0,0 +1,527 @@ + + + + Copas - Coroutine Oriented Portable Asynchronous Services for Lua + + + + +
+ +
+ +
Copas
+
Coroutine Oriented Portable Asynchronous Services for Lua
+
+ + +
+ + + +
+ +

Installing

+ +

You can install Copas using LuaRocks:

+ +
+luarocks install copas
+
+

Note: LuaSec is not automatically installed as a dependency. If you want to use ssl with Copas, +you need to manually install LuaSec as well.

+ +

Runtime

+ +Copas can either be used as a regular Lua library, or as a runtime. A command line script that +acts as a runtime engine is included. + +When using the runtime, the library is available as a global (copas), and the +scheduler will automatically be started. For example: + +
+  #!/usr/bin/env copas
+
+  local count = 0
+  copas.timer.new {
+    delay = 1,
+    recurring = true,
+    callback = function(self)
+      count = count + 1
+      print('hello world ' .. count)
+      if count >= 5 then
+        self:cancel()
+      end
+    end
+  }
+
+ +

Introduction to Copas

+ +

+Copas is a dispatcher that can help a lot in the creation of servers based on +LuaSocket. +Here we present a quick introduction to Copas and how to implement a server with it. +

+ +

+Assuming you know how to implement the desired server protocol, the first thing you have +to do in order to create a Copas based server is create a server socket to receive the +client connections. To do this you have to bind a host and a port using LuaSocket: +

+ +
+server = socket.bind(host, port)
+
+ +

Then you have to create a handler function that implements the server protocol. +The handler function will be called with a socket for each client connection +and you can use copas.send() and copas.receive() on that socket to +exchange data with the client.

+ +

For example, a simple echo handler would be:

+ +
+function echoHandler(skt)
+  while true do
+    local data = copas.receive(skt)
+    if data == "quit" then
+      break
+    end
+    copas.send(skt, data)
+  end
+end
+
+ +

You may alternatively use copas.wrap() to let your code more close to a standard +LuaSocket use:

+ +
+function echoHandler(skt)
+  skt = copas.wrap(skt)
+  while true do
+    local data = skt:receive()
+    if data == "quit" then
+      break
+    end
+    skt:send(data)
+  end
+end
+
+ +

+To register the server socket with Copas and associate it with the corresponding +handler we do: +

+ +
+copas.addserver(server, echoHandler)
+
+ +

Finally, to start Copas and all the registered servers we just call:

+ +
+copas()
+
+ +

As long as every handler uses Copas's send and receive, +simultaneous connections will be handled transparently by Copas for every registered +server.

+ +

+Since Copas is coroutine based, using it within a Lua pcall or +xpcall context does not work with Lua 5.1 yielding. If you need to use +any of those functions in your handler we strongly suggest using +coxpcall, a coroutine safe +version of the Lua 5.1 protected calls. For an example of this usage please check Xavante. +

+ +

Why use Copas?

+ +

+For those who already have a server implemented, here is an explanation of why and +how to migrate to Copas. In a typical LuaSocket server usually there is a dispatcher +loop like the one below: +

+ +
+server = socket.bind(host, port)
+while true do
+  skt = server:accept()
+  handle(skt)
+end
+
+ +

Here handle is a function that implements the server protocol using LuaSocket's +socket functions:

+ +
+function handle(skt)
+  ...
+  -- gets some data from the client - "the request"
+  reqdata = skt:receive(pattern)
+  ...
+  -- sends some data to the client - "the response"
+  skt:send(respdata)
+  ...
+end
+
+ +

+The problem with that approach is that the dispatcher loop is doing a busy wait +and can handle just one connection at a time. To solve the busy waiting we can +use LuaSocket's socket.select(), like in:

+ +
+server = socket.bind(host, port)
+reading = {server}
+while true do
+  input = socket.select(reading)
+  skt = input:accept()
+  handle(skt)
+end
+
+ +

+While this helps our CPU usage, the server is still accepting only one client +connection at a time. To handle more than one client the server must be able to +multitask, and the solution usually involves some kind of threads.

+

The dispatcher loop then becomes something like:

+ +
+server = socket.bind(host, port)
+reading = {server}
+while true do
+  input = socket.select(reading)
+  skt = input:accept()
+  newthread(handle(skt))
+end
+
+ +

+where newthread is able to create a new thread that executes +independently the handler function.

+ +

+The use of threads in the new loop solves the multitasking problem but may +create another. Some platforms does not offer multithreading or maybe you +don't want to use threads at all. +

+ +

+If that is the case, using Lua's coroutines may help a lot, and that's +exactly what Copas does. Copas implements the dispatcher loop using coroutines +so the handlers can multitask without the use of threads.

+ +

Using Copas with an existing server

+ +

+If you already have a running server using some dispatcher like the previous +example, migrating to Copas is quite simple, usually consisting of just three +steps. +

+ +

+First each server socket and its corresponding handler function have to be registered +with Copas:

+ +
+server = socket.bind(host, port)
+copas.addserver(server, handle)
+
+ +

Secondly the server handler has to be adapted to use Copas. One solution +is to use Copas send and receive functions to receive +and send data to the client:

+ +
+function handle(skt)
+  ...
+  -- gets some data from the client - "the request"
+  reqdata = copas.receive(skt, pattern)
+  ...
+  -- sends some data to the client - "the response"
+  copas.send(skt, respdata)
+   ...
+end
+
+ +

The other alternative is to wrap the socket in a Copas socket. This +allows your handler code to remain basically the same:

+ +
+function handle(skt)
+  -- this line may suffice for your handler to work with Copas
+  skt = copas.wrap(skt)   -- or... skip this line and wrap `handle` using copas.handler()
+  -- now skt behaves like a LuaSocket socket but uses Copas'
+  ...
+  -- gets some data from the client - "the request"
+  reqdata = skt:receive(pattern)
+  ...
+  -- sends some data to the client - "the response"
+  skt:send(respdata)
+   ...
+end
+
+ +

Note that by default Copas might return different timeout errors than the +traditional Lua libraries. Checkout copas.useSocketTimeoutErrors() +for more information.

+ +

Finally, to run the dispatcher loop you just call:

+ +
+copas()
+
+ +

During the loop Copas' dispatcher accepts connections from clients and +automatically calls the corresponding handler functions.

+ +

Using UDP servers

+

Copas may also be used for UDP servers. Here is an example;

+
+local port = 51034
+local server = socket.udp()
+server:setsockname("*",port)
+
+function handler(skt)
+  skt = copas.wrap(skt)
+  print("UDP connection handler")
+
+  while true do
+    local s, err
+    print("receiving...")
+    s, err = skt:receive(2048)
+    if not s then
+      print("Receive error: ", err)
+      return
+    end
+    print("Received data, bytes:" , #s)
+  end
+end
+
+copas.addserver(server, handler, 1)
+copas()
+
+

For UDP sockets the receivefrom() and sendto() +methods are available, both for copas and when the socket is wrapped. These +methods cannot be used on TCP sockets.

+

IMPORTANT: UDP sockets do not have the notion of master and client sockets, so where a handler function can close the client socket for a TCP connection, a handler should never close a UDP socket, because the socket is the same as the server socket, hence closing it destroys the server.

+

NOTE: When using the copas.receive([size]) method +on a UDP socket, the size parameter is NOT optional as with regular +luasocket UDP sockets. This limitation is removed when the socket is wrapped +(it then defaults to 8192, the max UDP datagram size luasocket supports).

+ +

Adding tasks

+

Additional threads may be added to the scheduler, as long as they use the Copas send, receive or sleep methods. Below an example of a thread being added to create an outgoing TCP connection using Copas;

+
+local socket = require("socket")
+local copas = require("copas")
+
+local host = "127.0.0.1"
+local port = 10000
+
+local skt = socket.connect(host, port)
+skt:settimeout(0)  -- important: make it non-blocking
+
+copas.addthread(function()
+   while true do
+      print("receiving...")
+      local resp = copas.receive(skt, 6)
+      print("received:", resp or "nil")
+      if resp and resp:sub(1,4) == "quit" then
+         skt:close()
+         break
+      end
+   end
+end)
+
+copas()
+
+

The example connects, echoes whatever it receives and exits upon receiving 'quit'. For an +example passing arguments to a task, see the async http example below.

+ +

Creating timers

+

Timers can be created using the copas.timer module. +Below an example of a timer;

+
+local copas = require("copas")
+
+copas(function()
+   copas.timer.new({
+     delay = 1,                        -- delay in seconds
+     recurring = true,                 -- make the timer repeat
+     params = "hello world",
+     callback = function(timer_obj, params)
+       print(params)                   -- prints "hello world"
+       timer_obj:cancel()              -- cancel the timer after 1 occurence
+     end
+   })
+end)
+
+

The example simply prints a message once every second, but gets cancelled right after the first one.

+ +

Synchronization primitives

+

Since Copas allows to asynchroneously schedule tasks, synchronization might be required +to protect resources from concurrent access. In this case the copas.lock and +copas.semaphore classes +can be used. The lock/semaphore will ensure that the coroutine running will be yielded until +the protected resource becomes available, without blocking other threads. +

+
+local copas = require("copas")
+
+local lock = copas.lock.new()
+
+local function some_func()
+  local ok, err, wait = lock:get()
+  if not ok then
+    return nil, "we got error '" .. err .. "' after " .. wait .. " seconds"
+  end
+
+  print("doing something on my own")
+  copas.pause()  -- allow to yield, while inside the lock
+  print("after " .. ok .. " seconds waiting")
+
+  lock:release()
+end
+
+

The some_func function may now be called and the 2 lines will +be printed together because of the lock.

+ +

Ssl support

+

LuaSec is transparently integrated in the Copas scheduler (though must be installed separately when using LuaRocks).

+

+Here's an example for an incoming connection in a server scenario; +

+
+function handler(skt)
+  skt = copas.wrap(skt):dohandshake(sslparams)
+  -- skt = copas.wrap(skt, sslparams):dohandshake()  -- would be identical
+
+  while true do
+    -- perform the regular reading/writing ops on skt
+  end
+end
+
+

+A simpler handler would wrap the handler function to do the wrapping and +handshake before the handler gets called; +

+
+function handle(skt)
+  -- by now `skt` is copas wrapped, and has its handshake already completed
+
+  while true do
+    -- perform the regular reading/writing ops on skt
+  end
+end
+handle = copas.handler(handle, sslparams)  -- wraps the handler to auto wrap and handshake
+
+

+Here's an example for an outgoing request; +

+
+copas.addthread(function()
+  local skt = copas.wrap(socket.tcp(), sslparams)
+  skt:connect(host, port)  -- connecting will also perform the handshake on a wrapped socket
+
+  while true do
+
+    -- perform the regular reading/writing ops on skt
+
+  end
+end
+
+ +

High level requests

+ +

+For creating high level requests; http(s), ftp or smtp versions of the +methods are available that handle them async. As opposed to the original +LuaSocket and LuaSec implementations. +

+

+Below an example that schedules a number of http requests, then starts the Copas +loop to execute them. The loop exits when it's done. +

+ +
+local copas = require("copas")
+local asynchttp = require("copas.http").request
+
+local list = {
+  "http://www.google.com",
+  "http://www.microsoft.com",
+  "http://www.apple.com",
+  "http://www.facebook.com",
+  "http://www.yahoo.com",
+}
+
+local handler = function(host)
+  res, err = asynchttp(host)
+  print("Host done: "..host)
+end
+
+for _, host in ipairs(list) do copas.addthread(handler, host) end
+copas()
+
+ +

Controlling Copas

+ +

+If you do not want copas to simply enter an infinite loop (maybe you have to +respond to events from other sources, such as an user interface), you should +have your own loop and just call copas.step() at each iteration of +the loop: +

+ +
+while condition do
+  copas.step()
+  -- processing for other events from your system here
+end
+
+

+When using your own main loop, you should consider manually setting the +copas.running flag. +

+ + + +
+ +
+ +
+

Valid XHTML 1.0!

+

$Id: manual.html,v 1.19 2009/03/24 22:04:26 carregal Exp $

+
+ +
+ + diff --git a/thirdparty/copas/docs/reference.html b/thirdparty/copas/docs/reference.html new file mode 100644 index 000000000..2fd3b78a5 --- /dev/null +++ b/thirdparty/copas/docs/reference.html @@ -0,0 +1,1003 @@ + + + + Copas - Coroutine Oriented Portable Asynchronous Services for Lua + + + + + +
+ +
+ +
Copas
+
Coroutine Oriented Portable Asynchronous Services for Lua
+
+ +
+ + + +
+

Reference

+ +

+NOTE: Some functions require DNS lookups, which is handled internally +by LuaSocket. This is being done in a blocking manner. Hence every function +that accepts a hostname as an argument (e.g. tcp:connect(), +udp:sendto(), etc.) is potentially blocking on the DNS resolving part. +So either provide IP addresses (assuming the underlying OS will detect those and resolve +locally, non-blocking) or accept that the lookup might block. +

+ +

Getting started examples

+ +

Example for a server handling incoming connections:

+
+local copas = require("copas")
+local socket = require("socket")
+
+local address = "*"
+local port = 20000
+local ssl_params = {
+    wrap = {
+        mode = "server",
+        protocol = "any",  -- not really secure...
+    },
+}
+
+local server_socket = assert(socket.bind(address, port))
+
+local function connection_handler(skt)
+    local data, err = skt:receive()
+
+    -- do something
+
+end
+
+copas.addserver(server_socket, copas.handler(connection_handler,
+    ssl_params), "my_TCP_server")
+
+copas()
+
+ + +

Example for a client making a connection to a remote server:

+
+local copas = require("copas")
+local socket = require("socket")
+
+copas.addthread(function()
+    local port = 20000
+    local host = "somehost.com"
+    local ssl_params = {
+        wrap = {
+            mode = "client",
+            protocol = "any",  -- not really secure...
+        },
+    }
+
+    local sock = copas.wrap(socket.tcp(), ssl_params)
+    copas.setsocketname("my_TCP_client", sock)
+    assert(sock:connect(host, port))
+
+    local data, err = sock:receive("*l")
+
+    -- do something
+
+end)
+
+copas()
+
+ + +

Copas dispatcher main functions

+ +

The group of functions is relative to the use of the dispatcher itself and +are used to register servers and to execute the main loop of Copas:

+ +
+
copas([init_func, ][timeout])
+
+

This is a shortcut to copas.loop.

+
+ +
copas.addserver(server, handler [, timeout [, name]])
+
+

Adds a new server and its handler to the dispatcher + using an optional timeout.

+ +

server is a LuaSocket server socket created using socket.bind().

+ +

handler is a function that receives a LuaSocket client socket + and handles the communication with that client. + The handler will be executed in parallel with other threads and + registered handlers as long as it uses the Copas socket functions.

+ +

timeout is the timeout in seconds. Upon accepting connections, + the timeout will be inherited by TCP client sockets (only applies to TCP).

+ +

name is the internal name to use for this socket. The handler threads and + (in case of TCP) the incoming client connections will get a name derived from the server socket.

+ +

    +
  • TCP: client-socket name: "[server_name]:client_XX" and the handler thread + "[server_name]:handler_XX" where XX is a sequential number matching + between the client-socket and handler.
  • +
  • UDP: the handler thread will be named "[server_name]:handler"
  • +

+
+ +
coro = copas.addnamedthread(name, func [, ...])
+
+

Same as copas.addthread, but also names the new thread.

+
+ +
coro = copas.addthread(func [, ...])
+
+

Adds a function as a new coroutine/thread to the dispatcher. The optional + parameters will be passed to the function func.

+ +

The thread will be executed in parallel with other threads and the + registered handlers as long as it uses the Copas socket/sleep functions.

+ +

It returns the created coroutine.

+
+ +
copas.autoclose
+
+

Boolean that controls whether sockets are automatically closed (defaults to true). + This only applies to incoming connections accepted on a TCP server socket.

+ +

When a TCP handler function completes and terminates, then the client + socket will automatically be closed when copas.autoclose is + truthy.

+
+ +
bool = copas.finished()
+
+

Checks whether anything remains to be done.

+ +

Returns false when the socket lists for reading and writing + are empty and there is not another (sleeping) task to execute.

+ +

NOTE: when tasks or sockets have been scheduled/setup this + function will return true even if the loop has not yet started. + See also copas.running.

+
+ +
func = copas.geterrorhandler([coro])
+
+

Returns the currently active errorhandler function for the coroutine (either + the explicitly set errorhandler, or the default one). + coro will default to the currently running coroutine if omitted.

+
+ +
string = copas.getsocketname(skt)
+
+

Returns the name for the socket.

+
+ +
string = copas.getthreadname([co])
+
+

Returns the name for the coroutine/thread. If not given defaults to the + currently running coroutine.

+
+ +
string = copas.gettraceback([msg], [co], [skt])
+
+

Creates a traceback (string). Can be used from custom errorhandlers to create + a proper trace. See copas.seterrorhandler.

+
+ +
func = copas.handler(connhandler [, sslparams])
+
+

Wraps the connhandler function.

+ +

Returns a new function that + wraps the client socket, and (if sslparams is provided) performs + the ssl handshake, before calling connhandler.

+ +

See sslparams definition below.

+
+ +
copas.loop([init_func, ][timeout])
+
+

Starts the Copas loop accepting client connections for the + registered servers and handling those connections with the corresponding + handlers. Calling on the module table itself is a shortcut to this function. + Every time a server accepts a connection, Copas calls the + associated handler passing the client socket returned by + socket.accept().

+ +

The init_func function is an + optional initialization function that runs as a Copas thread + (with name "copas_initializer"). + The timeout parameter is optional, + and is passed to the copas.step() function.

+ +

The loop returns when copas.finished() == true.

+
+ +
result = copas.removeserver(skt [, keep_open])
+
+

Removes a server socket from the Copas scheduler. + By default, the socket will be closed to allow the socket to be reused right away after + removing the server. If keep_open is true, the socket + is removed from the scheduler but it is not closed.

+ +

Returns the result of skt:close() or true if the socket + was kept open.

+
+ +
copas.removethread(coroutine)
+
+

Removes a coroutine added to the Copas scheduler. + Takes a coroutine created by copas.addthread() and removes + it from the dispatcher the next time it tries to resume. If coroutine isn't + registered, it does nothing.

+
+ +
copas.running
+
+

A flag set to true when copas.loop() starts, and + reset to false when the loop exits. See also + copas.finished().

+
+ +
copas.seterrorhandler([func], [default])
+
+

Sets the error handling function for the current thread. + Any errors will be forwarded to this handler, it will receive the + error, coroutine, and socket as arguments; + function(err, co, skt). See the Copas source code + on how to deal with the arguments when implementing your own, and check + copas.gettraceback.

+ +

If func is omitted, then the error handler is cleared (restores the default handler + for this coroutine).

+ +

If default is truthy, then the handler will become the new default, used for all threads + that do not have their own set (in this case func must be provided).

+
+ +
copas.setsocketname(name, skt)
+
+

Sets the name for the socket.

+
+ +
copas.setthreadname(name [,co])
+
+

Sets the name for the coroutine/thread. co defaults to the + currently running coroutine.

+
+ +
skt = copas.wrap(skt [, sslparams] )
+
+

Wraps a LuaSocket socket and returns a Copas socket that implements LuaSocket's API + but use Copas' methods like copas.send() and copas.receive() + automatically. If the socket was already wrapped, then it will not + wrap it again.

+ +

If the sslparams is provided, then a call to the wrapped + sock:connect() method will automatically include the handshake (and in that + case connect() might throw an error instead of returning nil+error, see + copas.dohandshake()).

+ +

See sslparams definition below.

+
+ +
sslparams
+
+

This is the data-structure that is passed to the copas.handler, and + copas.wrap functions. Passing the structure will allow Copas to take + care of the entire TLS handshake process.

+ +

The structure is set up to mimic the LuaSec functions for the handshake.

+
+{
+  wrap = table | context,    -- parameter to LuaSec 'wrap()'
+  sni = {                    -- parameters to LuaSec 'sni()'
+    names = string | table   --   1st parameter
+    strict = bool            --   2nd parameter
+  }
+}
+
+
+
+ +

Non-blocking data exchange and timer/sleep functions

+ +

These are used by the handler functions to exchange data with +the clients, and by threads registered with addthread to +exchange data with other services.

+ +
+
copas.pause([delay])
+
+

Pauses the current co-routine. Parameter delay (in seconds) is optional + and defaults to 0. If delay <= 0 then it will pause for 0 seconds.

+
+ +
copas.pauseforever()
+
+

Pauses the current co-routine until explicitly woken by a call to copas.wakeup().

+
+ +
copas.sleep([sleeptime])
+
+

Deprecated: use copas.pause and copas.pauseforever instead.

+
+ +
copas.wakeup(co)
+
+

Immediately wakes up a coroutine that was sleeping or sleeping-forever. + co is the coroutine to wakeup, see copas.pause() + and copas.pauseforever(). Does nothing if the coroutine wasn't sleeping.

+
+ +
sock:close()
+
+

Equivalent to the LuaSocket method (after copas.wrap).

+
+ +
sock:connect(address, port)
+
+

Non-blocking equivalent to the LuaSocket method (after copas.wrap).

+ +

If sslparams was provided when wrapping the socket, the connect + method will also perform the full TLS handshake. So after connect returns + the connection will be secured.

+
+ +
sock:dohandshake(sslparams)
+
+

Non-blocking quivalent to the LuaSec method (after copas.wrap). Instead of using + this method, it is preferred to pass the sslparams to the functions + copas.handler (for incoming connections) and copas.wrap (for + outgoing connections), which then ensures that the connection will automatically be + secured when started.

+
+ +
sock:receive([pattern [, prefix]])
+
+

Non-blocking equivalent to the LuaSocket method (after copas.wrap). + Please see sock:receivepartial for differences with LuaSocket, especially + when using the "*a" pattern.

+
+ +
sock:receivefrom([size])
+
+

Reads data from a UDP socket just like LuaSocket, but non-blocking. + socket:receivefrom().

+
+ +
sock:receivepartial([pattern [, prefix]])
+
+

This method is the same as the receive method, the difference being + that this method will return on any data received, even if the specified pattern was not + yet satisfied.

+ +

When using delimited formats or known byte-size (pattern is "*l" or a number) + the regular receive method will usually be fine. But when reading a stream with + the "*a" pattern the receivepartial method should be used.

+ +

The reason for this is the difference in timeouts between Copas and LuaSocket. The Copas + timeout will apply on each underlying socket read/write operation. So on every chunk received + Copas will reset the timeout. So if reading pattern "*a" with a 10 second timeout, + and the sender sends a stream of data (unlimited size), in 1kb chunks, with 5 seconds intervals, + then there will never be a timeout when using receive, and hence the call would + never return.

+ +

If using receivepartial with the "*a" + pattern, the (repeated) call would return the 1kb chunks, with a "timeout" error.

+
+ +
sock:send(data [, i [, j]])
+
+

Non-blocking equivalent to the LuaSocket method (after copas.wrap).

+
+ +
sock:settimeout([timeout])
+
+

Sets the timeouts (in seconds) for a socket (after copas.wrap). + The default is to not have a timeout and wait indefinitely. If a timeout is hit, + the operation will return nil + "timeout". This method is compatible + with LuaSocket, but sets all three timeouts to the same value.

+

Behaviour: +

    +
  • nil: block indefinitely
  • +
  • number < 0: block indefinitely
  • +
  • number >= 0: timeout value in seconds
  • +
+ Important: this behaviour is the same as LuaSocket, but different from + sock:settimeouts, where nil means 'do not change' the timeout.

+
+ +
sock:settimeouts([connect], [send], [receive])
+
+

Sets the timeouts (in seconds) for a socket (after copas.wrap). A positive + number sets the timeout, a negative number removes the timeout, and nil will not + change the currently set timeout. The default is to not have a timeout and wait + indefinitely.

+

Behaviour: +

    +
  • nil: do not change the current setting
  • +
  • number < 0: block indefinitely
  • +
  • number >= 0: timeout value in seconds
  • +
+ Important: this behaviour is different from + sock:settimeout, where nil means 'wait indefinitely'.

+ +

If a timeout is hit, the operation will return nil + "timeout".

+
+ +
sock:sni(...)
+
+

Equivalent to the LuaSec method (after copas.wrap). Instead of using + this method, it is preferred to pass the sslparams to the functions + copas.handler (for incoming connections) and copas.wrap (for + outgoing connections), which then ensures that the connection will automatically be + secured when started.

+
+ +
lock:destroy()
+
+

Will destroy the lock and release all waiting threads. The result for those + threads will be nil + "destroyed" + wait_time, any new call on any + method will return nil + "destroyed" from here on.

+
+ +
lock:get([timeout])
+
+

Will try and acquire the lock. The optional timeout can be + used to override the timeout value set when the lock was created.

+ +

If the lock is not available, the coroutine will yield until either the + lock becomes available, or it times out. The one exception is when + timeout is 0, then it will immediately return without yielding. + If the timeout is set to math.huge, then it will wait forever.

+ +

Upon success, it will return the wait-time in seconds. Upon failure it will + return nil + error + wait-time. Upon a timeout the error value will + be "timeout".

+
+ +
copas.lock.new([timeout], [not_reentrant])
+
+

Creates and returns a new lock. The timeout specifies the + default timeout for the lock in seconds, and defaults to 10 (set it to math.huge + to wait forever).

+ +

By default the lock is re-entrant, + except if not_reentrant is set to a truthy value.

+
+ +
lock:release()
+
+

Releases the currently held lock.

+

Returns true or nil + error.

+
+ +
queue:add_worker(func)
+
+

Adds a worker that will handle whatever is passed into the queue. Can be called + multiple times to add more workers. The function func is wrapped and added + as a copas thread. The threads automatically exit when the queue is destroyed.

+ +

Worker function signature: function(item) (Note: worker functions run + unprotected, so wrap code in an (x)pcall if errors are expected, otherwise the + worker will exit on an error, and queue handling will stop).

+ +

Returns the coroutine added, or nil+"destroyed".

+
+ +
queue:destroy()
+
+

Destroys a queue immediately. Abandons what is left in the queue. + Releases all waiting calls to queue:pop() with nil+"destroyed". + Returns true, or nil+"destroyed".

+
+ +
queue:finish([timeout], [no_destroy_on_timeout])
+
+

Finishes a queue. Calls queue:stop() and then waits for the queue to run + empty (and be destroyed) before returning.

+ +

The timeout defaults to 10 seconds + (the default timeout value for a lock), math.huge can be used to wait forever.

+ +

Parameter no_destroy_on_timeout indicates if the queue is not to be forcefully + destroyed on a timeout (abandonning what ever is left in the queue).

+ +

Returns true, or nil+"timeout", or nil+"destroyed".

+
+ +
queue:get_size()
+
+

Gets the number of items in the queue currently. + Returns number or nil + "destroyed".

+
+ +
queue:get_workers()
+
+

Returns a list/array of current workers (coroutines) handling the queue + (only the workers added by queue:add_worker(), and still active, + will be in the list). Returns list or nil + "destroyed".

+
+ +
queue.name
+
+

A field set to name of the queue. See copas.queue.new().

+
+ +
copas.queue.new([options])
+
+

Creates and returns a new queue. The options table has the following fields: +

    +
  • options.name: (optional) the name for the queue, the default name will be + "copas_queue_XX". The name will be used to name any workers + added to the queue using queue:add_worker(), their name will be + "[queue_name]:worker_XX"
  • +

+
+ +
queue:pop([timeout])
+
+

Will pop an item from the queue. If there are no items in the queue it will yield + until there are or a timeout happens (exception is when timeout == 0, then it will + not yield but return immediately, be careful not to create a hanging loop!).

+ +

Timeout defaults to the default time-out of a semaphore. If the timeout is math.huge + then it will wait forever.

+ +

Returns an item, or nil+"timeout", or nil+"destroyed". Since an item + can be nil, make sure to check for the error message to detect errors.

+
+ +
queue:push(item)
+
+

Will push a new item in the queue. Item can be any type, including 'nil'.

+ +

Returns true or nil + "destroyed".

+
+ +
queue:stop()
+
+

Instructs the queue to stop, and returns immediately. It will no longer + accept calls to queue:push(), and will call queue:destroy() + once the queue is empty.

+ +

Returns true or nil + "destroyed".

+
+ +
semaphore:destroy()
+
+

Will destroy the sempahore and release all waiting threads. The result for those + threads will be nil + "destroyed", any new call on any + method will return nil + "destroyed" from here on.

+
+ +
semaphore:get_count()
+
+

Returns the number of resources currently available in the semaphore.

+
+ +
semaphore:get_wait()
+
+

Returns the total number of resources requested by all currently waiting threads minus + the available resources. Such that sempahore:give(semaphore:get_wait()) will + release all waiting threads and leave the semaphore with 0 resources. If there are no waiting threads + then the result will be 0, and the number of resources in the semaphore will be greater than or equal to 0.

+
+ +
semaphore:give([given])
+
+

Gives resources to the semaphore. Parameter given is the number of resources + given to the semaphore, if omitted it defaults to 1.

+ +

If the total resources in the semaphore exceed the maximum, then it will be capped at the + maximum. In that case the result will be nil + "too many".

+
+ +
copas.semaphore.new(max, [start], [timeout])
+
+

Creates and returns a new semaphore (fifo).

+ +

max specifies the maximum number of resources the semaphore can hold. + The optional start parameter (default 0) specifies the number of resources upon creation.

+ +

The timeout specifies the default timeout for the semaphore in + seconds, and defaults to 10 (math.huge can be used to wait forever).

+
+ +
semaphore:take([requested], [timeout])
+
+

Takes resources from the semaphore. Parameter requested is the number of resources + requested from the semaphore, if omitted it defaults to 1.

+ +

If not enough resources are available it + will yield and wait until enough resources are available, or a timeout occurs. The exception is when + timeout is set to 0, in that case it will immediately return without yielding if there are + not enough resources available.

+ +

The optional timeout parameter can be used to override the default timeout as set upon + semaphore creation. If the timeout is math.huge then it will wait forever.

+ +

Returns true upon success or nil + "timeout" on a timeout. In case more + resources are requested than maximum available the error will be "too many".

+
+ +
copas.timer.new(options)
+
+

Creates and returns an (armed) timer object. The options table has the following fields; +

    +
  • options.name (optional): the name for the timer, the default name will be + "copas_timer_XX". The name will be used to name the timer thread.
  • +
  • options.recurring (optional, default false): boolean
  • +
  • options.delay: expiry delay in seconds
  • +
  • options.initial_delay (optional): see timer:arm()
  • +
  • options.params (optional): an opaque value that is passed to the callback upon expiry
  • +
  • options.callback: is the function to execute on timer expiry. + The callback function has function(timer_obj, params) as signature, where + params is the value initially passed in options.params
  • +
  • options.errorhandler (optional): a Copas errorhandler function (see + copas.seterrorhandler for the signature.
  • +

+
+ +
timer:arm([initial_delay])
+
+

Arms a timer that was previously cancelled or exited (arming a non-recurring timer again + from its own handler is explicitly supported). Returns the timer.

+ +

The optional parameter initial_delay, determines the first delay. + For example a recurring timer with delay = 5, and initial_delay = 0 will + execute immediately and then recur every 5 seconds.

+
+ +
timer:cancel()
+
+

Will cancel the timer.

+
+ +
+ +

High level request functions

+ +

The last ones are the higher level client functions to perform requests to (remote) +servers.

+ +
+
copas.http.request(url [, body]) or
+ copas.http.request(requestparams)
+
+

Performs an http or https request, identical to the LuaSocket and LuaSec + implementations, but wrapped in an async operation. As opposed to the original + implementations, this one also allows for redirects cross scheme (http to https and + viceversa).

+ +

Options: +

    +
  • options.url: the URL for the request.
  • +
  • options.sink (optional): the LTN12 sink to pass the body chunks to.
  • +
  • options.method (optional, default "GET"): the http request method.
  • +
  • options.headers (optional): any additional HTTP headers to send with the request. + Hash-table, header-values by header-names.
  • +
  • options.source (optional): simple LTN12 source to provide the request body. + If there is a body, you need to provide an appropriate "content-length" request header field, + or the function will attempt to send the body as "chunked" (something few servers support). + Defaults to the empty source
  • +
  • options.step (optional): LTN12 pump step function used to move data. Defaults to + the LTN12 pump.step function.
  • +
  • proxy (optional, default none): The URL of a proxy server to use.
  • +
  • options.redirect (optional, default true): Set to false to prevent + GET or HEAD requests from automatically following 301, 302, 303, and 307 server redirect messages.
    + Note: https to http redirects are not allowed by default, but only when this option is set + to a string value "all".
  • +
  • options.create (optional): a function to be used instead of socket.tcp when the + communications socket is created.
  • +
  • options.maxredirects (optional, default 5): An optional number specifying the maximum number + of redirects to follow. A boolean false value means no maximum (unlimited).
  • +
  • options.timeout (optional, default 60): A number specifying the timeout for + connect/send/receive operations. Or a table with keys "connect", "send", and + "receive", to specify individual timeouts (keys omitted from the table will get a default of + 30).
  • +

+
+ +
copas.ftp.put(url, content) or
+ copas.ftp.put(requestparams)
+
+

Performs an ftp request, identical to the LuaSocket implementation, but wrapped in + an async operation.

+
+ +
copas.ftp.get(url) or
+ copas.ftp.get(requestparams)
+
+

Performs an ftp request, identical to the LuaSocket implementation, but wrapped in + an async operation.

+
+ +
copas.smtp.send(msgparams)
+
+

Sends an smtp request, identical to the LuaSocket implementation, but wrapped in + an async operation.

+
+ +
copas.smtp.message(msgt)
+
+

Just points to socket.smtp.message, provided so the copas.smtp + module is a drop-in replacement for the socket.smtp module.

+
+ +
+ +

Low level Copas functions

+ +

Most of these are wrapped in the socket wrapper functions, and wouldn't need + to be used by user code on a regular basis.

+ +
+
copas.close(skt)
+
+

Closes the socket. Any read/write operations in progress will return + with an error.

+
+ +
copas.connect(skt, address, port)
+
+

Connects and transforms a master socket to a client just like LuaSocket + socket:connect(). The Copas version does not block and allows + the multitasking of the other handlers and threads.

+
+ +
copas.dohandshake(skt, sslparams)
+
+

Performs an ssl handshake on an already connected TCP client socket. It + returns the new ssl-socket on success, or throws an error on failure.

+
+ +
copas.flush(skt)
+
+

(deprecated)

+
+ +
copas.receive(skt [, pattern [, prefix]]) (TCP) or
+ copas.receive(size) (UDP)
+
+

Reads data from a client socket according to a pattern just like LuaSocket + socket:receive(). The Copas version does not block and allows + the multitasking of the other handlers and threads.

+ +

Note: for UDP sockets the size parameter is NOT + optional. For the wrapped function socket:receive([size]) it is + optional again.

+
+ +
copas.receivepartial(skt [, pattern [, prefix]])
+
+

The same as receive, except that this method will return on + any data received. See sock:receivepartial for details.

+
+ +
copas.receivefrom(skt [, size])
+
+

Reads data from a UDP socket just like LuaSocket + socket:receivefrom(). The Copas version does not block and allows + the multitasking of the other handlers and threads.

+
+ +
copas.send(skt, data [, i [, j]]) (TCP) or
+ copas.send(skt, datagram) (UDP)
+
+

Sends data to a client socket just like socket:send(). The Copas version + is buffered and does not block, allowing the multitasking of the other handlers and threads.

+ +

Note: only for TCP, UDP send doesn't block, hence doesn't require this function to be used.

+
+ +
copas.settimeout(skt, [timeout])
+
+

Sets the timeout (in seconds) for a socket. A negative timout or absent timeout (nil) + will wait indefinitely.

+ +

Important: this behaviour is the same as LuaSocket, but different from + copas.settimeouts, where nil means 'do not change' the timeout.

+ +

If a timeout is hit, the operation will return nil + "timeout". + Timeouts are applied on: receive, receivefrom, receivepartial, send, connect, dohandshake.

+ +

See copas.usesockettimeouterrors() below for alternative error messages.

+
+ +
copas.settimeouts(skt, [connect], [send], [receive])
+
+

Sets the timeouts (in seconds) for a socket. The default is to not have a timeout and wait + indefinitely.

+ +

Important: this behaviour is different from copas.settimeout, + where nil means 'wait indefinitely'.

+ +

If a timeout is hit, the operation will return nil + "timeout". + Timeouts are applied on: receive, receivefrom, receivepartial, send, connect, dohandshake.

+ +

See copas.usesockettimeouterrors() below for alternative error messages.

+
+ +
t = copas.status([enable_stats])
+
+

Returns metadata about the current scheduler status. By default only number/type of tasks is being + reported. If enable_stats == true then detailed statistics will be enabled. Calling it again + later with enable_stats == false will disabled it again. +

    +
  • t.running: boolean, same as copas.running.
  • +
  • t.read: the number of tasks waiting to read on a socket.
  • +
  • t.write: the number of tasks waiting to write to a socket.
  • +
  • t.active: the number of tasks ready to resume.
  • +
  • t.timer: the number of timers for tasks (in the binary tree).
  • +
  • t.inactive: the number of tasks waiting to be woken up.
  • +
  • t.timeout: the number of timers for timeouts (in the timerwheel).
  • +
  • t.time_start*: measurement time started (seconds, previous call to status).
  • +
  • t.time_end*: measurement time ended (seconds, now, current call to status).
  • +
  • t.time_avg*: average time per step (millisec).
  • +
  • t.steps*: the number of loop steps executed.
  • +
  • t.duration_tot*: total time spent executing (millisec, processing tasks, excluding waiting for the network select call).
  • +
  • t.duration_min*: smallest execution time per step (millisec).
  • +
  • t.duration_min_ever*: smallest execution time per step ever (millisec).
  • +
  • t.duration_max*: highest execution time per step (millisec).
  • +
  • t.duration_max_ever*: highest execution time per step ever (millisec).
  • +
  • t.duration_avg*: average execution time per step (millisec).
  • +
+ The properties marked with * will only be reported if detailed statistics are enabled. + Every call to this function will reset the time and duration statistics (except for the "ever" ones).

+
+ + +
bool = copas.step([timeout])
+
+

Executes one copas iteration accepting client connections for the + registered servers and handling those connections with the corresponding + handlers. When a server accepts a connection, Copas calls the + associated handler passing the client socket returned by + socket.accept(). The timeout parameter is optional. + It returns false when no data was handled (timeout) or + true if there was data handled (or alternatively nil + error + message in case of errors).

+ +

NOTE: the copas.running flag will not automatically + be (un)set. So when using your own main loop, consider manually setting the flag.

+
+ +
copas.timeout(delay [, callback])
+
+

Creates a timeout timer for the current coroutine. The delay + is the timeout in seconds, and the callback will + be called upon an actual timeout occuring.

+ Calling it with delay = 0 (or math.huge) will cancel the timeout.

+ Calling it repeatedly will simply replace the timeout on the current + coroutine and any previous callback set will no longer be called.

+ +

NOTE: The timeouts are a low-level Copas feature, and + should only be used to wrap an explicit yield to the Copas scheduler. They + should not be used to wrap user code.

+ +

Example usage:

+
+local copas = require "copas"
+local result = "nothing"
+
+copas(function()
+
+  local function timeout_handler(co)  -- co will be the coroutine from which 'timeout()' was called
+    print("executing timeout handler")
+    result = "timeout"
+    copas.removethread(co)            -- drop the thread, because we timed out
+  end
+
+  copas.addthread(function()
+    copas.timeout(5, timeout_handler) -- timeout on the current coroutine after 5 seconds
+    copas.pause(10)                   -- sleep for 10 seconds
+    print("just woke up")
+    result = "slept like a baby"
+    copas.timeout(0)                  -- cancel the timeout on the current coroutine
+  end)
+end)
+
+print("result: ", result)
+
+

For usage examples see the lock and semaphore + implementations.

+
+ +
copas.usesockettimeouterrors([bool])
+
+

Sets the timeout errors to return for the current co-routine. + The default is false, meaning + that a timeout error will always return an error string "timeout". + If you are porting an existing application to Copas and want LuaSocket or LuaSec + compatible error messages then set it to true.

+ +

In case of using socket timeout errors, they can also be "wantread" + or "wantwrite" when using ssl/tls connections. These can be returned at any point + if during a read or write operation an ssl-renegotiation happens.

+ +

Due to platform difference the connect method may also return + "Operation already in progress" as a timeout error message.

+
+ +
+ +

Copas debugging functions

+ +

These functions are mainly used for debugging Copas itself and should be considered experimental.

+ +
+ +
copas.debug.start([logger] [, core])
+
+

This will internally replace coroutine handler functions to provide log output to + the provided logger function. The logger signature is function(...) + and the default value is the global print() function.

+ +

If the core parameter is truthy, then also the Copas core timer will be + logged (very noisy).

+
+ +
copas.debug.stop()
+
+

Stops the debug output being generated.

+
+ +
debug_socket = copas.debug.socket(socket)
+
+

This wraps the socket in a debug-wrapper. This will for each method call on the + socket object print the method, the parameters and the return values. Check the source + code on how to add additional introspection using extra callbacks etc.

+

Extremely noisy and experimental!

+

NOTE 1: for TLS you'll probably need to first do the TLS handshake.

+

NOTE 2: this is separate from the other debugging functions.

+
+ +
+ + +
+ +
+ +
+

Valid XHTML 1.0!

+

$Id: reference.html,v 1.16 2009/04/07 21:34:52 carregal Exp $

+
+ +
+ + diff --git a/thirdparty/copas/misc/cosocket.lua b/thirdparty/copas/misc/cosocket.lua new file mode 100644 index 000000000..3ebc9ceb1 --- /dev/null +++ b/thirdparty/copas/misc/cosocket.lua @@ -0,0 +1,44 @@ +------------------------------------------------------------------------------- +-- Copas - Coroutine Oriented Portable Asynchronous Services +-- +-- Copas Wrapper for socket.http module +-- +-- Written by Leonardo Godinho da Cunha +------------------------------------------------------------------------------- +local copas = require("copas") +local socket = require("socket") + +local cosocket = {} + +-- Meta information is public even begining with an "_" +cosocket._COPYRIGHT = "Copyright (C) 2004-2006 Kepler Project" +cosocket._DESCRIPTION = "Coroutine Oriented Portable Asynchronous Services Wrapper for socket module" +cosocket._NAME = "Copas.cosocket" +cosocket._VERSION = "0.1" + +function cosocket.tcp () + local skt = socket.tcp() + local w_skt_mt = { __index = skt } + local ret_skt = setmetatable ({ socket = skt }, w_skt_mt) + ret_skt.settimeout = function (self,val) + return self.socket:settimeout (val) + end + ret_skt.connect = function (self,host, port) + local ret,err = copas.connect (self.socket,host, port) + local d = copas.wrap(self.socket) + + self.send= function(client, data) + local ret,val=d.send(client, data) + return ret,val + end + self.receive=d.receive + self.close = function (w_socket) + ret=w_socket.socket:close() + return ret + end + return ret,err + end + return ret_skt +end + +return cosocket diff --git a/thirdparty/copas/misc/echoserver.lua b/thirdparty/copas/misc/echoserver.lua new file mode 100644 index 000000000..89323951c --- /dev/null +++ b/thirdparty/copas/misc/echoserver.lua @@ -0,0 +1,24 @@ +-- Tests Copas with a simple Echo server +-- +-- Run the test file and the connect to the server using telnet on the used port. +-- The server should be able to echo any input, to stop the test just send the command "quit" + +local copas = require("copas") +local socket = require("socket") + +local function echoHandler(skt) + skt = copas.wrap(skt) + while true do + local data = skt:receive() + if not data or data == "quit" then + break + end + skt:send(data) + end +end + +local server = socket.bind("localhost", 20000) + +copas.addserver(server, echoHandler) + +copas.loop() diff --git a/thirdparty/copas/misc/testasyncspeed.lua b/thirdparty/copas/misc/testasyncspeed.lua new file mode 100644 index 000000000..37eea7620 --- /dev/null +++ b/thirdparty/copas/misc/testasyncspeed.lua @@ -0,0 +1,58 @@ +local copas = require("copas") +local synchttp = require("socket.http").request +local asynchttp = copas.http.request +local gettime = require("socket").gettime + +local targets = { + "http://www.google.com", + "http://www.microsoft.com", + "http://www.apple.com", + "http://www.facebook.com", + "http://www.yahoo.com", +} + +local function sync(list) + for _, host in ipairs(list) do + local res = synchttp(host) + if not res then + print("Error sync: "..host.." failed, rerun test!") + else + print("Sync host done: "..host) + end + end +end + +local handler = function(host) + local res = asynchttp(host) + if not res then + print("Error async: "..host.." failed, rerun test!") + else + print("Async host done: "..host) + end +end + +local function async(list) + for _, host in ipairs(list) do copas.addthread(handler, host) end + copas.loop() +end + +-- three times to remove caching differences +async(targets) +async(targets) +async(targets) +print("\nNow starting the real test...\n") + +local t1 = gettime() +print("Sync:") +sync(targets) +local t2 = gettime() +print("Async:") +async(targets) +local t3 = gettime() + +print("\nResults:") +print("========") +print(" Sync : ", t2-t1) +print(" Async: ", t3-t2) + + diff --git a/thirdparty/copas/rockspec/copas-1.1.2-1.rockspec b/thirdparty/copas/rockspec/copas-1.1.2-1.rockspec new file mode 100644 index 000000000..be2c86b4b --- /dev/null +++ b/thirdparty/copas/rockspec/copas-1.1.2-1.rockspec @@ -0,0 +1,30 @@ +package = "Copas" +version = "1.1.2-1" +source = { + url = "http://luaforge.net/frs/download.php/3367/copas-1.1.2.tar.gz", +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "http://www.keplerproject.org/copas/" +} +dependencies = { + "lua >= 5.1", + "luasocket >= 2.0" +} +build = { + type = "make", + build_pass = false, + install_variables = { + LUA_DIR = "$(LUADIR)" + } +} diff --git a/thirdparty/copas/rockspec/copas-1.1.3-1.rockspec b/thirdparty/copas/rockspec/copas-1.1.3-1.rockspec new file mode 100644 index 000000000..96c452019 --- /dev/null +++ b/thirdparty/copas/rockspec/copas-1.1.3-1.rockspec @@ -0,0 +1,31 @@ +package = "Copas" +version = "1.1.3-1" +source = { + url = "http://luaforge.net/frs/download.php/3409/copas-1.1.3.tar.gz", +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "http://www.keplerproject.org/copas/" +} +dependencies = { + "lua >= 5.1", + "luasocket >= 2.0", + "coxpcall >= 1.13", +} +build = { + type = "make", + build_pass = false, + install_variables = { + LUA_DIR = "$(LUADIR)" + } +} diff --git a/thirdparty/copas/rockspec/copas-1.1.4-1.rockspec b/thirdparty/copas/rockspec/copas-1.1.4-1.rockspec new file mode 100644 index 000000000..3185870ce --- /dev/null +++ b/thirdparty/copas/rockspec/copas-1.1.4-1.rockspec @@ -0,0 +1,28 @@ +package = "Copas" +version = "1.1.4-1" +source = { + url = "http://luaforge.net/frs/download.php/3896/copas-1.1.4.tar.gz", +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "http://www.keplerproject.org/copas/" +} +dependencies = { + "lua >= 5.1", + "luasocket >= 2.0", + "coxpcall >= 1.13", +} +build = { + type = "module", + modules = { copas = "src/copas/copas.lua" } +} diff --git a/thirdparty/copas/rockspec/copas-1.1.5-1.rockspec b/thirdparty/copas/rockspec/copas-1.1.5-1.rockspec new file mode 100644 index 000000000..e73246b64 --- /dev/null +++ b/thirdparty/copas/rockspec/copas-1.1.5-1.rockspec @@ -0,0 +1,28 @@ +package = "Copas" +version = "1.1.5-1" +source = { + url = "http://luaforge.net/frs/download.php/4027/copas-1.1.5.tar.gz", +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "http://www.keplerproject.org/copas/" +} +dependencies = { + "lua >= 5.1", + "luasocket >= 2.0", + "coxpcall >= 1.13", +} +build = { + type = "module", + modules = { copas = "src/copas/copas.lua" } +} diff --git a/thirdparty/copas/rockspec/copas-1.1.6-1.rockspec b/thirdparty/copas/rockspec/copas-1.1.6-1.rockspec new file mode 100644 index 000000000..f97f89a07 --- /dev/null +++ b/thirdparty/copas/rockspec/copas-1.1.6-1.rockspec @@ -0,0 +1,28 @@ +package = "Copas" +version = "1.1.6-1" +source = { + url = "http://github.com/downloads/keplerproject/copas/copas-1.1.6.tar.gz", +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "http://www.keplerproject.org/copas/" +} +dependencies = { + "lua >= 5.1", + "luasocket >= 2.0", + "coxpcall >= 1.13", +} +build = { + type = "builtin", + modules = { copas = "src/copas/copas.lua" } +} diff --git a/thirdparty/copas/rockspec/copas-1.2.0-1.rockspec b/thirdparty/copas/rockspec/copas-1.2.0-1.rockspec new file mode 100644 index 000000000..ee63d1446 --- /dev/null +++ b/thirdparty/copas/rockspec/copas-1.2.0-1.rockspec @@ -0,0 +1,29 @@ +package = "Copas" +version = "1.2.0-1" +source = { + url = "https://github.com/keplerproject/copas/archive/v1_2_0.tar.gz", + dir = "copas-1_2_0", +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "http://www.keplerproject.org/copas/" +} +dependencies = { + "lua >= 5.1, < 5.3", + "luasocket >= 2.1", + "coxpcall >= 1.14", +} +build = { + type = "builtin", + modules = { copas = "src/copas/copas.lua" } +} diff --git a/thirdparty/copas/rockspec/copas-1.2.1-1.rockspec b/thirdparty/copas/rockspec/copas-1.2.1-1.rockspec new file mode 100644 index 000000000..e181fb71f --- /dev/null +++ b/thirdparty/copas/rockspec/copas-1.2.1-1.rockspec @@ -0,0 +1,29 @@ +package = "Copas" +version = "1.2.1-1" +source = { + url = "https://github.com/keplerproject/copas/archive/v1_2_1.tar.gz", + dir = "copas-1_2_1", +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "http://www.keplerproject.org/copas/" +} +dependencies = { + "lua >= 5.1, < 5.3", + "luasocket >= 2.1", + "coxpcall >= 1.14", +} +build = { + type = "builtin", + modules = { copas = "src/copas/copas.lua" } +} diff --git a/thirdparty/copas/rockspec/copas-2.0.0-1.rockspec b/thirdparty/copas/rockspec/copas-2.0.0-1.rockspec new file mode 100644 index 000000000..b236a5ae5 --- /dev/null +++ b/thirdparty/copas/rockspec/copas-2.0.0-1.rockspec @@ -0,0 +1,35 @@ +package = "Copas" +version = "2.0.0-1" +source = { + url = "https://github.com/keplerproject/copas/archive/v2_0_0.tar.gz", + dir = "copas-2_0_0", +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "http://www.keplerproject.org/copas/" +} +dependencies = { + "lua >= 5.1, < 5.3", + "luasocket >= 2.1", + "coxpcall >= 1.14", +} +build = { + type = "builtin", + modules = { + ["copas"] = "src/copas.lua", + ["copas.http"] = "src/copas/http.lua", + ["copas.ftp"] = "src/copas/ftp.lua", + ["copas.smtp"] = "src/copas/smtp.lua", + ["copas.limit"] = "src/copas/limit.lua", + } +} diff --git a/thirdparty/copas/rockspec/copas-2.0.0-2.rockspec b/thirdparty/copas/rockspec/copas-2.0.0-2.rockspec new file mode 100644 index 000000000..c9c7a8a05 --- /dev/null +++ b/thirdparty/copas/rockspec/copas-2.0.0-2.rockspec @@ -0,0 +1,35 @@ +package = "Copas" +version = "2.0.0-2" +source = { + url = "https://github.com/keplerproject/copas/archive/v2_0_0.tar.gz", + dir = "copas-2_0_0", +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "http://www.keplerproject.org/copas/" +} +dependencies = { + "lua >= 5.1, < 5.4", + "luasocket >= 2.1", + "coxpcall >= 1.14", +} +build = { + type = "builtin", + modules = { + ["copas"] = "src/copas.lua", + ["copas.http"] = "src/copas/http.lua", + ["copas.ftp"] = "src/copas/ftp.lua", + ["copas.smtp"] = "src/copas/smtp.lua", + ["copas.limit"] = "src/copas/limit.lua", + } +} diff --git a/thirdparty/copas/rockspec/copas-2.0.1-1.rockspec b/thirdparty/copas/rockspec/copas-2.0.1-1.rockspec new file mode 100644 index 000000000..1d35b3244 --- /dev/null +++ b/thirdparty/copas/rockspec/copas-2.0.1-1.rockspec @@ -0,0 +1,35 @@ +package = "Copas" +version = "2.0.1-1" +source = { + url = "https://github.com/keplerproject/copas/archive/v2_0_1.tar.gz", + dir = "copas-2_0_1", +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "http://www.keplerproject.org/copas/" +} +dependencies = { + "lua >= 5.1, < 5.4", + "luasocket >= 2.1", + "coxpcall >= 1.14", +} +build = { + type = "builtin", + modules = { + ["copas"] = "src/copas.lua", + ["copas.http"] = "src/copas/http.lua", + ["copas.ftp"] = "src/copas/ftp.lua", + ["copas.smtp"] = "src/copas/smtp.lua", + ["copas.limit"] = "src/copas/limit.lua", + } +} diff --git a/thirdparty/copas/rockspec/copas-2.0.2-1.rockspec b/thirdparty/copas/rockspec/copas-2.0.2-1.rockspec new file mode 100644 index 000000000..9aa5ca39f --- /dev/null +++ b/thirdparty/copas/rockspec/copas-2.0.2-1.rockspec @@ -0,0 +1,35 @@ +package = "Copas" +version = "2.0.2-1" +source = { + url = "https://github.com/keplerproject/copas/archive/v2_0_2.tar.gz", + dir = "copas-2_0_2", +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "http://www.keplerproject.org/copas/" +} +dependencies = { + "lua >= 5.1, < 5.4", + "luasocket >= 2.1", + "coxpcall >= 1.14", +} +build = { + type = "builtin", + modules = { + ["copas"] = "src/copas.lua", + ["copas.http"] = "src/copas/http.lua", + ["copas.ftp"] = "src/copas/ftp.lua", + ["copas.smtp"] = "src/copas/smtp.lua", + ["copas.limit"] = "src/copas/limit.lua", + } +} diff --git a/thirdparty/copas/rockspec/copas-3.0.0-1.rockspec b/thirdparty/copas/rockspec/copas-3.0.0-1.rockspec new file mode 100644 index 000000000..d038a34e1 --- /dev/null +++ b/thirdparty/copas/rockspec/copas-3.0.0-1.rockspec @@ -0,0 +1,51 @@ +local package_name = "copas" +local package_version = "3.0.0" +local rockspec_revision = "1" +local github_account_name = "keplerproject" +local github_repo_name = package_name + + +package = package_name +version = package_version.."-"..rockspec_revision +source = { + url = "git://github.com/"..github_account_name.."/"..github_repo_name..".git", + branch = (package_version == "cvs") and "master" or nil, + tag = (package_version ~= "cvs") and package_version or nil, +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, +} +dependencies = { + "lua >= 5.1, < 5.4", + "luasocket >= 2.1", + "coxpcall >= 1.14", + "binaryheap >= 0.4", + "timerwheel >= 0.2", +} +build = { + type = "builtin", + modules = { + ["copas"] = "src/copas.lua", + ["copas.http"] = "src/copas/http.lua", + ["copas.ftp"] = "src/copas/ftp.lua", + ["copas.smtp"] = "src/copas/smtp.lua", + ["copas.limit"] = "src/copas/limit.lua", + ["copas.timer"] = "src/copas/timer.lua", + ["copas.lock"] = "src/copas/lock.lua", + ["copas.semaphore"] = "src/copas/semaphore.lua", + }, + copy_directories = { + "docs", + }, +} diff --git a/thirdparty/copas/rockspec/copas-3.0.0-2.rockspec b/thirdparty/copas/rockspec/copas-3.0.0-2.rockspec new file mode 100644 index 000000000..5fdb032c4 --- /dev/null +++ b/thirdparty/copas/rockspec/copas-3.0.0-2.rockspec @@ -0,0 +1,51 @@ +local package_name = "copas" +local package_version = "3.0.0" +local rockspec_revision = "2" +local github_account_name = "lunarmodules" +local github_repo_name = package_name + + +package = package_name +version = package_version.."-"..rockspec_revision +source = { + url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", + branch = (package_version == "cvs") and "master" or nil, + tag = (package_version ~= "cvs") and package_version or nil, +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, +} +dependencies = { + "lua >= 5.1, < 5.5", + "luasocket >= 2.1", + "coxpcall >= 1.14", + "binaryheap >= 0.4", + "timerwheel >= 0.2", +} +build = { + type = "builtin", + modules = { + ["copas"] = "src/copas.lua", + ["copas.http"] = "src/copas/http.lua", + ["copas.ftp"] = "src/copas/ftp.lua", + ["copas.smtp"] = "src/copas/smtp.lua", + ["copas.limit"] = "src/copas/limit.lua", + ["copas.timer"] = "src/copas/timer.lua", + ["copas.lock"] = "src/copas/lock.lua", + ["copas.semaphore"] = "src/copas/semaphore.lua", + }, + copy_directories = { + "docs", + }, +} diff --git a/thirdparty/copas/rockspec/copas-3.0.0-3.rockspec b/thirdparty/copas/rockspec/copas-3.0.0-3.rockspec new file mode 100644 index 000000000..f6c42566f --- /dev/null +++ b/thirdparty/copas/rockspec/copas-3.0.0-3.rockspec @@ -0,0 +1,51 @@ +local package_name = "copas" +local package_version = "3.0.0" +local rockspec_revision = "3" +local github_account_name = "lunarmodules" +local github_repo_name = package_name + + +package = package_name +version = package_version.."-"..rockspec_revision +source = { + url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", + branch = (package_version == "cvs") and "master" or nil, + tag = (package_version ~= "cvs") and package_version or nil, +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, +} +dependencies = { + "lua >= 5.1, < 5.5", + "luasocket >= 2.1, <= 3.0rc1-2", + "coxpcall >= 1.14", + "binaryheap >= 0.4", + "timerwheel >= 0.2", +} +build = { + type = "builtin", + modules = { + ["copas"] = "src/copas.lua", + ["copas.http"] = "src/copas/http.lua", + ["copas.ftp"] = "src/copas/ftp.lua", + ["copas.smtp"] = "src/copas/smtp.lua", + ["copas.limit"] = "src/copas/limit.lua", + ["copas.timer"] = "src/copas/timer.lua", + ["copas.lock"] = "src/copas/lock.lua", + ["copas.semaphore"] = "src/copas/semaphore.lua", + }, + copy_directories = { + "docs", + }, +} diff --git a/thirdparty/copas/rockspec/copas-4.0.0-1.rockspec b/thirdparty/copas/rockspec/copas-4.0.0-1.rockspec new file mode 100644 index 000000000..d8e86a5e8 --- /dev/null +++ b/thirdparty/copas/rockspec/copas-4.0.0-1.rockspec @@ -0,0 +1,51 @@ +local package_name = "copas" +local package_version = "4.0.0" +local rockspec_revision = "1" +local github_account_name = "lunarmodules" +local github_repo_name = package_name + + +package = package_name +version = package_version.."-"..rockspec_revision +source = { + url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", + branch = (package_version == "cvs") and "master" or nil, + tag = (package_version ~= "cvs") and package_version or nil, +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, +} +dependencies = { + "lua >= 5.1, < 5.5", + "luasocket >= 2.1, <= 3.0rc1-2", + "coxpcall >= 1.14", + "binaryheap >= 0.4", + "timerwheel >= 0.2", +} +build = { + type = "builtin", + modules = { + ["copas"] = "src/copas.lua", + ["copas.http"] = "src/copas/http.lua", + ["copas.ftp"] = "src/copas/ftp.lua", + ["copas.smtp"] = "src/copas/smtp.lua", + ["copas.timer"] = "src/copas/timer.lua", + ["copas.lock"] = "src/copas/lock.lua", + ["copas.semaphore"] = "src/copas/semaphore.lua", + ["copas.queue"] = "src/copas/queue.lua", + }, + copy_directories = { + "docs", + }, +} diff --git a/thirdparty/copas/rockspec/copas-4.1.0-1.rockspec b/thirdparty/copas/rockspec/copas-4.1.0-1.rockspec new file mode 100644 index 000000000..cb545751d --- /dev/null +++ b/thirdparty/copas/rockspec/copas-4.1.0-1.rockspec @@ -0,0 +1,51 @@ +local package_name = "copas" +local package_version = "4.1.0" +local rockspec_revision = "1" +local github_account_name = "lunarmodules" +local github_repo_name = package_name + + +package = package_name +version = package_version.."-"..rockspec_revision +source = { + url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", + branch = (package_version == "cvs") and "master" or nil, + tag = (package_version ~= "cvs") and package_version or nil, +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, +} +dependencies = { + "lua >= 5.1, < 5.5", + "luasocket >= 2.1, <= 3.0rc1-2", + "coxpcall >= 1.14", + "binaryheap >= 0.4", + "timerwheel ~> 1", +} +build = { + type = "builtin", + modules = { + ["copas"] = "src/copas.lua", + ["copas.http"] = "src/copas/http.lua", + ["copas.ftp"] = "src/copas/ftp.lua", + ["copas.smtp"] = "src/copas/smtp.lua", + ["copas.timer"] = "src/copas/timer.lua", + ["copas.lock"] = "src/copas/lock.lua", + ["copas.semaphore"] = "src/copas/semaphore.lua", + ["copas.queue"] = "src/copas/queue.lua", + }, + copy_directories = { + "docs", + }, +} diff --git a/thirdparty/copas/rockspec/copas-4.2.0-1.rockspec b/thirdparty/copas/rockspec/copas-4.2.0-1.rockspec new file mode 100644 index 000000000..662ecb767 --- /dev/null +++ b/thirdparty/copas/rockspec/copas-4.2.0-1.rockspec @@ -0,0 +1,51 @@ +local package_name = "copas" +local package_version = "4.2.0" +local rockspec_revision = "1" +local github_account_name = "lunarmodules" +local github_repo_name = package_name + + +package = package_name +version = package_version.."-"..rockspec_revision +source = { + url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", + branch = (package_version == "cvs") and "master" or nil, + tag = (package_version ~= "cvs") and package_version or nil, +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, +} +dependencies = { + "lua >= 5.1, < 5.5", + "luasocket >= 2.1, <= 3.0rc1-2", + "coxpcall >= 1.14", + "binaryheap >= 0.4", + "timerwheel ~> 1", +} +build = { + type = "builtin", + modules = { + ["copas"] = "src/copas.lua", + ["copas.http"] = "src/copas/http.lua", + ["copas.ftp"] = "src/copas/ftp.lua", + ["copas.smtp"] = "src/copas/smtp.lua", + ["copas.timer"] = "src/copas/timer.lua", + ["copas.lock"] = "src/copas/lock.lua", + ["copas.semaphore"] = "src/copas/semaphore.lua", + ["copas.queue"] = "src/copas/queue.lua", + }, + copy_directories = { + "docs", + }, +} diff --git a/thirdparty/copas/rockspec/copas-4.3.0-1.rockspec b/thirdparty/copas/rockspec/copas-4.3.0-1.rockspec new file mode 100644 index 000000000..37e8a9b66 --- /dev/null +++ b/thirdparty/copas/rockspec/copas-4.3.0-1.rockspec @@ -0,0 +1,51 @@ +local package_name = "copas" +local package_version = "4.3.0" +local rockspec_revision = "1" +local github_account_name = "lunarmodules" +local github_repo_name = package_name + + +package = package_name +version = package_version.."-"..rockspec_revision +source = { + url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", + branch = (package_version == "cvs") and "master" or nil, + tag = (package_version ~= "cvs") and package_version or nil, +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, +} +dependencies = { + "lua >= 5.1, < 5.5", + "luasocket >= 2.1, <= 3.0rc1-2", + "coxpcall >= 1.14", + "binaryheap >= 0.4", + "timerwheel ~> 1", +} +build = { + type = "builtin", + modules = { + ["copas"] = "src/copas.lua", + ["copas.http"] = "src/copas/http.lua", + ["copas.ftp"] = "src/copas/ftp.lua", + ["copas.smtp"] = "src/copas/smtp.lua", + ["copas.timer"] = "src/copas/timer.lua", + ["copas.lock"] = "src/copas/lock.lua", + ["copas.semaphore"] = "src/copas/semaphore.lua", + ["copas.queue"] = "src/copas/queue.lua", + }, + copy_directories = { + "docs", + }, +} diff --git a/thirdparty/copas/rockspec/copas-4.3.1-1.rockspec b/thirdparty/copas/rockspec/copas-4.3.1-1.rockspec new file mode 100644 index 000000000..5c35a73fa --- /dev/null +++ b/thirdparty/copas/rockspec/copas-4.3.1-1.rockspec @@ -0,0 +1,51 @@ +local package_name = "copas" +local package_version = "4.3.1" +local rockspec_revision = "1" +local github_account_name = "lunarmodules" +local github_repo_name = package_name + + +package = package_name +version = package_version.."-"..rockspec_revision +source = { + url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", + branch = (package_version == "cvs") and "master" or nil, + tag = (package_version ~= "cvs") and package_version or nil, +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, +} +dependencies = { + "lua >= 5.1, < 5.5", + "luasocket >= 2.1, <= 3.0rc1-2", + "coxpcall >= 1.14", + "binaryheap >= 0.4", + "timerwheel ~> 1", +} +build = { + type = "builtin", + modules = { + ["copas"] = "src/copas.lua", + ["copas.http"] = "src/copas/http.lua", + ["copas.ftp"] = "src/copas/ftp.lua", + ["copas.smtp"] = "src/copas/smtp.lua", + ["copas.timer"] = "src/copas/timer.lua", + ["copas.lock"] = "src/copas/lock.lua", + ["copas.semaphore"] = "src/copas/semaphore.lua", + ["copas.queue"] = "src/copas/queue.lua", + }, + copy_directories = { + "docs", + }, +} diff --git a/thirdparty/copas/rockspec/copas-4.3.2-1.rockspec b/thirdparty/copas/rockspec/copas-4.3.2-1.rockspec new file mode 100644 index 000000000..38ef4352e --- /dev/null +++ b/thirdparty/copas/rockspec/copas-4.3.2-1.rockspec @@ -0,0 +1,51 @@ +local package_name = "copas" +local package_version = "4.3.2" +local rockspec_revision = "1" +local github_account_name = "lunarmodules" +local github_repo_name = package_name + + +package = package_name +version = package_version.."-"..rockspec_revision +source = { + url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", + branch = (package_version == "cvs") and "master" or nil, + tag = (package_version ~= "cvs") and package_version or nil, +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, +} +dependencies = { + "lua >= 5.1, < 5.5", + "luasocket >= 2.1, <= 3.0rc1-2", + "coxpcall >= 1.14", + "binaryheap >= 0.4", + "timerwheel ~> 1", +} +build = { + type = "builtin", + modules = { + ["copas"] = "src/copas.lua", + ["copas.http"] = "src/copas/http.lua", + ["copas.ftp"] = "src/copas/ftp.lua", + ["copas.smtp"] = "src/copas/smtp.lua", + ["copas.timer"] = "src/copas/timer.lua", + ["copas.lock"] = "src/copas/lock.lua", + ["copas.semaphore"] = "src/copas/semaphore.lua", + ["copas.queue"] = "src/copas/queue.lua", + }, + copy_directories = { + "docs", + }, +} diff --git a/thirdparty/copas/rockspec/copas-4.4.0-1.rockspec b/thirdparty/copas/rockspec/copas-4.4.0-1.rockspec new file mode 100644 index 000000000..d5c6de52b --- /dev/null +++ b/thirdparty/copas/rockspec/copas-4.4.0-1.rockspec @@ -0,0 +1,51 @@ +local package_name = "copas" +local package_version = "4.4.0" +local rockspec_revision = "1" +local github_account_name = "lunarmodules" +local github_repo_name = package_name + + +package = package_name +version = package_version.."-"..rockspec_revision +source = { + url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", + branch = (package_version == "cvs") and "master" or nil, + tag = (package_version ~= "cvs") and package_version or nil, +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, +} +dependencies = { + "lua >= 5.1, < 5.5", + "luasocket >= 2.1, <= 3.0rc1-2", + "coxpcall >= 1.14", + "binaryheap >= 0.4", + "timerwheel ~> 1", +} +build = { + type = "builtin", + modules = { + ["copas"] = "src/copas.lua", + ["copas.http"] = "src/copas/http.lua", + ["copas.ftp"] = "src/copas/ftp.lua", + ["copas.smtp"] = "src/copas/smtp.lua", + ["copas.timer"] = "src/copas/timer.lua", + ["copas.lock"] = "src/copas/lock.lua", + ["copas.semaphore"] = "src/copas/semaphore.lua", + ["copas.queue"] = "src/copas/queue.lua", + }, + copy_directories = { + "docs", + }, +} diff --git a/thirdparty/copas/rockspec/copas-4.5.0-1.rockspec b/thirdparty/copas/rockspec/copas-4.5.0-1.rockspec new file mode 100644 index 000000000..6842a6e99 --- /dev/null +++ b/thirdparty/copas/rockspec/copas-4.5.0-1.rockspec @@ -0,0 +1,51 @@ +local package_name = "copas" +local package_version = "4.5.0" +local rockspec_revision = "1" +local github_account_name = "lunarmodules" +local github_repo_name = package_name + + +package = package_name +version = package_version.."-"..rockspec_revision +source = { + url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", + branch = (package_version == "cvs") and "master" or nil, + tag = (package_version ~= "cvs") and package_version or nil, +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, +} +dependencies = { + "lua >= 5.1, < 5.5", + "luasocket >= 2.1, <= 3.0rc1-2", + "coxpcall >= 1.14", + "binaryheap >= 0.4", + "timerwheel ~> 1", +} +build = { + type = "builtin", + modules = { + ["copas"] = "src/copas.lua", + ["copas.http"] = "src/copas/http.lua", + ["copas.ftp"] = "src/copas/ftp.lua", + ["copas.smtp"] = "src/copas/smtp.lua", + ["copas.timer"] = "src/copas/timer.lua", + ["copas.lock"] = "src/copas/lock.lua", + ["copas.semaphore"] = "src/copas/semaphore.lua", + ["copas.queue"] = "src/copas/queue.lua", + }, + copy_directories = { + "docs", + }, +} diff --git a/thirdparty/copas/rockspec/copas-4.6.0-1.rockspec b/thirdparty/copas/rockspec/copas-4.6.0-1.rockspec new file mode 100644 index 000000000..480d87305 --- /dev/null +++ b/thirdparty/copas/rockspec/copas-4.6.0-1.rockspec @@ -0,0 +1,51 @@ +local package_name = "copas" +local package_version = "4.6.0" +local rockspec_revision = "1" +local github_account_name = "lunarmodules" +local github_repo_name = package_name + + +package = package_name +version = package_version.."-"..rockspec_revision +source = { + url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", + branch = (package_version == "cvs") and "master" or nil, + tag = (package_version ~= "cvs") and package_version or nil, +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, +} +dependencies = { + "lua >= 5.1, < 5.5", + "luasocket >= 2.1, <= 3.0rc1-2", + "coxpcall >= 1.14", + "binaryheap >= 0.4", + "timerwheel ~> 1", +} +build = { + type = "builtin", + modules = { + ["copas"] = "src/copas.lua", + ["copas.http"] = "src/copas/http.lua", + ["copas.ftp"] = "src/copas/ftp.lua", + ["copas.smtp"] = "src/copas/smtp.lua", + ["copas.timer"] = "src/copas/timer.lua", + ["copas.lock"] = "src/copas/lock.lua", + ["copas.semaphore"] = "src/copas/semaphore.lua", + ["copas.queue"] = "src/copas/queue.lua", + }, + copy_directories = { + "docs", + }, +} diff --git a/thirdparty/copas/rockspec/copas-4.7.0-1.rockspec b/thirdparty/copas/rockspec/copas-4.7.0-1.rockspec new file mode 100644 index 000000000..20bb40435 --- /dev/null +++ b/thirdparty/copas/rockspec/copas-4.7.0-1.rockspec @@ -0,0 +1,56 @@ +local package_name = "copas" +local package_version = "4.7.0" +local rockspec_revision = "1" +local github_account_name = "lunarmodules" +local github_repo_name = package_name + + +package = package_name +version = package_version.."-"..rockspec_revision +source = { + url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", + branch = (package_version == "cvs") and "master" or nil, + tag = (package_version ~= "cvs") and package_version or nil, +} +description = { + summary = "Coroutine Oriented Portable Asynchronous Services", + detailed = [[ + Copas is a dispatcher based on coroutines that can be used by + TCP/IP servers. It uses LuaSocket as the interface with the + TCP/IP stack. A server registered with Copas should provide a + handler for requests and use Copas socket functions to send + the response. Copas loops through requests and invokes the + corresponding handlers. For a full implementation of a Copas + HTTP server you can refer to Xavante as an example. + ]], + license = "MIT/X11", + homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, +} +dependencies = { + "lua >= 5.1, < 5.5", + "luasocket >= 2.1, <= 3.0rc1-2", + "coxpcall >= 1.14", + "binaryheap >= 0.4", + "timerwheel ~> 1", +} +build = { + type = "builtin", + install = { + bin = { + copas = "bin/copas.lua", + } + }, + modules = { + ["copas"] = "src/copas.lua", + ["copas.http"] = "src/copas/http.lua", + ["copas.ftp"] = "src/copas/ftp.lua", + ["copas.smtp"] = "src/copas/smtp.lua", + ["copas.timer"] = "src/copas/timer.lua", + ["copas.lock"] = "src/copas/lock.lua", + ["copas.semaphore"] = "src/copas/semaphore.lua", + ["copas.queue"] = "src/copas/queue.lua", + }, + copy_directories = { + "docs", + }, +} diff --git a/thirdparty/copas/src/copas.lua b/thirdparty/copas/src/copas.lua new file mode 100644 index 000000000..021798231 --- /dev/null +++ b/thirdparty/copas/src/copas.lua @@ -0,0 +1,2008 @@ +------------------------------------------------------------------------------- +-- Copas - Coroutine Oriented Portable Asynchronous Services +-- +-- A dispatcher based on coroutines that can be used by TCP/IP servers. +-- Uses LuaSocket as the interface with the TCP/IP stack. +-- +-- Authors: Andre Carregal, Javier Guerra, and Fabio Mascarenhas +-- Contributors: Diego Nehab, Mike Pall, David Burgess, Leonardo Godinho, +-- Thomas Harning Jr., and Gary NG +-- +-- Copyright 2005-2013 - Kepler Project (www.keplerproject.org), 2015-2023 Thijs Schreijer +-- +-- $Id: copas.lua,v 1.37 2009/04/07 22:09:52 carregal Exp $ +------------------------------------------------------------------------------- + +if package.loaded["socket.http"] and (_VERSION=="Lua 5.1") then -- obsolete: only for Lua 5.1 compatibility + error("you must require copas before require'ing socket.http") +end +if package.loaded["copas.http"] and (_VERSION=="Lua 5.1") then -- obsolete: only for Lua 5.1 compatibility + error("you must require copas before require'ing copas.http") +end + + +local socket = require "socket" +local binaryheap = require "binaryheap" +local gettime = socket.gettime +local ssl -- only loaded upon demand + +local WATCH_DOG_TIMEOUT = 120 +local UDP_DATAGRAM_MAX = socket._DATAGRAMSIZE or 8192 +local TIMEOUT_PRECISION = 0.1 -- 100ms +local fnil = function() end + + +local coroutine_create = coroutine.create +local coroutine_running = coroutine.running +local coroutine_yield = coroutine.yield +local coroutine_resume = coroutine.resume +local coroutine_status = coroutine.status + + +-- nil-safe versions for pack/unpack +local _unpack = unpack or table.unpack +local unpack = function(t, i, j) return _unpack(t, i or 1, j or t.n or #t) end +local pack = function(...) return { n = select("#", ...), ...} end + + +local pcall = pcall +if _VERSION=="Lua 5.1" and not jit then -- obsolete: only for Lua 5.1 compatibility + pcall = require("coxpcall").pcall + coroutine_running = require("coxpcall").running +end + + +do + -- Redefines LuaSocket functions with coroutine safe versions (pure Lua) + -- (this allows the use of socket.http from within copas) + local err_mt = { + __tostring = function (self) + return "Copas 'try' error intermediate table: '"..tostring(self[1].."'") + end, + } + + local function statusHandler(status, ...) + if status then return ... end + local err = (...) + if type(err) == "table" and getmetatable(err) == err_mt then + return nil, err[1] + else + error(err) + end + end + + function socket.protect(func) + return function (...) + return statusHandler(pcall(func, ...)) + end + end + + function socket.newtry(finalizer) + return function (...) + local status = (...) + if not status then + pcall(finalizer or fnil, select(2, ...)) + error(setmetatable({ (select(2, ...)) }, err_mt), 0) + end + return ... + end + end + + socket.try = socket.newtry() +end + + +-- Setup the Copas meta table to auto-load submodules and define a default method +local copas do + local submodules = { "ftp", "http", "lock", "queue", "semaphore", "smtp", "timer" } + for i, key in ipairs(submodules) do + submodules[key] = true + submodules[i] = nil + end + + copas = setmetatable({},{ + __index = function(self, key) + if submodules[key] then + self[key] = require("copas."..key) + submodules[key] = nil + return rawget(self, key) + end + end, + __call = function(self, ...) + return self.loop(...) + end, + }) +end + + +-- Meta information is public even if beginning with an "_" +copas._COPYRIGHT = "Copyright (C) 2005-2013 Kepler Project, 2015-2023 Thijs Schreijer" +copas._DESCRIPTION = "Coroutine Oriented Portable Asynchronous Services" +copas._VERSION = "Copas 4.7.0" + +-- Close the socket associated with the current connection after the handler finishes +copas.autoclose = true + +-- indicator for the loop running +copas.running = false + + +------------------------------------------------------------------------------- +-- Object names, to track names of thread/coroutines and sockets +------------------------------------------------------------------------------- +local object_names = setmetatable({}, { + __mode = "k", + __index = function(self, key) + local name = tostring(key) + if key ~= nil then + rawset(self, key, name) + end + return name + end +}) + +------------------------------------------------------------------------------- +-- Simple set implementation +-- adds a FIFO queue for each socket in the set +------------------------------------------------------------------------------- + +local function newsocketset() + local set = {} + + do -- set implementation + local reverse = {} + + -- Adds a socket to the set, does nothing if it exists + -- @return skt if added, or nil if it existed + function set:insert(skt) + if not reverse[skt] then + self[#self + 1] = skt + reverse[skt] = #self + return skt + end + end + + -- Removes socket from the set, does nothing if not found + -- @return skt if removed, or nil if it wasn't in the set + function set:remove(skt) + local index = reverse[skt] + if index then + reverse[skt] = nil + local top = self[#self] + self[#self] = nil + if top ~= skt then + reverse[top] = index + self[index] = top + end + return skt + end + end + + end + + do -- queues implementation + local fifo_queues = setmetatable({},{ + __mode = "k", -- auto collect queue if socket is gone + __index = function(self, skt) -- auto create fifo queue if not found + local newfifo = {} + self[skt] = newfifo + return newfifo + end, + }) + + -- pushes an item in the fifo queue for the socket. + function set:push(skt, itm) + local queue = fifo_queues[skt] + queue[#queue + 1] = itm + end + + -- pops an item from the fifo queue for the socket + function set:pop(skt) + local queue = fifo_queues[skt] + return table.remove(queue, 1) + end + + end + + return set +end + + + +-- Threads immediately resumable +local _resumable = {} do + local resumelist = {} + + function _resumable:push(co) + resumelist[#resumelist + 1] = co + end + + function _resumable:clear_resumelist() + local lst = resumelist + resumelist = {} + return lst + end + + function _resumable:done() + return resumelist[1] == nil + end + + function _resumable:count() + return #resumelist + #_resumable + end + +end + + + +-- Similar to the socket set above, but tailored for the use of +-- sleeping threads +local _sleeping = {} do + + local heap = binaryheap.minUnique() + local lethargy = setmetatable({}, { __mode = "k" }) -- list of coroutines sleeping without a wakeup time + + + -- Required base implementation + ----------------------------------------- + _sleeping.insert = fnil + _sleeping.remove = fnil + + -- push a new timer on the heap + function _sleeping:push(sleeptime, co) + if sleeptime < 0 then + lethargy[co] = true + elseif sleeptime == 0 then + _resumable:push(co) + else + heap:insert(gettime() + sleeptime, co) + end + end + + -- find the thread that should wake up to the time, if any + function _sleeping:pop(time) + if time < (heap:peekValue() or math.huge) then + return + end + return heap:pop() + end + + -- additional methods for time management + ----------------------------------------- + function _sleeping:getnext() -- returns delay until next sleep expires, or nil if there is none + local t = heap:peekValue() + if t then + -- never report less than 0, because select() might block + return math.max(t - gettime(), 0) + end + end + + function _sleeping:wakeup(co) + if lethargy[co] then + lethargy[co] = nil + _resumable:push(co) + return + end + if heap:remove(co) then + _resumable:push(co) + end + end + + function _sleeping:cancel(co) + lethargy[co] = nil + heap:remove(co) + end + + -- @param tos number of timeouts running + function _sleeping:done(tos) + -- return true if we have nothing more to do + -- the timeout task doesn't qualify as work (fallbacks only), + -- the lethargy also doesn't qualify as work ('dead' tasks), + -- but the combination of a timeout + a lethargy can be work + return heap:size() == 1 -- 1 means only the timeout-timer task is running + and not (tos > 0 and next(lethargy)) + end + + -- gets number of threads in binaryheap and lethargy + function _sleeping:status() + local c = 0 + for _ in pairs(lethargy) do c = c + 1 end + + return heap:size(), c + end + +end -- _sleeping + + + +------------------------------------------------------------------------------- +-- Tracking coroutines and sockets +------------------------------------------------------------------------------- + +local _servers = newsocketset() -- servers being handled +local _threads = setmetatable({}, {__mode = "k"}) -- registered threads added with addthread() +local _canceled = setmetatable({}, {__mode = "k"}) -- threads that are canceled and pending removal +local _autoclose = setmetatable({}, {__mode = "kv"}) -- sockets (value) to close when a thread (key) exits +local _autoclose_r = setmetatable({}, {__mode = "kv"}) -- reverse: sockets (key) to close when a thread (value) exits + + +-- for each socket we log the last read and last write times to enable the +-- watchdog to follow up if it takes too long. +-- tables contain the time, indexed by the socket +local _reading_log = {} +local _writing_log = {} + +local _closed = {} -- track sockets that have been closed (list/array) + +local _reading = newsocketset() -- sockets currently being read +local _writing = newsocketset() -- sockets currently being written +local _isSocketTimeout = { -- set of errors indicating a socket-timeout + ["timeout"] = true, -- default LuaSocket timeout + ["wantread"] = true, -- LuaSec specific timeout + ["wantwrite"] = true, -- LuaSec specific timeout +} + +------------------------------------------------------------------------------- +-- Coroutine based socket timeouts. +------------------------------------------------------------------------------- +local user_timeouts_connect +local user_timeouts_send +local user_timeouts_receive +do + local timeout_mt = { + __mode = "k", + __index = function(self, skt) + -- if there is no timeout found, we insert one automatically, to block forever + self[skt] = math.huge + return self[skt] + end, + } + + user_timeouts_connect = setmetatable({}, timeout_mt) + user_timeouts_send = setmetatable({}, timeout_mt) + user_timeouts_receive = setmetatable({}, timeout_mt) +end + +local useSocketTimeoutErrors = setmetatable({},{ __mode = "k" }) + + +-- sto = socket-time-out +local sto_timeout, sto_timed_out, sto_change_queue, sto_error do + + local socket_register = setmetatable({}, { __mode = "k" }) -- socket by coroutine + local operation_register = setmetatable({}, { __mode = "k" }) -- operation "read"/"write" by coroutine + local timeout_flags = setmetatable({}, { __mode = "k" }) -- true if timedout, by coroutine + + + local function socket_callback(co) + local skt = socket_register[co] + local queue = operation_register[co] + + -- flag the timeout and resume the coroutine + timeout_flags[co] = true + _resumable:push(co) + + -- clear the socket from the current queue + if queue == "read" then + _reading:remove(skt) + elseif queue == "write" then + _writing:remove(skt) + else + error("bad queue name; expected 'read'/'write', got: "..tostring(queue)) + end + end + + + -- Sets a socket timeout. + -- Calling it as `sto_timeout()` will cancel the timeout. + -- @param queue (string) the queue the socket is currently in, must be either "read" or "write" + -- @param skt (socket) the socket on which to operate + -- @param use_connect_to (bool) timeout to use is determined based on queue (read/write) or if this + -- is truthy, it is the connect timeout. + -- @return true + function sto_timeout(skt, queue, use_connect_to) + local co = coroutine_running() + socket_register[co] = skt + operation_register[co] = queue + timeout_flags[co] = nil + if skt then + local to = (use_connect_to and user_timeouts_connect[skt]) or + (queue == "read" and user_timeouts_receive[skt]) or + user_timeouts_send[skt] + copas.timeout(to, socket_callback) + else + copas.timeout(0) + end + return true + end + + + -- Changes the timeout to a different queue (read/write). + -- Only usefull with ssl-handshakes and "wantread", "wantwrite" errors, when + -- the queue has to be changed, so the timeout handler knows where to find the socket. + -- @param queue (string) the new queue the socket is in, must be either "read" or "write" + -- @return true + function sto_change_queue(queue) + operation_register[coroutine_running()] = queue + return true + end + + + -- Responds with `true` if the operation timed-out. + function sto_timed_out() + return timeout_flags[coroutine_running()] + end + + + -- Returns the proper timeout error + function sto_error(err) + return useSocketTimeoutErrors[coroutine_running()] and err or "timeout" + end +end + + + +------------------------------------------------------------------------------- +-- Coroutine based socket I/O functions. +------------------------------------------------------------------------------- + +-- Returns "tcp"" for plain TCP and "ssl" for ssl-wrapped sockets, so truthy +-- for tcp based, and falsy for udp based. +local isTCP do + local lookup = { + tcp = "tcp", + SSL = "ssl", + } + + function isTCP(socket) + return lookup[tostring(socket):sub(1,3)] + end +end + +function copas.close(skt, ...) + _closed[#_closed+1] = skt + return skt:close(...) +end + + + +-- nil or negative is indefinitly +function copas.settimeout(skt, timeout) + timeout = timeout or -1 + if type(timeout) ~= "number" then + return nil, "timeout must be 'nil' or a number" + end + + return copas.settimeouts(skt, timeout, timeout, timeout) +end + +-- negative is indefinitly, nil means do not change +function copas.settimeouts(skt, connect, send, read) + + if connect ~= nil and type(connect) ~= "number" then + return nil, "connect timeout must be 'nil' or a number" + end + if connect then + if connect < 0 then + connect = nil + end + user_timeouts_connect[skt] = connect + end + + + if send ~= nil and type(send) ~= "number" then + return nil, "send timeout must be 'nil' or a number" + end + if send then + if send < 0 then + send = nil + end + user_timeouts_send[skt] = send + end + + + if read ~= nil and type(read) ~= "number" then + return nil, "read timeout must be 'nil' or a number" + end + if read then + if read < 0 then + read = nil + end + user_timeouts_receive[skt] = read + end + + + return true +end + +-- reads a pattern from a client and yields to the reading set on timeouts +-- UDP: a UDP socket expects a second argument to be a number, so it MUST +-- be provided as the 'pattern' below defaults to a string. Will throw a +-- 'bad argument' error if omitted. +function copas.receive(client, pattern, part) + local s, err + pattern = pattern or "*l" + local current_log = _reading_log + sto_timeout(client, "read") + + repeat + s, err, part = client:receive(pattern, part) + + -- guarantees that high throughput doesn't take other threads to starvation + if (math.random(100) > 90) then + copas.pause() + end + + if s then + current_log[client] = nil + sto_timeout() + return s, err, part + + elseif not _isSocketTimeout[err] then + current_log[client] = nil + sto_timeout() + return s, err, part + + elseif sto_timed_out() then + current_log[client] = nil + return nil, sto_error(err), part + end + + if err == "wantwrite" then -- wantwrite may be returned during SSL renegotiations + current_log = _writing_log + current_log[client] = gettime() + sto_change_queue("write") + coroutine_yield(client, _writing) + else + current_log = _reading_log + current_log[client] = gettime() + sto_change_queue("read") + coroutine_yield(client, _reading) + end + until false +end + +-- receives data from a client over UDP. Not available for TCP. +-- (this is a copy of receive() method, adapted for receivefrom() use) +function copas.receivefrom(client, size) + local s, err, port + size = size or UDP_DATAGRAM_MAX + sto_timeout(client, "read") + + repeat + s, err, port = client:receivefrom(size) -- upon success err holds ip address + + -- garantees that high throughput doesn't take other threads to starvation + if (math.random(100) > 90) then + copas.pause() + end + + if s then + _reading_log[client] = nil + sto_timeout() + return s, err, port + + elseif err ~= "timeout" then + _reading_log[client] = nil + sto_timeout() + return s, err, port + + elseif sto_timed_out() then + _reading_log[client] = nil + return nil, sto_error(err), port + end + + _reading_log[client] = gettime() + coroutine_yield(client, _reading) + until false +end + +-- same as above but with special treatment when reading chunks, +-- unblocks on any data received. +function copas.receivepartial(client, pattern, part) + local s, err + pattern = pattern or "*l" + local orig_size = #(part or "") + local current_log = _reading_log + sto_timeout(client, "read") + + repeat + s, err, part = client:receive(pattern, part) + + -- guarantees that high throughput doesn't take other threads to starvation + if (math.random(100) > 90) then + copas.pause() + end + + if s or (type(part) == "string" and #part > orig_size) then + current_log[client] = nil + sto_timeout() + return s, err, part + + elseif not _isSocketTimeout[err] then + current_log[client] = nil + sto_timeout() + return s, err, part + + elseif sto_timed_out() then + current_log[client] = nil + return nil, sto_error(err), part + end + + if err == "wantwrite" then + current_log = _writing_log + current_log[client] = gettime() + sto_change_queue("write") + coroutine_yield(client, _writing) + else + current_log = _reading_log + current_log[client] = gettime() + sto_change_queue("read") + coroutine_yield(client, _reading) + end + until false +end +copas.receivePartial = copas.receivepartial -- compat: receivePartial is deprecated + +-- sends data to a client. The operation is buffered and +-- yields to the writing set on timeouts +-- Note: from and to parameters will be ignored by/for UDP sockets +function copas.send(client, data, from, to) + local s, err + from = from or 1 + local lastIndex = from - 1 + local current_log = _writing_log + sto_timeout(client, "write") + + repeat + s, err, lastIndex = client:send(data, lastIndex + 1, to) + + -- guarantees that high throughput doesn't take other threads to starvation + if (math.random(100) > 90) then + copas.pause() + end + + if s then + current_log[client] = nil + sto_timeout() + return s, err, lastIndex + + elseif not _isSocketTimeout[err] then + current_log[client] = nil + sto_timeout() + return s, err, lastIndex + + elseif sto_timed_out() then + current_log[client] = nil + return nil, sto_error(err), lastIndex + end + + if err == "wantread" then + current_log = _reading_log + current_log[client] = gettime() + sto_change_queue("read") + coroutine_yield(client, _reading) + else + current_log = _writing_log + current_log[client] = gettime() + sto_change_queue("write") + coroutine_yield(client, _writing) + end + until false +end + +function copas.sendto(client, data, ip, port) + -- deprecated; for backward compatibility only, since UDP doesn't block on sending + return client:sendto(data, ip, port) +end + +-- waits until connection is completed +function copas.connect(skt, host, port) + skt:settimeout(0) + local ret, err, tried_more_than_once + sto_timeout(skt, "write", true) + + repeat + ret, err = skt:connect(host, port) + + -- non-blocking connect on Windows results in error "Operation already + -- in progress" to indicate that it is completing the request async. So essentially + -- it is the same as "timeout" + if ret or (err ~= "timeout" and err ~= "Operation already in progress") then + _writing_log[skt] = nil + sto_timeout() + -- Once the async connect completes, Windows returns the error "already connected" + -- to indicate it is done, so that error should be ignored. Except when it is the + -- first call to connect, then it was already connected to something else and the + -- error should be returned + if (not ret) and (err == "already connected" and tried_more_than_once) then + return 1 + end + return ret, err + + elseif sto_timed_out() then + _writing_log[skt] = nil + return nil, sto_error(err) + end + + tried_more_than_once = tried_more_than_once or true + _writing_log[skt] = gettime() + coroutine_yield(skt, _writing) + until false +end + + +-- Wraps a tcp socket in an ssl socket and configures it. If the socket was +-- already wrapped, it does nothing and returns the socket. +-- @param wrap_params the parameters for the ssl-context +-- @return wrapped socket, or throws an error +local function ssl_wrap(skt, wrap_params) + if isTCP(skt) == "ssl" then return skt end -- was already wrapped + if not wrap_params then + error("cannot wrap socket into a secure socket (using 'ssl.wrap()') without parameters/context") + end + + ssl = ssl or require("ssl") + local nskt = assert(ssl.wrap(skt, wrap_params)) -- assert, because we do not want to silently ignore this one!! + + nskt:settimeout(0) -- non-blocking on the ssl-socket + copas.settimeouts(nskt, user_timeouts_connect[skt], + user_timeouts_send[skt], user_timeouts_receive[skt]) -- copy copas user-timeout to newly wrapped one + + local co = _autoclose_r[skt] + if co then + -- socket registered for autoclose, move registration to wrapped one + _autoclose[co] = nskt + _autoclose_r[skt] = nil + _autoclose_r[nskt] = co + end + + local sock_name = object_names[skt] + if sock_name ~= tostring(skt) then + -- socket had a custom name, so copy it over + object_names[nskt] = sock_name + end + return nskt +end + + +-- For each luasec method we have a subtable, allows for future extension. +-- Required structure: +-- { +-- wrap = ... -- parameter to 'wrap()'; the ssl parameter table, or the context object +-- sni = { -- parameters to 'sni()' +-- names = string | table -- 1st parameter +-- strict = bool -- 2nd parameter +-- } +-- } +local function normalize_sslt(sslt) + local t = type(sslt) + local r = setmetatable({}, { + __index = function(self, key) + -- a bug if this happens, here as a sanity check, just being careful since + -- this is security stuff + error("accessing unknown 'ssl_params' table key: "..tostring(key)) + end, + }) + if t == "nil" then + r.wrap = false + r.sni = false + + elseif t == "table" then + if sslt.mode or sslt.protocol then + -- has the mandatory fields for the ssl-params table for handshake + -- backward compatibility + r.wrap = sslt + r.sni = false + else + -- has the target definition, copy our known keys + r.wrap = sslt.wrap or false -- 'or false' because we do not want nils + r.sni = sslt.sni or false -- 'or false' because we do not want nils + end + + elseif t == "userdata" then + -- it's an ssl-context object for the handshake + -- backward compatibility + r.wrap = sslt + r.sni = false + + else + error("ssl parameters; did not expect type "..tostring(sslt)) + end + + return r +end + + +--- +-- Peforms an (async) ssl handshake on a connected TCP client socket. +-- NOTE: if not ssl-wrapped already, then replace all previous socket references, with the returned new ssl wrapped socket +-- Throws error and does not return nil+error, as that might silently fail +-- in code like this; +-- copas.addserver(s1, function(skt) +-- skt = copas.wrap(skt, sparams) +-- skt:dohandshake() --> without explicit error checking, this fails silently and +-- skt:send(body) --> continues unencrypted +-- @param skt Regular LuaSocket CLIENT socket object +-- @param wrap_params Table with ssl parameters +-- @return wrapped ssl socket, or throws an error +function copas.dohandshake(skt, wrap_params) + ssl = ssl or require("ssl") + + local nskt = ssl_wrap(skt, wrap_params) + + sto_timeout(nskt, "write", true) + local queue + + repeat + local success, err = nskt:dohandshake() + + if success then + sto_timeout() + return nskt + + elseif not _isSocketTimeout[err] then + sto_timeout() + error("TLS/SSL handshake failed: " .. tostring(err)) + + elseif sto_timed_out() then + return nil, sto_error(err) + + elseif err == "wantwrite" then + sto_change_queue("write") + queue = _writing + + elseif err == "wantread" then + sto_change_queue("read") + queue = _reading + + else + error("TLS/SSL handshake failed: " .. tostring(err)) + end + + coroutine_yield(nskt, queue) + until false +end + +-- flushes a client write buffer (deprecated) +function copas.flush() +end + +-- wraps a TCP socket to use Copas methods (send, receive, flush and settimeout) +local _skt_mt_tcp = { + __tostring = function(self) + return tostring(self.socket).." (copas wrapped)" + end, + + __index = { + send = function (self, data, from, to) + return copas.send (self.socket, data, from, to) + end, + + receive = function (self, pattern, prefix) + if user_timeouts_receive[self.socket] == 0 then + return copas.receivepartial(self.socket, pattern, prefix) + end + return copas.receive(self.socket, pattern, prefix) + end, + + receivepartial = function (self, pattern, prefix) + return copas.receivepartial(self.socket, pattern, prefix) + end, + + flush = function (self) + return copas.flush(self.socket) + end, + + settimeout = function (self, time) + return copas.settimeout(self.socket, time) + end, + + settimeouts = function (self, connect, send, receive) + return copas.settimeouts(self.socket, connect, send, receive) + end, + + -- TODO: socket.connect is a shortcut, and must be provided with an alternative + -- if ssl parameters are available, it will also include a handshake + connect = function(self, ...) + local res, err = copas.connect(self.socket, ...) + if res then + if self.ssl_params.sni then self:sni() end + if self.ssl_params.wrap then res, err = self:dohandshake() end + end + return res, err + end, + + close = function(self, ...) + return copas.close(self.socket, ...) + end, + + -- TODO: socket.bind is a shortcut, and must be provided with an alternative + bind = function(self, ...) return self.socket:bind(...) end, + + -- TODO: is this DNS related? hence blocking? + getsockname = function(self, ...) + local ok, ip, port, family = pcall(self.socket.getsockname, self.socket, ...) + if ok then + return ip, port, family + else + return nil, "not implemented by LuaSec" + end + end, + + getstats = function(self, ...) return self.socket:getstats(...) end, + + setstats = function(self, ...) return self.socket:setstats(...) end, + + listen = function(self, ...) return self.socket:listen(...) end, + + accept = function(self, ...) return self.socket:accept(...) end, + + setoption = function(self, ...) + local ok, res, err = pcall(self.socket.setoption, self.socket, ...) + if ok then + return res, err + else + return nil, "not implemented by LuaSec" + end + end, + + getoption = function(self, ...) + local ok, val, err = pcall(self.socket.getoption, self.socket, ...) + if ok then + return val, err + else + return nil, "not implemented by LuaSec" + end + end, + + -- TODO: is this DNS related? hence blocking? + getpeername = function(self, ...) + local ok, ip, port, family = pcall(self.socket.getpeername, self.socket, ...) + if ok then + return ip, port, family + else + return nil, "not implemented by LuaSec" + end + end, + + shutdown = function(self, ...) return self.socket:shutdown(...) end, + + sni = function(self, names, strict) + local sslp = self.ssl_params + self.socket = ssl_wrap(self.socket, sslp.wrap) + if names == nil then + names = sslp.sni.names + strict = sslp.sni.strict + end + return self.socket:sni(names, strict) + end, + + dohandshake = function(self, wrap_params) + local nskt, err = copas.dohandshake(self.socket, wrap_params or self.ssl_params.wrap) + if not nskt then return nskt, err end + self.socket = nskt -- replace internal socket with the newly wrapped ssl one + return self + end, + + getalpn = function(self, ...) + local ok, proto, err = pcall(self.socket.getalpn, self.socket, ...) + if ok then + return proto, err + else + return nil, "not a tls socket" + end + end, + + getsniname = function(self, ...) + local ok, name, err = pcall(self.socket.getsniname, self.socket, ...) + if ok then + return name, err + else + return nil, "not a tls socket" + end + end, + } +} + +-- wraps a UDP socket, copy of TCP one adapted for UDP. +local _skt_mt_udp = {__index = { }} +for k,v in pairs(_skt_mt_tcp) do _skt_mt_udp[k] = _skt_mt_udp[k] or v end +for k,v in pairs(_skt_mt_tcp.__index) do _skt_mt_udp.__index[k] = v end + +_skt_mt_udp.__index.send = function(self, ...) return self.socket:send(...) end + +_skt_mt_udp.__index.sendto = function(self, ...) return self.socket:sendto(...) end + + +_skt_mt_udp.__index.receive = function (self, size) + return copas.receive (self.socket, (size or UDP_DATAGRAM_MAX)) + end + +_skt_mt_udp.__index.receivefrom = function (self, size) + return copas.receivefrom (self.socket, (size or UDP_DATAGRAM_MAX)) + end + + -- TODO: is this DNS related? hence blocking? +_skt_mt_udp.__index.setpeername = function(self, ...) return self.socket:setpeername(...) end + +_skt_mt_udp.__index.setsockname = function(self, ...) return self.socket:setsockname(...) end + + -- do not close client, as it is also the server for udp. +_skt_mt_udp.__index.close = function(self, ...) return true end + +_skt_mt_udp.__index.settimeouts = function (self, connect, send, receive) + return copas.settimeouts(self.socket, connect, send, receive) + end + + + +--- +-- Wraps a LuaSocket socket object in an async Copas based socket object. +-- @param skt The socket to wrap +-- @sslt (optional) Table with ssl parameters, use an empty table to use ssl with defaults +-- @return wrapped socket object +function copas.wrap (skt, sslt) + if (getmetatable(skt) == _skt_mt_tcp) or (getmetatable(skt) == _skt_mt_udp) then + return skt -- already wrapped + end + + skt:settimeout(0) + + if isTCP(skt) then + return setmetatable ({socket = skt, ssl_params = normalize_sslt(sslt)}, _skt_mt_tcp) + else + return setmetatable ({socket = skt}, _skt_mt_udp) + end +end + +--- Wraps a handler in a function that deals with wrapping the socket and doing the +-- optional ssl handshake. +function copas.handler(handler, sslparams) + -- TODO: pass a timeout value to set, and use during handshake + return function (skt, ...) + skt = copas.wrap(skt, sslparams) -- this call will normalize the sslparams table + local sslp = skt.ssl_params + if sslp.sni then skt:sni(sslp.sni.names, sslp.sni.strict) end + if sslp.wrap then skt:dohandshake(sslp.wrap) end + return handler(skt, ...) + end +end + + +-------------------------------------------------- +-- Error handling +-------------------------------------------------- + +local _errhandlers = setmetatable({}, { __mode = "k" }) -- error handler per coroutine + + +function copas.gettraceback(msg, co, skt) + local co_str = co == nil and "nil" or copas.getthreadname(co) + local skt_str = skt == nil and "nil" or copas.getsocketname(skt) + local msg_str = msg == nil and "" or tostring(msg) + if msg_str == "" then + msg_str = ("(coroutine: %s, socket: %s)"):format(msg_str, co_str, skt_str) + else + msg_str = ("%s (coroutine: %s, socket: %s)"):format(msg_str, co_str, skt_str) + end + + if type(co) == "thread" then + -- regular Copas coroutine + return debug.traceback(co, msg_str) + end + -- not a coroutine, but the main thread, this happens if a timeout callback + -- (see `copas.timeout` causes an error (those callbacks run on the main thread). + return debug.traceback(msg_str, 2) +end + + +local function _deferror(msg, co, skt) + print(copas.gettraceback(msg, co, skt)) +end + + +function copas.seterrorhandler(err, default) + assert(err == nil or type(err) == "function", "Expected the handler to be a function, or nil") + if default then + assert(err ~= nil, "Expected the handler to be a function when setting the default") + _deferror = err + else + _errhandlers[coroutine_running()] = err + end +end +copas.setErrorHandler = copas.seterrorhandler -- deprecated; old casing + + +function copas.geterrorhandler(co) + co = co or coroutine_running() + return _errhandlers[co] or _deferror +end + + +-- if `bool` is truthy, then the original socket errors will be returned in case of timeouts; +-- `timeout, wantread, wantwrite, Operation already in progress`. If falsy, it will always +-- return `timeout`. +function copas.useSocketTimeoutErrors(bool) + useSocketTimeoutErrors[coroutine_running()] = not not bool -- force to a boolean +end + +------------------------------------------------------------------------------- +-- Thread handling +------------------------------------------------------------------------------- + +local function _doTick (co, skt, ...) + if not co then return end + + -- if a coroutine was canceled/removed, don't resume it + if _canceled[co] then + _canceled[co] = nil -- also clean up the registry + _threads[co] = nil + return + end + + -- res: the socket (being read/write on) or the time to sleep + -- new_q: either _writing, _reading, or _sleeping + -- local time_before = gettime() + local ok, res, new_q = coroutine_resume(co, skt, ...) + -- local duration = gettime() - time_before + -- if duration > 1 then + -- duration = math.floor(duration * 1000) + -- pcall(_errhandlers[co] or _deferror, "task ran for "..tostring(duration).." milliseconds.", co, skt) + -- end + + if new_q == _reading or new_q == _writing or new_q == _sleeping then + -- we're yielding to a new queue + new_q:insert (res) + new_q:push (res, co) + return + end + + -- coroutine is terminating + + if ok and coroutine_status(co) ~= "dead" then + -- it called coroutine.yield from a non-Copas function which is unexpected + ok = false + res = "coroutine.yield was called without a resume first, user-code cannot yield to Copas" + end + + if not ok then + local k, e = pcall(_errhandlers[co] or _deferror, res, co, skt) + if not k then + print("Failed executing error handler: " .. tostring(e)) + end + end + + local skt_to_close = _autoclose[co] + if skt_to_close then + skt_to_close:close() + _autoclose[co] = nil + _autoclose_r[skt_to_close] = nil + end + + _errhandlers[co] = nil +end + + +local _accept do + local client_counters = setmetatable({}, { __mode = "k" }) + + -- accepts a connection on socket input + function _accept(server_skt, handler) + local client_skt = server_skt:accept() + if client_skt then + local count = (client_counters[server_skt] or 0) + 1 + client_counters[server_skt] = count + object_names[client_skt] = object_names[server_skt] .. ":client_" .. count + + client_skt:settimeout(0) + copas.settimeouts(client_skt, user_timeouts_connect[server_skt], -- copy server socket timeout settings + user_timeouts_send[server_skt], user_timeouts_receive[server_skt]) + + local co = coroutine_create(handler) + object_names[co] = object_names[server_skt] .. ":handler_" .. count + + if copas.autoclose then + _autoclose[co] = client_skt + _autoclose_r[client_skt] = co + end + + _doTick(co, client_skt) + end + end +end + +------------------------------------------------------------------------------- +-- Adds a server/handler pair to Copas dispatcher +------------------------------------------------------------------------------- + +do + local function addTCPserver(server, handler, timeout, name) + server:settimeout(0) + if name then + object_names[server] = name + end + _servers[server] = handler + _reading:insert(server) + if timeout then + copas.settimeout(server, timeout) + end + end + + local function addUDPserver(server, handler, timeout, name) + server:settimeout(0) + local co = coroutine_create(handler) + if name then + object_names[server] = name + end + object_names[co] = object_names[server]..":handler" + _reading:insert(server) + if timeout then + copas.settimeout(server, timeout) + end + _doTick(co, server) + end + + + function copas.addserver(server, handler, timeout, name) + if isTCP(server) then + addTCPserver(server, handler, timeout, name) + else + addUDPserver(server, handler, timeout, name) + end + end +end + + +function copas.removeserver(server, keep_open) + local skt = server + local mt = getmetatable(server) + if mt == _skt_mt_tcp or mt == _skt_mt_udp then + skt = server.socket + end + + _servers:remove(skt) + _reading:remove(skt) + + if keep_open then + return true + end + return server:close() +end + + + +------------------------------------------------------------------------------- +-- Adds an new coroutine thread to Copas dispatcher +------------------------------------------------------------------------------- +function copas.addnamedthread(name, handler, ...) + if type(name) == "function" and type(handler) == "string" then + -- old call, flip args for compatibility + name, handler = handler, name + end + + -- create a coroutine that skips the first argument, which is always the socket + -- passed by the scheduler, but `nil` in case of a task/thread + local thread = coroutine_create(function(_, ...) + copas.pause() + return handler(...) + end) + if name then + object_names[thread] = name + end + + _threads[thread] = true -- register this thread so it can be removed + _doTick (thread, nil, ...) + return thread +end + + +function copas.addthread(handler, ...) + return copas.addnamedthread(nil, handler, ...) +end + + +function copas.removethread(thread) + -- if the specified coroutine is registered, add it to the canceled table so + -- that next time it tries to resume it exits. + _canceled[thread] = _threads[thread or 0] + _sleeping:cancel(thread) +end + + + +------------------------------------------------------------------------------- +-- Sleep/pause management functions +------------------------------------------------------------------------------- + +-- yields the current coroutine and wakes it after 'sleeptime' seconds. +-- If sleeptime < 0 then it sleeps until explicitly woken up using 'wakeup' +-- TODO: deprecated, remove in next major +function copas.sleep(sleeptime) + coroutine_yield((sleeptime or 0), _sleeping) +end + + +-- yields the current coroutine and wakes it after 'sleeptime' seconds. +-- if sleeptime < 0 then it sleeps 0 seconds. +function copas.pause(sleeptime) + if sleeptime and sleeptime > 0 then + coroutine_yield(sleeptime, _sleeping) + else + coroutine_yield(0, _sleeping) + end +end + + +-- yields the current coroutine until explicitly woken up using 'wakeup' +function copas.pauseforever() + coroutine_yield(-1, _sleeping) +end + + +-- Wakes up a sleeping coroutine 'co'. +function copas.wakeup(co) + _sleeping:wakeup(co) +end + + + +------------------------------------------------------------------------------- +-- Timeout management +------------------------------------------------------------------------------- + +do + local timeout_register = setmetatable({}, { __mode = "k" }) + local time_out_thread + local timerwheel = require("timerwheel").new({ + precision = TIMEOUT_PRECISION, + ringsize = math.floor(60*60*24/TIMEOUT_PRECISION), -- ring size 1 day + err_handler = function(err) + return _deferror(err, time_out_thread) + end, + }) + + time_out_thread = copas.addnamedthread("copas_core_timer", function() + while true do + copas.pause(TIMEOUT_PRECISION) + timerwheel:step() + end + end) + + -- get the number of timeouts running + function copas.gettimeouts() + return timerwheel:count() + end + + --- Sets the timeout for the current coroutine. + -- @param delay delay (seconds), use 0 (or math.huge) to cancel the timerout + -- @param callback function with signature: `function(coroutine)` where coroutine is the routine that timed-out + -- @return true + function copas.timeout(delay, callback) + local co = coroutine_running() + local existing_timer = timeout_register[co] + + if existing_timer then + timerwheel:cancel(existing_timer) + end + + if delay > 0 and delay ~= math.huge then + timeout_register[co] = timerwheel:set(delay, callback, co) + elseif delay == 0 or delay == math.huge then + timeout_register[co] = nil + else + error("timout value must be greater than or equal to 0, got: "..tostring(delay)) + end + + return true + end + +end + + +------------------------------------------------------------------------------- +-- main tasks: manage readable and writable socket sets +------------------------------------------------------------------------------- +-- a task is an object with a required method `step()` that deals with a +-- single step for that task. + +local _tasks = {} do + function _tasks:add(tsk) + _tasks[#_tasks + 1] = tsk + end +end + + +-- a task to check ready to read events +local _readable_task = {} do + + local function tick(skt) + local handler = _servers[skt] + if handler then + _accept(skt, handler) + else + _reading:remove(skt) + _doTick(_reading:pop(skt), skt) + end + end + + function _readable_task:step() + for _, skt in ipairs(self._events) do + tick(skt) + end + end + + _tasks:add(_readable_task) +end + + +-- a task to check ready to write events +local _writable_task = {} do + + local function tick(skt) + _writing:remove(skt) + _doTick(_writing:pop(skt), skt) + end + + function _writable_task:step() + for _, skt in ipairs(self._events) do + tick(skt) + end + end + + _tasks:add(_writable_task) +end + + + +-- sleeping threads task +local _sleeping_task = {} do + + function _sleeping_task:step() + local now = gettime() + + local co = _sleeping:pop(now) + while co do + -- we're pushing them to _resumable, since that list will be replaced before + -- executing. This prevents tasks running twice in a row with pause(0) for example. + -- So here we won't execute, but at _resumable step which is next + _resumable:push(co) + co = _sleeping:pop(now) + end + end + + _tasks:add(_sleeping_task) +end + + + +-- resumable threads task +local _resumable_task = {} do + + function _resumable_task:step() + -- replace the resume list before iterating, so items placed in there + -- will indeed end up in the next copas step, not in this one, and not + -- create a loop + local resumelist = _resumable:clear_resumelist() + + for _, co in ipairs(resumelist) do + _doTick(co) + end + end + + _tasks:add(_resumable_task) +end + + +------------------------------------------------------------------------------- +-- Checks for reads and writes on sockets +------------------------------------------------------------------------------- +local _select_plain do + + local last_cleansing = 0 + local duration = function(t2, t1) return t2-t1 end + + _select_plain = function(timeout) + local err + local now = gettime() + + -- remove any closed sockets to prevent select from hanging on them + if _closed[1] then + for i, skt in ipairs(_closed) do + _closed[i] = { _reading:remove(skt), _writing:remove(skt) } + end + end + + _readable_task._events, _writable_task._events, err = socket.select(_reading, _writing, timeout) + local r_events, w_events = _readable_task._events, _writable_task._events + + -- inject closed sockets in readable/writeable task so they can error out properly + if _closed[1] then + for i, skts in ipairs(_closed) do + _closed[i] = nil + r_events[#r_events+1] = skts[1] + w_events[#w_events+1] = skts[2] + end + end + + if duration(now, last_cleansing) > WATCH_DOG_TIMEOUT then + last_cleansing = now + + -- Check all sockets selected for reading, and check how long they have been waiting + -- for data already, without select returning them as readable + for skt,time in pairs(_reading_log) do + if not r_events[skt] and duration(now, time) > WATCH_DOG_TIMEOUT then + -- This one timedout while waiting to become readable, so move + -- it in the readable list and try and read anyway, despite not + -- having been returned by select + _reading_log[skt] = nil + r_events[#r_events + 1] = skt + r_events[skt] = #r_events + end + end + + -- Do the same for writing + for skt,time in pairs(_writing_log) do + if not w_events[skt] and duration(now, time) > WATCH_DOG_TIMEOUT then + _writing_log[skt] = nil + w_events[#w_events + 1] = skt + w_events[skt] = #w_events + end + end + end + + if err == "timeout" and #r_events + #w_events > 0 then + return nil + else + return err + end + end +end + + + +------------------------------------------------------------------------------- +-- Dispatcher loop step. +-- Listen to client requests and handles them +-- Returns false if no socket-data was handled, or true if there was data +-- handled (or nil + error message) +------------------------------------------------------------------------------- + +local copas_stats +local min_ever, max_ever + +local _select = _select_plain + +-- instrumented version of _select() to collect stats +local _select_instrumented = function(timeout) + if copas_stats then + local step_duration = gettime() - copas_stats.step_start + copas_stats.duration_max = math.max(copas_stats.duration_max, step_duration) + copas_stats.duration_min = math.min(copas_stats.duration_min, step_duration) + copas_stats.duration_tot = copas_stats.duration_tot + step_duration + copas_stats.steps = copas_stats.steps + 1 + else + copas_stats = { + duration_max = -1, + duration_min = 999999, + duration_tot = 0, + steps = 0, + } + end + + local err = _select_plain(timeout) + + local now = gettime() + copas_stats.time_start = copas_stats.time_start or now + copas_stats.step_start = now + + return err +end + + +function copas.step(timeout) + -- Need to wake up the select call in time for the next sleeping event + if not _resumable:done() then + timeout = 0 + else + timeout = math.min(_sleeping:getnext(), timeout or math.huge) + end + + local err = _select(timeout) + + for _, tsk in ipairs(_tasks) do + tsk:step() + end + + if err then + if err == "timeout" then + if timeout + 0.01 > TIMEOUT_PRECISION and math.random(100) > 90 then + -- we were idle, so occasionally do a GC sweep to ensure lingering + -- sockets are closed, and we don't accidentally block the loop from + -- exiting + collectgarbage() + end + return false + end + return nil, err + end + + return true +end + + +------------------------------------------------------------------------------- +-- Check whether there is something to do. +-- returns false if there are no sockets for read/write nor tasks scheduled +-- (which means Copas is in an empty spin) +------------------------------------------------------------------------------- +function copas.finished() + return #_reading == 0 and #_writing == 0 and _resumable:done() and _sleeping:done(copas.gettimeouts()) +end + +local _getstats do + local _getstats_instrumented, _getstats_plain + + + function _getstats_plain(enable) + -- this function gets hit if turned off, so turn on if true + if enable == true then + _select = _select_instrumented + _getstats = _getstats_instrumented + -- reset stats + min_ever = nil + max_ever = nil + copas_stats = nil + end + return {} + end + + + -- convert from seconds to millisecs, with microsec precision + local function useconds(t) + return math.floor((t * 1000000) + 0.5) / 1000 + end + -- convert from seconds to seconds, with millisec precision + local function mseconds(t) + return math.floor((t * 1000) + 0.5) / 1000 + end + + + function _getstats_instrumented(enable) + if enable == false then + _select = _select_plain + _getstats = _getstats_plain + -- instrumentation disabled, so switch to the plain implementation + return _getstats(enable) + end + if (not copas_stats) or (copas_stats.step == 0) then + return {} + end + local stats = copas_stats + copas_stats = nil + min_ever = math.min(min_ever or 9999999, stats.duration_min) + max_ever = math.max(max_ever or 0, stats.duration_max) + stats.duration_min_ever = min_ever + stats.duration_max_ever = max_ever + stats.duration_avg = stats.duration_tot / stats.steps + stats.step_start = nil + stats.time_end = gettime() + stats.time_tot = stats.time_end - stats.time_start + stats.time_avg = stats.time_tot / stats.steps + + stats.duration_avg = useconds(stats.duration_avg) + stats.duration_max = useconds(stats.duration_max) + stats.duration_max_ever = useconds(stats.duration_max_ever) + stats.duration_min = useconds(stats.duration_min) + stats.duration_min_ever = useconds(stats.duration_min_ever) + stats.duration_tot = useconds(stats.duration_tot) + stats.time_avg = useconds(stats.time_avg) + stats.time_start = mseconds(stats.time_start) + stats.time_end = mseconds(stats.time_end) + stats.time_tot = mseconds(stats.time_tot) + return stats + end + + _getstats = _getstats_plain +end + + +function copas.status(enable_stats) + local res = _getstats(enable_stats) + res.running = not not copas.running + res.timeout = copas.gettimeouts() + res.timer, res.inactive = _sleeping:status() + res.read = #_reading + res.write = #_writing + res.active = _resumable:count() + return res +end + + +------------------------------------------------------------------------------- +-- Dispatcher endless loop. +-- Listen to client requests and handles them forever +------------------------------------------------------------------------------- +function copas.loop(initializer, timeout) + if type(initializer) == "function" then + copas.addnamedthread("copas_initializer", initializer) + else + timeout = initializer or timeout + end + + copas.running = true + while not copas.finished() do copas.step(timeout) end + copas.running = false +end + + +------------------------------------------------------------------------------- +-- Naming sockets and coroutines. +------------------------------------------------------------------------------- +do + local function realsocket(skt) + local mt = getmetatable(skt) + if mt == _skt_mt_tcp or mt == _skt_mt_udp then + return skt.socket + else + return skt + end + end + + + function copas.setsocketname(name, skt) + assert(type(name) == "string", "expected arg #1 to be a string") + skt = assert(realsocket(skt), "expected arg #2 to be a socket") + object_names[skt] = name + end + + + function copas.getsocketname(skt) + skt = assert(realsocket(skt), "expected arg #1 to be a socket") + return object_names[skt] + end +end + + +function copas.setthreadname(name, coro) + assert(type(name) == "string", "expected arg #1 to be a string") + coro = coro or coroutine_running() + assert(type(coro) == "thread", "expected arg #2 to be a coroutine or nil") + object_names[coro] = name +end + + +function copas.getthreadname(coro) + coro = coro or coroutine_running() + assert(type(coro) == "thread", "expected arg #1 to be a coroutine or nil") + return object_names[coro] +end + +------------------------------------------------------------------------------- +-- Debug functionality. +------------------------------------------------------------------------------- +do + copas.debug = {} + + local log_core -- if truthy, the core-timer will also be logged + local debug_log -- function used as logger + + + local debug_yield = function(skt, queue) + local name = object_names[coroutine_running()] + + if log_core or name ~= "copas_core_timer" then + if queue == _sleeping then + debug_log("yielding '", name, "' to SLEEP for ", skt," seconds") + + elseif queue == _writing then + debug_log("yielding '", name, "' to WRITE on '", object_names[skt], "'") + + elseif queue == _reading then + debug_log("yielding '", name, "' to READ on '", object_names[skt], "'") + + else + debug_log("thread '", name, "' yielding to unexpected queue; ", tostring(queue), " (", type(queue), ")", debug.traceback()) + end + end + + return coroutine.yield(skt, queue) + end + + + local debug_resume = function(coro, skt, ...) + local name = object_names[coro] + + if skt then + debug_log("resuming '", name, "' for socket '", object_names[skt], "'") + else + if log_core or name ~= "copas_core_timer" then + debug_log("resuming '", name, "'") + end + end + return coroutine.resume(coro, skt, ...) + end + + + local debug_create = function(f) + local f_wrapped = function(...) + local results = pack(f(...)) + debug_log("exiting '", object_names[coroutine_running()], "'") + return unpack(results) + end + + return coroutine.create(f_wrapped) + end + + + debug_log = fnil + + + -- enables debug output for all coroutine operations. + function copas.debug.start(logger, core) + log_core = core + debug_log = logger or print + coroutine_yield = debug_yield + coroutine_resume = debug_resume + coroutine_create = debug_create + end + + + -- disables debug output for coroutine operations. + function copas.debug.stop() + debug_log = fnil + coroutine_yield = coroutine.yield + coroutine_resume = coroutine.resume + coroutine_create = coroutine.create + end + + do + local call_id = 0 + + -- Description table of socket functions for debug output. + -- each socket function name has TWO entries; + -- 'name_in' and 'name_out', each being an array of names/descriptions of respectively + -- input parameters and return values. + -- If either table has a 'callback' key, then that is a function that will be called + -- with the parameters/return-values for further inspection. + local args = { + settimeout_in = { + "socket ", + "seconds", + "mode ", + }, + settimeout_out = { + "success", + "error ", + }, + connect_in = { + "socket ", + "address", + "port ", + }, + connect_out = { + "success", + "error ", + }, + getfd_in = { + "socket ", + -- callback = function(...) + -- print(debug.traceback("called from:", 4)) + -- end, + }, + getfd_out = { + "fd", + }, + send_in = { + "socket ", + "data ", + "idx-start", + "idx-end ", + }, + send_out = { + "last-idx-send ", + "error ", + "err-last-idx-send", + }, + receive_in = { + "socket ", + "pattern", + "prefix ", + }, + receive_out = { + "received ", + "error ", + "partial data", + }, + dirty_in = { + "socket", + -- callback = function(...) + -- print(debug.traceback("called from:", 4)) + -- end, + }, + dirty_out = { + "data in read-buffer", + }, + close_in = { + "socket", + -- callback = function(...) + -- print(debug.traceback("called from:", 4)) + -- end, + }, + close_out = { + "success", + "error", + }, + } + local function print_call(func, msg, ...) + print(msg) + local arg = pack(...) + local desc = args[func] or {} + for i = 1, math.max(arg.n, #desc) do + local value = arg[i] + if type(value) == "string" then + local xvalue = value:sub(1,30) + if xvalue ~= value then + xvalue = xvalue .."(...truncated)" + end + print("\t"..(desc[i] or i)..": '"..tostring(xvalue).."' ("..type(value).." #"..#value..")") + else + print("\t"..(desc[i] or i)..": '"..tostring(value).."' ("..type(value)..")") + end + end + if desc.callback then + desc.callback(...) + end + end + + local debug_mt = { + __index = function(self, key) + local value = self.__original_socket[key] + if type(value) ~= "function" then + return value + end + return function(self2, ...) + local my_id = call_id + 1 + call_id = my_id + local results + + if self2 ~= self then + -- there is no self + print_call(tostring(key).."_in", my_id .. "-calling '"..tostring(key) .. "' with; ", self, ...) + results = pack(value(self, ...)) + else + print_call(tostring(key).."_in", my_id .. "-calling '" .. tostring(key) .. "' with; ", self.__original_socket, ...) + results = pack(value(self.__original_socket, ...)) + end + print_call(tostring(key).."_out", my_id .. "-results '"..tostring(key) .. "' returned; ", unpack(results)) + return unpack(results) + end + end, + __tostring = function(self) + return tostring(self.__original_socket) + end + } + + + -- wraps a socket (copas or luasocket) in a debug version printing all calls + -- and their parameters/return values. Extremely noisy! + -- returns the wrapped socket. + -- NOTE: only for plain sockets, will not support TLS + function copas.debug.socket(original_skt) + if (getmetatable(original_skt) == _skt_mt_tcp) or (getmetatable(original_skt) == _skt_mt_udp) then + -- already wrapped as Copas socket, so recurse with the original luasocket one + original_skt.socket = copas.debug.socket(original_skt.socket) + return original_skt + end + + local proxy = setmetatable({ + __original_socket = original_skt + }, debug_mt) + + return proxy + end + end +end + + +return copas diff --git a/thirdparty/copas/src/copas/ftp.lua b/thirdparty/copas/src/copas/ftp.lua new file mode 100644 index 000000000..f7b8cd3a9 --- /dev/null +++ b/thirdparty/copas/src/copas/ftp.lua @@ -0,0 +1,95 @@ +------------------------------------------------------------------- +-- identical to the socket.ftp module except that it uses +-- async wrapped Copas sockets + +local copas = require("copas") +local socket = require("socket") +local ftp = require("socket.ftp") +local ltn12 = require("ltn12") +local url = require("socket.url") + + +local create = function() return copas.wrap(socket.tcp()) end +local forwards = { -- setting these will be forwarded to the original smtp module + PORT = true, + TIMEOUT = true, + PASSWORD = true, + USER = true +} + +copas.ftp = setmetatable({}, { + -- use original module as metatable, to lookup constants like socket.TIMEOUT, etc. + __index = ftp, + -- Setting constants is forwarded to the luasocket.ftp module. + __newindex = function(self, key, value) + if forwards[key] then ftp[key] = value return end + return rawset(self, key, value) + end, + }) +local _M = copas.ftp + +---[[ copy of Luasocket stuff here untile PR #133 is accepted +-- a copy of the version in LuaSockets' ftp.lua +-- no 'create' can be passed in the string form, hence a local copy here +local default = { + path = "/", + scheme = "ftp" +} + +-- a copy of the version in LuaSockets' ftp.lua +-- no 'create' can be passed in the string form, hence a local copy here +local function parse(u) + local t = socket.try(url.parse(u, default)) + socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") + socket.try(t.host, "missing hostname") + local pat = "^type=(.)$" + if t.params then + t.type = socket.skip(2, string.find(t.params, pat)) + socket.try(t.type == "a" or t.type == "i", + "invalid type '" .. t.type .. "'") + end + return t +end + +-- parses a simple form into the advanced form +-- if `body` is provided, a PUT, otherwise a GET. +-- If GET, then a field `target` is added to store the results +_M.parseRequest = function(u, body) + local t = parse(u) + if body then + t.source = ltn12.source.string(body) + else + t.target = {} + t.sink = ltn12.sink.table(t.target) + end +end +--]] + +_M.put = socket.protect(function(putt, body) + if type(putt) == "string" then + putt = _M.parseRequest(putt, body) + _M.put(putt) + return table.concat(putt.target) + else + putt.create = putt.create or create + return ftp.put(putt) + end +end) + +_M.get = socket.protect(function(gett) + if type(gett) == "string" then + gett = _M.parseRequest(gett) + _M.get(gett) + return table.concat(gett.target) + else + gett.create = gett.create or create + return ftp.get(gett) + end +end) + +_M.command = function(cmdt) + cmdt.create = cmdt.create or create + return ftp.command(cmdt) +end + +return _M diff --git a/thirdparty/copas/src/copas/http.lua b/thirdparty/copas/src/copas/http.lua new file mode 100644 index 000000000..83f0c20b2 --- /dev/null +++ b/thirdparty/copas/src/copas/http.lua @@ -0,0 +1,459 @@ +----------------------------------------------------------------------------- +-- Full copy of the LuaSocket code, modified to include +-- https and http/https redirects, and Copas async enabled. +----------------------------------------------------------------------------- +-- HTTP/1.1 client support for the Lua language. +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +------------------------------------------------------------------------------- +local socket = require("socket") +local url = require("socket.url") +local ltn12 = require("ltn12") +local mime = require("mime") +local string = require("string") +local headers = require("socket.headers") +local base = _G +local table = require("table") +local copas = require("copas") +copas.http = {} +local _M = copas.http + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- connection timeout in seconds +_M.TIMEOUT = 60 +-- default port for document retrieval +_M.PORT = 80 +-- user agent field sent in request +_M.USERAGENT = socket._VERSION + +-- Default settings for SSL +_M.SSLPORT = 443 +_M.SSLPROTOCOL = "tlsv1_2" +_M.SSLOPTIONS = "all" +_M.SSLVERIFY = "none" +_M.SSLSNISTRICT = false + + +----------------------------------------------------------------------------- +-- Reads MIME headers from a connection, unfolding where needed +----------------------------------------------------------------------------- +local function receiveheaders(sock, headers) + local line, name, value, err + headers = headers or {} + -- get first line + line, err = sock:receive() + if err then return nil, err end + -- headers go until a blank line is found + while line ~= "" do + -- get field-name and value + name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)")) + if not (name and value) then return nil, "malformed reponse headers" end + name = string.lower(name) + -- get next line (value might be folded) + line, err = sock:receive() + if err then return nil, err end + -- unfold any folded values + while string.find(line, "^%s") do + value = value .. line + line, err = sock:receive() + if err then return nil, err end + end + -- save pair in table + if headers[name] then headers[name] = headers[name] .. ", " .. value + else headers[name] = value end + end + return headers +end + +----------------------------------------------------------------------------- +-- Extra sources and sinks +----------------------------------------------------------------------------- +socket.sourcet["http-chunked"] = function(sock, headers) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + -- get chunk size, skip extention + local line, err = sock:receive() + if err then return nil, err end + local size = base.tonumber(string.gsub(line, ";.*", ""), 16) + if not size then return nil, "invalid chunk size" end + -- was it the last chunk? + if size > 0 then + -- if not, get chunk and skip terminating CRLF + local chunk, err = sock:receive(size) + if chunk then sock:receive() end + return chunk, err + else + -- if it was, read trailers into headers table + headers, err = receiveheaders(sock, headers) + if not headers then return nil, err end + end + end + }) +end + +socket.sinkt["http-chunked"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if not chunk then return sock:send("0\r\n\r\n") end + local size = string.format("%X\r\n", string.len(chunk)) + return sock:send(size .. chunk .. "\r\n") + end + }) +end + +----------------------------------------------------------------------------- +-- Low level HTTP API +----------------------------------------------------------------------------- +local metat = { __index = {} } + +function _M.open(reqt) + -- create socket with user connect function + local c = socket.try(reqt:create()) -- method call, passing reqt table as self! + local h = base.setmetatable({ c = c }, metat) + -- create finalized try + h.try = socket.newtry(function() h:close() end) + -- set timeout before connecting + local to = reqt.timeout or _M.TIMEOUT + if type(to) == "table" then + h.try(c:settimeouts( + to.connect or _M.TIMEOUT, + to.send or _M.TIMEOUT, + to.receive or _M.TIMEOUT)) + else + h.try(c:settimeout(to)) + end + h.try(c:connect(reqt.host, reqt.port or _M.PORT)) + -- here everything worked + return h +end + +function metat.__index:sendrequestline(method, uri) + local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri) + return self.try(self.c:send(reqline)) +end + +function metat.__index:sendheaders(tosend) + local canonic = headers.canonic + local h = "\r\n" + for f, v in base.pairs(tosend) do + h = (canonic[f] or f) .. ": " .. v .. "\r\n" .. h + end + self.try(self.c:send(h)) + return 1 +end + +function metat.__index:sendbody(headers, source, step) + source = source or ltn12.source.empty() + step = step or ltn12.pump.step + -- if we don't know the size in advance, send chunked and hope for the best + local mode = "http-chunked" + if headers["content-length"] then mode = "keep-open" end + return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step)) +end + +function metat.__index:receivestatusline() + local status = self.try(self.c:receive(5)) + -- identify HTTP/0.9 responses, which do not contain a status line + -- this is just a heuristic, but is what the RFC recommends + if status ~= "HTTP/" then return nil, status end + -- otherwise proceed reading a status line + status = self.try(self.c:receive("*l", status)) + local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) + return self.try(base.tonumber(code), status) +end + +function metat.__index:receiveheaders() + return self.try(receiveheaders(self.c)) +end + +function metat.__index:receivebody(headers, sink, step) + sink = sink or ltn12.sink.null() + step = step or ltn12.pump.step + local length = base.tonumber(headers["content-length"]) + local t = headers["transfer-encoding"] -- shortcut + local mode = "default" -- connection close + if t and t ~= "identity" then mode = "http-chunked" + elseif base.tonumber(headers["content-length"]) then mode = "by-length" end + return self.try(ltn12.pump.all(socket.source(mode, self.c, length), + sink, step)) +end + +function metat.__index:receive09body(status, sink, step) + local source = ltn12.source.rewind(socket.source("until-closed", self.c)) + source(status) + return self.try(ltn12.pump.all(source, sink, step)) +end + +function metat.__index:close() + return self.c:close() +end + +----------------------------------------------------------------------------- +-- High level HTTP API +----------------------------------------------------------------------------- +local function adjusturi(reqt) + local u = reqt + -- if there is a proxy, we need the full url. otherwise, just a part. + if not reqt.proxy and not _M.PROXY then + u = { + path = socket.try(reqt.path, "invalid path 'nil'"), + params = reqt.params, + query = reqt.query, + fragment = reqt.fragment + } + end + return url.build(u) +end + +local function adjustproxy(reqt) + local proxy = reqt.proxy or _M.PROXY + if proxy then + proxy = url.parse(proxy) + return proxy.host, proxy.port or 3128 + else + return reqt.host, reqt.port + end +end + +local function adjustheaders(reqt) + -- default headers + local host = string.gsub(reqt.authority, "^.-@", "") + local lower = { + ["user-agent"] = _M.USERAGENT, + ["host"] = host, + ["connection"] = "close, TE", + ["te"] = "trailers" + } + -- if we have authentication information, pass it along + if reqt.user and reqt.password then + lower["authorization"] = + "Basic " .. (mime.b64(reqt.user .. ":" .. reqt.password)) + end + -- override with user headers + for i,v in base.pairs(reqt.headers or lower) do + lower[string.lower(i)] = v + end + return lower +end + +-- default url parts +local default = { + host = "", + port = _M.PORT, + path ="/", + scheme = "http" +} + +local function adjustrequest(reqt) + -- parse url if provided + local nreqt = reqt.url and url.parse(reqt.url, default) or {} + -- explicit components override url + for i,v in base.pairs(reqt) do nreqt[i] = v end + if nreqt.port == "" then nreqt.port = 80 end + socket.try(nreqt.host and nreqt.host ~= "", + "invalid host '" .. base.tostring(nreqt.host) .. "'") + -- compute uri if user hasn't overriden + nreqt.uri = reqt.uri or adjusturi(nreqt) + -- ajust host and port if there is a proxy + nreqt.host, nreqt.port = adjustproxy(nreqt) + -- adjust headers in request + nreqt.headers = adjustheaders(nreqt) + return nreqt +end + +local function shouldredirect(reqt, code, headers) + return headers.location and + string.gsub(headers.location, "%s", "") ~= "" and + (reqt.redirect ~= false) and + (code == 301 or code == 302 or code == 303 or code == 307) and + (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") + and (not reqt.nredirects or reqt.nredirects < 5) +end + +local function shouldreceivebody(reqt, code) + if reqt.method == "HEAD" then return nil end + if code == 204 or code == 304 then return nil end + if code >= 100 and code < 200 then return nil end + return 1 +end + +-- forward declarations +local trequest, tredirect + +--[[local]] function tredirect(reqt, location) + local result, code, headers, status = trequest { + -- the RFC says the redirect URL has to be absolute, but some + -- servers do not respect that + url = url.absolute(reqt.url, location), + source = reqt.source, + sink = reqt.sink, + headers = reqt.headers, + proxy = reqt.proxy, + nredirects = (reqt.nredirects or 0) + 1, + create = reqt.create, + timeout = reqt.timeout, + } + -- pass location header back as a hint we redirected + headers = headers or {} + headers.location = headers.location or location + return result, code, headers, status +end + +--[[local]] function trequest(reqt) + -- we loop until we get what we want, or + -- until we are sure there is no way to get it + local nreqt = adjustrequest(reqt) + local h = _M.open(nreqt) + -- send request line and headers + h:sendrequestline(nreqt.method, nreqt.uri) + h:sendheaders(nreqt.headers) + -- if there is a body, send it + if nreqt.source then + h:sendbody(nreqt.headers, nreqt.source, nreqt.step) + end + local code, status = h:receivestatusline() + -- if it is an HTTP/0.9 server, simply get the body and we are done + if not code then + h:receive09body(status, nreqt.sink, nreqt.step) + return 1, 200 + end + local headers + -- ignore any 100-continue messages + while code == 100 do + h:receiveheaders() + code, status = h:receivestatusline() + end + headers = h:receiveheaders() + -- at this point we should have a honest reply from the server + -- we can't redirect if we already used the source, so we report the error + if shouldredirect(nreqt, code, headers) and not nreqt.source then + h:close() + return tredirect(reqt, headers.location) + end + -- here we are finally done + if shouldreceivebody(nreqt, code) then + h:receivebody(headers, nreqt.sink, nreqt.step) + end + h:close() + return 1, code, headers, status +end + +-- Return a function which creates a tcp socket that will +-- include the optional SSL/TLS connection, and unsafe redirect checks +function _M.getcreatefunc(params) + params = params or {} + local ssl_params = params.sslparams or {} + ssl_params.wrap = ssl_params.wrap or { + -- backward compatibility + protocol = params.protocol, + options = params.options, + verify = params.verify, + } + ssl_params.sni = ssl_params.sni or { + strict = _M.SSLSNISTRICT + } + + -- Default settings + ssl_params.wrap.protocol = ssl_params.wrap.protocol or _M.SSLPROTOCOL + ssl_params.wrap.options = ssl_params.wrap.options or _M.SSLOPTIONS + if ssl_params.wrap.verify == nil then + ssl_params.wrap.verify = _M.SSLVERIFY + end + ssl_params.wrap.mode = "client" -- Force client mode + + if not ssl_params.sni.names then + -- names haven't been set, and hence will be set below. Since this alters + -- the table, we must make a copy. Otherwise the altered table might be + -- reused if a redirect is encountered. + local old_params = ssl_params + ssl_params = {} + for k,v in pairs(old_params) do + ssl_params[k] = v + end + ssl_params.sni = { strict = old_params.sni.strict } + end + + -- upvalue to track https -> http redirection + local washttps = false + + -- 'create' function for LuaSocket + return function (reqt) + local u = url.parse(reqt.url) + if (reqt.scheme or u.scheme) == "https" then + -- set SNI name to host if not given + ssl_params.sni.names = ssl_params.sni.names or u.host + -- https, provide an ssl wrapped socket + local conn = copas.wrap(socket.tcp(), ssl_params) + -- insert https default port, overriding http port inserted by LuaSocket + if not u.port then + u.port = _M.SSLPORT + reqt.url = url.build(u) + reqt.port = _M.SSLPORT + end + washttps = true + return conn + else + -- regular http, needs just a socket... + if washttps and params.redirect ~= "all" then + socket.try(nil, "Unallowed insecure redirect https to http") + end + return copas.wrap(socket.tcp()) + end + end +end + +-- parses a shorthand form into the advanced table form. +-- adds field `target` to the table. This will hold the return values. +_M.parseRequest = function(u, b) + local reqt = { + url = u, + target = {}, + } + reqt.sink = ltn12.sink.table(reqt.target) + if b then + reqt.source = ltn12.source.string(b) + reqt.headers = { + ["content-length"] = string.len(b), + ["content-type"] = "application/x-www-form-urlencoded" + } + reqt.method = "POST" + end + return reqt +end + +_M.request = socket.protect(function(reqt, body) + if base.type(reqt) == "string" then + reqt = _M.parseRequest(reqt, body) + local ok, code, headers, status = _M.request(reqt) + + if ok then + return table.concat(reqt.target), code, headers, status + else + return nil, code + end + else + -- strict check on timeout table to prevent typo's from going unnoticed + if type(reqt.timeout) == "table" then + local allowed = { connect = true, send = true, receive = true } + for k in pairs(reqt.timeout) do + assert(allowed[k], "'"..tostring(k).."' is not a valid timeout option. Valid: 'connect', 'send', 'receive'") + end + end + reqt.create = reqt.create or _M.getcreatefunc(reqt) + return trequest(reqt) + end +end) + +return _M diff --git a/thirdparty/copas/src/copas/lock.lua b/thirdparty/copas/src/copas/lock.lua new file mode 100644 index 000000000..7b33c0da5 --- /dev/null +++ b/thirdparty/copas/src/copas/lock.lua @@ -0,0 +1,191 @@ +local copas = require("copas") +local gettime = require("socket").gettime + +local DEFAULT_TIMEOUT = 10 + +local lock = {} +lock.__index = lock + + +-- registry, locks indexed by the coroutines using them. +local registry = setmetatable({}, { __mode="kv" }) + + + +--- Creates a new lock. +-- @param seconds (optional) default timeout in seconds when acquiring the lock (defaults to 10), +-- set to `math.huge` to have no timeout. +-- @param not_reentrant (optional) if truthy the lock will not allow a coroutine to grab the same lock multiple times +-- @return the lock object +function lock.new(seconds, not_reentrant) + local timeout = tonumber(seconds or DEFAULT_TIMEOUT) or -1 + if timeout < 0 then + error("expected timeout (1st argument) to be a number greater than or equal to 0, got: " .. tostring(seconds), 2) + end + return setmetatable({ + timeout = timeout, + not_reentrant = not_reentrant, + queue = {}, + q_tip = 0, -- index of the first in line waiting + q_tail = 0, -- index where the next one will be inserted + owner = nil, -- coroutine holding lock currently + call_count = nil, -- recursion call count + errors = setmetatable({}, { __mode = "k" }), -- error indexed by coroutine + }, lock) +end + + + +do + local destroyed_func = function() + return nil, "destroyed" + end + + local destroyed_lock_mt = { + __index = function() + return destroyed_func + end + } + + --- destroy a lock. + -- Releases all waiting threads with `nil+"destroyed"` + function lock:destroy() + --print("destroying ",self) + for i = self.q_tip, self.q_tail do + local co = self.queue[i] + self.queue[i] = nil + + if co then + self.errors[co] = "destroyed" + --print("marked destroyed ", co) + copas.wakeup(co) + end + end + + if self.owner then + self.errors[self.owner] = "destroyed" + --print("marked destroyed ", co) + end + self.queue = {} + self.q_tip = 0 + self.q_tail = 0 + self.destroyed = true + + setmetatable(self, destroyed_lock_mt) + return true + end +end + + +local function timeout_handler(co) + local self = registry[co] + if not self then + return + end + + for i = self.q_tip, self.q_tail do + if co == self.queue[i] then + self.queue[i] = nil + self.errors[co] = "timeout" + --print("marked timeout ", co) + copas.wakeup(co) + return + end + end + -- if we get here, we own it currently, or we finished it by now, or + -- the lock was destroyed. Anyway, nothing to do here... +end + + +--- Acquires the lock. +-- If the lock is owned by another thread, this will yield control, until the +-- lock becomes available, or it times out. +-- If `timeout == 0` then it will immediately return (without yielding). +-- @param timeout (optional) timeout in seconds, defaults to the timeout passed to `new` (use `math.huge` to have no timeout). +-- @return wait-time on success, or nil+error+wait_time on failure. Errors can be "timeout", "destroyed", or "lock is not re-entrant" +function lock:get(timeout) + local co = coroutine.running() + local start_time + + -- is the lock already taken? + if self.owner then + -- are we re-entering? + if co == self.owner and not self.not_reentrant then + self.call_count = self.call_count + 1 + return 0 + end + + self.queue[self.q_tail] = co + self.q_tail = self.q_tail + 1 + timeout = timeout or self.timeout + if timeout == 0 then + return nil, "timeout", 0 + end + + -- set up timeout + registry[co] = self + copas.timeout(timeout, timeout_handler) + + start_time = gettime() + copas.pauseforever() + + local err = self.errors[co] + self.errors[co] = nil + registry[co] = nil + + --print("released ", co, err) + if err ~= "timeout" then + copas.timeout(0) + end + if err then + return nil, err, gettime() - start_time + end + end + + -- it's ours to have + self.owner = co + self.call_count = 1 + return start_time and (gettime() - start_time) or 0 +end + + +--- Releases the lock currently held. +-- Releasing a lock that is not owned by the current co-routine will return +-- an error. +-- returns true, or nil+err on an error +function lock:release() + local co = coroutine.running() + + if co ~= self.owner then + return nil, "cannot release a lock not owned" + end + + self.call_count = self.call_count - 1 + if self.call_count > 0 then + -- same coro is still holding it + return true + end + + -- need a loop, since individual coroutines might have been removed + -- so there might be holes + while self.q_tip < self.q_tail do + local next_up = self.queue[self.q_tip] + if next_up then + self.owner = next_up + self.queue[self.q_tip] = nil + self.q_tip = self.q_tip + 1 + copas.wakeup(next_up) + return true + end + self.q_tip = self.q_tip + 1 + end + -- queue is empty, reset pointers + self.owner = nil + self.q_tip = 0 + self.q_tail = 0 + return true +end + + + +return lock diff --git a/thirdparty/copas/src/copas/queue.lua b/thirdparty/copas/src/copas/queue.lua new file mode 100644 index 000000000..01cd53c4a --- /dev/null +++ b/thirdparty/copas/src/copas/queue.lua @@ -0,0 +1,191 @@ +local copas = require "copas" +local Sema = copas.semaphore +local Lock = copas.lock + + +local Queue = {} +Queue.__index = Queue + + +local new_name do + local count = 0 + + function new_name() + count = count + 1 + return "copas_queue_" .. count + end +end + + +-- Creates a new Queue instance +function Queue.new(opts) + opts = opts or {} + local self = {} + setmetatable(self, Queue) + self.name = opts.name or new_name() + self.sema = Sema.new(10^9) + self.head = 1 + self.tail = 1 + self.list = {} + self.workers = setmetatable({}, { __mode = "k" }) + self.stopping = false + self.worker_id = 0 + return self +end + + +-- Pushes an item in the queue (can be 'nil') +-- returns true, or nil+err ("stopping", or "destroyed") +function Queue:push(item) + if self.stopping then + return nil, "stopping" + end + self.list[self.head] = item + self.head = self.head + 1 + self.sema:give() + return true +end + + +-- Pops and item from the queue. If there are no items in the queue it will yield +-- until there are or a timeout happens (exception is when `timeout == 0`, then it will +-- not yield but return immediately). If the timeout is `math.huge` it will wait forever. +-- Returns item, or nil+err ("timeout", or "destroyed") +function Queue:pop(timeout) + local ok, err = self.sema:take(1, timeout) + if not ok then + return ok, err + end + + local item = self.list[self.tail] + self.list[self.tail] = nil + self.tail = self.tail + 1 + + if self.tail == self.head then + -- reset queue + self.list = {} + self.tail = 1 + self.head = 1 + if self.stopping then + -- we're stopping and last item being returned, so we're done + self:destroy() + end + end + return item +end + + +-- return the number of items left in the queue +function Queue:get_size() + return self.head - self.tail +end + + +-- instructs the queue to stop. Will not accept any more 'push' calls. +-- will autocall 'destroy' when the queue is empty. +-- returns immediately. See `finish` +function Queue:stop() + if not self.stopping then + self.stopping = true + self.lock = Lock.new(nil, true) + self.lock:get() -- close the lock + if self:get_size() == 0 then + -- queue is already empty, so "pop" function cannot call destroy on next + -- pop, so destroy now. + self:destroy() + end + end + return true +end + + +-- Finishes a queue. Calls stop and then waits for the queue to run empty (and be +-- destroyed) before returning. returns true or nil+err ("timeout", or "destroyed") +-- Parameter no_destroy_on_timeout indicates if the queue is not to be forcefully +-- destroyed on a timeout. +function Queue:finish(timeout, no_destroy_on_timeout) + self:stop() + local _, err = self.lock:get(timeout) + -- the lock never gets released, only destroyed, so we have to check the error string + if err == "timeout" then + if not no_destroy_on_timeout then + self:destroy() + end + return nil, err + end + return true +end + + +do + local destroyed_func = function() + return nil, "destroyed" + end + + local destroyed_queue_mt = { + __index = function() + return destroyed_func + end + } + + -- destroys a queue immediately. Abandons what is left in the queue. + -- Releases all waiting threads with `nil+"destroyed"` + function Queue:destroy() + if self.lock then + self.lock:destroy() + end + self.sema:destroy() + setmetatable(self, destroyed_queue_mt) + + -- clear anything left in the queue + for key in pairs(self.list) do + self.list[key] = nil + end + + return true + end +end + + +-- adds a worker that will handle whatever is passed into the queue. Can be called +-- multiple times to add more workers. +-- The threads automatically exit when the queue is destroyed. +-- worker function signature: `function(item)` (Note: worker functions run +-- unprotected, so wrap code in an (x)pcall if errors are expected, otherwise the +-- worker will exit on an error, and queue handling will stop) +-- Returns the coroutine added. +function Queue:add_worker(worker) + assert(type(worker) == "function", "expected worker to be a function") + local coro + + self.worker_id = self.worker_id + 1 + local worker_name = self.name .. ":worker_" .. self.worker_id + + coro = copas.addnamedthread(worker_name, function() + while true do + local item, err = self:pop(math.huge) -- wait forever + if err then + break -- queue destroyed, exit + end + worker(item) -- TODO: wrap in errorhandling + end + self.workers[coro] = nil + end) + + self.workers[coro] = true + return coro +end + +-- returns a list/array of current workers (coroutines) handling the queue. +-- (only the workers added by `add_worker`, and still active, will be in this list) +function Queue:get_workers() + local lst = {} + for coro in pairs(self.workers) do + if coroutine.status(coro) ~= "dead" then + lst[#lst+1] = coro + end + end + return lst +end + +return Queue diff --git a/thirdparty/copas/src/copas/semaphore.lua b/thirdparty/copas/src/copas/semaphore.lua new file mode 100644 index 000000000..0f4fda362 --- /dev/null +++ b/thirdparty/copas/src/copas/semaphore.lua @@ -0,0 +1,202 @@ +local copas = require("copas") + +local DEFAULT_TIMEOUT = 10 + +local semaphore = {} +semaphore.__index = semaphore + + +-- registry, semaphore indexed by the coroutines using them. +local registry = setmetatable({}, { __mode="kv" }) + + +-- create a new semaphore +-- @param max maximum number of resources the semaphore can hold (this maximum does NOT include resources that have been given but not yet returned). +-- @param start (optional, default 0) the initial resources available +-- @param seconds (optional, default 10) default semaphore timeout in seconds, or `math.huge` to have no timeout. +function semaphore.new(max, start, seconds) + local timeout = tonumber(seconds or DEFAULT_TIMEOUT) or -1 + if timeout < 0 then + error("expected timeout (2nd argument) to be a number greater than or equal to 0, got: " .. tostring(seconds), 2) + end + if type(max) ~= "number" or max < 1 then + error("expected max resources (1st argument) to be a number greater than 0, got: " .. tostring(max), 2) + end + + local self = setmetatable({ + count = start or 0, + max = max, + timeout = timeout, + q_tip = 1, -- position of next entry waiting + q_tail = 1, -- position where next one will be inserted + queue = {}, + to_flags = setmetatable({}, { __mode = "k" }), -- timeout flags indexed by coroutine + }, semaphore) + + return self +end + + +do + local destroyed_func = function() + return nil, "destroyed" + end + + local destroyed_semaphore_mt = { + __index = function() + return destroyed_func + end + } + + -- destroy a semaphore. + -- Releases all waiting threads with `nil+"destroyed"` + function semaphore:destroy() + self:give(math.huge) + self.destroyed = true + setmetatable(self, destroyed_semaphore_mt) + return true + end +end + + +-- Gives resources. +-- @param given (optional, default 1) number of resources to return. If more +-- than the maximum are returned then it will be capped at the maximum and +-- error "too many" will be returned. +function semaphore:give(given) + local err + given = given or 1 + local count = self.count + given + --print("now at",count, ", after +"..given) + if count > self.max then + count = self.max + err = "too many" + end + + while self.q_tip < self.q_tail do + local i = self.q_tip + local nxt = self.queue[i] -- there can be holes, so nxt might be nil + if not nxt then + self.q_tip = i + 1 + else + if count >= nxt.requested then + -- release it + self.queue[i] = nil + self.to_flags[nxt.co] = nil + count = count - nxt.requested + self.q_tip = i + 1 + copas.wakeup(nxt.co) + nxt.co = nil + else + break -- we ran out of resources + end + end + end + + if self.q_tip == self.q_tail then -- reset queue + self.queue = {} + self.q_tip = 1 + self.q_tail = 1 + end + + self.count = count + if err then + return nil, err + end + return true +end + + + +local function timeout_handler(co) + local self = registry[co] + --print("checking timeout ", co) + if not self then + return + end + + for i = self.q_tip, self.q_tail do + local item = self.queue[i] + if item and co == item.co then + self.queue[i] = nil + self.to_flags[co] = true + --print("marked timeout ", co) + copas.wakeup(co) + return + end + end + -- nothing to do here... +end + + +-- Requests resources from the semaphore. +-- Waits if there are not enough resources available before returning. +-- @param requested (optional, default 1) the number of resources requested +-- @param timeout (optional, defaults to semaphore timeout) timeout in +-- seconds. If 0 it will either succeed or return immediately with error "timeout". +-- If `math.huge` it will wait forever. +-- @return true, or nil+"destroyed" +function semaphore:take(requested, timeout) + requested = requested or 1 + if self.q_tail == 1 and self.count >= requested then + -- nobody is waiting before us, and there is enough in store + self.count = self.count - requested + return true + end + + if requested > self.max then + return nil, "too many" + end + + local to = timeout or self.timeout + if to == 0 then + return nil, "timeout" + end + + -- get in line + local co = coroutine.running() + self.to_flags[co] = nil + registry[co] = self + copas.timeout(to, timeout_handler) + + self.queue[self.q_tail] = { + co = co, + requested = requested, + --timeout = nil, -- flag indicating timeout + } + self.q_tail = self.q_tail + 1 + + copas.pauseforever() -- block until woken + registry[co] = nil + + if self.to_flags[co] then + -- a timeout happened + self.to_flags[co] = nil + return nil, "timeout" + end + + copas.timeout(0) + + if self.destroyed then + return nil, "destroyed" + end + + return true +end + +-- returns current available resources +function semaphore:get_count() + return self.count +end + +-- returns total shortage for requested resources +function semaphore:get_wait() + local wait = 0 + for i = self.q_tip, self.q_tail - 1 do + wait = wait + ((self.queue[i] or {}).requested or 0) + end + return wait - self.count +end + + +return semaphore diff --git a/thirdparty/copas/src/copas/smtp.lua b/thirdparty/copas/src/copas/smtp.lua new file mode 100644 index 000000000..c48899567 --- /dev/null +++ b/thirdparty/copas/src/copas/smtp.lua @@ -0,0 +1,34 @@ +------------------------------------------------------------------- +-- identical to the socket.smtp module except that it uses +-- async wrapped Copas sockets + +local copas = require("copas") +local smtp = require("socket.smtp") +local socket = require("socket") + +local create = function() return copas.wrap(socket.tcp()) end +local forwards = { -- setting these will be forwarded to the original smtp module + PORT = true, + SERVER = true, + TIMEOUT = true, + DOMAIN = true, + TIMEZONE = true +} + +copas.smtp = setmetatable({}, { + -- use original module as metatable, to lookup constants like socket.SERVER, etc. + __index = smtp, + -- Setting constants is forwarded to the luasocket.smtp module. + __newindex = function(self, key, value) + if forwards[key] then smtp[key] = value return end + return rawset(self, key, value) + end, + }) +local _M = copas.smtp + +_M.send = function(mailt) + mailt.create = mailt.create or create + return smtp.send(mailt) +end + +return _M \ No newline at end of file diff --git a/thirdparty/copas/src/copas/timer.lua b/thirdparty/copas/src/copas/timer.lua new file mode 100644 index 000000000..09041eaaf --- /dev/null +++ b/thirdparty/copas/src/copas/timer.lua @@ -0,0 +1,130 @@ +local copas = require("copas") + +local xpcall = xpcall +local coroutine_running = coroutine.running + +if _VERSION=="Lua 5.1" and not jit then -- obsolete: only for Lua 5.1 compatibility + xpcall = require("coxpcall").xpcall + coroutine_running = require("coxpcall").running +end + + +local timer = {} +timer.__index = timer + + +local new_name do + local count = 0 + + function new_name() + count = count + 1 + return "copas_timer_" .. count + end +end + + +do + local function expire_func(self, initial_delay) + if self.errorhandler then + copas.seterrorhandler(self.errorhandler) + end + copas.pause(initial_delay) + while true do + if not self.cancelled then + if not self.recurring then + -- non-recurring timer + self.cancelled = true + self.co = nil + + self:callback(self.params) + return + + else + -- recurring timer + self:callback(self.params) + end + end + + if self.cancelled then + -- clean up and exit the thread + self.co = nil + self.cancelled = true + return + end + + copas.pause(self.delay) + end + end + + + --- Arms the timer object. + -- @param initial_delay (optional) the first delay to use, if not provided uses the timer delay + -- @return timer object, nil+error, or throws an error on bad input + function timer:arm(initial_delay) + assert(initial_delay == nil or initial_delay >= 0, "delay must be greater than or equal to 0") + if self.co then + return nil, "already armed" + end + + self.cancelled = false + self.co = copas.addnamedthread(self.name, expire_func, self, initial_delay or self.delay) + return self + end +end + + + +--- Cancels a running timer. +-- @return timer object, or nil+error +function timer:cancel() + if not self.co then + return nil, "not armed" + end + + if self.cancelled then + return nil, "already cancelled" + end + + self.cancelled = true + copas.wakeup(self.co) -- resume asap + copas.removethread(self.co) -- will immediately drop the thread upon resuming + self.co = nil + return self +end + + +do + -- xpcall error handler that forwards to the copas errorhandler + local ehandler = function(err_obj) + return copas.geterrorhandler()(err_obj, coroutine_running(), nil) + end + + + --- Creates a new timer object. + -- Note: the callback signature is: `function(timer_obj, params)`. + -- @param opts (table) `opts.delay` timer delay in seconds, `opts.callback` function to execute, `opts.recurring` boolean + -- `opts.params` (optional) this value will be passed to the timer callback, `opts.initial_delay` (optional) the first delay to use, defaults to `delay`. + -- @return timer object, or throws an error on bad input + function timer.new(opts) + assert(opts.delay or -1 >= 0, "delay must be greater than or equal to 0") + assert(type(opts.callback) == "function", "expected callback to be a function") + + local callback = function(timer_obj, params) + xpcall(opts.callback, ehandler, timer_obj, params) + end + + return setmetatable({ + name = opts.name or new_name(), + delay = opts.delay, + callback = callback, + recurring = not not opts.recurring, + params = opts.params, + cancelled = false, + errorhandler = opts.errorhandler, + }, timer):arm(opts.initial_delay) + end +end + + + +return timer diff --git a/thirdparty/copas/tests/certs/_readme.md b/thirdparty/copas/tests/certs/_readme.md new file mode 100644 index 000000000..1cd8396be --- /dev/null +++ b/thirdparty/copas/tests/certs/_readme.md @@ -0,0 +1,3 @@ +The certificate generation scripts here are copied from LuaSec + + diff --git a/thirdparty/copas/tests/certs/all.bat b/thirdparty/copas/tests/certs/all.bat new file mode 100644 index 000000000..b1e03caff --- /dev/null +++ b/thirdparty/copas/tests/certs/all.bat @@ -0,0 +1,14 @@ +REM make sure the 'openssl.exe' commandline tool is in your path before starting! +REM set the path below; +set opensslpath=c:\program files (x86)\openssl-win32\bin + + + +setlocal +set path=%opensslpath%;%path% +call roota.bat +call rootb.bat +call servera.bat +call serverb.bat +call clienta.bat +call clientb.bat diff --git a/thirdparty/copas/tests/certs/all.sh b/thirdparty/copas/tests/certs/all.sh new file mode 100755 index 000000000..da6ac96c6 --- /dev/null +++ b/thirdparty/copas/tests/certs/all.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +CWD=$(PWD) +cd $( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) + +./rootA.sh +./rootB.sh +./serverA.sh +./serverB.sh +./clientA.sh +./clientB.sh + +cd $CWD diff --git a/thirdparty/copas/tests/certs/clientA.bat b/thirdparty/copas/tests/certs/clientA.bat new file mode 100644 index 000000000..112cdefe0 --- /dev/null +++ b/thirdparty/copas/tests/certs/clientA.bat @@ -0,0 +1,9 @@ +rem #!/bin/sh + +openssl req -newkey rsa:1024 -sha1 -keyout clientAkey.pem -out clientAreq.pem -nodes -config ./clientA.cnf -days 365 -batch + +openssl x509 -req -in clientAreq.pem -sha1 -extfile ./clientA.cnf -extensions usr_cert -CA rootA.pem -CAkey rootAkey.pem -CAcreateserial -out clientAcert.pem -days 365 + +copy clientAcert.pem + rootA.pem clientA.pem + +openssl x509 -subject -issuer -noout -in clientA.pem diff --git a/thirdparty/copas/tests/certs/clientA.cnf b/thirdparty/copas/tests/certs/clientA.cnf new file mode 100644 index 000000000..0fea78724 --- /dev/null +++ b/thirdparty/copas/tests/certs/clientA.cnf @@ -0,0 +1,316 @@ +# +# OpenSSL example configuration file. +# This is mostly being used for generation of certificate requests. +# + +# This definition stops the following lines choking if HOME isn't +# defined. +HOME = . +RANDFILE = $ENV::HOME/.rnd + +# Extra OBJECT IDENTIFIER info: +#oid_file = $ENV::HOME/.oid +oid_section = new_oids + +# To use this configuration file with the "-extfile" option of the +# "openssl x509" utility, name here the section containing the +# X.509v3 extensions to use: +# extensions = +# (Alternatively, use a configuration file that has only +# X.509v3 extensions in its main [= default] section.) + +[ new_oids ] + +# We can add new OIDs in here for use by 'ca' and 'req'. +# Add a simple OID like this: +# testoid1=1.2.3.4 +# Or use config file substitution like this: +# testoid2=${testoid1}.5.6 + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_default ] + +dir = ./demoCA # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +#unique_subject = no # Set to 'no' to allow creation of + # several ctificates with same subject. +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number + # must be commented out to leave a V1 CRL +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem # The private key +RANDFILE = $dir/private/.rand # private random number file + +x509_extensions = usr_cert # The extensions to add to the cert + +# Comment out the following two lines for the "traditional" +# (and highly broken) format. +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +# Extension copying option: use with caution. +# copy_extensions = copy + +# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs +# so this is commented out by default to leave a V1 CRL. +# crlnumber must also be commented out to leave a V1 CRL. +# crl_extensions = crl_ext + +default_days = 365 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = sha1 # which md to use. +preserve = no # keep passed DN ordering + +# A few difference way of specifying how similar the request should look +# For type CA, the listed attributes must be the same, and the optional +# and supplied fields are just that :-) +policy = policy_match + +# For the CA policy +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +# For the 'anything' policy +# At this point in time, you must list all acceptable 'object' +# types. +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +[ req ] +default_bits = 1024 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +x509_extensions = v3_ca # The extensions to add to the self signed cert + +# Passwords for private keys if not present they will be prompted for +# input_password = secret +# output_password = secret + +# This sets a mask for permitted string types. There are several options. +# default: PrintableString, T61String, BMPString. +# pkix : PrintableString, BMPString. +# utf8only: only UTF8Strings. +# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). +# MASK:XXXX a literal mask value. +# WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings +# so use this option with caution! +string_mask = nombstr + +# req_extensions = v3_req # The extensions to add to a certificate request + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = BR +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = Some-State +stateOrProvinceName_default = Espirito Santo + +localityName = Locality Name (eg, city) +localityName_default = Santo Antonio do Canaa + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = Sao Tonico Ltda + +# we can do this but it is not needed normally :-) +#1.organizationName = Second Organization Name (eg, company) +#1.organizationName_default = World Wide Web Pty Ltd + +organizationalUnitName = Organizational Unit Name (eg, section) +organizationalUnitName_default = Department of Computer Science + +commonName = Common Name (eg, YOUR name) +commonName_default = Client A +commonName_max = 64 + +emailAddress = Email Address +emailAddress_max = 64 + +# SET-ex3 = SET extension number 3 + +[ req_attributes ] +challengePassword = A challenge password +challengePassword_min = 4 +challengePassword_max = 20 + +unstructuredName = An optional company name + +[ usr_cert ] + +# These extensions are added when 'ca' signs a request. + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. + +# This is OK for an SSL server. +# nsCertType = server + +# For an object signing certificate this would be used. +# nsCertType = objsign + +# For normal client use this is typical +# nsCertType = client, email + +# and for everything including object signing: +# nsCertType = client, email, objsign + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +[ v3_req ] + +# Extensions to add to a certificate request + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +[ v3_ca ] + + +# Extensions for a typical CA + + +# PKIX recommendation. + +subjectKeyIdentifier=hash + +authorityKeyIdentifier=keyid:always,issuer:always + +# This is what PKIX recommends but some broken software chokes on critical +# extensions. +#basicConstraints = critical,CA:true +# So we do this instead. +basicConstraints = CA:true + +# Key usage: this is typical for a CA certificate. However since it will +# prevent it being used as an test self-signed certificate it is best +# left out by default. +# keyUsage = cRLSign, keyCertSign + +# Some might want this also +# nsCertType = sslCA, emailCA + +# Include email address in subject alt name: another PKIX recommendation +# subjectAltName=email:copy +# Copy issuer details +# issuerAltName=issuer:copy + +# DER hex encoding of an extension: beware experts only! +# obj=DER:02:03 +# Where 'obj' is a standard or added object +# You can even override a supported extension: +# basicConstraints= critical, DER:30:03:01:01:FF + +[ crl_ext ] + +# CRL extensions. +# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always,issuer:always + +[ proxy_cert_ext ] +# These extensions should be added when creating a proxy certificate + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. + +# This is OK for an SSL server. +# nsCertType = server + +# For an object signing certificate this would be used. +# nsCertType = objsign + +# For normal client use this is typical +# nsCertType = client, email + +# and for everything including object signing: +# nsCertType = client, email, objsign + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer:always + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +# This really needs to be in place for it to be a proxy certificate. +proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo diff --git a/thirdparty/copas/tests/certs/clientA.sh b/thirdparty/copas/tests/certs/clientA.sh new file mode 100755 index 000000000..0350ede69 --- /dev/null +++ b/thirdparty/copas/tests/certs/clientA.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +openssl req -newkey rsa:1024 -sha1 -keyout clientAkey.pem -out clientAreq.pem \ + -nodes -config ./clientA.cnf -days 365 -batch + +openssl x509 -req -in clientAreq.pem -sha1 -extfile ./clientA.cnf \ + -extensions usr_cert -CA rootA.pem -CAkey rootAkey.pem -CAcreateserial \ + -out clientAcert.pem -days 365 + +cat clientAcert.pem rootA.pem > clientA.pem + +openssl x509 -subject -issuer -noout -in clientA.pem diff --git a/thirdparty/copas/tests/certs/clientB.bat b/thirdparty/copas/tests/certs/clientB.bat new file mode 100644 index 000000000..9f341f605 --- /dev/null +++ b/thirdparty/copas/tests/certs/clientB.bat @@ -0,0 +1,9 @@ +rem #!/bin/sh + +openssl req -newkey rsa:1024 -sha1 -keyout clientBkey.pem -out clientBreq.pem -nodes -config ./clientB.cnf -days 365 -batch + +openssl x509 -req -in clientBreq.pem -sha1 -extfile ./clientB.cnf -extensions usr_cert -CA rootB.pem -CAkey rootBkey.pem -CAcreateserial -out clientBcert.pem -days 365 + +copy clientBcert.pem + rootB.pem clientB.pem + +openssl x509 -subject -issuer -noout -in clientB.pem diff --git a/thirdparty/copas/tests/certs/clientB.cnf b/thirdparty/copas/tests/certs/clientB.cnf new file mode 100644 index 000000000..7de08de19 --- /dev/null +++ b/thirdparty/copas/tests/certs/clientB.cnf @@ -0,0 +1,316 @@ +# +# OpenSSL example configuration file. +# This is mostly being used for generation of certificate requests. +# + +# This definition stops the following lines choking if HOME isn't +# defined. +HOME = . +RANDFILE = $ENV::HOME/.rnd + +# Extra OBJECT IDENTIFIER info: +#oid_file = $ENV::HOME/.oid +oid_section = new_oids + +# To use this configuration file with the "-extfile" option of the +# "openssl x509" utility, name here the section containing the +# X.509v3 extensions to use: +# extensions = +# (Alternatively, use a configuration file that has only +# X.509v3 extensions in its main [= default] section.) + +[ new_oids ] + +# We can add new OIDs in here for use by 'ca' and 'req'. +# Add a simple OID like this: +# testoid1=1.2.3.4 +# Or use config file substitution like this: +# testoid2=${testoid1}.5.6 + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_default ] + +dir = ./demoCA # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +#unique_subject = no # Set to 'no' to allow creation of + # several ctificates with same subject. +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number + # must be commented out to leave a V1 CRL +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem # The private key +RANDFILE = $dir/private/.rand # private random number file + +x509_extensions = usr_cert # The extensions to add to the cert + +# Comment out the following two lines for the "traditional" +# (and highly broken) format. +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +# Extension copying option: use with caution. +# copy_extensions = copy + +# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs +# so this is commented out by default to leave a V1 CRL. +# crlnumber must also be commented out to leave a V1 CRL. +# crl_extensions = crl_ext + +default_days = 365 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = sha1 # which md to use. +preserve = no # keep passed DN ordering + +# A few difference way of specifying how similar the request should look +# For type CA, the listed attributes must be the same, and the optional +# and supplied fields are just that :-) +policy = policy_match + +# For the CA policy +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +# For the 'anything' policy +# At this point in time, you must list all acceptable 'object' +# types. +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +[ req ] +default_bits = 1024 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +x509_extensions = v3_ca # The extensions to add to the self signed cert + +# Passwords for private keys if not present they will be prompted for +# input_password = secret +# output_password = secret + +# This sets a mask for permitted string types. There are several options. +# default: PrintableString, T61String, BMPString. +# pkix : PrintableString, BMPString. +# utf8only: only UTF8Strings. +# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). +# MASK:XXXX a literal mask value. +# WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings +# so use this option with caution! +string_mask = nombstr + +# req_extensions = v3_req # The extensions to add to a certificate request + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = BR +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = Some-State +stateOrProvinceName_default = Espirito Santo + +localityName = Locality Name (eg, city) +localityName_default = Santo Antonio do Canaa + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = Sao Tonico Ltda + +# we can do this but it is not needed normally :-) +#1.organizationName = Second Organization Name (eg, company) +#1.organizationName_default = World Wide Web Pty Ltd + +organizationalUnitName = Organizational Unit Name (eg, section) +organizationalUnitName_default = Department of Computer Science + +commonName = Common Name (eg, YOUR name) +commonName_default = Client B +commonName_max = 64 + +emailAddress = Email Address +emailAddress_max = 64 + +# SET-ex3 = SET extension number 3 + +[ req_attributes ] +challengePassword = A challenge password +challengePassword_min = 4 +challengePassword_max = 20 + +unstructuredName = An optional company name + +[ usr_cert ] + +# These extensions are added when 'ca' signs a request. + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. + +# This is OK for an SSL server. +# nsCertType = server + +# For an object signing certificate this would be used. +# nsCertType = objsign + +# For normal client use this is typical +# nsCertType = client, email + +# and for everything including object signing: +# nsCertType = client, email, objsign + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +[ v3_req ] + +# Extensions to add to a certificate request + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +[ v3_ca ] + + +# Extensions for a typical CA + + +# PKIX recommendation. + +subjectKeyIdentifier=hash + +authorityKeyIdentifier=keyid:always,issuer:always + +# This is what PKIX recommends but some broken software chokes on critical +# extensions. +#basicConstraints = critical,CA:true +# So we do this instead. +basicConstraints = CA:true + +# Key usage: this is typical for a CA certificate. However since it will +# prevent it being used as an test self-signed certificate it is best +# left out by default. +# keyUsage = cRLSign, keyCertSign + +# Some might want this also +# nsCertType = sslCA, emailCA + +# Include email address in subject alt name: another PKIX recommendation +# subjectAltName=email:copy +# Copy issuer details +# issuerAltName=issuer:copy + +# DER hex encoding of an extension: beware experts only! +# obj=DER:02:03 +# Where 'obj' is a standard or added object +# You can even override a supported extension: +# basicConstraints= critical, DER:30:03:01:01:FF + +[ crl_ext ] + +# CRL extensions. +# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always,issuer:always + +[ proxy_cert_ext ] +# These extensions should be added when creating a proxy certificate + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. + +# This is OK for an SSL server. +# nsCertType = server + +# For an object signing certificate this would be used. +# nsCertType = objsign + +# For normal client use this is typical +# nsCertType = client, email + +# and for everything including object signing: +# nsCertType = client, email, objsign + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer:always + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +# This really needs to be in place for it to be a proxy certificate. +proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo diff --git a/thirdparty/copas/tests/certs/clientB.sh b/thirdparty/copas/tests/certs/clientB.sh new file mode 100755 index 000000000..94f898622 --- /dev/null +++ b/thirdparty/copas/tests/certs/clientB.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +openssl req -newkey rsa:1024 -sha1 -keyout clientBkey.pem -out clientBreq.pem \ + -nodes -config ./clientB.cnf -days 365 -batch + +openssl x509 -req -in clientBreq.pem -sha1 -extfile ./clientB.cnf \ + -extensions usr_cert -CA rootB.pem -CAkey rootBkey.pem -CAcreateserial \ + -out clientBcert.pem -days 365 + +cat clientBcert.pem rootB.pem > clientB.pem + +openssl x509 -subject -issuer -noout -in clientB.pem diff --git a/thirdparty/copas/tests/certs/rootA.bat b/thirdparty/copas/tests/certs/rootA.bat new file mode 100644 index 000000000..6449bfa46 --- /dev/null +++ b/thirdparty/copas/tests/certs/rootA.bat @@ -0,0 +1,7 @@ +REM #!/bin/sh + +openssl req -newkey rsa:1024 -sha1 -keyout rootAkey.pem -out rootAreq.pem -nodes -config ./rootA.cnf -days 365 -batch + +openssl x509 -req -in rootAreq.pem -sha1 -extfile ./rootA.cnf -extensions v3_ca -signkey rootAkey.pem -out rootA.pem -days 365 + +openssl x509 -subject -issuer -noout -in rootA.pem diff --git a/thirdparty/copas/tests/certs/rootA.cnf b/thirdparty/copas/tests/certs/rootA.cnf new file mode 100644 index 000000000..2dc39c842 --- /dev/null +++ b/thirdparty/copas/tests/certs/rootA.cnf @@ -0,0 +1,315 @@ +# +# OpenSSL example configuration file. +# This is mostly being used for generation of certificate requests. +# + +# This definition stops the following lines choking if HOME isn't +# defined. +HOME = . +RANDFILE = $ENV::HOME/.rnd + +# Extra OBJECT IDENTIFIER info: +#oid_file = $ENV::HOME/.oid +oid_section = new_oids + +# To use this configuration file with the "-extfile" option of the +# "openssl x509" utility, name here the section containing the +# X.509v3 extensions to use: +# extensions = +# (Alternatively, use a configuration file that has only +# X.509v3 extensions in its main [= default] section.) + +[ new_oids ] + +# We can add new OIDs in here for use by 'ca' and 'req'. +# Add a simple OID like this: +# testoid1=1.2.3.4 +# Or use config file substitution like this: +# testoid2=${testoid1}.5.6 + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_default ] + +dir = ./demoCA # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +#unique_subject = no # Set to 'no' to allow creation of + # several ctificates with same subject. +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number + # must be commented out to leave a V1 CRL +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem # The private key +RANDFILE = $dir/private/.rand # private random number file + +x509_extensions = usr_cert # The extensions to add to the cert + +# Comment out the following two lines for the "traditional" +# (and highly broken) format. +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +# Extension copying option: use with caution. +# copy_extensions = copy + +# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs +# so this is commented out by default to leave a V1 CRL. +# crlnumber must also be commented out to leave a V1 CRL. +# crl_extensions = crl_ext + +default_days = 365 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = sha1 # which md to use. +preserve = no # keep passed DN ordering + +# A few difference way of specifying how similar the request should look +# For type CA, the listed attributes must be the same, and the optional +# and supplied fields are just that :-) +policy = policy_match + +# For the CA policy +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +# For the 'anything' policy +# At this point in time, you must list all acceptable 'object' +# types. +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +[ req ] +default_bits = 1024 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +x509_extensions = v3_ca # The extensions to add to the self signed cert + +# Passwords for private keys if not present they will be prompted for +# input_password = secret +# output_password = secret + +# This sets a mask for permitted string types. There are several options. +# default: PrintableString, T61String, BMPString. +# pkix : PrintableString, BMPString. +# utf8only: only UTF8Strings. +# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). +# MASK:XXXX a literal mask value. +# WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings +# so use this option with caution! +string_mask = nombstr + +# req_extensions = v3_req # The extensions to add to a certificate request + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = BR +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = Espirito Santo + +localityName = Locality Name (eg, city) +localityName_default = Santo Antonio do Canaa + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = Santo Tonico Ltda + +# we can do this but it is not needed normally :-) +#1.organizationName = Second Organization Name (eg, company) +#1.organizationName_default = World Wide Web Pty Ltd + +organizationalUnitName = Organizational Unit Name (eg, section) +organizationalUnitName_default = Department of Computer Science + +commonName = Common Name (eg, YOUR name) +commonName_max = 64 +commonName_default = Root A + +emailAddress = Email Address +emailAddress_max = 64 + +# SET-ex3 = SET extension number 3 + +[ req_attributes ] +challengePassword = A challenge password +challengePassword_min = 4 +challengePassword_max = 20 + +unstructuredName = An optional company name + +[ usr_cert ] + +# These extensions are added when 'ca' signs a request. + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. + +# This is OK for an SSL server. +# nsCertType = server + +# For an object signing certificate this would be used. +# nsCertType = objsign + +# For normal client use this is typical +# nsCertType = client, email + +# and for everything including object signing: +# nsCertType = client, email, objsign + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +[ v3_req ] + +# Extensions to add to a certificate request + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +[ v3_ca ] + + +# Extensions for a typical CA + + +# PKIX recommendation. + +subjectKeyIdentifier=hash + +authorityKeyIdentifier=keyid:always,issuer:always + +# This is what PKIX recommends but some broken software chokes on critical +# extensions. +#basicConstraints = critical,CA:true +# So we do this instead. +basicConstraints = CA:true + +# Key usage: this is typical for a CA certificate. However since it will +# prevent it being used as an test self-signed certificate it is best +# left out by default. +# keyUsage = cRLSign, keyCertSign + +# Some might want this also +# nsCertType = sslCA, emailCA + +# Include email address in subject alt name: another PKIX recommendation +# subjectAltName=email:copy +# Copy issuer details +# issuerAltName=issuer:copy + +# DER hex encoding of an extension: beware experts only! +# obj=DER:02:03 +# Where 'obj' is a standard or added object +# You can even override a supported extension: +# basicConstraints= critical, DER:30:03:01:01:FF + +[ crl_ext ] + +# CRL extensions. +# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always,issuer:always + +[ proxy_cert_ext ] +# These extensions should be added when creating a proxy certificate + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. + +# This is OK for an SSL server. +# nsCertType = server + +# For an object signing certificate this would be used. +# nsCertType = objsign + +# For normal client use this is typical +# nsCertType = client, email + +# and for everything including object signing: +# nsCertType = client, email, objsign + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer:always + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +# This really needs to be in place for it to be a proxy certificate. +proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo diff --git a/thirdparty/copas/tests/certs/rootA.sh b/thirdparty/copas/tests/certs/rootA.sh new file mode 100755 index 000000000..7b588bfd8 --- /dev/null +++ b/thirdparty/copas/tests/certs/rootA.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +openssl req -newkey rsa:1024 -sha1 -keyout rootAkey.pem -out rootAreq.pem -nodes -config ./rootA.cnf -days 365 -batch + +openssl x509 -req -in rootAreq.pem -sha1 -extfile ./rootA.cnf -extensions v3_ca -signkey rootAkey.pem -out rootA.pem -days 365 + +openssl x509 -subject -issuer -noout -in rootA.pem diff --git a/thirdparty/copas/tests/certs/rootB.bat b/thirdparty/copas/tests/certs/rootB.bat new file mode 100644 index 000000000..99f358ad5 --- /dev/null +++ b/thirdparty/copas/tests/certs/rootB.bat @@ -0,0 +1,7 @@ +rem #!/bin/sh + +openssl req -newkey rsa:1024 -sha1 -keyout rootBkey.pem -out rootBreq.pem -nodes -config ./rootB.cnf -days 365 -batch + +openssl x509 -req -in rootBreq.pem -sha1 -extfile ./rootB.cnf -extensions v3_ca -signkey rootBkey.pem -out rootB.pem -days 365 + +openssl x509 -subject -issuer -noout -in rootB.pem diff --git a/thirdparty/copas/tests/certs/rootB.cnf b/thirdparty/copas/tests/certs/rootB.cnf new file mode 100644 index 000000000..ee45752fe --- /dev/null +++ b/thirdparty/copas/tests/certs/rootB.cnf @@ -0,0 +1,315 @@ +# +# OpenSSL example configuration file. +# This is mostly being used for generation of certificate requests. +# + +# This definition stops the following lines choking if HOME isn't +# defined. +HOME = . +RANDFILE = $ENV::HOME/.rnd + +# Extra OBJECT IDENTIFIER info: +#oid_file = $ENV::HOME/.oid +oid_section = new_oids + +# To use this configuration file with the "-extfile" option of the +# "openssl x509" utility, name here the section containing the +# X.509v3 extensions to use: +# extensions = +# (Alternatively, use a configuration file that has only +# X.509v3 extensions in its main [= default] section.) + +[ new_oids ] + +# We can add new OIDs in here for use by 'ca' and 'req'. +# Add a simple OID like this: +# testoid1=1.2.3.4 +# Or use config file substitution like this: +# testoid2=${testoid1}.5.6 + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_default ] + +dir = ./demoCA # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +#unique_subject = no # Set to 'no' to allow creation of + # several ctificates with same subject. +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number + # must be commented out to leave a V1 CRL +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem # The private key +RANDFILE = $dir/private/.rand # private random number file + +x509_extensions = usr_cert # The extensions to add to the cert + +# Comment out the following two lines for the "traditional" +# (and highly broken) format. +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +# Extension copying option: use with caution. +# copy_extensions = copy + +# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs +# so this is commented out by default to leave a V1 CRL. +# crlnumber must also be commented out to leave a V1 CRL. +# crl_extensions = crl_ext + +default_days = 365 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = sha1 # which md to use. +preserve = no # keep passed DN ordering + +# A few difference way of specifying how similar the request should look +# For type CA, the listed attributes must be the same, and the optional +# and supplied fields are just that :-) +policy = policy_match + +# For the CA policy +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +# For the 'anything' policy +# At this point in time, you must list all acceptable 'object' +# types. +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +[ req ] +default_bits = 1024 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +x509_extensions = v3_ca # The extensions to add to the self signed cert + +# Passwords for private keys if not present they will be prompted for +# input_password = secret +# output_password = secret + +# This sets a mask for permitted string types. There are several options. +# default: PrintableString, T61String, BMPString. +# pkix : PrintableString, BMPString. +# utf8only: only UTF8Strings. +# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). +# MASK:XXXX a literal mask value. +# WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings +# so use this option with caution! +string_mask = nombstr + +# req_extensions = v3_req # The extensions to add to a certificate request + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = BR +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = Espirito Santo + +localityName = Locality Name (eg, city) +localityName_default = Santo Antonio do Canaa + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = Sao Tonico Ltda + +# we can do this but it is not needed normally :-) +#1.organizationName = Second Organization Name (eg, company) +#1.organizationName_default = World Wide Web Pty Ltd + +organizationalUnitName = Organizational Unit Name (eg, section) +organizationalUnitName_default = Department of Computer Science + +commonName = Common Name (eg, YOUR name) +commonName_default = Root B +commonName_max = 64 + +emailAddress = Email Address +emailAddress_max = 64 + +# SET-ex3 = SET extension number 3 + +[ req_attributes ] +challengePassword = A challenge password +challengePassword_min = 4 +challengePassword_max = 20 + +unstructuredName = An optional company name + +[ usr_cert ] + +# These extensions are added when 'ca' signs a request. + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. + +# This is OK for an SSL server. +# nsCertType = server + +# For an object signing certificate this would be used. +# nsCertType = objsign + +# For normal client use this is typical +# nsCertType = client, email + +# and for everything including object signing: +# nsCertType = client, email, objsign + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +[ v3_req ] + +# Extensions to add to a certificate request + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +[ v3_ca ] + + +# Extensions for a typical CA + + +# PKIX recommendation. + +subjectKeyIdentifier=hash + +authorityKeyIdentifier=keyid:always,issuer:always + +# This is what PKIX recommends but some broken software chokes on critical +# extensions. +#basicConstraints = critical,CA:true +# So we do this instead. +basicConstraints = CA:true + +# Key usage: this is typical for a CA certificate. However since it will +# prevent it being used as an test self-signed certificate it is best +# left out by default. +# keyUsage = cRLSign, keyCertSign + +# Some might want this also +# nsCertType = sslCA, emailCA + +# Include email address in subject alt name: another PKIX recommendation +# subjectAltName=email:copy +# Copy issuer details +# issuerAltName=issuer:copy + +# DER hex encoding of an extension: beware experts only! +# obj=DER:02:03 +# Where 'obj' is a standard or added object +# You can even override a supported extension: +# basicConstraints= critical, DER:30:03:01:01:FF + +[ crl_ext ] + +# CRL extensions. +# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always,issuer:always + +[ proxy_cert_ext ] +# These extensions should be added when creating a proxy certificate + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. + +# This is OK for an SSL server. +# nsCertType = server + +# For an object signing certificate this would be used. +# nsCertType = objsign + +# For normal client use this is typical +# nsCertType = client, email + +# and for everything including object signing: +# nsCertType = client, email, objsign + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer:always + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +# This really needs to be in place for it to be a proxy certificate. +proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo diff --git a/thirdparty/copas/tests/certs/rootB.sh b/thirdparty/copas/tests/certs/rootB.sh new file mode 100755 index 000000000..53969b349 --- /dev/null +++ b/thirdparty/copas/tests/certs/rootB.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +openssl req -newkey rsa:1024 -sha1 -keyout rootBkey.pem -out rootBreq.pem -nodes -config ./rootB.cnf -days 365 -batch + +openssl x509 -req -in rootBreq.pem -sha1 -extfile ./rootB.cnf -extensions v3_ca -signkey rootBkey.pem -out rootB.pem -days 365 + +openssl x509 -subject -issuer -noout -in rootB.pem diff --git a/thirdparty/copas/tests/certs/serverA.bat b/thirdparty/copas/tests/certs/serverA.bat new file mode 100644 index 000000000..78934d503 --- /dev/null +++ b/thirdparty/copas/tests/certs/serverA.bat @@ -0,0 +1,9 @@ +rem #!/bin/sh + +openssl req -newkey rsa:1024 -keyout serverAkey.pem -out serverAreq.pem -config ./serverA.cnf -nodes -days 365 -batch + +openssl x509 -req -in serverAreq.pem -sha1 -extfile ./serverA.cnf -extensions usr_cert -CA rootA.pem -CAkey rootAkey.pem -CAcreateserial -out serverAcert.pem -days 365 + +copy serverAcert.pem + rootA.pem serverA.pem + +openssl x509 -subject -issuer -noout -in serverA.pem diff --git a/thirdparty/copas/tests/certs/serverA.cnf b/thirdparty/copas/tests/certs/serverA.cnf new file mode 100644 index 000000000..b9c736f74 --- /dev/null +++ b/thirdparty/copas/tests/certs/serverA.cnf @@ -0,0 +1,316 @@ +# +# OpenSSL example configuration file. +# This is mostly being used for generation of certificate requests. +# + +# This definition stops the following lines choking if HOME isn't +# defined. +HOME = . +RANDFILE = $ENV::HOME/.rnd + +# Extra OBJECT IDENTIFIER info: +#oid_file = $ENV::HOME/.oid +oid_section = new_oids + +# To use this configuration file with the "-extfile" option of the +# "openssl x509" utility, name here the section containing the +# X.509v3 extensions to use: +# extensions = +# (Alternatively, use a configuration file that has only +# X.509v3 extensions in its main [= default] section.) + +[ new_oids ] + +# We can add new OIDs in here for use by 'ca' and 'req'. +# Add a simple OID like this: +# testoid1=1.2.3.4 +# Or use config file substitution like this: +# testoid2=${testoid1}.5.6 + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_default ] + +dir = ./demoCA # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +#unique_subject = no # Set to 'no' to allow creation of + # several ctificates with same subject. +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number + # must be commented out to leave a V1 CRL +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem # The private key +RANDFILE = $dir/private/.rand # private random number file + +x509_extensions = usr_cert # The extensions to add to the cert + +# Comment out the following two lines for the "traditional" +# (and highly broken) format. +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +# Extension copying option: use with caution. +# copy_extensions = copy + +# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs +# so this is commented out by default to leave a V1 CRL. +# crlnumber must also be commented out to leave a V1 CRL. +# crl_extensions = crl_ext + +default_days = 365 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = sha1 # which md to use. +preserve = no # keep passed DN ordering + +# A few difference way of specifying how similar the request should look +# For type CA, the listed attributes must be the same, and the optional +# and supplied fields are just that :-) +policy = policy_match + +# For the CA policy +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +# For the 'anything' policy +# At this point in time, you must list all acceptable 'object' +# types. +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +[ req ] +default_bits = 1024 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +x509_extensions = v3_ca # The extensions to add to the self signed cert + +# Passwords for private keys if not present they will be prompted for +# input_password = secret +# output_password = secret + +# This sets a mask for permitted string types. There are several options. +# default: PrintableString, T61String, BMPString. +# pkix : PrintableString, BMPString. +# utf8only: only UTF8Strings. +# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). +# MASK:XXXX a literal mask value. +# WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings +# so use this option with caution! +string_mask = nombstr + +# req_extensions = v3_req # The extensions to add to a certificate request + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = BR +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = Some-State +stateOrProvinceName_default = Espirito Santo + +localityName = Locality Name (eg, city) +localityName_default = Santo Antonio do Canaa + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = Sao Tonico Ltda + +# we can do this but it is not needed normally :-) +#1.organizationName = Second Organization Name (eg, company) +#1.organizationName_default = World Wide Web Pty Ltd + +organizationalUnitName = Organizational Unit Name (eg, section) +organizationalUnitName_default = Department of Computer Science + +commonName = Common Name (eg, YOUR name) +commonName_default = Server A +commonName_max = 64 + +emailAddress = Email Address +emailAddress_max = 64 + +# SET-ex3 = SET extension number 3 + +[ req_attributes ] +challengePassword = A challenge password +challengePassword_min = 4 +challengePassword_max = 20 + +unstructuredName = An optional company name + +[ usr_cert ] + +# These extensions are added when 'ca' signs a request. + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. + +# This is OK for an SSL server. +nsCertType = server + +# For an object signing certificate this would be used. +# nsCertType = objsign + +# For normal client use this is typical +# nsCertType = client, email + +# and for everything including object signing: +# nsCertType = client, email, objsign + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +[ v3_req ] + +# Extensions to add to a certificate request + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +[ v3_ca ] + + +# Extensions for a typical CA + + +# PKIX recommendation. + +subjectKeyIdentifier=hash + +authorityKeyIdentifier=keyid:always,issuer:always + +# This is what PKIX recommends but some broken software chokes on critical +# extensions. +#basicConstraints = critical,CA:true +# So we do this instead. +basicConstraints = CA:true + +# Key usage: this is typical for a CA certificate. However since it will +# prevent it being used as an test self-signed certificate it is best +# left out by default. +# keyUsage = cRLSign, keyCertSign + +# Some might want this also +# nsCertType = sslCA, emailCA + +# Include email address in subject alt name: another PKIX recommendation +# subjectAltName=email:copy +# Copy issuer details +# issuerAltName=issuer:copy + +# DER hex encoding of an extension: beware experts only! +# obj=DER:02:03 +# Where 'obj' is a standard or added object +# You can even override a supported extension: +# basicConstraints= critical, DER:30:03:01:01:FF + +[ crl_ext ] + +# CRL extensions. +# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always,issuer:always + +[ proxy_cert_ext ] +# These extensions should be added when creating a proxy certificate + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. + +# This is OK for an SSL server. +# nsCertType = server + +# For an object signing certificate this would be used. +# nsCertType = objsign + +# For normal client use this is typical +# nsCertType = client, email + +# and for everything including object signing: +# nsCertType = client, email, objsign + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer:always + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +# This really needs to be in place for it to be a proxy certificate. +proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo diff --git a/thirdparty/copas/tests/certs/serverA.sh b/thirdparty/copas/tests/certs/serverA.sh new file mode 100755 index 000000000..7fa04e05f --- /dev/null +++ b/thirdparty/copas/tests/certs/serverA.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +openssl req -newkey rsa:1024 -keyout serverAkey.pem -out serverAreq.pem \ + -config ./serverA.cnf -nodes -days 365 -batch + +openssl x509 -req -in serverAreq.pem -sha1 -extfile ./serverA.cnf \ + -extensions usr_cert -CA rootA.pem -CAkey rootAkey.pem -CAcreateserial \ + -out serverAcert.pem -days 365 + +cat serverAcert.pem rootA.pem > serverA.pem + +openssl x509 -subject -issuer -noout -in serverA.pem diff --git a/thirdparty/copas/tests/certs/serverB.bat b/thirdparty/copas/tests/certs/serverB.bat new file mode 100644 index 000000000..294be57f5 --- /dev/null +++ b/thirdparty/copas/tests/certs/serverB.bat @@ -0,0 +1,9 @@ +rem #!/bin/sh + +openssl req -newkey rsa:1024 -keyout serverBkey.pem -out serverBreq.pem -config ./serverB.cnf -nodes -days 365 -batch + +openssl x509 -req -in serverBreq.pem -sha1 -extfile ./serverB.cnf -extensions usr_cert -CA rootB.pem -CAkey rootBkey.pem -CAcreateserial -out serverBcert.pem -days 365 + +copy serverBcert.pem + rootB.pem serverB.pem + +openssl x509 -subject -issuer -noout -in serverB.pem diff --git a/thirdparty/copas/tests/certs/serverB.cnf b/thirdparty/copas/tests/certs/serverB.cnf new file mode 100644 index 000000000..ec5d031b7 --- /dev/null +++ b/thirdparty/copas/tests/certs/serverB.cnf @@ -0,0 +1,316 @@ +# +# OpenSSL example configuration file. +# This is mostly being used for generation of certificate requests. +# + +# This definition stops the following lines choking if HOME isn't +# defined. +HOME = . +RANDFILE = $ENV::HOME/.rnd + +# Extra OBJECT IDENTIFIER info: +#oid_file = $ENV::HOME/.oid +oid_section = new_oids + +# To use this configuration file with the "-extfile" option of the +# "openssl x509" utility, name here the section containing the +# X.509v3 extensions to use: +# extensions = +# (Alternatively, use a configuration file that has only +# X.509v3 extensions in its main [= default] section.) + +[ new_oids ] + +# We can add new OIDs in here for use by 'ca' and 'req'. +# Add a simple OID like this: +# testoid1=1.2.3.4 +# Or use config file substitution like this: +# testoid2=${testoid1}.5.6 + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_default ] + +dir = ./demoCA # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +#unique_subject = no # Set to 'no' to allow creation of + # several ctificates with same subject. +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number + # must be commented out to leave a V1 CRL +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem # The private key +RANDFILE = $dir/private/.rand # private random number file + +x509_extensions = usr_cert # The extensions to add to the cert + +# Comment out the following two lines for the "traditional" +# (and highly broken) format. +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +# Extension copying option: use with caution. +# copy_extensions = copy + +# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs +# so this is commented out by default to leave a V1 CRL. +# crlnumber must also be commented out to leave a V1 CRL. +# crl_extensions = crl_ext + +default_days = 365 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = sha1 # which md to use. +preserve = no # keep passed DN ordering + +# A few difference way of specifying how similar the request should look +# For type CA, the listed attributes must be the same, and the optional +# and supplied fields are just that :-) +policy = policy_match + +# For the CA policy +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +# For the 'anything' policy +# At this point in time, you must list all acceptable 'object' +# types. +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +[ req ] +default_bits = 1024 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +x509_extensions = v3_ca # The extensions to add to the self signed cert + +# Passwords for private keys if not present they will be prompted for +# input_password = secret +# output_password = secret + +# This sets a mask for permitted string types. There are several options. +# default: PrintableString, T61String, BMPString. +# pkix : PrintableString, BMPString. +# utf8only: only UTF8Strings. +# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). +# MASK:XXXX a literal mask value. +# WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings +# so use this option with caution! +string_mask = nombstr + +# req_extensions = v3_req # The extensions to add to a certificate request + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = BR +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = Some-State +stateOrProvinceName_default = Espirito Santo + +localityName = Locality Name (eg, city) +localityName_default = Santo Antonio do Canaa + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = Sao Tonico Ltda + +# we can do this but it is not needed normally :-) +#1.organizationName = Second Organization Name (eg, company) +#1.organizationName_default = World Wide Web Pty Ltd + +organizationalUnitName = Organizational Unit Name (eg, section) +organizationalUnitName_default = Department of Computer Science + +commonName = Common Name (eg, YOUR name) +commonName_default = Server B +commonName_max = 64 + +emailAddress = Email Address +emailAddress_max = 64 + +# SET-ex3 = SET extension number 3 + +[ req_attributes ] +challengePassword = A challenge password +challengePassword_min = 4 +challengePassword_max = 20 + +unstructuredName = An optional company name + +[ usr_cert ] + +# These extensions are added when 'ca' signs a request. + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. + +# This is OK for an SSL server. +nsCertType = server + +# For an object signing certificate this would be used. +# nsCertType = objsign + +# For normal client use this is typical +# nsCertType = client, email + +# and for everything including object signing: +# nsCertType = client, email, objsign + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +[ v3_req ] + +# Extensions to add to a certificate request + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +[ v3_ca ] + + +# Extensions for a typical CA + + +# PKIX recommendation. + +subjectKeyIdentifier=hash + +authorityKeyIdentifier=keyid:always,issuer:always + +# This is what PKIX recommends but some broken software chokes on critical +# extensions. +#basicConstraints = critical,CA:true +# So we do this instead. +basicConstraints = CA:true + +# Key usage: this is typical for a CA certificate. However since it will +# prevent it being used as an test self-signed certificate it is best +# left out by default. +# keyUsage = cRLSign, keyCertSign + +# Some might want this also +# nsCertType = sslCA, emailCA + +# Include email address in subject alt name: another PKIX recommendation +# subjectAltName=email:copy +# Copy issuer details +# issuerAltName=issuer:copy + +# DER hex encoding of an extension: beware experts only! +# obj=DER:02:03 +# Where 'obj' is a standard or added object +# You can even override a supported extension: +# basicConstraints= critical, DER:30:03:01:01:FF + +[ crl_ext ] + +# CRL extensions. +# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always,issuer:always + +[ proxy_cert_ext ] +# These extensions should be added when creating a proxy certificate + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. + +# This is OK for an SSL server. +# nsCertType = server + +# For an object signing certificate this would be used. +# nsCertType = objsign + +# For normal client use this is typical +# nsCertType = client, email + +# and for everything including object signing: +# nsCertType = client, email, objsign + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer:always + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +# This really needs to be in place for it to be a proxy certificate. +proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo diff --git a/thirdparty/copas/tests/certs/serverB.sh b/thirdparty/copas/tests/certs/serverB.sh new file mode 100755 index 000000000..c75b00aea --- /dev/null +++ b/thirdparty/copas/tests/certs/serverB.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +openssl req -newkey rsa:1024 -keyout serverBkey.pem -out serverBreq.pem \ + -config ./serverB.cnf -nodes -days 365 -batch + +openssl x509 -req -in serverBreq.pem -sha1 -extfile ./serverB.cnf \ + -extensions usr_cert -CA rootB.pem -CAkey rootBkey.pem -CAcreateserial \ + -out serverBcert.pem -days 365 + +cat serverBcert.pem rootB.pem > serverB.pem + +openssl x509 -subject -issuer -noout -in serverB.pem diff --git a/thirdparty/copas/tests/close.lua b/thirdparty/copas/tests/close.lua new file mode 100644 index 000000000..cbc31b39b --- /dev/null +++ b/thirdparty/copas/tests/close.lua @@ -0,0 +1,75 @@ +-- when a socket is being closed, while another coroutine +-- is reading on it, the `select` call does not return an event on that +-- socket. Hence the loop keeps running until the watch-dog kicks-in, reads +-- on the socket, and that returns the final "closed" error to the reading +-- coroutine. +-- +-- when a socket is closed, any read/write operation should return immediately +-- with a "closed" error. + +local copas = require "copas" +local socket = require "socket" + +copas.loop(function() + + local client_socket + local close_time + local send_end_time + local receive_end_time + + print "------------- starting close test ---------------" + + local function check_exit() + if receive_end_time and send_end_time then + -- both set, so we're done + print "success!" + os.exit(0) + end + end + + + do -- set up a server that accepts but doesn't read or write anything + local server = socket.bind("localhost", 20000) + + copas.addserver(server, copas.handler(function(conn_skt) + -- client connected, we're not doing anything, let the client + -- wait in the read/write queues + copas.pause(2) + -- now we're closing the connecting_socket + close_time = socket.gettime() + print("closing client socket now, client receive and send operation should immediately error out now") + client_socket:close() + + copas.pause(10) + conn_skt:close() + copas.removeserver(server) + print "timeout, test failed" + os.exit(1) + end)) + end + + + do -- create a client that connect to the server + client_socket = copas.wrap(socket.connect("localhost", 20000)) + + copas.addthread(function() + local data, err = client_socket:receive(1) + print("receive result: ", tostring(data), tostring(err)) + receive_end_time = socket.gettime() + print("receive took: ", receive_end_time - close_time) + check_exit() + end) + + copas.addthread(function() + local ok, err = true, nil + while ok do -- loop to fill any buffers, until we get stuck + ok, err = client_socket:send(("hello world"):rep(100)) + end + print("send result: ", tostring(ok), tostring(err)) + send_end_time = socket.gettime() + print("send took: ", send_end_time - close_time) + check_exit() + end) + end + +end) diff --git a/thirdparty/copas/tests/connecttwice.lua b/thirdparty/copas/tests/connecttwice.lua new file mode 100644 index 000000000..eaeedccc8 --- /dev/null +++ b/thirdparty/copas/tests/connecttwice.lua @@ -0,0 +1,36 @@ +-- test reconnecting a socket, should return an error "already connected" +-- test based on Windows behaviour, see comments in `copas.connect()` function +local copas = require("copas") +local socket = require("socket") + +local skt = copas.wrap(socket.tcp()) +local done = false + +copas.addthread(function() + print("First try... (should succeed)") + local ok, err = skt:connect("google.com", 80) + if ok then + print("Success") + else + print("Failed: "..err) + os.exit(1) + end + + print("\nSecond try... (should error as already connected)") + ok, err = skt:connect("thijsschreijer.nl", 80) + if ok then + print("Unexpected success") + os.exit(1) + else + print("Failed: "..err) + end + + done = true +end) + +copas.loop() + +if not done then + print("Loop completed with test not finished") + os.exit(1) +end diff --git a/thirdparty/copas/tests/errhandlers.lua b/thirdparty/copas/tests/errhandlers.lua new file mode 100644 index 000000000..cc5de10f9 --- /dev/null +++ b/thirdparty/copas/tests/errhandlers.lua @@ -0,0 +1,160 @@ +-- Tests Copas socket timeouts +-- +-- Run the test file, it should exit successfully without hanging. + +-- make sure we are pointing to the local copas first +package.path = string.format("../src/?.lua;%s", package.path) + + + +--local platform = "unix" +--if package.config:sub(1,1) == "\\" then +-- platform = "windows" +--elseif io.popen("uname", "r"):read("*a"):find("Darwin") then +-- platform = "mac" +--end +--print("Testing platform: " .. platform) + + + +local copas = require("copas") + + + +local tests = {} + +if _VERSION ~= "Lua 5.1" then + -- do not run these for Lua 5.1 since it has a different stacktrace + + tests.default_properly_formats_coro_errors = function() + local old_print = print + local msg + print = function(errmsg) --luacheck: ignore + msg = errmsg + --old_print(msg) + end + + copas.loop(function() + local f = function() + error("hi there!") + end + f() + end) + + print = old_print --luacheck: ignore + + assert(msg:find("errhandlers%.lua:%d-: hi there! %(coroutine: copas_initializer, socket: nil%)"), "got:\n"..msg) + assert(msg:find("stack traceback:.+errhandlers%.lua"), "got:\n"..msg) + end + + + tests.default_properly_formats_timerwheel_errors = function() + local old_print = print + local msg + print = function(errmsg) --luacheck: ignore + msg = errmsg + --old_print(msg) + end + + copas.loop(function() + copas.timeout(0.01, function(co) + local f = function() + error("hi there!") + end + f() + end) + copas.pause(1) + end) + + print = old_print --luacheck: ignore + + assert(msg:find("errhandlers%.lua:%d-: hi there! %(coroutine: copas_core_timer, socket: nil%)"), "got:\n"..msg) + assert(msg:find("stack traceback:.+errhandlers%.lua"), "got:\n"..msg) + end +end + + +tests.yielding_from_user_code_fails = function() + local old_print = print + local msg + print = function(errmsg) --luacheck: ignore + msg = errmsg + --old_print(msg) + end + + copas.loop(function() + copas.pause(1) + coroutine.yield() -- directly yield to Copas + end) + + print = old_print --luacheck: ignore + + assert(msg:find("coroutine.yield was called without a resume first, user-code cannot yield to Copas", 1, true), "got:\n"..msg) +end + + +tests.handler_gets_called_if_set = function() + local call_count = 0 + copas.loop(function() + copas.setErrorHandler(function() call_count = call_count + 1 end) + + error("end of the world!") + end) + + assert(call_count == 1, "expected callcount 1, got: " .. tostring(call_count)) +end + + + +tests.default_handler_gets_called_if_set = function() + local call_count = 0 + copas.setErrorHandler(function() call_count = call_count + 10 end, true) + copas.loop(function() + + error("end of the world!") + end) + + assert(call_count == 10, "expected callcount 10, got: " .. tostring(call_count)) +end + + + +tests.default_handler_doesnt_get_called_if_overridden = function() + local call_count = 0 + copas.setErrorHandler(function() call_count = call_count + 10 end, true) + copas.loop(function() + copas.setErrorHandler(function() call_count = call_count + 1 end) + + error("end of the world!") + end) + + assert(call_count == 1, "expected callcount 1, got: " .. tostring(call_count)) +end + + +tests.timerwheel_callbacks_call_the_default_error_handler = function() + local call_count = 0 + copas.setErrorHandler(function() call_count = call_count - 10 end, true) + copas.loop(function() + copas.timeout(0.01, function(co) error("hi there!") end) + copas.pause(1) + end) + + assert(call_count == -10, "expected callcount -10, got: " .. tostring(call_count)) +end + + +-- test "framework" +for name, test in pairs(tests) do + -- reload copas, to clear default err handlers + package.loaded.copas = nil + copas = require "copas" + + print("testing: "..tostring(name)) + local status, err = pcall(test) + if not status then + error(err) + end +end + +print("[✓] all tests completed successuly") diff --git a/thirdparty/copas/tests/exit.lua b/thirdparty/copas/tests/exit.lua new file mode 100644 index 000000000..ecf732100 --- /dev/null +++ b/thirdparty/copas/tests/exit.lua @@ -0,0 +1,65 @@ +-- tests whether the main loop automatically exits when all work is done + +local copas = require("copas") +local socket = require("socket") + +local done = false + +copas.addthread(function() + for i = 1, 5 do + copas.pause() + print(i) + end + + done = true +end) + +print("Test 1 count to 5... then exit") +copas.loop() + +if done then + print("Test 1 done") +else + print("Loop completed with test 1 not finished") + os.exit(1) +end + +done = false +local server = socket.bind("*", 20000) +local message = "Hello world!" + +copas.addserver(server, function(skt) + local received = copas.receive(skt) + + if received ~= message then + print("Incorrect return from copas.receive: "..tostring(received)) + os.exit(1) + else + print("Received "..message) + end + + copas.removeserver(server) + done = true +end) + +copas.addthread(function() + copas.pause(1) + local skt = socket.connect("localhost", 20000) + print("Sending "..message.."\\n") + local bytes = copas.send(skt, message.."\n") + + if bytes ~= #message + 1 then + print("Incorrect return from copas.send: "..tostring(bytes)) + os.exit(1) + end +end) + +print("Test 2 send and receive some... then exit") +copas.loop() + +if done then + print("Test 2 done") +else + print("Loop completed with test 2 not finished") + os.exit(1) +end diff --git a/thirdparty/copas/tests/exittest.lua b/thirdparty/copas/tests/exittest.lua new file mode 100644 index 000000000..5d4ad6771 --- /dev/null +++ b/thirdparty/copas/tests/exittest.lua @@ -0,0 +1,74 @@ +print([[ +Testing to automatically exit the copas loop when nothing remains to be done. +So none of the tests below should hang, as that means it did not exit... +============================================================================= + +]]) + +local copas = require("copas") +local testran + +print("1) Testing exiting when a task finishes before the loop even starts") +copas.addthread(function() + print("","1 running...") + testran = 1 +end) +copas.loop() +assert(testran == 1, "Test 1 was not executed!") +print("1) success") + +print("2) Testing exiting when a task finishes within the loop") +copas.addthread(function() + copas.pause(0.1) -- wait until loop is running + copas.pause(0.1) -- wait again to make sure its not the initial step in the loop + print("","2 running...") + testran = 2 +end) +copas.loop() +assert(testran == 2, "Test 2 was not executed!") +print("2) success") + +print("3) Testing exiting when a task fails before the loop even starts") +copas.addthread(function() + print("","3 running...") + testran = 3 + error("error on purpose") +end) +copas.loop() +assert(testran == 3, "Test 3 was not executed!") +print("3) success") + +print("4) Testing exiting when a task fails in the loop") +copas.addthread(function() + copas.pause(0.1) -- wait until loop is running + copas.pause(0.1) -- wait again to make sure its not the initial step in the loop + print("","4 running...") + testran = 4 + error("error on purpose") +end) +copas.loop() +assert(testran == 4, "Test 4 was not executed!") +print("4) success") + +print("5) Testing exiting when a task permanently sleeps before the loop") +copas.addthread(function() + print("","5 running...") + testran = 5 + copas.pauseforever() -- sleep until explicitly woken up +end) +copas.loop() +assert(testran == 5, "Test 5 was not executed!") +print("5) success") + +print("6) Testing exiting when a task permanently sleeps in the loop") +copas.addthread(function() + copas.pause(0.1) -- wait until loop is running + copas.pause(0.1) -- wait again to make sure its not the initial step in the loop + print("","6 running...") + testran = 6 + copas.pauseforever() -- sleep until explicitly woken up +end) +copas.loop() +assert(testran == 6, "Test 6 was not executed!") +print("6) success") + diff --git a/thirdparty/copas/tests/http-timeout.lua b/thirdparty/copas/tests/http-timeout.lua new file mode 100644 index 000000000..5379b4bc0 --- /dev/null +++ b/thirdparty/copas/tests/http-timeout.lua @@ -0,0 +1,331 @@ +-- tests timeouts with http requests. +-- +-- in sending/receiving headers/body +-- + +local copas = require 'copas' +local socket = require 'socket' +local ltn12 = require 'ltn12' +local request = copas.http.request + +-- copas.debug.start() + +local response_body = ("A"):rep(1023).."x" -- 1 kb string +local response_headers = [[HTTP/1.1 200 OK +Date: Mon, 27 Jul 2009 12:28:53 GMT +Server: Apache/2.2.14 (Win32) +Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT +Content-Type: text/html +Content-Length: ]] +local response = response_headers .. tostring(#response_body) .. "\n\n" .. response_body +response = response:gsub("\13?\10", "\13\10") -- update lineendings + +local request_headers = { + Header1 = "header value 1 which is really a dummy value", + Header2 = "header value 2 which is really a dummy value", + Header3 = "header value 3 which is really a dummy value", + Header4 = "header value 4 which is really a dummy value", + Header5 = "header value 5 which is really a dummy value", + Header6 = "header value 6 which is really a dummy value", +} +local request_body = response_body +request_headers["Content-Length"] = #request_body +local request_size -- will be set on first test + +local timeout = 5 +local timeout_bytes_request +local timeout_bytes_response + + + +copas.setErrorHandler(function(msg, co, skt) + -- on any error we want to exit forcefully + print(copas.gettraceback(msg, co, skt)) + os.exit(1) +end, true) -- true: make it the default for all threads/coros + + + +local function runtest() + local s1 = socket.bind('*', 49500) + copas.addserver(s1, copas.handler(function(skt) + -- HTTP server that will optionally do a timeout on the request, or on the response + copas.setsocketname("Server 49500", skt) + copas.setthreadname("Server 49500") + -- read bytes up to where we're supposed to do the timeout (if at all) + print("Server reading port 49500: incoming connection") + skt:settimeout(1) + + if timeout_bytes_request then + print("Server reading port 49500: generating request-timeout at byte: ", timeout_bytes_request) + local res, err, part = skt:receive(timeout_bytes_request) + if not res then + print("Server reading port 49500: full request:", err, "received:", part) + os.exit(1) + end + -- we timeout on the request, so sleep here and exit + copas.pause(timeout + 1) -- sleep 1 second more than the requester timeout, to force a timeout on the request + print("Server reading port 49500: request-timeout complete") + return skt:close() + end + + -- receive full request + print("Server reading port 49500: reading full request") + local res, err, part = skt:receive("*a") + -- print("res",res) + -- print("err",err) + -- print("part",part) + if not res and err == "timeout" then + res = part + end + request_size = #res + print("request size: ", #res) + if not res then + print("Server reading port 49500: first chunk:", err, "received:", part) + os.exit(1) + end + + -- request was read, now send the reposnse + if not timeout_bytes_response then + -- just send full response, not timeout generation + local ok, err = skt:send(response) + if not ok then + print("Server reading port 49500: failed sending full response:", err) + os.exit(1) + end + print("Server reading port 49500: send full response, done!") + return skt:close() + end + + -- we send a partial response and timeout + print("Server reading port 49500: generating response-timeout at byte: ", timeout_bytes_response) + local ok, err = skt:send(response:sub(1, timeout_bytes_response)) + if not ok then + print("Server reading port 49500: failed sending partial response:", err) + os.exit(1) + end + + copas.pause(timeout + 1) -- sleep 1 second more than the requester timeout, to force a timeout on the response + print("Server reading port 49500: response-timeout complete") + return skt:close() + end)) + + + + copas.addnamedthread("test request", function() + + print "Waiting a bit for server to start..." + copas.pause(1) -- give server some time to start + + do + print("first test: succesfull round trip") + timeout_bytes_request = nil + timeout_bytes_response = nil + -- make request + local ok, rstatus, rheaders, rstatus_line = request { + url = "http://localhost:49500/some/path", + method = "POST", + headers = request_headers, + timeout = timeout, + source = ltn12.source.string(("B"):rep(1023).."x"), -- request body + sink = ltn12.sink.table({}), -- response body + } + print("Client received response: ") + print(" ok = "..tostring(ok)) + print(" status = "..tostring(rstatus)) + if not rheaders then + print(" headers = "..tostring(rheaders)) + else + print(" headers = {") + for k, v in pairs(rheaders) do + print(" "..tostring(k)..": "..tostring(v)) + end + print(" }") + end + print(" status_line = "..tostring(rstatus_line)) + if ok and rstatus == 200 then + print("Client: received a '200 OK', as expected!") + else + print("Client: error when requesting:", rstatus) + os.exit(1) + end + + -- cleanup; sleep 2 secs to wait for closing server socket + -- to ensure any error messages do not get intermixed with the next tests output + copas.pause(2) + print(("="):rep(80)) + end + + + do + print("second test: server generates time-out while receiving the headers") + timeout_bytes_request = 100 -- 100 bytes is still headers + timeout_bytes_response = nil + -- make request + local ok, rstatus, rheaders, rstatus_line = request { + url = "http://localhost:49500/some/path", + method = "POST", + headers = request_headers, + timeout = timeout, + source = ltn12.source.string(("B"):rep(1023).."x"), -- request body + sink = ltn12.sink.table({}), -- response body + } + print("Client received response: ") + print(" ok = "..tostring(ok)) + print(" status = "..tostring(rstatus)) + if not rheaders then + print(" headers = "..tostring(rheaders)) + else + print(" headers = {") + for k, v in pairs(rheaders) do + print(" "..tostring(k)..": "..tostring(v)) + end + print(" }") + end + print(" status_line = "..tostring(rstatus_line)) + if not ok and rstatus == "timeout" then + print("Client: received a timeout error, as expected!") + else + print("Client: error when requesting:", rstatus) + os.exit(1) + end + + -- cleanup; sleep 2 secs to wait for closing server socket + -- to ensure any error messages do not get intermixed with the next tests output + copas.pause(2) + print(("="):rep(80)) + end + + + do + print("third test: server generates time-out while receiving the body") + timeout_bytes_request = request_size - 500 -- body = 1k, so 500 before end is right in the middle of the body + timeout_bytes_response = nil + -- make request + local ok, rstatus, rheaders, rstatus_line = request { + url = "http://localhost:49500/some/path", + method = "POST", + headers = request_headers, + timeout = timeout, + source = ltn12.source.string(("B"):rep(1023).."x"), -- request body + sink = ltn12.sink.table({}), -- response body + } + print("Client received response: ") + print(" ok = "..tostring(ok)) + print(" status = "..tostring(rstatus)) + if not rheaders then + print(" headers = "..tostring(rheaders)) + else + print(" headers = {") + for k, v in pairs(rheaders) do + print(" "..tostring(k)..": "..tostring(v)) + end + print(" }") + end + print(" status_line = "..tostring(rstatus_line)) + if not ok and rstatus == "timeout" then + print("Client: received a timeout error, as expected!") + else + print("Client: error when requesting:", rstatus) + os.exit(1) + end + + -- cleanup; sleep 2 secs to wait for closing server socket + -- to ensure any error messages do not get intermixed with the next tests output + copas.pause(2) + print(("="):rep(80)) + end + + + do + print("fourth test: server generates time-out while sending the headers") + timeout_bytes_request = nil + timeout_bytes_response = 100 -- after 100 bytes, is still in the headers + -- make request + local ok, rstatus, rheaders, rstatus_line = request { + url = "http://localhost:49500/some/path", + method = "POST", + headers = request_headers, + timeout = timeout, + source = ltn12.source.string(("B"):rep(1023).."x"), -- request body + sink = ltn12.sink.table({}), -- response body + } + print("Client received response: ") + print(" ok = "..tostring(ok)) + print(" status = "..tostring(rstatus)) + if not rheaders then + print(" headers = "..tostring(rheaders)) + else + print(" headers = {") + for k, v in pairs(rheaders) do + print(" "..tostring(k)..": "..tostring(v)) + end + print(" }") + end + print(" status_line = "..tostring(rstatus_line)) + if not ok and rstatus == "timeout" then + print("Client: received a timeout error, as expected!") + else + print("Client: error when requesting:", rstatus) + os.exit(1) + end + + -- cleanup; sleep 2 secs to wait for closing server socket + -- to ensure any error messages do not get intermixed with the next tests output + copas.pause(2) + print(("="):rep(80)) + end + + + do + print("fifth test: server generates time-out while sending the body") + timeout_bytes_request = nil + timeout_bytes_response = #response - 500 -- body = 1024, so 500 before end is right in the middle of the body + -- make request + local ok, rstatus, rheaders, rstatus_line = request { + url = "http://localhost:49500/some/path", + method = "POST", + headers = request_headers, + timeout = timeout, + source = ltn12.source.string(("B"):rep(1023).."x"), -- request body + sink = ltn12.sink.table({}), -- response body + } + print("Client received response: ") + print(" ok = "..tostring(ok)) + print(" status = "..tostring(rstatus)) + if not rheaders then + print(" headers = "..tostring(rheaders)) + else + print(" headers = {") + for k, v in pairs(rheaders) do + print(" "..tostring(k)..": "..tostring(v)) + end + print(" }") + end + print(" status_line = "..tostring(rstatus_line)) + if not ok and rstatus == "timeout" then + print("Client: received a timeout error, as expected!") + else + print("Client: error when requesting:", rstatus) + os.exit(1) + end + + -- cleanup; sleep 2 secs to wait for closing server socket + -- to ensure any error messages do not get intermixed with the next tests output + copas.pause(2) + print(("="):rep(80)) + end + + + -- close server and exit + print("closing server and exiting...") + copas.removeserver(s1) + end) + + print("starting loop") + copas.loop() + print("Loop done") +end + +runtest() +print "test success!" diff --git a/thirdparty/copas/tests/httpredirect.lua b/thirdparty/copas/tests/httpredirect.lua new file mode 100644 index 000000000..ad2bb245d --- /dev/null +++ b/thirdparty/copas/tests/httpredirect.lua @@ -0,0 +1,91 @@ +-- test redirecting http <-> https combinations + +local copas = require("copas") +local http = copas.http +local ltn12 = require("ltn12") +local dump_all_headers = false +local redirect +local socket = require "socket" + + +local function doreq(url) + local reqt = { + url = url, + redirect = redirect, --> allows https-> http redirect + target = {}, + } + reqt.sink = ltn12.sink.table(reqt.target) + + local result, code, headers, status = http.request(reqt) + print(string.rep("-",70)) + print("Fetching:",url,"==>",code, status) + if dump_all_headers then + if headers then + print("HEADERS") + for k,v in pairs(headers) do print("",k,v) end + end + else + print(" at:", (headers or {}).location) + end + --print(string.rep("=",70)) + return result, code, headers, status +end + +local done = false + +copas.addthread(function() + local _, code, headers = doreq("https://goo.gl/UBCUc5") -- https --> https redirect + assert(tonumber(code)==200) + assert(headers.location == "https://github.com/brunoos/luasec") + print("https -> https redirect OK!") + copas.addthread(function() + local _, code, headers = doreq("http://goo.gl/UBCUc5") -- http --> https redirect + assert(tonumber(code)==200) + assert(headers.location == "https://github.com/brunoos/luasec") + print("http -> https redirect OK!") + copas.addthread(function() + --local result, code, headers, status = doreq("http://goo.gl/tBfqNu") -- http --> http redirect + -- the above no longer works for testing, since Google auto-inserts a + -- initial redirect to the same url, over https, hence the final + -- redirect is a downgrade which then errors out + -- so we set up a local http-server to deal with this + local server = assert(socket.bind("127.0.0.1", 9876)) + local crlf = string.char(13)..string.char(10) + copas.addserver(server, function(skt) + skt = copas.wrap(skt) + assert(skt:receive()) + local response = + "HTTP/1.1 302 Found" .. crlf .. + "Location: http://www.httpvshttps.com" .. crlf .. crlf + assert(skt:send(response)) + skt:close() + end) + -- execute test request + local _, code, headers = doreq("http://localhost:9876/") -- http --> http redirect + copas.removeserver(server) -- immediately close server again + assert(tonumber(code)==200) + assert(headers.location == "http://www.httpvshttps.com") + print("http -> http redirect OK!") + copas.addthread(function() + local result, code = doreq("https://bit.ly/3vmhXhW") -- https --> http security test case + assert(result==nil and code == "Unallowed insecure redirect https to http") + print("https -> http redirect, while not allowed OK!:", code) + copas.addthread(function() + redirect = "all" + local _, code, headers = doreq("https://bit.ly/3vmhXhW") -- https --> http security test case + assert(tonumber(code)==200) + assert(headers.location == "http://www.httpvshttps.com/") + print("https -> http redirect, while allowed OK!") + done = true + end) + end) + end) + end) +end) + +copas.loop() + +if not done then + print("Some checks above failed") + os.exit(1) +end diff --git a/thirdparty/copas/tests/largetransfer.lua b/thirdparty/copas/tests/largetransfer.lua new file mode 100644 index 000000000..21045a907 --- /dev/null +++ b/thirdparty/copas/tests/largetransfer.lua @@ -0,0 +1,118 @@ +-- tests large transmissions, sending and receiving +-- uses `receive` and `receivepartial` +-- Does send the same string twice simultaneously +-- +-- Test should; +-- * show timer output, once per second, and actual time should be 1 second increments +-- * both transmissions should take appr. equal time, then they we're nicely cooperative + +local copas = require 'copas' +local socket = require 'socket' + +-- copas.debug.start() + +local body = ("A"):rep(1024*1024*50) -- 50 mb string +local start = socket.gettime() +local done = 0 +local sparams, cparams + +local function runtest() + local s1 = socket.bind('*', 49500) + copas.addserver(s1, copas.handler(function(skt) + copas.setsocketname("Server 49500", skt) + copas.setthreadname("Server 49500") + --skt:settimeout(0) -- don't set, uses `receive` method + local res, err, part = skt:receive('*a') + res = res or part + if res ~= body then print("Received doesn't match send") end + print("Server reading port 49500... Done!", socket.gettime()-start, err, #res) + copas.removeserver(s1) + done = done + 1 + end, sparams)) + + local s2 = socket.bind('*', 49501) + copas.addserver(s2, copas.handler(function(skt) + skt:settimeout(0) -- set, uses the `receivepartial` method + copas.setsocketname("Server 49501", skt) + copas.setthreadname("Server 49501") + local res, err, part = skt:receive('*a') + res = res or part + if res ~= body then print("Received doesn't match send") end + print("Server reading port 49501... Done!", socket.gettime()-start, err, #res) + copas.removeserver(s2) + done = done + 1 + end, sparams)) + + copas.addnamedthread("Client 49500", function() + local skt = socket.tcp() + skt = copas.wrap(skt, cparams) + copas.setsocketname("Client 49500", skt) + skt:connect("localhost", 49500) + local last_byte_sent, err + repeat + last_byte_sent, err = skt:send(body, last_byte_sent or 1, -1) + until last_byte_sent == nil or last_byte_sent == #body + print("Client writing port 49500... Done!", socket.gettime()-start, err, #body) + -- we're not closing the socket, so the Copas GC-when-idle can kick-in to clean up + skt = nil -- luacheck: ignore + done = done + 1 + end) + + copas.addnamedthread("Client 49501", function() + local skt = socket.tcp() + skt = copas.wrap(skt, cparams) + copas.setsocketname("Client 49501", skt) + skt:connect("localhost", 49501) + local last_byte_sent, err + repeat + last_byte_sent, err = skt:send(body, last_byte_sent or 1, -1) + until last_byte_sent == nil or last_byte_sent == #body + print("Client writing port 49501... Done!", socket.gettime()-start, err, #body) + -- we're not closing the socket, so the Copas GC-when-idle can kick-in to clean up + skt = nil -- luacheck: ignore + done = done + 1 + end) + + copas.addnamedthread("test timeout thread", function() + local i = 1 + while done ~= 4 do + copas.pause(1) + print(i, "seconds:", socket.gettime()-start) + i = i + 1 + if i > 60 then + print"timeout" + os.exit(1) + end + end + print "success!" + end) + + print("starting loop") + copas.loop() + print("Loop done") +end + +runtest() -- run test using regular connection (s/cparams == nil) + +-- set ssl parameters and do it again +sparams = { + mode = "server", + protocol = "tlsv1", + key = "tests/certs/serverAkey.pem", + certificate = "tests/certs/serverA.pem", + cafile = "tests/certs/rootA.pem", + verify = {"peer", "fail_if_no_peer_cert"}, + options = {"all", "no_sslv2"}, +} +cparams = { + mode = "client", + protocol = "tlsv1", + key = "tests/certs/clientAkey.pem", + certificate = "tests/certs/clientA.pem", + cafile = "tests/certs/rootA.pem", + verify = {"peer", "fail_if_no_peer_cert"}, + options = {"all", "no_sslv2"}, +} +done = 0 +start = socket.gettime() +runtest() diff --git a/thirdparty/copas/tests/lock.lua b/thirdparty/copas/tests/lock.lua new file mode 100644 index 000000000..0754053d3 --- /dev/null +++ b/thirdparty/copas/tests/lock.lua @@ -0,0 +1,107 @@ +-- make sure we are pointing to the local copas first +package.path = string.format("../src/?.lua;%s", package.path) + + + +local copas = require "copas" +local Lock = copas.lock +local gettime = require("socket").gettime + +local test_complete = false +copas.loop(function() + + local lock1 = Lock.new(nil, true) -- not re-entrant + assert(lock1:get()) + local s = gettime() + local _, err = lock1:get(1) + local duration = gettime() - s + assert(err == "timeout", "got errror: "..tostring(err)) + assert(duration > 1 and duration < 1.2, string.format("expected timeout of 1 second, but took: %f",duration)) + + -- let go and reacquire + assert(lock1:release()) + local _, err = lock1:release() + assert(err == "cannot release a lock not owned", "got error: "..tostring(err)) + + assert(lock1:get()) + lock1:destroy() + local _, err = lock1:release() + assert(err == "destroyed", "got errror: "..tostring(err)) + + + -- let's scale, go grab a lock + lock1 = assert(Lock.new(10)) + assert(lock1:get()) + + local success_count = 0 + local timeout_count = 0 + local destroyed_count = 0 + -- now add another bunch of threads for the same lock + local size = 750 -- must be multiple of 3 !! + print("creating "..size.." threads hitting the lock...", gettime()) + local tracker = {} + for i = 1, size do + tracker[i] = true + copas.addthread(function() + local timeout + if i > (size*2)/3 then + timeout = 60 -- the ones to hit "destroyed" + elseif i > size/3 and i <= (size*2)/3 then + timeout = 2 -- the ones to hit "timeout" + else + timeout = 1 -- the ones to succeed + end + --print(i, "waiting...") + local ok, err = lock1:get(timeout) + if ok then + --print(i, "got it!") + success_count = success_count + 1 + if i == size/3 then + copas.pause(3) -- keep it long enough for the next 500 to timeout + --print(i, "releasing ") + assert(lock1:release()) -- by now the 2nd 500 timed out + --print(i, "destroying ") + assert(lock1:destroy()) -- make the last 500 fail on "destroyed" + else + --print(i, "releasing ") + assert(lock1:release()) + end + tracker[i] = nil + + elseif err == "timeout" then + --print(i, "timed out!") + timeout_count = timeout_count + 1 + --if i == (size*2)/3 then + -- copas.pause(2) -- to ensure thread 500 finished its sleep above + --end + tracker[i] = nil + + elseif err == "destroyed" then + --print(i, "destroyed!") + destroyed_count = destroyed_count + 1 + tracker[i] = nil + + else + tracker[i] = nil + error("didn't expect error: '"..tostring(err).."' thread "..i) + end + + end) -- added thread function + end -- for loop + print("releasing "..size.." threads...", gettime()) + assert(lock1:release()) + print("waiting to finish...") + while next(tracker) do copas.pause(0.1) end + -- check results + print("success: ", success_count) + print("timeout: ", timeout_count) + print("destroyed: ", destroyed_count) + assert(success_count == size/3) + assert(timeout_count == size/3) + assert(destroyed_count == size/3) + + test_complete = true +end) +assert(test_complete, "test did not complete!") + +print("test success!") diff --git a/thirdparty/copas/tests/loop_starter.lua b/thirdparty/copas/tests/loop_starter.lua new file mode 100644 index 000000000..bada3c5b6 --- /dev/null +++ b/thirdparty/copas/tests/loop_starter.lua @@ -0,0 +1,13 @@ +-- make sure we are pointing to the local copas first +package.path = string.format("../src/?.lua;%s", package.path) +local copas = require "copas" + +local x + +copas.loop(function() + -- Copas initialization function + x = true +end) + +assert(x, "expected 'x' to be truthy") +print "test success!" diff --git a/thirdparty/copas/tests/pause.lua b/thirdparty/copas/tests/pause.lua new file mode 100644 index 000000000..385dda846 --- /dev/null +++ b/thirdparty/copas/tests/pause.lua @@ -0,0 +1,30 @@ +-- check no memory leaks when sleeping + +-- make sure we are pointing to the local copas first +package.path = string.format("../src/?.lua;%s", package.path) + +local copas = require("copas") + +local t1 = copas.addthread( + function() + copas.pauseforever() -- sleep until woken up + end +) + + +-- prepare GC test +local validate_gc = setmetatable({ + [t1] = true, + },{ __mode = "k" }) + +-- start test +copas.loop() + +t1 = nil -- luacheck: ignore +collectgarbage() +collectgarbage() + +--check GC +assert(next(validate_gc) == nil, "the 'validate_gc' table should have been empty!") + +print "test success!" diff --git a/thirdparty/copas/tests/queue.lua b/thirdparty/copas/tests/queue.lua new file mode 100644 index 000000000..2e6d71fb0 --- /dev/null +++ b/thirdparty/copas/tests/queue.lua @@ -0,0 +1,138 @@ +-- make sure we are pointing to the local copas first +package.path = string.format("../src/?.lua;%s", package.path) +local now = require("socket").gettime + + +local copas = require "copas" +local Queue = copas.queue + + + +local test_complete = false +copas.loop(function() + + -- basic push/pop + local q = Queue:new() + q:push "hello" + assert(q:pop() == "hello", "expected the input to be returned") + + -- yielding on pop when queue is empty + local s = now() + copas.addthread(function() + copas.pause(0.5) + q:push("delayed") + end) + assert(q:pop() == "delayed", "expected a delayed result") + assert(now() - s >= 0.5, "result was not delayed!") + + -- pop times out + local ok, err = q:pop(0.5) + assert(err == "timeout", "expected a timeout") + assert(ok == nil) + + -- get_size returns queue size + assert(q:get_size() == 0) + q:push(1) + assert(q:get_size() == 1) + q:push(2) + assert(q:get_size() == 2) + q:push(3) + assert(q:get_size() == 3) + + -- queue behaves as fifo + assert(q:pop() == 1) + assert(q:pop() == 2) + assert(q:pop() == 3) + + -- handles nil values + q:push(1) + q:push(nil) + q:push(3) + + assert(q:pop() == 1) + local val, err = q:pop() + assert(val == nil) + assert(err == nil) + assert(q:pop() == 3) + + -- stopping + q:push(1) + q:push(2) + q:push(3) + assert(q:stop()) + local count = 0 + local coro = q:add_worker(function(item) + count = count + 1 + end) + copas.pause(0.1) + assert(count == 3, "expected all 3 items handled") + assert(coroutine.status(coro) == "dead", "expected thread to be gone") + -- coro should be GC'able + local weak = setmetatable({}, {__mode="v"}) + weak[{}] = coro + coro = nil -- luacheck: ignore + collectgarbage() + collectgarbage() + assert(not next(weak)) + -- worker exited, so queue is destroyed now? + ok, err = q:push() + assert(err == "destroyed", "expected queue to be destroyed") + assert(ok == nil) + ok, err = q:pop() + assert(err == "destroyed", "expected queue to be destroyed") + assert(ok == nil) + + + test_complete = true +end) + +-- copas loop exited when here + +assert(test_complete, "test did not complete!") +print("test 1 success!") + + + +-- a worker handling nil values +local count = 0 +copas.loop(function() + local q = Queue:new() + q:push(1) + q:push(nil) + q:push(3) + q:add_worker(function() count = count + 1 end) + copas.pause(0.5) -- to activate the worker, which will now be blocked on the q semaphore + assert(q:finish(5)) +end) +assert(count == 3, "expected count to be 3, got "..tostring(count)) +print("test 2 success!") + + +-- finish blocks for a timeout +local passed = false +copas.loop(function() + local q = Queue:new() + q:push(1) -- no workers, so this one will not be handled + + local s = now() + local ok, err = q:finish(1) + local duration = now() - s + + assert(not ok, "expected a falsy value, got: "..tostring(ok)) + assert(err == "timeout", "expected error 'timeout', got: "..tostring(err)) + assert(duration > 1 and duration < 1.2, string.format("expected timeout of 1 second, but took: %f",duration)) + passed = true +end) +assert(passed, "test failed!") +print("test 3 success!") + + +-- destroying a queue while workers are idle +copas.loop(function() + local q = Queue:new() + q:add_worker(function() end) + copas.pause(0.5) -- to activate the worker, which will now be blocked on the q semaphore + q:stop() -- this should exit the idle workers and exit the copas loop +end) + +print("test 4 success!") diff --git a/thirdparty/copas/tests/removeserver.lua b/thirdparty/copas/tests/removeserver.lua new file mode 100644 index 000000000..cf6a280b8 --- /dev/null +++ b/thirdparty/copas/tests/removeserver.lua @@ -0,0 +1,40 @@ + +--- Test for removeserver(skt, true) +-- that keeps the socket open after removal. + +local copas = require("copas") +local socket = require("socket") + +local wskt = socket.bind("*", 0) +local whost, wport = wskt:getsockname() +wport = tonumber(wport) + +local function wait_for_trigger() + copas.addserver(wskt, function(cskt) + local data, _, partial = cskt:receive() + if partial and not data then + data = partial + end + print("triggered", data) + copas.removeserver(wskt, true) + end) +end + +local function trigger_it(n) + local cskt = socket.tcp() + local ok = cskt:connect(whost, wport) + if ok then + cskt:send("hi "..n) + end + cskt:close() +end + +copas.addthread(function() + for i = 1, 3 do + wait_for_trigger() + trigger_it(i) + copas.pause(0.1) + end +end) + +copas.loop() diff --git a/thirdparty/copas/tests/removethread.lua b/thirdparty/copas/tests/removethread.lua new file mode 100644 index 000000000..7c4dd7e33 --- /dev/null +++ b/thirdparty/copas/tests/removethread.lua @@ -0,0 +1,66 @@ +--- Test for removethread(thread) + +-- make sure we are pointing to the local copas first +package.path = string.format("../src/?.lua;%s", package.path) + +local copas = require("copas") +local now = require("socket").gettime + + +-- Test 1: basic test + +local t1 = copas.addthread( + function() + print("endless thread start") + local n = 0 + while true do + n = n + 1 + print("endless thread:",n) + copas.pause(0.5) + end + end) + +local t2 = copas.addthread(function() + for i = 1, 5 do + copas.pause(0.6) + end + print("stopping endless thread externally") + copas.removethread(t1) +end) + + +-- prepare GC test +local validate_gc = setmetatable({ + [t1] = true, + [t2] = true, + },{ __mode = "k" }) + +-- start test +copas.loop() + +t1 = nil -- luacheck: ignore +t2 = nil -- luacheck: ignore +collectgarbage() +collectgarbage() + +--check GC +assert(next(validate_gc) == nil, "the 'validate_gc' table should have been empty!") +print "test 1: success!" + + + +-- Test 2: ensure a waiting thread leaves in a timely manner + +local coro = copas.addthread(function() + copas.pause(2) +end) + +local start_time = now() +copas(function() + copas.pause(0.5) + copas.removethread(coro) +end) +local duration = now() - start_time + +assert(duration < 0.6, ("Expected immediate exit, after removal of thread, took: %.2f"):format(duration)) +print "test 2: success!" diff --git a/thirdparty/copas/tests/request.lua b/thirdparty/copas/tests/request.lua new file mode 100644 index 000000000..a8c0549ef --- /dev/null +++ b/thirdparty/copas/tests/request.lua @@ -0,0 +1,66 @@ +local copas = require("copas") +local http = copas.http + +local url = assert(arg[1], "missing url argument") +local debug_mode = not not arg[2] + +print("Testing copas.http.request with url " .. url .. (debug_mode and "(in debug mode)" or "")) +local switches, max_switches = 0, 10000000 +local done = false + + +if debug_mode then + copas.debug.start() + local socket = require "socket" + local old_tcp = socket.tcp + socket.tcp = function(...) + local sock, err = old_tcp(...) + if not sock then + return sock, err + end + return copas.debug.socket(sock) + end +end + + +copas.addthread(function() + while switches < max_switches do + copas.pause() + switches = switches + 1 + end + + if not done then + print(("Error: Request not finished after %d thread switches"):format(switches)) + os.exit(1) + end +end) + +copas.addthread(function() + print("Starting request") + local content, code, headers, status = http.request(url) + print(("Finished request after %d thread switches"):format(switches)) + + if type(content) ~= "string" or type(code) ~= "number" or + type(headers) ~= "table" or type(status) ~= "string" then + print("Error: incorrect return values:") + print(content) + print(code) + print(headers) + print(status) + os.exit(1) + end + + print(("Status: %s, content: %d bytes"):format(status, #content)) + done = true + max_switches = switches + 10 -- just do a few more and finish the test +end) + +print("Starting loop") +copas.loop() + +if done then + print("Finished loop") +else + print("Error: Finished loop but request is not complete") + os.exit(1) +end diff --git a/thirdparty/copas/tests/semaphore.lua b/thirdparty/copas/tests/semaphore.lua new file mode 100644 index 000000000..e9220147a --- /dev/null +++ b/thirdparty/copas/tests/semaphore.lua @@ -0,0 +1,146 @@ +-- make sure we are pointing to the local copas first +package.path = string.format("../src/?.lua;%s", package.path) +local now = require("socket").gettime + + +local copas = require "copas" +local semaphore = copas.semaphore + + + +local test_complete = false +copas.loop(function() + + local sema = semaphore.new(10, 5, 1) + assert(sema:get_count() == 5) + + assert(sema:take(3)) + assert(sema:get_count() == 2) + + local ok, _, err + local start = now() + _, err = sema:take(3, 0) -- 1 too many, immediate timeout + assert(err == "timeout", "expected a timeout") + assert(now() - start < 0.001, "expected it not to block with timeout = 0") + + start = now() + _, err = sema:take(10, 0) -- way too many, immediate timeout + assert(err == "timeout", "expected a timeout") + assert(now() - start < 0.001, "expected it not to block with timeout = 0") + + start = now() + _, err = sema:take(11) -- more than 'max'; "too many" error + assert(err == "too many", "expected a 'too many' error") + assert(now() - start < 0.001, "expected it not to block") + + start = now() + _, err = sema:take(10) -- not too many, let's timeout + assert(err == "timeout", "expected a 'timeout' error") + assert(now() - start > 1, "expected it to block for 1s") + + assert(sema:get_count() == 2) + + --validate async threads + local state = 0 + copas.addthread(function() + assert(sema:take(5)) + print("got the first 5!") + state = state + 1 + end) + copas.addthread(function() + assert(sema:take(5)) + print("got the next 5!") + state = state + 2 + end) + copas.pause(0.1) + assert(state == 0, "expected state to still be 0") + assert(sema:get_count() == 2, "expected count to still have 2 resources") + + assert(sema:give(4)) + assert(sema:get_count() == 1, "expected count to now have 1 resource") + copas.pause(0.1) + assert(state == 1, "expected 1 from the first thread to be added to state") + + assert(sema:give(4)) + assert(sema:get_count() == 0, "gave 4 more, so 5 in total, releasing 5, leaves 0 as expected") + copas.pause(0.1) + assert(state == 3, "expected 2 from the 2nd thread to be added to state") + + + ok, err = sema:give(100) + assert(not ok) + assert(err == "too many") + assert(sema:get_count() == 10) + + -- validate destroying + assert(sema:take(sema:get_count())) -- empty the semaphore + assert(sema:get_count() == 0, "should be empty now") + local state = 0 + copas.addthread(function() + local ok, err = sema:take(5) + if ok then + print("got 5, this is unexpected") + elseif err == "destroyed" then + state = state + 1 + end + end) + copas.addthread(function() + local ok, err = sema:take(5) + if ok then + print("got 5, this is unexpected") + elseif err == "destroyed" then + state = state + 1 + end + end) + copas.pause(0.1) + assert(sema:destroy()) + copas.pause(0.1) + assert(state == 2, "expected 2 threads to error with 'destroyed'") + + -- only returns errors from now on, on all methods + ok, err = sema:destroy(); assert(ok == nil and err == "destroyed", "expected an error") + ok, err = sema:give(1); assert(ok == nil and err == "destroyed", "expected an error") + ok, err = sema:take(1); assert(ok == nil and err == "destroyed", "expected an error") + ok, err = sema:get_count(); assert(ok == nil and err == "destroyed", "expected an error") + + + + -- timeouts get cancelled upon destruction + -- we set a timeout to 0.5 seconds, then destroy the semaphore + -- the timeout should not execute + -- Reproduce https://github.com/lunarmodules/copas/issues/118 + local track_table = setmetatable({}, { __mode = "v" }) + local sema = semaphore.new(10, 0, 0.5) + track_table.sema = sema + local state = 0 + track_table.coro = copas.addthread(function() + local ok, err = sema:take(1) + if ok then + print("got one, this is unexpected") + elseif err == "destroyed" then + state = state + 1 + end + end) + copas.pause(0.1) + assert(sema:destroy()) + copas.pause(0.1) + assert(state == 1, "expected 1 thread to error with 'destroyed'") + sema = nil + + local errors = 0 + copas.setErrorHandler(function(msg) + print("got error: "..tostring(msg)) + print("--------------------------------------") + errors = errors + 1 + end, true) + + collectgarbage() -- collect garbage to force eviction from the semaphore registry + collectgarbage() + + copas.pause(0.5) -- wait for the timeout to expire if it is still set + assert(errors == 0, "expected no errors") + + test_complete = true +end) +assert(test_complete, "test did not complete!") +print("test success!") diff --git a/thirdparty/copas/tests/starve.lua b/thirdparty/copas/tests/starve.lua new file mode 100644 index 000000000..6d07d3b0d --- /dev/null +++ b/thirdparty/copas/tests/starve.lua @@ -0,0 +1,87 @@ +-- tests looping 100% in receive/send +-- Should not prevent other threads from running +-- +-- Test should; +-- * sleep incremental, not on absolute time so it slowly diverges if the timer +-- thread is being starved +-- * seconds printed and elapsed should stay very close + +local copas = require 'copas' +local socket = require 'socket' + +--copas.debug.start() + +local body = ("A"):rep(1024*1024*50) -- 50 mb string +local done = 0 + +local function runtest() + local s1 = socket.bind('*', 49500) + copas.addserver(s1, copas.handler(function(skt) + copas.setsocketname("Server 49500", skt) + copas.setthreadname("Server 49500") + print "Server 49500 accepted incoming connection" + local end_time = socket.gettime() + 30 -- we run for 30 seconds + while end_time > socket.gettime() do + local res, err, _ = skt:receive(1) -- single byte from 50mb chunks + if res == nil and err ~= "timeout" then + print("Server 49500 returned: " .. err) + os.exit(1) + end + end + done = done + 1 + print("Server reading port 49500... Done!") + skt:close() + copas.removeserver(s1) + end)) + + copas.addnamedthread("Client 49500", function() + local skt = socket.tcp() + skt = copas.wrap(skt) + copas.setsocketname("Client 49500", skt) + skt:connect("localhost", 49500) + local last_byte_sent, err, complete + while not complete do + repeat + last_byte_sent, err = skt:send(body, last_byte_sent or 1, -1) + if err == "closed" then + -- server closed connection, so exit, test is finished + complete = true + break + end + if last_byte_sent == nil and err ~= "timeout" then + print("client 49500 returned: " .. err) + os.exit(1) + end + until last_byte_sent == nil or last_byte_sent == #body + end + print("Client writing port 49500... Done!") + skt:close() + done = done + 1 + end) + + copas.addnamedthread("test timeout thread", function() + local i = 0 + local start = socket.gettime() + while done ~= 2 do + copas.pause(1) -- delta sleep, so it slowly diverges if starved + i = i + 1 + local time_passed = socket.gettime()-start + print("slept "..i.." seconds, time passed: ".. time_passed.." seconds") + if math.abs(i - time_passed) > 2 then + print("timer diverged by more than 2 seconds: failed!") + os.exit(1) + end + if i > 60 then + print"timeout" + os.exit(1) + end + end + print "success!" + end) + + print("starting loop") + copas.loop() + print("Loop done") +end + +runtest() diff --git a/thirdparty/copas/tests/tcptimeout.lua b/thirdparty/copas/tests/tcptimeout.lua new file mode 100644 index 000000000..6d16251d5 --- /dev/null +++ b/thirdparty/copas/tests/tcptimeout.lua @@ -0,0 +1,169 @@ +-- Tests Copas socket timeouts +-- +-- Run the test file, it should exit successfully without hanging. + +-- make sure we are pointing to the local copas first +package.path = string.format("../src/?.lua;%s", package.path) + +local platform = "unix" +if package.config:sub(1,1) == "\\" then + platform = "windows" +elseif io.popen("uname", "r"):read("*a"):find("Darwin") then + platform = "mac" +end +print("Testing platform: " .. platform) + + +local copas = require("copas") +local socket = require("socket") + +-- hack; no way to kill copas.loop from thread +local function error(err) + print(debug.traceback(err, 2)) + os.exit(-1) +end +local function assert(truthy, err) + if not truthy then + print(debug.traceback(err, 2)) + os.exit(-1) + end +end + +-- tcp echo server for testing against, returns `ip, port` to connect to +-- send `quit\n` to cause server to disconnect client +-- stops listen server after first connection +local function singleuseechoserver() + local server = socket.bind("127.0.0.1", 0) -- "localhost" fails because of IPv6 error + local ip, port = server:getsockname() + + local function echoHandler(skt) + -- remove server after first connection + copas.removeserver(server) + + skt = copas.wrap(skt) + while true do + local data = skt:receive() + if not data or data == "quit" then + break + end + skt:send(data..'\n') + end + end + + copas.addserver(server, echoHandler) + + return ip, port +end + + + + +local tests = {} + +function tests.just_exit() + copas.loop() +end + +function tests.connect_and_exit() + local ip, port = singleuseechoserver() + copas.addthread(function() + local client = socket.connect(ip, port) + client = copas.wrap(client) + + client:close() + end) + + copas.loop() +end + + +if platform == "mac" then + -- this test fails on a Mac, looks like the 'listen(0)' isn't being honoured + print("\nSkipping test on Mac!\n") +else + function tests.connect_timeout_copas() + local server = socket.tcp() + server:bind("localhost", 0) + server:listen(0) -- zero backlog, single connection will block further connections + -- note: not servicing connections + local ip, port = server:getsockname() + + copas.addthread(function() + -- fill server's implicit connection backlog + socket.connect(ip,port) + + local client = socket.tcp() + client = copas.wrap(client) + client:settimeout(0.01) + local status, err = client:connect(ip, port) + assert(status == nil, "connect somehow succeeded") + assert(err == "timeout", "connect failed with non-timeout error: "..tostring(err)) + client:close() + end) + + copas.loop() + end + + + function tests.connect_timeout_socket() + local server = socket.tcp() + server:bind("localhost", 0) + server:listen(0) -- zero backlog, single connection will block further connections + -- note: not servicing connections + local ip, port = server:getsockname() + + copas.addthread(function() + copas.useSocketTimeoutErrors(true) + -- fill server's implicit connection backlog + socket.connect(ip,port) + + local client = socket.tcp() + client = copas.wrap(client) + client:settimeout(0.01) + local status, err = client:connect(ip, port) + assert(status == nil, "connect somehow succeeded") + -- we test for a different error message becasue we expect socket errors, not copas ones + assert(err == "Operation already in progress", "connect failed with non-timeout error: "..tostring(err)) + client:close() + end) + + copas.loop() + end +end + + +function tests.receive_timeout() + local ip, port = singleuseechoserver() + + copas.addthread(function() + local client = socket.tcp() + client = copas.wrap(client) + client:settimeout(0.01) + local status, err = client:connect(ip, port) + assert(status, "failed to connect: "..tostring(err)) + + client:send("foo\n") + local data, err = client:receive() + assert(data, "failed to recieve: "..tostring(err)) + assert(data == "foo", "recieved wrong echo: "..tostring(data)) + + local data, err = client:receive() + assert(data == nil, "somehow recieved echo without sending") + assert(err == "timeout", "failed with non-timeout error: "..tostring(err)) + + client:close() + end) + + copas.loop() +end + +-- test "framework" +for name, test in pairs(tests) do + print("testing: "..tostring(name)) + local status, err = pcall(test) + if not status then + error(err) + end +end + +print("[✓] all tests completed successuly") diff --git a/thirdparty/copas/tests/timeout_errors.lua b/thirdparty/copas/tests/timeout_errors.lua new file mode 100644 index 000000000..d0411bd85 --- /dev/null +++ b/thirdparty/copas/tests/timeout_errors.lua @@ -0,0 +1,50 @@ +-- Tests Copas timeout mnechanism, when a timeout handler errors out +-- +-- Run the test file, it should exit successfully without hanging. + +-- make sure we are pointing to the local copas first +package.path = string.format("../src/?.lua;%s", package.path) +local copas = require("copas") + + +local tests = {} + + +function tests.error_on_timeout() + local err_received + + copas.addthread(function() + copas.setErrorHandler(function(err, coro, skt) + err_received = err + end, true) + print "setting timeout in 0.1 seconds" + copas.timeout(0.1, function() + print "throwing an error now..." + error("oops...") + end) + print "going to sleep for 1 second" + copas.pause(1) + + if not (err_received or ""):find("oops...", 1, true) then + print("expected to find the error string 'oops...', but got: " .. tostring(err_received)) + os.exit(1) + end + end) + + copas.loop() +end + + + + + +-- test "framework" +for name, test in pairs(tests) do + print("testing: "..tostring(name)) + local status, err = pcall(test) + if not status then + error(err) + end +end + +print("[✓] all tests completed successuly") diff --git a/thirdparty/copas/tests/timer.lua b/thirdparty/copas/tests/timer.lua new file mode 100644 index 000000000..833db4a03 --- /dev/null +++ b/thirdparty/copas/tests/timer.lua @@ -0,0 +1,127 @@ +-- make sure we are pointing to the local copas first +package.path = string.format("../src/?.lua;%s", package.path) + + + +local copas = require "copas" +local gettime = require("socket").gettime +local timer = copas.timer + +local successes = 0 + +copas.loop(function() + + local count_t1 = 0 + local t1 = timer.new({ + delay = 0.5, + recurring = true, + params = "hello world", + callback = function(timer_obj, params) + -- let's ensure parameters get passed + assert(params == "hello world", "expected: hello world") + successes = successes + 1 -- 6 to come + count_t1 = count_t1 + 1 + print(params .. " " .. count_t1) + end, + }) + -- succes count = 6 + + local t2 = timer.new({ + delay = 0.2, -- we'll override this with 0.1 below + recurring = false, + params = { + start_time = gettime() + }, + initial_delay = 0.1, -- initial delay, only 0.1 + callback = function(timer_obj, params) + assert(gettime() - params.start_time < 0.11, "didn't honour initial delay, or recurred") + print("this seems to go well, and should print only once") + successes = successes + 1 -- 1 to come + end, + }) + -- succes count = 7 + + timer.new({ + delay = 3.3, --> allows T1 to run 6 times + callback = function(timer_obj, params) + t1:cancel() + local _, err = t2:cancel() + assert(err == "not armed", "expected t2 to already be stopped") + successes = successes + 1 -- 1 to come + assert(count_t1 == 6, "expected t1 to run 6 times!") + successes = successes + 1 -- 1 to come + timer_obj:cancel() -- cancel myself + end, + }) + -- succes count = 9 + + timer.new({ + delay = 0.1, + recurring = true, + callback = function(timer_obj, params) + -- re-arm myself (recurring), should not be possible + local ok, err = timer_obj:arm(1) + assert(err == "already armed", "expected myself to be already armed") + assert(ok == nil, "expected 'ok' to be nil") + print("failed to re-arm a recurring timer, so that's ok") + successes = successes + 1 -- 1 to come + assert(timer_obj:cancel()) -- cancel myself + end, + }) + -- succes count = 10 + + local touched = false + timer.new({ + delay = 0.1, + recurring = false, + callback = function(timer_obj, params) + if touched == false then + -- re-arm myself (non-recurring), should be possible + local ok, err = timer_obj:arm(3) + assert(ok == timer_obj) + assert(err == nil, "expected 'err' to be nil") + touched = gettime() + print("re-armed a non-recurring timer, so that's ok") + successes = successes + 1 -- 1 to come + else + print("a re-armed non-recurring timer executed, so that's ok") + successes = successes + 1 -- 1 to come + local t = math.abs(gettime() - touched - 3) + assert(t < 0.01, "expected a 3 second delay for the rearmed timer. Got: "..(gettime() - touched)) + successes = successes + 1 -- 1 to come + end + end, + }) + -- succes count = 13 + + local count = 0 + local params_in = {} + -- timer shouldn't be cancelled if its handler errors + timer.new({ + name = "error-test", + delay = 0.1, + recurring = true, + params = params_in, + errorhandler = function(msg, co, skt) + local errmsg = copas.gettraceback(msg, co, skt) + assert(errmsg:find("error%-test"), "the threadname wasn't found") + assert(errmsg:find("error 1!") or errmsg:find("error 2!"), "the error message wasn't found") + --print(errmsg) + successes = successes + 1 + end, + callback = function(timer_obj, params) + assert(params == params_in, "Params table wasn't passed along") + count = count + 1 + if count == 2 then + -- 2nd call, so we're done + timer_obj:cancel() + end + error("error "..count.."!") + end, + }) + -- succes count = 15 + +end) + +assert(successes == 15, "number of successes didn't match! got: "..successes) +print("test success!") diff --git a/thirdparty/copas/tests/tls-sni.lua b/thirdparty/copas/tests/tls-sni.lua new file mode 100644 index 000000000..94841b342 --- /dev/null +++ b/thirdparty/copas/tests/tls-sni.lua @@ -0,0 +1,106 @@ +-- Tests Copas with a simple Echo server +-- +-- Run the test file and the connect to the server using telnet on the used port. +-- The server should be able to echo any input, to stop the test just send the command "quit" + +local port = 20000 +local copas = require("copas") +local socket = require("socket") +local ssl = require("ssl") +local server + +if _VERSION=="Lua 5.1" and not jit then -- obsolete: only for Lua 5.1 compatibility + pcall = require("coxpcall").pcall -- luacheck: ignore +end + +local server_params = { + wrap = { + mode = "server", + protocol = "tlsv1", + key = "tests/certs/serverAkey.pem", + certificate = "tests/certs/serverA.pem", + cafile = "tests/certs/rootA.pem", + verify = {"peer", "fail_if_no_peer_cert"}, + options = {"all", "no_sslv2"}, + }, + sni = { + strict = true, -- only allow connection 'myhost.com' + names = {} + } +} +server_params.sni.names["myhost.com"] = ssl.newcontext(server_params.wrap) + +local client_params = { + wrap = { + mode = "client", + protocol = "tlsv1", + key = "tests/certs/clientAkey.pem", + certificate = "tests/certs/clientA.pem", + cafile = "tests/certs/rootA.pem", + verify = {"peer", "fail_if_no_peer_cert"}, + options = {"all", "no_sslv2"}, + }, + sni = { + names = "" -- will be added in test below + } +} + +local function echoHandler(skt) + while true do + local data, err = skt:receive() + if not data then + if err ~= "closed" then + return error("client connection error: "..tostring(err)) + else + return -- client closed the connection + end + + elseif data == "quit" then + return -- close this client connection + + elseif data == "exit" then + copas.removeserver(server) + return -- close this client connection, after stopping the server + + end + skt:send(data) + end +end + +server = assert(socket.bind("*", port)) +copas.addserver(server, copas.handler(echoHandler, server_params)) + +copas.addthread(function() + copas.pause(0.5) -- allow server socket to be ready + + ---------------------- + -- Tests start here -- + ---------------------- + + -- try with a bad SNI (non matching) + client_params.sni.names = "badhost.com" + local skt = copas.wrap(socket.tcp(), client_params) + local _, err = pcall(skt.connect, skt, "localhost", port) + if not tostring(err):match("TLS/SSL handshake failed:") then + print "expected handshake to fail" + os.exit(1) + end + + + -- try again with a proper SNI (matching) + client_params.sni.names = "myhost.com" + local skt = copas.wrap(socket.tcp(), client_params) + local success, ok = pcall(skt.connect, skt, "localhost", port) + if not (success and ok) then + print "expected connection to be completed" + os.exit(1) + end + + print "succesfully completed test" + os.exit(0) +end) + +-- no ugly errors please, comment out when debugging +copas.setErrorHandler(function() end, true) + +copas.loop() diff --git a/thirdparty/copas/tests/udptimeout.lua b/thirdparty/copas/tests/udptimeout.lua new file mode 100644 index 000000000..8707cfa09 --- /dev/null +++ b/thirdparty/copas/tests/udptimeout.lua @@ -0,0 +1,107 @@ +-- Tests Copas socket timeouts +-- +-- Run the test file, it should exit successfully without hanging. + +-- make sure we are pointing to the local copas first +package.path = string.format("../src/?.lua;%s", package.path) + + +local copas = require("copas") +local socket = require("socket") + +-- hack; no way to kill copas.loop from thread +local function error(err) + print(debug.traceback(err, 2)) + os.exit(-1) +end +local function assert(truthy, err) + if not truthy then + print(debug.traceback(err, 2)) + os.exit(-1) + end +end + +-- udp echo server for testing against, returns `ip, port` to connect to +-- send `quit\n` to cause server to disconnect client +-- stops listen server after provided number of echos +local function singleuseechoserver(die_after) + die_after = die_after or 1 + local server = socket.udp() + server:setsockname("127.0.0.1", 0) -- "localhost" fails because of IPv6 error + local ip, port = server:getsockname() + + copas.addthread(function() + local skt = copas.wrap(server) + while die_after > 0 do + local data, ip, port = skt:receivefrom() + if not data or data == "quit" then + break + end + skt:sendto(data, ip, port) + die_after = die_after - 1 + end + end) + + return ip, port +end + +local tests = {} + +function tests.receive_timeout() + local ip, port = singleuseechoserver(1) + + copas.addthread(function() + local client = socket.udp() + client = copas.wrap(client) + client:settimeout(0.01) + local status, err = client:setpeername(ip, port) + assert(status, "failed to connect: "..tostring(err)) + + client:send("foo") + local data, err = client:receive() + assert(data, "failed to recieve: "..tostring(err)) + assert(data == "foo", "recieved wrong echo: "..tostring(data)) + + local data, err = client:receive() + assert(data == nil, "somehow recieved echo without sending") + assert(err == "timeout", "failed with non-timeout error") + + client:close() + end) + + copas.loop() +end + +function tests.receivefrom_timeout() + local ip, port = singleuseechoserver(1) + + copas.addthread(function() + local client = socket.udp() + client = copas.wrap(client) + client:settimeout(0.01) + + client:sendto("foo", ip, port) + local data, err = client:receivefrom() + assert(data, "failed to recieve: "..tostring(err)) + assert(data == "foo", "recieved wrong echo: "..tostring(data)) + + local data, err = client:receivefrom() + assert(data == nil, "somehow recieved echo without sending") + assert(err == "timeout", "failed with non-timeout error") + + client:close() + end) + + copas.loop() +end + +-- test "framework" +for name, test in pairs(tests) do + print("testing: "..tostring(name)) + local status, err = pcall(test) + if not status then + error(err) + end +end + +print("[✓] all tests completed successuly") diff --git a/thirdparty/timerwheel.lua/.busted b/thirdparty/timerwheel.lua/.busted new file mode 100644 index 000000000..313c185cd --- /dev/null +++ b/thirdparty/timerwheel.lua/.busted @@ -0,0 +1,7 @@ +return { + default = { + verbose = true, + coverage = true, + output = "gtest", + }, +} diff --git a/thirdparty/timerwheel.lua/.editorconfig b/thirdparty/timerwheel.lua/.editorconfig new file mode 100644 index 000000000..7c06305e4 --- /dev/null +++ b/thirdparty/timerwheel.lua/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +[*.lua] +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab diff --git a/thirdparty/timerwheel.lua/.gitignore b/thirdparty/timerwheel.lua/.gitignore new file mode 100644 index 000000000..15ca12dec --- /dev/null +++ b/thirdparty/timerwheel.lua/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +luacov.* +*.rock diff --git a/thirdparty/timerwheel.lua/.luacheckrc b/thirdparty/timerwheel.lua/.luacheckrc new file mode 100644 index 000000000..5336df863 --- /dev/null +++ b/thirdparty/timerwheel.lua/.luacheckrc @@ -0,0 +1,32 @@ +unused_args = false +redefined = false +max_line_length = false + +globals = { + "ngx", +} + +not_globals = { + -- deprecated Lua 5.0 functions + "string.len", + "table.getn", +} + +include_files = { + "**/*.lua", + "*.rockspec", + ".busted", + ".luacheckrc", +} + +files["spec/**/*.lua"] = { + std = "+busted", +} + +exclude_files = { + -- The Github Actions Lua Environment + ".lua", + ".luarocks", + ".install", +} + diff --git a/thirdparty/timerwheel.lua/.luacov b/thirdparty/timerwheel.lua/.luacov new file mode 100644 index 000000000..46c851378 --- /dev/null +++ b/thirdparty/timerwheel.lua/.luacov @@ -0,0 +1,6 @@ +modules = { + ["timerwheel"] = "src/timerwheel.lua", + ["timerwheel.*"] = "src" +} +runreport = true +deletestats = false -- file still needed to push to coveralls diff --git a/thirdparty/timerwheel.lua/LICENSE b/thirdparty/timerwheel.lua/LICENSE new file mode 100644 index 000000000..ffff7be58 --- /dev/null +++ b/thirdparty/timerwheel.lua/LICENSE @@ -0,0 +1,22 @@ +MIT License Terms +================= + +Copyright (c) 2018-2022 Thijs Schreijer. + +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. diff --git a/thirdparty/timerwheel.lua/README.md b/thirdparty/timerwheel.lua/README.md new file mode 100644 index 000000000..308857dfd --- /dev/null +++ b/thirdparty/timerwheel.lua/README.md @@ -0,0 +1,90 @@ +[![Unix build](https://img.shields.io/github/actions/workflow/status/Tieske/timerwheel.lua/unix_build.yml?branch=master&label=Unix%20build&logo=linux)](https://github.com/Tieske/timerwheel.lua/actions/workflows/unix_build.yml) +[![Coveralls code coverage](https://img.shields.io/coveralls/github/Tieske/timerwheel.lua?logo=coveralls)](https://coveralls.io/github/Tieske/timerwheel.lua) +[![Lint](https://github.com/Tieske/timerwheel.lua/workflows/Lint/badge.svg)](https://github.com/Tieske/timerwheel.lua/actions/workflows/lint.yml) + + +timerwheel.lua +============== + +Efficient timer for timeout related timers: fast insertion, deletion, and +execution (all as O(1) implemented), but with lesser precision. + +This module will not provide the timer/runloop itself. Use your own runloop +and call `wheel:step` to check and execute timers. + + +Installation +============ + +Install through LuaRocks (`luarocks install timerwheel`) or from source, see the +[github repo](https://github.com/Tieske/timerwheel.lua). + +Documentation +============= + +The docs are [available online](https://tieske.github.io/timerwheel.lua/), or can +be generated using [Ldoc](http://stevedonovan.github.io/ldoc/). Just run +`"ldoc ."` from the repo. + + +Tests +===== + +Tests are in the `spec` folder and can be executed using the +[busted test framework](https://lunarmodules.github.io/busted/). Just run +`"busted"` from the repo. + +Besides that `luacheck` is configured for linting, just run `"luacheck ."` from +the repo. And if LuaCov is installed, the Busted test-run will result in a +coverage report (file `"luacov.report.out"`). + + +Copyright and License +===================== + +See [LICENSE](https://github.com/Tieske/timerwheel.lua/blob/master/LICENSE). + +History +======= + +Versioning is strictly based on [Semantic Versioning](https://semver.org/). + +#### Releasing new versions + +- create a release branch +- update the changelog below +- update copyright-years in `./LICENSE` +- create a new rockspec and update the version inside the new rockspec:
+ `cp timerwheel-scm-1.rockspec ./rockspecs/timerwheel-X.Y.Z-1.rockspec` +- render the docs: run `ldoc .` +- commit the changes as `release X.Y.Z` +- push the commit, and create a release PR +- after merging tag the release commit with `X.Y.Z` +- upload to LuaRocks:
+ `luarocks upload ./rockspecs/timerwheel-X.Y.Z-1.rockspec --api-key=ABCDEFGH` +- test the newly created rock:
+ `luarocks install timerwheel` + +## 1.0.2 released 03-Nov-2022 + +- Fix: memory leak, occasionally ID's were not removed. Causing a memory leak in + long running processes + +## 1.0.1 released 04-Oct-2022 + +- Fix: if a slot was modified (by cancelling) a hole might appear in the table + causing an xpcall with `nil` instead of the callback function + +## 1.0.0 released 22-Aug-2022 + +- Bump to 1.0 since API is stable +- Fix: added a newline when writing errors to `stderr`, since `io.stderr:write()` + does not automatically do this like `print()` does. + +## 0.2.0 released 11-Feb-2020 + +- Added `count` method to retrieve the current number of active timers + +## 0.1.0 released 01-Feb-2020 + +- Initial released version diff --git a/thirdparty/timerwheel.lua/config.ld b/thirdparty/timerwheel.lua/config.ld new file mode 100644 index 000000000..fe90b4a33 --- /dev/null +++ b/thirdparty/timerwheel.lua/config.ld @@ -0,0 +1,10 @@ +project='timerwheel.lua' +title='timerwheel' +description='Timers based on a timerwheel' +format='markdown' +file='./src/' +dir='docs' +readme='readme.md' +sort=true +sort_modules=true +style='./docs/' diff --git a/thirdparty/timerwheel.lua/docs/index.html b/thirdparty/timerwheel.lua/docs/index.html new file mode 100644 index 000000000..93edacc09 --- /dev/null +++ b/thirdparty/timerwheel.lua/docs/index.html @@ -0,0 +1,313 @@ + + + + + timerwheel + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ +

Module timerwheel

+

Timer wheel implementation

+ +

Efficient timer for timeout related timers: fast insertion, deletion, and + execution (all as O(1) implemented), but with lesser precision.

+

This module will not provide the timer/runloop itself. Use your own runloop + and call wheel:step to check and execute timers.

+ +

Implementation: + Consider a stack of rings, a timer beyond the current ring size is in the + next ring (or beyond). Precision is based on a slot with a specific size.

+ +

The code explicitly avoids using pairs, ipairs and next to ensure JIT + compilation when using LuaJIT

+ + +

Functions

+ + + + + + + + + + + + + + + + + + + + + + + + + +
new (opts)Creates a new timer wheel.
wheel:cancel (id)Cancels a timer.
wheel:count ()Gets the number of timers.
wheel:peek ([max_ahead])Looks up the next expiring timer.
wheel:set (expire_in, cb, arg)Sets a timer.
wheel:step ()Checks and executes timers.
+ +
+
+ + +

Functions

+ +
+
+ + new (opts) +
+
+ Creates a new timer wheel. + + +

Parameters:

+
    +
  • opts the options table +
      +
    • precision + number + the precision of the timer wheel in seconds (slot size), + (default 0.050) +
    • +
    • ringsize + int + number of slots in each ring, defaults to 72000 (1 + hour span, with precision == 0.050) + (optional) +
    • +
    • now + function + a function returning the curent time in seconds. Defaults + to ngx.now or luasocket.gettime if available. + (optional) +
    • +
    • err_handler + function + a function to use as error handler in an xpcall when + executing the callback. The default will write the stacktrace to stderr. + (optional) +
    • +
    +
+ +

Returns:

+
    + + wheel + the timerwheel object +
+ + + + +
+
+ + wheel:cancel (id) +
+
+ Cancels a timer. + + +

Parameters:

+
    +
  • id + int + the timer id to cancel +
  • +
+ +

Returns:

+
    + + boolean + true if cancelled, false if not found +
+ + + + +
+
+ + wheel:count () +
+
+ Gets the number of timers. + + + +

Returns:

+
    + + int + number of timers +
+ + + + +
+
+ + wheel:peek ([max_ahead]) +
+
+ Looks up the next expiring timer. + Note: traverses the wheel, O(n) operation! + + +

Parameters:

+
    +
  • max_ahead + number + maximum time (in seconds) + to look ahead + (optional) +
  • +
+ +

Returns:

+
    + + number + number of seconds until next timer expires (can be negative), or + 'nil' if there is no timer from now to max_ahead +
+ + + +

Usage:

+
    +
    local t = wheel:peek(10)
    +if t then
    +  print("next timer expires in ", t," seconds")
    +else
    +  print("no timer scheduled for the next 10 seconds")
    +end
    +
+ +
+
+ + wheel:set (expire_in, cb, arg) +
+
+ Sets a timer. + + +

Parameters:

+
    +
  • expire_in + number + in how many seconds should the timer expire +
  • +
  • cb + function + callback function to execute upon expiring (NOTE: the + callback will run within an xpcall) +
  • +
  • arg + parameter to be passed to cb when executing +
  • +
+ +

Returns:

+
    + + int + the id of the newly set timer +
+ + + +

Usage:

+
    +
    local cb = function(arg)
    +  print("timer executed with: ", arg)  --> "timer executed with: hello world"
    +end
    +local id = wheel:set(5, cb, "hello world")
    +
    +-- do stuff here, while regularly calling wheel:step()
    +
    +wheel:cancel(id)  -- cancel the timer again
    +
+ +
+
+ + wheel:step () +
+
+ Checks and executes timers. + Call this function (at least) every precision seconds. + + + +

Returns:

+
    + + true +
+ + + + +
+
+ + +
+
+
+generated by LDoc 1.4.6 +Last updated 2022-11-03 17:31:27 +
+
+ + diff --git a/thirdparty/timerwheel.lua/docs/ldoc.css b/thirdparty/timerwheel.lua/docs/ldoc.css new file mode 100644 index 000000000..f6e3d99f9 --- /dev/null +++ b/thirdparty/timerwheel.lua/docs/ldoc.css @@ -0,0 +1,291 @@ +body { + color: #47555c; + font-size: 16px; + font-family: "Open Sans", sans-serif; + margin: 0; + background: #eff4ff; +} + +a:link { color: #008fee; } +a:visited { color: #008fee; } +a:hover { color: #22a7ff; } + +h1 { font-size:26px; font-weight: normal; } +h2 { font-size:22px; font-weight: normal; } +h3 { font-size:18px; font-weight: normal; } +h4 { font-size:16px; font-weight: bold; } + +hr { + height: 1px; + background: #c1cce4; + border: 0px; + margin: 15px 0; +} + +code, tt { + font-family: monospace; +} +span.parameter { + font-family: monospace; + font-weight: bold; + color: rgb(99, 115, 131); +} +span.parameter:after { + content:":"; +} +span.types:before { + content:"("; +} +span.types:after { + content:")"; +} +.type { + font-weight: bold; font-style:italic +} + +p.name { + font-family: "Andale Mono", monospace; +} + +#navigation { + float: left; + background-color: white; + border-right: 1px solid #d3dbec; + border-bottom: 1px solid #d3dbec; + + width: 14em; + vertical-align: top; + overflow: visible; +} + +#navigation br { + display: none; +} + +#navigation h1 { + background-color: white; + border-bottom: 1px solid #d3dbec; + padding: 15px; + margin-top: 0px; + margin-bottom: 0px; +} + +#navigation h2 { + font-size: 18px; + background-color: white; + border-bottom: 1px solid #d3dbec; + padding-left: 15px; + padding-right: 15px; + padding-top: 10px; + padding-bottom: 10px; + margin-top: 30px; + margin-bottom: 0px; +} + +#content h1 { + background-color: #2c3e67; + color: white; + padding: 15px; + margin: 0px; +} + +#content h2 { + background-color: #6c7ea7; + color: white; + padding: 15px; + padding-top: 15px; + padding-bottom: 15px; + margin-top: 0px; +} + +#content h2 a { + background-color: #6c7ea7; + color: white; + text-decoration: none; +} + +#content h2 a:hover { + text-decoration: underline; +} + +#content h3 { + font-style: italic; + padding-top: 15px; + padding-bottom: 4px; + margin-right: 15px; + margin-left: 15px; + margin-bottom: 5px; + border-bottom: solid 1px #bcd; +} + +#content h4 { + margin-right: 15px; + margin-left: 15px; + border-bottom: solid 1px #bcd; +} + +#content pre { + margin: 15px; +} + +pre { + background-color: rgb(50, 55, 68); + color: white; + border-radius: 3px; + /* border: 1px solid #C0C0C0; /* silver */ + padding: 15px; + overflow: auto; + font-family: "Andale Mono", monospace; +} + +#content ul pre.example { + margin-left: 0px; +} + +table.index { +/* border: 1px #00007f; */ +} +table.index td { text-align: left; vertical-align: top; } + +#navigation ul +{ + font-size:1em; + list-style-type: none; + margin: 1px 1px 10px 1px; + padding-left: 20px; +} + +#navigation li { + text-indent: -1em; + display: block; + margin: 3px 0px 0px 22px; +} + +#navigation li li a { + margin: 0px 3px 0px -1em; +} + +#content { + margin-left: 14em; +} + +#content p { + padding-left: 15px; + padding-right: 15px; +} + +#content table { + padding-left: 15px; + padding-right: 15px; + background-color: white; +} + +#content p, #content table, #content ol, #content ul, #content dl { + max-width: 900px; +} + +#about { + padding: 15px; + padding-left: 16em; + background-color: white; + border-top: 1px solid #d3dbec; + border-bottom: 1px solid #d3dbec; +} + +table.module_list, table.function_list { + border-width: 1px; + border-style: solid; + border-color: #cccccc; + border-collapse: collapse; + margin: 15px; +} +table.module_list td, table.function_list td { + border-width: 1px; + padding-left: 10px; + padding-right: 10px; + padding-top: 5px; + padding-bottom: 5px; + border: solid 1px rgb(193, 204, 228); +} +table.module_list td.name, table.function_list td.name { + background-color: white; min-width: 200px; border-right-width: 0px; +} +table.module_list td.summary, table.function_list td.summary { + background-color: white; width: 100%; border-left-width: 0px; +} + +dl.function { + margin-right: 15px; + margin-left: 15px; + border-bottom: solid 1px rgb(193, 204, 228); + border-left: solid 1px rgb(193, 204, 228); + border-right: solid 1px rgb(193, 204, 228); + background-color: white; +} + +dl.function dt { + color: rgb(99, 123, 188); + font-family: monospace; + border-top: solid 1px rgb(193, 204, 228); + padding: 15px; +} + +dl.function dd { + margin-left: 15px; + margin-right: 15px; + margin-top: 5px; + margin-bottom: 15px; +} + +#content dl.function dd h3 { + margin-top: 0px; + margin-left: 0px; + padding-left: 0px; + font-size: 16px; + color: rgb(128, 128, 128); + border-bottom: solid 1px #def; +} + +#content dl.function dd ul, #content dl.function dd ol { + padding: 0px; + padding-left: 15px; + list-style-type: none; +} + +ul.nowrap { + overflow:auto; + white-space:nowrap; +} + +.section-description { + padding-left: 15px; + padding-right: 15px; +} + +/* stop sublists from having initial vertical space */ +ul ul { margin-top: 0px; } +ol ul { margin-top: 0px; } +ol ol { margin-top: 0px; } +ul ol { margin-top: 0px; } + +/* make the target distinct; helps when we're navigating to a function */ +a:target + * { + background-color: #FF9; +} + + +/* styles for prettification of source */ +pre .comment { color: #bbccaa; } +pre .constant { color: #a8660d; } +pre .escape { color: #844631; } +pre .keyword { color: #ffc090; font-weight: bold; } +pre .library { color: #0e7c6b; } +pre .marker { color: #512b1e; background: #fedc56; font-weight: bold; } +pre .string { color: #8080ff; } +pre .number { color: #f8660d; } +pre .operator { color: #2239a8; font-weight: bold; } +pre .preprocessor, pre .prepro { color: #a33243; } +pre .global { color: #c040c0; } +pre .user-keyword { color: #800080; } +pre .prompt { color: #558817; } +pre .url { color: #272fc2; text-decoration: underline; } diff --git a/thirdparty/timerwheel.lua/docs/topics/readme.md.html b/thirdparty/timerwheel.lua/docs/topics/readme.md.html new file mode 100644 index 000000000..092ca6440 --- /dev/null +++ b/thirdparty/timerwheel.lua/docs/topics/readme.md.html @@ -0,0 +1,165 @@ + + + + + timerwheel + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ + +

Unix build +Coveralls code coverage +Lint

+ + +

timerwheel.lua

+ +

Efficient timer for timeout related timers: fast insertion, deletion, and +execution (all as O(1) implemented), but with lesser precision.

+ +

This module will not provide the timer/runloop itself. Use your own runloop +and call wheel:step to check and execute timers.

+ + +

Installation

+ +

Install through LuaRocks (luarocks install timerwheel) or from source, see the +github repo.

+ +

Documentation

+ +

The docs are available online, or can +be generated using Ldoc. Just run +"ldoc ." from the repo.

+ + +

Tests

+ +

Tests are in the spec folder and can be executed using the +busted test framework. Just run +"busted" from the repo.

+ +

Besides that luacheck is configured for linting, just run "luacheck ." from +the repo. And if LuaCov is installed, the Busted test-run will result in a +coverage report (file "luacov.report.out").

+ + +

Copyright and License

+ +

See LICENSE.

+ +

History

+ +

Versioning is strictly based on Semantic Versioning.

+ +

Releasing new versions

+ +
    +
  • create a release branch
  • +
  • update the changelog below
  • +
  • update copyright-years in ./LICENSE
  • +
  • create a new rockspec and update the version inside the new rockspec:
    + cp timerwheel-scm-1.rockspec ./rockspecs/timerwheel-X.Y.Z-1.rockspec
  • +
  • render the docs: run ldoc .
  • +
  • commit the changes as release X.Y.Z
  • +
  • push the commit, and create a release PR
  • +
  • after merging tag the release commit with X.Y.Z
  • +
  • upload to LuaRocks:
    + luarocks upload ./rockspecs/timerwheel-X.Y.Z-1.rockspec --api-key=ABCDEFGH
  • +
  • test the newly created rock:
    + luarocks install timerwheel
  • +
+ +

+

1.0.2 released 03-Nov-2022

+ +
    +
  • Fix: memory leak, occasionally ID's were not removed. Causing a memory leak in + long running processes
  • +
+ +

+

1.0.1 released 04-Oct-2022

+ +
    +
  • Fix: if a slot was modified (by cancelling) a hole might appear in the table + causing an xpcall with nil instead of the callback function
  • +
+ +

+

1.0.0 released 22-Aug-2022

+ +
    +
  • Bump to 1.0 since API is stable
  • +
  • Fix: added a newline when writing errors to stderr, since io.stderr:write() + does not automatically do this like print() does.
  • +
+ +

+

0.2.0 released 11-Feb-2020

+ +
    +
  • Added count method to retrieve the current number of active timers
  • +
+ +

+

0.1.0 released 01-Feb-2020

+ +
    +
  • Initial released version
  • +
+ + +
+
+
+generated by LDoc 1.4.6 +Last updated 2022-11-03 17:31:27 +
+
+ + diff --git a/thirdparty/timerwheel.lua/rockspecs/timerwheel-0.1.0-1.rockspec b/thirdparty/timerwheel.lua/rockspecs/timerwheel-0.1.0-1.rockspec new file mode 100644 index 000000000..1f1e4b5c5 --- /dev/null +++ b/thirdparty/timerwheel.lua/rockspecs/timerwheel-0.1.0-1.rockspec @@ -0,0 +1,27 @@ +package = "timerwheel" +version = "0.1.0-1" +source = { + url = "git://github.com/Tieske/timerwheel.lua", + tag = "0.1.0", + --branch = "master", +} +description = { + summary = "Timers based on a timerwheel", + detailed = [[ + Creating and deleting is very fast. Typically suited for + setting timeouts, which usually do not execute. + ]], + homepage = "https://github.com/Tieske/timerwheel.lua", + license = "MIT" +} +dependencies = { + "lua >= 5.1, < 5.4", + "coxpcall", +} +build = { + type = "builtin", + modules = { + ["timerwheel.init"] = "src/timerwheel.lua", + }, + copy_directories = {"docs"} +} diff --git a/thirdparty/timerwheel.lua/rockspecs/timerwheel-0.1.0-2.rockspec b/thirdparty/timerwheel.lua/rockspecs/timerwheel-0.1.0-2.rockspec new file mode 100644 index 000000000..e836f3de6 --- /dev/null +++ b/thirdparty/timerwheel.lua/rockspecs/timerwheel-0.1.0-2.rockspec @@ -0,0 +1,28 @@ +package = "timerwheel" +version = "0.1.0-2" +source = { + url = "https://github.com/Tieske/timerwheel.lua/archive/0.1.0.tar.gz", + dir = "timerwheel.lua-0.1.0", + --tag = "0.1.0", + --branch = "master", +} +description = { + summary = "Timers based on a timerwheel", + detailed = [[ + Creating and deleting is very fast. Typically suited for + setting timeouts, which usually do not execute. + ]], + homepage = "https://github.com/Tieske/timerwheel.lua", + license = "MIT" +} +dependencies = { + "lua >= 5.1, < 5.4", + "coxpcall", +} +build = { + type = "builtin", + modules = { + ["timerwheel.init"] = "src/timerwheel.lua", + }, + copy_directories = {"docs"} +} diff --git a/thirdparty/timerwheel.lua/rockspecs/timerwheel-0.2.0-1.rockspec b/thirdparty/timerwheel.lua/rockspecs/timerwheel-0.2.0-1.rockspec new file mode 100644 index 000000000..27d25d4ea --- /dev/null +++ b/thirdparty/timerwheel.lua/rockspecs/timerwheel-0.2.0-1.rockspec @@ -0,0 +1,28 @@ +package = "timerwheel" +version = "0.2.0-1" +source = { + url = "https://github.com/Tieske/timerwheel.lua/archive/0.2.0.tar.gz", + dir = "timerwheel.lua-0.2.0", + --tag = "0.1.0", + --branch = "master", +} +description = { + summary = "Timers based on a timerwheel", + detailed = [[ + Creating and deleting is very fast. Typically suited for + setting timeouts, which usually do not execute. + ]], + homepage = "https://github.com/Tieske/timerwheel.lua", + license = "MIT" +} +dependencies = { + "lua >= 5.1, < 5.4", + "coxpcall", +} +build = { + type = "builtin", + modules = { + ["timerwheel.init"] = "src/timerwheel.lua", + }, + copy_directories = {"docs"} +} diff --git a/thirdparty/timerwheel.lua/rockspecs/timerwheel-0.2.0-2.rockspec b/thirdparty/timerwheel.lua/rockspecs/timerwheel-0.2.0-2.rockspec new file mode 100644 index 000000000..59e34c682 --- /dev/null +++ b/thirdparty/timerwheel.lua/rockspecs/timerwheel-0.2.0-2.rockspec @@ -0,0 +1,33 @@ +local package_name = "timerwheel" +local package_version = "0.2.0" +local rockspec_revision = "2" +local github_account_name = "Tieske" +local github_repo_name = package_name .. ".lua" + + +package = package_name +version = package_version.."-"..rockspec_revision +source = { + url = "git://github.com/"..github_account_name.."/"..github_repo_name..".git", + branch = (package_version == "scm") and "master" or nil, + tag = (package_version ~= "scm") and package_version or nil, +} +description = { + summary = "Timers based on a timerwheel", + detailed = [[ + Creating and deleting is very fast. Typically suited for + setting timeouts, which usually do not execute. + ]], + homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, + license = "MIT" +} +dependencies = { + "lua >= 5.1, < 5.5", + "coxpcall", +} +build = { + type = "builtin", + modules = { + ["timerwheel.init"] = "src/timerwheel.lua", + }, +} diff --git a/thirdparty/timerwheel.lua/rockspecs/timerwheel-1.0.0-1.rockspec b/thirdparty/timerwheel.lua/rockspecs/timerwheel-1.0.0-1.rockspec new file mode 100644 index 000000000..d1966bde5 --- /dev/null +++ b/thirdparty/timerwheel.lua/rockspecs/timerwheel-1.0.0-1.rockspec @@ -0,0 +1,36 @@ +local package_name = "timerwheel" +local package_version = "1.0.0" +local rockspec_revision = "1" +local github_account_name = "Tieske" +local github_repo_name = package_name .. ".lua" + + +package = package_name +version = package_version.."-"..rockspec_revision +source = { + url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", + branch = (package_version == "scm") and "master" or nil, + tag = (package_version ~= "scm") and package_version or nil, +} +description = { + summary = "Timers based on a timerwheel", + detailed = [[ + Creating and deleting is very fast. Typically suited for + setting timeouts, which usually do not execute. + ]], + homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, + license = "MIT" +} +dependencies = { + "lua >= 5.1, < 5.5", + "coxpcall", +} +build = { + type = "builtin", + modules = { + ["timerwheel.init"] = "src/timerwheel/init.lua", + }, + copy_directories = { + "docs", + }, +} diff --git a/thirdparty/timerwheel.lua/rockspecs/timerwheel-1.0.1-1.rockspec b/thirdparty/timerwheel.lua/rockspecs/timerwheel-1.0.1-1.rockspec new file mode 100644 index 000000000..ccd88b62f --- /dev/null +++ b/thirdparty/timerwheel.lua/rockspecs/timerwheel-1.0.1-1.rockspec @@ -0,0 +1,36 @@ +local package_name = "timerwheel" +local package_version = "1.0.1" +local rockspec_revision = "1" +local github_account_name = "Tieske" +local github_repo_name = package_name .. ".lua" + + +package = package_name +version = package_version.."-"..rockspec_revision +source = { + url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", + branch = (package_version == "scm") and "master" or nil, + tag = (package_version ~= "scm") and package_version or nil, +} +description = { + summary = "Timers based on a timerwheel", + detailed = [[ + Creating and deleting is very fast. Typically suited for + setting timeouts, which usually do not execute. + ]], + homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, + license = "MIT" +} +dependencies = { + "lua >= 5.1, < 5.5", + "coxpcall", +} +build = { + type = "builtin", + modules = { + ["timerwheel.init"] = "src/timerwheel/init.lua", + }, + copy_directories = { + "docs", + }, +} diff --git a/thirdparty/timerwheel.lua/rockspecs/timerwheel-1.0.2-1.rockspec b/thirdparty/timerwheel.lua/rockspecs/timerwheel-1.0.2-1.rockspec new file mode 100644 index 000000000..b00704e22 --- /dev/null +++ b/thirdparty/timerwheel.lua/rockspecs/timerwheel-1.0.2-1.rockspec @@ -0,0 +1,36 @@ +local package_name = "timerwheel" +local package_version = "1.0.2" +local rockspec_revision = "1" +local github_account_name = "Tieske" +local github_repo_name = package_name .. ".lua" + + +package = package_name +version = package_version.."-"..rockspec_revision +source = { + url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", + branch = (package_version == "scm") and "master" or nil, + tag = (package_version ~= "scm") and package_version or nil, +} +description = { + summary = "Timers based on a timerwheel", + detailed = [[ + Creating and deleting is very fast. Typically suited for + setting timeouts, which usually do not execute. + ]], + homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, + license = "MIT" +} +dependencies = { + "lua >= 5.1, < 5.5", + "coxpcall", +} +build = { + type = "builtin", + modules = { + ["timerwheel.init"] = "src/timerwheel/init.lua", + }, + copy_directories = { + "docs", + }, +} diff --git a/thirdparty/timerwheel.lua/spec/timerwheel_spec.lua b/thirdparty/timerwheel.lua/spec/timerwheel_spec.lua new file mode 100644 index 000000000..b51250c4a --- /dev/null +++ b/thirdparty/timerwheel.lua/spec/timerwheel_spec.lua @@ -0,0 +1,460 @@ +_G._TEST = true -- instruct for extra exports for test purposes +local tw = require "timerwheel" + +-- make sure luasocket is installed when tesing +assert( + (pcall(require, "socket")), + "LuaSocket must be installed to run the test suite" +) + +describe("Timerwheel", function() + + + local set_time, now + do + local _time + set_time = function(t) + assert(type(t) == "number", "expected number") + assert(t > _time, "new time should be greater than old time") + _time = t + end + now = function() + return _time + end + before_each(function() + _time = 0 + end) + end + + describe("new()", function() + + it("succeeds without options", function() + local wheel = tw.new() + assert.is_table(wheel) + assert.is_function(wheel.step) + end) + + + it("fails with bad options", function() + local function factory(opts) + return function() + tw.new(opts) + end + end + assert.has_error(function() tw:new() end, + "new should not be called with colon ':' notation") + assert.has_error(factory {precision = -0.3}, + "expected 'precision' to be number > 0") + assert.has_error(factory {precision = 0}, + "expected 'precision' to be number > 0") + assert.has_error(factory {ringsize = -1}, + "expected 'ringsize' to be an integer number > 0") + assert.has_error(factory {ringsize = 0}, + "expected 'ringsize' to be an integer number > 0") + assert.has_error(factory {ringsize = 1.5}, + "expected 'ringsize' to be an integer number > 0") + assert.has_error(factory {now = "hello"}, + "expected 'now' to be a function, got: string") + assert.has_error(factory {err_handler = "hello"}, + "expected 'err_handler' to be a function, got: string") + end) + + + it("succeeds with proper options", function() + local wheel = tw.new { + precision = 0.5, + ringsize = 10, + now = function() end, + err_handler = function() end, + } + assert.is_table(wheel) + assert.is_function(wheel.step) + end) + + end) + + + + describe("set() and step()", function() + + local wheel, precision, ringsize = nil, 1, 10 + + before_each(function() + wheel = assert(tw.new({ + now = now, + precision = precision, + ringsize = ringsize, + })) + end) + + + it("sets a timer", function() + local count = 0 + local id = wheel:set(0.5, function() count = count + 1 end) + assert.is.equal(-1, id) + assert.is.equal(1, wheel:count()) + set_time(1) + wheel:step() + assert.is.equal(1, count) + assert.is.equal(0, wheel:count()) + end) + + + it("sets a timer and passes the argument", function() + local count = 0 + local arg = {} + local id = wheel:set(0.5, function(cb_arg) + assert.are.equal(arg, cb_arg) + count = count + 1 + end, arg) + assert.is.equal(-1, id) + set_time(1) + wheel:step() + assert.is.equal(1, count) + end) + + + it("doesn't fail on a callback error", function() + wheel = assert(tw.new({ + now = now, + precision = precision, + ringsize = ringsize, + err_handler = function() end, -- drop the error + })) + + local id = wheel:set(0.5, function() error() end) + assert.is.equal(-1, id) + set_time(1) + assert.has.no.error(function() wheel:step() end) + end) + + + it("sets 10 timers", function() + local count = 0 + local cb = function() count = count + 1 end + for i = 0, 9 do + local id = wheel:set(i + 0.5, cb) + assert.is.equal(-1-i, id) + end + for i = 1, 10 do + set_time(i) + wheel:step() + assert.is.equal(i, count) + end + end) + + + it("sets 10 timers and reuses the tables", function() + local count = 0 + local cb = function() count = count + 1 end + for i = 0, 9 do + local id = wheel:set(i + 0.5, cb) + assert.is.equal(-1-i, id) + end + for i = 1, 10 do + set_time(i) + wheel:step() + assert.is.equal(i, count) + end + -- do the same again + for i = 0, 9 do + local id = wheel:set(i + 0.5, cb) + assert.is.equal(-1-i-10, id) + end + for i = 11, 20 do + set_time(i) + wheel:step() + assert.is.equal(i, count) + end + -- check reused tables + assert.equal(10, #wheel._tables) + for _, t in ipairs(wheel._tables) do + assert.same({ arg = {}, ids = {}, n = 0 }, t) + end + end) + + + it("sets a timer in the past", function() + local count = 0 + local id = wheel:set(-0.5, function() count = count + 1 end) + assert.is.equal(-1, id) + + set_time(1) + wheel:step() + assert.is.equal(1, count) + end) + + + it("sets a timer on the old edge", function() + local count = 0 + local id = wheel:set(0, function() count = count + 1 end) + assert.is.equal(-1, id) + + set_time(1) + wheel:step() + assert.is.equal(1, count) + end) + + + it("sets a timer on the new edge", function() + local count = 0 + local id = wheel:set(1, function() count = count + 1 end) + assert.is.equal(-1, id) + + set_time(1) + wheel:step() + assert.is.equal(0, count) + + set_time(2) + wheel:step() + assert.is.equal(1, count) + end) + + + it("sets timers over the ring edge", function() + local count = 0 + local cb = function() count = count + 1 end + for i = 0, 10 * ringsize - 1 do + local id = wheel:set(i + 0.5, cb) + assert.is.equal(-1-i, id) + end + for i = 1, 10 * ringsize do + set_time(i) + wheel:step() + assert.is.equal(i, count) + end + end) + + + it("sets timers, skipping over empty rings", function() + local count = 0 + local cb = function() count = count + 1 end + local total = 10 * ringsize + for i = 0, total - 1 do + if i >= total/2 then -- skip first half == multiple rings + assert(wheel:set(i + 0.5, cb)) + end + end + for i = 1, total do + set_time(i) + wheel:step() + end + assert.is.equal(total/2, count) + end) + + + it("doesn't execute before edge, but on/after edge", function() + local count = 0 + local id = wheel:set(0.5, function() count = count + 1 end) + assert.is.equal(-1, id) + + set_time(0.99999) + wheel:step() + assert.is.equal(0, count) + + set_time(1) + wheel:step() + assert.is.equal(1, count) + end) + + + it("callback and args gets GC'ed after executing", function() + local count = 0 + local cb = function() count = count + 1 end + local arg = {} + local check_table = setmetatable({ + value1 = cb, + value2 = arg, + }, { + __mode = "v" + }) + local id = wheel:set(0.5, cb, arg) + assert.is.equal(-1, id) + + cb = nil -- luacheck: ignore + arg = nil -- luacheck: ignore + collectgarbage() + collectgarbage() + assert.is.Not.Nil(next(check_table)) + + set_time(1) + wheel:step() + assert.is.equal(1, count) + + collectgarbage() + collectgarbage() + assert.is.Nil(next(check_table)) + end) + + + it("calls the error handler on an error", function() + local err_count = 0 + wheel = assert(tw.new({ + now = now, + precision = precision, + ringsize = ringsize, + err_handler = function(err) err_count = err_count + 1 end, + })) + local id = wheel:set(0.5, function() error() end) + assert.is.equal(-1, id) + set_time(1) + wheel:step() + assert.is.equal(1, err_count) + end) + + end) + + + + describe("peek()", function() + + local wheel, precision, ringsize = nil, 1, 10 + + before_each(function() + wheel = assert(tw.new({ + now = now, + precision = precision, + ringsize = ringsize, + })) + end) + + + it("returns time to execution", function() + assert.is.Nil(wheel:peek()) -- on empty wheel + + local count = 0 + wheel:set(0.5, function() count = count + 1 end) + assert.is.near(1, wheel:peek(), 0.00001) + assert.is.Nil(wheel:peek(0.999)) + + set_time(0.9) + assert.is.near(0.1, wheel:peek(), 0.00001) + assert.is.Nil(wheel:peek(0.0999)) + end) + + + it("returns time to execution from last slot", function() + local count = 0 + wheel:set(9.5, function() count = count + 1 end) + assert.is.near(10, wheel:peek(), 0.00001) + assert.is.Nil(wheel:peek(9.999)) + + set_time(9.9) + assert.is.near(0.1, wheel:peek(), 0.00001) + assert.is.Nil(wheel:peek(0.0999)) + end) + + + it("returns time to execution a few (empty) rings ahead", function() + local count = 0 + wheel:set(109.5, function() count = count + 1 end) + assert.is.near(110, wheel:peek(), 0.00001) + assert.is.Nil(wheel:peek(109.999)) + + set_time(109.9) + assert.is.near(0.1, wheel:peek(), 0.00001) + assert.is.Nil(wheel:peek(0.0999)) + end) + + + it("returns time to execution in the past", function() + local count = 0 + wheel:set(0.5, function() count = count + 1 end) + set_time(100) + assert.is.near(-99, wheel:peek(), 0.00001) + end) + + end) + + + + describe("cancel()", function() + + local wheel, precision, ringsize = nil, 1, 10 + + before_each(function() + wheel = assert(tw.new({ + now = now, + precision = precision, + ringsize = ringsize, + })) + end) + + + it("removes a timer", function() + local count = 0 + local id = wheel:set(0.5, function() count = count + 1 end) + assert.is.equal(1, wheel:count()) + assert.is.True(wheel:cancel(id)) + + set_time(1) + wheel:step() + assert.is.equal(0, count) + assert.is.equal(0, wheel:count()) + + -- check reusable tables + assert.equal(1, #wheel._tables) + assert.same({ arg = {}, ids = {}, n = 0 }, wheel._tables[1]) + end) + + + it("removes a non-existing timer", function() + local id = "something bad" + assert.is.False(wheel:cancel(id)) + end) + + + it("removes timers, leaving holes in the slot", function() + local called = {} + local id1 = wheel:set(0.5, function(arg) called[#called+1] = arg end, "id1") + local _ = wheel:set(0.5, function(arg) called[#called+1] = arg end, "id2") + local id3 = wheel:set(0.5, function(arg) called[#called+1] = arg end, "id3") + local _ = wheel:set(0.5, function(arg) called[#called+1] = arg end, "id4") + local id5 = wheel:set(0.5, function(arg) called[#called+1] = arg end, "id5") + assert.is.equal(5, wheel:count()) + assert.is.True(wheel:cancel(id1)) + assert.is.True(wheel:cancel(id3)) + assert.is.True(wheel:cancel(id5)) + + set_time(1) + wheel:step() + + table.sort(called) + assert.is.equal(2, #called) + assert.is.same({ "id2", "id4" }, called) + + -- check reused tables + assert.equal(1, #wheel._tables) + assert.same({ arg = {}, ids = {}, n = 0 }, wheel._tables[1]) + end) + + + it("callback and args gets GC'ed after cancelling", function() + local count = 0 + local cb = function() count = count + 1 end + local arg = {} + local check_table = setmetatable({ + value1 = cb, + value2 = arg, + }, { + __mode = "v" + }) + local id = wheel:set(0.5, cb, arg) + + cb = nil -- luacheck: ignore + arg = nil -- luacheck: ignore + collectgarbage() + collectgarbage() + assert.is.Not.Nil(next(check_table)) + + + wheel:cancel(id) + collectgarbage() + collectgarbage() + + assert.is.Nil(next(check_table)) + end) + + end) + +end) diff --git a/thirdparty/timerwheel.lua/src/timerwheel/timerwheel.lua b/thirdparty/timerwheel.lua/src/timerwheel/timerwheel.lua new file mode 100644 index 000000000..d97f2bed5 --- /dev/null +++ b/thirdparty/timerwheel.lua/src/timerwheel/timerwheel.lua @@ -0,0 +1,307 @@ +--- Timer wheel implementation +-- +-- Efficient timer for timeout related timers: fast insertion, deletion, and +-- execution (all as O(1) implemented), but with lesser precision. +-- +-- This module will not provide the timer/runloop itself. Use your own runloop +-- and call `wheel:step` to check and execute timers. +-- +-- Implementation: +-- Consider a stack of rings, a timer beyond the current ring size is in the +-- next ring (or beyond). Precision is based on a slot with a specific size. +-- +-- The code explicitly avoids using `pairs`, `ipairs` and `next` to ensure JIT +-- compilation when using LuaJIT + +local default_now -- return time in seconds +if ngx then + default_now = ngx.now +else + local ok, socket = pcall(require, "socket") + if ok then + default_now = socket.gettime + else + default_now = nil -- we don't have a default + end +end + +local ok, new_tab = pcall(require, "table.new") +if not ok then + new_tab = function(narr, nrec) return {} end +end + +--local xpcall = require("coxpcall").xpcall +local default_err_handler = function(err) + io.stderr:write(debug.traceback("TimerWheel callback failed with: " .. tostring(err)).."\n") +end + +local math_floor = math.floor +local math_huge = math.huge +local EMPTY = {} + +local _M = {} + + +--- Creates a new timer wheel. +-- @tparam table opts the options table +-- @tparam[opt=0.050] number opts.precision the precision of the timer wheel in seconds (slot size), +-- @tparam[opt] int opts.ringsize number of slots in each ring, defaults to 72000 (1 +-- hour span, with `precision == 0.050`) +-- @tparam[opt] function opts.now a function returning the curent time in seconds. Defaults +-- to `ngx.now` or `luasocket.gettime` if available. +-- @tparam[opt] function opts.err_handler a function to use as error handler in an `xpcall` when +-- executing the callback. The default will write the stacktrace to `stderr`. +-- @treturn wheel the timerwheel object +function _M.new(opts) + assert(opts ~= _M, "new should not be called with colon ':' notation") + + opts = opts or EMPTY + assert(type(opts) == "table", "expected options to be a table") + + local precision = opts.precision or 0.050 -- in seconds, 50ms by default + local ringsize = opts.ringsize or 72000 -- #slots per ring, default 1 hour = 60 * 60 / 0.050 + local now = opts.now or default_now -- function to get time in seconds + local err_handler = opts.err_handler or default_err_handler + opts = nil -- luacheck: ignore + + assert(type(precision) == "number" and precision > 0, + "expected 'precision' to be number > 0") + assert(type(ringsize) == "number" and ringsize > 0 and math_floor(ringsize) == ringsize, + "expected 'ringsize' to be an integer number > 0") + assert(type(now) == "function", + "expected 'now' to be a function, got: " .. type(now)) + assert(type(err_handler) == "function", + "expected 'err_handler' to be a function, got: " .. type(err_handler)) + + local start = now() + local position = 1 -- position next up in first ring of timer wheel + local id_count = 0 -- counter to generate unique ids (all negative) + local id_list = {} -- reverse lookup table to find timers by id + local rings = {} -- list of rings, index 1 is the current ring + local rings_n = 0 -- the number of the last ring in the rings list + local count = 0 -- how many timers do we have + local wheel = {} -- the returned wheel object + + -- because we assume hefty setting and cancelling, we're reusing tables + -- to prevent excessive GC. + local tables = {} -- list of tables to be reused + local tables_n = 0 -- number of tables in the list + + --- Checks and executes timers. + -- Call this function (at least) every `precision` seconds. + -- @return `true` + function wheel:step() + local new_position = math_floor((now() - start) / precision) + 1 + local ring = rings[1] or EMPTY + + while position < new_position do + + -- get the expired slot, and remove it from the ring + local slot = ring[position] + ring[position] = nil + + -- forward pointers + position = position + 1 + if position > ringsize then + -- current ring is done, remove it and forward pointers + for i = 1, rings_n do + -- manual loop, since table.remove won't deal with holes + -- FIXME: If there are a large number of rings, then this loop becomes + -- a "stop the world" event. + rings[i] = rings[i + 1] + end + rings_n = rings_n - 1 + + ring = rings[1] or EMPTY + start = start + ringsize * precision + position = 1 + new_position = new_position - ringsize + end + + -- only deal with slot after forwarding pointers, to make sure that + -- any cb inserting another timer, does not end up in the slot being + -- handled + if slot then + -- deal with the slot + local ids = slot.ids + local args = slot.arg + for i = 1, slot.n do + local id = slot[i]; slot[i] = nil; slot[id] = nil + local cb = ids[id]; ids[id] = nil + local arg = args[id]; args[id] = nil + id_list[id] = nil + count = count - 1 + xpcall(cb, err_handler, arg) + end + + slot.n = 0 + -- delete the slot + tables_n = tables_n + 1 + tables[tables_n] = slot + end + + end + return true + end + + --- Gets the number of timers. + -- @treturn int number of timers + function wheel:count() + return count + end + + --- Sets a timer. + -- @tparam number expire_in in how many seconds should the timer expire + -- @tparam function cb callback function to execute upon expiring (NOTE: the + -- callback will run within an `xpcall`) + -- @param arg parameter to be passed to `cb` when executing + -- @treturn int the id of the newly set timer + -- @usage + -- local cb = function(arg) + -- print("timer executed with: ", arg) --> "timer executed with: hello world" + -- end + -- local id = wheel:set(5, cb, "hello world") + -- + -- -- do stuff here, while regularly calling `wheel:step()` + -- + -- wheel:cancel(id) -- cancel the timer again + function wheel:set(expire_in, cb, arg) + local time_expire = now() + expire_in + local pos = math_floor((time_expire - start) / precision) + 1 + if pos < position then + -- we cannot set it in the past + pos = position + end + local ring_idx = math_floor((pos - 1) / ringsize) + 1 + local slot_idx = pos - (ring_idx - 1) * ringsize + + -- fetch actual ring table + local ring = rings[ring_idx] + if not ring then + ring = new_tab(ringsize, 0) + rings[ring_idx] = ring + if ring_idx > rings_n then + rings_n = ring_idx + end + end + + -- fetch actual slot + local slot = ring[slot_idx] + if not slot then + if tables_n == 0 then + slot = { n = 0, ids = {}, arg = {} } + else + slot = tables[tables_n] + tables_n = tables_n - 1 + end + ring[slot_idx] = slot + end + + -- get new id + local id = id_count - 1 -- use negative idx to not interfere with array part + id_count = id + + -- store timer + -- if we do not do this check, it will go unnoticed and lead to very + -- hard to find bugs (`count` will go out of sync) + slot.ids[id] = cb or error("the callback parameter is required", 2) + slot.arg[id] = arg + local idx = slot.n + 1 + slot.n = idx + slot[idx] = id + slot[id] = idx + id_list[id] = slot + count = count + 1 + + return id + end + + --- Cancels a timer. + -- @tparam int id the timer id to cancel + -- @treturn boolean `true` if cancelled, `false` if not found + function wheel:cancel(id) + local slot = id_list[id] + if slot then + local idx = slot[id] + slot[id] = nil + slot.ids[id] = nil + slot.arg[id] = nil + local n = slot.n + if idx ~= n then + local moved_id = slot[n] + slot[idx] = moved_id + slot[moved_id] = idx + end + slot[n] = nil + slot.n = n - 1 + id_list[id] = nil + count = count - 1 + return true + end + return false + end + + --- Looks up the next expiring timer. + -- Note: traverses the wheel, O(n) operation! + -- @tparam[opt] number max_ahead maximum time (in seconds) + -- to look ahead + -- @treturn number number of seconds until next timer expires (can be negative), or + -- 'nil' if there is no timer from now to `max_ahead` + -- @usage + -- local t = wheel:peek(10) + -- if t then + -- print("next timer expires in ", t," seconds") + -- else + -- print("no timer scheduled for the next 10 seconds") + -- end + function wheel:peek(max_ahead) + if count == 0 then + return nil + end + local time_now = now() + + -- convert max_ahead from seconds to positions + if max_ahead then + max_ahead = math_floor((time_now + max_ahead - start) / precision) + else + max_ahead = math_huge + end + + local position_idx = position + local ring_idx = 1 + local ring = rings[ring_idx] or EMPTY -- TODO: if EMPTY then we can skip it? + local ahead_count = 0 + while ahead_count < max_ahead do + + local slot = ring[position_idx] + if slot then + if slot[1] then + -- we have a timer + return ((ring_idx - 1) * ringsize + position_idx) * precision + + start - time_now + end + end + + -- there is nothing in this position + position_idx = position_idx + 1 + ahead_count = ahead_count + 1 + if position_idx > ringsize then + position_idx = 1 + ring_idx = ring_idx + 1 + ring = rings[ring_idx] or EMPTY + end + end + + -- we hit max_ahead, without finding a timer + return nil + end + + if _G._TEST then -- export test variables only when testing + -- wheel._rings = rings + wheel._tables = tables + end + + return wheel +end + +return _M diff --git a/thirdparty/timerwheel.lua/timerwheel-scm-1.rockspec b/thirdparty/timerwheel.lua/timerwheel-scm-1.rockspec new file mode 100644 index 000000000..ab107e4e0 --- /dev/null +++ b/thirdparty/timerwheel.lua/timerwheel-scm-1.rockspec @@ -0,0 +1,36 @@ +local package_name = "timerwheel" +local package_version = "scm" +local rockspec_revision = "1" +local github_account_name = "Tieske" +local github_repo_name = package_name .. ".lua" + + +package = package_name +version = package_version.."-"..rockspec_revision +source = { + url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", + branch = (package_version == "scm") and "master" or nil, + tag = (package_version ~= "scm") and package_version or nil, +} +description = { + summary = "Timers based on a timerwheel", + detailed = [[ + Creating and deleting is very fast. Typically suited for + setting timeouts, which usually do not execute. + ]], + homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, + license = "MIT" +} +dependencies = { + "lua >= 5.1, < 5.5", + "coxpcall", +} +build = { + type = "builtin", + modules = { + ["timerwheel.init"] = "src/timerwheel/init.lua", + }, + copy_directories = { + "docs", + }, +}