Mercurial > malikania
changeset 4:c12262a01559
Docs: remove specifications from main repository
Task: #436
author | David Demelier <markand@malikania.fr> |
---|---|
date | Tue, 22 Mar 2016 23:02:56 +0100 |
parents | 67edc8b3e5e9 |
children | 83db85eb4de9 |
files | CMakeLists.txt cmake/MalikaniaFunctions.cmake cmake/MalikaniaOptions.cmake docs/CMakeLists.txt docs/Colors.txt docs/UML/CMakeLists.txt docs/UML/client/CMakeLists.txt docs/UML/client/Rendering.txt docs/UML/client/States.txt docs/UML/database/Accounts.txt docs/UML/database/Builds.txt docs/UML/database/CMakeLists.txt docs/UML/database/Inventory.txt docs/books/CMakeLists.txt docs/books/conventions/CMakeLists.txt docs/books/conventions/Conventions.txt docs/books/developer/CMakeLists.txt docs/books/developer/Developer.txt docs/books/mercurial/CMakeLists.txt docs/books/mercurial/Mercurial.txt docs/books/mercurial/images/Add-context.png docs/books/mercurial/images/Add-window.png docs/books/mercurial/images/Clone.png docs/books/mercurial/images/Commit-context.png docs/books/mercurial/images/Commit-window.png docs/books/mercurial/images/Diff-context.png docs/books/mercurial/images/Diff-window.png docs/books/mercurial/images/Merge-context.png docs/books/mercurial/images/Pull-context.png docs/books/mercurial/images/Pull-window.png docs/books/mercurial/images/Push-context.png docs/books/mercurial/images/Push-window.png docs/books/mercurial/images/Remove-context.png docs/books/mercurial/images/Remove-window.png docs/books/mercurial/images/Update-context.png docs/books/mercurial/images/Update-window.png docs/books/specifications/Architecture/Intro.txt docs/books/specifications/Architecture/Technologies.txt docs/books/specifications/Bundle/Format.txt docs/books/specifications/Bundle/Intro.txt docs/books/specifications/Bundle/Usage.txt docs/books/specifications/CMakeLists.txt docs/books/specifications/GameData/Animations.txt docs/books/specifications/GameData/Game.txt docs/books/specifications/GameData/Intro.txt docs/books/specifications/GameData/Manifest.txt docs/books/specifications/GameData/Sprites.txt docs/books/specifications/GameData/images/format.png docs/books/specifications/Gameplay/Intro.txt docs/books/specifications/Gameplay/Stats.txt docs/books/specifications/Intro.txt docs/books/specifications/Network/Commands.txt docs/books/specifications/Network/Commands/account-create.txt docs/books/specifications/Network/Commands/account-identify.txt docs/books/specifications/Network/Commands/character-create.txt docs/books/specifications/Network/Commands/character-delete.txt docs/books/specifications/Network/Commands/character-list.txt docs/books/specifications/Network/Commands/character-select.txt docs/books/specifications/Network/Commands/exchange-add.txt docs/books/specifications/Network/Commands/exchange-start.txt docs/books/specifications/Network/Intro.txt docs/books/specifications/Network/Messages.txt docs/books/specifications/Network/Messages/server-info.txt docs/books/specifications/Network/Messages/server-message.txt docs/books/specifications/Overview/Intro.txt docs/books/specifications/Tools/Intro.txt docs/books/specifications/Tools/Malikania-bundle.txt docs/books/specifications/Tools/Malikania-vm.txt libcommon/malikania/Socket.cpp libcommon/malikania/SocketAddress.cpp libcommon/malikania/SocketListener.cpp libcommon/malikania/SocketSsl.cpp libcommon/malikania/SocketTcp.cpp libcommon/malikania/Sockets.cpp libcommon/malikania/Sockets.h libserver/CMakeLists.txt libserver/malikania/Server.cpp libserver/malikania/Server.h libserver/malikania/ServerApp.cpp libserver/malikania/ServerApp.h |
diffstat | 80 files changed, 31 insertions(+), 8669 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Tue Mar 22 22:50:35 2016 +0100 +++ b/CMakeLists.txt Tue Mar 22 23:02:56 2016 +0100 @@ -59,6 +59,4 @@ message(" Release flags: ${CMAKE_CXX_FLAGS_RELEASE}") message("") message("Documentation:") -message(" UML diagrams: ${WITH_DOCS_UML_MSG}") message(" Doxygen: ${WITH_DOCS_DOXYGEN_MSG}") -message(" Books: ${WITH_DOCS_BOOKS_MSG}")
--- a/cmake/MalikaniaFunctions.cmake Tue Mar 22 22:50:35 2016 +0100 +++ b/cmake/MalikaniaFunctions.cmake Tue Mar 22 23:02:56 2016 +0100 @@ -21,28 +21,6 @@ # --------------------------------------------------------- # The following macros are available: # -# malikania_generate_book -# ----------------------- -# -# malikania_generate_book( -# name The target name (book-${name}) -# output The output file name -# sources The sources files, kept in order -# ) -# -# Generate a book using pandoc. -# -# malikania_generate_uml -# ---------------------- -# -# malikania_generate_uml( -# NAME The target name -# DIRECTORY Output directory (relative to docs_BINARY_DIR) -# SOURCES The sources files -# ) -# -# Generate a target uml-${NAME} with all ${SOURCES} specified to be processed by plantuml. -# # malikania_define_executable # --------------------------- # @@ -183,38 +161,6 @@ apply_flags(${LIB_TARGET} LIB_FLAGS) endfunction() -function(malikania_generate_uml) - set(options "") - set(oneValueArgs NAME DIRECTORY) - set(multiValueArgs SOURCES) - - cmake_parse_arguments(UML "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - foreach (f ${UML_SOURCES}) - get_filename_component(filename ${f} NAME_WE) - - list(APPEND outputs ${docs_BINARY_DIR}/uml/${filename}.png) - - add_custom_command( - OUTPUT ${docs_BINARY_DIR}/uml/${filename}.png - DEPENDS ${f} - COMMAND - ${CMAKE_COMMAND} -E make_directory ${docs_BINARY_DIR}/uml - COMMAND - ${Java_JAVA_EXECUTABLE} -jar ${PLANTUML_JAR} ${f} -o ${docs_BINARY_DIR}/uml - VERBATIM - ) - endforeach () - - add_custom_target( - docs-uml-${UML_NAME} - DEPENDS ${outputs} - SOURCES ${UML_SOURCES} - ) - - add_dependencies(docs-uml docs-uml-${UML_NAME}) -endfunction() - function(malikania_create_test) set(singleArgs NAME) set(multiArgs LIBRARIES SOURCES RESOURCES) @@ -273,22 +219,6 @@ add_dependencies(tests test-${TEST_NAME}) endfunction() -function(malikania_generate_book name output sources) - pandoc( - TARGET docs-book-${name} - SOURCES ${sources} - OUTPUT ${docs_BINARY_DIR}/books/${output} - FROM markdown - TO latex - TOC - STANDALONE - MAKE_DIRECTORY - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - ) - - add_dependencies(docs-books docs-book-${name}) -endfunction() - macro(setg var value) set(${var} ${value} CACHE INTERNAL "" FORCE) endmacro()
--- a/cmake/MalikaniaOptions.cmake Tue Mar 22 22:50:35 2016 +0100 +++ b/cmake/MalikaniaOptions.cmake Tue Mar 22 23:02:56 2016 +0100 @@ -33,20 +33,14 @@ # # The following options are available: # WITH_DOCS - Disable or enable all docs, if set to Off, disable all documentation. -# WITH_DOCS_UML - Enable UML diagram generation. -# WITH_DOCS_DOXYGEN - Enable doxygen -# WITH_DOCS_BOOKS - Enable build of books +# WITH_DOXYGEN - Enable doxygen # option(WITH_DOCS "Build all documentation" On) -option(WITH_DOCS_UML "Enable UML diagrams generation" On) -option(WITH_DOCS_DOXYGEN "Enable doxygen generation" On) -option(WITH_DOCS_BOOKS "Enable build of books" On) +option(WITH_DOXYGEN "Enable doxygen generation" On) if (NOT WITH_DOCS) - set(WITH_DOCS_UML Off) - set(WITH_DOCS_DOXYGEN Off) - set(WITH_DOCS_BOOKS Off) + set(WITH_DOXYGEN Off) endif () #
--- a/docs/CMakeLists.txt Tue Mar 22 22:50:35 2016 +0100 +++ b/docs/CMakeLists.txt Tue Mar 22 23:02:56 2016 +0100 @@ -32,67 +32,16 @@ find_package(Doxygen) if (DOXYGEN_FOUND) - if (WITH_DOCS_DOXYGEN) - setg(WITH_DOCS_DOXYGEN_MSG "Yes") + if (WITH_DOXYGEN) + setg(WITH_DOXYGEN_MSG "Yes") else () - setg(WITH_DOCS_DOXYGEN_MSG "No (disabled by user)") + setg(WITH_DOXYGEN_MSG "No (disabled by user)") endif () else () - setg(WITH_DOCS_DOXYGEN_MSG "No (doxygen not found)") - setg(WITH_DOCS_DOXYGEN Off) -endif () - -if (WITH_DOCS_DOXYGEN) - add_subdirectory(doxygen) + setg(WITH_DOXYGEN_MSG "No (doxygen not found)") + setg(WITH_DOXYGEN Off) endif () -# -# UML generation with plantuml. -# ------------------------------------------------------------------- -# - -find_package(Java COMPONENTS Runtime) - -if (Java_JAVA_EXECUTABLE AND DOXYGEN_DOT_EXECUTABLE) - if (WITH_DOCS_UML) - setg(WITH_DOCS_UML_MSG "Yes") - else () - setg(WITH_DOCS_UML_MSG "No (disabled by user)") - endif () -elseif (NOT Java_JAVA_EXECUTABLE) - setg(WITH_DOCS_UML_MSG "No (Java not found)") - setg(WITH_DOCS_UML Off) -elseif (NOT DOXYGEN_DOT_EXECUTABLE) - setg(WITH_DOCS_UML_MSG "No (dot not found)") - setg(WITH_DOCS_UML Off) -endif () - -if (WITH_DOCS_UML) - add_subdirectory(UML) +if (WITH_DOXYGEN) + add_subdirectory(doxygen) endif () - -# -# Books, specifications, docs generation. -# ------------------------------------------------------------------- -# - -find_package(Pandoc) -find_package(LATEX) - -if (Pandoc_FOUND AND (PDFLATEX_COMPILER OR LATEX_COMPILER OR BIBTEX_COMPILER OR MAKEINDEX_COMPILER)) - if (WITH_DOCS_BOOKS) - setg(WITH_DOCS_BOOKS_MSG "Yes") - else () - setg(WITH_DOCS_BOOKS_MSG "No (disabled by user)") - endif () -elseif (NOT Pandoc_FOUND) - setg(WITH_DOCS_BOOKS_MSG "No (pandoc not found)") - setg(WITH_DOCS_BOOKS Off) -elseif (NOT PDFLATEX_COMPILER) - setg(WITH_DOCS_BOOKS_MSG "No (pdflatex not found)") - setg(WITH_DOCS_BOOKS Off) -endif () - -if (WITH_DOCS_BOOKS) - add_subdirectory(books) -endif ()
--- a/docs/Colors.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,39 +0,0 @@ -Official colors scheme for any Malikania art. - -##### Color Palette by Paletton.com -##### Palette URL: http://paletton.com/#uid=7030u0kiZsx93KzekyUn2oOqMk4 - -*** Primary color: - - shade 0 = #E3665C = rgb(227,102, 92) = rgba(227,102, 92,1) = rgb0(0.89,0.4,0.361) - shade 1 = #FFBCB7 = rgb(255,188,183) = rgba(255,188,183,1) = rgb0(1,0.737,0.718) - shade 2 = #FF958D = rgb(255,149,141) = rgba(255,149,141,1) = rgb0(1,0.584,0.553) - shade 3 = #C64237 = rgb(198, 66, 55) = rgba(198, 66, 55,1) = rgb0(0.776,0.259,0.216) - shade 4 = #A0241A = rgb(160, 36, 26) = rgba(160, 36, 26,1) = rgb0(0.627,0.141,0.102) - -*** Secondary color (1): - - shade 0 = #E39D5C = rgb(227,157, 92) = rgba(227,157, 92,1) = rgb0(0.89,0.616,0.361) - shade 1 = #FFD9B7 = rgb(255,217,183) = rgba(255,217,183,1) = rgb0(1,0.851,0.718) - shade 2 = #FFC48D = rgb(255,196,141) = rgba(255,196,141,1) = rgb0(1,0.769,0.553) - shade 3 = #C67C37 = rgb(198,124, 55) = rgba(198,124, 55,1) = rgb0(0.776,0.486,0.216) - shade 4 = #A05A1A = rgb(160, 90, 26) = rgba(160, 90, 26,1) = rgb0(0.627,0.353,0.102) - -*** Secondary color (2): - - shade 0 = #3A818B = rgb( 58,129,139) = rgba( 58,129,139,1) = rgb0(0.227,0.506,0.545) - shade 1 = #94C5CC = rgb(148,197,204) = rgba(148,197,204,1) = rgb0(0.58,0.773,0.8) - shade 2 = #5D9CA5 = rgb( 93,156,165) = rgba( 93,156,165,1) = rgb0(0.365,0.612,0.647) - shade 3 = #236F79 = rgb( 35,111,121) = rgba( 35,111,121,1) = rgb0(0.137,0.435,0.475) - shade 4 = #115862 = rgb( 17, 88, 98) = rgba( 17, 88, 98,1) = rgb0(0.067,0.345,0.384) - -*** Complement color: - - shade 0 = #47AE59 = rgb( 71,174, 89) = rgba( 71,174, 89,1) = rgb0(0.278,0.682,0.349) - shade 1 = #A1E0AC = rgb(161,224,172) = rgba(161,224,172,1) = rgb0(0.631,0.878,0.675) - shade 2 = #6FC97E = rgb(111,201,126) = rgba(111,201,126,1) = rgb0(0.435,0.788,0.494) - shade 3 = #2A973D = rgb( 42,151, 61) = rgba( 42,151, 61,1) = rgb0(0.165,0.592,0.239) - shade 4 = #147A26 = rgb( 20,122, 38) = rgba( 20,122, 38,1) = rgb0(0.078,0.478,0.149) - - -##### Generated by Paletton.com (c) 2002-2014
--- a/docs/UML/CMakeLists.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -# -# CMakeLists.txt -- CMake build system for malikania -# -# Copyright (c) 2013, 2014, 2015 Malikania Authors -# -# Permission to use, copy, modify, and/or distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -# - -project(UML) - -add_custom_target(docs-uml COMMENT "UML diagram generation") - -add_dependencies(docs docs-uml) - -add_subdirectory(database) -add_subdirectory(client)
--- a/docs/UML/client/CMakeLists.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -# -# CMakeLists.txt -- CMake build system for malikania -# -# Copyright (c) 2013, 2014, 2015 Malikania Authors -# -# Permission to use, copy, modify, and/or distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -# - -project(client) - -set( - FILES - ${client_SOURCE_DIR}/States.txt - ${client_SOURCE_DIR}/Rendering.txt -) - -malikania_generate_uml( - NAME client - DIRECTORY client - SOURCES ${FILES} -) -
--- a/docs/UML/client/Rendering.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,85 +0,0 @@ -' -' Rendering.txt -- rendering -' -' Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr> -' -' Permission to use, copy, modify, and/or distribute this software for any -' purpose with or without fee is hereby granted, provided that the above -' copyright notice and this permission notice appear in all copies. -' -' THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -' WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -' MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -' ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -' WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -' ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -' OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -' - -@startuml - -class Screen { - +Screen(width, height, title) - +setFullscreen(mode: bool) - +width() const: int - +height() const: int - +isOpen() const: bool -} - -class Renderer { - +drawPoint(point, color) - +drawPoints(points, color) - +drawLine(line, color) - +drawLines(lines, color) - +drawCircle(circle, color) - +drawCircles(circles, color) - +clear() - +present() -} - -class Image { - +Image(path) - +width() const - +height() const - +draw(x: int, y: int) -} - -class Sprite { - +Sprite(path) - +width() const: int - +height() const: int - +spacingX() const: int - +spacingY() const: int - +marginX() const: int - +marginY() const: int - +cellWidth() const: int - +cellHeight() const: int - +cellCount() const: int - +draw(index: int) -} - -class Animation { - +Animation(path) - +sprite(): const Sprite & -} - -class Animator { - +Animator(sprite: const Sprite &) - +update() - +draw(x: int, y: int) -} - -Animation -- "1" Sprite: m_sprite -Animator -- "1" Animation: m_animation -Sprite -- "1" Image: m_image - -note right of Image : Each implementation has its appropriate source file, for example Image.h and Image_android.cpp, Image_sdl.cpp. - -skinparam defaultFontName DejaVu Sans Mono -skinparam defaultFontSize 10 - -skinparam classBorderColor #236F79 -skinparam classBackgroundColor #5D9CA5 -skinparam classArrowColor #236F79 - -@enduml
--- a/docs/UML/client/States.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -' -' States.txt -- different default client states -' -' Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr> -' -' Permission to use, copy, modify, and/or distribute this software for any -' purpose with or without fee is hereby granted, provided that the above -' copyright notice and this permission notice appear in all copies. -' -' THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -' WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -' MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -' ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -' WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -' ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -' OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -' - -@startuml - -[*] --> Loading -Loading --> Login -Login --> Connecting -Connecting --> Downloading -Connecting --> LoadingMap -LoadingMap --> Running -Downloading --> Running -Running --> LoadingBattle -LoadingBattle --> Battle -Battle --> EndBattle - -Loading : Loading game resources -Login : Waiting for user input for connecting -Connecting : Waiting for connection to the server -LoadingMap : Loading the map -Downloading : Downloading updates locally -Running : In game map -LoadingBattle : State before a battle starts (useful for animations) -Battle : In game battle -EndBattle : When a battle has finished - -skinparam defaultFontName DejaVu Sans Mono -skinparam defaultFontSize 10 - -skinparam stateBorderColor #236F79 -skinparam stateBackgroundColor #5D9CA5 -skinparam stateArrowColor #236F79 - -@enduml
--- a/docs/UML/database/Accounts.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ -' -' Accounts.txt -- database diagram for accounts -' -' Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr> -' -' Permission to use, copy, modify, and/or distribute this software for any -' purpose with or without fee is hereby granted, provided that the above -' copyright notice and this permission notice appear in all copies. -' -' THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -' WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -' MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -' ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -' WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -' ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -' OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -' - -@startuml - -class account { - ac_id: int - ac_name: varchar [32] - ac_email: varchar [128] - ac_firstname: varchar [32] - ac_lastname: varchar [32] - ac_salt: varchar [8] - ac_password: varchar [256] - ac_joindate: date -} - -class log_history { - log_id : int - log_ac_id : int # -} - -class log_history_item { - lhi_id : int - lhi_log_id : int # - lhi_status : enum - lhi_date : time -} - -class character { - ch_id : int - ch_ac_id : int # - ch_class : varchar [32] - ch_createdate : date -} - -class status { - st_id : int - st_status : enum - st_hp : int - st_mp : int -} - -class points { - pt_id : int - pt_st_id : int # - pt_avail : int16_t - pt_force : int16_t - pt_defense : int16_t - pt_agility : int16_t -} - -hide account methods -hide log_history methods -hide log_history_item methods -hide character methods -hide status methods -hide points methods - -account "1" -- "1" log_history -account "1" -- "0..8" character -character "1" -- "1" status -log_history "1" -- "0..*" log_history_item -status "1" -- "1" points - -skinparam defaultFontName DejaVu Sans Mono -skinparam defaultFontSize 10 - -skinparam classBorderColor #C67C37 -skinparam classBackgroundColor FFC48D -skinparam classArrowColor #C67C37 - -@enduml
--- a/docs/UML/database/Builds.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -' -' Buildings.txt -- database diagram for in game buildings -' -' Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr> -' -' Permission to use, copy, modify, and/or distribute this software for any -' purpose with or without fee is hereby granted, provided that the above -' copyright notice and this permission notice appear in all copies. -' -' THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -' WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -' MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -' ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -' WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -' ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -' OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -' - -@startuml - -class build { - bd_id : int - bd_ac_id : int # - bd_position : int [2] - bd_title : varchar [32] - bd_password : varchar [25] - bd_kind : varchar [32] -} - -class chest { - ch_id : int - ch_bd_id : int # - ch_name : varchar [32] -} - -class chest_object { - cho_id : int - cho_ch_id : int # - cho_name : varchar [32] - cho_count : int16_t -} - -hide build methods -hide chest methods -hide chest_object methods - -build "1" -- "1" chest -chest "1" -- "0..*" chest_object - -skinparam defaultFontName DejaVu Sans Mono -skinparam defaultFontSize 10 - -skinparam classBorderColor #C67C37 -skinparam classBackgroundColor FFC48D -skinparam classArrowColor #C67C37 - -@enduml
--- a/docs/UML/database/CMakeLists.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -# -# CMakeLists.txt -- CMake build system for malikania -# -# Copyright (c) 2013, 2014, 2015 Malikania Authors -# -# Permission to use, copy, modify, and/or distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -# - -project(database) - -set( - FILES - ${database_SOURCE_DIR}/Accounts.txt - ${database_SOURCE_DIR}/Builds.txt - ${database_SOURCE_DIR}/Inventory.txt -) - -malikania_generate_uml( - NAME database - DIRECTORY database - SOURCES ${FILES} -)
--- a/docs/UML/database/Inventory.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -' -' Inventory.txt -- database diagram for inventory -' -' Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr> -' -' Permission to use, copy, modify, and/or distribute this software for any -' purpose with or without fee is hereby granted, provided that the above -' copyright notice and this permission notice appear in all copies. -' -' THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -' WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -' MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -' ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -' WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -' ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -' OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -' - -@startuml - -class inventory { - inv_id : int - inv_ch_id : int # -} - -class inventory_object { - invo_id : int - invo_inv_id : int # - invo_name : varchar [32] - invo_count : int16_t -} - -class character { -} - -class artefact { - atf_id : int - atf_ch_id : int # - atf_name : varchar [32] -} - -class quest { - qs_id : int - qs_ch_id : int - qs_name : varchar [32] - qs_status : enum -} - -class quest_property { - qp_id : int - qp_name : varchar [32] - qp_value : varchar [64] -} - -hide inventory methods -hide inventory_object methods -hide character attributes -hide character methods -hide artefact methods -hide quest methods -hide quest_property methods - -character "1" -- "0..3" artefact -character "1" -- "0..*" quest -character "1" -- "1" inventory -inventory "1" -- "0..*" inventory_object -quest "1" -- "0..*" quest_property - -skinparam defaultFontName DejaVu Sans Mono -skinparam defaultFontSize 10 - -skinparam classBorderColor #C67C37 -skinparam classBackgroundColor FFC48D -skinparam classArrowColor #C67C37 - -@enduml
--- a/docs/books/CMakeLists.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -# -# CMakeLists.txt -- CMake build system for malikania -# -# Copyright (c) 2013, 2014, 2015 Malikania Authors -# -# Permission to use, copy, modify, and/or distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -# - -project(books) - -add_custom_target(docs-books COMMENT "Generated books") - -add_dependencies(docs docs-books) - -add_subdirectory(conventions) -add_subdirectory(developer) -add_subdirectory(specifications) -add_subdirectory(mercurial)
--- a/docs/books/conventions/CMakeLists.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -# -# CMakeLists.txt -- CMake build system for malikania -# -# Copyright (c) 2013, 2014, 2015 Malikania Authors -# -# Permission to use, copy, modify, and/or distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -# - -malikania_generate_book( - conventions - Conventions.pdf - Conventions.txt -)
--- a/docs/books/conventions/Conventions.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,649 +0,0 @@ ---- -title: Coding conventions -author: - - David Demelier <markand@malikania.fr> - - Renaud Jenny <renox0@malikania.fr> - - Alexis Dörr <nanahara@malikania.fr> -date: April 13, 2015 -abstract: Official coding conventions for Malikania related developments. ---- - -Please read this page with care, we are **very** strict about coding guidelines. Code which does not honour this is rejected. - -# C++ - -## Style - -### General rules - -- Never write two consecutives blank lines, -- Always use 8 tabs indent (no spaces), -- No jokes, -- No easter eggs, -- Don’t use raw pointers, -- A function should do one thing but well [KISS](http://en.wikipedia.org/wiki/KISS_principle) - -### Header in the source file - -Each file (except examples) must have the license header like this: - -````cpp -/* - * filename.ext -- short description here - * - * Copyright (c) 2013, 2014, 2015 Malikania Authors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ -```` - -**Note**: For other file than C++, be sure to adapt the comment tokens, see the `CMakeLists.txt` for examples. - -### Statement separation - -Always separate blocks by contexts. - -````cpp -void Object::destroy() -{ - if (m_motor.isStarted()) { - m_motor.brake(); - } - if (m_motor.hasOil()) { - m_motor.control(); - } - - if (m_driver.isDead()) { - sendLetter(); - } - - for (auto r : m_wheels) { - r.checkInflation(); - } -} -```` - -### Long lines - -We usually like to keep code in less that 100 characters, we admit up to -120 characters but that should be rare. If possible use local variables -if it’s too long. - -To avoid: - -````cpp -if (myObject.getParameter("foo").getValue().substr(0, 7) == "test") { - ... -} -```` - -Prefer: - -````cpp -auto &value = myObject.getParameter("foo").getValue(); - -if (value.substr(0, 7) == "test") { - ... -} -```` - -When long lines appear in a conditional, indent the next line to 4 characters. In this situation, you must add braces on the next line - -````cpp -if (onecondition && twocondition && - three && four) -{ - somestuff(); -} -```` - -### Braces - -Braces comes on the same line they are needed. - -````cpp -if (condition) { - apply(); - add(); -} else { - something(); - ok(); -} -```` - -Braces are mandatory everywhere, even if there is only one statement. - -````cpp -// Correct -if (condition) { - validate(); -} - -// Also correct -if (condition1) { - validate(); -} else { - clear(); - restart(); -} -```` - -And a lambda has its brace on the same line too: - -````cpp -sort([&] (object &) -> bool { - return true; -}); -```` - -Only functions have their braces on a new line: - -````cpp -void f() -{ -} -```` - -### Symbols placement and qualifiers - -The symbols `&` and `*` must be place just next to the variable name. The qualifiers must always be placed **before** the type. - -Bad: - -````cpp -Entity const& entity = find(); -```` - -Correct: - -````cpp -const Entity &entity = find(); -```` - -### Naming - -All functions, variables, class names are always camelCase. Only -namespaces must be all lowercase, short and concise. - -*Note:* you **must** not create a new namespace except malikania and -anonymous ones. - -More rules: - -- English names -- Member variables start with m\_ -- No hungarian notation - -````cpp -int variable; - -void myFunction() -{ -} -```` - -## Programming - -### Make elegant code - -This is useless: - -````cpp -if (m_valid) { - return true; -} - -return false; -```` - -Just try to be as concise as possible: - -````cpp -return m_valid; -```` - -### Useless else - -When you use `continue`, `break` or `return` you **don't** need else. - -````cpp -for (auto &f : values) { - if (f.isValid()) { - continue; - } - - code(); -} -```` - -````cpp -if (notValid) { - return; -} - -statement(); -```` - -### Note about continue - -The `continue` keyword is very welcome, but don't make heavy usage of it if the -loop is simple. - -Bad: - -````cpp -for (auto &f : values) { - if (!f.isValid()) { - continue; - } - if (!f.advance(100)) { - continue; - } - - f.trap(); -} -```` - -Better: - -````cpp -for (auto &f : values) { - if (f.isValid() && f.advance(100)) { - f.trap(); - } -} -```` - -### Comments - -Avoid useless comments in source files. Comment complex things or why it -is done like this. However any public method / function in the .h -**must** be documented as doxygen without exception. - -````cpp -/* - * Multi line comments look like - * this. - */ - -// Short comment -```` - -Example of class with doxygen: - -````cpp -// header.h - -/** - * @class A - * @brief The A class - */ -class A { -public: - /** - * Destory the object and all its ancestors. - * - * @return true - */ - bool destroy(); -}; -```` - -### Getters / setters - -This concept applies the most to Java programmers. If possible, setters must be -avoided, it is not a good programming practices. However, getters are -welcome if they are needed and must return: - -- If object: - - Const reference to the object (and the function is marked const) - - Overload non const -- If primitive: - - A copy of the value - -If you really need getters and setters, follow the Qt convention: - -- The getter must be the variable name (e.g `title()`) -- The setter is prefixed by set (e.g `setTitle()`) - -Bad: - -````cpp -Window w; - -w.setTitle("Fabulous window title here"); -w.setIcon("path/to/the/icon/file.png"); -w.setSize(100, 200); -```` - -Better : - -````cpp -WindowParams params { - "Fabulous window title here", - "path/to/the/icon/file.png", - 100, 200 -}; - -Window w(params); -```` - -If you need a bunch of parameters, don't hesitate to use small packages classes with public members (with no m\_ prefixe). - -### Header guards - -- Header guards must be named `MALIKANIA_FILENAME_H` -- The `#endif` must have a comment - -Example if the file is `Foo.h`: - -````cpp -#ifndef MALIKANIA_FOO_H -#define MALIKANIA_FOO_H - -#endif // !MALIKANIA_FOO_H -```` - -## Constructs - -### Arguments passing - -The following rules of thumb must be used when passing arguments: - -- The type is an object **non-polymorphic** - - The argument will not be copied nor modified in the receiver: **const reference** - - The argument is an in/out parameter: **non-const reference** - - The argument is a sink-argument (receiver needs its own copy): **value** -- The type is an object **polymorphic** - - You must store the object (e.g a vector, a member variable) - - Use smart pointer and pass by value - - The object will just be read or forwarded - - Pass by template with same rules as non-polymorphic - - Pass by universal reference if the type must be forwarded to another function -- The type is primitive - - Pass always by value - -Example of sink arguments: - -````cpp -class Person { -private: - std::string m_firstName; - std::string m_lastName; - -public: - Person(std::string firstName, std::string lastName) - : m_firstName(std::move(firstName)) - , m_lastName(std::move(lastName)) - { - } -}; -```` - -Example of **polymorphic** read-only - -````cpp -template <typename Drawable> -void draw(Drawable &drawable) -{ - drawable.draw(); -} - -Rectangle r{1, 2}; - -draw(r); -```` - -Example of **polymorphic** sink-argument - -````cpp -void addEffect(std::unique_ptr<Effect> effect) -{ - m_effets.push_back(std::move(effect)); -} - -addEffect(std::make_unique<Fire>()); -```` - -### Exceptions - -Exceptions must be used instead of returns code to indicate errors. However be sure that: - -1. You don't throw exceptions in most used functions (e.g main loop) -2. Try to limit exceptions at loading, on user input -3. You always catch by const reference - -### Class declaration - -A class must have the following order: - -1. Public, private types (with using, not typedef), -2. Private fields, -3. Protected fields, -4. Private functions -5. Protected functions -6. Public functions -7. Virtual and overrided functions -8. Operators - -````cpp -class Foo { -public: - using List = std::vector; - -private: - List m_list; - int m_count; - -public: - Foo(); - - int &operator[](int); -}; -```` - -**Note**: always add a space after a scope block (public, private, protected). - -### Commented code - -Commented code must be surrounded by `#if 0` and `#endif`. It easier to manipulate and allows nested comments. Avoid commenting too much code, if the code is useless, just delete it. - -````cpp -#if 0 - // This is currently buggy - memcpy(NULL, "don't use C routines", BUFSIZ); -#endif -```` - -### Comment labels - -Lot of IDEs support some labels in comments such as: - -- **FIXME**, a label to mark the code as unoptimized / perfectible -- **TODO**, missing feature or stuff -- **XXX**, something wrong - -Please note that using them indicates that you're probably doing something wrong. - -### Includes order - -The includes should always come in the following order. - -1. C++ headers -2. C header -3. Third party libraries -4. Application headers in "" - -````cpp -#include <sys/types.h> -#include <sys/socket.h> - -#include <iostream> - -#include <Converter.h> - -#include "Foo.h" -```` - -**Note**: always use C++ headers for C equivalent, stdio.h -\> cstdio, etc. - -### RTTI - -- Usage of dynamic_cast is strictly forbidden -- Same for anything requiring RTTI (typeid) - -### Singleton - -Do not use singletons. - -### C language - -Don't use anything from C such as `#define`, `memcpy`, `fopen` or even `malloc`. - -# CMake - -## Style - -CMake files guidelines are quite close to the C++ one. - -- Use only tabs -- Put spaces before keywords (if, foreach) - -## Argument indent - -When there is a function which takes named arguments, indent all of them if the -function is too long. - -You must also indent if there are more than one argument to parameters. - -````cmake -long_command( - NAME foo - TARGET foo - ARGS - -j6 - -c'a' - SOURCES - a.txt - b.txt -) -```` - -# JavaScript - -JavaScript coding conventions are close to the C++ guidelines. - -## Semicolons - -JavaScript can omit semicolons, but you must use them for each statement. - -## Conditionals - -If possible always use strict comparisons using `===` and `!==`. - -## Object oriented naming - -To mimic object oriented JavaScript, just name function constructors in -CamelCase. - -```` -function Vehicle() -{ -} - -Vehicle.prototype.drive = function () -{ -} -```` - -## Lexical this issue - -When you have to capture the real `this` pointer into a function callback, add -a local variable named `self`. - -````javascript -function Foo() -{ - this.entries = []; - - var self = this; - - something(function (x) { - self.entries.push(x); - }); -} -```` - -# Json - -JSON files are close to the C++ guidelines. - -## Indent - -Only use 2 spaces indentation. - -````json -{ - "property": "value" -} -```` - -## Braces - -Braces follow same conventions as C++ except for arrays where you must place -object braces on their own line. - -````json -{ - "property": "value", - "verbose": false, - "network": { - "port": 19000 - }, - "array": [ - { - "has-indent": true - } - ] -} -```` - -# Markdown - -Markdown is used for the documentation process. - -## Bullet lists - -Markdown offers different bullet list symbols, you must only use `-`. - -## Headers - -Do not use underlined headers (`====` and `----`), only use `#`. - -Bad: - -````markdown -Level 1 -======= - -Level 2 -------- -```` - -Correct: - -````markdown -# Level 1 - -## Level 2 -````
--- a/docs/books/developer/CMakeLists.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -# -# CMakeLists.txt -- CMake build system for malikania -# -# Copyright (c) 2013, 2014, 2015 Malikania Authors -# -# Permission to use, copy, modify, and/or distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -# - -malikania_generate_book( - developer - Developer.pdf - Developer.txt -) \ No newline at end of file
--- a/docs/books/developer/Developer.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,196 +0,0 @@ ---- -title: Malikania Engine developer guide -author: - - David Demelier <markand@malikania.fr> - - Renaud Jenny <renox0@malikania.fr> - - Alexis Dörr <nanahara@malikania.fr> -date: February 26, 2015 -abstract: Document related to Malikania's model of development and developer procedures. ---- - -# Introduction - -This paper describes the model of development used in Malikania Engine. You must -read it if you're a Malikania Developer. - -You may also read that paper if you want to use a development version for your -own use. - -# Branching model - -The Malikania developments use [Mercurial](http://mercurial.selenic.com) -bookmarks and branches for different purposes. - -The branches are used to separate the current development and the stables -versions. - -Malikania Engine offers security fixes from N to N-3 major versions. For example -if version 5.0 is released and a bug is found in 2.4.5, it is fixed. - -However, when a new major version is released (N), the last major version (N-1) -does not get any new feature anymore. - -## Branch: default - -This is the most active branch. It can either contains the most recent code -for the next major release or the next minor release depending on the -roadmap. - -The default branch has active developers who add new features and then merge -them to stable branches (see below). - -## Branch: x-stable - -The branch `x-stable` contains the last minor versions for major version -numbered **x**. - -For example, if the last stable branch is `3-stable`, then this branch will -produces 3.1.z, 3.2.z and such. - -The default branch MUST always merge to the **latest** stable branch. - -There are no general rules when a new major version is planned, it highly depends -on the needs and features planned. - -This branch is useful if you want to test new features without having to update -your game since it completely honours semantic versioning, thus having complete -backward compatibility since version x.0.0. - -## Branch: x.y-release - -This is the most inactive branch, it is also called as a maintainance branch. -Only fixes are commited to that branch. It's also the only branch were tags -are created. - -It is the **recommended** branch for any user who wants to use Malikania Engine -Mercurial repository. - -This branch produces the next patch version, for example, with a branch numbered -`4.1-release`, this branch will produces 4.1.1, 4.1.2, etc. - -# Usage of bookmarks - -Bookmarks are distinct from branches and are used for different purposes. If you -don't know Mercurial bookmarks, have a look at this -[explanation](http://mercurial.selenic.com/wiki/Bookmarks). - -Bookmarks are slightly different than Git branches as they do not create heads -automatically. This is quite confusing at first but it's not a real problem -since there is a special bookmark named @ (see below). - -Bookmarks are created by developers to mark specific works, see below for the -list of bookmarks. - -**Note:** When you push the bookmark for the first time, you must pass it to the -arguments (e.g `hg push -B feature-hello`). - -## Bookmark: @ - -This is the "default" bookmark. It has a special meaning in Mercurial, it is the -automatically update bookmark on **new clones**. - -It's also the bookmark where all development takes place. When you start -a **new** work, you MUST always update to that bookmark. - -## Bookmark: feature-xyz - -When starting a new feature, you MUST create a new bookmark named `feature-xyz` -where xyz is a shortname for the feature. - -## Bookmark: change-xyz - -This bookmark is created when a change has to been done. It does not add any new -features. - -## Bookmark: fix-xyz - -The `fix-xyz` bookmark is used to fix a bug. These bookmarks usually take places -only in `x.y-release` branches. - -# Finishing a feature - -When you have finished a feature, change or any work. There are several steps to -do correctly. - -## Redmine fields - -If your task has been created for Redmine, you must do the following - -1. Set the amount of time done -2. Set the percentage -3. Set the revision number in the description (e.g `r123`) - -**Note:** Redmine does not fetch automatically new revisions, you may need to -visualize the repository from redmine to check the revision number. - -## Commit message - -You must also use an appropriate commit message. Every commit message starts at -least with a capital letter and ends with period. - -Then, if the task is complete, you may set the following fields after a blank -line: - -- **Task:** the task number (with a #) -- **Requested by:** requested by a specific person (if no task) -- **Submitted by:** if the user has provided a patch - -For the person identity, use the following convention: - -- FirstName LastName \<email\> (if user allow all) -- FirstName LastName (if user does not want its email to be shown) - -Otherwise, don't set the identity if the person does not want to be represented. - -You MUST indent the fields in two tabs (8-size indent, no spaces). - -Example of a commit message for a standard Redmine task: - -```` -Add support for animation files. - -Task: #127 -```` - -Example of a commit message for a contribution: - -```` -Add support for Mac OS X. - -Submitted by: Jean-François Aloué -```` - -# Contributing - -This section targets users who wants to contribute to Malikania Engine and are -not developers. - -## You found a bug - -1. Make sure you indeed found a bug. -2. Double check if there are no pending issues about the same bug. -3. Check one more time for the same bug - -Then please provide if possible, a reproducable example so we can test easily. -If you already made a fix for the bug then please add `[PATCH]` prefix to the -Redmine task and attach it as files. - -## You want a new feature - -Adding new features makes the Malikania Engine more and more powerful, it also -makes it more and more big and maybe more and more slow. - -Always consider twice for the feature, if it can be done from ECMAScript, then -keep it into your project. - -Then follow these steps: - -1. Check if there is an issue about your feature - - There is a closed feature - - There are no plans to add this feature - - There are no issue - - Provide a correct description about the feature - -Please take in account that we love adding contributions to our project but if -it is a very big feature always ask us in the forums, IRC or mails to be sure -if we will accept the feature to avoid you wasting your time. \ No newline at end of file
--- a/docs/books/mercurial/CMakeLists.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -# -# CMakeLists.txt -- CMake build system for malikania -# -# Copyright (c) 2013, 2014, 2015 Malikania Authors -# -# Permission to use, copy, modify, and/or distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -# - -malikania_generate_book( - mercurial - Mercurial.pdf - Mercurial.txt -)
--- a/docs/books/mercurial/Mercurial.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,214 +0,0 @@ ---- -title: Mercurial -author: - - David Demelier <markand@malikania.fr> - - Renaud Jenny <renox0@malikania.fr> - - Alexis Dörr <nanahara@malikania.fr> -date: February 11, 2015 -abstract: Beginner guide to Mercurial SCM ---- - -# Introduction - -This document targets those who are unfamiliar with Mercurial or with DVCS -tools. - -If you are familiar with Git, you will understand Mercurial easily. - -## What is a DVCS - -A Distributed Revision Control System (DRCS) is a tool used to develop and -control the version of a project. In contrast to a RCS, the distributed one -can work offline. - -Note: DVCS is a synonym. - -# Mercurial - -[Mercurial](http://mercurial.selenic.com/) is a popular cross-platform SCM. - -## Why Mercurial - -Why mercurial and not other? - -The Mercurial choise is almost an historical reason, it was first chosen for -various reasons where other SCM were not options. - -On the other hand, Mercurial has several benefits: - -- Portable, this feature is mandatory in the project goal -- Easy, creating and starting using a Mercurial repository is very easy -- Beautiful, TortoiseHg and its dedicated workbench is a very convenient tool - -# Mercurial commands - -## clone - -Used to copy an existing repository locally. - -**Syntax** - - hg clone <scheme><path> [destination] - -**Example** - - hg clone http://hg.foo.org/repository - -![Clone.png](images/Clone.png) - -## pull - -Pull updates (without applying) from a repository. - -**Syntax** - - hg pull [source] - -**Example** - - hg pull - hg pull http://hg.foo.org/repo - -![Pull](images/Pull-context.png) - -![Pull](images/Pull-window.png) - -## update - -Update the content of a repository. This command let you update the repository to any revision (older or newer). It is commonly used after a pull. - -**Syntax** - - hg update [args...] - -**Example** - - hg up - hg up -r 234 # update to revision 234 - -![Update](images/Update-context.png) - -![Update](images/Update-window.png) - -## merge - -This action allows you to merge different branches / revisions / bookmarks. It will try to resolve conflict by itself and may ask you to resolve them if not possible. - -**Syntax** - - hg merge [args...] - -**Example** - - hg merge # merge with the default next head - hg merge -r 123 # merge with revision 123 - -![Merge](images/Merge-context.png) - -## add - -Add files or directories. - -**Syntax** - - hg add files... - -**Example** - - hg add myfile.cpp - hg add myfile1.cpp myfile2.cpp - hg add mydirectory - -![Add](images/Add-context.png) - -![Add](images/Add-window.png) - -## remove - -Remove tracked files. - -**Syntax** - - hg remove files... - -**Example** - - hg remove myfile.cpp - hg remove myfile1.cpp myfile2.cpp - hg remove mydirectory - -![Remove](images/Remove-context.png) - -![Remove](images/Remove-window.png) - -## commit - -Commit validates and create a new changeset locally. Thanks to the DRCS nature of Mercurial, it is completely possible to rollback. You may commit multiple times before pushing. - -**Syntax** - - hg commit [args...] [files] - -**Example** - - hg ci -m "test" - hg ci a.cpp - -![Commit](images/Commit-context.png) - -![Commit](images/Commit-window.png) - -## push - -Push every local changesets to the destination. - -**Syntax** - - hg push [args...] [destination] - -**Example** - - hg push - -![Push](images/Push-context.png) - -![Push](images/Push-window.png) - -## diff - -Shows the difference between revisions. - -**Syntax** - - hg diff [-r r1 [-r r2]] - hg diff [-c r] - -**Example** - - hg diff - hg diff -r 124 - hg diff -r 1 -r 2 - -![Diff](images/Diff-context.png) - -![Diff](images/Diff-window.png) - -## heads - -Show the different heads available. - -**Syntax** - - hg heads [args...] - -**Example** - - hg heads - -**Possible output** - -changeset: 237:370dccb35ac9 -tag: tip -user: David Demelier <markand@malikania.fr> -date: Sat Jul 05 09:06:39 2014 +0200 -summary: OptionParser: add more tests
--- a/docs/books/specifications/Architecture/Intro.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -# Architecture - -Malikania Engine is exclusively client-server based. While it is strictly a -client-server architecture, it is still possible to write one player games -by running a local server on the user's machine with some effort. - -## Server - -Everything is computed on the server side to avoid any cheat. That includes: - -- Handling authentication, -- Handling players, -- Handling battles, -- Handling moves on maps. - -## Client - -The client is only responsible of: - -- Graphics, -- Sounds, -- Input, -- Connection with server. -
--- a/docs/books/specifications/Architecture/Technologies.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -## Technologies - -The Malikania Engine is written in C++11. - -### Runtime libraries - -The following libraries are *required* as the base Malikania Engine core: - -- C++14, -- [OpenSSL](http://www.openssl.org), for the secure part of network protocol, -- [libjansson](http://www.digip.org/jansson), JSON library for game data, -- [libzip](http://www.nih.at/libzip/), ZIP library for archive bundles, -- [libcurl](http://curl.haxx.se/libcurl), library for downloading updates. - -#### On desktop - -- [SDL 2.0](http://libsdl.org), for the client part only - -### Development libraries and tools - -The following libraries may be needed when building and developing a game: - -- [CMake](http://www.cmake.org), for building the toolchain -- [Pandoc](http://http://johnmacfarlane.net/pandoc), used for generating documentation -- [Java](https://www.java.com/en), for UML diagrams
--- a/docs/books/specifications/Bundle/Format.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -## Format - -The format is a compressed ZIP archive file with no password. -
--- a/docs/books/specifications/Bundle/Intro.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -# Bundles - -Bundles are archives containing the required files to be played in the engine. -
--- a/docs/books/specifications/Bundle/Usage.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -## Usage - -A bundle archive is just an archive that contains all the game data. - -It is close to what a cardrige is to a game console, the game is simply loaded -in the Malikania Engine and the engine knows how to run it. - -This offers the following benefits: - -- Easy to download, provide, -- Compression, -- Associate the file type to the engine in the operating system. -
--- a/docs/books/specifications/CMakeLists.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -# -# CMakeLists.txt -- CMake build system for malikania -# -# Copyright (c) 2013, 2014, 2015 Malikania Authors -# -# Permission to use, copy, modify, and/or distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -# - -# -# This order makes the book order, don't change it -# -set( - FILES - "${CMAKE_CURRENT_SOURCE_DIR}/Intro.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Overview/Intro.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Architecture/Intro.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Architecture/Technologies.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Gameplay/Intro.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Gameplay/Stats.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Bundle/Intro.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Bundle/Usage.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Bundle/Format.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/GameData/Intro.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/GameData/Game.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/GameData/Manifest.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/GameData/Sprites.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/GameData/Animations.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Tools/Intro.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Tools/Malikania-vm.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Tools/Malikania-bundle.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Network/Intro.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Network/Messages.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Network/Messages/server-info.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Network/Messages/server-message.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Network/Commands.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Network/Commands/account-create.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Network/Commands/account-identify.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Network/Commands/character-create.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Network/Commands/character-delete.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Network/Commands/character-list.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Network/Commands/character-select.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Network/Commands/exchange-start.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/Network/Commands/exchange-add.txt" -) - -malikania_generate_book( - specifications - Specifications.pdf - "${FILES}" -)
--- a/docs/books/specifications/GameData/Animations.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -## Animations - -The animations are animated sprites. They provide a convenient way of drawing -animations onto the screen because they update themselves automatically. - -Animations use [Sprites][] to be shown. - -### JSON description - -A JSON file is always associated with an animation. If you have ten animations, you have ten JSON files for them. - -````json -{ - "alias": "Fire", - "sprite": "Fire-sprite", - "frames": [ - { "delay": 100, "cell": 1 }, - { "delay": 105, "cell": 0 } - ] -} -```` - -- **alias**: the name required to register the animation -- **sprite**: the sprite name (the sprite's alias) -- **frames**: for each frame the following: - - **delay**: the delay in milliseconds - - **cell**: (Optional) the cell index - -With that following code, a file will be generated with the following images: - -1. delay of 100 ms before switching to the second frame -2. delay of 105 ms before switching finishing the animation
--- a/docs/books/specifications/GameData/Game.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -## Game - -The game definition is mandatory and MUST be present in the both server and -client bundle. - -It is defined in the file **game.json** - -### JSON description - -````json -{ - "name": "Short game name", - "authors": [ "Name LastName <email>" ], - "version": "Game version", - "requires": "Engine version format", - "license": "License acronym" -} -```` - -- **name**: the game name, -- **authors**: the game authors (at least one), -- **version**: the game version as string, can be arbitrary, -- **requires**: the required version of Malikania Engine, see below -- **license**: (Optional) the license, - -**About the version** - -There is no specific format for the **requires** field, you just specify a -version (at least major). - -Because Malikania Engine is backward compatible only with minor versions (as -specified in the semver specification) the game is automatically disabled -if tried to be ran from an incompatible Malikania Engine version. - -**Example** - -1. You have developed your game using Malikania Engine 1.3.2, then set **requires** -to "1.3.2", It will be compatible with any version greater or equal to 1.3.2 but -not with version greater or equal to 2.0.0. - -2. You have developed your game using Malikania Engine 1.0, you can just set -**requires** to "1".
--- a/docs/books/specifications/GameData/Intro.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -# Game data definition - -This section details how game objects are defined. - -Usually games are defined in JSON if applicable, other scriptable parts -are written in ECMAScript. -
--- a/docs/books/specifications/GameData/Manifest.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -## Manifest - -The manifest file tells the engine how many files for each component are -available. - -It is mandatory to list all of the game data. - -### JSON description - -The file is named `manifest.json` and is installed in the root game directory. - -````json -{ - "images": [ - "images/wood.png", - "images/cat.png" - ], - "spells": [ - "spells/fire.js" - ] -} -```` - -You can use the [malikania-bundle][] tool to create manifest files -automatically.
--- a/docs/books/specifications/GameData/Sprites.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -## Sprites - -The sprites are definitions of graphical libraries. - -An image can store multiple frames of a sprite and then the game can select one -of this frame. It is useful to store the different images sprites of characters, -object for example. - -Each sprite requires one JSON file. - -### JSON description - -````json -{ - "image": "jack.png", - "alias": "Jack", - "cell": [ 16, 16 ], - "size": [ 100, 100 ], - "space": [ 0, 0 ], - "margin": [ 0, 0 ] -} -```` - -- **alias**: the sprite alias that will be used to retrieve in the registry -- **image**: the relative path to the image -- **cell**: each cell size -- **size**: Optional, the full size of the image, if not set determined at compilation -- **space**: Optional, space between cells -- **margin**: Optional, space before the first cell horizontally and vertically - -### Margin and padding - -Padding and margin are allowed before and between cells. - -![Optional padding, margin and space](GameData/images/format.png)\ - - -On that image, you can see: - -* a margin of 8x8 pixels (in blue) -* a padding of 4x4 pixels between cells (in red) -* the cell size of 32x32 pixels (in green)
--- a/docs/books/specifications/Gameplay/Intro.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -# Gameplay - -This section details the characteristics of a game created with Malikania -Engine.
--- a/docs/books/specifications/Gameplay/Stats.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -## Character statistics - -Each character has the following statistics. - -### Levels - -A character level starts at level 1 and goes to 100 included. This is not -configurable by the game. - -### Spells - -The number of spells is defined by the specifications and cannot be configured -by the game. - -The following table shows how the spells are gained during the player -lifetime. - -| Level | Number of spells available | -|-------|----------------------------| -| 2 | 3 | -| 3 | 4 | -| 8 | 5 | -| 12 | 6 | -| 15 | 7 | -| 20 | 8 | -| 28 | 9 | -| 40 | 10 | -| 56 | 11 | -| 68 | 12 | -| 85 | 13 | -| 100 | 14 | - -### Statistics - -Each character has: - -| Name | Range | Description | -|--------------|---------|-------------------------| -| Heal points | 0-32767 | The player heal points | -| Magic points | 0-127 | The player magic points | -| Move points | 0-20 | The player move points |
--- a/docs/books/specifications/Intro.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ ---- -title: Malikania Engine -author: - - David Demelier <markand@malikania.fr> - - Renaud Jenny <renox0@malikania.fr> - - Alexis Dörr <nanahara@malikania.fr> -date: February 26, 2015 -abstract: Official Malikania Engine specifications ---- - -# Synopsis - -This paper details the official Malikania Engine specifications and its -reference implementation. - -# Terms - -The keywords MUST, SHOULD, MAY are to be interpreted as described in -[RFC 2119](http://tools.ietf.org/html/rfc2119).
--- a/docs/books/specifications/Network/Commands.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -## Commands - -The commands are sent from the client to the server.
--- a/docs/books/specifications/Network/Commands/account-create.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -### account-create (secure) - -Create an account on the server. Registration must be enabled, otherwise -an error is raised to the client. - -#### Synopsis - -````json -{ - "command": "account-create", - "login": "login name", - "first-name": "first name", - "last-name": "last name", - "password": "password", - "email": "email address" -} -```` - -#### Fields - -- **login**: the login, following the login naming rules -- **first-name**: the user first name -- **last-name**: the user last name -- **password**: clear password -- **email**: the email address
--- a/docs/books/specifications/Network/Commands/account-identify.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ -### account-identify (secure) - -Identify a player. - -#### Synopsis - -````json -{ - "command": "account-identify", - "login": "jean", - "password": "password" -} -```` - -#### Fields - -- **login**, the account name -- **password**, the password
--- a/docs/books/specifications/Network/Commands/character-create.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -### character-create - -Create a character. - -#### Synopsis - -````json -{ - "command": "character-create", - "nickname": "character nickname", - "class": "class alias", - "gender": "male or female" -} -```` - -#### Fields - -- **nickname**: the character nickname -- **class**: the class alias -- **gender**: the character gender
--- a/docs/books/specifications/Network/Commands/character-delete.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -### character-delete - -Deletes a character, there are no confirmation possible, the client must do it. - -#### Synopsis - -````json -{ - "command": "character-delete", - "id": 123 -} -```` - -#### Fields - -- **id**: the character id
--- a/docs/books/specifications/Network/Commands/character-list.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -### character-list - -List all characters available for the connected player. - -#### Synopsis - -````json -{ - "command": "character-list" -} -````
--- a/docs/books/specifications/Network/Commands/character-select.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -### character-select - -Select a character. - -#### Synopsis - -````json -{ - "command": "character-select", - "id": "character id" -} -```` - -#### Fields - -- **id**: the character id
--- a/docs/books/specifications/Network/Commands/exchange-add.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -### exchange-add - -Add an object to the exchange session, this automatically disable acceptation -in the other side. - -#### Synopsis - -````json -{ - "command": "exchange-add", - "id": 123 -} -```` - -#### Fields - -- **id**: the object id in the player inventory
--- a/docs/books/specifications/Network/Commands/exchange-start.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -### exchange-start (secure) - -Start an exchange session with a player. - -#### Synopsis - -````json -{ - "command": "exchange-start", - "id": 123 -} -```` - -#### Fields - -- **id**: the player id
--- a/docs/books/specifications/Network/Intro.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -# Network - -Networking in Malikania Engine. - -In that part, we will describe messages and requests. The messages come -from the server while the requests come from the clients. - -## Syntax - -The network protocol use JSON in multiline format. - -You can use both Unix or Windows line endings. However, to terminate -a message, an empty line must be added and terminated by '\\r\\n'. - -**Example:** - -1. This is a simple single-line message - -````json -{ "command": "reload" } -```` - -2. This is a multi-lines message - -````json -{ - "command": "identify", - "nickname": "foo", -} -````
--- a/docs/books/specifications/Network/Messages.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -## Messages - -The message are emitted from the server and goes either to a specific -client or to all connected players
--- a/docs/books/specifications/Network/Messages/server-info.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -### server-info - -Get the server information. - -#### Synopsis - -````json -{ - "command": "server-info", - "version": 1.0, - "engine": 3.2, - "admins": [ "player1", "player2" ], - "motd": "Message of the day", - "download": { - "enabled": true, - "sites": [ - "http://pub.mygame.org/files", - "http://pub2.mygame.org/files" - ] - } -} -```` - -#### Fields - -- **version**, the game version -- **engine**, the engine version running -- **admins**, (Optional) the list of administrators -- **motd**, (Optional) a message of the day -- **download**, (Optional) the download properties - - **enabled**: (Optional) enable download from the server. Default: false. - - **sites**: (Optional) list of mirrors where to download data, required if **enabled** is set. -
--- a/docs/books/specifications/Network/Messages/server-message.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ -### server-message - -The server has sent a global message which is sent to all connected users. - -#### Synopsis - -````json -{ - "command": "server-message", - "origin": "nickname", - "message": "Content of the message" -} -```` - -#### Fields - -- **origin**: the user who has sent the global message -- **message**: the content of the message, never sent empty
--- a/docs/books/specifications/Overview/Intro.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -# Overview - -## What is Malikania Engine? - -Malikania Engine is a powerful free, opensource 2D MMORPG engine written in -C++14 and use ECMAScript for the games. - -It is designed in mind to be as easy as possible to use. It is also very -flexible to let you create games quickly with no difficulties. - -It is specialized in MMORPG only, while you can create your game how you want, -it is not designed to be a general game engine. - -## How does it work? - -Malikania Engine is a set of multiple executables which will execute games -written in ECMAScript.
--- a/docs/books/specifications/Tools/Intro.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -# Tools - -The following list of tools come with the reference implementation of the -Malikania Engine: - -- malikania-vm [^malikania-vm-except] -- malikania-bundle -- malikania-animation -- malikania-sprite -- malikania-map -- malikania-translate -- malikania-manifest - -[^malikania-vm-except]: the the `malikania-vm` tool is an external package as it -helps managing the Malikania Engine VMs.
--- a/docs/books/specifications/Tools/Malikania-bundle.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -## malikania-bundle - -The `malikania-bundle` tool manages the games cartridges. - -### Command line usage - -(@) `malikania-bundle create *archive* *game-directory*` -(@) `malikania-bundle install [-d directory] *archive*` -(@) `malikania-bundle info *field* *name*` -(@) `malikania-bundle list` - -**Signature (1)** - -Creates the file *archive* from the game directory specified by -*game-directory*. - -**Signature (2)** - -Install the game archive specified by *archive* to the optional directory -specified by *-d directory*, otherwise, install it to the user Malikania Engine -folder. - -**Note**: the *directory* is the prefix, invoking -`malikania-bundle install -d /tmp/ game-1.mcg` will install */tmp/game-1*. - -**Signature (3)** - -Extract a field information from the game which may be an already installed game -or an game archive specified by path. This usage MUST search for the filesystem path -before seeking installed games. - -The *field* must be one of the following: - -- *version*, the game version -- *engine*, the Malikania Engine version -- *author*, the game author
--- a/docs/books/specifications/Tools/Malikania-vm.txt Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ -## malikania-vm - -The `malikania-vm` tool is the most important tool within the Malikania Engine -framework. - -Because Malikania Engine is backward compatible only with minor versions (as -specified by semver), it is sometimes necessary to install multiple major -versions of the Malikania Engine. - -To facilitate the launch of the games, the malikania-vm tool will use the -appropriate version of the engine according to the game requirements. - -Because of that, this tool is built as a completely separate project and MUST -be present before installation of any engine. - -The malikania-vm provides all the wrappers for every other Malikania Engine -tools. - -### Installing new Malikania Engine version - -When a Malikania Engine version is installed, it also register itself to a small -file kept by malikania-vm. - -This can be done from the Malikania Engine install target or by hand with the -malikania-vm utility directly. - -**Note**: because Malikania Engine is backward compatible with minor versions, -it does not make sense to install two versions from the same major version. - -Bad example: - -- 1.0.6 -- 1.2.4 - -Correct example: - -- 1.4.0 -- 2.7 - -### The Malikania-VM JSON description file - -The malikania-vm stores all engines installed into a JSON file. It is installed -by default in: - -- **PREFIX/etc/malikania/vm.json** - -````json -[ - { - "version": "1.0.6", - "prefix": "/usr/local", - "execdir": "libexec/malikania/1.0.6", - "libdir": "lib", - "dbdir": "lib/malikania/1.0.6", - "datadir": "share/malikania/1.0.6", - "docdir": "doc/malikania/1.0.6", - "etcdir": "etc/malikania/1.0.6" - } -] -```` - -- **version**: the engine version -- **prefix**: Optional, the prefix where installed (if set, MUST be absolute) -- **execdir**: Optional, the directory where to store Malikania Engine binaries -- **libdir**: Optional, the directory where Malikania Engine public libraries are stored -- **dbdir**: Optional, the directory where database backends are stored -- **datadir**: Optional, the directory where Malikania Engine data are stored -- **docdir**: Optional, the directory where Malikania Engine documentation are stored -- **etcdir**: Optional, the directory where Malikania Engine configuration are stored - -**Note**: If any of **execdir**, **libdir**, **dbdir**, **datadir**, **docdir**, -**etcdir** is not absolute, then it is relative to the prefix (recommended). - -### Command line usage - -(@) `malikania-vm list` -(@) `malikania-vm unregister version` -(@) `malikania-vm register version [-p prefix] [-e execdir] [-l libdir] [-d dbdir] -[-s datadir] [-D docdir] [-c etcdir]` - -**Signature (1)** - -List all Malikania Engine installations. - -**Signature (2)** - -Remove the specified Malikania Engine version from the registry (not from the -disk). - -**Signature (3)** - -Add a new Malikania Engine version to the Malikania VM registry. - -### CMake module - -The malikania-vm tool installs **MalikaniaVMConfig.cmake**, the Malikania Engine -can set `MALIKANIA_VM_NO_REGISTER` before including the file so that the install -target does not register itself to the malikania-vm (not recommended).
--- a/libcommon/malikania/Socket.cpp Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,196 +0,0 @@ -/* - * Socket.cpp -- portable C++ socket wrappers - * - * Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <cstring> - -#include "Socket.h" -#include "SocketAddress.h" - -/* -------------------------------------------------------- - * System dependent code - * -------------------------------------------------------- */ - -#if defined(_WIN32) - -std::string Socket::syserror(int errn) -{ - LPSTR str = nullptr; - std::string errmsg = "Unknown error"; - - FormatMessageA( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - NULL, - errn, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPSTR)&str, 0, NULL); - - - if (str) { - errmsg = std::string(str); - LocalFree(str); - } - - return errmsg; -} - -#else - -#include <cerrno> - -std::string Socket::syserror(int errn) -{ - return strerror(errn); -} - -#endif - -std::string Socket::syserror() -{ -#if defined(_WIN32) - return syserror(WSAGetLastError()); -#else - return syserror(errno); -#endif -} - -/* -------------------------------------------------------- - * SocketError class - * -------------------------------------------------------- */ - -SocketError::SocketError(Code code, std::string function) - : m_code(code) - , m_function(std::move(function)) - , m_error(Socket::syserror()) -{ -} - -SocketError::SocketError(Code code, std::string function, int error) - : m_code(code) - , m_function(std::move(function)) - , m_error(Socket::syserror(error)) -{ -} - -SocketError::SocketError(Code code, std::string function, std::string error) - : m_code(code) - , m_function(std::move(function)) - , m_error(std::move(error)) -{ -} - -/* -------------------------------------------------------- - * Socket class - * -------------------------------------------------------- */ - -#if defined(_WIN32) -std::mutex Socket::s_mutex; -std::atomic<bool> Socket::s_initialized{false}; -#endif - -Socket::Socket(int domain, int type, int protocol) -{ -#if defined(_WIN32) && !defined(SOCKET_NO_WSA_INIT) - if (!s_initialized) { - initialize(); - } -#endif - - m_handle = ::socket(domain, type, protocol); - - if (m_handle == Invalid) { - throw SocketError(SocketError::System, "socket"); - } - - m_state = SocketState::Opened; -} - -SocketAddress Socket::address() const -{ -#if defined(_WIN32) - int length; -#else - socklen_t length; -#endif - - sockaddr_storage ss; - - if (getsockname(m_handle, (sockaddr *)&ss, &length) == Error) - throw SocketError(SocketError::System, "getsockname"); - - return SocketAddress(ss, length); -} - -void Socket::bind(const SocketAddress &address) -{ - const auto &sa = address.address(); - const auto addrlen = address.length(); - - if (::bind(m_handle, reinterpret_cast<const sockaddr *>(&sa), addrlen) == Error) { - throw SocketError(SocketError::System, "bind"); - } - - m_state = SocketState::Bound; -} - -void Socket::close() -{ -#if defined(_WIN32) - ::closesocket(m_handle); -#else - ::close(m_handle); -#endif - - m_state = SocketState::Closed; -} - -void Socket::setBlockMode(bool block) -{ -#if defined(O_NONBLOCK) && !defined(_WIN32) - int flags; - - if ((flags = fcntl(m_handle, F_GETFL, 0)) == -1) { - flags = 0; - } - - if (block) { - flags &= ~(O_NONBLOCK); - } else { - flags |= O_NONBLOCK; - } - - if (fcntl(m_handle, F_SETFL, flags) == Error) { - throw SocketError(SocketError::System, "setBlockMode"); - } -#else - unsigned long flags = (block) ? 0 : 1; - - if (ioctlsocket(m_handle, FIONBIO, &flags) == Error) { - throw SocketError(SocketError::System, "setBlockMode"); - } -#endif -} - -bool operator==(const Socket &s1, const Socket &s2) -{ - return s1.handle() == s2.handle(); -} - -bool operator<(const Socket &s1, const Socket &s2) -{ - return s1.handle() < s2.handle(); -}
--- a/libcommon/malikania/SocketAddress.cpp Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,138 +0,0 @@ -/* - * SocketAddress.cpp -- socket addresses management - * - * Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <algorithm> -#include <cstring> - -#include "Socket.h" -#include "SocketAddress.h" - -namespace address { - -/* -------------------------------------------------------- - * Internet implementation - * -------------------------------------------------------- */ - -Internet::Internet(const std::string &host, unsigned port, int domain) -{ - if (host == "*") { - if (domain == AF_INET6) { - sockaddr_in6 *ptr = (sockaddr_in6 *)&m_addr; - - ptr->sin6_addr = in6addr_any; - ptr->sin6_family = AF_INET6; - ptr->sin6_port = htons(port); - - m_addrlen = sizeof (sockaddr_in6); - } else { - sockaddr_in *ptr = (sockaddr_in *)&m_addr; - - ptr->sin_addr.s_addr = INADDR_ANY; - ptr->sin_family = AF_INET; - ptr->sin_port = htons(port); - - m_addrlen = sizeof (sockaddr_in); - } - } else { - addrinfo hints, *res; - - std::memset(&hints, 0, sizeof (addrinfo)); - hints.ai_family = domain; - - auto error = getaddrinfo(host.c_str(), std::to_string(port).c_str(), &hints, &res); - if (error != 0) { - throw SocketError(SocketError::System, "getaddrinfo", gai_strerror(error)); - } - - std::memcpy(&m_addr, res->ai_addr, res->ai_addrlen); - m_addrlen = res->ai_addrlen; - freeaddrinfo(res); - } -} - -/* -------------------------------------------------------- - * Unix implementation - * -------------------------------------------------------- */ - -#if !defined(_WIN32) - -#include <sys/un.h> - -Unix::Unix(const std::string &path, bool rm) -{ - sockaddr_un *sun = (sockaddr_un *)&m_addr; - - // Silently remove the file even if it fails - if (rm) { - ::remove(path.c_str()); - } - - // Copy the path - memset(sun->sun_path, 0, sizeof (sun->sun_path)); - strncpy(sun->sun_path, path.c_str(), sizeof (sun->sun_path) - 1); - - // Set the parameters - sun->sun_family = AF_UNIX; - m_addrlen = SUN_LEN(sun); -} - -#endif // _WIN32 - -} // !address - -/* -------------------------------------------------------- - * SocketAddress implementation - * -------------------------------------------------------- */ - -SocketAddress::SocketAddress() - : m_addrlen(0) -{ - memset(&m_addr, 0, sizeof (m_addr)); -} - -SocketAddress::SocketAddress(const sockaddr_storage &addr, socklen_t length) - : m_addr(addr) - , m_addrlen(length) -{ -} - -const sockaddr_storage &SocketAddress::address() const -{ - return m_addr; -} - -socklen_t SocketAddress::length() const -{ - return m_addrlen; -} - -bool operator<(const SocketAddress &s1, const SocketAddress &s2) -{ - const auto &array1 = reinterpret_cast<const unsigned char *>(&s1.address()); - const auto &array2 = reinterpret_cast<const unsigned char *>(&s2.address()); - - return std::lexicographical_compare(array1, array1 + s1.length(), array2, array2 + s2.length()); -} - -bool operator==(const SocketAddress &s1, const SocketAddress &s2) -{ - const auto &array1 = reinterpret_cast<const unsigned char *>(&s1.address()); - const auto &array2 = reinterpret_cast<const unsigned char *>(&s2.address()); - - return std::equal(array1, array1 + s1.length(), array2, array2 + s2.length()); -}
--- a/libcommon/malikania/SocketListener.cpp Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,380 +0,0 @@ -/* - * SocketListener.cpp -- portable select() wrapper - * - * Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <algorithm> -#include <set> - -#include "SocketListener.h" - -/* -------------------------------------------------------- - * Select implementation - * -------------------------------------------------------- */ - -namespace backend { - -std::vector<SocketStatus> Select::wait(const SocketTable &table, int ms) -{ - timeval maxwait, *towait; - fd_set readset; - fd_set writeset; - - FD_ZERO(&readset); - FD_ZERO(&writeset); - - Socket::Handle max = 0; - - for (const auto &s : table) { - if (s.second.second & SocketListener::Read) { - FD_SET(s.first, &readset); - } - if (s.second.second & SocketListener::Write) { - FD_SET(s.first, &writeset); - } - - if (s.first > max) { - max = s.first; - } - } - - maxwait.tv_sec = 0; - maxwait.tv_usec = ms * 1000; - - // Set to nullptr for infinite timeout. - towait = (ms < 0) ? nullptr : &maxwait; - - auto error = ::select(max + 1, &readset, &writeset, nullptr, towait); - if (error == Socket::Error) { - throw SocketError(SocketError::System, "select"); - } - if (error == 0) { - throw SocketError(SocketError::Timeout, "select", "Timeout while listening"); - } - - std::vector<SocketStatus> sockets; - - for (auto &c : table) { - if (FD_ISSET(c.first, &readset)) { - sockets.push_back(SocketStatus{c.second.first, SocketListener::Read}); - } - if (FD_ISSET(c.first, &writeset)) { - sockets.push_back(SocketStatus{c.second.first, SocketListener::Write}); - } - } - - return sockets; -} - -/* -------------------------------------------------------- - * Poll implementation - * -------------------------------------------------------- */ - -#if defined(SOCKET_HAVE_POLL) - -#if defined(_WIN32) -# define poll WSAPoll -#endif - -short Poll::topoll(int flags) const noexcept -{ - short result(0); - - if (flags & SocketListener::Read) { - result |= POLLIN; - } - if (flags & SocketListener::Write) { - result |= POLLOUT; - } - - return result; -} - -int Poll::toflags(short &event) const noexcept -{ - int flags = 0; - - /* - * Poll implementations mark the socket differently regarding - * the disconnection of a socket. - * - * At least, even if POLLHUP or POLLIN is set, recv() always - * return 0 so we mark the socket as readable. - */ - if ((event & POLLIN) || (event & POLLHUP)) { - flags |= SocketListener::Read; - } - if (event & POLLOUT) { - flags |= SocketListener::Write; - } - - // Reset event for safety - event = 0; - - return flags; -} - -void Poll::set(const SocketTable &, Socket s, int flags, bool add) -{ - if (add) { - m_lookup.emplace(s.handle(), s); - m_fds.push_back(pollfd{s.handle(), topoll(flags), 0}); - } else { - auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const struct pollfd &pfd) { - return pfd.fd == s.handle(); - }); - - it->events |= topoll(flags); - } -} - -void Poll::unset(const SocketTable &, Socket s, int flags, bool remove) -{ - auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const struct pollfd &pfd) { - return pfd.fd == s.handle(); - }); - - if (remove) { - m_lookup.erase(it->fd); - m_fds.erase(it); - } else { - it->events &= ~(topoll(flags)); - } -} - -std::vector<SocketStatus> Poll::wait(const SocketTable &, int ms) -{ - auto result = poll(m_fds.data(), m_fds.size(), ms); - if (result == 0) { - throw SocketError(SocketError::Timeout, "select", "Timeout while listening"); - } - if (result < 0) { - throw SocketError(SocketError::System, "poll"); - } - - std::vector<SocketStatus> sockets; - for (auto &fd : m_fds) { - if (fd.revents != 0) { - sockets.push_back(SocketStatus{m_lookup.at(fd.fd), toflags(fd.revents)}); - } - } - - return sockets; -} - -#endif // !SOCKET_HAVE_POLL - -/* -------------------------------------------------------- - * Epoll implementation - * -------------------------------------------------------- */ - -#if defined(SOCKET_HAVE_EPOLL) - -uint32_t Epoll::toepoll(int flags) const noexcept -{ - uint32_t events = 0; - - if (flags & SocketListener::Read) { - events |= EPOLLIN; - } - if (flags & SocketListener::Write) { - events |= EPOLLOUT; - } - - return events; -} - -int Epoll::toflags(uint32_t events) const noexcept -{ - int flags = 0; - - if ((events & EPOLLIN) || (events & EPOLLHUP)) { - flags |= SocketListener::Read; - } - if (events & EPOLLOUT) { - flags |= SocketListener::Write; - } - - return flags; -} - -void Epoll::update(Socket &sc, int op, int flags) -{ - struct epoll_event ev; - - std::memset(&ev, 0, sizeof (struct epoll_event)); - - ev.events = flags; - ev.data.fd = sc.handle(); - - if (epoll_ctl(m_handle, op, sc.handle(), &ev) < 0) { - throw SocketError(SocketError::System, "epoll_ctl"); - } -} - -Epoll::Epoll() - : m_handle(epoll_create1(0)) -{ - if (m_handle < 0) { - throw SocketError(SocketError::System, "epoll_create"); - } -} - -Epoll::~Epoll() -{ - close(m_handle); -} - -/* - * Add a new epoll_event or just update it. - */ -void Epoll::set(const SocketTable &, Socket &sc, int flags, bool add) -{ - update(sc, add ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, toepoll(flags)); - - if (add) { - m_events.resize(m_events.size() + 1); - } -} - -/* - * Unset is a bit complicated case because SocketListener tells us which - * flag to remove but to update epoll descriptor we need to pass - * the effective flags that we want to be applied. - * - * So we put the same flags that are currently effective and remove the - * requested one. - */ -void Epoll::unset(const SocketTable &table, Socket &sc, int flags, bool remove) -{ - if (remove) { - update(sc, EPOLL_CTL_DEL, 0); - m_events.resize(m_events.size() - 1); - } else { - update(sc, EPOLL_CTL_MOD, table.at(sc.handle()).second & ~(toepoll(flags))); - } -} - -std::vector<SocketStatus> Epoll::wait(const SocketTable &table, int ms) -{ - int ret = epoll_wait(m_handle, m_events.data(), m_events.size(), ms); - std::vector<SocketStatus> result; - - if (ret == 0) { - throw SocketError(SocketError::Timeout, "epoll_wait"); - } - if (ret < 0) { - throw SocketError(SocketError::System, "epoll_wait"); - } - - for (int i = 0; i < ret; ++i) { - result.push_back(SocketStatus{table.at(m_events[i].data.fd).first, toflags(m_events[i].events)}); - } - - return result; -} - -#endif // !SOCKET_HAVE_EPOLL - -/* -------------------------------------------------------- - * Kqueue implementation - * -------------------------------------------------------- */ - -#if defined(SOCKET_HAVE_KQUEUE) - -Kqueue::Kqueue() - : m_handle(kqueue()) -{ - if (m_handle < 0) { - throw SocketError(SocketError::System, "kqueue"); - } -} - -Kqueue::~Kqueue() -{ - close(m_handle); -} - -void Kqueue::update(const Socket &sc, int filter, int flags) -{ - struct kevent ev; - - EV_SET(&ev, sc.handle(), filter, flags, 0, 0, nullptr); - - if (kevent(m_handle, &ev, 1, nullptr, 0, nullptr) < 0) { - throw SocketError(SocketError::System, "kevent"); - } -} - -void Kqueue::set(const SocketTable &, const Socket &sc, int flags, bool add) -{ - if (flags & SocketListener::Read) { - update(sc, EVFILT_READ, EV_ADD | EV_ENABLE); - } - if (flags & SocketListener::Write) { - update(sc, EVFILT_WRITE, EV_ADD | EV_ENABLE); - } - - if (add) { - m_result.resize(m_result.size() + 1); - } -} - -void Kqueue::unset(const SocketTable &, const Socket &sc, int flags, bool remove) -{ - if (flags & SocketListener::Read) { - update(sc, EVFILT_READ, EV_DELETE); - } - if (flags & SocketListener::Write) { - update(sc, EVFILT_WRITE, EV_DELETE); - } - - if (remove) { - m_result.resize(m_result.size() - 1); - } -} - -std::vector<SocketStatus> Kqueue::wait(const SocketTable &table, int ms) -{ - std::vector<SocketStatus> sockets; - timespec ts = { 0, 0 }; - timespec *pts = (ms <= 0) ? nullptr : &ts; - - ts.tv_sec = ms / 1000; - ts.tv_nsec = (ms % 1000) * 1000000; - - int nevents = kevent(m_handle, nullptr, 0, &m_result[0], m_result.capacity(), pts); - - if (nevents == 0) { - throw SocketError(SocketError::Timeout, "kevent"); - } - if (nevents < 0) { - throw SocketError(SocketError::System, "kevent"); - } - - for (int i = 0; i < nevents; ++i) { - Socket sc = table.at(m_result[i].ident).first; - int flags = m_result[i].filter == EVFILT_READ ? SocketListener::Read : SocketListener::Write; - - sockets.push_back(SocketStatus{sc, flags}); - } - - return sockets; -} - -#endif // !SOCKET_HAVE_KQUEUE - -} // !backend
--- a/libcommon/malikania/SocketSsl.cpp Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,230 +0,0 @@ -/* - * SocketSsl.cpp -- OpenSSL extension for sockets - * - * Copyright (c) 2013, David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "SocketAddress.h" -#include "SocketListener.h" -#include "SocketSsl.h" - -namespace { - -auto sslMethod(int mflags) -{ - if (mflags & SocketSslOptions::All) - return SSLv23_method(); - if (mflags & SocketSslOptions::SSLv3) - return SSLv3_method(); - if (mflags & SocketSslOptions::TLSv1) - return TLSv1_method(); - - return SSLv23_method(); -} - -inline std::string sslError(int error) -{ - return ERR_reason_error_string(error); -} - -inline int toDirection(int error) -{ - if (error == SocketError::WouldBlockRead) - return SocketListener::Read; - if (error == SocketError::WouldBlockWrite) - return SocketListener::Write; - - return 0; -} - -} // !namespace - -std::mutex SocketSsl::s_sslMutex; -std::atomic<bool> SocketSsl::s_sslInitialized{false}; - -SocketSsl::SocketSsl(Socket::Handle handle, SSL_CTX *context, SSL *ssl) - : SocketAbstractTcp(handle) - , m_context(context, SSL_CTX_free) - , m_ssl(ssl, SSL_free) -{ -#if !defined(SOCKET_NO_SSL_INIT) - if (!s_sslInitialized) { - sslInitialize(); - } -#endif -} - -SocketSsl::SocketSsl(int family, int protocol, SocketSslOptions options) - : SocketAbstractTcp(family, protocol) - , m_options(std::move(options)) -{ -#if !defined(SOCKET_NO_SSL_INIT) - if (!s_sslInitialized) { - sslInitialize(); - } -#endif -} - -void SocketSsl::connect(const SocketAddress &address) -{ - standardConnect(address); - - // Context first - auto context = SSL_CTX_new(sslMethod(m_options.method)); - - m_context = ContextHandle(context, SSL_CTX_free); - - // SSL object then - auto ssl = SSL_new(context); - - m_ssl = SslHandle(ssl, SSL_free); - - SSL_set_fd(ssl, m_handle); - - auto ret = SSL_connect(ssl); - - if (ret <= 0) { - auto error = SSL_get_error(ssl, ret); - - if (error == SSL_ERROR_WANT_READ) { - throw SocketError(SocketError::WouldBlockRead, "connect", "Operation in progress"); - } else if (error == SSL_ERROR_WANT_WRITE) { - throw SocketError(SocketError::WouldBlockWrite, "connect", "Operation in progress"); - } else { - throw SocketError(SocketError::System, "connect", sslError(error)); - } - } - - m_state = SocketState::Connected; -} - -void SocketSsl::waitConnect(const SocketAddress &address, int timeout) -{ - try { - // Initial try - connect(address); - } catch (const SocketError &ex) { - if (ex.code() == SocketError::WouldBlockRead || ex.code() == SocketError::WouldBlockWrite) { - SocketListener listener{{*this, toDirection(ex.code())}}; - - listener.wait(timeout); - - // Second try - connect(address); - } else { - throw; - } - } -} - -SocketSsl SocketSsl::accept() -{ - SocketAddress dummy; - - return accept(dummy); -} - -SocketSsl SocketSsl::accept(SocketAddress &info) -{ - auto client = standardAccept(info); - auto context = SSL_CTX_new(sslMethod(m_options.method)); - - if (m_options.certificate.size() > 0) - SSL_CTX_use_certificate_file(context, m_options.certificate.c_str(), SSL_FILETYPE_PEM); - if (m_options.privateKey.size() > 0) - SSL_CTX_use_PrivateKey_file(context, m_options.privateKey.c_str(), SSL_FILETYPE_PEM); - if (m_options.verify && !SSL_CTX_check_private_key(context)) { - client.close(); - throw SocketError(SocketError::System, "accept", "certificate failure"); - } - - // SSL object - auto ssl = SSL_new(context); - - SSL_set_fd(ssl, client.handle()); - - auto ret = SSL_accept(ssl); - - if (ret <= 0) { - auto error = SSL_get_error(ssl, ret); - - if (error == SSL_ERROR_WANT_READ) { - throw SocketError(SocketError::WouldBlockRead, "accept", "Operation would block"); - } else if (error == SSL_ERROR_WANT_WRITE) { - throw SocketError(SocketError::WouldBlockWrite, "accept", "Operation would block"); - } else { - throw SocketError(SocketError::System, "accept", sslError(error)); - } - } - - return SocketSsl(client.handle(), context, ssl); -} - -unsigned SocketSsl::recv(void *data, unsigned len) -{ - auto nbread = SSL_read(m_ssl.get(), data, len); - - if (nbread <= 0) { - auto error = SSL_get_error(m_ssl.get(), nbread); - - if (error == SSL_ERROR_WANT_READ) { - throw SocketError(SocketError::WouldBlockRead, "recv", "Operation would block"); - } else if (error == SSL_ERROR_WANT_WRITE) { - throw SocketError(SocketError::WouldBlockWrite, "recv", "Operation would block"); - } else { - throw SocketError(SocketError::System, "recv", sslError(error)); - } - } - - return nbread; -} - -unsigned SocketSsl::waitRecv(void *data, unsigned len, int timeout) -{ - SocketListener listener{{*this, SocketListener::Read}}; - - listener.wait(timeout); - - return recv(data, len); -} - -unsigned SocketSsl::send(const void *data, unsigned len) -{ - auto nbread = SSL_write(m_ssl.get(), data, len); - - if (nbread <= 0) { - auto error = SSL_get_error(m_ssl.get(), nbread); - - if (error == SSL_ERROR_WANT_READ) { - throw SocketError(SocketError::WouldBlockRead, "send", "Operation would block"); - } else if (error == SSL_ERROR_WANT_WRITE) { - throw SocketError(SocketError::WouldBlockWrite, "send", "Operation would block"); - } else { - throw SocketError(SocketError::System, "send", sslError(error)); - } - } - - return nbread; -} - -unsigned SocketSsl::waitSend(const void *data, unsigned len, int timeout) -{ - SocketListener listener{{*this, SocketListener::Write}}; - - listener.wait(timeout); - - return send(data, len); -} -
--- a/libcommon/malikania/SocketTcp.cpp Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,238 +0,0 @@ -/* - * SocketTcp.cpp -- portable C++ socket wrappers - * - * Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "SocketAddress.h" -#include "SocketListener.h" -#include "SocketTcp.h" - -/* -------------------------------------------------------- - * SocketAbstractTcp - * -------------------------------------------------------- */ - -void SocketAbstractTcp::listen(int max) -{ - if (::listen(m_handle, max) == Error) { - throw SocketError(SocketError::System, "listen"); - } -} - -Socket SocketAbstractTcp::standardAccept(SocketAddress &info) -{ - Socket::Handle handle; - - // Store the information - sockaddr_storage address; - socklen_t addrlen; - - addrlen = sizeof (sockaddr_storage); - handle = ::accept(m_handle, reinterpret_cast<sockaddr *>(&address), &addrlen); - - if (handle == Invalid) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - throw SocketError(SocketError::WouldBlockRead, "accept", error); - } - - throw SocketError(SocketError::System, "accept", error); -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - throw SocketError(SocketError::WouldBlockRead, "accept"); - } - - throw SocketError(SocketError::System, "accept"); -#endif - } - - info = SocketAddress(address, addrlen); - - return Socket(handle); -} - -void SocketAbstractTcp::standardConnect(const SocketAddress &address) -{ - if (m_state == SocketState::Connected) { - return; - } - - auto &sa = address.address(); - auto addrlen = address.length(); - - if (::connect(m_handle, reinterpret_cast<const sockaddr *>(&sa), addrlen) == Error) { - /* - * Determine if the error comes from a non-blocking connect that cannot be - * accomplished yet. - */ -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - throw SocketError(SocketError::WouldBlockWrite, "connect", error); - } - - throw SocketError(SocketError::System, "connect", error); -#else - if (errno == EINPROGRESS) { - throw SocketError(SocketError::WouldBlockWrite, "connect"); - } - - throw SocketError(SocketError::System, "connect"); -#endif - } - - m_state = SocketState::Connected; -} - -/* -------------------------------------------------------- - * SocketTcp - * -------------------------------------------------------- */ - -SocketTcp SocketTcp::accept() -{ - SocketAddress dummy; - - return accept(dummy); -} - -SocketTcp SocketTcp::accept(SocketAddress &info) -{ - return standardAccept(info); -} - -void SocketTcp::connect(const SocketAddress &address) -{ - return standardConnect(address); -} - -void SocketTcp::waitConnect(const SocketAddress &address, int timeout) -{ - if (m_state == SocketState::Connected) { - return; - } - - // Initial try - try { - connect(address); - } catch (const SocketError &ex) { - if (ex.code() == SocketError::WouldBlockWrite) { - SocketListener listener{{*this, SocketListener::Write}}; - - listener.wait(timeout); - - // Socket is writable? Check if there is an error - int error = get<int>(SOL_SOCKET, SO_ERROR); - - if (error) { - throw SocketError(SocketError::System, "connect", error); - } - } else { - throw; - } - } - - m_state = SocketState::Connected; -} - -SocketTcp SocketTcp::waitAccept(int timeout) -{ - SocketAddress dummy; - - return waitAccept(dummy, timeout); -} - -SocketTcp SocketTcp::waitAccept(SocketAddress &info, int timeout) -{ - SocketListener listener{{*this, SocketListener::Read}}; - - listener.wait(timeout); - - return accept(info); -} - -unsigned SocketTcp::recv(void *data, unsigned dataLen) -{ - int nbread; - - nbread = ::recv(m_handle, (Socket::Arg)data, dataLen, 0); - if (nbread == Error) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - throw SocketError(SocketError::WouldBlockRead, "recv", error); - } - - throw SocketError(SocketError::System, "recv", error); -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - throw SocketError(SocketError::WouldBlockRead, "recv"); - } - - throw SocketError(SocketError::System, "recv"); -#endif - } else if (nbread == 0) { - m_state = SocketState::Closed; - } - - return (unsigned)nbread; -} - -unsigned SocketTcp::waitRecv(void *data, unsigned length, int timeout) -{ - SocketListener listener{{*this, SocketListener::Read}}; - - listener.wait(timeout); - - return recv(data, length); -} - -unsigned SocketTcp::send(const void *data, unsigned length) -{ - int nbsent; - - nbsent = ::send(m_handle, (Socket::ConstArg)data, length, 0); - if (nbsent == Error) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - throw SocketError(SocketError::WouldBlockWrite, "send", error); - } - - throw SocketError(SocketError::System, "send", error); -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - throw SocketError(SocketError::WouldBlockWrite, "send"); - } - - throw SocketError(SocketError::System, "send"); -#endif - } - - return (unsigned)nbsent; -} - -unsigned SocketTcp::waitSend(const void *data, unsigned length, int timeout) -{ - SocketListener listener{{*this, SocketListener::Write}}; - - listener.wait(timeout); - - return send(data, length); -}
--- a/libcommon/malikania/Sockets.cpp Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,713 +0,0 @@ -/* - * Sockets.cpp -- portable C++ socket wrappers - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#define TIMEOUT_MSG "operation timeout" - -#include <algorithm> -#include <atomic> -#include <cstring> -#include <mutex> - -#include "Sockets.h" - -namespace malikania { - -namespace net { - -/* - * Portable constants - * ------------------------------------------------------------------ - */ - -/* {{{ Constants */ - -#if defined(_WIN32) - -const Handle Invalid{INVALID_SOCKET}; -const int Failure{SOCKET_ERROR}; - -#else - -const Handle Invalid{-1}; -const int Failure{-1}; - -#endif - -/* }}} */ - -/* - * Portable functions - * ------------------------------------------------------------------ - */ - -/* {{{ Functions */ - -#if defined(_WIN32) - -namespace { - -static std::mutex s_mutex; -static std::atomic<bool> s_initialized{false}; - -} // !namespace - -#endif // !_WIN32 - -void init() noexcept -{ -#if defined(_WIN32) - std::lock_guard<std::mutex> lock(s_mutex); - - if (!s_initialized) { - s_initialized = true; - - WSADATA wsa; - WSAStartup(MAKEWORD(2, 2), &wsa); - - /* - * If SOCKET_WSA_NO_INIT is not set then the user - * must also call finish himself. - */ -#if !defined(SOCKET_NO_AUTO_INIT) - atexit(finish); -#endif - } -#endif -} - -void finish() noexcept -{ -#if defined(_WIN32) - WSACleanup(); -#endif -} - -std::string error(int errn) -{ -#if defined(_WIN32) - LPSTR str = nullptr; - std::string errmsg = "Unknown error"; - - FormatMessageA( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - NULL, - errn, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPSTR)&str, 0, NULL); - - - if (str) { - errmsg = std::string(str); - LocalFree(str); - } - - return errmsg; -#else - return strerror(errn); -#endif -} - -std::string error() -{ -#if defined(_WIN32) - return error(WSAGetLastError()); -#else - return error(errno); -#endif -} - -/* }}} */ - -/* - * SSL stuff - * ------------------------------------------------------------------ - */ - -/* {{{ SSL initialization */ - -#if !defined(SOCKET_NO_SSL) - -namespace ssl { - -namespace { - -std::mutex mutex; -std::atomic<bool> initialized{false}; - -} // !namespace - -void finish() noexcept -{ - ERR_free_strings(); -} - -void init() noexcept -{ - std::lock_guard<std::mutex> lock{mutex}; - - if (!initialized) { - initialized = true; - - SSL_library_init(); - SSL_load_error_strings(); - OpenSSL_add_all_algorithms(); - -#if !defined(SOCKET_NO_AUTO_SSL_INIT) - atexit(finish); -#endif // SOCKET_NO_AUTO_SSL_INIT - } -} - -} // !ssl - -#endif // SOCKET_NO_SSL - -/* }}} */ - -/* - * Error class - * ------------------------------------------------------------------ - */ - -/* {{{ Error */ - -Error::Error(Code code, std::string function) - : m_code{code} - , m_function{std::move(function)} - , m_error{error()} -{ -} - -Error::Error(Code code, std::string function, int n) - : m_code{code} - , m_function{std::move(function)} - , m_error{error(n)} -{ -} - -Error::Error(Code code, std::string function, std::string error) - : m_code{code} - , m_function{std::move(function)} - , m_error{std::move(error)} -{ -} - -/* }}} */ - -/* - * Predefine addressed to be used - * ------------------------------------------------------------------ - */ - -/* {{{ Addresses */ - -namespace address { - -/* Default domain */ -int Ip::m_default{AF_INET}; - -Ip::Ip(Type domain) noexcept - : m_domain(static_cast<int>(domain)) -{ - assert(m_domain == AF_INET6 || m_domain == AF_INET); - - if (m_domain == AF_INET6) { - std::memset(&m_sin6, 0, sizeof (sockaddr_in6)); - } else { - std::memset(&m_sin, 0, sizeof (sockaddr_in)); - } -} - -Ip::Ip(const std::string &host, int port, Type domain) - : m_domain(static_cast<int>(domain)) -{ - assert(m_domain == AF_INET6 || m_domain == AF_INET); - - if (host == "*") { - if (m_domain == AF_INET6) { - std::memset(&m_sin6, 0, sizeof (sockaddr_in6)); - - m_length = sizeof (sockaddr_in6); - m_sin6.sin6_addr = in6addr_any; - m_sin6.sin6_family = AF_INET6; - m_sin6.sin6_port = htons(port); - } else { - std::memset(&m_sin, 0, sizeof (sockaddr_in)); - - m_length = sizeof (sockaddr_in); - m_sin.sin_addr.s_addr = INADDR_ANY; - m_sin.sin_family = AF_INET; - m_sin.sin_port = htons(port); - } - } else { - addrinfo hints, *res; - - std::memset(&hints, 0, sizeof (addrinfo)); - hints.ai_family = domain; - - auto error = getaddrinfo(host.c_str(), std::to_string(port).c_str(), &hints, &res); - if (error != 0) { - throw Error{Error::System, "getaddrinfo", gai_strerror(error)}; - } - - if (m_domain == AF_INET6) { - std::memcpy(&m_sin6, res->ai_addr, res->ai_addrlen); - } else { - std::memcpy(&m_sin, res->ai_addr, res->ai_addrlen); - } - - m_length = res->ai_addrlen; - freeaddrinfo(res); - } -} - -Ip::Ip(const sockaddr_storage *ss, socklen_t length) noexcept - : m_length{length} - , m_domain{ss->ss_family} -{ - assert(ss->ss_family == AF_INET6 || ss->ss_family == AF_INET); - - if (ss->ss_family == AF_INET6) { - std::memcpy(&m_sin6, ss, length); - } else if (ss->ss_family == AF_INET) { - std::memcpy(&m_sin, ss, length); - } -} - -#if !defined(_WIN32) - -Local::Local() noexcept -{ - std::memset(&m_sun, 0, sizeof (sockaddr_un)); -} - -Local::Local(std::string path, bool rm) noexcept - : m_path{std::move(path)} -{ - /* Silently remove the file even if it fails */ - if (rm) { - ::remove(m_path.c_str()); - } - - /* Copy the path */ - std::memset(m_sun.sun_path, 0, sizeof (m_sun.sun_path)); - std::strncpy(m_sun.sun_path, m_path.c_str(), sizeof (m_sun.sun_path) - 1); - - /* Set the parameters */ - m_sun.sun_family = AF_LOCAL; -} - -Local::Local(const sockaddr_storage *ss, socklen_t length) noexcept -{ - assert(ss->ss_family == AF_LOCAL); - - if (ss->ss_family == AF_LOCAL) { - std::memcpy(&m_sun, ss, length); - m_path = reinterpret_cast<const sockaddr_un &>(m_sun).sun_path; - } -} - -#endif // !_WIN32 - -} // !address - -/* }}} */ - -/* - * Select - * ------------------------------------------------------------------ - */ - -/* {{{ Select */ - -std::vector<ListenerStatus> Select::wait(const ListenerTable &table, int ms) -{ - timeval maxwait, *towait; - fd_set readset; - fd_set writeset; - - FD_ZERO(&readset); - FD_ZERO(&writeset); - - Handle max = 0; - - for (const auto &pair : table) { - if ((pair.second & Condition::Readable) == Condition::Readable) { - FD_SET(pair.first, &readset); - } - if ((pair.second & Condition::Writable) == Condition::Writable) { - FD_SET(pair.first, &writeset); - } - - if (pair.first > max) { - max = pair.first; - } - } - - maxwait.tv_sec = 0; - maxwait.tv_usec = ms * 1000; - - // Set to nullptr for infinite timeout. - towait = (ms < 0) ? nullptr : &maxwait; - - auto error = ::select(max + 1, &readset, &writeset, nullptr, towait); - if (error == Failure) { - throw Error{Error::System, "select"}; - } - if (error == 0) { - throw Error{Error::Timeout, "select", TIMEOUT_MSG}; - } - - std::vector<ListenerStatus> sockets; - - for (const auto &pair : table) { - if (FD_ISSET(pair.first, &readset)) { - sockets.push_back(ListenerStatus{pair.first, Condition::Readable}); - } - if (FD_ISSET(pair.first, &writeset)) { - sockets.push_back(ListenerStatus{pair.first, Condition::Writable}); - } - } - - return sockets; -} - -/* }}} */ - -/* - * Poll - * ------------------------------------------------------------------ - */ - -/* {{{ Poll */ - -/* - * Poll implementation - * ------------------------------------------------------------------ - */ - -#if defined(SOCKET_HAVE_POLL) - -#if defined(_WIN32) -# define poll WSAPoll -#endif - -short Poll::toPoll(Condition condition) const noexcept -{ - short result(0); - - if ((condition & Condition::Readable) == Condition::Readable) { - result |= POLLIN; - } - if ((condition & Condition::Writable) == Condition::Writable) { - result |= POLLOUT; - } - - return result; -} - -Condition Poll::toCondition(short &event) const noexcept -{ - Condition condition{Condition::None}; - - /* - * Poll implementations mark the socket differently regarding - * the disconnection of a socket. - * - * At least, even if POLLHUP or POLLIN is set, recv() always - * return 0 so we mark the socket as readable. - */ - if ((event & POLLIN) || (event & POLLHUP)) { - condition |= Condition::Readable; - } - if (event & POLLOUT) { - condition |= Condition::Writable; - } - - /* Reset event for safety */ - event = 0; - - return condition; -} - -void Poll::set(const ListenerTable &, Handle h, Condition condition, bool add) -{ - if (add) { - m_fds.push_back(pollfd{h, toPoll(condition), 0}); - } else { - auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const pollfd &pfd) { - return pfd.fd == h; - }); - - it->events |= toPoll(condition); - } -} - -void Poll::unset(const ListenerTable &, Handle h, Condition condition, bool remove) -{ - auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const pollfd &pfd) { - return pfd.fd == h; - }); - - if (remove) { - m_fds.erase(it); - } else { - it->events &= ~(toPoll(condition)); - } -} - -std::vector<ListenerStatus> Poll::wait(const ListenerTable &, int ms) -{ - auto result = poll(m_fds.data(), m_fds.size(), ms); - if (result == 0) { - throw Error{Error::Timeout, "select", TIMEOUT_MSG}; - } - if (result < 0) { - throw Error{Error::System, "poll"}; - } - - std::vector<ListenerStatus> sockets; - for (auto &fd : m_fds) { - if (fd.revents != 0) { - sockets.push_back(ListenerStatus{fd.fd, toCondition(fd.revents)}); - } - } - - return sockets; -} - -#endif // !SOCKET_HAVE_POLL - -/* }}} */ - -/* - * Epoll implementation - * ------------------------------------------------------------------ - */ - -/* {{{ Epoll */ - -#if defined(SOCKET_HAVE_EPOLL) - -uint32_t Epoll::toEpoll(Condition condition) const noexcept -{ - uint32_t events = 0; - - if ((condition & Condition::Readable) == Condition::Readable) { - events |= EPOLLIN; - } - if ((condition & Condition::Writable) == Condition::Writable) { - events |= EPOLLOUT; - } - - return events; -} - -Condition Epoll::toCondition(uint32_t events) const noexcept -{ - Condition condition{Condition::None}; - - if ((events & EPOLLIN) || (events & EPOLLHUP)) { - condition |= Condition::Readable; - } - if (events & EPOLLOUT) { - condition |= Condition::Writable; - } - - return condition; -} - -void Epoll::update(Handle h, int op, int eflags) -{ - epoll_event ev; - - std::memset(&ev, 0, sizeof (epoll_event)); - - ev.events = eflags; - ev.data.fd = h; - - if (epoll_ctl(m_handle, op, h, &ev) < 0) { - throw Error{Error::System, "epoll_ctl"}; - } -} - -Epoll::Epoll() - : m_handle{epoll_create1(0)} -{ - if (m_handle < 0) { - throw Error{Error::System, "epoll_create"}; - } -} - -Epoll::~Epoll() -{ - close(m_handle); -} - -/* - * For set and unset, we need to apply the whole flags required, so if the socket - * was set to Connection::Readable and user add Connection::Writable, we must - * place both. - */ -void Epoll::set(const ListenerTable &table, Handle sc, Condition condition, bool add) -{ - if (add) { - update(sc, EPOLL_CTL_ADD, toEpoll(condition)); - m_events.resize(m_events.size() + 1); - } else { - update(sc, EPOLL_CTL_MOD, toEpoll(table.at(sc) | condition)); - } -} - -/* - * Unset is a bit complicated case because Listener tells us which - * flag to remove but to update epoll descriptor we need to pass - * the effective flags that we want to be applied. - * - * So we put the same flags that are currently effective and remove the - * requested one. - */ -void Epoll::unset(const ListenerTable &table, Handle sc, Condition condition, bool remove) -{ - if (remove) { - update(sc, EPOLL_CTL_DEL, 0); - m_events.resize(m_events.size() - 1); - } else { - update(sc, EPOLL_CTL_MOD, toEpoll(table.at(sc) & ~(condition))); - } -} - -std::vector<ListenerStatus> Epoll::wait(const ListenerTable &, int ms) -{ - int ret = epoll_wait(m_handle, m_events.data(), m_events.size(), ms); - std::vector<ListenerStatus> result; - - if (ret == 0) { - throw Error{Error::Timeout, "epoll_wait", TIMEOUT_MSG}; - } - if (ret < 0) { - throw Error{Error::System, "epoll_wait"}; - } - - for (int i = 0; i < ret; ++i) { - result.push_back(ListenerStatus{m_events[i].data.fd, toCondition(m_events[i].events)}); - } - - return result; -} - -#endif // !SOCKET_HAVE_EPOLL - -/* }}} */ - -/* - * Kqueue implementation - * ------------------------------------------------------------------ - */ - -/* {{{ Kqueue */ - -#if defined(SOCKET_HAVE_KQUEUE) - -Kqueue::Kqueue() - : m_handle(kqueue()) -{ - if (m_handle < 0) { - throw Error{Error::System, "kqueue"}; - } -} - -Kqueue::~Kqueue() -{ - close(m_handle); -} - -void Kqueue::update(Handle h, int filter, int kflags) -{ - struct kevent ev; - - EV_SET(&ev, h, filter, kflags, 0, 0, nullptr); - - if (kevent(m_handle, &ev, 1, nullptr, 0, nullptr) < 0) { - throw Error{Error::System, "kevent"}; - } -} - -void Kqueue::set(const ListenerTable &, Handle h, Condition condition, bool add) -{ - if ((condition & Condition::Readable) == Condition::Readable) { - update(h, EVFILT_READ, EV_ADD | EV_ENABLE); - } - if ((condition & Condition::Writable) == Condition::Writable) { - update(h, EVFILT_WRITE, EV_ADD | EV_ENABLE); - } - - if (add) { - m_result.resize(m_result.size() + 1); - } -} - -void Kqueue::unset(const ListenerTable &, Handle h, Condition condition, bool remove) -{ - if ((condition & Condition::Readable) == Condition::Readable) { - update(h, EVFILT_READ, EV_DELETE); - } - if ((condition & Condition::Writable) == Condition::Writable) { - update(h, EVFILT_WRITE, EV_DELETE); - } - - if (remove) { - m_result.resize(m_result.size() - 1); - } -} - -std::vector<ListenerStatus> Kqueue::wait(const ListenerTable &, int ms) -{ - std::vector<ListenerStatus> sockets; - timespec ts = { 0, 0 }; - timespec *pts = (ms <= 0) ? nullptr : &ts; - - ts.tv_sec = ms / 1000; - ts.tv_nsec = (ms % 1000) * 1000000; - - int nevents = kevent(m_handle, nullptr, 0, &m_result[0], m_result.capacity(), pts); - - if (nevents == 0) { - throw Error{Error::Timeout, "kevent", TIMEOUT_MSG}; - } - if (nevents < 0) { - throw Error{Error::System, "kevent"}; - } - - for (int i = 0; i < nevents; ++i) { - sockets.push_back(ListenerStatus{ - static_cast<Handle>(m_result[i].ident), - m_result[i].filter == EVFILT_READ ? Condition::Readable : Condition::Writable - }); - } - - return sockets; -} - -#endif // !SOCKET_HAVE_KQUEUE - -/* }}} */ - -} // !net - -} // !malikania
--- a/libcommon/malikania/Sockets.h Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4092 +0,0 @@ -/* - * Sockets.h -- portable C++ socket wrappers - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _SOCKETS_H_ -#define _SOCKETS_H_ - -/** - * @file Sockets.h - * @brief Portable socket abstraction - * - * # Introduction - * - * This file is a portable networking library. - * - * ## Options - * - * The user may set the following variables before compiling these files: - * - * ### General options - * - * - **SOCKET_NO_AUTO_INIT**: (bool) Set to 0 if you don't want Socket class to - * automatically calls net::init function and net::finish functions. - * - **SOCKET_NO_SSL**: (bool) Set to 0 if you don't have access to OpenSSL library. - * - **SOCKET_NO_AUTO_SSL_INIT**: (bool) Set to 0 if you don't want Socket class with Tls to automatically init - * the OpenSSL library. You will need to call net::ssl::init and net::ssl::finish. - * - * ### Options for Listener class - * - * Feature detection, multiple implementations may be avaible, for example, Linux has poll, select and epoll. - * - * We assume that `select(2)` is always available. - * - * Of course, you can set the variables yourself if you test it with your build system. - * - * - **SOCKET_HAVE_POLL**: Defined on all BSD, Linux. Also defined on Windows - * if _WIN32_WINNT is set to 0x0600 or greater. - * - **SOCKET_HAVE_KQUEUE**: Defined on all BSD and Apple. - * - **SOCKET_HAVE_EPOLL**: Defined on Linux only. - * - **SOCKET_DEFAULT_BACKEND**: Which backend to use (e.g. `Select`). - * - * The preference priority is ordered from left to right. - * - * | System | Backend | Class name | - * |---------------|-------------------------|--------------| - * | Linux | epoll(7) | Epoll | - * | *BSD | kqueue(2) | Kqueue | - * | Windows | poll(2), select(2) | Poll, Select | - * | Mac OS X | kqueue(2) | Kqueue | - */ - -#if defined(_WIN32) -# if _WIN32_WINNT >= 0x0600 && !defined(SOCKET_HAVE_POLL) -# define SOCKET_HAVE_POLL -# endif -#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) -# if !defined(SOCKET_HAVE_KQUEUE) -# define SOCKET_HAVE_KQUEUE -# endif -# if !defined(SOCKET_HAVE_POLL) -# define SOCKET_HAVE_POLL -# endif -#elif defined(__linux__) -# if !defined(SOCKET_HAVE_EPOLL) -# define SOCKET_HAVE_EPOLL -# endif -# if !defined(SOCKET_HAVE_POLL) -# define SOCKET_HAVE_POLL -# endif -#endif - -/* - * Define SOCKET_DEFAULT_BACKEND - * ------------------------------------------------------------------ - */ - -/** - * Defines the default Listener backend to use. - * - * @note Do not rely on the value shown in doxygen. - */ -#if defined(_WIN32) -# if !defined(SOCKET_DEFAULT_BACKEND) -# if defined(SOCKET_HAVE_POLL) -# define SOCKET_DEFAULT_BACKEND Poll -# else -# define SOCKET_DEFAULT_BACKEND Select -# endif -# endif -#elif defined(__linux__) -# include <sys/epoll.h> - -# if !defined(SOCKET_DEFAULT_BACKEND) -# define SOCKET_DEFAULT_BACKEND Epoll -# endif -#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__) -# include <sys/types.h> -# include <sys/event.h> -# include <sys/time.h> - -# if !defined(SOCKET_DEFAULT_BACKEND) -# define SOCKET_DEFAULT_BACKEND Kqueue -# endif -#else -# if !defined(SOCKET_DEFAULT_BACKEND) -# define SOCKET_DEFAULT_BACKEND Select -# endif -#endif - -#if defined(SOCKET_HAVE_POLL) && !defined(_WIN32) -# include <poll.h> -#endif - -/* - * Headers to include - * ------------------------------------------------------------------ - */ - -#if defined(_WIN32) -# include <cstdlib> - -# include <WinSock2.h> -# include <WS2tcpip.h> -#else -# include <cerrno> - -# include <sys/ioctl.h> -# include <sys/types.h> -# include <sys/socket.h> -# include <sys/un.h> - -# include <arpa/inet.h> - -# include <netinet/in.h> -# include <netinet/tcp.h> - -# include <fcntl.h> -# include <netdb.h> -# include <unistd.h> -#endif - -#if !defined(SOCKET_NO_SSL) -# include <openssl/err.h> -# include <openssl/evp.h> -# include <openssl/ssl.h> -#endif - -#include <cassert> -#include <chrono> -#include <cstdlib> -#include <cstring> -#include <exception> -#include <functional> -#include <map> -#include <memory> -#include <string> -#include <vector> - -namespace malikania { - -/** - * General network namespace. - */ -namespace net { - -/* - * Portables types - * ------------------------------------------------------------------ - * - * The following types are defined differently between Unix and Windows. - */ - -/* {{{ Protocols */ - -#if defined(_WIN32) - -/** - * Socket type, SOCKET. - */ -using Handle = SOCKET; - -/** - * Argument to pass to set. - */ -using ConstArg = const char *; - -/** - * Argument to pass to get. - */ -using Arg = char *; - -#else - -/** - * Socket type, int. - */ -using Handle = int; - -/** - * Argument to pass to set. - */ -using ConstArg = const void *; - -/** - * Argument to pass to get. - */ -using Arg = void *; - -#endif - -/* }}} */ - -/* - * Portable constants - * ------------------------------------------------------------------ - * - * These constants are needed to check functions return codes, they are rarely needed in end user code. - */ - -/* {{{ Constants */ - -/* - * The following constants are defined differently from Unix - * to Windows. - */ -#if defined(_WIN32) - -/** - * Socket creation failure or invalidation. - */ -extern const Handle Invalid; - -/** - * Socket operation failure. - */ -extern const int Failure; - -#else - -/** - * Socket creation failure or invalidation. - */ -extern const int Invalid; - -/** - * Socket operation failure. - */ -extern const int Failure; - -#endif - -#if !defined(SOCKET_NO_SSL) - -namespace ssl { - -/** - * @enum Method - * @brief Which OpenSSL method to use. - */ -enum Method { - Tlsv1, //!< TLS v1.2 (recommended) - Sslv3 //!< SSLv3 -}; - -} // !ssl - -#endif - -/* }}} */ - -/* - * Portable functions - * ------------------------------------------------------------------ - * - * The following free functions can be used to initialize the library or to get the last system error. - */ - -/* {{{ Functions */ - -/** - * Initialize the socket library. Except if you defined SOCKET_NO_AUTO_INIT, you don't need to call this - * function manually. - */ -void init() noexcept; - -/** - * Close the socket library. - */ -void finish() noexcept; - -#if !defined(SOCKET_NO_SSL) - -/** - * OpenSSL namespace. - */ -namespace ssl { - -/** - * Initialize the OpenSSL library. Except if you defined SOCKET_NO_AUTO_SSL_INIT, you don't need to call this function - * manually. - */ -void init() noexcept; - -/** - * Close the OpenSSL library. - */ -void finish() noexcept; - -} // !ssl - -#endif // SOCKET_NO_SSL - -/** - * Get the last socket system error. The error is set from errno or from - * WSAGetLastError on Windows. - * - * @return a string message - */ -std::string error(); - -/** - * Get the last system error. - * - * @param errn the error number (errno or WSAGetLastError) - * @return the error - */ -std::string error(int errn); - -/* }}} */ - -/* - * Error class - * ------------------------------------------------------------------ - * - * This is the main exception thrown on socket operations. - */ - -/* {{{ Error */ - -/** - * @class Error - * @brief Base class for sockets error - */ -class Error : public std::exception { -public: - /** - * @enum Code - * @brief Which kind of error - */ - enum Code { - Timeout, ///!< The action did timeout - System, ///!< There is a system error - Other ///!< Other custom error - }; - -private: - Code m_code; - std::string m_function; - std::string m_error; - -public: - /** - * Constructor that use the last system error. - * - * @param code which kind of error - * @param function the function name - */ - Error(Code code, std::string function); - - /** - * Constructor that use the system error set by the user. - * - * @param code which kind of error - * @param function the function name - * @param error the error - */ - Error(Code code, std::string function, int error); - - /** - * Constructor that set the error specified by the user. - * - * @param code which kind of error - * @param function the function name - * @param error the error - */ - Error(Code code, std::string function, std::string error); - - /** - * Get which function has triggered the error. - * - * @return the function name (e.g connect) - */ - inline const std::string &function() const noexcept - { - return m_function; - } - - /** - * The error code. - * - * @return the code - */ - inline Code code() const noexcept - { - return m_code; - } - - /** - * Get the error (only the error content). - * - * @return the error - */ - const char *what() const noexcept - { - return m_error.c_str(); - } -}; - -/* }}} */ - -/* - * State class - * ------------------------------------------------------------------ - * - * To facilitate higher-level stuff, the socket has a state. - */ - -/* {{{ State */ - -/** - * @enum State - * @brief Current socket state. - */ -enum class State { - Open, //!< Socket is open - Bound, //!< Socket is bound to an address - Connecting, //!< The connection is in progress - Connected, //!< Connection is complete - Accepted, //!< Socket has been accepted (client) - Accepting, //!< The client acceptation is in progress - Closed, //!< The socket has been closed - Disconnected, //!< The connection was lost -}; - -/* }}} */ - -/* - * Action enum - * ------------------------------------------------------------------ - * - * Defines the pending operation. - */ - -/* {{{ Action */ - -/** - * @enum Action - * @brief Define the current operation that must complete. - * - * Some operations like accept, connect, recv or send must sometimes do several round-trips to complete and the socket - * action is set with that enumeration. The user is responsible of calling accept, connect send or recv until the - * operation is complete. - * - * Note: the user must wait for the appropriate condition in Socket::condition to check if the required condition is - * met. - * - * It is important to complete the operation in the correct order because protocols like Tls may require to continue - * re-negociating when calling Socket::send or Socket::Recv. - */ -enum class Action { - None, //!< No action is required, socket is ready - Accept, //!< The socket is not yet accepted, caller must call accept() again - Connect, //!< The socket is not yet connected, caller must call connect() again - Receive, //!< The received operation has not succeeded yet, caller must call recv() or recvfrom() again - Send //!< The send operation has not succeded yet, caller must call send() or sendto() again -}; - -/* }}} */ - -/* - * Condition enum - * ------------------------------------------------------------------ - * - * Defines if we must wait for reading or writing. - */ - -/* {{{ Condition */ - -/** - * @enum Condition - * @brief Define the required condition for the socket. - * - * As explained in Action enumeration, some operations required to be called several times, before calling these - * operations, the user must wait the socket to be readable or writable. This can be checked with Socket::condition. - */ -enum class Condition { - None, //!< No condition is required - Readable = (1 << 0), //!< The socket must be readable - Writable = (1 << 1) //!< The socket must be writable -}; - -/** - * Apply bitwise XOR. - * - * @param v1 the first value - * @param v2 the second value - * @return the new value - */ -constexpr Condition operator^(Condition v1, Condition v2) noexcept -{ - return static_cast<Condition>(static_cast<int>(v1) ^ static_cast<int>(v2)); -} - -/** - * Apply bitwise AND. - * - * @param v1 the first value - * @param v2 the second value - * @return the new value - */ -constexpr Condition operator&(Condition v1, Condition v2) noexcept -{ - return static_cast<Condition>(static_cast<int>(v1) & static_cast<int>(v2)); -} - -/** - * Apply bitwise OR. - * - * @param v1 the first value - * @param v2 the second value - * @return the new value - */ -constexpr Condition operator|(Condition v1, Condition v2) noexcept -{ - return static_cast<Condition>(static_cast<int>(v1) | static_cast<int>(v2)); -} - -/** - * Apply bitwise NOT. - * - * @param v the value - * @return the complement - */ -constexpr Condition operator~(Condition v) noexcept -{ - return static_cast<Condition>(~static_cast<int>(v)); -} - -/** - * Assign bitwise OR. - * - * @param v1 the first value - * @param v2 the second value - * @return the new value - */ -inline Condition &operator|=(Condition &v1, Condition v2) noexcept -{ - v1 = static_cast<Condition>(static_cast<int>(v1) | static_cast<int>(v2)); - - return v1; -} - -/** - * Assign bitwise AND. - * - * @param v1 the first value - * @param v2 the second value - * @return the new value - */ -inline Condition &operator&=(Condition &v1, Condition v2) noexcept -{ - v1 = static_cast<Condition>(static_cast<int>(v1) & static_cast<int>(v2)); - - return v1; -} - -/** - * Assign bitwise XOR. - * - * @param v1 the first value - * @param v2 the second value - * @return the new value - */ -inline Condition &operator^=(Condition &v1, Condition v2) noexcept -{ - v1 = static_cast<Condition>(static_cast<int>(v1) ^ static_cast<int>(v2)); - - return v1; -} - -/* }}} */ - -/* - * Base Socket class - * ------------------------------------------------------------------ - * - * This base class has operations that are common to all types of sockets but you usually instanciate - * a SocketTcp or SocketUdp - */ - -/* {{{ Socket */ - -/** - * @class Socket - * @brief Base socket class for socket operations. - * - * **Important:** When using non-blocking sockets, some considerations must be taken. See the implementation of the - * underlying protocol for more details. - * - * @see protocol::Tls - * @see protocol::Tcp - * @see protocol::Udp - */ -template <typename Address, typename Protocol> -class Socket { -private: - Protocol m_proto; - State m_state{State::Closed}; - Action m_action{Action::None}; - Condition m_condition{Condition::None}; - -protected: - /** - * The native handle. - */ - Handle m_handle{Invalid}; - -public: - /** - * Create a socket handle. - * - * This is the primary function and the only one that creates the socket handle, all other constructors - * are just overloaded functions. - * - * @param domain the domain AF_* - * @param type the type SOCK_* - * @param protocol the protocol - * @param iface the implementation - * @throw net::Error on errors - * @post state is set to Open - * @post handle is not set to Invalid - */ - Socket(int domain, int type, int protocol, Protocol iface = {}) - : m_proto(std::move(iface)) - { -#if !defined(SOCKET_NO_AUTO_INIT) - init(); -#endif - m_handle = ::socket(domain, type, protocol); - - if (m_handle == Invalid) { - throw Error{Error::System, "socket"}; - } - - m_proto.create(*this); - m_state = State::Open; - - assert(m_handle != Invalid); - } - - /** - * This tries to create a socket. - * - * Domain and type are determined by the Address and Protocol object. - * - * @param protocol the protocol - * @param address which type of address - * @throw net::Error on errors - */ - explicit inline Socket(Protocol protocol = {}, const Address &address = {}) - : Socket{address.domain(), protocol.type(), 0, std::move(protocol)} - { - } - - /** - * Construct a socket with an already created descriptor. - * - * @param handle the native descriptor - * @param state specify the socket state - * @param protocol the type of socket implementation - * @post action is set to None - * @post condition is set to None - */ - explicit inline Socket(Handle handle, State state = State::Closed, Protocol protocol = {}) noexcept - : m_proto(std::move(protocol)) - , m_state{state} - , m_handle{handle} - { - assert(m_action == Action::None); - assert(m_condition == Condition::None); - } - - /** - * Create an invalid socket. Can be used when you cannot instanciate the socket immediately. - */ - explicit inline Socket(std::nullptr_t) noexcept - : m_handle{Invalid} - { - } - - /** - * Copy constructor deleted. - */ - Socket(const Socket &) = delete; - - /** - * Transfer ownership from other to this. - * - * @param other the other socket - */ - inline Socket(Socket &&other) noexcept - : m_proto(std::move(other.m_proto)) - , m_state{other.m_state} - , m_action{other.m_action} - , m_condition{other.m_condition} - , m_handle{other.m_handle} - { - /* Invalidate other */ - other.m_handle = Invalid; - other.m_state = State::Closed; - other.m_action = Action::None; - other.m_condition = Condition::None; - } - - /** - * Default destructor. - */ - virtual ~Socket() - { - close(); - } - - /** - * Access the implementation. - * - * @return the implementation - * @warning use this function with care - */ - inline const Protocol &protocol() const noexcept - { - return m_proto; - } - - /** - * Overloaded function. - * - * @return the implementation - */ - inline Protocol &protocol() noexcept - { - return m_proto; - } - - /** - * Get the current socket state. - * - * @return the state - */ - inline State state() const noexcept - { - return m_state; - } - - /** - * Change the current socket state. - * - * @param state the new state - * @warning only implementations should call this function - */ - inline void setState(State state) noexcept - { - m_state = state; - } - - /** - * Get the pending operation. - * - * @return the action to complete before continuing - * @note usually only needed in non-blocking sockets - */ - inline Action action() const noexcept - { - return m_action; - } - - /** - * Change the pending operation. - * - * @param action the action - * @warning you should not call this function yourself - */ - inline void setAction(Action action) noexcept - { - m_action = action; - } - - /** - * Get the condition to wait for. - * - * @return the condition - */ - inline Condition condition() const noexcept - { - return m_condition; - } - - /** - * Change the condition required. - * - * @param condition the condition - * @warning you should not call this function yourself - */ - inline void setCondition(Condition condition) noexcept - { - m_condition = condition; - } - - /** - * Set an option for the socket. Wrapper of setsockopt(2). - * - * @param level the setting level - * @param name the name - * @param arg the value - * @throw net::Error on errors - */ - template <typename Argument> - void set(int level, int name, const Argument &arg) - { - if (setsockopt(m_handle, level, name, (ConstArg)&arg, sizeof (arg)) == Failure) { - throw Error{Error::System, "set"}; - } - } - - /** - * Object-oriented option setter. - * - * The object must have `set(Socket<Address, Protocol> &) const`. - * - * @param option the option - * @throw net::Error on errors - */ - template <typename Option> - inline void set(const Option &option) - { - option.set(*this); - } - - /** - * Get an option for the socket. Wrapper of getsockopt(2). - * - * @param level the setting level - * @param name the name - * @throw net::Error on errors - */ - template <typename Argument> - Argument get(int level, int name) - { - Argument desired, result{}; - socklen_t size = sizeof (result); - - if (getsockopt(m_handle, level, name, (Arg)&desired, &size) == Failure) { - throw Error{Error::System, "get"}; - } - - std::memcpy(&result, &desired, size); - - return result; - } - - /** - * Object-oriented option getter. - * - * The object must have `T get(Socket<Address, Protocol> &) const`, T can be any type and it is the value - * returned from this function. - * - * @return the same value as get() in the option - * @throw net::Error on errors - */ - template <typename Option> - inline auto get() -> decltype(std::declval<Option>().get(*this)) - { - return Option{}.get(*this); - } - - /** - * Get the native handle. - * - * @return the handle - * @warning Not portable - */ - inline Handle handle() const noexcept - { - return m_handle; - } - - /** - * Bind using a native address. - * - * @param address the address - * @param length the size - * @pre state must not be Bound - * @throw net::Error on errors - */ - void bind(const sockaddr *address, socklen_t length) - { - assert(m_state != State::Bound); - - if (::bind(m_handle, address, length) == Failure) { - throw Error{Error::System, "bind"}; - } - - m_state = State::Bound; - } - - /** - * Overload that takes an address. - * - * @param address the address - * @throw net::Error on errors - */ - inline void bind(const Address &address) - { - bind(address.address(), address.length()); - } - - /** - * Listen for pending connection. - * - * @param max the maximum number - * @pre state must be Bound - * @throw net::Error on errors - */ - inline void listen(int max = 128) - { - assert(m_state == State::Bound); - - if (::listen(this->m_handle, max) == Failure) { - throw Error{Error::System, "listen"}; - } - } - - /** - * Connect to the address. - * - * If connection cannot be established immediately, connect with no argument must be called again. See - * the underlying protocol for more information. - * - * @pre state must be State::Open - * @param address the address - * @param length the the address length - * @throw net::Error on errors - * @post state is set to State::Connecting or State::Connected - * @note For non-blocking sockets, see the underlying protocol function for more details - */ - void connect(const sockaddr *address, socklen_t length) - { - assert(m_state == State::Open); - - m_action = Action::None; - m_condition = Condition::None; - - m_proto.connect(*this, address, length); - - assert((m_state == State::Connected && m_action == Action::None && m_condition == Condition::None) || - (m_state == State::Connecting && m_action == Action::Connect && m_condition != Condition::None)); - } - - /** - * Overloaded function. - * - * Effectively call connect(address.address(), address.length()); - * - * @param address the address - */ - inline void connect(const Address &address) - { - connect(address.address(), address.length()); - } - - /** - * Continue the connection, only required with non-blocking sockets. - * - * @pre state must be State::Connecting - * @throw net::Error on errors - */ - void connect() - { - assert(m_state == State::Connecting); - - m_action = Action::None; - m_condition = Condition::None; - - m_proto.connect(*this); - - assert((m_state == State::Connected && m_action == Action::None && m_condition == Condition::None) || - (m_state == State::Connecting && m_action == Action::Connect && m_condition != Condition::None)); - } - - /** - * Accept a new client. If there are no pending connection, throws an error. - * - * If the client cannot be accepted immediately, the client is returned and accept with no arguments - * must be called on it. See the underlying protocol for more information. - * - * @pre state must be State::Bound - * @param info the address where to store client's information (optional) - * @return the new socket - * @throw Error on errors - * @post returned client's state is set to State::Accepting or State::Accepted - * @note For non-blocking sockets, see the underlying protocol function for more details - */ - Socket<Address, Protocol> accept(Address *info) - { - assert(m_state == State::Bound); - - m_action = Action::None; - m_condition = Condition::None; - - sockaddr_storage storage; - socklen_t length = sizeof (storage); - - Socket<Address, Protocol> sc = m_proto.accept(*this, reinterpret_cast<sockaddr *>(&storage), &length); - - if (info) { - *info = Address{&storage, length}; - } - - /* Master do not change */ - assert(m_state == State::Bound); - assert(m_action == Action::None); - assert(m_condition == Condition::None); - - /* Client */ - assert( - (sc.state() == State::Accepting && sc.action() == Action::Accept && sc.condition() != Condition::None) || - (sc.state() == State::Accepted && sc.action() == Action::None && sc.condition() == Condition::None) - ); - - return sc; - } - - /** - * Continue the accept process on this client. This function must be called only when the socket is - * ready to be readable or writable! (see condition). - * - * @pre state must be State::Accepting - * @throw Error on errors - * @post if connection is complete, state is changed to State::Accepted, action and condition are unset - * @post if connection is still in progress, condition is set - */ - void accept() - { - assert(m_state == State::Accepting); - - m_action = Action::None; - m_condition = Condition::None; - - m_proto.accept(*this); - - assert( - (m_state == State::Accepting && m_action == Action::Accept && m_condition != Condition::None) || - (m_state == State::Accepted && m_action == Action::None && m_condition == Condition::None) - ); - } - - /** - * Get the local name. This is a wrapper of getsockname(). - * - * @return the address - * @throw Error on failures - * @pre state() must not be State::Closed - */ - Address address() const - { - assert(m_state != State::Closed); - - sockaddr_storage ss; - socklen_t length = sizeof (sockaddr_storage); - - if (::getsockname(m_handle, (sockaddr *)&ss, &length) == Failure) { - throw Error{Error::System, "getsockname"}; - } - - return Address(&ss, length); - } - - /** - * Receive some data. - * - * If the operation cannot be complete immediately, 0 is returned and user must call the function - * again when ready. See the underlying protocol for more information. - * - * If action is set to Action::None and result is set to 0, disconnection occured. - * - * @param data the destination buffer - * @param length the buffer length - * @pre action must not be Action::Send - * @return the number of bytes received or 0 - * @throw Error on error - * @note For non-blocking sockets, see the underlying protocol function for more details - */ - unsigned recv(void *data, unsigned length) - { - assert(m_action != Action::Send); - - m_action = Action::None; - m_condition = Condition::None; - - return m_proto.recv(*this, data, length); - } - - /** - * Overloaded function. - * - * @param count the number of bytes to receive - * @return the string - * @throw Error on error - */ - inline std::string recv(unsigned count) - { - std::string result; - - result.resize(count); - auto n = recv(const_cast<char *>(result.data()), count); - result.resize(n); - - return result; - } - - /** - * Send some data. - * - * If the operation cannot be complete immediately, 0 is returned and user must call the function - * again when ready. See the underlying protocol for more information. - * - * @param data the data buffer - * @param length the buffer length - * @return the number of bytes sent or 0 - * @pre action() must not be Flag::Receive - * @throw Error on error - * @note For non-blocking sockets, see the underlying protocol function for more details - */ - unsigned send(const void *data, unsigned length) - { - assert(m_action != Action::Receive); - - m_action = Action::None; - m_condition = Condition::None; - - return m_proto.send(*this, data, length); - } - - /** - * Overloaded function. - * - * @param data the string to send - * @return the number of bytes sent - * @throw Error on error - */ - inline unsigned send(const std::string &data) - { - return send(data.c_str(), data.size()); - } - - /** - * Send data to an end point. - * - * If the operation cannot be complete immediately, 0 is returned and user must call the function - * again when ready. See the underlying protocol for more information. - * - * @param data the buffer - * @param length the buffer length - * @param address the client address - * @param addrlen the address length - * @return the number of bytes sent - * @throw net::Error on errors - * @note For non-blocking sockets, see the underlying protocol function for more details - */ - inline unsigned sendto(const void *data, unsigned length, const sockaddr *address, socklen_t addrlen) - { - return m_proto.sendto(*this, data, length, address, addrlen); - } - - /** - * Overloaded function. - * - * @param data the buffer - * @param length the buffer length - * @param address the destination - * @return the number of bytes sent - * @throw net::Error on errors - */ - inline unsigned sendto(const void *data, unsigned length, const Address &address) - { - return sendto(data, length, address.address(), address.length()); - } - - /** - * Overloaded function. - * - * @param data the data - * @param address the address - * @return the number of bytes sent - * @throw net:;Error on errors - */ - inline unsigned sendto(const std::string &data, const Address &address) - { - return sendto(data.c_str(), data.length(), address); - } - - /** - * Receive data from an end point. - * - * If the operation cannot be complete immediately, 0 is returned and user must call the function - * again when ready. See the underlying protocol for more information. - * - * @param data the destination buffer - * @param length the buffer length - * @param address the address destination - * @param addrlen the address length (in/out) - * @return the number of bytes received - * @throw net::Error on errors - * @note For non-blocking sockets, see the underlying protocol function for more details - */ - inline unsigned recvfrom(void *data, unsigned length, sockaddr *address, socklen_t *addrlen) - { - return m_proto.recvfrom(*this, data, length, address, addrlen); - } - - /** - * Overloaded function. - * - * @param data the destination buffer - * @param length the buffer length - * @param info the address destination - * @return the number of bytes received - * @throw net::Error on errors - */ - inline unsigned recvfrom(void *data, unsigned length, Address *info = nullptr) - { - sockaddr_storage storage; - socklen_t addrlen = sizeof (sockaddr_storage); - - auto n = recvfrom(data, length, reinterpret_cast<sockaddr *>(&storage), &addrlen); - - if (info && n != 0) { - *info = Address{&storage, addrlen}; - } - - return n; - } - - /** - * Overloaded function. - * - * @param count the maximum number of bytes to receive - * @param info the client information - * @return the string - * @throw net::Error on errors - */ - std::string recvfrom(unsigned count, Address *info = nullptr) - { - std::string result; - - result.resize(count); - auto n = recvfrom(const_cast<char *>(result.data()), count, info); - result.resize(n); - - return result; - } - - /** - * Close the socket. - * - * Automatically called from the destructor. - */ - void close() - { - if (m_handle != Invalid) { -#if defined(_WIN32) - ::closesocket(m_handle); -#else - ::close(m_handle); -#endif - m_handle = Invalid; - } - - m_state = State::Closed; - m_action = Action::None; - m_condition = Condition::None; - } - - /** - * Assignment operator forbidden. - * - * @return *this - */ - Socket &operator=(const Socket &) = delete; - - /** - * Transfer ownership from other to this. The other socket is left - * invalid and will not be closed. - * - * @param other the other socket - * @return this - */ - Socket &operator=(Socket &&other) noexcept - { - m_handle = other.m_handle; - m_proto = std::move(other.m_proto); - m_state = other.m_state; - m_action = other.m_action; - m_condition = other.m_condition; - - /* Invalidate other */ - other.m_handle = Invalid; - other.m_state = State::Closed; - other.m_action = Action::None; - other.m_condition = Condition::None; - - return *this; - } -}; - -/** - * Compare two sockets. - * - * @param s1 the first socket - * @param s2 the second socket - * @return true if they equals - */ -template <typename Address, typename Protocol> -bool operator==(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() == s2.handle(); -} - -/** - * Compare two sockets. - * - * @param s1 the first socket - * @param s2 the second socket - * @return true if they are different - */ -template <typename Address, typename Protocol> -bool operator!=(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() != s2.handle(); -} - -/** - * Compare two sockets. - * - * @param s1 the first socket - * @param s2 the second socket - * @return true if s1 < s2 - */ -template <typename Address, typename Protocol> -bool operator<(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() < s2.handle(); -} - -/** - * Compare two sockets. - * - * @param s1 the first socket - * @param s2 the second socket - * @return true if s1 > s2 - */ -template <typename Address, typename Protocol> -bool operator>(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() > s2.handle(); -} - -/** - * Compare two sockets. - * - * @param s1 the first socket - * @param s2 the second socket - * @return true if s1 <= s2 - */ -template <typename Address, typename Protocol> -bool operator<=(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() <= s2.handle(); -} - -/** - * Compare two sockets. - * - * @param s1 the first socket - * @param s2 the second socket - * @return true if s1 >= s2 - */ -template <typename Address, typename Protocol> -bool operator>=(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() >= s2.handle(); -} - -/* }}} */ - -/* - * Predefined options - * ------------------------------------------------------------------ - */ - -/* {{{ Options */ - -/** - * Namespace of predefined options. - */ -namespace option { - -/* - * Options for socket - * ------------------------------------------------------------------ - */ - -/* {{{ Options for socket */ - -/** - * @class SockBlockMode - * @brief Set or get the blocking-mode for a socket. - * @warning On Windows, it's not possible to check if the socket is blocking or not. - */ -class SockBlockMode { -public: - /** - * Set to false if you want non-blocking socket. - */ - bool value{false}; - - /** - * Set the option. - * - * @param sc the socket - * @throw Error on errors - */ - template <typename Address, typename Protocol> - void set(Socket<Address, Protocol> &sc) const - { -#if defined(O_NONBLOCK) && !defined(_WIN32) - int flags; - - if ((flags = fcntl(sc.handle(), F_GETFL, 0)) < 0) { - flags = 0; - } - - if (value) { - flags &= ~(O_NONBLOCK); - } else { - flags |= O_NONBLOCK; - } - - if (fcntl(sc.handle(), F_SETFL, flags) < 0) { - throw Error{Error::System, "fcntl"}; - } -#else - unsigned long flags = (value) ? 0 : 1; - - if (ioctlsocket(sc.handle(), FIONBIO, &flags) == Failure) { - throw Error{Error::System, "fcntl"}; - } -#endif - } - - /** - * Get the option. - * - * @return the value - * @throw Error on errors - */ - template <typename Address, typename Protocol> - bool get(Socket<Address, Protocol> &sc) const - { -#if defined(O_NONBLOCK) && !defined(_WIN32) - int flags = fcntl(sc.handle(), F_GETFL, 0); - - if (flags < 0) { - throw Error{Error::System, "fcntl"}; - } - - return !(flags & O_NONBLOCK); -#else - throw Error{Error::Other, "get", "Windows API cannot let you get the blocking status of a socket"}; -#endif - } -}; - -/** - * @class SockReuseAddress - * @brief Reuse address, must be used before calling Socket::bind - */ -class SockReuseAddress { -public: - /** - * Set to true if you want to set the SOL_SOCKET/SO_REUSEADDR option. - */ - bool value{true}; - - /** - * Set the option. - * - * @param sc the socket - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const - { - sc.set(SOL_SOCKET, SO_REUSEADDR, value ? 1 : 0); - } - - /** - * Get the option. - * - * @return the value - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline bool get(Socket<Address, Protocol> &sc) const - { - return static_cast<bool>(sc.template get<int>(SOL_SOCKET, SO_REUSEADDR)); - } -}; - -/** - * @class SockSendBuffer - * @brief Set or get the output buffer. - */ -class SockSendBuffer { -public: - /** - * Set to the buffer size. - */ - int value{2048}; - - /** - * Set the option. - * - * @param sc the socket - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const - { - sc.set(SOL_SOCKET, SO_SNDBUF, value); - } - - /** - * Get the option. - * - * @return the value - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline int get(Socket<Address, Protocol> &sc) const - { - return sc.template get<int>(SOL_SOCKET, SO_SNDBUF); - } -}; - -/** - * @class SockReceiveBuffer - * @brief Set or get the input buffer. - */ -class SockReceiveBuffer { -public: - /** - * Set to the buffer size. - */ - int value{2048}; - - /** - * Set the option. - * - * @param sc the socket - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const - { - sc.set(SOL_SOCKET, SO_RCVBUF, value); - } - - /** - * Get the option. - * - * @return the value - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline int get(Socket<Address, Protocol> &sc) const - { - return sc.template get<int>(SOL_SOCKET, SO_RCVBUF); - } -}; - -/* }}} */ - -/** - * @class TcpNoDelay - * @brief Set this option if you want to disable nagle's algorithm. - */ -class TcpNoDelay { -public: - /** - * Set to true to set TCP_NODELAY option. - */ - bool value{true}; - - /** - * Set the option. - * - * @param sc the socket - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const - { - sc.set(IPPROTO_TCP, TCP_NODELAY, value ? 1 : 0); - } - - /** - * Get the option. - * - * @return the value - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline bool get(Socket<Address, Protocol> &sc) const - { - return static_cast<bool>(sc.template get<int>(IPPROTO_TCP, TCP_NODELAY)); - } -}; - -/** - * @class Ipv6Only - * @brief Control IPPROTO_IPV6/IPV6_V6ONLY - * - * Note: some systems may or not set this option by default so it's a good idea to set it in any case to either - * false or true if portability is a concern. - */ -class Ipv6Only { -public: - /** - * Set this to use only IPv6. - */ - bool value{true}; - - /** - * Set the option. - * - * @param sc the socket - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const - { - sc.set(IPPROTO_IPV6, IPV6_V6ONLY, value ? 1 : 0); - } - - /** - * Get the option. - * - * @return the value - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline bool get(Socket<Address, Protocol> &sc) const - { - return static_cast<bool>(sc.template get<int>(IPPROTO_IPV6, IPV6_V6ONLY)); - } -}; - -} // !option - -/* }}} */ - -/* - * Predefined addressed to be used - * ------------------------------------------------------------------ - * - * - Ip, - * - Local. - */ - -/* {{{ Addresses */ - -/** - * Set of predefined addresses. - */ -namespace address { - -/** - * @class Ip - * @brief Base class for IPv6 and IPv4, you can use it if you don't know in advance if you'll use IPv6 or IPv4. - */ -class Ip { -public: - /** - * @enum Type - * @brief Type of ip address. - */ - enum Type { - v4 = AF_INET, //!< AF_INET - v6 = AF_INET6 //!< AF_INET6 - }; - -private: - /* - * Default domain when using default constructors. - * - * Note: AF_INET or AF_INET6, not - */ - static int m_default; - - union { - mutable sockaddr_in m_sin; - mutable sockaddr_in6 m_sin6; - }; - - socklen_t m_length{0}; - int m_domain{AF_INET}; - -public: - /** - * Set the default domain to use when using default Ip constructor. By default, AF_INET is used. - * - * @pre domain must be Type::v4 or Type::v6 - */ - static inline void setDefault(Type domain) noexcept - { - assert(domain == Type::v4 || domain == Type::v6); - - m_default = static_cast<int>(domain); - } - - /** - * Construct using the default domain. - */ - inline Ip() noexcept - : Ip(static_cast<Type>(m_default)) - { - } - - /** - * Default initialize the Ip domain. - * - * @pre domain must be AF_INET or AF_INET6 only - * @param domain the domain (AF_INET or AF_INET6) - */ - Ip(Type domain) noexcept; - - /** - * Construct an address suitable for bind() or connect(). - * - * @pre domain must be Type::v4 or Type::v6 - * @param domain the domain (AF_INET or AF_INET6) - * @param host the host (* for any) - * @param port the port number - * @throw Error on errors - */ - Ip(const std::string &host, int port, Type domain = v4); - - /** - * Construct an address from a storage. - * - * @pre storage's domain must be AF_INET or AF_INET6 only - * @param ss the storage - * @param length the length - */ - Ip(const sockaddr_storage *ss, socklen_t length) noexcept; - - /** - * Get the domain (AF_INET or AF_INET6). - * - * @return the domain - */ - inline int domain() const noexcept - { - return m_domain; - } - - /** - * Return the underlying address, either sockaddr_in6 or sockaddr_in. - * - * @return the address - */ - inline const sockaddr *address() const noexcept - { - if (m_domain == AF_INET6) { - return reinterpret_cast<const sockaddr *>(&m_sin6); - } - - return reinterpret_cast<const sockaddr *>(&m_sin); - } - - /** - * Return the underlying address length. - * - * @return the length - */ - inline socklen_t length() const noexcept - { - return m_length; - } - - /** - * Get the port. - * - * @return the port - */ - inline int port() const noexcept - { - if (m_domain == AF_INET6) { - return ntohs(m_sin6.sin6_port); - } - - return ntohs(m_sin.sin_port); - } - - /** - * Get the IP address in textual form. - * - * @return the address - * @throw Error on errors - */ - std::string ip() const - { - char result[128]; - - std::memset(result, 0, sizeof (result)); - - if (m_domain == AF_INET6) { - if (!inet_ntop(AF_INET6, &m_sin6.sin6_addr, result, sizeof (result))) { - throw Error{Error::System, "inet_ntop"}; - } - } else { - if (!inet_ntop(AF_INET, &m_sin.sin_addr, result, sizeof (result))) { - throw Error{Error::System, "inet_ntop"}; - } - } - - return result; - } -}; - -#if !defined(_WIN32) - -/** - * @class Local - * @brief unix family sockets - * - * Create an address to a specific path. Only available on Unix. - */ -class Local { -private: - sockaddr_un m_sun; - std::string m_path; - -public: - /** - * Get the domain AF_LOCAL. - * - * @return AF_LOCAL - */ - inline int domain() const noexcept - { - return AF_LOCAL; - } - - /** - * Default constructor. - */ - Local() noexcept; - - /** - * Construct an address to a path. - * - * @param path the path - * @param rm remove the file before (default: false) - */ - Local(std::string path, bool rm = false) noexcept; - - /** - * Construct an unix address from a storage address. - * - * @pre storage's domain must be AF_LOCAL - * @param ss the storage - * @param length the length - */ - Local(const sockaddr_storage *ss, socklen_t length) noexcept; - - /** - * Get the sockaddr_un. - * - * @return the address - */ - inline const sockaddr *address() const noexcept - { - return reinterpret_cast<const sockaddr *>(&m_sun); - } - - /** - * Get the address length. - * - * @return the length - */ - inline socklen_t length() const noexcept - { -#if defined(SOCKET_HAVE_SUN_LEN) - return SUN_LEN(&m_sun); -#else - return sizeof (m_sun); -#endif - } -}; - -#endif // !_WIN32 - -} // !address - -/* }}} */ - -/* - * Predefined protocols - * ------------------------------------------------------------------ - * - * - Tcp, for standard stream connections, - * - Udp, for standard datagram connections, - * - Tls, for secure stream connections. - */ - -/* {{{ Protocols */ - -/** - * Set of predefined protocols. - */ -namespace protocol { - -/* {{{ Tcp */ - -/** - * @class Tcp - * @brief Clear TCP implementation. - * - * This is the basic TCP protocol that implements recv, send, connect and accept as wrappers of the usual - * C functions. - */ -class Tcp { -public: - /** - * Socket type. - * - * @return SOCK_STREAM - */ - inline int type() const noexcept - { - return SOCK_STREAM; - } - - /** - * Do nothing. - * - * This function is just present for compatibility, it should never be called. - */ - template <typename Address> - inline void create(Socket<Address, Tcp> &) const noexcept - { - /* No-op */ - } - - /** - * Standard connect. - * - * If the socket is marked non-blocking and the connection cannot be established immediately, then the - * following is true: - * - * - state is set to State::Connecting, - * - action is set to Action::Connect, - * - condition is set to Condition::Writable. - * - * Then the user must wait until the socket is writable and call connect() with 0 arguments. - * - * If the socket is blocking, this function blocks until the connection is complete or an error occurs, in - * that case state is either set to State::Connected or State::Disconnected but action and condition are - * not set. - * - * @param sc the socket - * @param address the address - * @param length the length - * @throw net::Error on errors - * @note Wrapper of connect(2) - */ - template <typename Address, typename Protocol> - void connect(Socket<Address, Protocol> &sc, const sockaddr *address, socklen_t length) - { - if (::connect(sc.handle(), address, length) == Failure) { - /* - * Determine if the error comes from a non-blocking connect that cannot be - * accomplished yet. - */ -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - sc.setState(State::Connecting); - sc.setAction(Action::Connect); - sc.setCondition(Condition::Writable); - } else { - sc.setState(State::Disconnected); - throw Error{Error::System, "connect", error}; - } -#else - if (errno == EINPROGRESS) { - sc.setState(State::Connecting); - sc.setAction(Action::Connect); - sc.setCondition(Condition::Writable); - } else { - sc.setState(State::Disconnected); - throw Error{Error::System, "connect"}; - } -#endif - } else { - sc.setState(State::Connected); - } - } - - /** - * Continue the connection. This function must only be called when the socket is ready for writing, - * the user is responsible of waiting for that condition. - * - * This function check for SOL_SOCKET/SO_ERROR status. - * - * If the connection is complete, status is set to State::Connected, otherwise it is set to - * State::Disconnected. In both cases, action and condition are not set. - * - * @param sc the socket - * @throw net::Error on errors - */ - template <typename Address, typename Protocol> - void connect(Socket<Address, Protocol> &sc) - { - int error = sc.template get<int>(SOL_SOCKET, SO_ERROR); - - if (error == Failure) { - sc.setState(State::Disconnected); - throw Error{Error::System, "connect", error}; - } - - sc.setState(State::Connected); - } - - /** - * Accept a clear client. - * - * If the socket is marked non-blocking and there are no pending connection, this function throws an - * error. The user must wait that the socket is readable before calling this function. - * - * If the socket is blocking, this function blocks until a new client is connected or throws an error on - * errors. - * - * If the socket is correctly returned, its state is set to State::Accepted and its action and condition - * are not set. - * - * In any case, action and condition of this socket are not set. - * - * @param sc the socket - * @param address the address destination - * @param length the address length - * @return the socket - * @throw net::Error on errors - * @note Wrapper of accept(2) - */ - template <typename Address, typename Protocol> - Socket<Address, Protocol> accept(Socket<Address, Protocol> &sc, sockaddr *address, socklen_t *length) - { - Handle handle = ::accept(sc.handle(), address, length); - - if (handle == Invalid) { - throw Error{Error::System, "accept"}; - } - - return Socket<Address, Protocol>{handle, State::Accepted}; - } - - /** - * Continue accept. - * - * This function is just present for compatibility, it should never be called. - */ - template <typename Address, typename Protocol> - inline void accept(Socket<Address, Protocol> &) const noexcept - { - /* no-op */ - } - - /** - * Receive data. - * - * If the socket is marked non-blocking and no data is available, 0 is returned and condition is set to - * Condition::Readable. If 0 is returned and condition is not set, then the state is set to - * State::Disconnected. - * - * If the socket is blocking, this function blocks until some data is available or if an error occurs. - * - * In any case, action is never set. - * - * @param sc the socket - * @param data the destination - * @param length the destination length - * @return the number of bytes read - * @throw Error on errors - * @note Wrapper of recv(2) - */ - template <typename Address> - unsigned recv(Socket<Address, Tcp> &sc, void *data, unsigned length) - { - int nbread = ::recv(sc.handle(), (Arg)data, length, 0); - - if (nbread == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - nbread = 0; - sc.setCondition(Condition::Readable); - } else { - sc.setState(State::Disconnected); - throw Error{Error::System, "recv", error}; - } -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - sc.setCondition(Condition::Readable); - } else { - sc.setState(State::Disconnected); - throw Error{Error::System, "recv"}; - } -#endif - } else if (nbread == 0) { - sc.setState(State::Disconnected); - } - - return static_cast<unsigned>(nbread); - } - - /** - * Send some data. - * - * If the socket is marked non-blocking and the operation would block, then 0 is returned and condition is set to - * Condition::Writable. - * - * If the socket is blocking, this function blocks until the data has been sent. - * - * On any other errors, this function throw net::Error. - * - * @param sc the socket - * @param data the buffer to send - * @param length the buffer length - * @return the number of bytes sent - * @throw net::Error on errors - * @note Wrapper of send(2) - */ - template <typename Address> - unsigned send(Socket<Address, Tcp> &sc, const void *data, unsigned length) - { - int nbsent = ::send(sc.handle(), (ConstArg)data, length, 0); - - if (nbsent == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - nbsent = 0; - sc.setCondition(Condition::Writable); - } else { - sc.setState(State::Disconnected); - throw Error{Error::System, "send", error}; - } -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - nbsent = 0; - sc.setCondition(Condition::Writable); - } else { - sc.setState(State::Disconnected); - throw Error{Error::System, "send"}; - } -#endif - } - - return static_cast<unsigned>(nbsent); - } -}; - -/* }}} */ - -/* {{{ Udp */ - -/** - * @class Udp - * @brief Clear UDP type. - * - * This class is the basic implementation of UDP sockets. - */ -class Udp { -public: - /** - * Socket type. - * - * @return SOCK_DGRAM - */ - inline int type() const noexcept - { - return SOCK_DGRAM; - } - - /** - * Do nothing. - */ - template <typename Address> - inline void create(Socket<Address, Udp> &) noexcept - { - /* No-op */ - } - - /** - * Receive data from an end point. - * - * If the socket is marked non-blocking and no data is available, 0 is returned and condition is set to - * Condition::Readable. - * - * If the socket is blocking, this functions blocks until some data is available or if an error occurs. - * - * @param sc the socket - * @param data the destination buffer - * @param length the buffer length - * @param address the address - * @param addrlen the initial address length - * @return the number of bytes received - * @throw Error on error - */ - template <typename Address> - unsigned recvfrom(Socket<Address, Udp> &sc, void *data, unsigned length, sockaddr *address, socklen_t *addrlen) - { - int nbread; - - nbread = ::recvfrom(sc.handle(), (Arg)data, length, 0, address, addrlen); - - if (nbread == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - nbread = 0; - sc.setCondition(Condition::Readable); - } else { - throw Error{Error::System, "recvfrom"}; - } -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - nbread = 0; - sc.setCondition(Condition::Readable); - } else { - throw Error{Error::System, "recvfrom"}; - } -#endif - } - - return static_cast<unsigned>(nbread); - } - - /** - * Send data to an end point. - * - * If the socket is marked non-blocking and the operation would block, then 0 is returned and condition is set to - * Condition::Writable. - * - * If the socket is blocking, this functions blocks until the data has been sent. - * - * @param sc the socket - * @param data the buffer - * @param length the buffer length - * @param address the client address - * @param addrlen the adderss length - * @return the number of bytes sent - * @throw Error on error - */ - template <typename Address> - unsigned sendto(Socket<Address, Udp> &sc, const void *data, unsigned length, const sockaddr *address, socklen_t addrlen) - { - int nbsent; - - nbsent = ::sendto(sc.handle(), (ConstArg)data, length, 0, address, addrlen); - if (nbsent == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - nbsent = 0; - sc.setCondition(Condition::Writable); - } else { - throw Error{Error::System, "sendto", error}; - } -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - nbsent = 0; - sc.setCondition(Condition::Writable); - } else { - throw Error{Error::System, "sendto"}; - } -#endif - } - - return static_cast<unsigned>(nbsent); - } -}; - -/* }}} */ - -/* {{{ Tls */ - -#if !defined(SOCKET_NO_SSL) - -/** - * @class Tls - * @brief OpenSSL secure layer for TCP. - * - * **Note:** This protocol is much more difficult to use with non-blocking sockets, if some operations would block, the - * user is responsible of calling the function again by waiting for the appropriate condition. See the functions for - * more details. - * - * @see Tls::accept - * @see Tls::connect - * @see Tls::recv - * @see Tls::send - */ -class Tls : private Tcp { -private: - using Context = std::shared_ptr<SSL_CTX>; - using Ssl = std::unique_ptr<SSL, void (*)(SSL *)>; - - /* OpenSSL objects */ - Context m_context; - Ssl m_ssl{nullptr, nullptr}; - - /* Status */ - bool m_tcpconnected{false}; - - /* - * User definable parameters - */ - ssl::Method m_method{ssl::Tlsv1}; - std::string m_key; - std::string m_certificate; - bool m_verify{false}; - - /* - * Construct with a context and ssl, for Tls::accept. - */ - Tls(Context context, Ssl ssl) - : m_context{std::move(context)} - , m_ssl{std::move(ssl)} - { - } - - /* - * Get the OpenSSL error message. - */ - inline std::string error(int error) - { - auto msg = ERR_reason_error_string(error); - - return msg == nullptr ? "" : msg; - } - - /* - * Update the states after an uncompleted operation. - */ - template <typename Address, typename Protocol> - inline void updateStates(Socket<Address, Protocol> &sc, State state, Action action, int code) - { - assert(code == SSL_ERROR_WANT_READ || code == SSL_ERROR_WANT_WRITE); - - sc.setState(state); - sc.setAction(action); - - if (code == SSL_ERROR_WANT_READ) { - sc.setCondition(Condition::Readable); - } else { - sc.setCondition(Condition::Writable); - } - } - - /* - * Continue the connect operation. - */ - template <typename Address, typename Protocol> - void processConnect(Socket<Address, Protocol> &sc) - { - int ret = SSL_connect(m_ssl.get()); - - if (ret <= 0) { - int no = SSL_get_error(m_ssl.get(), ret); - - if (no == SSL_ERROR_WANT_READ || no == SSL_ERROR_WANT_WRITE) { - updateStates(sc, State::Connecting, Action::Connect, no); - } else { - sc.setState(State::Disconnected); - throw Error{Error::System, "connect", error(no)}; - } - } else { - sc.setState(State::Connected); - } - } - - /* - * Continue accept. - */ - template <typename Address, typename Protocol> - void processAccept(Socket<Address, Protocol> &sc) - { - int ret = SSL_accept(m_ssl.get()); - - if (ret <= 0) { - int no = SSL_get_error(m_ssl.get(), ret); - - if (no == SSL_ERROR_WANT_READ || no == SSL_ERROR_WANT_WRITE) { - updateStates(sc, State::Accepting, Action::Accept, no); - } else { - sc.setState(State::Disconnected); - throw Error(Error::System, "accept", error(no)); - } - } else { - sc.setState(State::Accepted); - } - } - -public: - /** - * @copydoc Tcp::type - */ - inline int type() const noexcept - { - return SOCK_STREAM; - } - - /** - * Empty TLS constructor. - */ - Tls() - { -#if !defined(SOCKET_NO_SSL_AUTO_INIT) - net::ssl::init(); -#endif - } - - /** - * Set the method. - * - * @param method the method - * @pre the socket must not be already created - */ - inline void setMethod(ssl::Method method) noexcept - { - assert(!m_context); - assert(!m_ssl); - - m_method = method; - } - - /** - * Use the specified private key file. - * - * @param file the path to the private key - */ - inline void setPrivateKey(std::string file) noexcept - { - m_key = std::move(file); - } - - /** - * Use the specified certificate file. - * - * @param file the path to the file - */ - inline void setCertificate(std::string file) noexcept - { - m_certificate = std::move(file); - } - - /** - * Set to true if we must verify the certificate and private key. - * - * @param verify the mode - */ - inline void setVerify(bool verify = true) noexcept - { - m_verify = verify; - } - - /** - * Initialize the SSL objects after have created. - * - * @param sc the socket - * @throw net::Error on errors - */ - template <typename Address> - inline void create(Socket<Address, Tls> &sc) - { - auto method = (m_method == ssl::Tlsv1) ? TLSv1_method() : SSLv23_method(); - - m_context = {SSL_CTX_new(method), SSL_CTX_free}; - m_ssl = {SSL_new(m_context.get()), SSL_free}; - - SSL_set_fd(m_ssl.get(), sc.handle()); - - /* Load certificates */ - if (m_certificate.size() > 0) { - SSL_CTX_use_certificate_file(m_context.get(), m_certificate.c_str(), SSL_FILETYPE_PEM); - } - if (m_key.size() > 0) { - SSL_CTX_use_PrivateKey_file(m_context.get(), m_key.c_str(), SSL_FILETYPE_PEM); - } - if (m_verify && !SSL_CTX_check_private_key(m_context.get())) { - throw Error{Error::System, "(openssl)", "unable to verify key"}; - } - } - - /** - * Connect to a secure host. - * - * If the socket is marked non-blocking and the connection cannot be established yet, then the state is set - * to State::Connecting, the condition is set to Condition::Readable or Condition::Writable, the user must - * wait for the appropriate condition before calling the overload connect which takes 0 argument. - * - * If the socket is blocking, this functions blocks until the connection is complete. - * - * If the connection was completed correctly the state is set to State::Connected. - * - * @param sc the socket - * @param address the address - * @param length the address length - * @throw net::Error on errors - */ - template <typename Address, typename Protocol> - void connect(Socket<Address, Protocol> &sc, const sockaddr *address, socklen_t length) - { - /* 1. Connect using raw TCP */ - Tcp::connect(sc, address, length); - - /* 2. If the connection is complete (e.g. non-blocking), try handshake */ - if (sc.state() == State::Connected) { - m_tcpconnected = true; - processConnect(sc); - } - } - - /** - * Continue the connection. - * - * This function must be called when the socket is ready for reading or writing (check with Socket::condition), - * the state may change exactly like the initial connect call. - * - * @param sc the socket - * @throw net::Error on errors - */ - template <typename Address, typename Protocol> - void connect(Socket<Address, Protocol> &sc) - { - /* 1. Be sure to complete standard connect before */ - if (!m_tcpconnected) { - Tcp::connect(sc); - m_tcpconnected = sc.state() == State::Connected; - } - - if (m_tcpconnected) { - processConnect(sc); - } - } - - /** - * Accept a secure client. - * - * Because SSL needs several round-trips, if the socket is marked non-blocking and the connection is not - * completed yet, a new socket is returned but with the State::Accepting state. Its condition is set to - * Condition::Readable or Condition::Writable, the user is responsible of calling accept overload which takes - * 0 arguments on the returned socket when the condition is met. - * - * If the socket is blocking, this function blocks until the client is accepted and returned. - * - * If the client is accepted correctly, its state is set to State::Accepted. This instance does not change. - * - * @param sc the socket - * @param address the address destination - * @param length the address length - * @return the client - * @throw net::Error on errors - */ - template <typename Address> - Socket<Address, Tls> accept(Socket<Address, Tls> &sc, sockaddr *address, socklen_t *length) - { - Socket<Address, Tls> client = Tcp::accept(sc, address, length); - Tls &proto = client.protocol(); - - /* 1. Share the context */ - proto.m_context = m_context; - - /* 2. Create new SSL instance */ - proto.m_ssl = Ssl{SSL_new(m_context.get()), SSL_free}; - SSL_set_fd(proto.m_ssl.get(), client.handle()); - - /* 3. Try accept process on the **new** client */ - proto.processAccept(client); - - return client; - } - - /** - * Continue accept. - * - * This function must be called on the client that is being accepted. - * - * Like accept or connect, user is responsible of calling this function until the connection is complete. - * - * @param sc the socket - * @throw net::Error on errors - */ - template <typename Address, typename Protocol> - inline void accept(Socket<Address, Protocol> &sc) - { - processAccept(sc); - } - - /** - * Receive some secure data. - * - * If the socket is marked non-blocking, 0 is returned if no data is available yet or if the connection - * needs renegociation. If renegociation is required case, the action is set to Action::Receive and condition - * is set to Condition::Readable or Condition::Writable. The user must wait that the condition is met and - * call this function again. - * - * @param sc the socket - * @param data the destination - * @param len the buffer length - * @return the number of bytes read - * @throw net::Error on errors - */ - template <typename Address> - unsigned recv(Socket<Address, Tls> &sc, void *data, unsigned len) - { - auto nbread = SSL_read(m_ssl.get(), data, len); - - if (nbread <= 0) { - auto no = SSL_get_error(m_ssl.get(), nbread); - - if (no == SSL_ERROR_WANT_READ || no == SSL_ERROR_WANT_WRITE) { - nbread = 0; - updateStates(sc, sc.state(), Action::Receive, no); - } else { - throw Error{Error::System, "recv", error(no)}; - } - } - - return nbread; - } - - /** - * Send some data. - * - * Like recv, if the socket is marked non-blocking and no data can be sent or a negociation is required, - * condition and action are set. See receive for more details - * - * @param sc the socket - * @param data the data to send - * @param len the buffer length - * @return the number of bytes sent - * @throw net::Error on errors - */ - template <typename Address> - unsigned send(Socket<Address, Tls> &sc, const void *data, unsigned len) - { - auto nbsent = SSL_write(m_ssl.get(), data, len); - - if (nbsent <= 0) { - auto no = SSL_get_error(m_ssl.get(), nbsent); - - if (no == SSL_ERROR_WANT_READ || no == SSL_ERROR_WANT_WRITE) { - nbsent = 0; - updateStates(sc, sc.state(), Action::Send, no); - } else { - throw Error{Error::System, "send", error(no)}; - } - } - - return nbsent; - } -}; - -#endif // !SOCKET_NO_SSL - -/* }}} */ - -} // !protocol - -/* }}} */ - -/* - * Convenient helpers - * ------------------------------------------------------------------ - * - * - SocketTcp<Address>, for TCP sockets, - * - SocketUdp<Address>, for UDP sockets, - * - SocketTls<Address>, for secure TCP sockets. - */ - -/* {{{ Helpers */ - -/** - * Helper to create TCP sockets. - */ -template <typename Address> -using SocketTcp = Socket<Address, protocol::Tcp>; - -/** - * Helper to create TCP/IP sockets. - */ -using SocketTcpIp = Socket<address::Ip, protocol::Tcp>; - -#if !defined(_WIN32) - -/** - * Helper to create TCP/Local sockets. - */ -using SocketTcpLocal = Socket<address::Local, protocol::Tcp>; - -#endif - -/** - * Helper to create UDP sockets. - */ -template <typename Address> -using SocketUdp = Socket<Address, protocol::Udp>; - -/** - * Helper to create UDP/IP sockets. - */ -using SocketUdpIp = Socket<address::Ip, protocol::Udp>; - -#if !defined(SOCKET_NO_SSL) - -/** - * Helper to create OpenSSL TCP sockets. - */ -template <typename Address> -using SocketTls = Socket<Address, protocol::Tls>; - -/** - * Helper to create OpenSSL TCP/Ip sockets. - */ -using SocketTlsIp = Socket<address::Ip, protocol::Tls>; - -#endif // !SOCKET_NO_SSL - -/* }}} */ - -/* - * Select wrapper - * ------------------------------------------------------------------ - * - * Wrapper for select(2) and other various implementations. - */ - -/* {{{ Listener */ - -/** - * @class ListenerStatus - * @brief Result of polling - * - * Result of a select call, returns the first ready socket found with its - * flags. - */ -class ListenerStatus { -public: - Handle socket; //!< which socket is ready - Condition flags; //!< the flags -}; - -/** - * Table used in the socket listener to store which sockets have been - * set in which directions. - */ -using ListenerTable = std::map<Handle, Condition>; - -/** - * @class Select - * @brief Implements select(2) - * - * This class is the fallback of any other method, it is not preferred at all for many reasons. - */ -class Select { -public: - /** - * No-op, uses the ListenerTable directly. - */ - inline void set(const ListenerTable &, Handle, Condition, bool) noexcept {} - - /** - * No-op, uses the ListenerTable directly. - */ - inline void unset(const ListenerTable &, Handle, Condition, bool) noexcept {} - - /** - * Return the sockets - */ - std::vector<ListenerStatus> wait(const ListenerTable &table, int ms); - - /** - * Backend identifier - */ - inline const char *name() const noexcept - { - return "select"; - } -}; - -#if defined(SOCKET_HAVE_POLL) - -/** - * @class Poll - * @brief Implements poll(2). - * - * Poll is widely supported and is better than select(2). It is still not the - * best option as selecting the sockets is O(n). - */ -class Poll { -private: - std::vector<pollfd> m_fds; - - short toPoll(Condition flags) const noexcept; - Condition toCondition(short &event) const noexcept; - -public: - /** - * Set the handle. - */ - void set(const ListenerTable &, Handle, Condition, bool); - - /** - * Unset the handle. - */ - void unset(const ListenerTable &, Handle, Condition, bool); - - /** - * Wait for events. - */ - std::vector<ListenerStatus> wait(const ListenerTable &, int ms); - - /** - * Backend identifier - */ - inline const char *name() const noexcept - { - return "poll"; - } -}; - -#endif - -#if defined(SOCKET_HAVE_EPOLL) - -/** - * @class Epoll - * @brief Linux's epoll. - */ -class Epoll { -private: - int m_handle; - std::vector<epoll_event> m_events; - - Epoll(const Epoll &) = delete; - Epoll &operator=(const Epoll &) = delete; - Epoll(const Epoll &&) = delete; - Epoll &operator=(const Epoll &&) = delete; - - uint32_t toEpoll(Condition flags) const noexcept; - Condition toCondition(uint32_t events) const noexcept; - void update(Handle sc, int op, int eflags); - -public: - /** - * Construct the epoll instance. - */ - Epoll(); - - /** - * Close the epoll instance. - */ - ~Epoll(); - - /** - * Set the handle. - */ - void set(const ListenerTable &, Handle, Condition, bool); - - /** - * Unset the handle. - */ - void unset(const ListenerTable &, Handle, Condition, bool); - - /** - * Wait for events. - */ - std::vector<ListenerStatus> wait(const ListenerTable &, int); - - /** - * Backend identifier - */ - inline const char *name() const noexcept - { - return "epoll"; - } -}; - -#endif - -#if defined(SOCKET_HAVE_KQUEUE) - -/** - * @class Kqueue - * @brief Implements kqueue(2). - * - * This implementation is available on all BSD and Mac OS X. It is better than - * poll(2) because it's O(1), however it's a bit more memory consuming. - */ -class Kqueue { -private: - std::vector<struct kevent> m_result; - int m_handle; - - Kqueue(const Kqueue &) = delete; - Kqueue &operator=(const Kqueue &) = delete; - Kqueue(Kqueue &&) = delete; - Kqueue &operator=(Kqueue &&) = delete; - - void update(Handle sc, int filter, int kflags); - -public: - /** - * Construct the kqueue instance. - */ - Kqueue(); - - /** - * Destroy the kqueue instance. - */ - ~Kqueue(); - - /** - * Set the handle. - */ - void set(const ListenerTable &, Handle, Condition, bool); - - /** - * Unset the handle. - */ - void unset(const ListenerTable &, Handle, Condition, bool); - - /** - * Wait for events. - */ - std::vector<ListenerStatus> wait(const ListenerTable &, int); - - /** - * Backend identifier - */ - inline const char *name() const noexcept - { - return "kqueue"; - } -}; - -#endif - -/** - * @class Listener - * @brief Synchronous multiplexing - * - * Convenient wrapper around the select() system call. - * - * This class is implemented using a bridge pattern to allow different uses - * of listener implementation. - * - * You should not reinstanciate a new Listener at each iteartion of your - * main loop as it can be extremely costly. Instead use the same listener that - * you can safely modify on the fly. - * - * Currently, poll, epoll, select and kqueue are available. - * - * To implement the backend, the following functions must be available: - * - * ### Set - * - * @code - * void set(const ListenerTable &, Handle sc, Condition condition, bool add); - * @endcode - * - * This function, takes the socket to be added and the flags. The condition is - * always guaranteed to be correct and the function will never be called twice - * even if the user tries to set the same flag again. - * - * An optional add argument is added for backends which needs to do different - * operation depending if the socket was already set before or if it is the - * first time (e.g EPOLL_CTL_ADD vs EPOLL_CTL_MOD for epoll(7). - * - * ### Unset - * - * @code - * void unset(const ListenerTable &, Handle sc, Condition condition, bool remove); - * @endcode - * - * Like set, this function is only called if the condition is actually set and will - * not be called multiple times. - * - * Also like set, an optional remove argument is set if the socket is being - * completely removed (e.g no more flags are set for this socket). - * - * ### Wait - * - * @code - * std::vector<ListenerStatus> wait(const ListenerTable &, int ms); - * @endcode - * - * Wait for the sockets to be ready with the specified milliseconds. Must return a list of ListenerStatus, - * may throw any exceptions. - * - * ### Name - * - * @code - * inline const char *name() const noexcept - * @endcode - * - * Returns the backend name. Usually the class in lower case. - */ -template <typename Backend = SOCKET_DEFAULT_BACKEND> -class Listener { -private: - Backend m_backend; - ListenerTable m_table; - -public: - /** - * Construct an empty listener. - */ - Listener() = default; - - /** - * Get the backend. - * - * @return the backend - */ - inline const Backend &backend() const noexcept - { - return m_backend; - } - - /** - * Get the non-modifiable table. - * - * @return the table - */ - inline const ListenerTable &table() const noexcept - { - return m_table; - } - - /** - * Overloaded function. - * - * @return the iterator - */ - inline ListenerTable::const_iterator begin() const noexcept - { - return m_table.begin(); - } - - /** - * Overloaded function. - * - * @return the iterator - */ - inline ListenerTable::const_iterator cbegin() const noexcept - { - return m_table.cbegin(); - } - - /** - * Overloaded function. - * - * @return the iterator - */ - inline ListenerTable::const_iterator end() const noexcept - { - return m_table.end(); - } - - /** - * Overloaded function. - * - * @return the iterator - */ - inline ListenerTable::const_iterator cend() const noexcept - { - return m_table.cend(); - } - - /** - * Add or update a socket to the listener. - * - * If the socket is already placed with the appropriate flags, the - * function is a no-op. - * - * If incorrect flags are passed, the function does nothing. - * - * @param sc the socket - * @param condition the condition (may be OR'ed) - * @throw Error if the backend failed to set - */ - void set(Handle sc, Condition condition) - { - /* Invalid or useless flags */ - if (condition == Condition::None || static_cast<int>(condition) > 0x3) - return; - - auto it = m_table.find(sc); - - /* - * Do not update the table if the backend failed to add - * or update. - */ - if (it == m_table.end()) { - m_backend.set(m_table, sc, condition, true); - m_table.emplace(sc, condition); - } else { - /* Remove flag if already present */ - if ((condition & Condition::Readable) == Condition::Readable && - (it->second & Condition::Readable) == Condition::Readable) { - condition &= ~(Condition::Readable); - } - if ((condition & Condition::Writable) == Condition::Writable && - (it->second & Condition::Writable) == Condition::Writable) { - condition &= ~(Condition::Writable); - } - - /* Still need a call? */ - if (condition != Condition::None) { - m_backend.set(m_table, sc, condition, false); - it->second |= condition; - } - } - } - - /** - * Unset a socket from the listener, only the flags is removed - * unless the two flagss are requested. - * - * For example, if you added a socket for both reading and writing, - * unsetting the write flags will keep the socket for reading. - * - * @param sc the socket - * @param condition the condition (may be OR'ed) - * @see remove - */ - void unset(Handle sc, Condition condition) - { - auto it = m_table.find(sc); - - /* Invalid or useless flags */ - if (condition == Condition::None || static_cast<int>(condition) > 0x3 || it == m_table.end()) - return; - - /* - * Like set, do not update if the socket is already at the appropriate - * state. - */ - if ((condition & Condition::Readable) == Condition::Readable && - (it->second & Condition::Readable) != Condition::Readable) { - condition &= ~(Condition::Readable); - } - if ((condition & Condition::Writable) == Condition::Writable && - (it->second & Condition::Writable) != Condition::Writable) { - condition &= ~(Condition::Writable); - } - - if (condition != Condition::None) { - /* Determine if it's a complete removal */ - bool removal = ((it->second) & ~(condition)) == Condition::None; - - m_backend.unset(m_table, sc, condition, removal); - - if (removal) { - m_table.erase(it); - } else { - it->second &= ~(condition); - } - } - } - - /** - * Remove completely the socket from the listener. - * - * It is a shorthand for unset(sc, Condition::Readable | Condition::Writable); - * - * @param sc the socket - */ - inline void remove(Handle sc) - { - unset(sc, Condition::Readable | Condition::Writable); - } - - /** - * Remove all sockets. - */ - inline void clear() - { - while (!m_table.empty()) { - remove(m_table.begin()->first); - } - } - - /** - * Get the number of sockets in the listener. - */ - inline ListenerTable::size_type size() const noexcept - { - return m_table.size(); - } - - /** - * Select a socket. Waits for a specific amount of time specified as the duration. - * - * @param duration the duration - * @return the socket ready - */ - template <typename Rep, typename Ratio> - inline ListenerStatus wait(const std::chrono::duration<Rep, Ratio> &duration) - { - auto cvt = std::chrono::duration_cast<std::chrono::milliseconds>(duration); - - return m_backend.wait(m_table, cvt.count())[0]; - } - - /** - * Overload with milliseconds. - * - * @param timeout the optional timeout in milliseconds - * @return the socket ready - */ - inline ListenerStatus wait(int timeout = -1) - { - return wait(std::chrono::milliseconds(timeout)); - } - - /** - * Select multiple sockets. - * - * @param duration the duration - * @return the socket ready - */ - template <typename Rep, typename Ratio> - inline std::vector<ListenerStatus> waitMultiple(const std::chrono::duration<Rep, Ratio> &duration) - { - auto cvt = std::chrono::duration_cast<std::chrono::milliseconds>(duration); - - return m_backend.wait(m_table, cvt.count()); - } - - /** - * Overload with milliseconds. - * - * @return the socket ready - */ - inline std::vector<ListenerStatus> waitMultiple(int timeout = -1) - { - return waitMultiple(std::chrono::milliseconds(timeout)); - } -}; - -/* }}} */ - -/* - * Callback - * ------------------------------------------------------------------ - * - * Function owner with tests. - */ - -/* {{{ Callback */ - -/** - * @class Callback - * @brief Convenient signal owner that checks if the target is valid. - * - * This class also catch all errors thrown from signals to avoid interfering with our process. - */ -template <typename... Args> -class Callback : public std::function<void (Args...)> { -public: - /** - * Inherited constructors. - */ - using std::function<void (Args...)>::function; - - /** - * Execute the callback only if a target is set. - */ - void operator()(Args... args) const - { - if (*this) { - try { - std::function<void (Args...)>::operator()(args...); - } catch (...) { - } - } - } -}; - -/* }}} */ - -/* - * StreamConnection - * ------------------------------------------------------------------ - * - * Client connected on the server side. - */ - -/* {{{ StreamConnection */ - -/** - * @class StreamConnection - * @brief Connected client on the server side. - * - * This object is created from StreamServer when a new client is connected, it is the higher - * level object of sockets and completely asynchronous. - */ -template <typename Address, typename Protocol> -class StreamConnection { -public: - /** - * Called when the output has changed. - */ - using WriteHandler = Callback<>; - -private: - /* Signals */ - WriteHandler m_onWrite; - - /* Sockets and output buffer */ - Socket<Address, Protocol> m_socket; - std::string m_output; - -public: - /** - * Create the connection. - * - * @param s the socket - */ - StreamConnection(Socket<Address, Protocol> s) - : m_socket{std::move(s)} - { - m_socket.set(net::option::SockBlockMode{false}); - } - - /** - * Access the underlying socket. - * - * @return the socket - * @warning use with care - */ - inline Socket<Address, Protocol> &socket() noexcept - { - return m_socket; - } - - /** - * Access the current output. - * - * @return the output - */ - inline const std::string &output() const noexcept - { - return m_output; - } - - /** - * Overloaded function - * - * @return the output - * @warning use with care, avoid modifying the output if you don't know what you're doing - */ - inline std::string &output() noexcept - { - return m_output; - } - - /** - * Post some data to be sent asynchronously. - * - * @param str the data to append - */ - inline void send(std::string str) - { - m_output += str; - m_onWrite(); - } - - /** - * Kill the client. - */ - inline void close() - { - m_socket.close(); - } - - /** - * Set the write handler, the signal is emitted when the output has changed so that the StreamServer owner - * knows that there are some data to send. - * - * @param handler the handler - * @warning you usually never need to set this yourself - */ - inline void setWriteHandler(WriteHandler handler) - { - m_onWrite = std::move(handler); - } -}; - -/* }}} */ - -/* - * StreamServer - * ------------------------------------------------------------------ - * - * Convenient stream oriented server. - */ - -/* {{{ StreamServer */ - -/** - * @class StreamServer - * @brief Convenient stream server for TCP and TLS. - * - * This class does all the things for you as accepting new clients, listening for it and sending data. It works - * asynchronously without blocking to let you control your process workflow. - * - * This class is not thread safe and you must not call any of the functions from different threads. - */ -template <typename Address, typename Protocol> -class StreamServer { -public: - /** - * Handler when a new client is connected. - */ - using ConnectionHandler = Callback<const std::shared_ptr<StreamConnection<Address, Protocol>> &>; - - /** - * Handler when a client is disconnected. - */ - using DisconnectionHandler = Callback<const std::shared_ptr<StreamConnection<Address, Protocol>> &>; - - /** - * Handler when data has been received from a client. - */ - using ReadHandler = Callback<const std::shared_ptr<StreamConnection<Address, Protocol>> &, const std::string &>; - - /** - * Handler when data has been correctly sent to a client. - */ - using WriteHandler = Callback<const std::shared_ptr<StreamConnection<Address, Protocol>> &, const std::string &>; - - /** - * Handler when an error occured. - */ - using ErrorHandler = Callback<const Error &>; - - /** - * Handler when there was a timeout. - */ - using TimeoutHandler = Callback<>; - -private: - using ClientMap = std::map<Handle, std::shared_ptr<StreamConnection<Address, Protocol>>>; - - /* Signals */ - ConnectionHandler m_onConnection; - DisconnectionHandler m_onDisconnection; - ReadHandler m_onRead; - WriteHandler m_onWrite; - ErrorHandler m_onError; - TimeoutHandler m_onTimeout; - - /* Sockets */ - Socket<Address, Protocol> m_master; - Listener<> m_listener; - ClientMap m_clients; - - /* - * Update flags depending on the required condition. - */ - void updateFlags(std::shared_ptr<StreamConnection<Address, Protocol>> &client) - { - assert(client->socket().action() != Action::None); - - m_listener.remove(client->socket().handle()); - m_listener.set(client->socket().handle(), client->socket().condition()); - } - - /* - * Continue accept process. - */ - template <typename AcceptCall> - void processAccept(std::shared_ptr<StreamConnection<Address, Protocol>> &client, const AcceptCall &acceptFunc) - { - try { - /* Do the accept */ - acceptFunc(); - - /* 1. First remove completely the client */ - m_listener.remove(client->socket().handle()); - - /* 2. If accept is not finished, wait for the appropriate condition */ - if (client->socket().state() == State::Accepted) { - /* 3. Client is accepted, notify the user */ - m_listener.set(client->socket().handle(), Condition::Readable); - m_onConnection(client); - } else { - /* Operation still in progress */ - updateFlags(client); - } - } catch (const Error &error) { - m_clients.erase(client->socket().handle()); - m_listener.remove(client->socket().handle()); - m_onError(error); - } - } - - /* - * Process initial accept of master socket, this is the initial accepting process. Except on errors, the - * socket is stored but the user will be notified only once the socket is completely accepted. - */ - void processInitialAccept() - { - // TODO: store address too. - std::shared_ptr<StreamConnection<Address, Protocol>> client = std::make_shared<StreamConnection<Address, Protocol>>(m_master.accept(nullptr)); - std::weak_ptr<StreamConnection<Address, Protocol>> ptr{client}; - - /* 1. Register output changed to update listener */ - client->setWriteHandler([this, ptr] () { - auto client = ptr.lock(); - - /* Do not update the listener immediately if an action is pending */ - if (client && client->socket().action() == Action::None && !client->output().empty()) { - m_listener.set(client->socket().handle(), Condition::Writable); - } - }); - - /* 2. Add the client */ - m_clients.insert(std::make_pair(client->socket().handle(), client)); - - /* - * 2. Do an initial check to set the listener flags, at this moment the socket may or not be - * completely accepted. - */ - processAccept(client, [&] () {}); - } - - /* - * Read or complete the read operation. - */ - void processRead(std::shared_ptr<StreamConnection<Address, Protocol>> &client) - { - /* - * Read because there is something to read or because the pending operation is - * read and must complete. - */ - auto buffer = client->socket().recv(512); - - /* - * Now the receive operation may be completed, in that case, two possibilities: - * - * 1. The action is set to None (completed) - * 2. The action is still not complete, update the flags - */ - if (client->socket().action() == Action::None) { - /* Empty mean normal disconnection */ - if (buffer.empty()) { - m_listener.remove(client->socket().handle()); - m_clients.erase(client->socket().handle()); - m_onDisconnection(client); - } else { - /* - * At this step, it is possible that we were completing a receive operation, in this - * case the write flag may be removed, add it if required. - */ - if (!client->output().empty()) { - m_listener.set(client->socket().handle(), Condition::Writable); - } - - m_onRead(client, buffer); - } - } else { - /* Operation in progress */ - updateFlags(client); - } - } - - /* - * Flush the output buffer. - */ - void processWrite(std::shared_ptr<StreamConnection<Address, Protocol>> &client) - { - auto &output = client->output(); - auto nsent = client->socket().send(output); - - if (client->socket().action() == Action::None) { - /* 1. Create a copy of content that has been sent */ - auto sent = output.substr(0, nsent); - - /* 2. Erase the content sent */ - output.erase(0, nsent); - - /* 3. Update listener */ - if (output.empty()) { - m_listener.unset(client->socket().handle(), Condition::Writable); - } - - /* 4. Notify user */ - m_onWrite(client, sent); - } else { - updateFlags(client); - } - } - - void processSync(std::shared_ptr<StreamConnection<Address, Protocol>> &client, Condition flags) - { - try { - auto action = client->socket().action(); - - if (action == Action::Receive || - (action == Action::None && (flags & Condition::Readable) == Condition::Readable)) { - processRead(client); - } else if ((flags & Condition::Writable) == Condition::Writable) { - processWrite(client); - } - } catch (const Error &error) { - m_onDisconnection(client); - m_listener.remove(client->socket().handle()); - m_clients.erase(client->socket().handle()); - } - } - -public: - /** - * Create a stream server with the specified address to bind. - * - * @param protocol the protocol (Tcp or Tls) - * @param address the address to bind - * @param max the max number to listen - * @throw Error on errors - */ - StreamServer(Protocol protocol, const Address &address, int max = 128) - : m_master{std::move(protocol), address} - { - // TODO: m_onError - m_master.set(SOL_SOCKET, SO_REUSEADDR, 1); - m_master.bind(address); - m_master.listen(max); - m_listener.set(m_master.handle(), Condition::Readable); - } - - /** - * Set the connection handler, called when a new client is connected. - * - * @param handler the handler - */ - inline void setConnectionHandler(ConnectionHandler handler) - { - m_onConnection = std::move(handler); - } - - /** - * Set the disconnection handler, called when a client died. - * - * @param handler the handler - */ - inline void setDisconnectionHandler(DisconnectionHandler handler) - { - m_onDisconnection = std::move(handler); - } - - /** - * Set the receive handler, called when a client has sent something. - * - * @param handler the handler - */ - inline void setReadHandler(ReadHandler handler) - { - m_onRead = std::move(handler); - } - - /** - * Set the writing handler, called when some data has been sent to a client. - * - * @param handler the handler - */ - inline void setWriteHandler(WriteHandler handler) - { - m_onWrite = std::move(handler); - } - - /** - * Set the error handler, called when unrecoverable error has occured. - * - * @param handler the handler - */ - inline void setErrorHandler(ErrorHandler handler) - { - m_onError = std::move(handler); - } - - /** - * Set the timeout handler, called when the selection has timeout. - * - * @param handler the handler - */ - inline void setTimeoutHandler(TimeoutHandler handler) - { - m_onTimeout = std::move(handler); - } - - /** - * Poll for the next event. - * - * @param timeout the timeout (-1 for indefinitely) - * @throw Error on errors - */ - void poll(int timeout = -1) - { - try { - auto st = m_listener.wait(timeout); - - if (st.socket == m_master.handle()) { - /* New client */ - processInitialAccept(); - } else { - /* Recv / Send / Accept on a client */ - auto client = m_clients[st.socket]; - - if (client->socket().state() == State::Accepted) { - processSync(client, st.flags); - } else { - processAccept(client, [&] () { client->socket().accept(); }); - } - } - } catch (const Error &error) { - if (error.code() == Error::Timeout) { - m_onTimeout(); - } else { - m_onError(error); - } - } - } -}; - -/* }}} */ - -/* - * StreamClient - * ------------------------------------------------------------------ - */ - -/* {{{ StreamClient */ - -/** - * @class StreamClient - * @brief Client side connection to a server. - * - * This class is not thread safe and you must not call any of the functions from different threads. - */ -template <typename Address, typename Protocol> -class StreamClient { -public: - /** - * Handler when connection is complete. - */ - using ConnectionHandler = Callback<>; - - /** - * Handler when data has been received. - */ - using ReadHandler = Callback<const std::string &>; - - /** - * Handler when data has been sent correctly. - */ - using WriteHandler = Callback<const std::string &>; - - /** - * Handler when disconnected. - */ - using DisconnectionHandler = Callback<>; - - /** - * Handler on unrecoverable error. - */ - using ErrorHandler = Callback<const Error &>; - - /** - * Handler when timeout occured. - */ - using TimeoutHandler = Callback<>; - -private: - /* Signals */ - ConnectionHandler m_onConnection; - ReadHandler m_onRead; - WriteHandler m_onWrite; - DisconnectionHandler m_onDisconnection; - ErrorHandler m_onError; - TimeoutHandler m_onTimeout; - - /* Socket */ - Socket<Address, Protocol> m_socket; - Listener<> m_listener; - - /* Output buffer */ - std::string m_output; - - /* - * Update the flags after an uncompleted operation. This function must only be called when the operation - * has not complete (e.g. connect, recv, send). - */ - void updateFlags() - { - assert(m_socket.action() != Action::None); - - m_listener.remove(m_socket.handle()); - m_listener.set(m_socket.handle(), m_socket.condition()); - } - - /* - * This is the generic connect helper, it will be used to both initiate the connection or to continue the - * connection process if needed. - * - * Thus the template parameter is the appropriate function to call either, m_socket.connect(address) or - * m_socket.connect(). - * - * See poll() and connect() to understand. - */ - template <typename ConnectCall> - void processConnect(const ConnectCall &connectFunc) - { - /* Call m_socket.connect() or m_socket.connect(address) */ - connectFunc(); - - /* Remove entirely */ - m_listener.remove(m_socket.handle()); - - if (m_socket.state() == State::Connected) { - m_onConnection(); - m_listener.set(m_socket.handle(), Condition::Readable); - } else { - /* Connection still in progress */ - updateFlags(); - } - } - - /* - * Receive or complete the receive command, if the command is not complete, the listener is updated - * accordingly. - */ - void processRead() - { - auto received = m_socket.recv(512); - - if (m_socket.action() == Action::None) { - /* 0 means disconnection */ - if (received.empty()) { - m_onDisconnection(); - } else { - /* - * At this step, it is possible that we were completing a receive operation, in this - * case the write flag may be removed, add it if required. - */ - if (m_output.empty()) { - m_listener.unset(m_socket.handle(), Condition::Writable); - } - - m_onRead(received); - } - } else { - /* Receive operation in progress */ - updateFlags(); - } - } - - /* - * Send or complete the send command, if the command is not complete, the listener is updated - * accordingly. - */ - void processWrite() - { - auto nsent = m_socket.send(m_output); - - if (m_socket.action() == Action::None) { - /* 1. Make a copy of what has been sent */ - auto sent = m_output.substr(0, nsent); - - /* 2. Erase sent content */ - m_output.erase(0, nsent); - - /* 3. Update flags if needed */ - if (m_output.empty()) { - m_listener.unset(m_socket.handle(), Condition::Writable); - } - - /* 4. Notify user */ - m_onWrite(sent); - } else { - /* Send operation in progress */ - updateFlags(); - } - } - - /* - * Receive or send. - */ - void processSync(Condition condition) - { - if ((m_socket.action() == Action::Receive) || - (m_socket.action() == Action::None && (condition & Condition::Readable) == Condition::Readable)) { - processRead(); - } else { - processWrite(); - } - } - -public: - /** - * Create a client. The client is automatically marked as non-blocking. - * - * @param protocol the protocol (Tcp or Tls) - * @param address the optional address - * @throw net::Error on failures - */ - StreamClient(Protocol protocol = {}, const Address &address = {}) - : m_socket{std::move(protocol), address} - { - m_socket.set(net::option::SockBlockMode{false}); - m_listener.set(m_socket.handle(), Condition::Readable); - } - - /** - * Set the connection handler, called when the connection succeed. - * - * @param handler the handler - */ - inline void setConnectionHandler(ConnectionHandler handler) - { - m_onConnection = std::move(handler); - } - - /** - * Set the disconnection handler, called when the server closed the connection. - * - * @param handler the handler - */ - inline void setDisconnectionHandler(DisconnectionHandler handler) - { - m_onDisconnection = std::move(handler); - } - - /** - * Set the read handler, called when we received something. - * - * @param handler the handler - */ - inline void setReadHandler(ReadHandler handler) - { - m_onRead = std::move(handler); - } - - /** - * Set the write handler, called when we successfully sent data. - * - * @param handler the handler - */ - inline void setWriteHandler(WriteHandler handler) - { - m_onWrite = std::move(handler); - } - - /** - * Set the error handler, called when unexpected error occurs. - * - * @param handler the handler - */ - inline void setErrorHandler(ErrorHandler handler) - { - m_onError = std::move(handler); - } - - /** - * Connect to a server, this function may connect immediately or not in any case the connection handler - * will be called when the connection completed. - * - * @param address the address to connect to - */ - void connect(const Address &address) noexcept - { - assert(m_socket.state() == State::Open); - - processConnect([&] () { m_socket.connect(address); }); - } - - /** - * Asynchronously send data to the server. - * - * @param str the data to append - */ - void send(std::string str) - { - m_output += str; - - /* Don't update the listener if there is a pending operation */ - if (m_socket.state() == State::Connected && m_socket.action() == Action::None && !m_output.empty()) { - m_listener.set(m_socket.handle(), Condition::Writable); - } - } - - /** - * Wait for the next event. - * - * @param timeout the time to wait in milliseconds - * @throw Error on errors - */ - void poll(int timeout = -1) noexcept - { - try { - auto st = m_listener.wait(timeout); - - if (m_socket.state() != State::Connected) { - /* Continue the connection */ - processConnect([&] () { m_socket.connect(); }); - } else { - /* Read / Write */ - processSync(st.flags); - } - } catch (const Error &error) { - if (error.code() == Error::Timeout) { - m_onTimeout(); - } else { - m_listener.remove(m_socket.handle()); - m_onError(error); - } - } - } -}; - -/* }}} */ - -} // !net - -} // !malikania - -#endif // !_SOCKETS_H_
--- a/libserver/CMakeLists.txt Tue Mar 22 22:50:35 2016 +0100 +++ b/libserver/CMakeLists.txt Tue Mar 22 23:02:56 2016 +0100 @@ -16,24 +16,24 @@ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -set( - HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/malikania/Server.h - ${CMAKE_CURRENT_SOURCE_DIR}/malikania/ServerApp.h -) - -set( - SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/malikania/Server.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/malikania/ServerApp.cpp -) - -malikania_create_library( - PROJECT libserver - TARGET libserver - SOURCES ${HEADERS} ${SOURCES} - PUBLIC_INCLUDES - ${CMAKE_CURRENT_SOURCE_DIR} - LIBRARIES - libcommon -) +# set( +# HEADERS +# ${CMAKE_CURRENT_SOURCE_DIR}/malikania/Server.h +# ${CMAKE_CURRENT_SOURCE_DIR}/malikania/ServerApp.h +# ) +# +# set( +# SOURCES +# ${CMAKE_CURRENT_SOURCE_DIR}/malikania/Server.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/malikania/ServerApp.cpp +# ) +# +# malikania_create_library( +# PROJECT libserver +# TARGET libserver +# SOURCES ${HEADERS} ${SOURCES} +# PUBLIC_INCLUDES +# ${CMAKE_CURRENT_SOURCE_DIR} +# LIBRARIES +# libcommon +# )
--- a/libserver/malikania/Server.cpp Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -#include "Server.h" - -namespace malikania { - -#if 0 - -net::address::Ip Server::buildIp(const ServerSettings &st) const noexcept -{ - return net::address::Ip(st.address, st.port); -} - -net::protocol::Tls Server::buildTls(const ServerSettings &st) const noexcept -{ - net::protocol::Tls tls; - - - return tls; -} - -Server::Server(const ServerSettings &st) - : m_server(net::address::Ip) -{ -} - -#endif - -} // !malikania
--- a/libserver/malikania/Server.h Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ -#ifndef MALIKANIA_SERVER_H -#define MALIKANIA_SERVER_H - -#include <cstdint> - -#include <malikania/Sockets.h> - -namespace malikania { - -class ServerSettings { -public: - std::string address{"*"}; - std::uint16_t port{3320}; -}; - -class Server { -private: - using StreamServer = net::StreamServer<net::address::Ip, net::protocol::Tls>; - - StreamServer m_server; - -#if 0 - - net::address::Ip buildIp() const noexcept; - net::protocol::Tls buildTls() const noexcept; - -#endif - -public: - /** - * Construct a server. - * - * @param st the settings - */ - //Server(const ServerSettings &st); - - /** - * Poll the next events. - * - * @param timeeout - */ - void poll(unsigned timeout = -1); -}; - -} // !malikania - -#endif // !MALIKANIA_SERVER_H
--- a/libserver/malikania/ServerApp.cpp Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -/* - * ServerApp.cpp -- main class for creating a server application - * - * Copyright (c) 2013-2015 Malikania Authors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <malikania/Game.h> -#include <malikania/Json.h> - -#include "ServerApp.h" - -namespace malikania { - -ServerApp::ServerApp(const ResourcesLoader &) - //: Game{directory.metadata()} -{ -} - -} // !malikania
--- a/libserver/malikania/ServerApp.h Tue Mar 22 22:50:35 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -/* - * ServerApp.h -- main class for creating a server application - * - * Copyright (c) 2013-2015 Malikania Authors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef SERVER_APP_H -#define SERVER_APP_H - -#include <malikania/Game.h> -#include <malikania/ResourcesLoader.h> - -namespace malikania { - -/** - * @class ServerApp - * @brief Create a server application - */ -class ServerApp { -public: - /** - * Create a server application. - * - * @param directory the directory - */ - ServerApp(const ResourcesLoader &directory); -}; - -} // !malikania - -#endif // !SERVER_APP_H