Use coroutines via boost::asio::spawn to improve overall code structure in servers (#5341)

* Convert server_base class to use coroutine instead of handlers

* Rework wesnothd's client login to use coroutine

* Merge 3 player handling functions into a single coroutine

* update cmakelists too

* Implement send_doc_queued in terms of coroutine

* Use brace initialization for making asio buffers

* Implement campaignd's request handling in coroutine

* Brace-initialize entire vector

* Remove old handler based send/receive helpers

* Document coroutine send/receive helpers

* Made coro_send_doc() helper take wml doc by reference

In most cases there is no need to rely on shared pointers to ensure
object lifetime if using coroutines since even when coroutine is
suspended args are still kept alive by its context.

* Document coro_send_file()

* Silence deprecation warning to fix build on earlier versions of boost

* Explicitly check for boost.context to allow linking against static boost libs

* Add boost.coroutine to flatpak manifest

* Port winapi TransmitFile codepath to coroutines

* Exception safety fix

* Add boost.scope_exit to vcpkg

* Fix build with pre-1.66 boost

* Move coro_* helpers into server_base class

Those helpers were in .ipp solely because they were templated on handler
types, this is no longer true after coroutine based rework.

* Make server_base::coro_send_file non-inline

* CleanUp Xcode project

Co-authored-by: Martin Hrubý (hrubymar10) <hrubymar10@gmail.com>
This commit is contained in:
Sergey Popov 2020-12-30 19:08:34 +03:00 committed by GitHub
parent 204357c6d2
commit 3933ebab5c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 567 additions and 728 deletions

View file

@ -341,7 +341,7 @@ jobs:
shell: cmd shell: cmd
run: | run: |
%~dp0../wesnoth/vcpkg/vcpkg integrate install %~dp0../wesnoth/vcpkg/vcpkg integrate install
%~dp0../wesnoth/vcpkg/vcpkg install sdl2:x64-windows sdl2-image:x64-windows sdl2-image[libjpeg-turbo]:x64-windows sdl2-mixer[libvorbis,dynamic-load]:x64-windows sdl2-ttf:x64-windows bzip2:x64-windows zlib:x64-windows pango:x64-windows cairo:x64-windows fontconfig:x64-windows libvorbis:x64-windows libogg:x64-windows boost-filesystem:x64-windows boost-iostreams:x64-windows boost-locale[icu]:x64-windows boost-random:x64-windows boost-regex[icu]:x64-windows boost-asio:x64-windows boost-program-options:x64-windows boost-system:x64-windows boost-thread:x64-windows boost-bimap:x64-windows boost-multi-array:x64-windows boost-ptr-container:x64-windows boost-logic:x64-windows boost-format:x64-windows %~dp0../wesnoth/vcpkg/vcpkg install sdl2:x64-windows sdl2-image:x64-windows sdl2-image[libjpeg-turbo]:x64-windows sdl2-mixer[libvorbis,dynamic-load]:x64-windows sdl2-ttf:x64-windows bzip2:x64-windows zlib:x64-windows pango:x64-windows cairo:x64-windows fontconfig:x64-windows libvorbis:x64-windows libogg:x64-windows boost-filesystem:x64-windows boost-iostreams:x64-windows boost-locale[icu]:x64-windows boost-random:x64-windows boost-regex[icu]:x64-windows boost-asio:x64-windows boost-program-options:x64-windows boost-system:x64-windows boost-thread:x64-windows boost-bimap:x64-windows boost-multi-array:x64-windows boost-ptr-container:x64-windows boost-logic:x64-windows boost-format:x64-windows boost-scope-exit:x64-windows
- name: DOS3 - name: DOS3
shell: cmd shell: cmd
run: | run: |
@ -374,7 +374,7 @@ jobs:
shell: cmd shell: cmd
run: | run: |
%~dp0../wesnoth/vcpkg/vcpkg integrate install %~dp0../wesnoth/vcpkg/vcpkg integrate install
%~dp0../wesnoth/vcpkg/vcpkg install sdl2:x64-windows sdl2-image:x64-windows sdl2-image[libjpeg-turbo]:x64-windows sdl2-mixer[libvorbis,dynamic-load]:x64-windows sdl2-ttf:x64-windows bzip2:x64-windows zlib:x64-windows pango:x64-windows cairo:x64-windows fontconfig:x64-windows libvorbis:x64-windows libogg:x64-windows boost-filesystem:x64-windows boost-iostreams:x64-windows boost-locale[icu]:x64-windows boost-random:x64-windows boost-regex[icu]:x64-windows boost-asio:x64-windows boost-program-options:x64-windows boost-system:x64-windows boost-thread:x64-windows boost-bimap:x64-windows boost-multi-array:x64-windows boost-ptr-container:x64-windows boost-logic:x64-windows boost-format:x64-windows %~dp0../wesnoth/vcpkg/vcpkg install sdl2:x64-windows sdl2-image:x64-windows sdl2-image[libjpeg-turbo]:x64-windows sdl2-mixer[libvorbis,dynamic-load]:x64-windows sdl2-ttf:x64-windows bzip2:x64-windows zlib:x64-windows pango:x64-windows cairo:x64-windows fontconfig:x64-windows libvorbis:x64-windows libogg:x64-windows boost-filesystem:x64-windows boost-iostreams:x64-windows boost-locale[icu]:x64-windows boost-random:x64-windows boost-regex[icu]:x64-windows boost-asio:x64-windows boost-program-options:x64-windows boost-system:x64-windows boost-thread:x64-windows boost-bimap:x64-windows boost-multi-array:x64-windows boost-ptr-container:x64-windows boost-logic:x64-windows boost-format:x64-windows boost-scope-exit:x64-windows
- name: DOS3 - name: DOS3
shell: cmd shell: cmd
run: | run: |

View file

@ -81,7 +81,8 @@ else()
set(CRYPTO_LIBRARY "-framework Security") set(CRYPTO_LIBRARY "-framework Security")
endif() endif()
find_package(Boost ${BOOST_VERSION} REQUIRED COMPONENTS iostreams program_options regex system thread random) find_package(Boost ${BOOST_VERSION} REQUIRED COMPONENTS iostreams
program_options regex system thread random coroutine)
# no, gettext executables are not required when NLS is deactivated # no, gettext executables are not required when NLS is deactivated
find_package(Gettext) find_package(Gettext)

View file

@ -356,7 +356,9 @@ if env["prereqs"]:
have_libpthread = conf.CheckLib("pthread") have_libpthread = conf.CheckLib("pthread")
return have_libpthread & \ return have_libpthread & \
conf.CheckBoost("system") & \ conf.CheckBoost("system") & \
conf.CheckBoost("asio", header_only = True) conf.CheckBoost("asio", header_only = True) & \
conf.CheckBoost("context") & \
conf.CheckBoost("coroutine")
def have_sdl_other(): def have_sdl_other():
return \ return \
@ -429,6 +431,8 @@ if env["prereqs"]:
if env["history"]: if env["history"]:
client_env.Append(CPPDEFINES = ["HAVE_HISTORY"]) client_env.Append(CPPDEFINES = ["HAVE_HISTORY"])
env.Append(CPPDEFINES = ["BOOST_COROUTINES_NO_DEPRECATION_WARNING"])
if env["forum_user_handler"]: if env["forum_user_handler"]:
found_connector = False found_connector = False
for sql_config in ["mariadb_config", "mysql_config"]: for sql_config in ["mariadb_config", "mysql_config"]:

View file

@ -39,7 +39,7 @@
} }
], ],
"build-commands": [ "build-commands": [
"./bootstrap.sh --prefix=/app --with-libraries=filesystem,locale,iostreams,program_options,regex,random,thread", "./bootstrap.sh --prefix=/app --with-libraries=filesystem,locale,iostreams,program_options,regex,random,thread,coroutine,context",
"./b2 -j$FLATPAK_BUILDER_N_JOBS install cxxflags='-fPIE -fstack-protector-strong' define=_FORTIFY_SOURCE=2 link=static variant=release address-model=64 --layout=system" "./b2 -j$FLATPAK_BUILDER_N_JOBS install cxxflags='-fPIE -fstack-protector-strong' define=_FORTIFY_SOURCE=2 link=static variant=release address-model=64 --layout=system"
] ]
}, },

View file

@ -21,8 +21,6 @@
460D899E24DC7843000B1ABC /* forum_user_handler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460D899324DC7842000B1ABC /* forum_user_handler.cpp */; }; 460D899E24DC7843000B1ABC /* forum_user_handler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460D899324DC7842000B1ABC /* forum_user_handler.cpp */; };
460D899F24DC7843000B1ABC /* simple_wml.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460D899524DC7842000B1ABC /* simple_wml.cpp */; }; 460D899F24DC7843000B1ABC /* simple_wml.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460D899524DC7842000B1ABC /* simple_wml.cpp */; };
460D89A024DC7843000B1ABC /* simple_wml.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460D899524DC7842000B1ABC /* simple_wml.cpp */; }; 460D89A024DC7843000B1ABC /* simple_wml.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460D899524DC7842000B1ABC /* simple_wml.cpp */; };
460D89A124DC7843000B1ABC /* server_base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460D899824DC7842000B1ABC /* server_base.cpp */; };
460D89A224DC7843000B1ABC /* server_base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460D899824DC7842000B1ABC /* server_base.cpp */; };
460D89B624DC7863000B1ABC /* addon_utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460D89AD24DC7862000B1ABC /* addon_utils.cpp */; }; 460D89B624DC7863000B1ABC /* addon_utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460D89AD24DC7862000B1ABC /* addon_utils.cpp */; };
460D89B724DC7863000B1ABC /* server.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460D89AE24DC7862000B1ABC /* server.cpp */; }; 460D89B724DC7863000B1ABC /* server.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460D89AE24DC7862000B1ABC /* server.cpp */; };
460D89B824DC7863000B1ABC /* fs_commit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460D89B024DC7862000B1ABC /* fs_commit.cpp */; }; 460D89B824DC7863000B1ABC /* fs_commit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460D89B024DC7862000B1ABC /* fs_commit.cpp */; };
@ -84,6 +82,8 @@
464C03752283695A007D2741 /* libSDL2_ttf-2.0.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 464C0366228361B6007D2741 /* libSDL2_ttf-2.0.0.dylib */; }; 464C03752283695A007D2741 /* libSDL2_ttf-2.0.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 464C0366228361B6007D2741 /* libSDL2_ttf-2.0.0.dylib */; };
464C03762283695D007D2741 /* libSDL2_mixer-2.0.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 464C0368228361B7007D2741 /* libSDL2_mixer-2.0.0.dylib */; }; 464C03762283695D007D2741 /* libSDL2_mixer-2.0.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 464C0368228361B7007D2741 /* libSDL2_mixer-2.0.0.dylib */; };
464C037722836961007D2741 /* libSDL2_image-2.0.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 464C0362228361B5007D2741 /* libSDL2_image-2.0.0.dylib */; }; 464C037722836961007D2741 /* libSDL2_image-2.0.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 464C0362228361B5007D2741 /* libSDL2_image-2.0.0.dylib */; };
464F7FD125937D72006AB37B /* server_base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460D899824DC7842000B1ABC /* server_base.cpp */; };
464F7FDA25937D74006AB37B /* server_base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460D899824DC7842000B1ABC /* server_base.cpp */; };
46515C302569CE0B00084CE2 /* libssl.1.1.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 46515C2E2569CE0B00084CE2 /* libssl.1.1.dylib */; }; 46515C302569CE0B00084CE2 /* libssl.1.1.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 46515C2E2569CE0B00084CE2 /* libssl.1.1.dylib */; };
46515C312569CE0B00084CE2 /* libssl.1.1.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 46515C2E2569CE0B00084CE2 /* libssl.1.1.dylib */; }; 46515C312569CE0B00084CE2 /* libssl.1.1.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 46515C2E2569CE0B00084CE2 /* libssl.1.1.dylib */; };
46515C322569CE0B00084CE2 /* libssl.1.1.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 46515C2E2569CE0B00084CE2 /* libssl.1.1.dylib */; }; 46515C322569CE0B00084CE2 /* libssl.1.1.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 46515C2E2569CE0B00084CE2 /* libssl.1.1.dylib */; };
@ -1466,7 +1466,6 @@
460D899024DC7842000B1ABC /* user_handler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = user_handler.cpp; path = ../../src/server/common/user_handler.cpp; sourceTree = SOURCE_ROOT; }; 460D899024DC7842000B1ABC /* user_handler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = user_handler.cpp; path = ../../src/server/common/user_handler.cpp; sourceTree = SOURCE_ROOT; };
460D899124DC7842000B1ABC /* user_handler.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = user_handler.hpp; path = ../../src/server/common/user_handler.hpp; sourceTree = SOURCE_ROOT; }; 460D899124DC7842000B1ABC /* user_handler.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = user_handler.hpp; path = ../../src/server/common/user_handler.hpp; sourceTree = SOURCE_ROOT; };
460D899324DC7842000B1ABC /* forum_user_handler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = forum_user_handler.cpp; path = ../../src/server/common/forum_user_handler.cpp; sourceTree = SOURCE_ROOT; }; 460D899324DC7842000B1ABC /* forum_user_handler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = forum_user_handler.cpp; path = ../../src/server/common/forum_user_handler.cpp; sourceTree = SOURCE_ROOT; };
460D899424DC7842000B1ABC /* send_receive_wml_helpers.ipp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = send_receive_wml_helpers.ipp; path = ../../src/server/common/send_receive_wml_helpers.ipp; sourceTree = SOURCE_ROOT; };
460D899524DC7842000B1ABC /* simple_wml.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = simple_wml.cpp; path = ../../src/server/common/simple_wml.cpp; sourceTree = SOURCE_ROOT; }; 460D899524DC7842000B1ABC /* simple_wml.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = simple_wml.cpp; path = ../../src/server/common/simple_wml.cpp; sourceTree = SOURCE_ROOT; };
460D899624DC7842000B1ABC /* dbconn.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = dbconn.hpp; path = ../../src/server/common/dbconn.hpp; sourceTree = SOURCE_ROOT; }; 460D899624DC7842000B1ABC /* dbconn.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = dbconn.hpp; path = ../../src/server/common/dbconn.hpp; sourceTree = SOURCE_ROOT; };
460D899724DC7842000B1ABC /* server_base.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = server_base.hpp; path = ../../src/server/common/server_base.hpp; sourceTree = SOURCE_ROOT; }; 460D899724DC7842000B1ABC /* server_base.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = server_base.hpp; path = ../../src/server/common/server_base.hpp; sourceTree = SOURCE_ROOT; };
@ -3385,7 +3384,6 @@
460D899324DC7842000B1ABC /* forum_user_handler.cpp */, 460D899324DC7842000B1ABC /* forum_user_handler.cpp */,
460D898D24DC7841000B1ABC /* forum_user_handler.hpp */, 460D898D24DC7841000B1ABC /* forum_user_handler.hpp */,
460D89BC24DC95A2000B1ABC /* resultsets */, 460D89BC24DC95A2000B1ABC /* resultsets */,
460D899424DC7842000B1ABC /* send_receive_wml_helpers.ipp */,
460D899824DC7842000B1ABC /* server_base.cpp */, 460D899824DC7842000B1ABC /* server_base.cpp */,
460D899724DC7842000B1ABC /* server_base.hpp */, 460D899724DC7842000B1ABC /* server_base.hpp */,
460D899524DC7842000B1ABC /* simple_wml.cpp */, 460D899524DC7842000B1ABC /* simple_wml.cpp */,
@ -5626,7 +5624,6 @@
46685CAD219D643C0009CFFE /* tokenizer.cpp in Sources */, 46685CAD219D643C0009CFFE /* tokenizer.cpp in Sources */,
46F57088205FCF7E007031BF /* config_attribute_value.cpp in Sources */, 46F57088205FCF7E007031BF /* config_attribute_value.cpp in Sources */,
460D89A024DC7843000B1ABC /* simple_wml.cpp in Sources */, 460D89A024DC7843000B1ABC /* simple_wml.cpp in Sources */,
460D89A224DC7843000B1ABC /* server_base.cpp in Sources */,
91C548F21D88707D00FE6A7B /* filesystem_common.cpp in Sources */, 91C548F21D88707D00FE6A7B /* filesystem_common.cpp in Sources */,
46685CA8219D63FA0009CFFE /* preprocessor.cpp in Sources */, 46685CA8219D63FA0009CFFE /* preprocessor.cpp in Sources */,
46F57087205FCF5D007031BF /* filesystem_sdl.cpp in Sources */, 46F57087205FCF5D007031BF /* filesystem_sdl.cpp in Sources */,
@ -5640,6 +5637,7 @@
46F57086205FCE79007031BF /* hash.cpp in Sources */, 46F57086205FCE79007031BF /* hash.cpp in Sources */,
91C548DF1D886E2100FE6A7B /* log.cpp in Sources */, 91C548DF1D886E2100FE6A7B /* log.cpp in Sources */,
46685CA9219D63FE0009CFFE /* binary_or_text.cpp in Sources */, 46685CA9219D63FE0009CFFE /* binary_or_text.cpp in Sources */,
464F7FDA25937D74006AB37B /* server_base.cpp in Sources */,
91C548E01D886E2C00FE6A7B /* tstring.cpp in Sources */, 91C548E01D886E2C00FE6A7B /* tstring.cpp in Sources */,
460D89C524DC95DD000B1ABC /* tournaments.cpp in Sources */, 460D89C524DC95DD000B1ABC /* tournaments.cpp in Sources */,
460D89B624DC7863000B1ABC /* addon_utils.cpp in Sources */, 460D89B624DC7863000B1ABC /* addon_utils.cpp in Sources */,
@ -6232,6 +6230,7 @@
B5BB6C860F8943F600444FBF /* config_cache.cpp in Sources */, B5BB6C860F8943F600444FBF /* config_cache.cpp in Sources */,
46685CA1219D52DB0009CFFE /* parser.cpp in Sources */, 46685CA1219D52DB0009CFFE /* parser.cpp in Sources */,
B5BB6B8D0F893F2300444FBF /* config.cpp in Sources */, B5BB6B8D0F893F2300444FBF /* config.cpp in Sources */,
464F7FD125937D72006AB37B /* server_base.cpp in Sources */,
ECF9D43E19F3FF9400E6C9D9 /* filesystem.cpp in Sources */, ECF9D43E19F3FF9400E6C9D9 /* filesystem.cpp in Sources */,
460D898B24DC7831000B1ABC /* player.cpp in Sources */, 460D898B24DC7831000B1ABC /* player.cpp in Sources */,
903F959F1ED5496700F1BDD3 /* hash.cpp in Sources */, 903F959F1ED5496700F1BDD3 /* hash.cpp in Sources */,
@ -6257,7 +6256,6 @@
46BED4D52050611600842FA5 /* crypt_blowfish.c in Sources */, 46BED4D52050611600842FA5 /* crypt_blowfish.c in Sources */,
460D898724DC7831000B1ABC /* ban.cpp in Sources */, 460D898724DC7831000B1ABC /* ban.cpp in Sources */,
EC64D7661A085F120092EF75 /* mt_rng.cpp in Sources */, EC64D7661A085F120092EF75 /* mt_rng.cpp in Sources */,
460D89A124DC7843000B1ABC /* server_base.cpp in Sources */,
EC64D7671A085F2F0092EF75 /* seed_rng.cpp in Sources */, EC64D7671A085F2F0092EF75 /* seed_rng.cpp in Sources */,
B5BB6C710F8941F000444FBF /* tstring.cpp in Sources */, B5BB6C710F8941F000444FBF /* tstring.cpp in Sources */,
46685CA2219D52FA0009CFFE /* unicode.cpp in Sources */, 46685CA2219D52FA0009CFFE /* unicode.cpp in Sources */,
@ -6341,10 +6339,6 @@
"FIFODIR=\\\"/var/run/wesnoth_campaignd\\\"", "FIFODIR=\\\"/var/run/wesnoth_campaignd\\\"",
); );
INSTALL_PATH = /usr/local/bin; INSTALL_PATH = /usr/local/bin;
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/lib",
);
PRODUCT_NAME = campaignd; PRODUCT_NAME = campaignd;
}; };
name = Debug; name = Debug;
@ -6360,10 +6354,6 @@
"FIFODIR=\\\"/var/run/wesnoth_campaignd\\\"", "FIFODIR=\\\"/var/run/wesnoth_campaignd\\\"",
); );
INSTALL_PATH = /usr/local/bin; INSTALL_PATH = /usr/local/bin;
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/lib",
);
PRODUCT_NAME = campaignd; PRODUCT_NAME = campaignd;
}; };
name = Release; name = Release;
@ -6381,10 +6371,6 @@
BOOST_TEST_DYN_LINK, BOOST_TEST_DYN_LINK,
); );
INSTALL_PATH = /usr/local/bin; INSTALL_PATH = /usr/local/bin;
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/lib",
);
MACH_O_TYPE = mh_execute; MACH_O_TYPE = mh_execute;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"-lz", "-lz",
@ -6407,10 +6393,6 @@
BOOST_TEST_DYN_LINK, BOOST_TEST_DYN_LINK,
); );
INSTALL_PATH = /usr/local/bin; INSTALL_PATH = /usr/local/bin;
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/lib",
);
MACH_O_TYPE = mh_execute; MACH_O_TYPE = mh_execute;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"-lz", "-lz",
@ -6461,11 +6443,11 @@
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_WEAK = YES; CLANG_ENABLE_OBJC_WEAK = YES;
CODE_SIGN_ENTITLEMENTS = Resources/Wesnoth.entitlements; CODE_SIGN_ENTITLEMENTS = Resources/Wesnoth.entitlements;
CODE_SIGN_IDENTITY = "-"; CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = N5CYW96P9T;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
GCC_DYNAMIC_NO_PIC = NO; GCC_DYNAMIC_NO_PIC = NO;
GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES;
@ -6479,10 +6461,6 @@
INFOPLIST_FILE = Info.plist; INFOPLIST_FILE = Info.plist;
INSTALL_PATH = /Applications; INSTALL_PATH = /Applications;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/lib",
);
OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)";
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"-lz", "-lz",
@ -6519,10 +6497,6 @@
INFOPLIST_FILE = Info.plist; INFOPLIST_FILE = Info.plist;
INSTALL_PATH = /Applications; INSTALL_PATH = /Applications;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/lib",
);
OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)";
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"-lz", "-lz",

View file

@ -67,7 +67,9 @@ def CheckBoost(context, boost_lib, require_version = None, header_only = False):
"unit_test_framework" : "test/unit_test.hpp", "unit_test_framework" : "test/unit_test.hpp",
"filesystem" : "filesystem/operations.hpp", "filesystem" : "filesystem/operations.hpp",
"random" : "random/random_number_generator.hpp", "random" : "random/random_number_generator.hpp",
"system" : "system/error_code.hpp"} "system" : "system/error_code.hpp",
"context" : "context/continuation.hpp",
"coroutine" : "coroutine/coroutine.hpp" }
header_name = boost_headers.get(boost_lib, boost_lib + ".hpp") header_name = boost_headers.get(boost_lib, boost_lib + ".hpp")
libname = "boost_" + boost_lib + env.get("boost_suffix", "") libname = "boost_" + boost_lib + env.get("boost_suffix", "")

View file

@ -97,6 +97,7 @@ set(server-external-libs
${common-external-libs} ${common-external-libs}
${Boost_SYSTEM_LIBRARIES} ${Boost_SYSTEM_LIBRARIES}
${Boost_RANDOM_LIBRARY} ${Boost_RANDOM_LIBRARY}
${Boost_COROUTINE_LIBRARY}
${CRYPTO_LIBRARY} ${CRYPTO_LIBRARY}
${MYSQL_LIBS} ${MYSQL_LIBS}
-lpthread -lpthread

View file

@ -65,8 +65,6 @@ static lg::log_domain log_config("config");
static lg::log_domain log_server("server"); static lg::log_domain log_server("server");
#define ERR_SERVER LOG_STREAM(err, log_server) #define ERR_SERVER LOG_STREAM(err, log_server)
#include "server/common/send_receive_wml_helpers.ipp"
namespace campaignd { namespace campaignd {
namespace { namespace {
@ -477,34 +475,35 @@ std::ostream& operator<<(std::ostream& o, const server::request& r)
void server::handle_new_client(socket_ptr socket) void server::handle_new_client(socket_ptr socket)
{ {
async_receive_doc(socket, boost::asio::spawn(io_service_, [this, socket](boost::asio::yield_context yield) {
std::bind(&server::handle_request, this, std::placeholders::_1, std::placeholders::_2) while(true) {
); boost::system::error_code ec;
} auto doc { coro_receive_doc(socket, yield[ec]) };
if(check_error(ec, socket) || !doc) return;
void server::handle_request(socket_ptr socket, std::shared_ptr<simple_wml::document> doc) config data;
{ read(data, doc->output());
config data;
read(data, doc->output());
config::all_children_iterator i = data.ordered_begin(); config::all_children_iterator i = data.ordered_begin();
if(i != data.ordered_end()) { if(i != data.ordered_end()) {
// We only handle the first child. // We only handle the first child.
const config::any_child& c = *i; const config::any_child& c = *i;
request_handlers_table::const_iterator j request_handlers_table::const_iterator j
= handlers_.find(c.key); = handlers_.find(c.key);
if(j != handlers_.end()) { if(j != handlers_.end()) {
// Call the handler. // Call the handler.
request req{c.key, c.cfg, socket}; request req{c.key, c.cfg, socket, yield};
auto st = service_timer(req); auto st = service_timer(req);
j->second(this, req); j->second(this, req);
} else { } else {
send_error("Unrecognized [" + c.key + "] request.",socket); send_error("Unrecognized [" + c.key + "] request.",socket);
}
}
} }
} });
} }
#ifndef _WIN32 #ifndef _WIN32
@ -764,7 +763,7 @@ void server::send_message(const std::string& msg, socket_ptr sock)
const auto& escaped_msg = simple_wml_escape(msg); const auto& escaped_msg = simple_wml_escape(msg);
simple_wml::document doc; simple_wml::document doc;
doc.root().add_child("message").set_attr_dup("message", escaped_msg.c_str()); doc.root().add_child("message").set_attr_dup("message", escaped_msg.c_str());
async_send_doc(sock, doc, std::bind(&server::handle_new_client, this, std::placeholders::_1), null_handler); async_send_doc_queued(sock, doc);
} }
void server::send_error(const std::string& msg, socket_ptr sock) void server::send_error(const std::string& msg, socket_ptr sock)
@ -773,7 +772,7 @@ void server::send_error(const std::string& msg, socket_ptr sock)
const auto& escaped_msg = simple_wml_escape(msg); const auto& escaped_msg = simple_wml_escape(msg);
simple_wml::document doc; simple_wml::document doc;
doc.root().add_child("error").set_attr_dup("message", escaped_msg.c_str()); doc.root().add_child("error").set_attr_dup("message", escaped_msg.c_str());
async_send_doc(sock, doc, std::bind(&server::handle_new_client, this, std::placeholders::_1), null_handler); async_send_doc_queued(sock, doc);
} }
void server::send_error(const std::string& msg, const std::string& extra_data, unsigned int status_code, socket_ptr sock) void server::send_error(const std::string& msg, const std::string& extra_data, unsigned int status_code, socket_ptr sock)
@ -794,7 +793,7 @@ void server::send_error(const std::string& msg, const std::string& extra_data, u
err_cfg.set_attr_dup("extra_data", escaped_extra_data.c_str()); err_cfg.set_attr_dup("extra_data", escaped_extra_data.c_str());
err_cfg.set_attr_dup("status_code", escaped_status_str.c_str()); err_cfg.set_attr_dup("status_code", escaped_status_str.c_str());
async_send_doc(sock, doc, std::bind(&server::handle_new_client, this, std::placeholders::_1), null_handler); async_send_doc_queued(sock, doc);
} }
config& server::get_addon(const std::string& id) config& server::get_addon(const std::string& id)
@ -867,7 +866,7 @@ void server::handle_server_id(const server::request& req)
simple_wml::document doc(wml.c_str(), simple_wml::INIT_STATIC); simple_wml::document doc(wml.c_str(), simple_wml::INIT_STATIC);
doc.compress(); doc.compress();
async_send_doc(req.sock, doc, std::bind(&server::handle_new_client, this, std::placeholders::_1)); async_send_doc_queued(req.sock, doc);
} }
void server::handle_request_campaign_list(const server::request& req) void server::handle_request_campaign_list(const server::request& req)
@ -968,7 +967,7 @@ void server::handle_request_campaign_list(const server::request& req)
simple_wml::document doc(wml.c_str(), simple_wml::INIT_STATIC); simple_wml::document doc(wml.c_str(), simple_wml::INIT_STATIC);
doc.compress(); doc.compress();
async_send_doc(req.sock, doc, std::bind(&server::handle_new_client, this, std::placeholders::_1)); async_send_doc_queued(req.sock, doc);
} }
void server::handle_request_campaign(const server::request& req) void server::handle_request_campaign(const server::request& req)
@ -1071,7 +1070,9 @@ void server::handle_request_campaign(const server::request& req)
LOG_CS << req << "Sending add-on '" << name << "' version: " << from << " -> " << to << " (delta))\n"; LOG_CS << req << "Sending add-on '" << name << "' version: " << from << " -> " << to << " (delta))\n";
async_send_doc(req.sock, doc, std::bind(&server::handle_new_client, this, std::placeholders::_1), null_handler); boost::system::error_code ec;
coro_send_doc(req.sock, doc, req.yield[ec]);
if(check_error(ec, req.sock)) return;
full_pack_path.clear(); full_pack_path.clear();
} }
@ -1087,7 +1088,9 @@ void server::handle_request_campaign(const server::request& req)
} }
LOG_CS << req << "Sending add-on '" << name << "' version: " << to << " size: " << full_pack_size / 1024 << " KiB\n"; LOG_CS << req << "Sending add-on '" << name << "' version: " << to << " size: " << full_pack_size / 1024 << " KiB\n";
async_send_file(req.sock, full_pack_path, std::bind(&server::handle_new_client, this, std::placeholders::_1), null_handler); boost::system::error_code ec;
coro_send_file(req.sock, full_pack_path, req.yield[ec]);
if(check_error(ec, req.sock)) return;
} }
// Clients doing upgrades or some other specific thing shouldn't bump // Clients doing upgrades or some other specific thing shouldn't bump
@ -1139,7 +1142,9 @@ void server::handle_request_campaign_hash(const server::request& req)
} }
LOG_CS << req << "Sending add-on hash index for '" << req.cfg["name"] << "' size: " << file_size / 1024 << " KiB\n"; LOG_CS << req << "Sending add-on hash index for '" << req.cfg["name"] << "' size: " << file_size / 1024 << " KiB\n";
async_send_file(req.sock, path, std::bind(&server::handle_new_client, this, std::placeholders::_1), null_handler); boost::system::error_code ec;
coro_send_file(req.sock, path, req.yield[ec]);
if(check_error(ec, req.sock)) return;
} }
} }

View file

@ -59,6 +59,9 @@ public:
const socket_ptr sock; const socket_ptr sock;
const std::string addr; const std::string addr;
boost::asio::yield_context yield; ///< context of the coroutine the request is executed in
///< async operations on @a sock can use it instead of a handler.
/** /**
* Constructor. * Constructor.
* *
@ -74,11 +77,13 @@ public:
*/ */
request(const std::string& reqcmd, request(const std::string& reqcmd,
config& reqcfg, config& reqcfg,
socket_ptr reqsock) socket_ptr reqsock,
boost::asio::yield_context yield)
: cmd(reqcmd) : cmd(reqcmd)
, cfg(reqcfg) , cfg(reqcfg)
, sock(reqsock) , sock(reqsock)
, addr(client_address(sock)) , addr(client_address(sock))
, yield(yield)
{} {}
}; };
@ -125,7 +130,6 @@ private:
boost::asio::basic_waitable_timer<std::chrono::steady_clock> flush_timer_; boost::asio::basic_waitable_timer<std::chrono::steady_clock> flush_timer_;
void handle_new_client(socket_ptr socket); void handle_new_client(socket_ptr socket);
void handle_request(socket_ptr socket, std::shared_ptr<simple_wml::document> doc);
#ifndef _WIN32 #ifndef _WIN32
void handle_read_from_fifo(const boost::system::error_code& error, std::size_t bytes_transferred); void handle_read_from_fifo(const boost::system::error_code& error, std::size_t bytes_transferred);

View file

@ -1,358 +0,0 @@
/*
Copyright (C) 2016 by Sergey Popov <dave@whitevine.net>
Part of the Battle for Wesnoth Project https://www.wesnoth.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License 2
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#ifndef SEND_RECEIVE_WML_HELPERS_HPP
#define SEND_RECEIVE_WML_HELPERS_HPP
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_SENDFILE
#include <sys/sendfile.h>
#endif
#ifdef _WIN32
#include <windows.h>
#endif
#include "server/common/server_base.hpp"
#include "server/common/simple_wml.hpp"
#include "filesystem.hpp"
#include "serialization/unicode_cast.hpp" //only used in windows specific code.
#include <boost/asio/read.hpp>
#include <boost/asio/write.hpp>
#include <memory>
#include <stdexcept>
template<typename Handler, typename ErrorHandler>
struct handle_doc
{
Handler handler;
ErrorHandler error_handler;
socket_ptr socket;
union DataSize
{
uint32_t size;
char buf[4];
};
std::shared_ptr<DataSize> data_size;
std::shared_ptr<simple_wml::document> doc;
boost::shared_array<char> buffer;
handle_doc(socket_ptr socket, Handler handler, ErrorHandler error_handler, uint32_t size, std::shared_ptr<simple_wml::document> doc) :
handler(handler), error_handler(error_handler), socket(socket), data_size(new DataSize), doc(doc)
{
data_size->size = htonl(size);
}
handle_doc(socket_ptr socket, Handler handler, ErrorHandler error_handler) :
handler(handler), error_handler(error_handler), socket(socket), data_size(new DataSize)
{
}
void operator()(const boost::system::error_code& error, std::size_t)
{
if(check_error(error, socket)) {
error_handler(socket);
return;
}
handler(socket);
}
};
template<typename Handler, typename ErrorHandler>
void async_send_doc(socket_ptr socket, simple_wml::document& doc, Handler handler, ErrorHandler error_handler)
{
try {
std::shared_ptr<simple_wml::document> doc_ptr(doc.clone());
simple_wml::string_span s = doc_ptr->output_compressed();
std::vector<boost::asio::const_buffer> buffers;
handle_doc<Handler, ErrorHandler> handle_send_doc(socket, handler, error_handler, s.size(), doc_ptr);
buffers.push_back(boost::asio::buffer(handle_send_doc.data_size->buf, 4));
buffers.push_back(boost::asio::buffer(s.begin(), s.size()));
async_write(*socket, buffers, handle_send_doc);
} catch (simple_wml::error& e) {
WRN_CONFIG << __func__ << ": simple_wml error: " << e.message << std::endl;
}
}
static void null_handler(socket_ptr)
{
}
#ifdef HAVE_SENDFILE
template <typename Handler, typename ErrorHandler>
struct sendfile_op
{
socket_ptr sock_;
int fd_;
Handler handler_;
ErrorHandler error_handler_;
off_t offset_;
std::size_t total_bytes_transferred_;
// Function call operator meeting WriteHandler requirements.
// Used as the handler for the async_write_some operation.
void operator()(boost::system::error_code ec, std::size_t)
{
// Put the underlying socket into non-blocking mode.
if (!ec)
if (!sock_->native_non_blocking())
sock_->native_non_blocking(true, ec);
if (!ec)
{
for (;;)
{
// Try the system call.
errno = 0;
int n = ::sendfile(sock_->native_handle(), fd_, &offset_, 65536);
ec = boost::system::error_code(n < 0 ? errno : 0,
boost::asio::error::get_system_category());
total_bytes_transferred_ += ec ? 0 : n;
// Retry operation immediately if interrupted by signal.
if (ec == boost::asio::error::interrupted)
continue;
// Check if we need to run the operation again.
if (ec == boost::asio::error::would_block
|| ec == boost::asio::error::try_again)
{
// We have to wait for the socket to become ready again.
sock_->async_write_some(boost::asio::null_buffers(), *this);
return;
}
if (ec || n == 0)
{
// An error occurred, or we have reached the end of the file.
// Either way we must exit the loop so we can call the handler.
break;
}
// Loop around to try calling sendfile again.
}
}
close(fd_);
if(ec)
error_handler_(sock_);
else
handler_(sock_);
}
};
template<typename Handler, typename ErrorHandler>
void async_send_file(socket_ptr socket, const std::string& filename, Handler handler, ErrorHandler error_handler)
{
std::vector<boost::asio::const_buffer> buffers;
std::size_t filesize = filesystem::file_size(filename);
int in_file(open(filename.c_str(), O_RDONLY));
sendfile_op<Handler, ErrorHandler> op = { socket, in_file, handler, error_handler, 0, 0 };
handle_doc<Handler, ErrorHandler> handle_send_doc(socket, handler, error_handler, filesize, nullptr);
buffers.push_back(boost::asio::buffer(handle_send_doc.data_size->buf, 4));
async_write(*socket, buffers, op);
}
#elif defined(_WIN32)
template<typename Handler, typename ErrorHandler>
struct sendfile_op
{
socket_ptr sock_;
HANDLE file_;
OVERLAPPED overlap_;
Handler handler_;
ErrorHandler error_handler_;
bool pending_;
std::shared_ptr<handle_doc<Handler, ErrorHandler>> handle_send_doc_;
void operator()(boost::system::error_code, std::size_t)
{
bool failed = false;
if (!pending_)
{
BOOL success = TransmitFile(sock_->native_handle(), file_, 0, 0, &overlap_, nullptr, 0);
if (!success)
{
int winsock_ec = WSAGetLastError();
if (winsock_ec == WSA_IO_PENDING || winsock_ec == ERROR_IO_PENDING)
{
// The request is pending. Wait until it completes.
pending_ = true;
sock_->async_write_some(boost::asio::null_buffers(), *this);
return;
}
else
{
failed = true;
}
}
}
else
{
DWORD win_ec = GetLastError();
if (win_ec != ERROR_IO_PENDING && win_ec != ERROR_SUCCESS)
{
failed = true;
}
else if (!HasOverlappedIoCompleted(&overlap_))
{
// Keep waiting.
sock_->async_write_some(boost::asio::null_buffers(), *this);
return;
}
}
CloseHandle(file_);
CloseHandle(overlap_.hEvent);
if (!failed)
{
handler_(sock_);
}
else
{
error_handler_(sock_);
}
}
};
template<typename Handler, typename ErrorHandler>
void async_send_file(socket_ptr socket, const std::string& filename, Handler handler, ErrorHandler error_handler)
{
std::vector<boost::asio::const_buffer> buffers;
SetLastError(ERROR_SUCCESS);
std::size_t filesize = filesystem::file_size(filename);
std::wstring filename_ucs2 = unicode_cast<std::wstring>(filename);
HANDLE in_file = CreateFileW(filename_ucs2.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
if (GetLastError() != ERROR_SUCCESS)
{
throw std::runtime_error("Failed to open the file");
}
sendfile_op<Handler, ErrorHandler> op = { socket, in_file, OVERLAPPED(), handler, error_handler, false, nullptr };
HANDLE event = CreateEvent(nullptr, TRUE, TRUE, nullptr);
if (GetLastError() != ERROR_SUCCESS)
{
throw std::runtime_error("Failed to create an event");
}
op.overlap_.hEvent = event;
op.handle_send_doc_.reset(new handle_doc<Handler, ErrorHandler>(socket, handler, error_handler, filesize, nullptr));
buffers.push_back(boost::asio::buffer(op.handle_send_doc_->data_size->buf, 4));
async_write(*socket, buffers, op);
}
#else
// TODO: Implement this for systems without sendfile()
template<typename Handler, typename ErrorHandler>
void async_send_file(socket_ptr, const std::string&, Handler, ErrorHandler)
{
assert(false && "Not implemented yet");
}
#endif
template<typename Handler>
inline void async_send_doc(socket_ptr socket, simple_wml::document& doc, Handler handler)
{
async_send_doc(socket, doc, handler, null_handler);
}
inline void async_send_doc(socket_ptr socket, simple_wml::document& doc)
{
async_send_doc(socket, doc, null_handler, null_handler);
}
template<typename Handler, typename ErrorHandler>
struct handle_receive_doc : public handle_doc<Handler, ErrorHandler>
{
std::size_t buf_size;
handle_receive_doc(socket_ptr socket, Handler handler, ErrorHandler error_handler) :
handle_doc<Handler, ErrorHandler>(socket, handler, error_handler),
buf_size(0)
{
}
void operator()(const boost::system::error_code& error, std::size_t size)
{
if(check_error(error, this->socket)) {
this->error_handler(this->socket);
return;
}
if(!this->buffer) {
assert(size == 4);
buf_size = ntohl(this->data_size->size);
if(buf_size == 0) {
ERR_SERVER <<
client_address(this->socket) <<
"\treceived invalid packet with payload size 0" << std::endl;
this->error_handler(this->socket);
return;
}
if(buf_size > simple_wml::document::document_size_limit) {
ERR_SERVER <<
client_address(this->socket) <<
"\treceived packet with payload size over size limit" << std::endl;
this->error_handler(this->socket);
return;
}
this->buffer = boost::shared_array<char>(new char[buf_size]);
async_read(*(this->socket), boost::asio::buffer(this->buffer.get(), buf_size), *this);
} else {
simple_wml::string_span compressed_buf(this->buffer.get(), buf_size);
try {
this->doc.reset(new simple_wml::document(compressed_buf));
} catch (simple_wml::error& e) {
ERR_SERVER <<
client_address(this->socket) <<
"\tsimple_wml error in received data: " << e.message << std::endl;
async_send_error(this->socket, "Invalid WML received: " + e.message);
this->error_handler(this->socket);
return;
}
this->handler(this->socket, this->doc);
}
}
};
template<typename Handler, typename ErrorHandler>
inline void async_receive_doc(socket_ptr socket, Handler handler, ErrorHandler error_handler)
{
handle_receive_doc<Handler, ErrorHandler> handle_receive_doc(socket, handler, error_handler);
async_read(*socket, boost::asio::buffer(handle_receive_doc.data_size->buf, 4), handle_receive_doc);
}
template<typename Handler>
inline void async_receive_doc(socket_ptr socket, Handler handler)
{
async_receive_doc(socket, handler, null_handler);
}
#endif

View file

@ -16,6 +16,20 @@
#include "lexical_cast.hpp" #include "lexical_cast.hpp"
#include "log.hpp" #include "log.hpp"
#include "filesystem.hpp"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_SENDFILE
#include <sys/sendfile.h>
#endif
#ifdef _WIN32
#include <windows.h>
#include <boost/scope_exit.hpp>
#endif
#include <boost/asio/ip/v6_only.hpp> #include <boost/asio/ip/v6_only.hpp>
#include <boost/asio/read.hpp> #include <boost/asio/read.hpp>
@ -37,8 +51,6 @@ static lg::log_domain log_config("config");
#define ERR_CONFIG LOG_STREAM(err, log_config) #define ERR_CONFIG LOG_STREAM(err, log_config)
#define WRN_CONFIG LOG_STREAM(warn, log_config) #define WRN_CONFIG LOG_STREAM(warn, log_config)
#include "server/common/send_receive_wml_helpers.ipp"
server_base::server_base(unsigned short port, bool keep_alive) : server_base::server_base(unsigned short port, bool keep_alive) :
port_(port), port_(port),
keep_alive_(keep_alive), keep_alive_(keep_alive),
@ -53,26 +65,13 @@ server_base::server_base(unsigned short port, bool keep_alive) :
{ {
} }
void server_base::setup_acceptor(boost::asio::ip::tcp::acceptor& acceptor, boost::asio::ip::tcp::endpoint endpoint)
{
acceptor.open(endpoint.protocol());
acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
acceptor.set_option(boost::asio::ip::tcp::acceptor::keep_alive(keep_alive_));
if(endpoint.protocol() == boost::asio::ip::tcp::v6())
acceptor.set_option(boost::asio::ip::v6_only(true));
acceptor.bind(endpoint);
acceptor.listen();
}
void server_base::start_server() void server_base::start_server()
{ {
boost::asio::ip::tcp::endpoint endpoint_v6(boost::asio::ip::tcp::v6(), port_); boost::asio::ip::tcp::endpoint endpoint_v6(boost::asio::ip::tcp::v6(), port_);
setup_acceptor(acceptor_v6_, endpoint_v6); boost::asio::spawn(io_service_, [this, endpoint_v6](boost::asio::yield_context yield) { serve(yield, acceptor_v6_, endpoint_v6); });
serve(acceptor_v6_);
boost::asio::ip::tcp::endpoint endpoint_v4(boost::asio::ip::tcp::v4(), port_); boost::asio::ip::tcp::endpoint endpoint_v4(boost::asio::ip::tcp::v4(), port_);
setup_acceptor(acceptor_v4_, endpoint_v4); boost::asio::spawn(io_service_, [this, endpoint_v4](boost::asio::yield_context yield) { serve(yield, acceptor_v4_, endpoint_v4); });
serve(acceptor_v4_);
handshake_response_.connection_num = htonl(42); handshake_response_.connection_num = htonl(42);
@ -84,23 +83,31 @@ void server_base::start_server()
sigs_.async_wait(std::bind(&server_base::handle_termination, this, std::placeholders::_1, std::placeholders::_2)); sigs_.async_wait(std::bind(&server_base::handle_termination, this, std::placeholders::_1, std::placeholders::_2));
} }
void server_base::serve(boost::asio::ip::tcp::acceptor& acceptor) void server_base::serve(boost::asio::yield_context yield, boost::asio::ip::tcp::acceptor& acceptor, boost::asio::ip::tcp::endpoint endpoint)
{ {
socket_ptr socket = std::make_shared<boost::asio::ip::tcp::socket>(io_service_); if(!acceptor.is_open()) {
acceptor.async_accept(*socket, [&acceptor, socket, this](const boost::system::error_code& error){ acceptor.open(endpoint.protocol());
this->accept_connection(acceptor, error, socket); acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
}); acceptor.set_option(boost::asio::ip::tcp::acceptor::keep_alive(keep_alive_));
} if(endpoint.protocol() == boost::asio::ip::tcp::v6())
acceptor.set_option(boost::asio::ip::v6_only(true));
acceptor.bind(endpoint);
acceptor.listen();
}
void server_base::accept_connection(boost::asio::ip::tcp::acceptor& acceptor, const boost::system::error_code& error, socket_ptr socket) socket_ptr socket = std::make_shared<socket_ptr::element_type>(io_service_);
{
if(accepting_connections()) boost::system::error_code error;
serve(acceptor); acceptor.async_accept(socket->lowest_layer(), yield[error]);
if(error) { if(error) {
ERR_SERVER << "Accept failed: " << error.message() << "\n"; ERR_SERVER << "Accept failed: " << error.message() << "\n";
return; return;
} }
if(accepting_connections()) {
boost::asio::spawn(io_service_, [this, &acceptor, endpoint](boost::asio::yield_context yield) { serve(yield, acceptor, endpoint); });
}
#ifndef _WIN32 #ifndef _WIN32
if(keep_alive_) { if(keep_alive_) {
int timeout = 30; int timeout = 30;
@ -118,20 +125,9 @@ void server_base::accept_connection(boost::asio::ip::tcp::acceptor& acceptor, co
#endif #endif
DBG_SERVER << client_address(socket) << "\tnew connection tentatively accepted\n"; DBG_SERVER << client_address(socket) << "\tnew connection tentatively accepted\n";
serverside_handshake(socket);
}
void server_base::serverside_handshake(socket_ptr socket)
{
boost::shared_array<char> handshake(new char[4]); boost::shared_array<char> handshake(new char[4]);
async_read( async_read(*socket, boost::asio::buffer(handshake.get(), 4), yield[error]);
*socket, boost::asio::buffer(handshake.get(), 4),
std::bind(&server_base::handle_handshake, this, std::placeholders::_1, socket, handshake)
);
}
void server_base::handle_handshake(const boost::system::error_code& error, socket_ptr socket, boost::shared_array<char> handshake)
{
if(check_error(error, socket)) if(check_error(error, socket))
return; return;
@ -139,29 +135,26 @@ void server_base::handle_handshake(const boost::system::error_code& error, socke
ERR_SERVER << client_address(socket) << "\tincorrect handshake\n"; ERR_SERVER << client_address(socket) << "\tincorrect handshake\n";
return; return;
} }
async_write(
*socket, boost::asio::buffer(handshake_response_.buf, 4),
[=](const boost::system::error_code& error, std::size_t)
{
if(!check_error(error, socket)) {
const std::string ip = client_address(socket);
const std::string reason = is_ip_banned(ip); async_write(*socket, boost::asio::buffer(handshake_response_.buf, 4), yield[error]);
if (!reason.empty()) {
LOG_SERVER << ip << "\trejected banned user. Reason: " << reason << "\n"; if(!check_error(error, socket)) {
async_send_error(socket, "You are banned. Reason: " + reason); const std::string ip = client_address(socket);
return;
} else if (ip_exceeds_connection_limit(ip)) { const std::string reason = is_ip_banned(ip);
LOG_SERVER << ip << "\trejected ip due to excessive connections\n"; if (!reason.empty()) {
async_send_error(socket, "Too many connections from your IP."); LOG_SERVER << ip << "\trejected banned user. Reason: " << reason << "\n";
return; async_send_error(socket, "You are banned. Reason: " + reason);
} else { return;
DBG_SERVER << ip << "\tnew connection fully accepted\n"; } else if (ip_exceeds_connection_limit(ip)) {
this->handle_new_client(socket); LOG_SERVER << ip << "\trejected ip due to excessive connections\n";
} async_send_error(socket, "Too many connections from your IP.");
} return;
} } else {
); DBG_SERVER << ip << "\tnew connection fully accepted\n";
this->handle_new_client(socket);
}
}
} }
#ifndef _WIN32 #ifndef _WIN32
@ -219,7 +212,7 @@ bool check_error(const boost::system::error_code& error, socket_ptr socket)
namespace { namespace {
void info_table_into_simple_wml(simple_wml::document& doc, const std::string& parent_name, const info_table& info) void info_table_into_simple_wml(simple_wml::document& doc, const std::string& parent_name, const server_base::info_table& info)
{ {
if(info.empty()) { if(info.empty()) {
return; return;
@ -233,31 +226,227 @@ void info_table_into_simple_wml(simple_wml::document& doc, const std::string& pa
} }
using SendQueue = std::map<socket_ptr, std::queue<std::shared_ptr<simple_wml::document>>>; /// Send a WML document from within a coroutine
SendQueue send_queue; /// @param socket
/// @param doc
static void handle_async_send_doc_queued(socket_ptr socket) /// @param yield The function will suspend on write operation using this yield context
void server_base::coro_send_doc(socket_ptr socket, simple_wml::document& doc, boost::asio::yield_context yield)
{ {
if(send_queue[socket].empty()) { try {
send_queue.erase(socket); simple_wml::string_span s = doc.output_compressed();
} else {
async_send_doc(socket, *(send_queue[socket].front()), handle_async_send_doc_queued, handle_async_send_doc_queued); union DataSize
send_queue[socket].pop(); {
uint32_t size;
char buf[4];
} data_size;
data_size.size = htonl(s.size());
std::vector<boost::asio::const_buffer> buffers {
{ data_size.buf, 4 },
{ s.begin(), std::size_t(s.size()) }
};
async_write(*socket, buffers, yield);
} catch (simple_wml::error& e) {
WRN_CONFIG << __func__ << ": simple_wml error: " << e.message << std::endl;
throw;
} }
} }
void async_send_doc_queued(socket_ptr socket, simple_wml::document& doc) #ifdef HAVE_SENDFILE
void server_base::coro_send_file(socket_ptr socket, const std::string& filename, boost::asio::yield_context yield)
{ {
auto iter = send_queue.find(socket); std::size_t filesize { std::size_t(filesystem::file_size(filename)) };
if(iter == send_queue.end()) { int in_file { open(filename.c_str(), O_RDONLY) };
send_queue[socket]; off_t offset { 0 };
async_send_doc(socket, doc, handle_async_send_doc_queued, handle_async_send_doc_queued); std::size_t total_bytes_transferred { 0 };
} else {
send_queue[socket].emplace(doc.clone()); union DataSize
{
uint32_t size;
char buf[4];
} data_size;
data_size.size = htonl(filesize);
async_write(*socket, boost::asio::buffer(data_size.buf), yield);
if(*(yield.ec_)) return;
// Put the underlying socket into non-blocking mode.
if(!socket->native_non_blocking())
socket->native_non_blocking(true, *yield.ec_);
if(*(yield.ec_)) return;
for (;;)
{
// Try the system call.
errno = 0;
int n = ::sendfile(socket->native_handle(), in_file, &offset, 65536);
*(yield.ec_) = boost::system::error_code(n < 0 ? errno : 0,
boost::asio::error::get_system_category());
total_bytes_transferred += *(yield.ec_) ? 0 : n;
// Retry operation immediately if interrupted by signal.
if (*(yield.ec_) == boost::asio::error::interrupted)
continue;
// Check if we need to run the operation again.
if (*(yield.ec_) == boost::asio::error::would_block
|| *(yield.ec_) == boost::asio::error::try_again)
{
// We have to wait for the socket to become ready again.
socket->async_write_some(boost::asio::null_buffers(), yield);
continue;
}
if (*(yield.ec_) || n == 0)
{
// An error occurred, or we have reached the end of the file.
// Either way we must exit the loop.
break;
}
// Loop around to try calling sendfile again.
} }
} }
void async_send_error(socket_ptr socket, const std::string& msg, const char* error_code, const info_table& info) #elif defined(_WIN32)
void server_base::coro_send_file(socket_ptr socket, const std::string& filename, boost::asio::yield_context yield)
{
OVERLAPPED overlap;
std::vector<boost::asio::const_buffer> buffers;
SetLastError(ERROR_SUCCESS);
std::size_t filesize = filesystem::file_size(filename);
std::wstring filename_ucs2 = unicode_cast<std::wstring>(filename);
HANDLE in_file = CreateFileW(filename_ucs2.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
if (GetLastError() != ERROR_SUCCESS)
{
throw std::runtime_error("Failed to open the file");
}
BOOST_SCOPE_EXIT_ALL(in_file) {
CloseHandle(&in_file);
};
HANDLE event = CreateEvent(nullptr, TRUE, TRUE, nullptr);
if (GetLastError() != ERROR_SUCCESS)
{
throw std::runtime_error("Failed to create an event");
}
BOOST_SCOPE_EXIT_ALL(&overlap) {
CloseHandle(overlap.hEvent);
};
overlap.hEvent = event;
union DataSize
{
uint32_t size;
char buf[4];
} data_size;
data_size.size = htonl(filesize);
async_write(*socket, boost::asio::buffer(data_size.buf, 4), yield);
BOOL success = TransmitFile(socket->native_handle(), in_file, 0, 0, &overlap, nullptr, 0);
if(!success) {
int winsock_ec = WSAGetLastError();
if(winsock_ec == WSA_IO_PENDING || winsock_ec == ERROR_IO_PENDING) {
while(true) {
// The request is pending. Wait until it completes.
socket->async_write_some(boost::asio::null_buffers(), yield);
DWORD win_ec = GetLastError();
if (win_ec != ERROR_IO_PENDING && win_ec != ERROR_SUCCESS)
throw std::runtime_error("TransmitFile failed");
if(HasOverlappedIoCompleted(&overlap)) break;
}
} else {
throw std::runtime_error("TransmitFile failed");
}
}
}
#else
void server_base::coro_send_file(socket_ptr socket, const std::string& filename, boost::asio::yield_context yield)
{
// TODO: Implement this for systems without sendfile()
assert(false && "Not implemented yet");
}
#endif
/// Receive WML document from a coroutine
/// @param socket
/// @param yield The function will suspend on read operation using this yield context
std::shared_ptr<simple_wml::document> server_base::coro_receive_doc(socket_ptr socket, boost::asio::yield_context yield)
{
union DataSize
{
uint32_t size;
char buf[4];
} data_size;
async_read(*socket, boost::asio::buffer(data_size.buf, 4), yield);
if(*yield.ec_) return {};
uint32_t size = ntohl(data_size.size);
if(size == 0) {
ERR_SERVER <<
client_address(socket) <<
"\treceived invalid packet with payload size 0" << std::endl;
return {};
}
if(size > simple_wml::document::document_size_limit) {
ERR_SERVER <<
client_address(socket) <<
"\treceived packet with payload size over size limit" << std::endl;
return {};
}
boost::shared_array<char> buffer{ new char[size] };
async_read(*socket, boost::asio::buffer(buffer.get(), size), yield);
try {
simple_wml::string_span compressed_buf(buffer.get(), size);
return std::shared_ptr<simple_wml::document> { new simple_wml::document(compressed_buf) };
} catch (simple_wml::error& e) {
ERR_SERVER <<
client_address(socket) <<
"\tsimple_wml error in received data: " << e.message << std::endl;
async_send_error(socket, "Invalid WML received: " + e.message);
return {};
}
}
void server_base::async_send_doc_queued(socket_ptr socket, simple_wml::document& doc)
{
std::shared_ptr<simple_wml::document> doc_ptr { doc.clone() };
boost::asio::spawn(io_service_, [this, doc_ptr, socket](boost::asio::yield_context yield)
{
static std::map<socket_ptr, std::queue<std::shared_ptr<simple_wml::document>>> queues;
queues[socket].emplace(doc_ptr);
if(queues[socket].size() > 1) {
return;
}
while(queues[socket].size() > 0) {
coro_send_doc(socket, *(queues[socket].front()), yield);
queues[socket].pop();
}
queues.erase(socket);
});
}
void server_base::async_send_error(socket_ptr socket, const std::string& msg, const char* error_code, const info_table& info)
{ {
simple_wml::document doc; simple_wml::document doc;
doc.root().add_child("error").set_attr_dup("message", msg.c_str()); doc.root().add_child("error").set_attr_dup("message", msg.c_str());
@ -269,7 +458,7 @@ void async_send_error(socket_ptr socket, const std::string& msg, const char* err
async_send_doc_queued(socket, doc); async_send_doc_queued(socket, doc);
} }
void async_send_warning(socket_ptr socket, const std::string& msg, const char* warning_code, const info_table& info) void server_base::async_send_warning(socket_ptr socket, const std::string& msg, const char* warning_code, const info_table& info)
{ {
simple_wml::document doc; simple_wml::document doc;
doc.root().add_child("warning").set_attr_dup("message", msg.c_str()); doc.root().add_child("warning").set_attr_dup("message", msg.c_str());

View file

@ -22,6 +22,10 @@
#include "exceptions.hpp" #include "exceptions.hpp"
#include "server/common/simple_wml.hpp" #include "server/common/simple_wml.hpp"
#ifdef _WIN32
#include "serialization/unicode_cast.hpp"
#endif
#include <boost/asio/io_service.hpp> #include <boost/asio/io_service.hpp>
#include <boost/asio/ip/tcp.hpp> #include <boost/asio/ip/tcp.hpp>
#ifndef _WIN32 #ifndef _WIN32
@ -29,6 +33,7 @@
#endif #endif
#include <boost/asio/signal_set.hpp> #include <boost/asio/signal_set.hpp>
#include <boost/asio/streambuf.hpp> #include <boost/asio/streambuf.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/shared_array.hpp> #include <boost/shared_array.hpp>
#include <map> #include <map>
@ -47,23 +52,40 @@ public:
virtual ~server_base() {} virtual ~server_base() {}
void run(); void run();
/// Send a WML document from within a coroutine
/// @param socket
/// @param doc
/// @param yield The function will suspend on write operation using this yield context
void coro_send_doc(socket_ptr socket, simple_wml::document& doc, boost::asio::yield_context yield);
/// Send contents of entire file directly to socket from within a coroutine
/// @param socket
/// @param filename
/// @param yield The function will suspend on write operations using this yield context
void coro_send_file(socket_ptr socket, const std::string& filename, boost::asio::yield_context yield);
/// Receive WML document from a coroutine
/// @param socket
/// @param yield The function will suspend on read operation using this yield context
std::shared_ptr<simple_wml::document> coro_receive_doc(socket_ptr socket, boost::asio::yield_context yield);
void async_send_doc_queued(socket_ptr socket, simple_wml::document& doc);
typedef std::map<std::string, std::string> info_table;
void async_send_error(socket_ptr socket, const std::string& msg, const char* error_code = "", const info_table& info = {});
void async_send_warning(socket_ptr socket, const std::string& msg, const char* warning_code = "", const info_table& info = {});
protected: protected:
unsigned short port_; unsigned short port_;
bool keep_alive_; bool keep_alive_;
boost::asio::io_service io_service_; boost::asio::io_service io_service_;
boost::asio::ip::tcp::acceptor acceptor_v6_; boost::asio::ip::tcp::acceptor acceptor_v6_;
boost::asio::ip::tcp::acceptor acceptor_v4_; boost::asio::ip::tcp::acceptor acceptor_v4_;
void setup_acceptor(boost::asio::ip::tcp::acceptor& acceptor, boost::asio::ip::tcp::endpoint endpoint);
void start_server(); void start_server();
void serve(boost::asio::ip::tcp::acceptor& acceptor); void serve(boost::asio::yield_context yield, boost::asio::ip::tcp::acceptor& acceptor, boost::asio::ip::tcp::endpoint endpoint);
void accept_connection(boost::asio::ip::tcp::acceptor& acceptor, const boost::system::error_code& error, socket_ptr socket);
union { union {
uint32_t connection_num; uint32_t connection_num;
char buf[4]; char buf[4];
} handshake_response_; } handshake_response_;
void serverside_handshake(socket_ptr socket);
void handle_handshake(const boost::system::error_code& error, socket_ptr socket, boost::shared_array<char> buf);
virtual void handle_new_client(socket_ptr socket) = 0; virtual void handle_new_client(socket_ptr socket) = 0;
@ -87,9 +109,3 @@ protected:
std::string client_address(socket_ptr socket); std::string client_address(socket_ptr socket);
bool check_error(const boost::system::error_code& error, socket_ptr socket); bool check_error(const boost::system::error_code& error, socket_ptr socket);
void async_send_doc_queued(socket_ptr socket, simple_wml::document& doc);
typedef std::map<std::string, std::string> info_table;
void async_send_error(socket_ptr socket, const std::string& msg, const char* error_code = "", const info_table& info = {});
void async_send_warning(socket_ptr socket, const std::string& msg, const char* warning_code = "", const info_table& info = {});

View file

@ -73,15 +73,6 @@ std::vector<TResult> split(const simple_wml::string_span& val, TConvert conv, co
namespace wesnothd namespace wesnothd
{ {
template<typename Container>
void send_to_players(simple_wml::document& data, const Container& players, socket_ptr exclude = socket_ptr())
{
for(const auto& player : players) {
if(player != exclude) {
async_send_doc_queued(player, data);
}
}
}
int game::id_num = 1; int game::id_num = 1;
int game::db_id_num = 1; int game::db_id_num = 1;
@ -92,12 +83,13 @@ void game::missing_user(socket_ptr /*socket*/, const std::string& func) const
<< ") in player_info_ in game:\t\"" << name_ << "\" (" << id_ << ", " << db_id_ << ")\n"; << ") in player_info_ in game:\t\"" << name_ << "\" (" << id_ << ", " << db_id_ << ")\n";
} }
game::game(player_connections& player_connections, game::game(wesnothd::server& server, player_connections& player_connections,
const socket_ptr& host, const socket_ptr& host,
const std::string& name, const std::string& name,
bool save_replays, bool save_replays,
const std::string& replay_save_path) const std::string& replay_save_path)
: player_connections_(player_connections) : server(server)
, player_connections_(player_connections)
, id_(id_num++) , id_(id_num++)
, db_id_(db_id_num++) , db_id_(db_id_num++)
, name_(name) , name_(name)
@ -391,7 +383,7 @@ bool game::send_taken_side(simple_wml::document& cfg, const simple_wml::node* si
cfg.root().set_attr_dup("side", (*side)["side"]); cfg.root().set_attr_dup("side", (*side)["side"]);
// Tell the host which side the new player should take. // Tell the host which side the new player should take.
async_send_doc_queued(owner_, cfg); server.async_send_doc_queued(owner_, cfg);
return true; return true;
} }
@ -652,7 +644,7 @@ void game::change_controller(
// side_drop already.) // side_drop already.)
if(!player_left) { if(!player_left) {
response->root().child("change_controller")->set_attr("is_local", "yes"); response->root().child("change_controller")->set_attr("is_local", "yes");
async_send_doc_queued(sock, *response.get()); server.async_send_doc_queued(sock, *response.get());
} }
} }
@ -680,7 +672,7 @@ void game::notify_new_host()
cfg.root().add_child("host_transfer"); cfg.root().add_child("host_transfer");
std::string message = owner_name + " has been chosen as the new host."; std::string message = owner_name + " has been chosen as the new host.";
async_send_doc_queued(owner_, cfg); server.async_send_doc_queued(owner_, cfg);
send_and_record_server_message(message); send_and_record_server_message(message);
} }
@ -822,7 +814,7 @@ void game::unmute_observer(const simple_wml::node& unmute, const socket_ptr& unm
void game::send_leave_game(const socket_ptr& user) const void game::send_leave_game(const socket_ptr& user) const
{ {
static simple_wml::document leave_game("[leave_game]\n[/leave_game]\n", simple_wml::INIT_COMPRESSED); static simple_wml::document leave_game("[leave_game]\n[/leave_game]\n", simple_wml::INIT_COMPRESSED);
async_send_doc_queued(user, leave_game); server.async_send_doc_queued(user, leave_game);
} }
socket_ptr game::kick_member(const simple_wml::node& kick, const socket_ptr& kicker) socket_ptr game::kick_member(const simple_wml::node& kick, const socket_ptr& kicker)
@ -1242,7 +1234,7 @@ void game::handle_controller_choice(const simple_wml::node& req)
// Calling send_to_one to 0 connect causes the package to be sent to all clients. // Calling send_to_one to 0 connect causes the package to be sent to all clients.
if(sides_[side_index] != 0) { if(sides_[side_index] != 0) {
async_send_doc_queued(sides_[side_index], *mdata); server.async_send_doc_queued(sides_[side_index], *mdata);
} }
change_controller_wml.set_attr("is_local", "no"); change_controller_wml.set_attr("is_local", "no");
@ -1454,12 +1446,12 @@ bool game::add_player(const socket_ptr& player, bool observer)
DBG_GAME << debug_player_info(); DBG_GAME << debug_player_info();
// Send the user the game data. // Send the user the game data.
async_send_doc_queued(player, level_); server.async_send_doc_queued(player, level_);
if(started_) { if(started_) {
// Tell this player that the game has started // Tell this player that the game has started
static simple_wml::document start_game_doc("[start_game]\n[/start_game]\n", simple_wml::INIT_COMPRESSED); static simple_wml::document start_game_doc("[start_game]\n[/start_game]\n", simple_wml::INIT_COMPRESSED);
async_send_doc_queued(player, start_game_doc); server.async_send_doc_queued(player, start_game_doc);
// Send observer join of all the observers in the game to the new player // Send observer join of all the observers in the game to the new player
// only once the game started. The client forgets about it anyway otherwise. // only once the game started. The client forgets about it anyway otherwise.
@ -1587,7 +1579,7 @@ bool game::remove_player(const socket_ptr& player, const bool disconnect, const
DBG_GAME << "*** sending side drop: \n" << drop.output() << std::endl; DBG_GAME << "*** sending side drop: \n" << drop.output() << std::endl;
async_send_doc_queued(owner_, drop); server.async_send_doc_queued(owner_, drop);
} }
if(ai_transfer) { if(ai_transfer) {
@ -1600,7 +1592,7 @@ bool game::remove_player(const socket_ptr& player, const bool disconnect, const
return false; return false;
} }
void game::send_user_list(const socket_ptr& exclude) const void game::send_user_list(const socket_ptr& exclude)
{ {
// If the game hasn't started yet, then send all players a list of the users in the game. // If the game hasn't started yet, then send all players a list of the users in the game.
if(started_ /*|| description_ == nullptr*/) { if(started_ /*|| description_ == nullptr*/) {
@ -1679,8 +1671,8 @@ void game::load_next_scenario(const socket_ptr& user)
cfg_controller.set_attr("is_local", side_user == user ? "yes" : "no"); cfg_controller.set_attr("is_local", side_user == user ? "yes" : "no");
} }
async_send_doc_queued(user, cfg_scenario); server.async_send_doc_queued(user, cfg_scenario);
async_send_doc_queued(user, doc_controllers); server.async_send_doc_queued(user, doc_controllers);
players_not_advanced_.erase(user); players_not_advanced_.erase(user);
@ -1691,7 +1683,17 @@ void game::load_next_scenario(const socket_ptr& user)
send_observerjoins(user); send_observerjoins(user);
} }
void game::send_data(simple_wml::document& data, const socket_ptr& exclude, std::string /*packet_type*/) const template<typename Container>
void game::send_to_players(simple_wml::document& data, const Container& players, socket_ptr exclude)
{
for(const auto& player : players) {
if(player != exclude) {
server.async_send_doc_queued(player, data);
}
}
}
void game::send_data(simple_wml::document& data, const socket_ptr& exclude, std::string /*packet_type*/)
{ {
send_to_players(data, all_game_users(), exclude); send_to_players(data, all_game_users(), exclude);
} }
@ -1719,7 +1721,7 @@ struct controls_side_helper
void game::send_data_sides(simple_wml::document& data, void game::send_data_sides(simple_wml::document& data,
const simple_wml::string_span& sides, const simple_wml::string_span& sides,
const socket_ptr& exclude, const socket_ptr& exclude,
std::string /*packet_type*/) const std::string /*packet_type*/)
{ {
std::vector<int> sides_vec = ::split<int>(sides, ::split_conv_impl()); std::vector<int> sides_vec = ::split<int>(sides, ::split_conv_impl());
@ -1765,7 +1767,7 @@ std::string game::has_same_ip(const socket_ptr& user) const
return clones; return clones;
} }
void game::send_observerjoins(const socket_ptr& sock) const void game::send_observerjoins(const socket_ptr& sock)
{ {
for(const socket_ptr& ob : observers_) { for(const socket_ptr& ob : observers_) {
if(ob == sock) { if(ob == sock) {
@ -1780,12 +1782,12 @@ void game::send_observerjoins(const socket_ptr& sock) const
send_data(cfg, ob); send_data(cfg, ob);
} else { } else {
// Send to the (new) user. // Send to the (new) user.
async_send_doc_queued(sock, cfg); server.async_send_doc_queued(sock, cfg);
} }
} }
} }
void game::send_observerquit(const socket_ptr& observer) const void game::send_observerquit(const socket_ptr& observer)
{ {
simple_wml::document observer_quit; simple_wml::document observer_quit;
@ -1815,7 +1817,7 @@ void game::send_history(const socket_ptr& socket) const
simple_wml::document* doc = new simple_wml::document(buf.c_str(), simple_wml::INIT_STATIC); simple_wml::document* doc = new simple_wml::document(buf.c_str(), simple_wml::INIT_STATIC);
doc->compress(); doc->compress();
async_send_doc_queued(socket, *doc); server.async_send_doc_queued(socket, *doc);
history_.clear(); history_.clear();
history_.push_back(doc); history_.push_back(doc);
@ -2030,7 +2032,7 @@ void game::send_and_record_server_message(const char* message, const socket_ptr&
} }
} }
void game::send_server_message_to_all(const char* message, const socket_ptr& exclude) const void game::send_server_message_to_all(const char* message, const socket_ptr& exclude)
{ {
simple_wml::document doc; simple_wml::document doc;
send_server_message(message, socket_ptr(), &doc); send_server_message(message, socket_ptr(), &doc);
@ -2062,7 +2064,7 @@ void game::send_server_message(const char* message, const socket_ptr& sock, simp
} }
if(sock) { if(sock) {
async_send_doc_queued(sock, doc); server.async_send_doc_queued(sock, doc);
} }
} }

View file

@ -31,6 +31,7 @@ namespace wesnothd
{ {
typedef std::vector<socket_ptr> user_vector; typedef std::vector<socket_ptr> user_vector;
typedef std::vector<socket_ptr> side_vector; typedef std::vector<socket_ptr> side_vector;
class server;
class game class game
{ {
@ -41,7 +42,7 @@ public:
(EMPTY, "null") (EMPTY, "null")
) )
game(player_connections& player_connections, game(wesnothd::server& server, player_connections& player_connections,
const socket_ptr& host, const socket_ptr& host,
const std::string& name = "", const std::string& name = "",
bool save_replays = false, bool save_replays = false,
@ -243,8 +244,8 @@ public:
*/ */
bool describe_slots(); bool describe_slots();
void send_server_message_to_all(const char* message, const socket_ptr& exclude = socket_ptr()) const; void send_server_message_to_all(const char* message, const socket_ptr& exclude = socket_ptr());
void send_server_message_to_all(const std::string& message, const socket_ptr& exclude = socket_ptr()) const void send_server_message_to_all(const std::string& message, const socket_ptr& exclude = socket_ptr())
{ {
send_server_message_to_all(message.c_str(), exclude); send_server_message_to_all(message.c_str(), exclude);
} }
@ -264,8 +265,9 @@ public:
send_and_record_server_message(message.c_str(), exclude); send_and_record_server_message(message.c_str(), exclude);
} }
void send_data( template<typename Container>
simple_wml::document& data, const socket_ptr& exclude = socket_ptr(), std::string packet_type = "") const; void send_to_players(simple_wml::document& data, const Container& players, socket_ptr exclude = socket_ptr());
void send_data(simple_wml::document& data, const socket_ptr& exclude = socket_ptr(), std::string packet_type = "");
void clear_history(); void clear_history();
void record_data(simple_wml::document* data); void record_data(simple_wml::document* data);
@ -399,7 +401,7 @@ private:
void send_data_sides(simple_wml::document& data, void send_data_sides(simple_wml::document& data,
const simple_wml::string_span& sides, const simple_wml::string_span& sides,
const socket_ptr& exclude = socket_ptr(), const socket_ptr& exclude = socket_ptr(),
std::string packet_type = "") const; std::string packet_type = "");
void send_data_observers( void send_data_observers(
simple_wml::document& data, const socket_ptr& exclude = socket_ptr(), std::string packet_type = "") const; simple_wml::document& data, const socket_ptr& exclude = socket_ptr(), std::string packet_type = "") const;
@ -408,8 +410,8 @@ private:
* Send [observer] tags of all the observers in the game to the user or * Send [observer] tags of all the observers in the game to the user or
* everyone if none given. * everyone if none given.
*/ */
void send_observerjoins(const socket_ptr& sock = socket_ptr()) const; void send_observerjoins(const socket_ptr& sock = socket_ptr());
void send_observerquit(const socket_ptr& observer) const; void send_observerquit(const socket_ptr& observer);
void send_history(const socket_ptr& sock) const; void send_history(const socket_ptr& sock) const;
/** In case of a host transfer, notify the new host about its status. */ /** In case of a host transfer, notify the new host about its status. */
@ -450,7 +452,7 @@ private:
* *
* Only sends data if the game is initialized but not yet started. * Only sends data if the game is initialized but not yet started.
*/ */
void send_user_list(const socket_ptr& exclude = socket_ptr()) const; void send_user_list(const socket_ptr& exclude = socket_ptr());
/** Returns the name of the user or "(unfound)". */ /** Returns the name of the user or "(unfound)". */
std::string username(const socket_ptr& pl) const; std::string username(const socket_ptr& pl) const;
@ -470,6 +472,7 @@ private:
/** Helps debugging controller tweaks. */ /** Helps debugging controller tweaks. */
std::string debug_sides_info() const; std::string debug_sides_info() const;
wesnothd::server& server;
player_connections& player_connections_; player_connections& player_connections_;
// used for unique identification of game instances within wesnothd // used for unique identification of game instances within wesnothd

View file

@ -46,6 +46,7 @@
#endif #endif
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/scope_exit.hpp>
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
@ -78,8 +79,6 @@ static lg::log_domain log_config("config");
#define ERR_CONFIG LOG_STREAM(err, log_config) #define ERR_CONFIG LOG_STREAM(err, log_config)
#define WRN_CONFIG LOG_STREAM(warn, log_config) #define WRN_CONFIG LOG_STREAM(warn, log_config)
#include "server/common/send_receive_wml_helpers.ipp"
namespace wesnothd namespace wesnothd
{ {
// we take profiling info on every n requests // we take profiling info on every n requests
@ -608,88 +607,151 @@ void server::refresh_tournaments(const boost::system::error_code& ec)
void server::handle_new_client(socket_ptr socket) void server::handle_new_client(socket_ptr socket)
{ {
async_send_doc(socket, version_query_response_, std::bind(&server::handle_version, this, std::placeholders::_1)); boost::asio::spawn(io_service_, [socket, this](boost::asio::yield_context yield) { login_client(yield, socket); });
} }
void server::handle_version(socket_ptr socket) void server::login_client(boost::asio::yield_context yield, socket_ptr socket)
{ {
async_receive_doc(socket, std::bind(&server::read_version, this, std::placeholders::_1, std::placeholders::_2)); boost::system::error_code ec;
}
void server::read_version(socket_ptr socket, std::shared_ptr<simple_wml::document> doc) coro_send_doc(socket, version_query_response_, yield[ec]);
{ if(check_error(ec, socket)) return;
auto doc { coro_receive_doc(socket, yield[ec]) };
if(check_error(ec, socket) || !doc) return;
std::string client_version, client_source;
if(const simple_wml::node* const version = doc->child("version")) { if(const simple_wml::node* const version = doc->child("version")) {
const simple_wml::string_span& version_str_span = (*version)["version"]; const simple_wml::string_span& version_str_span = (*version)["version"];
const std::string version_str(version_str_span.begin(), version_str_span.end()); client_version = std::string { version_str_span.begin(), version_str_span.end() };
const simple_wml::string_span& source_str_span = (*version)["client_source"]; const simple_wml::string_span& source_str_span = (*version)["client_source"];
const std::string source_str(source_str_span.begin(), source_str_span.end()); client_source = std::string { source_str_span.begin(), source_str_span.end() };
// Check if it is an accepted version. // Check if it is an accepted version.
auto accepted_it = std::find_if(accepted_versions_.begin(), accepted_versions_.end(), auto accepted_it = std::find_if(accepted_versions_.begin(), accepted_versions_.end(),
std::bind(&utils::wildcard_string_match, version_str, std::placeholders::_1)); std::bind(&utils::wildcard_string_match, client_version, std::placeholders::_1));
if(accepted_it != accepted_versions_.end()) { if(accepted_it != accepted_versions_.end()) {
LOG_SERVER << client_address(socket) << "\tplayer joined using accepted version " << version_str LOG_SERVER << client_address(socket) << "\tplayer joined using accepted version " << client_version
<< ":\ttelling them to log in.\n"; << ":\ttelling them to log in.\n";
async_send_doc(socket, login_response_, std::bind(&server::login, this, std::placeholders::_1, version_str, source_str)); coro_send_doc(socket, login_response_, yield[ec]);
return; if(check_error(ec, socket)) return;
} } else {
simple_wml::document response;
simple_wml::document response; // Check if it is a redirected version
for(const auto& redirect_version : redirected_versions_) {
// Check if it is a redirected version if(utils::wildcard_string_match(client_version, redirect_version.first)) {
for(const auto& redirect_version : redirected_versions_) { LOG_SERVER << client_address(socket) << "\tplayer joined using version " << client_version
if(utils::wildcard_string_match(version_str, redirect_version.first)) {
LOG_SERVER << client_address(socket) << "\tplayer joined using version " << version_str
<< ":\tredirecting them to " << redirect_version.second["host"] << ":" << ":\tredirecting them to " << redirect_version.second["host"] << ":"
<< redirect_version.second["port"] << "\n"; << redirect_version.second["port"] << "\n";
simple_wml::node& redirect = response.root().add_child("redirect"); simple_wml::node& redirect = response.root().add_child("redirect");
for(const auto& attr : redirect_version.second.attribute_range()) { for(const auto& attr : redirect_version.second.attribute_range()) {
redirect.set_attr_dup(attr.first.c_str(), attr.second.str().c_str()); redirect.set_attr_dup(attr.first.c_str(), attr.second.str().c_str());
}
async_send_doc_queued(socket, response);
return;
} }
async_send_doc_queued(socket, response);
return;
} }
}
LOG_SERVER << client_address(socket) << "\tplayer joined using unknown version " << version_str LOG_SERVER << client_address(socket) << "\tplayer joined using unknown version " << client_version
<< ":\trejecting them\n"; << ":\trejecting them\n";
// For compatibility with older clients // For compatibility with older clients
response.set_attr("version", accepted_versions_.begin()->c_str()); response.set_attr("version", accepted_versions_.begin()->c_str());
simple_wml::node& reject = response.root().add_child("reject"); simple_wml::node& reject = response.root().add_child("reject");
reject.set_attr_dup("accepted_versions", utils::join(accepted_versions_).c_str()); reject.set_attr_dup("accepted_versions", utils::join(accepted_versions_).c_str());
async_send_doc_queued(socket, response); async_send_doc_queued(socket, response);
} else { return;
LOG_SERVER << client_address(socket) << "\tclient didn't send its version: rejecting\n";
}
}
void server::login(socket_ptr socket, std::string version, std::string source)
{
async_receive_doc(socket, std::bind(&server::handle_login, this, std::placeholders::_1, std::placeholders::_2, version, source));
}
void server::handle_login(socket_ptr socket, std::shared_ptr<simple_wml::document> doc, std::string version, std::string source)
{
if(const simple_wml::node* const login = doc->child("login")) {
if(!is_login_allowed(socket, login, version, source)) {
server::login(socket, version, source); // keep reading logins from client until we get a successful one
} }
} else { } else {
LOG_SERVER << client_address(socket) << "\tclient didn't send its version: rejecting\n";
return;
}
std::string username;
bool registered, is_moderator;
while(true) {
auto login_response { coro_receive_doc(socket, yield[ec]) };
if(check_error(ec, socket) || !login_response) return;
if(const simple_wml::node* const login = login_response->child("login")) {
username = (*login)["username"].to_string();
if(is_login_allowed(socket, login, username, registered, is_moderator)) {
break;
} else continue;
}
async_send_error(socket, "You must login first.", MP_MUST_LOGIN); async_send_error(socket, "You must login first.", MP_MUST_LOGIN);
} }
simple_wml::document join_lobby_response;
join_lobby_response.root().add_child("join_lobby").set_attr("is_moderator", is_moderator ? "yes" : "no");
join_lobby_response.root().child("join_lobby")->set_attr_dup("profile_url_prefix", "https://r.wesnoth.org/u");
coro_send_doc(socket, join_lobby_response, yield[ec]);
if(check_error(ec, socket)) return;
simple_wml::node& player_cfg = games_and_users_list_.root().add_child("user");
wesnothd::player new_player(
username,
player_cfg,
user_handler_ ? user_handler_->get_forum_id(username) : 0,
registered,
client_version,
client_source,
default_max_messages_,
default_time_period_,
is_moderator
);
boost::asio::spawn(io_service_,
[this, socket, new_player](boost::asio::yield_context yield) { handle_player(yield, socket, new_player); }
);
LOG_SERVER << client_address(socket) << "\t" << username << "\thas logged on"
<< (registered ? " to a registered account" : "") << "\n";
std::shared_ptr<game> last_sent;
for(const auto& record : player_connections_.get<game_t>()) {
auto g_ptr = record.get_game();
if(g_ptr != last_sent) {
// Note: This string is parsed by the client to identify lobby join messages!
g_ptr->send_server_message_to_all(username + " has logged into the lobby");
last_sent = g_ptr;
}
}
if(is_moderator) {
LOG_SERVER << "Admin automatically recognized: IP: " << client_address(socket) << "\tnick: " << username
<< std::endl;
// This string is parsed by the client!
send_server_message(socket,
"You are now recognized as an administrator. "
"If you no longer want to be automatically authenticated use '/query signout'.", "alert");
}
// Log the IP
connection_log ip_name = connection_log(username, client_address(socket), 0);
if(std::find(ip_log_.begin(), ip_log_.end(), ip_name) == ip_log_.end()) {
ip_log_.push_back(ip_name);
// Remove the oldest entry if the size of the IP log exceeds the maximum size
if(ip_log_.size() > max_ip_log_size_) {
ip_log_.pop_front();
}
}
} }
bool server::is_login_allowed(socket_ptr socket, const simple_wml::node* const login, const std::string& version, const std::string& source) bool server::is_login_allowed(socket_ptr socket, const simple_wml::node* const login, const std::string& username, bool& registered, bool& is_moderator)
{ {
// Check if the username is valid (all alpha-numeric plus underscore and hyphen) // Check if the username is valid (all alpha-numeric plus underscore and hyphen)
std::string username = (*login)["username"].to_string();
if(!utils::isvalid_username(username)) { if(!utils::isvalid_username(username)) {
async_send_error(socket, async_send_error(socket,
"The nickname '" + username + "' contains invalid " "The nickname '" + username + "' contains invalid "
@ -723,10 +785,8 @@ bool server::is_login_allowed(socket_ptr socket, const simple_wml::node* const l
// Check for password // Check for password
bool registered; if(!authenticate(socket, username, (*login)["password"].to_string(), name_taken, registered))
if(!authenticate(socket, username, (*login)["password"].to_string(), version, source, name_taken, registered)) return false;
return true; // it's a failed login but we don't want to call server::login again
// because send_password_request() will handle the next network write and read instead
// If we disallow unregistered users and this user is not registered send an error // If we disallow unregistered users and this user is not registered send an error
if(user_handler_ && !registered && deny_unregistered_login_) { if(user_handler_ && !registered && deny_unregistered_login_) {
@ -738,7 +798,7 @@ bool server::is_login_allowed(socket_ptr socket, const simple_wml::node* const l
return false; return false;
} }
const bool is_moderator = user_handler_ && user_handler_->user_is_moderator(username); is_moderator = user_handler_ && user_handler_->user_is_moderator(username);
user_handler::ban_info auth_ban; user_handler::ban_info auth_ban;
if(user_handler_) { if(user_handler_) {
@ -802,60 +862,15 @@ bool server::is_login_allowed(socket_ptr socket, const simple_wml::node* const l
} }
} }
simple_wml::document join_lobby_response;
join_lobby_response.root().add_child("join_lobby").set_attr("is_moderator", is_moderator ? "yes" : "no");
join_lobby_response.root().child("join_lobby")->set_attr_dup("profile_url_prefix", "https://r.wesnoth.org/u");
async_send_doc(socket, join_lobby_response, [this, username, registered, version, source](socket_ptr socket) {
simple_wml::node& player_cfg = games_and_users_list_.root().add_child("user");
add_player(socket, wesnothd::player(
username,
player_cfg,
user_handler_ ? user_handler_->get_forum_id(username) : 0,
registered,
version,
source,
default_max_messages_,
default_time_period_,
user_handler_ && user_handler_->user_is_moderator(username)
)
);
});
LOG_SERVER << client_address(socket) << "\t" << username << "\thas logged on"
<< (registered ? " to a registered account" : "") << "\n";
std::shared_ptr<game> last_sent;
for(const auto& record : player_connections_.get<game_t>()) {
auto g_ptr = record.get_game();
if(g_ptr != last_sent) {
// Note: This string is parsed by the client to identify lobby join messages!
g_ptr->send_server_message_to_all(username + " has logged into the lobby");
last_sent = g_ptr;
}
}
if(auth_ban.type) { if(auth_ban.type) {
send_server_message(socket, "You are currently banned by the forum administration.", "alert"); send_server_message(socket, "You are currently banned by the forum administration.", "alert");
} }
// Log the IP
connection_log ip_name = connection_log(username, client_address(socket), 0);
if(std::find(ip_log_.begin(), ip_log_.end(), ip_name) == ip_log_.end()) {
ip_log_.push_back(ip_name);
// Remove the oldest entry if the size of the IP log exceeds the maximum size
if(ip_log_.size() > max_ip_log_size_) {
ip_log_.pop_front();
}
}
return true; return true;
} }
bool server::authenticate( bool server::authenticate(
socket_ptr socket, const std::string& username, const std::string& password, const std::string& version, const std::string& source, bool name_taken, bool& registered) socket_ptr socket, const std::string& username, const std::string& password, bool name_taken, bool& registered)
{ {
// Current login procedure for registered nicks is: // Current login procedure for registered nicks is:
// - Client asks to log in with a particular nick // - Client asks to log in with a particular nick
@ -883,13 +898,13 @@ bool server::authenticate(
if(password.empty()) { if(password.empty()) {
if(!name_taken) { if(!name_taken) {
send_password_request(socket, "The nickname '" + username + "' is registered on this server.", send_password_request(socket, "The nickname '" + username + "' is registered on this server.",
username, version, source, MP_PASSWORD_REQUEST); username, MP_PASSWORD_REQUEST);
} else { } else {
send_password_request(socket, send_password_request(socket,
"The nickname '" + username + "' is registered on this server." "The nickname '" + username + "' is registered on this server."
"\n\nWARNING: There is already a client using this username, " "\n\nWARNING: There is already a client using this username, "
"logging in will cause that client to be kicked!", "logging in will cause that client to be kicked!",
username, version, source, MP_PASSWORD_REQUEST_FOR_LOGGED_IN_NAME, true username, MP_PASSWORD_REQUEST_FOR_LOGGED_IN_NAME, true
); );
} }
@ -899,7 +914,7 @@ bool server::authenticate(
// A password (or hashed password) was provided, however // A password (or hashed password) was provided, however
// there is no seed // there is no seed
if(seeds_[socket.get()].empty()) { if(seeds_[socket.get()].empty()) {
send_password_request(socket, "Please try again.", username, version, source, MP_NO_SEED_ERROR); send_password_request(socket, "Please try again.", username, MP_NO_SEED_ERROR);
return false; return false;
} }
@ -939,7 +954,7 @@ bool server::authenticate(
async_send_error(socket, "You have made too many failed login attempts.", MP_TOO_MANY_ATTEMPTS_ERROR); async_send_error(socket, "You have made too many failed login attempts.", MP_TOO_MANY_ATTEMPTS_ERROR);
} else { } else {
send_password_request(socket, send_password_request(socket,
"The password you provided for the nickname '" + username + "' was incorrect.", username,version,source, "The password you provided for the nickname '" + username + "' was incorrect.", username,
MP_INCORRECT_PASSWORD_ERROR); MP_INCORRECT_PASSWORD_ERROR);
} }
@ -964,8 +979,6 @@ bool server::authenticate(
void server::send_password_request(socket_ptr socket, void server::send_password_request(socket_ptr socket,
const std::string& msg, const std::string& msg,
const std::string& user, const std::string& user,
const std::string& version,
const std::string& source,
const char* error_code, const char* error_code,
bool force_confirmation) bool force_confirmation)
{ {
@ -984,7 +997,6 @@ void server::send_password_request(socket_ptr socket,
"cannot log in due to an error in the hashing algorithm. " "cannot log in due to an error in the hashing algorithm. "
"Logging into your forum account on https://forums.wesnoth.org " "Logging into your forum account on https://forums.wesnoth.org "
"may fix this problem."); "may fix this problem.");
login(socket, version, source);
return; return;
} }
@ -1002,10 +1014,10 @@ void server::send_password_request(socket_ptr socket,
e.set_attr("error_code", error_code); e.set_attr("error_code", error_code);
} }
async_send_doc(socket, doc, std::bind(&server::login, this, std::placeholders::_1, version, source)); async_send_doc_queued(socket, doc);
} }
void server::add_player(socket_ptr socket, const wesnothd::player& player) void server::handle_player(boost::asio::yield_context yield, socket_ptr socket, const player& player)
{ {
if(lan_server_) if(lan_server_)
abort_lan_server_timer(); abort_lan_server_timer();
@ -1014,6 +1026,10 @@ void server::add_player(socket_ptr socket, const wesnothd::player& player)
std::tie(std::ignore, inserted) = player_connections_.insert(player_connections::value_type(socket, player)); std::tie(std::ignore, inserted) = player_connections_.insert(player_connections::value_type(socket, player));
assert(inserted); assert(inserted);
BOOST_SCOPE_EXIT_ALL(this, &socket) {
remove_player(socket);
};
async_send_doc_queued(socket, games_and_users_list_); async_send_doc_queued(socket, games_and_users_list_);
if(!motd_.empty()) { if(!motd_.empty()) {
@ -1028,51 +1044,38 @@ void server::add_player(socket_ptr socket, const wesnothd::player& player)
send_server_message(socket, "A newer Wesnoth version, " + recommended_version_ + ", is out!", "alert"); send_server_message(socket, "A newer Wesnoth version, " + recommended_version_ + ", is out!", "alert");
} }
read_from_player(socket);
// Send other players in the lobby the update that the player has joined // Send other players in the lobby the update that the player has joined
simple_wml::document diff; simple_wml::document diff;
make_add_diff(games_and_users_list_.root(), nullptr, "user", diff); make_add_diff(games_and_users_list_.root(), nullptr, "user", diff);
send_to_lobby(diff, socket); send_to_lobby(diff, socket);
}
void server::read_from_player(socket_ptr socket) while(true) {
{ boost::system::error_code ec;
async_receive_doc(socket, auto doc { coro_receive_doc(socket, yield[ec]) };
std::bind(&server::handle_read_from_player, this, std::placeholders::_1, std::placeholders::_2), if(check_error(ec, socket) || !doc) return;
std::bind(&server::remove_player, this, std::placeholders::_1)
);
}
void server::handle_read_from_player(socket_ptr socket, std::shared_ptr<simple_wml::document> doc) // DBG_SERVER << client_address(socket) << "\tWML received:\n" << doc->output() << std::endl;
{ if(doc->child("refresh_lobby")) {
read_from_player(socket); async_send_doc_queued(socket, games_and_users_list_);
}
// DBG_SERVER << client_address(socket) << "\tWML received:\n" << doc->output() << std::endl; if(simple_wml::node* whisper = doc->child("whisper")) {
if(doc->child("refresh_lobby")) { handle_whisper(socket, *whisper);
async_send_doc_queued(socket, games_and_users_list_); }
return;
}
if(simple_wml::node* whisper = doc->child("whisper")) { if(simple_wml::node* query = doc->child("query")) {
handle_whisper(socket, *whisper); handle_query(socket, *query);
return; }
}
if(simple_wml::node* query = doc->child("query")) { if(simple_wml::node* nickserv = doc->child("nickserv")) {
handle_query(socket, *query); handle_nickserv(socket, *nickserv);
return; }
}
if(simple_wml::node* nickserv = doc->child("nickserv")) { if(!player_is_in_game(socket)) {
handle_nickserv(socket, *nickserv); handle_player_in_lobby(socket, doc);
return; } else {
} handle_player_in_game(socket, doc);
}
if(!player_is_in_game(socket)) {
handle_player_in_lobby(socket, doc);
} else {
handle_player_in_game(socket, doc);
} }
} }
@ -1306,7 +1309,7 @@ void server::create_game(player_record& host_record, simple_wml::node& create_ga
// Create the new game, remove the player from the lobby // Create the new game, remove the player from the lobby
// and set the player as the host/owner. // and set the player as the host/owner.
host_record.get_game().reset( host_record.get_game().reset(
new wesnothd::game(player_connections_, host_record.socket(), game_name, save_replays_, replay_save_path_), new wesnothd::game(*this, player_connections_, host_record.socket(), game_name, save_replays_, replay_save_path_),
std::bind(&server::cleanup_game, this, std::placeholders::_1) std::bind(&server::cleanup_game, this, std::placeholders::_1)
); );
@ -1840,7 +1843,7 @@ void server::handle_player_in_game(socket_ptr socket, std::shared_ptr<simple_wml
<< data.output(); << data.output();
} }
void send_server_message(socket_ptr socket, const std::string& message, const std::string& type) void server::send_server_message(socket_ptr socket, const std::string& message, const std::string& type)
{ {
simple_wml::document server_message; simple_wml::document server_message;
simple_wml::node& msg = server_message.root().add_child("message"); simple_wml::node& msg = server_message.root().add_child("message");
@ -1905,7 +1908,7 @@ void server::remove_player(socket_ptr socket)
if(game_ended) delete_game(g->id()); if(game_ended) delete_game(g->id());
} }
void server::send_to_lobby(simple_wml::document& data, socket_ptr exclude) const void server::send_to_lobby(simple_wml::document& data, socket_ptr exclude)
{ {
for(const auto& player : player_connections_.get<game_t>().equal_range(0)) { for(const auto& player : player_connections_.get<game_t>().equal_range(0)) {
if(player.socket() != exclude) { if(player.socket() != exclude) {
@ -1914,7 +1917,7 @@ void server::send_to_lobby(simple_wml::document& data, socket_ptr exclude) const
} }
} }
void server::send_server_message_to_lobby(const std::string& message, socket_ptr exclude) const void server::send_server_message_to_lobby(const std::string& message, socket_ptr exclude)
{ {
for(const auto& player : player_connections_.get<game_t>().equal_range(0)) { for(const auto& player : player_connections_.get<game_t>().equal_range(0)) {
if(player.socket() != exclude) { if(player.socket() != exclude) {
@ -1923,7 +1926,7 @@ void server::send_server_message_to_lobby(const std::string& message, socket_ptr
} }
} }
void server::send_server_message_to_all(const std::string& message, socket_ptr exclude) const void server::send_server_message_to_all(const std::string& message, socket_ptr exclude)
{ {
for(const auto& player : player_connections_) { for(const auto& player : player_connections_) {
if(player.socket() != exclude) { if(player.socket() != exclude) {

View file

@ -38,20 +38,14 @@ public:
private: private:
void handle_new_client(socket_ptr socket); void handle_new_client(socket_ptr socket);
void handle_version(socket_ptr socket); void login_client(boost::asio::yield_context yield, socket_ptr socket);
void read_version(socket_ptr socket, std::shared_ptr<simple_wml::document> doc); bool is_login_allowed(socket_ptr socket, const simple_wml::node* const login, const std::string& username, bool& registered, bool& is_moderator);
bool authenticate(socket_ptr socket, const std::string& username, const std::string& password, bool name_taken, bool& registered);
void login(socket_ptr socket, std::string version, std::string source);
void handle_login(socket_ptr socket, std::shared_ptr<simple_wml::document> doc, std::string version, std::string source);
bool is_login_allowed(socket_ptr socket, const simple_wml::node* const login, const std::string& version, const std::string& source);
bool authenticate(socket_ptr socket, const std::string& username, const std::string& password, const std::string& version, const std::string& source, bool name_taken, bool& registered);
void send_password_request(socket_ptr socket, const std::string& msg, void send_password_request(socket_ptr socket, const std::string& msg,
const std::string& user, const std::string& version, const std::string& source, const char* error_code = "", bool force_confirmation = false); const std::string& user, const char* error_code = "", bool force_confirmation = false);
bool accepting_connections() const { return !graceful_restart; } bool accepting_connections() const { return !graceful_restart; }
void add_player(socket_ptr socket, const wesnothd::player&); void handle_player(boost::asio::yield_context yield, socket_ptr socket, const player& player);
void read_from_player(socket_ptr socket);
void handle_read_from_player(socket_ptr socket, std::shared_ptr<simple_wml::document> doc);
void handle_player_in_lobby(socket_ptr socket, std::shared_ptr<simple_wml::document> doc); void handle_player_in_lobby(socket_ptr socket, std::shared_ptr<simple_wml::document> doc);
void handle_player_in_game(socket_ptr socket, std::shared_ptr<simple_wml::document> doc); void handle_player_in_game(socket_ptr socket, std::shared_ptr<simple_wml::document> doc);
void handle_whisper(socket_ptr socket, simple_wml::node& whisper); void handle_whisper(socket_ptr socket, simple_wml::node& whisper);
@ -64,9 +58,10 @@ private:
void handle_join_game(socket_ptr socket, simple_wml::node& join); void handle_join_game(socket_ptr socket, simple_wml::node& join);
void remove_player(socket_ptr socket); void remove_player(socket_ptr socket);
void send_to_lobby(simple_wml::document& data, socket_ptr exclude = socket_ptr()) const; void send_server_message(socket_ptr socket, const std::string& message, const std::string& type);
void send_server_message_to_lobby(const std::string& message, socket_ptr exclude = socket_ptr()) const; void send_to_lobby(simple_wml::document& data, socket_ptr exclude = socket_ptr());
void send_server_message_to_all(const std::string& message, socket_ptr exclude = socket_ptr()) const; void send_server_message_to_lobby(const std::string& message, socket_ptr exclude = socket_ptr());
void send_server_message_to_all(const std::string& message, socket_ptr exclude = socket_ptr());
bool player_is_in_game(socket_ptr socket) const { bool player_is_in_game(socket_ptr socket) const {
return bool(player_connections_.find(socket)->get_game()); return bool(player_connections_.find(socket)->get_game());
} }
@ -243,6 +238,4 @@ private:
void handle_lan_server_shutdown(const boost::system::error_code& error); void handle_lan_server_shutdown(const boost::system::error_code& error);
}; };
void send_server_message(socket_ptr socket, const std::string& message, const std::string& type);
} }