From f4ad9dc157c2db00acdb857acdc0676ebb2674b9 Mon Sep 17 00:00:00 2001 From: Joseph Aquino Date: Sun, 28 Dec 2025 05:07:16 -0500 Subject: [PATCH] Each project is now a module, makeing this project more portable to other premake projects -added sfml network --- flac/build-flac.lua | 62 +- freetype/build-freetype.lua | 141 ++-- imgui-sfml/build-imgui-sfml.lua | 56 +- ogg/build-ogg.lua | 36 +- sfml/build-sfml.lua | 114 ++-- sfml/include/SFML/Network/Export.hpp | 44 ++ sfml/include/SFML/Network/Ftp.hpp | 629 ++++++++++++++++++ sfml/include/SFML/Network/Http.hpp | 480 ++++++++++++++ sfml/include/SFML/Network/IpAddress.hpp | 297 +++++++++ sfml/include/SFML/Network/Packet.hpp | 548 ++++++++++++++++ sfml/include/SFML/Network/Socket.hpp | 229 +++++++ sfml/include/SFML/Network/SocketHandle.hpp | 52 ++ sfml/include/SFML/Network/SocketSelector.hpp | 273 ++++++++ sfml/include/SFML/Network/TcpListener.hpp | 166 +++++ sfml/include/SFML/Network/TcpSocket.hpp | 317 +++++++++ sfml/include/SFML/Network/UdpSocket.hpp | 293 +++++++++ sfml/src/SFML/Network/Ftp.cpp | 643 +++++++++++++++++++ sfml/src/SFML/Network/Http.cpp | 399 ++++++++++++ sfml/src/SFML/Network/IpAddress.cpp | 253 ++++++++ sfml/src/SFML/Network/Packet.cpp | 596 +++++++++++++++++ sfml/src/SFML/Network/Socket.cpp | 175 +++++ sfml/src/SFML/Network/SocketImpl.hpp | 123 ++++ sfml/src/SFML/Network/SocketSelector.cpp | 209 ++++++ sfml/src/SFML/Network/TcpListener.cpp | 132 ++++ sfml/src/SFML/Network/TcpSocket.cpp | 429 +++++++++++++ sfml/src/SFML/Network/UdpSocket.cpp | 223 +++++++ sfml/src/SFML/Network/Unix/SocketImpl.cpp | 111 ++++ sfml/src/SFML/Network/Win32/SocketImpl.cpp | 110 ++++ vorbis/build-vorbis.lua | 126 ++-- 29 files changed, 7034 insertions(+), 232 deletions(-) create mode 100644 sfml/include/SFML/Network/Export.hpp create mode 100644 sfml/include/SFML/Network/Ftp.hpp create mode 100644 sfml/include/SFML/Network/Http.hpp create mode 100644 sfml/include/SFML/Network/IpAddress.hpp create mode 100644 sfml/include/SFML/Network/Packet.hpp create mode 100644 sfml/include/SFML/Network/Socket.hpp create mode 100644 sfml/include/SFML/Network/SocketHandle.hpp create mode 100644 sfml/include/SFML/Network/SocketSelector.hpp create mode 100644 sfml/include/SFML/Network/TcpListener.hpp create mode 100644 sfml/include/SFML/Network/TcpSocket.hpp create mode 100644 sfml/include/SFML/Network/UdpSocket.hpp create mode 100644 sfml/src/SFML/Network/Ftp.cpp create mode 100644 sfml/src/SFML/Network/Http.cpp create mode 100644 sfml/src/SFML/Network/IpAddress.cpp create mode 100644 sfml/src/SFML/Network/Packet.cpp create mode 100644 sfml/src/SFML/Network/Socket.cpp create mode 100644 sfml/src/SFML/Network/SocketImpl.hpp create mode 100644 sfml/src/SFML/Network/SocketSelector.cpp create mode 100644 sfml/src/SFML/Network/TcpListener.cpp create mode 100644 sfml/src/SFML/Network/TcpSocket.cpp create mode 100644 sfml/src/SFML/Network/UdpSocket.cpp create mode 100644 sfml/src/SFML/Network/Unix/SocketImpl.cpp create mode 100644 sfml/src/SFML/Network/Win32/SocketImpl.cpp diff --git a/flac/build-flac.lua b/flac/build-flac.lua index a085a4e..e7c7594 100644 --- a/flac/build-flac.lua +++ b/flac/build-flac.lua @@ -1,9 +1,16 @@ -project"flac" - cppdialect"c++17" +local m = {} + +local scriptdir = path.getabsolute(path.getdirectory(_SCRIPT)) +local ogg = require("vendor/ogg/build-ogg") +function m.generateproject(liboutdir, intdir) + project"flac" + language"C" -- c++ will mangle names and sfml wont build kind"staticLib" - targetdir (libout) - staticruntime "off" + targetdir (liboutdir) objdir(intdir) + warnings"Off" + + ogg.link() defines { @@ -14,41 +21,46 @@ project"flac" } filter"system:linux" - defines - { - "HAVE_LROUND",--fix error in lpc.c - "HAVE_STDINT_H" --fix error in alloc.h <# error> - } + defines + { + "HAVE_LROUND",--fix error in lpc.c + "HAVE_STDINT_H", --fix error in alloc.h <# error> + } includedirs { - "include", - "src/libFLAC/include", - "../ogg/include" + path.join(scriptdir, "include"), + path.join(scriptdir, "src/libFLAC/include"), } files { - "src/libFLAC/**.c", + path.join(scriptdir, "src/libFLAC/**.c"), } removefiles { - "src/libFLAC/deduplication/**" + path.join(scriptdir, "src/libFLAC/deduplication/**"), } filter"system:windows" - files - { - "src/share/win_utf8_io/**.c" - } + files + { + path.join(scriptdir, "src/share/win_utf8_io/**.c"), + } - filter "configurations:Debug" - runtime "Debug" - symbols "on" + filter"" - filter "configurations:Release" - runtime "Release" - optimize "Speed" +end - filter"" \ No newline at end of file +function m.link() + externalincludedirs + { + path.join(scriptdir, "include"), + path.join(scriptdir, "src/libFLAC/include"), + path.join(scriptdir, "../ogg/include") + } + links {"flac", "ogg"} +end + +return m diff --git a/freetype/build-freetype.lua b/freetype/build-freetype.lua index 4c9c557..e786598 100644 --- a/freetype/build-freetype.lua +++ b/freetype/build-freetype.lua @@ -1,11 +1,16 @@ -project"freetype" - kind"staticLib" - cppdialect"c++17" - targetdir (libout) - staticruntime "off" - objdir(intdir) +local m = {} - includedirs {"include"} +local scriptdir = path.getabsolute(path.getdirectory(_SCRIPT)) + +function m.generateproject(liboutdir, intdir) + project"freetype" + kind"staticLib" + language"C" -- c++ will mangle names and sfml wont build + targetdir (liboutdir) + objdir(intdir) + warnings"Off" + + includedirs {path.join(scriptdir, "include")} defines { @@ -17,75 +22,75 @@ project"freetype" files { - "src/autofit/autofit.c", - "src/base/ftbase.c", - "src/base/ftbbox.c", - "src/base/ftbdf.c", - "src/base/ftbitmap.c", - "src/base/ftcid.c", - "src/base/ftfstype.c", - "src/base/ftgasp.c", - "src/base/ftglyph.c", - "src/base/ftgxval.c", - "src/base/ftinit.c", - "src/base/ftmm.c", - "src/base/ftotval.c", - "src/base/ftpatent.c", - "src/base/ftpfr.c", - "src/base/ftstroke.c", - "src/base/ftsynth.c", - "src/base/fttype1.c", - "src/base/ftwinfnt.c", - "src/bdf/bdf.c", - "src/bzip2/ftbzip2.c", - "src/cache/ftcache.c", - "src/cff/cff.c", - "src/cid/type1cid.c", - "src/gzip/ftgzip.c", - "src/lzw/ftlzw.c", - "src/pcf/pcf.c", - "src/pfr/pfr.c", - "src/psaux/psaux.c", - "src/pshinter/pshinter.c", - "src/psnames/psnames.c", - "src/raster/raster.c", - "src/sdf/sdf.c", - "src/sfnt/sfnt.c", - "src/smooth/smooth.c", - "src/svg/svg.c", - "src/truetype/truetype.c", - "src/type1/type1.c", - "src/type42/type42.c", - "src/winfonts/winfnt.c", - "src/base/ftdebug.c" --fix linking errors related to FT_THROW, etc + path.join(scriptdir, "src/autofit/autofit.c"), + path.join(scriptdir, "src/base/ftbase.c"), + path.join(scriptdir, "src/base/ftbbox.c"), + path.join(scriptdir, "src/base/ftbdf.c"), + path.join(scriptdir, "src/base/ftbitmap.c"), + path.join(scriptdir, "src/base/ftcid.c"), + path.join(scriptdir, "src/base/ftfstype.c"), + path.join(scriptdir, "src/base/ftgasp.c"), + path.join(scriptdir, "src/base/ftglyph.c"), + path.join(scriptdir, "src/base/ftgxval.c"), + path.join(scriptdir, "src/base/ftinit.c"), + path.join(scriptdir, "src/base/ftmm.c"), + path.join(scriptdir, "src/base/ftotval.c"), + path.join(scriptdir, "src/base/ftpatent.c"), + path.join(scriptdir, "src/base/ftpfr.c"), + path.join(scriptdir, "src/base/ftstroke.c"), + path.join(scriptdir, "src/base/ftsynth.c"), + path.join(scriptdir, "src/base/fttype1.c"), + path.join(scriptdir, "src/base/ftwinfnt.c"), + path.join(scriptdir, "src/bdf/bdf.c"), + path.join(scriptdir, "src/bzip2/ftbzip2.c"), + path.join(scriptdir, "src/cache/ftcache.c"), + path.join(scriptdir, "src/cff/cff.c"), + path.join(scriptdir, "src/cid/type1cid.c"), + path.join(scriptdir, "src/gzip/ftgzip.c"), + path.join(scriptdir, "src/lzw/ftlzw.c"), + path.join(scriptdir, "src/pcf/pcf.c"), + path.join(scriptdir, "src/pfr/pfr.c"), + path.join(scriptdir, "src/psaux/psaux.c"), + path.join(scriptdir, "src/pshinter/pshinter.c"), + path.join(scriptdir, "src/psnames/psnames.c"), + path.join(scriptdir, "src/raster/raster.c"), + path.join(scriptdir, "src/sdf/sdf.c"), + path.join(scriptdir, "src/sfnt/sfnt.c"), + path.join(scriptdir, "src/smooth/smooth.c"), + path.join(scriptdir, "src/svg/svg.c"), + path.join(scriptdir, "src/truetype/truetype.c"), + path.join(scriptdir, "src/type1/type1.c"), + path.join(scriptdir, "src/type42/type42.c"), + path.join(scriptdir, "src/winfonts/winfnt.c"), + path.join(scriptdir, "src/base/ftdebug.c"), --fix linking errors related to FT_THROW, et)c } filter "system:linux" - defines - { - "HAVE_FCNTL_H",--fix error in ftsystem.c - "HAVE_UNISTD_H" --fix error in ftsystem.c - } - - filter"" - + defines + { + "HAVE_FCNTL_H",--fix error in ftsystem.c + "HAVE_UNISTD_H" --fix error in ftsystem.c + } filter"system:windows" - files - { - "builds/windows/ftsystem.c", - "builds/windows/ftdebug.c" - } + files + { + path.join(scriptdir, "builds/windows/ftsystem.c"), + path.join(scriptdir, "builds/windows/ftdebug.c"), + } filter"system:linux" - files{"builds/unix/ftsystem.c"} + files{path.join(scriptdir, "builds/unix/ftsystem.c")} filter "configurations:Debug" - runtime "Debug" - symbols "on" + optimize"Speed" - filter "configurations:Release" - runtime "Release" - optimize "Speed" + filter"" +end - filter"" \ No newline at end of file +function m.link() + externalincludedirs{path.join(scriptdir, "include")} + links {"freetype"} +end + +return m \ No newline at end of file diff --git a/imgui-sfml/build-imgui-sfml.lua b/imgui-sfml/build-imgui-sfml.lua index e57b21b..0ae83c5 100644 --- a/imgui-sfml/build-imgui-sfml.lua +++ b/imgui-sfml/build-imgui-sfml.lua @@ -1,29 +1,35 @@ -project"imgui-sfml" - cppdialect"c++17" - kind"staticLib" - targetdir (libout) - staticruntime "off" - objdir(intdir) +local m = {} - includedirs - { - "../sfml/include", - "." - } +local scriptdir = path.getabsolute(path.getdirectory(_SCRIPT)) +local sfml = require("vendor/sfml/build-sfml") +function m.generateproject(liboutdir, intdir) + project"imgui-sfml" + cppdialect"c++17" + kind"staticLib" + targetdir (liboutdir) + objdir(intdir) + warnings"Off" - files - { - "imgui.cpp", - "imgui_draw.cpp", - "imgui_tables.cpp", - "imgui_widgets.cpp", - "imgui-SFML.cpp" - } + sfml.link() - filter "configurations:Debug" - runtime "Debug" - symbols "on" + includedirs + { + scriptdir, + } - filter "configurations:Release" - runtime "Release" - optimize "Speed" \ No newline at end of file + files + { + path.join(scriptdir, "imgui.cpp"), + path.join(scriptdir, "imgui_draw.cpp"), + path.join(scriptdir, "imgui_tables.cpp"), + path.join(scriptdir, "imgui_widgets.cpp"), + path.join(scriptdir, "imgui-SFML.cpp"), + } +end + +function m.link() + externalincludedirs{scriptdir, path.join(scriptdir, "../sfml/include")} + links {"imgui-sfml"} +end + +return m \ No newline at end of file diff --git a/ogg/build-ogg.lua b/ogg/build-ogg.lua index f458c35..a98adde 100644 --- a/ogg/build-ogg.lua +++ b/ogg/build-ogg.lua @@ -1,24 +1,28 @@ -project"ogg" - cppdialect"c++17" - kind"staticLib" - targetdir (libout) - staticruntime "off" - objdir(intdir) +local m = {} - includedirs"include" +local scriptdir = path.getabsolute(path.getdirectory(_SCRIPT)) + +function m.generateproject(liboutdir, intdir) + project"ogg" + language"C" -- c++ will mangle names and sfml wont build + kind"staticLib" + targetdir (liboutdir) + objdir(intdir) + warnings"Off" + + includedirs {path.join(scriptdir, "include")} files { - "src/**.h", - "src/**.c" + path.join(scriptdir, "src/**.h"), + path.join(scriptdir, "src/**.c"), } - filter "configurations:Debug" - runtime "Debug" - symbols "on" +end - filter "configurations:Release" - runtime "Release" - optimize "Speed" +function m.link() + links{"ogg"} + externalincludedirs{path.join(scriptdir, "/include")} +end - filter"" \ No newline at end of file +return m \ No newline at end of file diff --git a/sfml/build-sfml.lua b/sfml/build-sfml.lua index 2b4ebe8..d997797 100644 --- a/sfml/build-sfml.lua +++ b/sfml/build-sfml.lua @@ -1,12 +1,23 @@ -project"sfml" +local m = {} - links{"freetype", "ogg", "flac", "vorbis"} +local scriptdir = path.getabsolute(path.getdirectory(_SCRIPT)) +local ogg = require("vendor/ogg/build-ogg") +local flac = require("vendor/flac/build-flac") +local vorbis = require("vendor/vorbis/build-vorbis") +local freetype = require("vendor/freetype/build-freetype") +function m.generateproject(liboutdir, intdir) + project"sfml" cppdialect"c++17" kind "staticLib" - targetdir (libout) - staticruntime "off" + targetdir (liboutdir) objdir(intdir) - + warnings"Off" + + freetype.link() + ogg.link() + flac.link() + vorbis.link() + defines { "SFML_STATIC", @@ -20,52 +31,75 @@ project"sfml" "SFML_IS_BIG_ENDIAN=0", "FT2_BUILD_LIBRARY", "FLAC__NO_DLL", - "OV_EXCLUDE_STATIC_CALLBACKS" + "OV_EXCLUDE_STATIC_CALLBACKS", } - + includedirs { - "include", - "src", - "extlibs/headers/glad/include", - "extlibs/headers/mingw", - "extlibs/headers/miniaudio", - "extlibs/headers/minimp3", - "extlibs/headers/stb_image", - "extlibs/headers/vulkan", - "../freetype/include", - "../ogg/include", - "../flac/include", - "../vorbis/include" + path.join(scriptdir, "include"), + path.join(scriptdir, "src"), + path.join(scriptdir, "extlibs/headers/glad/include"), + path.join(scriptdir, "extlibs/headers/mingw"), + path.join(scriptdir, "extlibs/headers/miniaudio"), + path.join(scriptdir, "extlibs/headers/minimp3"), + path.join(scriptdir, "extlibs/headers/stb_image"), + path.join(scriptdir, "extlibs/headers/vulkan"), + } files { - "include/SFML/**.hpp", - "include/SFML/**.inl", - "src/SFML/**.hpp", - "src/SFML/**.cpp" + path.join(scriptdir, "include/SFML/**.hpp"), + path.join(scriptdir, "include/SFML/**.inl"), + path.join(scriptdir, "src/SFML/**.hpp"), + path.join(scriptdir, "src/SFML/**.cpp"), } filter"system:windows" - removefiles - { - "src/SFML/System/Unix/**", - "src/SFML/Window/Unix/**" - } + removefiles + { + path.join(scriptdir, "src/SFML/System/Unix/**"), + path.join(scriptdir, "src/SFML/Window/Unix/**"), + path.join(scriptdir, "src/SFML/Network/Unix/**"), + } filter"system:linux" - removefiles - { - "src/SFML/System/Win32/**", - "src/SFML/Window/Win32/**", - "src/SFML/Main/**" - } + removefiles + { + path.join(scriptdir, "src/SFML/System/Win32/**"), + path.join(scriptdir, "src/SFML/Network/Win32/**"), + path.join(scriptdir, "src/SFML/Window/Win32/**"), + path.join(scriptdir, "src/SFML/Main/**"), + } - filter "configurations:Debug" - runtime "Debug" - symbols "on" +end - filter "configurations:Release" - runtime "Release" - optimize "Speed" \ No newline at end of file +function m.link() + defines{"SFML_STATIC"} + externalincludedirs + { + path.join(scriptdir, "include"), + path.join(scriptdir, "src"), + path.join(scriptdir, "extlibs/headers/glad/include"), + path.join(scriptdir, "extlibs/headers/mingw"), + path.join(scriptdir, "extlibs/headers/miniaudio"), + path.join(scriptdir, "extlibs/headers/minimp3"), + path.join(scriptdir, "extlibs/headers/stb_image"), + path.join(scriptdir, "extlibs/headers/vulkan"), + path.join(scriptdir, "../freetype/include"), + path.join(scriptdir, "../ogg/include"), + path.join(scriptdir, "../flac/include"), + path.join(scriptdir, "../vorbis/include") + } + + links{"sfml", "freetype", "flac", "vorbis", "ogg"} + + filter"system:windows" + links{"legacy_stdio_definitions", "opengl32", "gdi32", "winmm", "ws2_32", "openal32"} + + filter"system:linux" + links{"Xi", "Xrandr", "Xcursor", "X11", "udev", "pthread", "OpenGL", "openal"} + +end + +return m diff --git a/sfml/include/SFML/Network/Export.hpp b/sfml/include/SFML/Network/Export.hpp new file mode 100644 index 0000000..edcc6e5 --- /dev/null +++ b/sfml/include/SFML/Network/Export.hpp @@ -0,0 +1,44 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + + +//////////////////////////////////////////////////////////// +// Portable import / export macros +//////////////////////////////////////////////////////////// +#if defined(SFML_NETWORK_EXPORTS) + +#define SFML_NETWORK_API SFML_API_EXPORT + +#else + +#define SFML_NETWORK_API SFML_API_IMPORT + +#endif diff --git a/sfml/include/SFML/Network/Ftp.hpp b/sfml/include/SFML/Network/Ftp.hpp new file mode 100644 index 0000000..12e3bce --- /dev/null +++ b/sfml/include/SFML/Network/Ftp.hpp @@ -0,0 +1,629 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +#include + +#include + +#include +#include +#include + + +namespace sf +{ +class IpAddress; + +//////////////////////////////////////////////////////////// +/// \brief A FTP client +/// +//////////////////////////////////////////////////////////// +class SFML_NETWORK_API Ftp +{ +public: + //////////////////////////////////////////////////////////// + /// \brief Enumeration of transfer modes + /// + //////////////////////////////////////////////////////////// + enum class TransferMode + { + Binary, //!< Binary mode (file is transferred as a sequence of bytes) + Ascii, //!< Text mode using ASCII encoding + Ebcdic //!< Text mode using EBCDIC encoding + }; + + //////////////////////////////////////////////////////////// + /// \brief FTP response + /// + //////////////////////////////////////////////////////////// + class SFML_NETWORK_API Response + { + public: + //////////////////////////////////////////////////////////// + /// \brief Status codes possibly returned by a FTP response + /// + //////////////////////////////////////////////////////////// + enum class Status + { + // 1xx: the requested action is being initiated, + // expect another reply before proceeding with a new command + RestartMarkerReply = 110, //!< Restart marker reply + ServiceReadySoon = 120, //!< Service ready in N minutes + DataConnectionAlreadyOpened = 125, //!< Data connection already opened, transfer starting + OpeningDataConnection = 150, //!< File status ok, about to open data connection + + // 2xx: the requested action has been successfully completed + Ok = 200, //!< Command ok + PointlessCommand = 202, //!< Command not implemented + SystemStatus = 211, //!< System status, or system help reply + DirectoryStatus = 212, //!< Directory status + FileStatus = 213, //!< File status + HelpMessage = 214, //!< Help message + SystemType = 215, //!< NAME system type, where NAME is an official system name from the list in the Assigned Numbers document + ServiceReady = 220, //!< Service ready for new user + ClosingConnection = 221, //!< Service closing control connection + DataConnectionOpened = 225, //!< Data connection open, no transfer in progress + ClosingDataConnection = 226, //!< Closing data connection, requested file action successful + EnteringPassiveMode = 227, //!< Entering passive mode + LoggedIn = 230, //!< User logged in, proceed. Logged out if appropriate + FileActionOk = 250, //!< Requested file action ok + DirectoryOk = 257, //!< PATHNAME created + + // 3xx: the command has been accepted, but the requested action + // is dormant, pending receipt of further information + NeedPassword = 331, //!< User name ok, need password + NeedAccountToLogIn = 332, //!< Need account for login + NeedInformation = 350, //!< Requested file action pending further information + + // 4xx: the command was not accepted and the requested action did not take place, + // but the error condition is temporary and the action may be requested again + ServiceUnavailable = 421, //!< Service not available, closing control connection + DataConnectionUnavailable = 425, //!< Can't open data connection + TransferAborted = 426, //!< Connection closed, transfer aborted + FileActionAborted = 450, //!< Requested file action not taken + LocalError = 451, //!< Requested action aborted, local error in processing + InsufficientStorageSpace = 452, //!< Requested action not taken; insufficient storage space in system, file unavailable + + // 5xx: the command was not accepted and + // the requested action did not take place + CommandUnknown = 500, //!< Syntax error, command unrecognized + ParametersUnknown = 501, //!< Syntax error in parameters or arguments + CommandNotImplemented = 502, //!< Command not implemented + BadCommandSequence = 503, //!< Bad sequence of commands + ParameterNotImplemented = 504, //!< Command not implemented for that parameter + NotLoggedIn = 530, //!< Not logged in + NeedAccountToStore = 532, //!< Need account for storing files + FileUnavailable = 550, //!< Requested action not taken, file unavailable + PageTypeUnknown = 551, //!< Requested action aborted, page type unknown + NotEnoughMemory = 552, //!< Requested file action aborted, exceeded storage allocation + FilenameNotAllowed = 553, //!< Requested action not taken, file name not allowed + + // 10xx: SFML custom codes + InvalidResponse = 1000, //!< Not part of the FTP standard, generated by SFML when a received response cannot be parsed + ConnectionFailed = 1001, //!< Not part of the FTP standard, generated by SFML when the low-level socket connection with the server fails + ConnectionClosed = 1002, //!< Not part of the FTP standard, generated by SFML when the low-level socket connection is unexpectedly closed + InvalidFile = 1003 //!< Not part of the FTP standard, generated by SFML when a local file cannot be read or written + }; + + //////////////////////////////////////////////////////////// + /// \brief Default constructor + /// + /// This constructor is used by the FTP client to build + /// the response. + /// + /// \param code Response status code + /// \param message Response message + /// + //////////////////////////////////////////////////////////// + explicit Response(Status code = Status::InvalidResponse, std::string message = ""); + + //////////////////////////////////////////////////////////// + /// \brief Check if the status code means a success + /// + /// This function is defined for convenience, it is + /// equivalent to testing if the status code is < 400. + /// + /// \return `true` if the status is a success, `false` if it is a failure + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] bool isOk() const; + + //////////////////////////////////////////////////////////// + /// \brief Get the status code of the response + /// + /// \return Status code + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Status getStatus() const; + + //////////////////////////////////////////////////////////// + /// \brief Get the full message contained in the response + /// + /// \return The response message + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] const std::string& getMessage() const; + + private: + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + Status m_status; //!< Status code returned from the server + std::string m_message; //!< Last message received from the server + }; + + //////////////////////////////////////////////////////////// + /// \brief Specialization of FTP response returning a directory + /// + //////////////////////////////////////////////////////////// + class SFML_NETWORK_API DirectoryResponse : public Response + { + public: + //////////////////////////////////////////////////////////// + /// \brief Default constructor + /// + /// \param response Source response + /// + //////////////////////////////////////////////////////////// + DirectoryResponse(const Response& response); + + //////////////////////////////////////////////////////////// + /// \brief Get the directory returned in the response + /// + /// \return Directory name + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] const std::filesystem::path& getDirectory() const; + + private: + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + std::filesystem::path m_directory; //!< Directory extracted from the response message + }; + + + //////////////////////////////////////////////////////////// + /// \brief Specialization of FTP response returning a + /// file name listing + //////////////////////////////////////////////////////////// + class SFML_NETWORK_API ListingResponse : public Response + { + public: + //////////////////////////////////////////////////////////// + /// \brief Default constructor + /// + /// \param response Source response + /// \param data Data containing the raw listing + /// + //////////////////////////////////////////////////////////// + ListingResponse(const Response& response, const std::string& data); + + //////////////////////////////////////////////////////////// + /// \brief Return the array of directory/file names + /// + /// \return Array containing the requested listing + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] const std::vector& getListing() const; + + private: + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + std::vector m_listing; //!< Directory/file names extracted from the data + }; + + //////////////////////////////////////////////////////////// + /// \brief Default constructor + /// + //////////////////////////////////////////////////////////// + Ftp() = default; + + //////////////////////////////////////////////////////////// + /// \brief Destructor + /// + /// Automatically closes the connection with the server if + /// it is still opened. + /// + //////////////////////////////////////////////////////////// + ~Ftp(); + + //////////////////////////////////////////////////////////// + /// \brief Deleted copy constructor + /// + //////////////////////////////////////////////////////////// + Ftp(const Ftp&) = delete; + + //////////////////////////////////////////////////////////// + /// \brief Deleted copy assignment + /// + //////////////////////////////////////////////////////////// + Ftp& operator=(const Ftp&) = delete; + + //////////////////////////////////////////////////////////// + /// \brief Connect to the specified FTP server + /// + /// The port has a default value of 21, which is the standard + /// port used by the FTP protocol. You shouldn't use a different + /// value, unless you really know what you do. + /// This function tries to connect to the server so it may take + /// a while to complete, especially if the server is not + /// reachable. To avoid blocking your application for too long, + /// you can use a timeout. The default value, `Time::Zero`, means that the + /// system timeout will be used (which is usually pretty long). + /// + /// \param server Name or address of the FTP server to connect to + /// \param port Port used for the connection + /// \param timeout Maximum time to wait + /// + /// \return Server response to the request + /// + /// \see `disconnect` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Response connect(IpAddress server, unsigned short port = 21, Time timeout = Time::Zero); + + //////////////////////////////////////////////////////////// + /// \brief Close the connection with the server + /// + /// \return Server response to the request + /// + /// \see `connect` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Response disconnect(); + + //////////////////////////////////////////////////////////// + /// \brief Log in using an anonymous account + /// + /// Logging in is mandatory after connecting to the server. + /// Users that are not logged in cannot perform any operation. + /// + /// \return Server response to the request + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Response login(); + + //////////////////////////////////////////////////////////// + /// \brief Log in using a username and a password + /// + /// Logging in is mandatory after connecting to the server. + /// Users that are not logged in cannot perform any operation. + /// + /// \param name User name + /// \param password Password + /// + /// \return Server response to the request + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Response login(const std::string& name, const std::string& password); + + //////////////////////////////////////////////////////////// + /// \brief Send a null command to keep the connection alive + /// + /// This command is useful because the server may close the + /// connection automatically if no command is sent. + /// + /// \return Server response to the request + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Response keepAlive(); + + //////////////////////////////////////////////////////////// + /// \brief Get the current working directory + /// + /// The working directory is the root path for subsequent + /// operations involving directories and/or filenames. + /// + /// \return Server response to the request + /// + /// \see `getDirectoryListing`, `changeDirectory`, `parentDirectory` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] DirectoryResponse getWorkingDirectory(); + + //////////////////////////////////////////////////////////// + /// \brief Get the contents of the given directory + /// + /// This function retrieves the sub-directories and files + /// contained in the given directory. It is not recursive. + /// The `directory` parameter is relative to the current + /// working directory. + /// + /// \param directory Directory to list + /// + /// \return Server response to the request + /// + /// \see `getWorkingDirectory`, `changeDirectory`, `parentDirectory` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] ListingResponse getDirectoryListing(const std::string& directory = ""); + + //////////////////////////////////////////////////////////// + /// \brief Change the current working directory + /// + /// The new directory must be relative to the current one. + /// + /// \param directory New working directory + /// + /// \return Server response to the request + /// + /// \see `getWorkingDirectory`, `getDirectoryListing`, `parentDirectory` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Response changeDirectory(const std::string& directory); + + //////////////////////////////////////////////////////////// + /// \brief Go to the parent directory of the current one + /// + /// \return Server response to the request + /// + /// \see `getWorkingDirectory`, `getDirectoryListing`, `changeDirectory` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Response parentDirectory(); + + //////////////////////////////////////////////////////////// + /// \brief Create a new directory + /// + /// The new directory is created as a child of the current + /// working directory. + /// + /// \param name Name of the directory to create + /// + /// \return Server response to the request + /// + /// \see `deleteDirectory` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Response createDirectory(const std::string& name); + + //////////////////////////////////////////////////////////// + /// \brief Remove an existing directory + /// + /// The directory to remove must be relative to the + /// current working directory. + /// Use this function with caution, the directory will + /// be removed permanently! + /// + /// \param name Name of the directory to remove + /// + /// \return Server response to the request + /// + /// \see `createDirectory` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Response deleteDirectory(const std::string& name); + + //////////////////////////////////////////////////////////// + /// \brief Rename an existing file + /// + /// The file names must be relative to the current working + /// directory. + /// + /// \param file File to rename + /// \param newName New name of the file + /// + /// \return Server response to the request + /// + /// \see `deleteFile` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Response renameFile(const std::filesystem::path& file, const std::filesystem::path& newName); + + //////////////////////////////////////////////////////////// + /// \brief Remove an existing file + /// + /// The file name must be relative to the current working + /// directory. + /// Use this function with caution, the file will be + /// removed permanently! + /// + /// \param name File to remove + /// + /// \return Server response to the request + /// + /// \see `renameFile` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Response deleteFile(const std::filesystem::path& name); + + //////////////////////////////////////////////////////////// + /// \brief Download a file from the server + /// + /// The file name of the distant file is relative to the + /// current working directory of the server, and the local + /// destination path is relative to the current directory + /// of your application. + /// If a file with the same file name as the distant file + /// already exists in the local destination path, it will + /// be overwritten. + /// + /// \param remoteFile File name of the distant file to download + /// \param localPath The directory in which to put the file on the local computer + /// \param mode Transfer mode + /// + /// \return Server response to the request + /// + /// \see `upload` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Response download(const std::filesystem::path& remoteFile, + const std::filesystem::path& localPath, + TransferMode mode = TransferMode::Binary); + + //////////////////////////////////////////////////////////// + /// \brief Upload a file to the server + /// + /// The name of the local file is relative to the current + /// working directory of your application, and the + /// remote path is relative to the current directory of the + /// FTP server. + /// + /// The append parameter controls whether the remote file is + /// appended to or overwritten if it already exists. + /// + /// \param localFile Path of the local file to upload + /// \param remotePath The directory in which to put the file on the server + /// \param mode Transfer mode + /// \param append Pass `true` to append to or `false` to overwrite the remote file if it already exists + /// + /// \return Server response to the request + /// + /// \see `download` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Response upload(const std::filesystem::path& localFile, + const std::filesystem::path& remotePath, + TransferMode mode = TransferMode::Binary, + bool append = false); + + //////////////////////////////////////////////////////////// + /// \brief Send a command to the FTP server + /// + /// While the most often used commands are provided as member + /// functions in the `sf::Ftp` class, this method can be used + /// to send any FTP command to the server. If the command + /// requires one or more parameters, they can be specified + /// in `parameter`. If the server returns information, you + /// can extract it from the response using `Response::getMessage()`. + /// + /// \param command Command to send + /// \param parameter Command parameter + /// + /// \return Server response to the request + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Response sendCommand(const std::string& command, const std::string& parameter = ""); + +private: + //////////////////////////////////////////////////////////// + /// \brief Receive a response from the server + /// + /// This function must be called after each call to + /// `sendCommand` that expects a response. + /// + /// \return Server response to the request + /// + //////////////////////////////////////////////////////////// + Response getResponse(); + + //////////////////////////////////////////////////////////// + /// \brief Utility class for exchanging data with the server + /// on the data channel + /// + //////////////////////////////////////////////////////////// + class DataChannel; + + friend class DataChannel; + + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + TcpSocket m_commandSocket; //!< Socket holding the control connection with the server + std::string m_receiveBuffer; //!< Received command data that is yet to be processed +}; + +} // namespace sf + + +//////////////////////////////////////////////////////////// +/// \class sf::Ftp +/// \ingroup network +/// +/// `sf::Ftp` is a very simple FTP client that allows you +/// to communicate with a FTP server. The FTP protocol allows +/// you to manipulate a remote file system (list files, +/// upload, download, create, remove, ...). +/// +/// Using the FTP client consists of 4 parts: +/// \li Connecting to the FTP server +/// \li Logging in (either as a registered user or anonymously) +/// \li Sending commands to the server +/// \li Disconnecting (this part can be done implicitly by the destructor) +/// +/// Every command returns a FTP response, which contains the +/// status code as well as a message from the server. Some +/// commands such as `getWorkingDirectory()` and `getDirectoryListing()` +/// return additional data, and use a class derived from +/// `sf::Ftp::Response` to provide this data. The most often used +/// commands are directly provided as member functions, but it is +/// also possible to use specific commands with the `sendCommand()` function. +/// +/// Note that response statuses >= 1000 are not part of the FTP standard, +/// they are generated by SFML when an internal error occurs. +/// +/// All commands, especially upload and download, may take some +/// time to complete. This is important to know if you don't want +/// to block your application while the server is completing +/// the task. +/// +/// Usage example: +/// \code +/// // Create a new FTP client +/// sf::Ftp ftp; +/// +/// // Connect to the server +/// sf::Ftp::Response response = ftp.connect("ftp://ftp.myserver.com"); +/// if (response.isOk()) +/// std::cout << "Connected" << std::endl; +/// +/// // Log in +/// response = ftp.login("laurent", "dF6Zm89D"); +/// if (response.isOk()) +/// std::cout << "Logged in" << std::endl; +/// +/// // Print the working directory +/// sf::Ftp::DirectoryResponse directory = ftp.getWorkingDirectory(); +/// if (directory.isOk()) +/// std::cout << "Working directory: " << directory.getDirectory() << std::endl; +/// +/// // Create a new directory +/// response = ftp.createDirectory("files"); +/// if (response.isOk()) +/// std::cout << "Created new directory" << std::endl; +/// +/// // Upload a file to this new directory +/// response = ftp.upload("local-path/file.txt", "files", sf::Ftp::TransferMode::Ascii); +/// if (response.isOk()) +/// std::cout << "File uploaded" << std::endl; +/// +/// // Send specific commands (here: FEAT to list supported FTP features) +/// response = ftp.sendCommand("FEAT"); +/// if (response.isOk()) +/// std::cout << "Feature list:\n" << response.getMessage() << std::endl; +/// +/// // Disconnect from the server (optional) +/// ftp.disconnect(); +/// \endcode +/// +//////////////////////////////////////////////////////////// diff --git a/sfml/include/SFML/Network/Http.hpp b/sfml/include/SFML/Network/Http.hpp new file mode 100644 index 0000000..46d2d98 --- /dev/null +++ b/sfml/include/SFML/Network/Http.hpp @@ -0,0 +1,480 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +#include +#include + +#include + +#include +#include +#include +#include + + +namespace sf +{ +//////////////////////////////////////////////////////////// +/// \brief A HTTP client +/// +//////////////////////////////////////////////////////////// +class SFML_NETWORK_API Http +{ +public: + //////////////////////////////////////////////////////////// + /// \brief HTTP request + /// + //////////////////////////////////////////////////////////// + class SFML_NETWORK_API Request + { + public: + //////////////////////////////////////////////////////////// + /// \brief Enumerate the available HTTP methods for a request + /// + //////////////////////////////////////////////////////////// + enum class Method + { + Get, //!< Request in get mode, standard method to retrieve a page + Post, //!< Request in post mode, usually to send data to a page + Head, //!< Request a page's header only + Put, //!< Request in put mode, useful for a REST API + Delete //!< Request in delete mode, useful for a REST API + }; + + //////////////////////////////////////////////////////////// + /// \brief Default constructor + /// + /// This constructor creates a GET request, with the root + /// URI ("/") and an empty body. + /// + /// \param uri Target URI + /// \param method Method to use for the request + /// \param body Content of the request's body + /// + //////////////////////////////////////////////////////////// + Request(const std::string& uri = "/", Method method = Method::Get, const std::string& body = ""); + + //////////////////////////////////////////////////////////// + /// \brief Set the value of a field + /// + /// The field is created if it doesn't exist. The name of + /// the field is case-insensitive. + /// By default, a request doesn't contain any field (but the + /// mandatory fields are added later by the HTTP client when + /// sending the request). + /// + /// \param field Name of the field to set + /// \param value Value of the field + /// + //////////////////////////////////////////////////////////// + void setField(const std::string& field, const std::string& value); + + //////////////////////////////////////////////////////////// + /// \brief Set the request method + /// + /// See the Method enumeration for a complete list of all + /// the available methods. + /// The method is `Http::Request::Method::Get` by default. + /// + /// \param method Method to use for the request + /// + //////////////////////////////////////////////////////////// + void setMethod(Method method); + + //////////////////////////////////////////////////////////// + /// \brief Set the requested URI + /// + /// The URI is the resource (usually a web page or a file) + /// that you want to get or post. + /// The URI is "/" (the root page) by default. + /// + /// \param uri URI to request, relative to the host + /// + //////////////////////////////////////////////////////////// + void setUri(const std::string& uri); + + //////////////////////////////////////////////////////////// + /// \brief Set the HTTP version for the request + /// + /// The HTTP version is 1.0 by default. + /// + /// \param major Major HTTP version number + /// \param minor Minor HTTP version number + /// + //////////////////////////////////////////////////////////// + void setHttpVersion(unsigned int major, unsigned int minor); + + //////////////////////////////////////////////////////////// + /// \brief Set the body of the request + /// + /// The body of a request is optional and only makes sense + /// for POST requests. It is ignored for all other methods. + /// The body is empty by default. + /// + /// \param body Content of the body + /// + //////////////////////////////////////////////////////////// + void setBody(const std::string& body); + + private: + friend class Http; + + //////////////////////////////////////////////////////////// + /// \brief Prepare the final request to send to the server + /// + /// This is used internally by Http before sending the + /// request to the web server. + /// + /// \return String containing the request, ready to be sent + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] std::string prepare() const; + + //////////////////////////////////////////////////////////// + /// \brief Check if the request defines a field + /// + /// This function uses case-insensitive comparisons. + /// + /// \param field Name of the field to test + /// + /// \return `true` if the field exists, `false` otherwise + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] bool hasField(const std::string& field) const; + + //////////////////////////////////////////////////////////// + // Types + //////////////////////////////////////////////////////////// + using FieldTable = std::map; // Use an ordered map for predictable payloads + + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + FieldTable m_fields; //!< Fields of the header associated to their value + Method m_method; //!< Method to use for the request + std::string m_uri; //!< Target URI of the request + unsigned int m_majorVersion{1}; //!< Major HTTP version + unsigned int m_minorVersion{}; //!< Minor HTTP version + std::string m_body; //!< Body of the request + }; + + //////////////////////////////////////////////////////////// + /// \brief HTTP response + /// + //////////////////////////////////////////////////////////// + class SFML_NETWORK_API Response + { + public: + //////////////////////////////////////////////////////////// + /// \brief Enumerate all the valid status codes for a response + /// + //////////////////////////////////////////////////////////// + enum class Status + { + // 2xx: success + Ok = 200, //!< Most common code returned when operation was successful + Created = 201, //!< The resource has successfully been created + Accepted = 202, //!< The request has been accepted, but will be processed later by the server + NoContent = 204, //!< The server didn't send any data in return + ResetContent = 205, //!< The server informs the client that it should clear the view (form) that caused the request to be sent + PartialContent = 206, //!< The server has sent a part of the resource, as a response to a partial GET request + + // 3xx: redirection + MultipleChoices = 300, //!< The requested page can be accessed from several locations + MovedPermanently = 301, //!< The requested page has permanently moved to a new location + MovedTemporarily = 302, //!< The requested page has temporarily moved to a new location + NotModified = 304, //!< For conditional requests, means the requested page hasn't changed and doesn't need to be refreshed + + // 4xx: client error + BadRequest = 400, //!< The server couldn't understand the request (syntax error) + Unauthorized = 401, //!< The requested page needs an authentication to be accessed + Forbidden = 403, //!< The requested page cannot be accessed at all, even with authentication + NotFound = 404, //!< The requested page doesn't exist + RangeNotSatisfiable = 407, //!< The server can't satisfy the partial GET request (with a "Range" header field) + + // 5xx: server error + InternalServerError = 500, //!< The server encountered an unexpected error + NotImplemented = 501, //!< The server doesn't implement a requested feature + BadGateway = 502, //!< The gateway server has received an error from the source server + ServiceNotAvailable = 503, //!< The server is temporarily unavailable (overloaded, in maintenance, ...) + GatewayTimeout = 504, //!< The gateway server couldn't receive a response from the source server + VersionNotSupported = 505, //!< The server doesn't support the requested HTTP version + + // 10xx: SFML custom codes + InvalidResponse = 1000, //!< Response is not a valid HTTP one + ConnectionFailed = 1001 //!< Connection with server failed + }; + + //////////////////////////////////////////////////////////// + /// \brief Get the value of a field + /// + /// If the field `field` is not found in the response header, + /// the empty string is returned. This function uses + /// case-insensitive comparisons. + /// + /// \param field Name of the field to get + /// + /// \return Value of the field, or empty string if not found + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] const std::string& getField(const std::string& field) const; + + //////////////////////////////////////////////////////////// + /// \brief Get the response status code + /// + /// The status code should be the first thing to be checked + /// after receiving a response, it defines whether it is a + /// success, a failure or anything else (see the Status + /// enumeration). + /// + /// \return Status code of the response + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Status getStatus() const; + + //////////////////////////////////////////////////////////// + /// \brief Get the major HTTP version number of the response + /// + /// \return Major HTTP version number + /// + /// \see `getMinorHttpVersion` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] unsigned int getMajorHttpVersion() const; + + //////////////////////////////////////////////////////////// + /// \brief Get the minor HTTP version number of the response + /// + /// \return Minor HTTP version number + /// + /// \see `getMajorHttpVersion` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] unsigned int getMinorHttpVersion() const; + + //////////////////////////////////////////////////////////// + /// \brief Get the body of the response + /// + /// The body of a response may contain: + /// \li the requested page (for GET requests) + /// \li a response from the server (for POST requests) + /// \li nothing (for HEAD requests) + /// \li an error message (in case of an error) + /// + /// \return The response body + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] const std::string& getBody() const; + + private: + friend class Http; + + //////////////////////////////////////////////////////////// + /// \brief Construct the header from a response string + /// + /// This function is used by `Http` to build the response + /// of a request. + /// + /// \param data Content of the response to parse + /// + //////////////////////////////////////////////////////////// + void parse(const std::string& data); + + + //////////////////////////////////////////////////////////// + /// \brief Read values passed in the answer header + /// + /// This function is used by `Http` to extract values passed + /// in the response. + /// + /// \param in String stream containing the header values + /// + //////////////////////////////////////////////////////////// + void parseFields(std::istream& in); + + //////////////////////////////////////////////////////////// + // Types + //////////////////////////////////////////////////////////// + using FieldTable = std::map; // Use an ordered map for predictable payloads + + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + FieldTable m_fields; //!< Fields of the header + Status m_status{Status::ConnectionFailed}; //!< Status code + unsigned int m_majorVersion{}; //!< Major HTTP version + unsigned int m_minorVersion{}; //!< Minor HTTP version + std::string m_body; //!< Body of the response + }; + + //////////////////////////////////////////////////////////// + /// \brief Default constructor + /// + //////////////////////////////////////////////////////////// + Http() = default; + + //////////////////////////////////////////////////////////// + /// \brief Construct the HTTP client with the target host + /// + /// This is equivalent to calling `setHost(host, port)`. + /// The port has a default value of 0, which means that the + /// HTTP client will use the right port according to the + /// protocol used (80 for HTTP). You should leave it like + /// this unless you really need a port other than the + /// standard one, or use an unknown protocol. + /// + /// \param host Web server to connect to + /// \param port Port to use for connection + /// + //////////////////////////////////////////////////////////// + Http(const std::string& host, unsigned short port = 0); + + //////////////////////////////////////////////////////////// + /// \brief Deleted copy constructor + /// + //////////////////////////////////////////////////////////// + Http(const Http&) = delete; + + //////////////////////////////////////////////////////////// + /// \brief Deleted copy assignment + /// + //////////////////////////////////////////////////////////// + Http& operator=(const Http&) = delete; + + //////////////////////////////////////////////////////////// + /// \brief Set the target host + /// + /// This function just stores the host address and port, it + /// doesn't actually connect to it until you send a request. + /// The port has a default value of 0, which means that the + /// HTTP client will use the right port according to the + /// protocol used (80 for HTTP). You should leave it like + /// this unless you really need a port other than the + /// standard one, or use an unknown protocol. + /// + /// \param host Web server to connect to + /// \param port Port to use for connection + /// + //////////////////////////////////////////////////////////// + void setHost(const std::string& host, unsigned short port = 0); + + //////////////////////////////////////////////////////////// + /// \brief Send a HTTP request and return the server's response. + /// + /// You must have a valid host before sending a request (see `setHost`). + /// Any missing mandatory header field in the request will be added + /// with an appropriate value. + /// Warning: this function waits for the server's response and may + /// not return instantly; use a thread if you don't want to block your + /// application, or use a timeout to limit the time to wait. A value + /// of `Time::Zero` means that the client will use the system default timeout + /// (which is usually pretty long). + /// + /// \param request Request to send + /// \param timeout Maximum time to wait + /// + /// \return Server's response + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Response sendRequest(const Request& request, Time timeout = Time::Zero); + +private: + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + TcpSocket m_connection; //!< Connection to the host + std::optional m_host; //!< Web host address + std::string m_hostName; //!< Web host name + unsigned short m_port{}; //!< Port used for connection with host +}; + +} // namespace sf + + +//////////////////////////////////////////////////////////// +/// \class sf::Http +/// \ingroup network +/// +/// `sf::Http` is a very simple HTTP client that allows you +/// to communicate with a web server. You can retrieve +/// web pages, send data to an interactive resource, +/// download a remote file, etc. The HTTPS protocol is +/// not supported. +/// +/// The HTTP client is split into 3 classes: +/// \li `sf::Http::Request` +/// \li `sf::Http::Response` +/// \li `sf::Http` +/// +/// `sf::Http::Request` builds the request that will be +/// sent to the server. A request is made of: +/// \li a method (what you want to do) +/// \li a target URI (usually the name of the web page or file) +/// \li one or more header fields (options that you can pass to the server) +/// \li an optional body (for POST requests) +/// +/// `sf::Http::Response` parse the response from the web server +/// and provides getters to read them. The response contains: +/// \li a status code +/// \li header fields (that may be answers to the ones that you requested) +/// \li a body, which contains the contents of the requested resource +/// +/// `sf::Http` provides a simple function, SendRequest, to send a +/// `sf::Http::Request` and return the corresponding `sf::Http::Response` +/// from the server. +/// +/// Usage example: +/// \code +/// // Create a new HTTP client +/// sf::Http http; +/// +/// // We'll work on http://www.sfml-dev.org +/// http.setHost("http://www.sfml-dev.org"); +/// +/// // Prepare a request to get the 'features.php' page +/// sf::Http::Request request("features.php"); +/// +/// // Send the request +/// sf::Http::Response response = http.sendRequest(request); +/// +/// // Check the status code and display the result +/// sf::Http::Response::Status status = response.getStatus(); +/// if (status == sf::Http::Response::Status::Ok) +/// { +/// std::cout << response.getBody() << std::endl; +/// } +/// else +/// { +/// std::cout << "Error " << status << std::endl; +/// } +/// \endcode +/// +//////////////////////////////////////////////////////////// diff --git a/sfml/include/SFML/Network/IpAddress.hpp b/sfml/include/SFML/Network/IpAddress.hpp new file mode 100644 index 0000000..a43fd0f --- /dev/null +++ b/sfml/include/SFML/Network/IpAddress.hpp @@ -0,0 +1,297 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +#include + +#include +#include +#include +#include + +#include + + +namespace sf +{ +//////////////////////////////////////////////////////////// +/// \brief Encapsulate an IPv4 network address +/// +//////////////////////////////////////////////////////////// +class SFML_NETWORK_API IpAddress +{ +public: + //////////////////////////////////////////////////////////// + /// \brief Construct the address from a null-terminated string view + /// + /// Here \a address can be either a decimal address + /// (ex: "192.168.1.56") or a network name (ex: "localhost"). + /// + /// \param address IP address or network name + /// + /// \return Address if provided argument was valid, otherwise `std::nullopt` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] static std::optional resolve(std::string_view address); + + //////////////////////////////////////////////////////////// + /// \brief Construct the address from 4 bytes + /// + /// Calling `IpAddress(a, b, c, d)` is equivalent to calling + /// `IpAddress::resolve("a.b.c.d")`, but safer as it doesn't + /// have to parse a string to get the address components. + /// + /// \param byte0 First byte of the address + /// \param byte1 Second byte of the address + /// \param byte2 Third byte of the address + /// \param byte3 Fourth byte of the address + /// + //////////////////////////////////////////////////////////// + IpAddress(std::uint8_t byte0, std::uint8_t byte1, std::uint8_t byte2, std::uint8_t byte3); + + //////////////////////////////////////////////////////////// + /// \brief Construct the address from a 32-bits integer + /// + /// This constructor uses the internal representation of + /// the address directly. It should be used for optimization + /// purposes, and only if you got that representation from + /// `IpAddress::toInteger()`. + /// + /// \param address 4 bytes of the address packed into a 32-bits integer + /// + /// \see `toInteger` + /// + //////////////////////////////////////////////////////////// + explicit IpAddress(std::uint32_t address); + + //////////////////////////////////////////////////////////// + /// \brief Get a string representation of the address + /// + /// The returned string is the decimal representation of the + /// IP address (like "192.168.1.56"), even if it was constructed + /// from a host name. + /// + /// \return String representation of the address + /// + /// \see `toInteger` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] std::string toString() const; + + //////////////////////////////////////////////////////////// + /// \brief Get an integer representation of the address + /// + /// The returned number is the internal representation of the + /// address, and should be used for optimization purposes only + /// (like sending the address through a socket). + /// The integer produced by this function can then be converted + /// back to a `sf::IpAddress` with the proper constructor. + /// + /// \return 32-bits unsigned integer representation of the address + /// + /// \see `toString` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] std::uint32_t toInteger() const; + + //////////////////////////////////////////////////////////// + /// \brief Get the computer's local address + /// + /// The local address is the address of the computer from the + /// LAN point of view, i.e. something like 192.168.1.56. It is + /// meaningful only for communications over the local network. + /// Unlike getPublicAddress, this function is fast and may be + /// used safely anywhere. + /// + /// \return Local IP address of the computer on success, `std::nullopt` otherwise + /// + /// \see `getPublicAddress` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] static std::optional getLocalAddress(); + + //////////////////////////////////////////////////////////// + /// \brief Get the computer's public address + /// + /// The public address is the address of the computer from the + /// internet point of view, i.e. something like 89.54.1.169. + /// It is necessary for communications over the world wide web. + /// The only way to get a public address is to ask it to a + /// distant website; as a consequence, this function depends on + /// both your network connection and the server, and may be + /// very slow. You should use it as few as possible. Because + /// this function depends on the network connection and on a distant + /// server, you may use a time limit if you don't want your program + /// to be possibly stuck waiting in case there is a problem; this + /// limit is deactivated by default. + /// + /// \param timeout Maximum time to wait + /// + /// \return Public IP address of the computer on success, `std::nullopt` otherwise + /// + /// \see `getLocalAddress` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] static std::optional getPublicAddress(Time timeout = Time::Zero); + + //////////////////////////////////////////////////////////// + // Static member data + //////////////////////////////////////////////////////////// + // NOLINTBEGIN(readability-identifier-naming) + static const IpAddress Any; //!< Value representing any address (0.0.0.0) + static const IpAddress LocalHost; //!< The "localhost" address (for connecting a computer to itself locally) + static const IpAddress Broadcast; //!< The "broadcast" address (for sending UDP messages to everyone on a local network) + // NOLINTEND(readability-identifier-naming) + +private: + friend SFML_NETWORK_API bool operator<(IpAddress left, IpAddress right); + + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + std::uint32_t m_address; //!< Address stored as an unsigned 32 bit integer +}; + +//////////////////////////////////////////////////////////// +/// \brief Overload of `operator==` to compare two IP addresses +/// +/// \param left Left operand (a IP address) +/// \param right Right operand (a IP address) +/// +/// \return `true` if both addresses are equal +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] SFML_NETWORK_API bool operator==(IpAddress left, IpAddress right); + +//////////////////////////////////////////////////////////// +/// \brief Overload of `operator!=` to compare two IP addresses +/// +/// \param left Left operand (a IP address) +/// \param right Right operand (a IP address) +/// +/// \return `true` if both addresses are different +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] SFML_NETWORK_API bool operator!=(IpAddress left, IpAddress right); + +//////////////////////////////////////////////////////////// +/// \brief Overload of `operator<` to compare two IP addresses +/// +/// \param left Left operand (a IP address) +/// \param right Right operand (a IP address) +/// +/// \return `true` if `left` is lesser than `right` +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] SFML_NETWORK_API bool operator<(IpAddress left, IpAddress right); + +//////////////////////////////////////////////////////////// +/// \brief Overload of `operator>` to compare two IP addresses +/// +/// \param left Left operand (a IP address) +/// \param right Right operand (a IP address) +/// +/// \return `true` if `left` is greater than `right` +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] SFML_NETWORK_API bool operator>(IpAddress left, IpAddress right); + +//////////////////////////////////////////////////////////// +/// \brief Overload of `operator<=` to compare two IP addresses +/// +/// \param left Left operand (a IP address) +/// \param right Right operand (a IP address) +/// +/// \return `true` if \a left is lesser or equal than \a right +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] SFML_NETWORK_API bool operator<=(IpAddress left, IpAddress right); + +//////////////////////////////////////////////////////////// +/// \brief Overload of `operator>=` to compare two IP addresses +/// +/// \param left Left operand (a IP address) +/// \param right Right operand (a IP address) +/// +/// \return `true` if `left` is greater or equal than `right` +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] SFML_NETWORK_API bool operator>=(IpAddress left, IpAddress right); + +//////////////////////////////////////////////////////////// +/// \brief Overload of `operator>>` to extract an IP address from an input stream +/// +/// \param stream Input stream +/// \param address IP address to extract +/// +/// \return Reference to the input stream +/// +//////////////////////////////////////////////////////////// +SFML_NETWORK_API std::istream& operator>>(std::istream& stream, std::optional& address); + +//////////////////////////////////////////////////////////// +/// \brief Overload of `operator<<` to print an IP address to an output stream +/// +/// \param stream Output stream +/// \param address IP address to print +/// +/// \return Reference to the output stream +/// +//////////////////////////////////////////////////////////// +SFML_NETWORK_API std::ostream& operator<<(std::ostream& stream, IpAddress address); + +} // namespace sf + + +//////////////////////////////////////////////////////////// +/// \class sf::IpAddress +/// \ingroup network +/// +/// `sf::IpAddress` is a utility class for manipulating network +/// addresses. It provides a set a implicit constructors and +/// conversion functions to easily build or transform an IP +/// address from/to various representations. +/// +/// Usage example: +/// \code +/// auto a2 = sf::IpAddress::resolve("127.0.0.1"); // the local host address +/// auto a3 = sf::IpAddress::Broadcast; // the broadcast address +/// sf::IpAddress a4(192, 168, 1, 56); // a local address +/// auto a5 = sf::IpAddress::resolve("my_computer"); // a local address created from a network name +/// auto a6 = sf::IpAddress::resolve("89.54.1.169"); // a distant address +/// auto a7 = sf::IpAddress::resolve("www.google.com"); // a distant address created from a network name +/// auto a8 = sf::IpAddress::getLocalAddress(); // my address on the local network +/// auto a9 = sf::IpAddress::getPublicAddress(); // my address on the internet +/// \endcode +/// +/// Note that `sf::IpAddress` currently doesn't support IPv6 +/// nor other types of network addresses. +/// +//////////////////////////////////////////////////////////// diff --git a/sfml/include/SFML/Network/Packet.hpp b/sfml/include/SFML/Network/Packet.hpp new file mode 100644 index 0000000..460a44c --- /dev/null +++ b/sfml/include/SFML/Network/Packet.hpp @@ -0,0 +1,548 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +#include +#include + +#include +#include + + +namespace sf +{ +class String; + +//////////////////////////////////////////////////////////// +/// \brief Utility class to build blocks of data to transfer +/// over the network +/// +//////////////////////////////////////////////////////////// +class SFML_NETWORK_API Packet +{ +public: + //////////////////////////////////////////////////////////// + /// \brief Default constructor + /// + /// Creates an empty packet. + /// + //////////////////////////////////////////////////////////// + Packet() = default; + + //////////////////////////////////////////////////////////// + /// \brief Virtual destructor + /// + //////////////////////////////////////////////////////////// + virtual ~Packet() = default; + + //////////////////////////////////////////////////////////// + /// \brief Copy constructor + /// + //////////////////////////////////////////////////////////// + Packet(const Packet&) = default; + + //////////////////////////////////////////////////////////// + /// \brief Copy assignment + /// + //////////////////////////////////////////////////////////// + Packet& operator=(const Packet&) = default; + + //////////////////////////////////////////////////////////// + /// \brief Move constructor + /// + //////////////////////////////////////////////////////////// + Packet(Packet&&) noexcept = default; + + //////////////////////////////////////////////////////////// + /// \brief Move assignment + /// + //////////////////////////////////////////////////////////// + Packet& operator=(Packet&&) noexcept = default; + + //////////////////////////////////////////////////////////// + /// \brief Append data to the end of the packet + /// + /// \param data Pointer to the sequence of bytes to append + /// \param sizeInBytes Number of bytes to append + /// + /// \see `clear` + /// \see `getReadPosition` + /// + //////////////////////////////////////////////////////////// + void append(const void* data, std::size_t sizeInBytes); + + //////////////////////////////////////////////////////////// + /// \brief Get the current reading position in the packet + /// + /// The next read operation will read data from this position + /// + /// \return The byte offset of the current read position + /// + /// \see `append` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] std::size_t getReadPosition() const; + + //////////////////////////////////////////////////////////// + /// \brief Clear the packet + /// + /// After calling Clear, the packet is empty. + /// + /// \see `append` + /// + //////////////////////////////////////////////////////////// + void clear(); + + //////////////////////////////////////////////////////////// + /// \brief Get a pointer to the data contained in the packet + /// + /// Warning: the returned pointer may become invalid after + /// you append data to the packet, therefore it should never + /// be stored. + /// The return pointer is a `nullptr` if the packet is empty. + /// + /// \return Pointer to the data + /// + /// \see `getDataSize` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] const void* getData() const; + + //////////////////////////////////////////////////////////// + /// \brief Get the size of the data contained in the packet + /// + /// This function returns the number of bytes pointed to by + /// what `getData` returns. + /// + /// \return Data size, in bytes + /// + /// \see `getData` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] std::size_t getDataSize() const; + + //////////////////////////////////////////////////////////// + /// \brief Tell if the reading position has reached the + /// end of the packet + /// + /// This function is useful to know if there is some data + /// left to be read, without actually reading it. + /// + /// \return `true` if all data was read, `false` otherwise + /// + /// \see `operator` bool + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] bool endOfPacket() const; + + //////////////////////////////////////////////////////////// + /// \brief Test the validity of the packet, for reading + /// + /// This operator allows to test the packet as a boolean + /// variable, to check if a reading operation was successful. + /// + /// A packet will be in an invalid state if it has no more + /// data to read. + /// + /// This behavior is the same as standard C++ streams. + /// + /// Usage example: + /// \code + /// float x; + /// packet >> x; + /// if (packet) + /// { + /// // ok, x was extracted successfully + /// } + /// + /// // -- or -- + /// + /// float x; + /// if (packet >> x) + /// { + /// // ok, x was extracted successfully + /// } + /// \endcode + /// + /// \return `true` if last data extraction from packet was successful + /// + /// \see `endOfPacket` + /// + //////////////////////////////////////////////////////////// + explicit operator bool() const; + + //////////////////////////////////////////////////////////// + /// Overload of `operator>>` to read data from the packet + /// + //////////////////////////////////////////////////////////// + Packet& operator>>(bool& data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator>>(std::int8_t& data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator>>(std::uint8_t& data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator>>(std::int16_t& data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator>>(std::uint16_t& data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator>>(std::int32_t& data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator>>(std::uint32_t& data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator>>(std::int64_t& data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator>>(std::uint64_t& data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator>>(float& data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator>>(double& data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator>>(char* data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator>>(std::string& data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator>>(wchar_t* data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator>>(std::wstring& data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator>>(String& data); + + //////////////////////////////////////////////////////////// + /// Overload of `operator<<` to write data into the packet + /// + //////////////////////////////////////////////////////////// + Packet& operator<<(bool data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator<<(std::int8_t data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator<<(std::uint8_t data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator<<(std::int16_t data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator<<(std::uint16_t data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator<<(std::int32_t data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator<<(std::uint32_t data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator<<(std::int64_t data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator<<(std::uint64_t data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator<<(float data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator<<(double data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator<<(const char* data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator<<(const std::string& data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator<<(const wchar_t* data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator<<(const std::wstring& data); + + //////////////////////////////////////////////////////////// + /// \overload + //////////////////////////////////////////////////////////// + Packet& operator<<(const String& data); + +protected: + friend class TcpSocket; + friend class UdpSocket; + + //////////////////////////////////////////////////////////// + /// \brief Called before the packet is sent over the network + /// + /// This function can be defined by derived classes to + /// transform the data before it is sent; this can be + /// used for compression, encryption, etc. + /// The function must return a pointer to the modified data, + /// as well as the number of bytes pointed. + /// The default implementation provides the packet's data + /// without transforming it. + /// + /// \param size Variable to fill with the size of data to send + /// + /// \return Pointer to the array of bytes to send + /// + /// \see `onReceive` + /// + //////////////////////////////////////////////////////////// + virtual const void* onSend(std::size_t& size); + + //////////////////////////////////////////////////////////// + /// \brief Called after the packet is received over the network + /// + /// This function can be defined by derived classes to + /// transform the data after it is received; this can be + /// used for decompression, decryption, etc. + /// The function receives a pointer to the received data, + /// and must fill the packet with the transformed bytes. + /// The default implementation fills the packet directly + /// without transforming the data. + /// + /// \param data Pointer to the received bytes + /// \param size Number of bytes + /// + /// \see `onSend` + /// + //////////////////////////////////////////////////////////// + virtual void onReceive(const void* data, std::size_t size); + +private: + //////////////////////////////////////////////////////////// + /// \brief Check if the packet can extract a given number of bytes + /// + /// This function updates accordingly the state of the packet. + /// + /// \param size Size to check + /// + /// \return `true` if \a size bytes can be read from the packet + /// + //////////////////////////////////////////////////////////// + bool checkSize(std::size_t size); + + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + std::vector m_data; //!< Data stored in the packet + std::size_t m_readPos{}; //!< Current reading position in the packet + std::size_t m_sendPos{}; //!< Current send position in the packet (for handling partial sends) + bool m_isValid{true}; //!< Reading state of the packet +}; + +} // namespace sf + + +//////////////////////////////////////////////////////////// +/// \class sf::Packet +/// \ingroup network +/// +/// Packets provide a safe and easy way to serialize data, +/// in order to send it over the network using sockets +/// (`sf::TcpSocket`, `sf::UdpSocket`). +/// +/// Packets solve 2 fundamental problems that arise when +/// transferring data over the network: +/// \li data is interpreted correctly according to the endianness +/// \li the bounds of the packet are preserved (one send == one receive) +/// +/// The `sf::Packet` class provides both input and output modes. +/// It is designed to follow the behavior of standard C++ streams, +/// using operators >> and << to extract and insert data. +/// +/// It is recommended to use only fixed-size types (like `std::int32_t`, etc.), +/// to avoid possible differences between the sender and the receiver. +/// Indeed, the native C++ types may have different sizes on two platforms +/// and your data may be corrupted if that happens. +/// +/// Usage example: +/// \code +/// std::uint32_t x = 24; +/// std::string s = "hello"; +/// double d = 5.89; +/// +/// // Group the variables to send into a packet +/// sf::Packet packet; +/// packet << x << s << d; +/// +/// // Send it over the network (socket is a valid sf::TcpSocket) +/// socket.send(packet); +/// +/// ----------------------------------------------------------------- +/// +/// // Receive the packet at the other end +/// sf::Packet packet; +/// socket.receive(packet); +/// +/// // Extract the variables contained in the packet +/// std::uint32_t x; +/// std::string s; +/// double d; +/// if (packet >> x >> s >> d) +/// { +/// // Data extracted successfully... +/// } +/// \endcode +/// +/// Packets have built-in `operator>>` and << overloads for +/// standard types: +/// \li `bool` +/// \li fixed-size integer types (`int[8|16|32]_t`, `uint[8|16|32]_t`) +/// \li floating point numbers (`float`, `double`) +/// \li string types (`char*`, `wchar_t*`, `std::string`, `std::wstring`, `sf::String`) +/// +/// Like standard streams, it is also possible to define your own +/// overloads of operators >> and << in order to handle your +/// custom types. +/// +/// \code +/// struct MyStruct +/// { +/// float number{}; +/// std::int8_t integer{}; +/// std::string str; +/// }; +/// +/// sf::Packet& operator <<(sf::Packet& packet, const MyStruct& m) +/// { +/// return packet << m.number << m.integer << m.str; +/// } +/// +/// sf::Packet& operator >>(sf::Packet& packet, MyStruct& m) +/// { +/// return packet >> m.number >> m.integer >> m.str; +/// } +/// \endcode +/// +/// Packets also provide an extra feature that allows to apply +/// custom transformations to the data before it is sent, +/// and after it is received. This is typically used to +/// handle automatic compression or encryption of the data. +/// This is achieved by inheriting from `sf::Packet`, and overriding +/// the onSend and onReceive functions. +/// +/// Here is an example: +/// \code +/// class ZipPacket : public sf::Packet +/// { +/// const void* onSend(std::size_t& size) override +/// { +/// const void* srcData = getData(); +/// std::size_t srcSize = getDataSize(); +/// +/// return MySuperZipFunction(srcData, srcSize, &size); +/// } +/// +/// void onReceive(const void* data, std::size_t size) override +/// { +/// std::size_t dstSize; +/// const void* dstData = MySuperUnzipFunction(data, size, &dstSize); +/// +/// append(dstData, dstSize); +/// } +/// }; +/// +/// // Use like regular packets: +/// ZipPacket packet; +/// packet << x << s << d; +/// ... +/// \endcode +/// +/// \see `sf::TcpSocket`, `sf::UdpSocket` +/// +//////////////////////////////////////////////////////////// diff --git a/sfml/include/SFML/Network/Socket.hpp b/sfml/include/SFML/Network/Socket.hpp new file mode 100644 index 0000000..2b7b79f --- /dev/null +++ b/sfml/include/SFML/Network/Socket.hpp @@ -0,0 +1,229 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +#include + + +namespace sf +{ +//////////////////////////////////////////////////////////// +/// \brief Base class for all the socket types +/// +//////////////////////////////////////////////////////////// +class SFML_NETWORK_API Socket +{ +public: + //////////////////////////////////////////////////////////// + /// \brief Status codes that may be returned by socket functions + /// + //////////////////////////////////////////////////////////// + enum class Status + { + Done, //!< The socket has sent / received the data + NotReady, //!< The socket is not ready to send / receive data yet + Partial, //!< The socket sent a part of the data + Disconnected, //!< The TCP socket has been disconnected + Error //!< An unexpected error happened + }; + + //////////////////////////////////////////////////////////// + /// \brief Some special values used by sockets + /// + //////////////////////////////////////////////////////////// + // NOLINTNEXTLINE(readability-identifier-naming) + static constexpr unsigned short AnyPort{0}; //!< Special value that tells the system to pick any available port + + //////////////////////////////////////////////////////////// + /// \brief Destructor + /// + //////////////////////////////////////////////////////////// + virtual ~Socket(); + + //////////////////////////////////////////////////////////// + /// \brief Deleted copy constructor + /// + //////////////////////////////////////////////////////////// + Socket(const Socket&) = delete; + + //////////////////////////////////////////////////////////// + /// \brief Deleted copy assignment + /// + //////////////////////////////////////////////////////////// + Socket& operator=(const Socket&) = delete; + + //////////////////////////////////////////////////////////// + /// \brief Move constructor + /// + //////////////////////////////////////////////////////////// + Socket(Socket&& socket) noexcept; + + //////////////////////////////////////////////////////////// + /// \brief Move assignment + /// + //////////////////////////////////////////////////////////// + Socket& operator=(Socket&& socket) noexcept; + + //////////////////////////////////////////////////////////// + /// \brief Set the blocking state of the socket + /// + /// In blocking mode, calls will not return until they have + /// completed their task. For example, a call to Receive in + /// blocking mode won't return until some data was actually + /// received. + /// In non-blocking mode, calls will always return immediately, + /// using the return code to signal whether there was data + /// available or not. + /// By default, all sockets are blocking. + /// + /// \param blocking `true` to set the socket as blocking, `false` for non-blocking + /// + /// \see `isBlocking` + /// + //////////////////////////////////////////////////////////// + void setBlocking(bool blocking); + + //////////////////////////////////////////////////////////// + /// \brief Tell whether the socket is in blocking or non-blocking mode + /// + /// \return `true` if the socket is blocking, `false` otherwise + /// + /// \see `setBlocking` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] bool isBlocking() const; + +protected: + //////////////////////////////////////////////////////////// + /// \brief Types of protocols that the socket can use + /// + //////////////////////////////////////////////////////////// + enum class Type + { + Tcp, //!< TCP protocol + Udp //!< UDP protocol + }; + + //////////////////////////////////////////////////////////// + /// \brief Default constructor + /// + /// This constructor can only be accessed by derived classes. + /// + /// \param type Type of the socket (TCP or UDP) + /// + //////////////////////////////////////////////////////////// + explicit Socket(Type type); + + //////////////////////////////////////////////////////////// + /// \brief Return the internal handle of the socket + /// + /// The returned handle may be invalid if the socket + /// was not created yet (or already destroyed). + /// This function can only be accessed by derived classes. + /// + /// \return The internal (OS-specific) handle of the socket + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] SocketHandle getNativeHandle() const; + + //////////////////////////////////////////////////////////// + /// \brief Create the internal representation of the socket + /// + /// This function can only be accessed by derived classes. + /// + //////////////////////////////////////////////////////////// + void create(); + + //////////////////////////////////////////////////////////// + /// \brief Create the internal representation of the socket + /// from a socket handle + /// + /// This function can only be accessed by derived classes. + /// + /// \param handle OS-specific handle of the socket to wrap + /// + //////////////////////////////////////////////////////////// + void create(SocketHandle handle); + + //////////////////////////////////////////////////////////// + /// \brief Close the socket gracefully + /// + /// This function can only be accessed by derived classes. + /// + //////////////////////////////////////////////////////////// + void close(); + +private: + friend class SocketSelector; + + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + Type m_type; //!< Type of the socket (TCP or UDP) + SocketHandle m_socket; //!< Socket descriptor + bool m_isBlocking{true}; //!< Current blocking mode of the socket +}; + +} // namespace sf + + +//////////////////////////////////////////////////////////// +/// \class sf::Socket +/// \ingroup network +/// +/// This class mainly defines internal stuff to be used by +/// derived classes. +/// +/// The only public features that it defines, and which +/// is therefore common to all the socket classes, is the +/// blocking state. All sockets can be set as blocking or +/// non-blocking. +/// +/// In blocking mode, socket functions will hang until +/// the operation completes, which means that the entire +/// program (well, in fact the current thread if you use +/// multiple ones) will be stuck waiting for your socket +/// operation to complete. +/// +/// In non-blocking mode, all the socket functions will +/// return immediately. If the socket is not ready to complete +/// the requested operation, the function simply returns +/// the proper status code (`Socket::Status::NotReady`). +/// +/// The default mode, which is blocking, is the one that is +/// generally used, in combination with threads or selectors. +/// The non-blocking mode is rather used in real-time +/// applications that run an endless loop that can poll +/// the socket often enough, and cannot afford blocking +/// this loop. +/// +/// \see `sf::TcpListener`, `sf::TcpSocket`, `sf::UdpSocket` +/// +//////////////////////////////////////////////////////////// diff --git a/sfml/include/SFML/Network/SocketHandle.hpp b/sfml/include/SFML/Network/SocketHandle.hpp new file mode 100644 index 0000000..7025069 --- /dev/null +++ b/sfml/include/SFML/Network/SocketHandle.hpp @@ -0,0 +1,52 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +#if defined(SFML_SYSTEM_WINDOWS) +#include +#endif + + +namespace sf +{ +//////////////////////////////////////////////////////////// +// Low-level socket handle type, specific to each platform +//////////////////////////////////////////////////////////// +#if defined(SFML_SYSTEM_WINDOWS) + +using SocketHandle = UINT_PTR; + +#else + +using SocketHandle = int; + +#endif + +} // namespace sf diff --git a/sfml/include/SFML/Network/SocketSelector.hpp b/sfml/include/SFML/Network/SocketSelector.hpp new file mode 100644 index 0000000..ac80a87 --- /dev/null +++ b/sfml/include/SFML/Network/SocketSelector.hpp @@ -0,0 +1,273 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +#include + +#include + + +namespace sf +{ +class Socket; + +//////////////////////////////////////////////////////////// +/// \brief Multiplexer that allows to read from multiple sockets +/// +//////////////////////////////////////////////////////////// +class SFML_NETWORK_API SocketSelector +{ +public: + //////////////////////////////////////////////////////////// + /// \brief Default constructor + /// + //////////////////////////////////////////////////////////// + SocketSelector(); + + //////////////////////////////////////////////////////////// + /// \brief Destructor + /// + //////////////////////////////////////////////////////////// + ~SocketSelector(); + + //////////////////////////////////////////////////////////// + /// \brief Copy constructor + /// + /// \param copy Instance to copy + /// + //////////////////////////////////////////////////////////// + SocketSelector(const SocketSelector& copy); + + //////////////////////////////////////////////////////////// + /// \brief Overload of assignment operator + /// + /// \param right Instance to assign + /// + /// \return Reference to self + /// + //////////////////////////////////////////////////////////// + SocketSelector& operator=(const SocketSelector& right); + + //////////////////////////////////////////////////////////// + /// \brief Move constructor + /// + //////////////////////////////////////////////////////////// + SocketSelector(SocketSelector&&) noexcept; + + //////////////////////////////////////////////////////////// + /// \brief Move assignment + /// + //////////////////////////////////////////////////////////// + SocketSelector& operator=(SocketSelector&&) noexcept; + + //////////////////////////////////////////////////////////// + /// \brief Add a new socket to the selector + /// + /// This function keeps a weak reference to the socket, + /// so you have to make sure that the socket is not destroyed + /// while it is stored in the selector. + /// This function does nothing if the socket is not valid. + /// + /// \param socket Reference to the socket to add + /// + /// \see `remove`, `clear` + /// + //////////////////////////////////////////////////////////// + void add(Socket& socket); + + //////////////////////////////////////////////////////////// + /// \brief Remove a socket from the selector + /// + /// This function doesn't destroy the socket, it simply + /// removes the reference that the selector has to it. + /// + /// \param socket Reference to the socket to remove + /// + /// \see `add`, `clear` + /// + //////////////////////////////////////////////////////////// + void remove(Socket& socket); + + //////////////////////////////////////////////////////////// + /// \brief Remove all the sockets stored in the selector + /// + /// This function doesn't destroy any instance, it simply + /// removes all the references that the selector has to + /// external sockets. + /// + /// \see `add`, `remove` + /// + //////////////////////////////////////////////////////////// + void clear(); + + //////////////////////////////////////////////////////////// + /// \brief Wait until one or more sockets are ready to receive + /// + /// This function returns as soon as at least one socket has + /// some data available to be received. To know which sockets are + /// ready, use the `isReady` function. + /// If you use a timeout and no socket is ready before the timeout + /// is over, the function returns `false`. + /// + /// \param timeout Maximum time to wait, (use Time::Zero for infinity) + /// + /// \return `true` if there are sockets ready, `false` otherwise + /// + /// \see `isReady` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] bool wait(Time timeout = Time::Zero); + + //////////////////////////////////////////////////////////// + /// \brief Test a socket to know if it is ready to receive data + /// + /// This function must be used after a call to Wait, to know + /// which sockets are ready to receive data. If a socket is + /// ready, a call to receive will never block because we know + /// that there is data available to read. + /// Note that if this function returns `true` for a TcpListener, + /// this means that it is ready to accept a new connection. + /// + /// \param socket Socket to test + /// + /// \return `true` if the socket is ready to read, `false` otherwise + /// + /// \see `isReady` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] bool isReady(Socket& socket) const; + +private: + struct SocketSelectorImpl; + + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + std::unique_ptr m_impl; //!< Opaque pointer to the implementation (which requires OS-specific types) +}; + +} // namespace sf + + +//////////////////////////////////////////////////////////// +/// \class sf::SocketSelector +/// \ingroup network +/// +/// Socket selectors provide a way to wait until some data is +/// available on a set of sockets, instead of just one. This +/// is convenient when you have multiple sockets that may +/// possibly receive data, but you don't know which one will +/// be ready first. In particular, it avoids to use a thread +/// for each socket; with selectors, a single thread can handle +/// all the sockets. +/// +/// All types of sockets can be used in a selector: +/// \li `sf::TcpListener` +/// \li `sf::TcpSocket` +/// \li `sf::UdpSocket` +/// +/// A selector doesn't store its own copies of the sockets +/// (socket classes are not copyable anyway), it simply keeps +/// a reference to the original sockets that you pass to the +/// "add" function. Therefore, you can't use the selector as a +/// socket container, you must store them outside and make sure +/// that they are alive as long as they are used in the selector. +/// +/// Using a selector is simple: +/// \li populate the selector with all the sockets that you want to observe +/// \li make it wait until there is data available on any of the sockets +/// \li test each socket to find out which ones are ready +/// +/// Usage example: +/// \code +/// // Create a socket to listen to new connections +/// sf::TcpListener listener; +/// if (listener.listen(55001) != sf::Socket::Status::Done) +/// { +/// // Handle error... +/// } +/// +/// // Create a list to store the future clients +/// std::vector clients; +/// +/// // Create a selector +/// sf::SocketSelector selector; +/// +/// // Add the listener to the selector +/// selector.add(listener); +/// +/// // Endless loop that waits for new connections +/// while (running) +/// { +/// // Make the selector wait for data on any socket +/// if (selector.wait()) +/// { +/// // Test the listener +/// if (selector.isReady(listener)) +/// { +/// // The listener is ready: there is a pending connection +/// sf::TcpSocket client; +/// if (listener.accept(client) == sf::Socket::Status::Done) +/// { +/// // Add the new client to the selector so that we will +/// // be notified when they send something +/// selector.add(client); +/// +/// // Add the new client to the clients list +/// clients.push_back(std::move(client)); +/// } +/// else +/// { +/// // Handle error... +/// } +/// } +/// else +/// { +/// // The listener socket is not ready, test all other sockets (the clients) +/// for (sf::TcpSocket& client : clients) +/// { +/// if (selector.isReady(client)) +/// { +/// // The client has sent some data, we can receive it +/// sf::Packet packet; +/// if (client.receive(packet) == sf::Socket::Status::Done) +/// { +/// ... +/// } +/// } +/// } +/// } +/// } +/// } +/// \endcode +/// +/// \see `sf::Socket` +/// +//////////////////////////////////////////////////////////// diff --git a/sfml/include/SFML/Network/TcpListener.hpp b/sfml/include/SFML/Network/TcpListener.hpp new file mode 100644 index 0000000..54cd6e6 --- /dev/null +++ b/sfml/include/SFML/Network/TcpListener.hpp @@ -0,0 +1,166 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +#include +#include + + +namespace sf +{ +class TcpSocket; + +//////////////////////////////////////////////////////////// +/// \brief Socket that listens to new TCP connections +/// +//////////////////////////////////////////////////////////// +class SFML_NETWORK_API TcpListener : public Socket +{ +public: + //////////////////////////////////////////////////////////// + /// \brief Default constructor + /// + //////////////////////////////////////////////////////////// + TcpListener(); + + //////////////////////////////////////////////////////////// + /// \brief Get the port to which the socket is bound locally + /// + /// If the socket is not listening to a port, this function + /// returns 0. + /// + /// \return Port to which the socket is bound + /// + /// \see `listen` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] unsigned short getLocalPort() const; + + //////////////////////////////////////////////////////////// + /// \brief Start listening for incoming connection attempts + /// + /// This function makes the socket start listening on the + /// specified port, waiting for incoming connection attempts. + /// + /// If the socket is already listening on a port when this + /// function is called, it will stop listening on the old + /// port before starting to listen on the new port. + /// + /// When providing `sf::Socket::AnyPort` as port, the listener + /// will request an available port from the system. + /// The chosen port can be retrieved by calling `getLocalPort()`. + /// + /// \param port Port to listen on for incoming connection attempts + /// \param address Address of the interface to listen on + /// + /// \return Status code + /// + /// \see `accept`, `close` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Status listen(unsigned short port, IpAddress address = IpAddress::Any); + + //////////////////////////////////////////////////////////// + /// \brief Stop listening and close the socket + /// + /// This function gracefully stops the listener. If the + /// socket is not listening, this function has no effect. + /// + /// \see `listen` + /// + //////////////////////////////////////////////////////////// + void close(); + + //////////////////////////////////////////////////////////// + /// \brief Accept a new connection + /// + /// If the socket is in blocking mode, this function will + /// not return until a connection is actually received. + /// + /// \param socket Socket that will hold the new connection + /// + /// \return Status code + /// + /// \see `listen` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Status accept(TcpSocket& socket); +}; + + +} // namespace sf + + +//////////////////////////////////////////////////////////// +/// \class sf::TcpListener +/// \ingroup network +/// +/// A listener socket is a special type of socket that listens to +/// a given port and waits for connections on that port. +/// This is all it can do. +/// +/// When a new connection is received, you must call accept and +/// the listener returns a new instance of `sf::TcpSocket` that +/// is properly initialized and can be used to communicate with +/// the new client. +/// +/// Listener sockets are specific to the TCP protocol, +/// UDP sockets are connectionless and can therefore communicate +/// directly. As a consequence, a listener socket will always +/// return the new connections as `sf::TcpSocket` instances. +/// +/// A listener is automatically closed on destruction, like all +/// other types of socket. However if you want to stop listening +/// before the socket is destroyed, you can call its `close()` +/// function. +/// +/// Usage example: +/// \code +/// // Create a listener socket and make it wait for new +/// // connections on port 55001 +/// sf::TcpListener listener; +/// listener.listen(55001); +/// +/// // Endless loop that waits for new connections +/// while (running) +/// { +/// sf::TcpSocket client; +/// if (listener.accept(client) == sf::Socket::Done) +/// { +/// // A new client just connected! +/// std::cout << "New connection received from " << client.getRemoteAddress().value() << std::endl; +/// doSomethingWith(client); +/// } +/// } +/// \endcode +/// +/// \see `sf::TcpSocket`, `sf::Socket` +/// +//////////////////////////////////////////////////////////// diff --git a/sfml/include/SFML/Network/TcpSocket.hpp b/sfml/include/SFML/Network/TcpSocket.hpp new file mode 100644 index 0000000..05df0d6 --- /dev/null +++ b/sfml/include/SFML/Network/TcpSocket.hpp @@ -0,0 +1,317 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +#include + +#include + +#include +#include + +#include +#include + + +namespace sf +{ +class TcpListener; +class IpAddress; +class Packet; + +//////////////////////////////////////////////////////////// +/// \brief Specialized socket using the TCP protocol +/// +//////////////////////////////////////////////////////////// +class SFML_NETWORK_API TcpSocket : public Socket +{ +public: + //////////////////////////////////////////////////////////// + /// \brief Default constructor + /// + //////////////////////////////////////////////////////////// + TcpSocket(); + + //////////////////////////////////////////////////////////// + /// \brief Get the port to which the socket is bound locally + /// + /// If the socket is not connected, this function returns 0. + /// + /// \return Port to which the socket is bound + /// + /// \see `connect`, `getRemotePort` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] unsigned short getLocalPort() const; + + //////////////////////////////////////////////////////////// + /// \brief Get the address of the connected peer + /// + /// If the socket is not connected, this function returns + /// an unset optional. + /// + /// \return Address of the remote peer + /// + /// \see `getRemotePort` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] std::optional getRemoteAddress() const; + + //////////////////////////////////////////////////////////// + /// \brief Get the port of the connected peer to which + /// the socket is connected + /// + /// If the socket is not connected, this function returns 0. + /// + /// \return Remote port to which the socket is connected + /// + /// \see `getRemoteAddress` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] unsigned short getRemotePort() const; + + //////////////////////////////////////////////////////////// + /// \brief Connect the socket to a remote peer + /// + /// In blocking mode, this function may take a while, especially + /// if the remote peer is not reachable. The last parameter allows + /// you to stop trying to connect after a given timeout. + /// If the socket is already connected, the connection is + /// forcibly disconnected before attempting to connect again. + /// + /// \param remoteAddress Address of the remote peer + /// \param remotePort Port of the remote peer + /// \param timeout Optional maximum time to wait + /// + /// \return Status code + /// + /// \see `disconnect` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Status connect(IpAddress remoteAddress, unsigned short remotePort, Time timeout = Time::Zero); + + //////////////////////////////////////////////////////////// + /// \brief Disconnect the socket from its remote peer + /// + /// This function gracefully closes the connection. If the + /// socket is not connected, this function has no effect. + /// + /// \see `connect` + /// + //////////////////////////////////////////////////////////// + void disconnect(); + + //////////////////////////////////////////////////////////// + /// \brief Send raw data to the remote peer + /// + /// To be able to handle partial sends over non-blocking + /// sockets, use the `send(const void*, std::size_t, std::size_t&)` + /// overload instead. + /// This function will fail if the socket is not connected. + /// + /// \param data Pointer to the sequence of bytes to send + /// \param size Number of bytes to send + /// + /// \return Status code + /// + /// \see `receive` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Status send(const void* data, std::size_t size); + + //////////////////////////////////////////////////////////// + /// \brief Send raw data to the remote peer + /// + /// This function will fail if the socket is not connected. + /// + /// \param data Pointer to the sequence of bytes to send + /// \param size Number of bytes to send + /// \param sent The number of bytes sent will be written here + /// + /// \return Status code + /// + /// \see `receive` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Status send(const void* data, std::size_t size, std::size_t& sent); + + //////////////////////////////////////////////////////////// + /// \brief Receive raw data from the remote peer + /// + /// In blocking mode, this function will wait until some + /// bytes are actually received. + /// This function will fail if the socket is not connected. + /// + /// \param data Pointer to the array to fill with the received bytes + /// \param size Maximum number of bytes that can be received + /// \param received This variable is filled with the actual number of bytes received + /// + /// \return Status code + /// + /// \see `send` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Status receive(void* data, std::size_t size, std::size_t& received); + + //////////////////////////////////////////////////////////// + /// \brief Send a formatted packet of data to the remote peer + /// + /// In non-blocking mode, if this function returns `sf::Socket::Status::Partial`, + /// you \em must retry sending the same unmodified packet before sending + /// anything else in order to guarantee the packet arrives at the remote + /// peer uncorrupted. + /// This function will fail if the socket is not connected. + /// + /// \param packet Packet to send + /// + /// \return Status code + /// + /// \see `receive` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Status send(Packet& packet); + + //////////////////////////////////////////////////////////// + /// \brief Receive a formatted packet of data from the remote peer + /// + /// In blocking mode, this function will wait until the whole packet + /// has been received. + /// This function will fail if the socket is not connected. + /// + /// \param packet Packet to fill with the received data + /// + /// \return Status code + /// + /// \see `send` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Status receive(Packet& packet); + +private: + friend class TcpListener; + + //////////////////////////////////////////////////////////// + /// \brief Structure holding the data of a pending packet + /// + //////////////////////////////////////////////////////////// + struct PendingPacket + { + std::uint32_t size{}; //!< Data of packet size + std::size_t sizeReceived{}; //!< Number of size bytes received so far + std::vector data; //!< Data of the packet + }; + + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + PendingPacket m_pendingPacket; //!< Temporary data of the packet currently being received + std::vector m_blockToSendBuffer; //!< Buffer used to prepare data being sent from the socket +}; + +} // namespace sf + + +//////////////////////////////////////////////////////////// +/// \class sf::TcpSocket +/// \ingroup network +/// +/// TCP is a connected protocol, which means that a TCP +/// socket can only communicate with the host it is connected +/// to. It can't send or receive anything if it is not connected. +/// +/// The TCP protocol is reliable but adds a slight overhead. +/// It ensures that your data will always be received in order +/// and without errors (no data corrupted, lost or duplicated). +/// +/// When a socket is connected to a remote host, you can +/// retrieve information about this host with the +/// `getRemoteAddress` and `getRemotePort` functions. You can +/// also get the local port to which the socket is bound +/// (which is automatically chosen when the socket is connected), +/// with the getLocalPort function. +/// +/// Sending and receiving data can use either the low-level +/// or the high-level functions. The low-level functions +/// process a raw sequence of bytes, and cannot ensure that +/// one call to Send will exactly match one call to Receive +/// at the other end of the socket. +/// +/// The high-level interface uses packets (see `sf::Packet`), +/// which are easier to use and provide more safety regarding +/// the data that is exchanged. You can look at the `sf::Packet` +/// class to get more details about how they work. +/// +/// The socket is automatically disconnected when it is destroyed, +/// but if you want to explicitly close the connection while +/// the socket instance is still alive, you can call disconnect. +/// +/// Usage example: +/// \code +/// // ----- The client ----- +/// +/// // Create a socket and connect it to 192.168.1.50 on port 55001 +/// sf::TcpSocket socket; +/// socket.connect("192.168.1.50", 55001); +/// +/// // Send a message to the connected host +/// std::string message = "Hi, I am a client"; +/// socket.send(message.c_str(), message.size() + 1); +/// +/// // Receive an answer from the server +/// std::array buffer; +/// std::size_t received = 0; +/// socket.receive(buffer.data(), buffer.size(), received); +/// std::cout << "The server said: " << buffer.data() << std::endl; +/// +/// // ----- The server ----- +/// +/// // Create a listener to wait for incoming connections on port 55001 +/// sf::TcpListener listener; +/// listener.listen(55001); +/// +/// // Wait for a connection +/// sf::TcpSocket socket; +/// listener.accept(socket); +/// std::cout << "New client connected: " << socket.getRemoteAddress().value() << std::endl; +/// +/// // Receive a message from the client +/// std::array buffer; +/// std::size_t received = 0; +/// socket.receive(buffer.data(), buffer.size(), received); +/// std::cout << "The client said: " << buffer.data() << std::endl; +/// +/// // Send an answer +/// std::string message = "Welcome, client"; +/// socket.send(message.c_str(), message.size() + 1); +/// \endcode +/// +/// \see `sf::Socket`, `sf::UdpSocket`, `sf::Packet` +/// +//////////////////////////////////////////////////////////// diff --git a/sfml/include/SFML/Network/UdpSocket.hpp b/sfml/include/SFML/Network/UdpSocket.hpp new file mode 100644 index 0000000..0950561 --- /dev/null +++ b/sfml/include/SFML/Network/UdpSocket.hpp @@ -0,0 +1,293 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +#include +#include + +#include +#include + +#include + + +namespace sf +{ +class Packet; + +//////////////////////////////////////////////////////////// +/// \brief Specialized socket using the UDP protocol +/// +//////////////////////////////////////////////////////////// +class SFML_NETWORK_API UdpSocket : public Socket +{ +public: + //////////////////////////////////////////////////////////// + // Constants + //////////////////////////////////////////////////////////// + // NOLINTNEXTLINE(readability-identifier-naming) + static constexpr std::size_t MaxDatagramSize{65507}; //!< The maximum number of bytes that can be sent in a single UDP datagram + + //////////////////////////////////////////////////////////// + /// \brief Default constructor + /// + //////////////////////////////////////////////////////////// + UdpSocket(); + + //////////////////////////////////////////////////////////// + /// \brief Get the port to which the socket is bound locally + /// + /// If the socket is not bound to a port, this function + /// returns 0. + /// + /// \return Port to which the socket is bound + /// + /// \see `bind` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] unsigned short getLocalPort() const; + + //////////////////////////////////////////////////////////// + /// \brief Bind the socket to a specific port + /// + /// Binding the socket to a port is necessary for being + /// able to receive data on that port. + /// + /// When providing `sf::Socket::AnyPort` as port, the listener + /// will request an available port from the system. + /// The chosen port can be retrieved by calling `getLocalPort()`. + /// + /// Since the socket can only be bound to a single port at + /// any given moment, if it is already bound when this + /// function is called, it will be unbound from the previous + /// port before being bound to the new one. + /// + /// \param port Port to bind the socket to + /// \param address Address of the interface to bind to + /// + /// \return Status code + /// + /// \see `unbind`, `getLocalPort` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Status bind(unsigned short port, IpAddress address = IpAddress::Any); + + //////////////////////////////////////////////////////////// + /// \brief Unbind the socket from the local port to which it is bound + /// + /// The port that the socket was previously bound to is immediately + /// made available to the operating system after this function is called. + /// This means that a subsequent call to `bind()` will be able to re-bind + /// the port if no other process has done so in the mean time. + /// If the socket is not bound to a port, this function has no effect. + /// + /// \see `bind` + /// + //////////////////////////////////////////////////////////// + void unbind(); + + //////////////////////////////////////////////////////////// + /// \brief Send raw data to a remote peer + /// + /// Make sure that `size` is not greater than + /// `UdpSocket::MaxDatagramSize`, otherwise this function will + /// fail and no data will be sent. + /// + /// \param data Pointer to the sequence of bytes to send + /// \param size Number of bytes to send + /// \param remoteAddress Address of the receiver + /// \param remotePort Port of the receiver to send the data to + /// + /// \return Status code + /// + /// \see `receive` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Status send(const void* data, std::size_t size, IpAddress remoteAddress, unsigned short remotePort); + + //////////////////////////////////////////////////////////// + /// \brief Receive raw data from a remote peer + /// + /// In blocking mode, this function will wait until some + /// bytes are actually received. + /// Be careful to use a buffer which is large enough for + /// the data that you intend to receive, if it is too small + /// then an error will be returned and *all* the data will + /// be lost. + /// + /// \param data Pointer to the array to fill with the received bytes + /// \param size Maximum number of bytes that can be received + /// \param received This variable is filled with the actual number of bytes received + /// \param remoteAddress Address of the peer that sent the data + /// \param remotePort Port of the peer that sent the data + /// + /// \return Status code + /// + /// \see `send` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Status receive(void* data, + std::size_t size, + std::size_t& received, + std::optional& remoteAddress, + unsigned short& remotePort); + + //////////////////////////////////////////////////////////// + /// \brief Send a formatted packet of data to a remote peer + /// + /// Make sure that the packet size is not greater than + /// `UdpSocket::MaxDatagramSize`, otherwise this function will + /// fail and no data will be sent. + /// + /// \param packet Packet to send + /// \param remoteAddress Address of the receiver + /// \param remotePort Port of the receiver to send the data to + /// + /// \return Status code + /// + /// \see `receive` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Status send(Packet& packet, IpAddress remoteAddress, unsigned short remotePort); + + //////////////////////////////////////////////////////////// + /// \brief Receive a formatted packet of data from a remote peer + /// + /// In blocking mode, this function will wait until the whole packet + /// has been received. + /// + /// \param packet Packet to fill with the received data + /// \param remoteAddress Address of the peer that sent the data + /// \param remotePort Port of the peer that sent the data + /// + /// \return Status code + /// + /// \see `send` + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Status receive(Packet& packet, std::optional& remoteAddress, unsigned short& remotePort); + +private: + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + std::vector m_buffer{MaxDatagramSize}; //!< Temporary buffer holding the received data in Receive(Packet) +}; + +} // namespace sf + + +//////////////////////////////////////////////////////////// +/// \class sf::UdpSocket +/// \ingroup network +/// +/// A UDP socket is a connectionless socket. Instead of +/// connecting once to a remote host, like TCP sockets, +/// it can send to and receive from any host at any time. +/// +/// It is a datagram protocol: bounded blocks of data (datagrams) +/// are transferred over the network rather than a continuous +/// stream of data (TCP). Therefore, one call to send will always +/// match one call to receive (if the datagram is not lost), +/// with the same data that was sent. +/// +/// The UDP protocol is lightweight but unreliable. Unreliable +/// means that datagrams may be duplicated, be lost or +/// arrive reordered. However, if a datagram arrives, its +/// data is guaranteed to be valid. +/// +/// UDP is generally used for real-time communication +/// (audio or video streaming, real-time games, etc.) where +/// speed is crucial and lost data doesn't matter much. +/// +/// Sending and receiving data can use either the low-level +/// or the high-level functions. The low-level functions +/// process a raw sequence of bytes, whereas the high-level +/// interface uses packets (see `sf::Packet`), which are easier +/// to use and provide more safety regarding the data that is +/// exchanged. You can look at the `sf::Packet` class to get +/// more details about how they work. +/// +/// It is important to note that `UdpSocket` is unable to send +/// datagrams bigger than `MaxDatagramSize`. In this case, it +/// returns an error and doesn't send anything. This applies +/// to both raw data and packets. Indeed, even packets are +/// unable to split and recompose data, due to the unreliability +/// of the protocol (dropped, mixed or duplicated datagrams may +/// lead to a big mess when trying to recompose a packet). +/// +/// If the socket is bound to a port, it is automatically +/// unbound from it when the socket is destroyed. However, +/// you can unbind the socket explicitly with the Unbind +/// function if necessary, to stop receiving messages or +/// make the port available for other sockets. +/// +/// Usage example: +/// \code +/// // ----- The client ----- +/// +/// // Create a socket and bind it to the port 55001 +/// sf::UdpSocket socket; +/// socket.bind(55001); +/// +/// // Send a message to 192.168.1.50 on port 55002 +/// std::string message = "Hi, I am " + sf::IpAddress::getLocalAddress().toString(); +/// socket.send(message.c_str(), message.size() + 1, "192.168.1.50", 55002); +/// +/// // Receive an answer (most likely from 192.168.1.50, but could be anyone else) +/// std::array buffer; +/// std::size_t received = 0; +/// std::optional sender; +/// unsigned short port; +/// if (socket.receive(buffer.data(), buffer.size(), received, sender, port) == sf::Socket::Status::Done) +/// std::cout << sender->toString() << " said: " << buffer.data() << std::endl; +/// +/// // ----- The server ----- +/// +/// // Create a socket and bind it to the port 55002 +/// sf::UdpSocket socket; +/// socket.bind(55002); +/// +/// // Receive a message from anyone +/// std::array buffer; +/// std::size_t received = 0; +/// std::optional sender; +/// unsigned short port; +/// if (socket.receive(buffer.data(), buffer.size(), received, sender, port) == sf::Socket::Status::Done) +/// std::cout << sender->toString() << " said: " << buffer.data() << std::endl; +/// +/// // Send an answer +/// std::string message = "Welcome " + sender.toString(); +/// socket.send(message.c_str(), message.size() + 1, sender, port); +/// \endcode +/// +/// \see `sf::Socket`, `sf::TcpSocket`, `sf::Packet` +/// +//////////////////////////////////////////////////////////// diff --git a/sfml/src/SFML/Network/Ftp.cpp b/sfml/src/SFML/Network/Ftp.cpp new file mode 100644 index 0000000..cdb0bc1 --- /dev/null +++ b/sfml/src/SFML/Network/Ftp.cpp @@ -0,0 +1,643 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +namespace sf +{ +//////////////////////////////////////////////////////////// +class Ftp::DataChannel +{ +public: + //////////////////////////////////////////////////////////// + explicit DataChannel(Ftp& owner); + + //////////////////////////////////////////////////////////// + /// \brief Deleted copy constructor + /// + //////////////////////////////////////////////////////////// + DataChannel(const DataChannel&) = delete; + + //////////////////////////////////////////////////////////// + /// \brief Deleted copy assignment + /// + //////////////////////////////////////////////////////////// + DataChannel& operator=(const DataChannel&) = delete; + + //////////////////////////////////////////////////////////// + Ftp::Response open(Ftp::TransferMode mode); + + //////////////////////////////////////////////////////////// + void send(std::istream& stream); + + //////////////////////////////////////////////////////////// + void receive(std::ostream& stream); + +private: + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + Ftp& m_ftp; //!< Reference to the owner Ftp instance + TcpSocket m_dataSocket; //!< Socket used for data transfers +}; + + +//////////////////////////////////////////////////////////// +Ftp::Response::Response(Status code, std::string message) : m_status(code), m_message(std::move(message)) +{ +} + + +//////////////////////////////////////////////////////////// +bool Ftp::Response::isOk() const +{ + return static_cast(m_status) < 400; +} + + +//////////////////////////////////////////////////////////// +Ftp::Response::Status Ftp::Response::getStatus() const +{ + return m_status; +} + + +//////////////////////////////////////////////////////////// +const std::string& Ftp::Response::getMessage() const +{ + return m_message; +} + + +//////////////////////////////////////////////////////////// +Ftp::DirectoryResponse::DirectoryResponse(const Ftp::Response& response) : Ftp::Response(response) +{ + if (isOk()) + { + // Extract the directory from the server response + const std::string::size_type begin = getMessage().find('"', 0); + const std::string::size_type end = getMessage().find('"', begin + 1); + m_directory = getMessage().substr(begin + 1, end - begin - 1); + } +} + + +//////////////////////////////////////////////////////////// +const std::filesystem::path& Ftp::DirectoryResponse::getDirectory() const +{ + return m_directory; +} + + +//////////////////////////////////////////////////////////// +Ftp::ListingResponse::ListingResponse(const Ftp::Response& response, const std::string& data) : Ftp::Response(response) +{ + if (isOk()) + { + // Fill the array of strings + std::string::size_type lastPos = 0; + for (std::string::size_type pos = data.find("\r\n"); pos != std::string::npos; pos = data.find("\r\n", lastPos)) + { + m_listing.push_back(data.substr(lastPos, pos - lastPos)); + lastPos = pos + 2; + } + } +} + + +//////////////////////////////////////////////////////////// +const std::vector& Ftp::ListingResponse::getListing() const +{ + return m_listing; +} + + +//////////////////////////////////////////////////////////// +Ftp::~Ftp() +{ + (void)disconnect(); +} + + +//////////////////////////////////////////////////////////// +Ftp::Response Ftp::connect(IpAddress server, unsigned short port, Time timeout) +{ + // Connect to the server + if (m_commandSocket.connect(server, port, timeout) != Socket::Status::Done) + return Response(Response::Status::ConnectionFailed); + + // Get the response to the connection + return getResponse(); +} + + +//////////////////////////////////////////////////////////// +Ftp::Response Ftp::login() +{ + return login("anonymous", "user@sfml-dev.org"); +} + + +//////////////////////////////////////////////////////////// +Ftp::Response Ftp::login(const std::string& name, const std::string& password) +{ + Response response = sendCommand("USER", name); + if (response.isOk()) + response = sendCommand("PASS", password); + + return response; +} + + +//////////////////////////////////////////////////////////// +Ftp::Response Ftp::disconnect() +{ + // Send the exit command + Response response = sendCommand("QUIT"); + if (response.isOk()) + m_commandSocket.disconnect(); + + return response; +} + + +//////////////////////////////////////////////////////////// +Ftp::Response Ftp::keepAlive() +{ + return sendCommand("NOOP"); +} + + +//////////////////////////////////////////////////////////// +Ftp::DirectoryResponse Ftp::getWorkingDirectory() +{ + return {sendCommand("PWD")}; +} + + +//////////////////////////////////////////////////////////// +Ftp::ListingResponse Ftp::getDirectoryListing(const std::string& directory) +{ + // Open a data channel on default port (20) using ASCII transfer mode + std::ostringstream directoryData; + DataChannel data(*this); + Response response = data.open(TransferMode::Ascii); + if (response.isOk()) + { + // Tell the server to send us the listing + response = sendCommand("NLST", directory); + if (response.isOk()) + { + // Receive the listing + data.receive(directoryData); + + // Get the response from the server + response = getResponse(); + } + } + + return {response, directoryData.str()}; +} + + +//////////////////////////////////////////////////////////// +Ftp::Response Ftp::changeDirectory(const std::string& directory) +{ + return sendCommand("CWD", directory); +} + + +//////////////////////////////////////////////////////////// +Ftp::Response Ftp::parentDirectory() +{ + return sendCommand("CDUP"); +} + + +//////////////////////////////////////////////////////////// +Ftp::Response Ftp::createDirectory(const std::string& name) +{ + return sendCommand("MKD", name); +} + + +//////////////////////////////////////////////////////////// +Ftp::Response Ftp::deleteDirectory(const std::string& name) +{ + return sendCommand("RMD", name); +} + + +//////////////////////////////////////////////////////////// +Ftp::Response Ftp::renameFile(const std::filesystem::path& file, const std::filesystem::path& newName) +{ + Response response = sendCommand("RNFR", file.string()); + if (response.isOk()) + response = sendCommand("RNTO", newName.string()); + + return response; +} + + +//////////////////////////////////////////////////////////// +Ftp::Response Ftp::deleteFile(const std::filesystem::path& name) +{ + return sendCommand("DELE", name.string()); +} + + +//////////////////////////////////////////////////////////// +Ftp::Response Ftp::download(const std::filesystem::path& remoteFile, const std::filesystem::path& localPath, TransferMode mode) +{ + // Open a data channel using the given transfer mode + DataChannel data(*this); + Response response = data.open(mode); + if (response.isOk()) + { + // Tell the server to start the transfer + response = sendCommand("RETR", remoteFile.string()); + if (response.isOk()) + { + // Create the file and truncate it if necessary + const std::filesystem::path filepath = localPath / remoteFile.filename(); + std::ofstream file(filepath, std::ios_base::binary | std::ios_base::trunc); + if (!file) + return Response(Response::Status::InvalidFile); + + // Receive the file data + data.receive(file); + + // Close the file + file.close(); + + // Get the response from the server + response = getResponse(); + + // If the download was unsuccessful, delete the partial file + if (!response.isOk()) + std::filesystem::remove(filepath); + } + } + + return response; +} + + +//////////////////////////////////////////////////////////// +Ftp::Response Ftp::upload(const std::filesystem::path& localFile, + const std::filesystem::path& remotePath, + TransferMode mode, + bool append) +{ + // Get the contents of the file to send + std::ifstream file(localFile, std::ios_base::binary); + if (!file) + return Response(Response::Status::InvalidFile); + + // Open a data channel using the given transfer mode + DataChannel data(*this); + Response response = data.open(mode); + if (response.isOk()) + { + // Tell the server to start the transfer + response = sendCommand(append ? "APPE" : "STOR", (remotePath / localFile.filename()).string()); + if (response.isOk()) + { + // Send the file data + data.send(file); + + // Get the response from the server + response = getResponse(); + } + } + + return response; +} + + +//////////////////////////////////////////////////////////// +Ftp::Response Ftp::sendCommand(const std::string& command, const std::string& parameter) +{ + // Build the command string + const std::string commandStr = parameter.empty() ? command + "\r\n" : command + " " + parameter + "\r\n"; + + // Send it to the server + if (m_commandSocket.send(commandStr.c_str(), commandStr.length()) != Socket::Status::Done) + return Response(Response::Status::ConnectionClosed); + + // Get the response + return getResponse(); +} + + +//////////////////////////////////////////////////////////// +Ftp::Response Ftp::getResponse() +{ + // We'll use a variable to keep track of the last valid code. + // It is useful in case of multi-lines responses, because the end of such a response + // will start by the same code + unsigned int lastCode = 0; + bool isInsideMultiline = false; + std::string message; + + for (;;) + { + // Receive the response from the server + std::array buffer{}; + std::size_t length = 0; + + if (m_receiveBuffer.empty()) + { + if (m_commandSocket.receive(buffer.data(), buffer.size(), length) != Socket::Status::Done) + return Response(Response::Status::ConnectionClosed); + } + else + { + std::copy(m_receiveBuffer.begin(), m_receiveBuffer.end(), buffer.data()); + length = m_receiveBuffer.size(); + m_receiveBuffer.clear(); + } + + // There can be several lines inside the received buffer, extract them all + std::istringstream in(std::string(buffer.data(), length), std::ios_base::binary); + while (in) + { + // Try to extract the code + unsigned int code = 0; + if (in >> code) + { + // Extract the separator + char separator = 0; + in.get(separator); + + // The '-' character means a multiline response + if ((separator == '-') && !isInsideMultiline) + { + // Set the multiline flag + isInsideMultiline = true; + + // Keep track of the code + if (lastCode == 0) + lastCode = code; + + // Extract the line + std::getline(in, message); + + // Remove the ending '\r' (all lines are terminated by "\r\n") + message.erase(message.length() - 1); + message = separator + message + "\n"; + } + else + { + // We must make sure that the code is the same, otherwise it means + // we haven't reached the end of the multiline response + if ((separator != '-') && ((code == lastCode) || (lastCode == 0))) + { + // Extract the line + std::string line; + std::getline(in, line); + + // Remove the ending '\r' (all lines are terminated by "\r\n") + line.erase(line.length() - 1); + + // Append it to the message + if (code == lastCode) + { + std::ostringstream out; + out << code << separator << line; + message += out.str(); + } + else + { + message = separator + line; + } + + // Save the remaining data for the next time getResponse() is called + m_receiveBuffer.assign(buffer.data() + static_cast(in.tellg()), + length - static_cast(in.tellg())); + + // Return the response code and message + return Response(static_cast(code), message); + } + + // The line we just read was actually not a response, + // only a new part of the current multiline response + + // Extract the line + std::string line; + std::getline(in, line); + + if (!line.empty()) + { + // Remove the ending '\r' (all lines are terminated by "\r\n") + line.erase(line.length() - 1); + + // Append it to the current message + std::ostringstream out; + out << code << separator << line << '\n'; + message += out.str(); + } + } + } + else if (lastCode != 0) + { + // It seems we are in the middle of a multiline response + + // Clear the error bits of the stream + in.clear(); + + // Extract the line + std::string line; + std::getline(in, line); + + if (!line.empty()) + { + // Remove the ending '\r' (all lines are terminated by "\r\n") + line.erase(line.length() - 1); + + // Append it to the current message + message += line + "\n"; + } + } + else + { + // Error: cannot extract the code, and we are not in a multiline response + return Response(Response::Status::InvalidResponse); + } + } + } + + // We never reach there +} + + +//////////////////////////////////////////////////////////// +Ftp::DataChannel::DataChannel(Ftp& owner) : m_ftp(owner) +{ +} + + +//////////////////////////////////////////////////////////// +Ftp::Response Ftp::DataChannel::open(Ftp::TransferMode mode) +{ + // Open a data connection in active mode (we connect to the server) + Ftp::Response response = m_ftp.sendCommand("PASV"); + if (response.isOk()) + { + // Extract the connection address and port from the response + const std::string::size_type begin = response.getMessage().find_first_of("0123456789"); + if (begin != std::string::npos) + { + std::array data{}; + std::string str = response.getMessage().substr(begin); + std::size_t index = 0; + for (unsigned char& datum : data) + { + // Extract the current number + while (std::isdigit(str[index])) + { + datum = static_cast( + static_cast(datum * 10) + static_cast(str[index] - '0')); + ++index; + } + + // Skip separator + ++index; + } + + // Reconstruct connection port and address + const auto port = static_cast(data[4] * 256 + data[5]); + const IpAddress address(data[0], data[1], data[2], data[3]); + + // Connect the data channel to the server + if (m_dataSocket.connect(address, port) == Socket::Status::Done) + { + // Translate the transfer mode to the corresponding FTP parameter + std::string modeStr; + switch (mode) + { + case Ftp::TransferMode::Binary: + modeStr = "I"; + break; + case Ftp::TransferMode::Ascii: + modeStr = "A"; + break; + case Ftp::TransferMode::Ebcdic: + modeStr = "E"; + break; + } + + // Set the transfer mode + response = m_ftp.sendCommand("TYPE", modeStr); + } + else + { + // Failed to connect to the server + response = Ftp::Response(Ftp::Response::Status::ConnectionFailed); + } + } + } + + return response; +} + + +//////////////////////////////////////////////////////////// +void Ftp::DataChannel::receive(std::ostream& stream) +{ + // Receive data + std::array buffer{}; + std::size_t received = 0; + while (m_dataSocket.receive(buffer.data(), buffer.size(), received) == Socket::Status::Done) + { + stream.write(buffer.data(), static_cast(received)); + + if (!stream.good()) + { + err() << "FTP Error: Writing to the file has failed" << std::endl; + break; + } + } + + // Close the data socket + m_dataSocket.disconnect(); +} + + +//////////////////////////////////////////////////////////// +void Ftp::DataChannel::send(std::istream& stream) +{ + // Send data + std::array buffer{}; + std::size_t count = 0; + + for (;;) + { + // read some data from the stream + stream.read(buffer.data(), buffer.size()); + + if (!stream.good() && !stream.eof()) + { + err() << "FTP Error: Reading from the file has failed" << std::endl; + break; + } + + count = static_cast(stream.gcount()); + + if (count > 0) + { + // we could read more data from the stream: send them + if (m_dataSocket.send(buffer.data(), count) != Socket::Status::Done) + break; + } + else + { + // no more data: exit the loop + break; + } + } + + // Close the data socket + m_dataSocket.disconnect(); +} + +} // namespace sf diff --git a/sfml/src/SFML/Network/Http.cpp b/sfml/src/SFML/Network/Http.cpp new file mode 100644 index 0000000..b0e132c --- /dev/null +++ b/sfml/src/SFML/Network/Http.cpp @@ -0,0 +1,399 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +namespace sf +{ +//////////////////////////////////////////////////////////// +Http::Request::Request(const std::string& uri, Method method, const std::string& body) : m_method(method) +{ + setUri(uri); + setBody(body); +} + + +//////////////////////////////////////////////////////////// +void Http::Request::setField(const std::string& field, const std::string& value) +{ + m_fields[toLower(field)] = value; +} + + +//////////////////////////////////////////////////////////// +void Http::Request::setMethod(Http::Request::Method method) +{ + m_method = method; +} + + +//////////////////////////////////////////////////////////// +void Http::Request::setUri(const std::string& uri) +{ + m_uri = uri; + + // Make sure it starts with a '/' + if (m_uri.empty() || (m_uri[0] != '/')) + m_uri.insert(m_uri.begin(), '/'); +} + + +//////////////////////////////////////////////////////////// +void Http::Request::setHttpVersion(unsigned int major, unsigned int minor) +{ + m_majorVersion = major; + m_minorVersion = minor; +} + + +//////////////////////////////////////////////////////////// +void Http::Request::setBody(const std::string& body) +{ + m_body = body; +} + + +//////////////////////////////////////////////////////////// +std::string Http::Request::prepare() const +{ + std::ostringstream out; + + // Convert the method to its string representation + std::string method; + switch (m_method) + { + case Method::Get: + method = "GET"; + break; + case Method::Post: + method = "POST"; + break; + case Method::Head: + method = "HEAD"; + break; + case Method::Put: + method = "PUT"; + break; + case Method::Delete: + method = "DELETE"; + break; + } + + // Write the first line containing the request type + out << method << " " << m_uri << " "; + out << "HTTP/" << m_majorVersion << "." << m_minorVersion << "\r\n"; + + // Write fields + for (const auto& [fieldKey, fieldValue] : m_fields) + { + out << fieldKey << ": " << fieldValue << "\r\n"; + } + + // Use an extra \r\n to separate the header from the body + out << "\r\n"; + + // Add the body + out << m_body; + + return out.str(); +} + + +//////////////////////////////////////////////////////////// +bool Http::Request::hasField(const std::string& field) const +{ + return m_fields.find(toLower(field)) != m_fields.end(); +} + + +//////////////////////////////////////////////////////////// +const std::string& Http::Response::getField(const std::string& field) const +{ + if (const auto it = m_fields.find(toLower(field)); it != m_fields.end()) + { + return it->second; + } + + static const std::string empty; + return empty; +} + + +//////////////////////////////////////////////////////////// +Http::Response::Status Http::Response::getStatus() const +{ + return m_status; +} + + +//////////////////////////////////////////////////////////// +unsigned int Http::Response::getMajorHttpVersion() const +{ + return m_majorVersion; +} + + +//////////////////////////////////////////////////////////// +unsigned int Http::Response::getMinorHttpVersion() const +{ + return m_minorVersion; +} + + +//////////////////////////////////////////////////////////// +const std::string& Http::Response::getBody() const +{ + return m_body; +} + + +//////////////////////////////////////////////////////////// +void Http::Response::parse(const std::string& data) +{ + std::istringstream in(data); + + // Extract the HTTP version from the first line + std::string version; + if (in >> version) + { + if ((version.size() >= 8) && (version[6] == '.') && (toLower(version.substr(0, 5)) == "http/") && + std::isdigit(version[5]) && std::isdigit(version[7])) + { + m_majorVersion = static_cast(version[5] - '0'); + m_minorVersion = static_cast(version[7] - '0'); + } + else + { + // Invalid HTTP version + m_status = Status::InvalidResponse; + return; + } + } + + // Extract the status code from the first line + int status = 0; + if (in >> status) + { + m_status = static_cast(status); + } + else + { + // Invalid status code + m_status = Status::InvalidResponse; + return; + } + + // Ignore the end of the first line + in.ignore(std::numeric_limits::max(), '\n'); + + // Parse the other lines, which contain fields, one by one + parseFields(in); + + m_body.clear(); + + // Determine whether the transfer is chunked + if (toLower(getField("transfer-encoding")) != "chunked") + { + // Not chunked - just read everything at once + std::copy(std::istreambuf_iterator(in), std::istreambuf_iterator(), std::back_inserter(m_body)); + } + else + { + // Chunked - have to read chunk by chunk + std::size_t length = 0; + + // Read all chunks, identified by a chunk-size not being 0 + while (in >> std::hex >> length) + { + // Drop the rest of the line (chunk-extension) + in.ignore(std::numeric_limits::max(), '\n'); + + // Copy the actual content data + std::istreambuf_iterator it(in); + const std::istreambuf_iterator itEnd; + for (std::size_t i = 0; ((i < length) && (it != itEnd)); ++i) + { + m_body.push_back(*it); + ++it; // Iterate in separate expression to work around false positive -Wnull-dereference warning in GCC 12.1.0 + } + } + + // Drop the rest of the line (chunk-extension) + in.ignore(std::numeric_limits::max(), '\n'); + + // Read all trailers (if present) + parseFields(in); + } +} + + +//////////////////////////////////////////////////////////// +void Http::Response::parseFields(std::istream& in) +{ + std::string line; + while (std::getline(in, line) && (line.size() > 2)) + { + const std::string::size_type pos = line.find(": "); + if (pos != std::string::npos) + { + // Extract the field name and its value + const std::string field = line.substr(0, pos); + std::string value = line.substr(pos + 2); + + // Remove any trailing \r + if (!value.empty() && (*value.rbegin() == '\r')) + value.erase(value.size() - 1); + + // Add the field + m_fields[toLower(field)] = value; + } + } +} + + +//////////////////////////////////////////////////////////// +Http::Http(const std::string& host, unsigned short port) +{ + setHost(host, port); +} + + +//////////////////////////////////////////////////////////// +void Http::setHost(const std::string& host, unsigned short port) +{ + // Check the protocol + if (toLower(host.substr(0, 7)) == "http://") + { + // HTTP protocol + m_hostName = host.substr(7); + m_port = (port != 0 ? port : 80); + } + else if (toLower(host.substr(0, 8)) == "https://") + { + // HTTPS protocol -- unsupported (requires encryption and certificates and stuff...) + err() << "HTTPS protocol is not supported by sf::Http" << std::endl; + m_hostName.clear(); + m_port = 0; + } + else + { + // Undefined protocol - use HTTP + m_hostName = host; + m_port = (port != 0 ? port : 80); + } + + // Remove any trailing '/' from the host name + if (!m_hostName.empty() && (*m_hostName.rbegin() == '/')) + m_hostName.erase(m_hostName.size() - 1); + + m_host = IpAddress::resolve(m_hostName); +} + + +//////////////////////////////////////////////////////////// +Http::Response Http::sendRequest(const Http::Request& request, Time timeout) +{ + // First make sure that the request is valid -- add missing mandatory fields + Request toSend(request); + if (!toSend.hasField("From")) + { + toSend.setField("From", "user@sfml-dev.org"); + } + if (!toSend.hasField("User-Agent")) + { + toSend.setField("User-Agent", "libsfml-network/3.x"); + } + if (!toSend.hasField("Host")) + { + toSend.setField("Host", m_hostName); + } + if (!toSend.hasField("Content-Length")) + { + std::ostringstream out; + out << toSend.m_body.size(); + toSend.setField("Content-Length", out.str()); + } + if ((toSend.m_method == Request::Method::Post) && !toSend.hasField("Content-Type")) + { + toSend.setField("Content-Type", "application/x-www-form-urlencoded"); + } + if ((toSend.m_majorVersion * 10 + toSend.m_minorVersion >= 11) && !toSend.hasField("Connection")) + { + toSend.setField("Connection", "close"); + } + + // Prepare the response + Response received; + + // Connect the socket to the host + if (m_connection.connect(m_host.value(), m_port, timeout) == Socket::Status::Done) + { + // Convert the request to string and send it through the connected socket + const std::string requestStr = toSend.prepare(); + + if (!requestStr.empty()) + { + // Send it through the socket + if (m_connection.send(requestStr.c_str(), requestStr.size()) == Socket::Status::Done) + { + // Wait for the server's response + std::string receivedStr; + std::size_t size = 0; + std::array buffer{}; + while (m_connection.receive(buffer.data(), buffer.size(), size) == Socket::Status::Done) + { + receivedStr.append(buffer.data(), buffer.data() + size); + } + + // Build the Response object from the received data + received.parse(receivedStr); + } + } + + // Close the connection + m_connection.disconnect(); + } + + return received; +} + +} // namespace sf diff --git a/sfml/src/SFML/Network/IpAddress.cpp b/sfml/src/SFML/Network/IpAddress.cpp new file mode 100644 index 0000000..ed4664b --- /dev/null +++ b/sfml/src/SFML/Network/IpAddress.cpp @@ -0,0 +1,253 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include + +#include + +#include +#include + +#include + + +namespace sf +{ +//////////////////////////////////////////////////////////// +const IpAddress IpAddress::Any(0, 0, 0, 0); +const IpAddress IpAddress::LocalHost(127, 0, 0, 1); +const IpAddress IpAddress::Broadcast(255, 255, 255, 255); + + +//////////////////////////////////////////////////////////// +std::optional IpAddress::resolve(std::string_view address) +{ + using namespace std::string_view_literals; + + if (address.empty()) + { + // Not generating en error message here as resolution failure is a valid outcome. + return std::nullopt; + } + + if (address == "255.255.255.255"sv) + { + // The broadcast address needs to be handled explicitly, + // because it is also the value returned by inet_addr on error + return Broadcast; + } + + if (address == "0.0.0.0"sv) + return Any; + + // Try to convert the address as a byte representation ("xxx.xxx.xxx.xxx") + if (const std::uint32_t ip = inet_addr(address.data()); ip != INADDR_NONE) + return IpAddress(ntohl(ip)); + + // Not a valid address, try to convert it as a host name + addrinfo hints{}; // Zero-initialize + hints.ai_family = AF_INET; + + addrinfo* result = nullptr; + if (getaddrinfo(address.data(), nullptr, &hints, &result) == 0 && result != nullptr) + { + sockaddr_in sin{}; + std::memcpy(&sin, result->ai_addr, sizeof(*result->ai_addr)); + + const std::uint32_t ip = sin.sin_addr.s_addr; + freeaddrinfo(result); + + return IpAddress(ntohl(ip)); + } + + // Not generating en error message here as resolution failure is a valid outcome. + return std::nullopt; +} + + +//////////////////////////////////////////////////////////// +IpAddress::IpAddress(std::uint8_t byte0, std::uint8_t byte1, std::uint8_t byte2, std::uint8_t byte3) : +m_address(static_cast((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3)) +{ +} + + +//////////////////////////////////////////////////////////// +IpAddress::IpAddress(std::uint32_t address) : m_address(address) +{ +} + + +//////////////////////////////////////////////////////////// +std::string IpAddress::toString() const +{ + in_addr address{}; + address.s_addr = htonl(m_address); + + return inet_ntoa(address); +} + + +//////////////////////////////////////////////////////////// +std::uint32_t IpAddress::toInteger() const +{ + return m_address; +} + + +//////////////////////////////////////////////////////////// +std::optional IpAddress::getLocalAddress() +{ + // The method here is to connect a UDP socket to a public ip, + // and get the local socket address with the getsockname function. + // UDP connection will not send anything to the network, so this function won't cause any overhead. + + // Create the socket + const SocketHandle sock = socket(PF_INET, SOCK_DGRAM, 0); + if (sock == priv::SocketImpl::invalidSocket()) + { + err() << "Failed to retrieve local address (invalid socket)" << std::endl; + return std::nullopt; + } + + // Connect the socket to a public ip (here 1.1.1.1) on any + // port. This will give the local address of the network interface + // used for default routing which is usually what we want. + sockaddr_in address = priv::SocketImpl::createAddress(0x01010101, 9); + if (connect(sock, reinterpret_cast(&address), sizeof(address)) == -1) + { + priv::SocketImpl::close(sock); + + err() << "Failed to retrieve local address (socket connection failure)" << std::endl; + return std::nullopt; + } + + // Get the local address of the socket connection + priv::SocketImpl::AddrLength size = sizeof(address); + if (getsockname(sock, reinterpret_cast(&address), &size) == -1) + { + priv::SocketImpl::close(sock); + + err() << "Failed to retrieve local address (socket local address retrieval failure)" << std::endl; + return std::nullopt; + } + + // Close the socket + priv::SocketImpl::close(sock); + + // Finally build the IP address + return IpAddress(ntohl(address.sin_addr.s_addr)); +} + + +//////////////////////////////////////////////////////////// +std::optional IpAddress::getPublicAddress(Time timeout) +{ + // The trick here is more complicated, because the only way + // to get our public IP address is to get it from a distant computer. + // Here we get the web page from http://www.sfml-dev.org/ip-provider.php + // and parse the result to extract our IP address + // (not very hard: the web page contains only our IP address). + + Http server("www.sfml-dev.org"); + const Http::Request request("/ip-provider.php", Http::Request::Method::Get); + const Http::Response page = server.sendRequest(request, timeout); + + const Http::Response::Status status = page.getStatus(); + + if (status == Http::Response::Status::Ok) + return IpAddress::resolve(page.getBody()); + + err() << "Failed to retrieve public address from external IP resolution server (HTTP response status " + << static_cast(status) << ")" << std::endl; + + return std::nullopt; +} + + +//////////////////////////////////////////////////////////// +bool operator==(IpAddress left, IpAddress right) +{ + return !(left < right) && !(right < left); +} + + +//////////////////////////////////////////////////////////// +bool operator!=(IpAddress left, IpAddress right) +{ + return !(left == right); +} + + +//////////////////////////////////////////////////////////// +bool operator<(IpAddress left, IpAddress right) +{ + return left.m_address < right.m_address; +} + + +//////////////////////////////////////////////////////////// +bool operator>(IpAddress left, IpAddress right) +{ + return right < left; +} + + +//////////////////////////////////////////////////////////// +bool operator<=(IpAddress left, IpAddress right) +{ + return !(right < left); +} + + +//////////////////////////////////////////////////////////// +bool operator>=(IpAddress left, IpAddress right) +{ + return !(left < right); +} + + +//////////////////////////////////////////////////////////// +std::istream& operator>>(std::istream& stream, std::optional& address) +{ + std::string str; + stream >> str; + address = IpAddress::resolve(str); + + return stream; +} + + +//////////////////////////////////////////////////////////// +std::ostream& operator<<(std::ostream& stream, IpAddress address) +{ + return stream << address.toString(); +} + +} // namespace sf diff --git a/sfml/src/SFML/Network/Packet.cpp b/sfml/src/SFML/Network/Packet.cpp new file mode 100644 index 0000000..0270395 --- /dev/null +++ b/sfml/src/SFML/Network/Packet.cpp @@ -0,0 +1,596 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include + +#include +#include + +#include + +#include +#include +#include + + +namespace sf +{ +//////////////////////////////////////////////////////////// +void Packet::append(const void* data, std::size_t sizeInBytes) +{ + if (data && (sizeInBytes > 0)) + { + const auto* begin = reinterpret_cast(data); + const auto* end = begin + sizeInBytes; + m_data.insert(m_data.end(), begin, end); + } +} + + +//////////////////////////////////////////////////////////// +std::size_t Packet::getReadPosition() const +{ + return m_readPos; +} + + +//////////////////////////////////////////////////////////// +void Packet::clear() +{ + m_data.clear(); + m_readPos = 0; + m_isValid = true; +} + + +//////////////////////////////////////////////////////////// +const void* Packet::getData() const +{ + return !m_data.empty() ? m_data.data() : nullptr; +} + + +//////////////////////////////////////////////////////////// +std::size_t Packet::getDataSize() const +{ + return m_data.size(); +} + + +//////////////////////////////////////////////////////////// +bool Packet::endOfPacket() const +{ + return m_readPos >= m_data.size(); +} + + +//////////////////////////////////////////////////////////// +Packet::operator bool() const +{ + return m_isValid; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator>>(bool& data) +{ + std::uint8_t value = 0; + if (*this >> value) + data = (value != 0); + + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator>>(std::int8_t& data) +{ + if (checkSize(sizeof(data))) + { + std::memcpy(&data, &m_data[m_readPos], sizeof(data)); + m_readPos += sizeof(data); + } + + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator>>(std::uint8_t& data) +{ + if (checkSize(sizeof(data))) + { + std::memcpy(&data, &m_data[m_readPos], sizeof(data)); + m_readPos += sizeof(data); + } + + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator>>(std::int16_t& data) +{ + if (checkSize(sizeof(data))) + { + std::memcpy(&data, &m_data[m_readPos], sizeof(data)); + data = static_cast(ntohs(static_cast(data))); + m_readPos += sizeof(data); + } + + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator>>(std::uint16_t& data) +{ + if (checkSize(sizeof(data))) + { + std::memcpy(&data, &m_data[m_readPos], sizeof(data)); + data = ntohs(data); + m_readPos += sizeof(data); + } + + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator>>(std::int32_t& data) +{ + if (checkSize(sizeof(data))) + { + std::memcpy(&data, &m_data[m_readPos], sizeof(data)); + data = static_cast(ntohl(static_cast(data))); + m_readPos += sizeof(data); + } + + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator>>(std::uint32_t& data) +{ + if (checkSize(sizeof(data))) + { + std::memcpy(&data, &m_data[m_readPos], sizeof(data)); + data = ntohl(data); + m_readPos += sizeof(data); + } + + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator>>(std::int64_t& data) +{ + if (checkSize(sizeof(data))) + { + // Since ntohll is not available everywhere, we have to convert + // to network byte order (big endian) manually + std::array bytes{}; + std::memcpy(bytes.data(), &m_data[m_readPos], bytes.size()); + + data = toInteger(bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]); + + m_readPos += sizeof(data); + } + + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator>>(std::uint64_t& data) +{ + if (checkSize(sizeof(data))) + { + // Since ntohll is not available everywhere, we have to convert + // to network byte order (big endian) manually + std::array bytes{}; + std::memcpy(bytes.data(), &m_data[m_readPos], sizeof(data)); + + data = toInteger(bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]); + + m_readPos += sizeof(data); + } + + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator>>(float& data) +{ + if (checkSize(sizeof(data))) + { + std::memcpy(&data, &m_data[m_readPos], sizeof(data)); + m_readPos += sizeof(data); + } + + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator>>(double& data) +{ + if (checkSize(sizeof(data))) + { + std::memcpy(&data, &m_data[m_readPos], sizeof(data)); + m_readPos += sizeof(data); + } + + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator>>(char* data) +{ + assert(data && "Packet::operator>> Data must not be null"); + + // First extract string length + std::uint32_t length = 0; + *this >> length; + + if ((length > 0) && checkSize(length)) + { + // Then extract characters + std::memcpy(data, &m_data[m_readPos], length); + data[length] = '\0'; + + // Update reading position + m_readPos += length; + } + + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator>>(std::string& data) +{ + // First extract string length + std::uint32_t length = 0; + *this >> length; + + data.clear(); + if ((length > 0) && checkSize(length)) + { + // Then extract characters + data.assign(reinterpret_cast(&m_data[m_readPos]), length); + + // Update reading position + m_readPos += length; + } + + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator>>(wchar_t* data) +{ + assert(data && "Packet::operator>> Data must not be null"); + + // First extract string length + std::uint32_t length = 0; + *this >> length; + + if ((length > 0) && checkSize(length * sizeof(std::uint32_t))) + { + // Then extract characters + for (std::uint32_t i = 0; i < length; ++i) + { + std::uint32_t character = 0; + *this >> character; + data[i] = static_cast(character); + } + data[length] = L'\0'; + } + + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator>>(std::wstring& data) +{ + // First extract string length + std::uint32_t length = 0; + *this >> length; + + data.clear(); + if ((length > 0) && checkSize(length * sizeof(std::uint32_t))) + { + // Then extract characters + for (std::uint32_t i = 0; i < length; ++i) + { + std::uint32_t character = 0; + *this >> character; + data += static_cast(character); + } + } + + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator>>(String& data) +{ + // First extract the string length + std::uint32_t length = 0; + *this >> length; + + data.clear(); + if ((length > 0) && checkSize(length * sizeof(std::uint32_t))) + { + // Then extract characters + for (std::uint32_t i = 0; i < length; ++i) + { + std::uint32_t character = 0; + *this >> character; + data += static_cast(character); + } + } + + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator<<(bool data) +{ + *this << static_cast(data); + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator<<(std::int8_t data) +{ + append(&data, sizeof(data)); + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator<<(std::uint8_t data) +{ + append(&data, sizeof(data)); + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator<<(std::int16_t data) +{ + const auto toWrite = static_cast(htons(static_cast(data))); + append(&toWrite, sizeof(toWrite)); + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator<<(std::uint16_t data) +{ + const std::uint16_t toWrite = htons(data); + append(&toWrite, sizeof(toWrite)); + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator<<(std::int32_t data) +{ + const auto toWrite = static_cast(htonl(static_cast(data))); + append(&toWrite, sizeof(toWrite)); + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator<<(std::uint32_t data) +{ + const std::uint32_t toWrite = htonl(data); + append(&toWrite, sizeof(toWrite)); + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator<<(std::int64_t data) +{ + // Since htonll is not available everywhere, we have to convert + // to network byte order (big endian) manually + + const std::array toWrite = {static_cast((data >> 56) & 0xFF), + static_cast((data >> 48) & 0xFF), + static_cast((data >> 40) & 0xFF), + static_cast((data >> 32) & 0xFF), + static_cast((data >> 24) & 0xFF), + static_cast((data >> 16) & 0xFF), + static_cast((data >> 8) & 0xFF), + static_cast((data) & 0xFF)}; + + append(toWrite.data(), toWrite.size()); + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator<<(std::uint64_t data) +{ + // Since htonll is not available everywhere, we have to convert + // to network byte order (big endian) manually + + const std::array toWrite = {static_cast((data >> 56) & 0xFF), + static_cast((data >> 48) & 0xFF), + static_cast((data >> 40) & 0xFF), + static_cast((data >> 32) & 0xFF), + static_cast((data >> 24) & 0xFF), + static_cast((data >> 16) & 0xFF), + static_cast((data >> 8) & 0xFF), + static_cast((data) & 0xFF)}; + + append(toWrite.data(), toWrite.size()); + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator<<(float data) +{ + append(&data, sizeof(data)); + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator<<(double data) +{ + append(&data, sizeof(data)); + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator<<(const char* data) +{ + assert(data && "Packet::operator<< Data must not be null"); + + // First insert string length + const auto length = static_cast(std::strlen(data)); + *this << length; + + // Then insert characters + append(data, length * sizeof(char)); + + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator<<(const std::string& data) +{ + // First insert string length + const auto length = static_cast(data.size()); + *this << length; + + // Then insert characters + if (length > 0) + append(data.c_str(), length * sizeof(std::string::value_type)); + + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator<<(const wchar_t* data) +{ + assert(data && "Packet::operator<< Data must not be null"); + + // First insert string length + const auto length = static_cast(std::wcslen(data)); + *this << length; + + // Then insert characters + for (const wchar_t* c = data; *c != L'\0'; ++c) + *this << static_cast(*c); + + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator<<(const std::wstring& data) +{ + // First insert string length + const auto length = static_cast(data.size()); + *this << length; + + // Then insert characters + if (length > 0) + { + for (const wchar_t c : data) + *this << static_cast(c); + } + + return *this; +} + + +//////////////////////////////////////////////////////////// +Packet& Packet::operator<<(const String& data) +{ + // First insert the string length + const auto length = static_cast(data.getSize()); + *this << length; + + // Then insert characters + if (length > 0) + { + for (const unsigned int datum : data) + *this << datum; + } + + return *this; +} + + +//////////////////////////////////////////////////////////// +bool Packet::checkSize(std::size_t size) +{ + // Determine if size is big enough to trigger an overflow + const bool overflowDetected = m_readPos + size < m_readPos; + m_isValid = m_isValid && (m_readPos + size <= m_data.size()) && !overflowDetected; + + return m_isValid; +} + + +//////////////////////////////////////////////////////////// +const void* Packet::onSend(std::size_t& size) +{ + size = getDataSize(); + return getData(); +} + + +//////////////////////////////////////////////////////////// +void Packet::onReceive(const void* data, std::size_t size) +{ + append(data, size); +} + +} // namespace sf diff --git a/sfml/src/SFML/Network/Socket.cpp b/sfml/src/SFML/Network/Socket.cpp new file mode 100644 index 0000000..c9daa77 --- /dev/null +++ b/sfml/src/SFML/Network/Socket.cpp @@ -0,0 +1,175 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include + +#include + +#include +#include + + +namespace sf +{ +//////////////////////////////////////////////////////////// +Socket::Socket(Type type) : m_type(type), m_socket(priv::SocketImpl::invalidSocket()) +{ +} + + +//////////////////////////////////////////////////////////// +Socket::~Socket() +{ + // Close the socket before it gets destructed + close(); +} + + +//////////////////////////////////////////////////////////// +Socket::Socket(Socket&& socket) noexcept : +m_type(socket.m_type), +m_socket(std::exchange(socket.m_socket, priv::SocketImpl::invalidSocket())), +m_isBlocking(socket.m_isBlocking) +{ +} + + +//////////////////////////////////////////////////////////// +Socket& Socket::operator=(Socket&& socket) noexcept +{ + if (&socket == this) + return *this; + + close(); + + m_type = socket.m_type; + m_socket = std::exchange(socket.m_socket, priv::SocketImpl::invalidSocket()); + m_isBlocking = socket.m_isBlocking; + return *this; +} + + +//////////////////////////////////////////////////////////// +void Socket::setBlocking(bool blocking) +{ + // Apply if the socket is already created + if (m_socket != priv::SocketImpl::invalidSocket()) + priv::SocketImpl::setBlocking(m_socket, blocking); + + m_isBlocking = blocking; +} + + +//////////////////////////////////////////////////////////// +bool Socket::isBlocking() const +{ + return m_isBlocking; +} + + +//////////////////////////////////////////////////////////// +SocketHandle Socket::getNativeHandle() const +{ + return m_socket; +} + + +//////////////////////////////////////////////////////////// +void Socket::create() +{ + // Don't create the socket if it already exists + if (m_socket == priv::SocketImpl::invalidSocket()) + { + const SocketHandle handle = socket(PF_INET, m_type == Type::Tcp ? SOCK_STREAM : SOCK_DGRAM, 0); + + if (handle == priv::SocketImpl::invalidSocket()) + { + err() << "Failed to create socket" << std::endl; + return; + } + + create(handle); + } +} + + +//////////////////////////////////////////////////////////// +void Socket::create(SocketHandle handle) +{ + // Don't create the socket if it already exists + if (m_socket == priv::SocketImpl::invalidSocket()) + { + // Assign the new handle + m_socket = handle; + + // Set the current blocking state + setBlocking(m_isBlocking); + + if (m_type == Type::Tcp) + { + // Disable the Nagle algorithm (i.e. removes buffering of TCP packets) + int yes = 1; + if (setsockopt(m_socket, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&yes), sizeof(yes)) == -1) + { + err() << "Failed to set socket option \"TCP_NODELAY\" ; " + << "all your TCP packets will be buffered" << std::endl; + } + +// On macOS, disable the SIGPIPE signal on disconnection +#ifdef SFML_SYSTEM_MACOS + if (setsockopt(m_socket, SOL_SOCKET, SO_NOSIGPIPE, reinterpret_cast(&yes), sizeof(yes)) == -1) + { + err() << "Failed to set socket option \"SO_NOSIGPIPE\"" << std::endl; + } +#endif + } + else + { + // Enable broadcast by default for UDP sockets + int yes = 1; + if (setsockopt(m_socket, SOL_SOCKET, SO_BROADCAST, reinterpret_cast(&yes), sizeof(yes)) == -1) + { + err() << "Failed to enable broadcast on UDP socket" << std::endl; + } + } + } +} + + +//////////////////////////////////////////////////////////// +void Socket::close() +{ + // Close the socket + if (m_socket != priv::SocketImpl::invalidSocket()) + { + priv::SocketImpl::close(m_socket); + m_socket = priv::SocketImpl::invalidSocket(); + } +} + +} // namespace sf diff --git a/sfml/src/SFML/Network/SocketImpl.hpp b/sfml/src/SFML/Network/SocketImpl.hpp new file mode 100644 index 0000000..1ad06b6 --- /dev/null +++ b/sfml/src/SFML/Network/SocketImpl.hpp @@ -0,0 +1,123 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include + +#if defined(SFML_SYSTEM_WINDOWS) + +#include + +#include +#include + +#else + +#include +#include +#include +#include +#include +#include +#include + +#include + +#endif + +#include + + +namespace sf::priv +{ +//////////////////////////////////////////////////////////// +/// \brief Helper class implementing all the non-portable +/// socket stuff +/// +//////////////////////////////////////////////////////////// +class SocketImpl +{ +public: + //////////////////////////////////////////////////////////// + // Types + //////////////////////////////////////////////////////////// +#if defined(SFML_SYSTEM_WINDOWS) + using AddrLength = int; + using Size = int; +#else + using AddrLength = socklen_t; + using Size = std::size_t; +#endif + + //////////////////////////////////////////////////////////// + /// \brief Create an internal sockaddr_in address + /// + /// \param address Target address + /// \param port Target port + /// + /// \return sockaddr_in ready to be used by socket functions + /// + //////////////////////////////////////////////////////////// + static sockaddr_in createAddress(std::uint32_t address, unsigned short port); + + //////////////////////////////////////////////////////////// + /// \brief Return the value of the invalid socket + /// + /// \return Special value of the invalid socket + /// + //////////////////////////////////////////////////////////// + static SocketHandle invalidSocket(); + + //////////////////////////////////////////////////////////// + /// \brief Close and destroy a socket + /// + /// \param sock Handle of the socket to close + /// + //////////////////////////////////////////////////////////// + static void close(SocketHandle sock); + + //////////////////////////////////////////////////////////// + /// \brief Set a socket as blocking or non-blocking + /// + /// \param sock Handle of the socket + /// \param block New blocking state of the socket + /// + //////////////////////////////////////////////////////////// + static void setBlocking(SocketHandle sock, bool block); + + //////////////////////////////////////////////////////////// + /// Get the last socket error status + /// + /// \return Status corresponding to the last socket error + /// + //////////////////////////////////////////////////////////// + static Socket::Status getErrorStatus(); +}; + +} // namespace sf::priv diff --git a/sfml/src/SFML/Network/SocketSelector.cpp b/sfml/src/SFML/Network/SocketSelector.cpp new file mode 100644 index 0000000..faa81d5 --- /dev/null +++ b/sfml/src/SFML/Network/SocketSelector.cpp @@ -0,0 +1,209 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include + +#include + +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable : 4127) // "conditional expression is constant" generated by the FD_SET macro +#endif + + +namespace sf +{ +//////////////////////////////////////////////////////////// +struct SocketSelector::SocketSelectorImpl +{ + fd_set allSockets{}; //!< Set containing all the sockets handles + fd_set socketsReady{}; //!< Set containing handles of the sockets that are ready + int maxSocket{}; //!< Maximum socket handle + int socketCount{}; //!< Number of socket handles +}; + + +//////////////////////////////////////////////////////////// +SocketSelector::SocketSelector() : m_impl(std::make_unique()) +{ + clear(); +} + + +//////////////////////////////////////////////////////////// +SocketSelector::~SocketSelector() = default; + + +//////////////////////////////////////////////////////////// +SocketSelector::SocketSelector(const SocketSelector& copy) : m_impl(std::make_unique(*copy.m_impl)) +{ +} + + +//////////////////////////////////////////////////////////// +SocketSelector& SocketSelector::operator=(const SocketSelector& right) +{ + SocketSelector temp(right); + std::swap(m_impl, temp.m_impl); + return *this; +} + + +//////////////////////////////////////////////////////////// +SocketSelector::SocketSelector(SocketSelector&&) noexcept = default; + + +////////////////////////////////////////////////////////////] +SocketSelector& SocketSelector::operator=(SocketSelector&&) noexcept = default; + + +//////////////////////////////////////////////////////////// +void SocketSelector::add(Socket& socket) +{ + const SocketHandle handle = socket.getNativeHandle(); + if (handle != priv::SocketImpl::invalidSocket()) + { + +#if defined(SFML_SYSTEM_WINDOWS) + + if (m_impl->socketCount >= FD_SETSIZE) + { + err() << "The socket can't be added to the selector because the " + << "selector is full. This is a limitation of your operating " + << "system's FD_SETSIZE setting."; + return; + } + + if (FD_ISSET(handle, &m_impl->allSockets)) + return; + + ++m_impl->socketCount; + +#else + + if (handle >= FD_SETSIZE) + { + err() << "The socket can't be added to the selector because its " + << "ID is too high. This is a limitation of your operating " + << "system's FD_SETSIZE setting."; + return; + } + + // SocketHandle is an int in POSIX + m_impl->maxSocket = std::max(m_impl->maxSocket, handle); + +#endif + + FD_SET(handle, &m_impl->allSockets); + } +} + + +//////////////////////////////////////////////////////////// +void SocketSelector::remove(Socket& socket) +{ + const SocketHandle handle = socket.getNativeHandle(); + if (handle != priv::SocketImpl::invalidSocket()) + { + +#if defined(SFML_SYSTEM_WINDOWS) + + if (!FD_ISSET(handle, &m_impl->allSockets)) + return; + + --m_impl->socketCount; + +#else + + if (handle >= FD_SETSIZE) + return; + +#endif + + FD_CLR(handle, &m_impl->allSockets); + FD_CLR(handle, &m_impl->socketsReady); + } +} + + +//////////////////////////////////////////////////////////// +void SocketSelector::clear() +{ + FD_ZERO(&m_impl->allSockets); + FD_ZERO(&m_impl->socketsReady); + + m_impl->maxSocket = 0; + m_impl->socketCount = 0; +} + + +//////////////////////////////////////////////////////////// +bool SocketSelector::wait(Time timeout) +{ + // Setup the timeout + timeval time{}; + time.tv_sec = static_cast(timeout.asMicroseconds() / 1000000); + time.tv_usec = static_cast(timeout.asMicroseconds() % 1000000); + + // Initialize the set that will contain the sockets that are ready + m_impl->socketsReady = m_impl->allSockets; + + // Wait until one of the sockets is ready for reading, or timeout is reached + // The first parameter is ignored on Windows + const int count = select(m_impl->maxSocket + 1, &m_impl->socketsReady, nullptr, nullptr, timeout != Time::Zero ? &time : nullptr); + + return count > 0; +} + + +//////////////////////////////////////////////////////////// +bool SocketSelector::isReady(Socket& socket) const +{ + const SocketHandle handle = socket.getNativeHandle(); + if (handle != priv::SocketImpl::invalidSocket()) + { + +#if !defined(SFML_SYSTEM_WINDOWS) + + if (handle >= FD_SETSIZE) + return false; + +#endif + + return FD_ISSET(handle, &m_impl->socketsReady) != 0; + } + + return false; +} + +} // namespace sf diff --git a/sfml/src/SFML/Network/TcpListener.cpp b/sfml/src/SFML/Network/TcpListener.cpp new file mode 100644 index 0000000..18ab76f --- /dev/null +++ b/sfml/src/SFML/Network/TcpListener.cpp @@ -0,0 +1,132 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include + +#include + +#include + + +namespace sf +{ +//////////////////////////////////////////////////////////// +TcpListener::TcpListener() : Socket(Type::Tcp) +{ +} + + +//////////////////////////////////////////////////////////// +unsigned short TcpListener::getLocalPort() const +{ + if (getNativeHandle() != priv::SocketImpl::invalidSocket()) + { + // Retrieve information about the local end of the socket + sockaddr_in address{}; + priv::SocketImpl::AddrLength size = sizeof(address); + if (getsockname(getNativeHandle(), reinterpret_cast(&address), &size) != -1) + { + return ntohs(address.sin_port); + } + } + + // We failed to retrieve the port + return 0; +} + + +//////////////////////////////////////////////////////////// +Socket::Status TcpListener::listen(unsigned short port, IpAddress address) +{ + // Close the socket if it is already bound + close(); + + // Create the internal socket if it doesn't exist + create(); + + // Check if the address is valid + if (address == IpAddress::Broadcast) + return Status::Error; + + // Bind the socket to the specified port + sockaddr_in addr = priv::SocketImpl::createAddress(address.toInteger(), port); + if (bind(getNativeHandle(), reinterpret_cast(&addr), sizeof(addr)) == -1) + { + // Not likely to happen, but... + err() << "Failed to bind listener socket to port " << port << std::endl; + return Status::Error; + } + + // Listen to the bound port + if (::listen(getNativeHandle(), SOMAXCONN) == -1) + { + // Oops, socket is deaf + err() << "Failed to listen to port " << port << std::endl; + return Status::Error; + } + + return Status::Done; +} + + +//////////////////////////////////////////////////////////// +void TcpListener::close() +{ + // Simply close the socket + Socket::close(); +} + + +//////////////////////////////////////////////////////////// +Socket::Status TcpListener::accept(TcpSocket& socket) +{ + // Make sure that we're listening + if (getNativeHandle() == priv::SocketImpl::invalidSocket()) + { + err() << "Failed to accept a new connection, the socket is not listening" << std::endl; + return Status::Error; + } + + // Accept a new connection + sockaddr_in address{}; + priv::SocketImpl::AddrLength length = sizeof(address); + const SocketHandle remote = ::accept(getNativeHandle(), reinterpret_cast(&address), &length); + + // Check for errors + if (remote == priv::SocketImpl::invalidSocket()) + return priv::SocketImpl::getErrorStatus(); + + // Initialize the new connected socket + socket.close(); + socket.create(remote); + + return Status::Done; +} + +} // namespace sf diff --git a/sfml/src/SFML/Network/TcpSocket.cpp b/sfml/src/SFML/Network/TcpSocket.cpp new file mode 100644 index 0000000..675d8d3 --- /dev/null +++ b/sfml/src/SFML/Network/TcpSocket.cpp @@ -0,0 +1,429 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#ifdef _MSC_VER +#pragma warning(disable : 4127) // "conditional expression is constant" generated by the FD_SET macro +#endif + + +namespace +{ +// Low-level send/receive flags (OS-dependent) +#ifdef SFML_SYSTEM_LINUX +constexpr int flags = MSG_NOSIGNAL; +#else +constexpr int flags = 0; +#endif +} // namespace + +namespace sf +{ +//////////////////////////////////////////////////////////// +TcpSocket::TcpSocket() : Socket(Type::Tcp) +{ +} + + +//////////////////////////////////////////////////////////// +unsigned short TcpSocket::getLocalPort() const +{ + if (getNativeHandle() != priv::SocketImpl::invalidSocket()) + { + // Retrieve information about the local end of the socket + sockaddr_in address{}; + priv::SocketImpl::AddrLength size = sizeof(address); + if (getsockname(getNativeHandle(), reinterpret_cast(&address), &size) != -1) + { + return ntohs(address.sin_port); + } + } + + // We failed to retrieve the port + return 0; +} + + +//////////////////////////////////////////////////////////// +std::optional TcpSocket::getRemoteAddress() const +{ + if (getNativeHandle() != priv::SocketImpl::invalidSocket()) + { + // Retrieve information about the remote end of the socket + sockaddr_in address{}; + priv::SocketImpl::AddrLength size = sizeof(address); + if (getpeername(getNativeHandle(), reinterpret_cast(&address), &size) != -1) + { + return IpAddress(ntohl(address.sin_addr.s_addr)); + } + } + + // We failed to retrieve the address + return std::nullopt; +} + + +//////////////////////////////////////////////////////////// +unsigned short TcpSocket::getRemotePort() const +{ + if (getNativeHandle() != priv::SocketImpl::invalidSocket()) + { + // Retrieve information about the remote end of the socket + sockaddr_in address{}; + priv::SocketImpl::AddrLength size = sizeof(address); + if (getpeername(getNativeHandle(), reinterpret_cast(&address), &size) != -1) + { + return ntohs(address.sin_port); + } + } + + // We failed to retrieve the port + return 0; +} + + +//////////////////////////////////////////////////////////// +Socket::Status TcpSocket::connect(IpAddress remoteAddress, unsigned short remotePort, Time timeout) +{ + // Disconnect the socket if it is already connected + disconnect(); + + // Create the internal socket if it doesn't exist + create(); + + // Create the remote address + sockaddr_in address = priv::SocketImpl::createAddress(remoteAddress.toInteger(), remotePort); + + if (timeout <= Time::Zero) + { + // ----- We're not using a timeout: just try to connect ----- + + // Connect the socket + if (::connect(getNativeHandle(), reinterpret_cast(&address), sizeof(address)) == -1) + return priv::SocketImpl::getErrorStatus(); + + // Connection succeeded + return Status::Done; + } + + // ----- We're using a timeout: we'll need a few tricks to make it work ----- + + // Save the previous blocking state + const bool blocking = isBlocking(); + + // Switch to non-blocking to enable our connection timeout + if (blocking) + setBlocking(false); + + // Try to connect to the remote address + if (::connect(getNativeHandle(), reinterpret_cast(&address), sizeof(address)) >= 0) + { + // We got instantly connected! (it may no happen a lot...) + setBlocking(blocking); + return Status::Done; + } + + // Get the error status + Status status = priv::SocketImpl::getErrorStatus(); + + // If we were in non-blocking mode, return immediately + if (!blocking) + return status; + + // Otherwise, wait until something happens to our socket (success, timeout or error) + if (status == Socket::Status::NotReady) + { + // Setup the selector + fd_set selector; + FD_ZERO(&selector); + FD_SET(getNativeHandle(), &selector); + + // Setup the timeout + timeval time{}; + time.tv_sec = static_cast(timeout.asMicroseconds() / 1000000); + time.tv_usec = static_cast(timeout.asMicroseconds() % 1000000); + + // Wait for something to write on our socket (which means that the connection request has returned) + if (select(static_cast(getNativeHandle() + 1), nullptr, &selector, nullptr, &time) > 0) + { + // At this point the connection may have been either accepted or refused. + // To know whether it's a success or a failure, we must check the address of the connected peer + if (getRemoteAddress().has_value()) + { + // Connection accepted + status = Status::Done; + } + else + { + // Connection refused + status = priv::SocketImpl::getErrorStatus(); + } + } + else + { + // Failed to connect before timeout is over + status = priv::SocketImpl::getErrorStatus(); + } + } + + // Switch back to blocking mode + setBlocking(true); + + return status; +} + + +//////////////////////////////////////////////////////////// +void TcpSocket::disconnect() +{ + // Close the socket + close(); + + // Reset the pending packet data + m_pendingPacket = PendingPacket(); +} + + +//////////////////////////////////////////////////////////// +Socket::Status TcpSocket::send(const void* data, std::size_t size) +{ + if (!isBlocking()) + err() << "Warning: Partial sends might not be handled properly." << std::endl; + + std::size_t sent = 0; + + return send(data, size, sent); +} + + +//////////////////////////////////////////////////////////// +Socket::Status TcpSocket::send(const void* data, std::size_t size, std::size_t& sent) +{ + // Check the parameters + if (!data || (size == 0)) + { + err() << "Cannot send data over the network (no data to send)" << std::endl; + return Status::Error; + } + + // Loop until every byte has been sent + int result = 0; + for (sent = 0; sent < size; sent += static_cast(result)) + { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuseless-cast" + // Send a chunk of data + result = static_cast(::send(getNativeHandle(), + static_cast(data) + sent, + static_cast(size - sent), + flags)); +#pragma GCC diagnostic pop + + // Check for errors + if (result < 0) + { + const Status status = priv::SocketImpl::getErrorStatus(); + + if ((status == Status::NotReady) && sent) + return Status::Partial; + + return status; + } + } + + return Status::Done; +} + + +//////////////////////////////////////////////////////////// +Socket::Status TcpSocket::receive(void* data, std::size_t size, std::size_t& received) +{ + // First clear the variables to fill + received = 0; + + // Check the destination buffer + if (!data) + { + err() << "Cannot receive data from the network (the destination buffer is invalid)" << std::endl; + return Status::Error; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuseless-cast" + // Receive a chunk of bytes + const int sizeReceived = static_cast( + recv(getNativeHandle(), static_cast(data), static_cast(size), flags)); +#pragma GCC diagnostic pop + + // Check the number of bytes received + if (sizeReceived > 0) + { + received = static_cast(sizeReceived); + return Status::Done; + } + if (sizeReceived == 0) + { + return Socket::Status::Disconnected; + } + + return priv::SocketImpl::getErrorStatus(); +} + + +//////////////////////////////////////////////////////////// +Socket::Status TcpSocket::send(Packet& packet) +{ + // TCP is a stream protocol, it doesn't preserve messages boundaries. + // This means that we have to send the packet size first, so that the + // receiver knows the actual end of the packet in the data stream. + + // We allocate an extra memory block so that the size can be sent + // together with the data in a single call. This may seem inefficient, + // but it is actually required to avoid partial send, which could cause + // data corruption on the receiving end. + + // Get the data to send from the packet + std::size_t size = 0; + const void* data = packet.onSend(size); + + // First convert the packet size to network byte order + std::uint32_t packetSize = htonl(static_cast(size)); + + // Allocate memory for the data block to send + m_blockToSendBuffer.resize(sizeof(packetSize) + size); + +// Copy the packet size and data into the block to send +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnull-dereference" // False positive. + std::memcpy(m_blockToSendBuffer.data(), &packetSize, sizeof(packetSize)); +#pragma GCC diagnostic pop + if (size > 0) + std::memcpy(m_blockToSendBuffer.data() + sizeof(packetSize), data, size); + +// These warnings are ignored here for portability, as even on Windows the +// signature of `send` might change depending on whether Win32 or MinGW is +// being used. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuseless-cast" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-conversion" + // Send the data block + std::size_t sent = 0; + const Status status = send(m_blockToSendBuffer.data() + packet.m_sendPos, + static_cast(m_blockToSendBuffer.size() - packet.m_sendPos), + sent); +#pragma GCC diagnostic pop +#pragma GCC diagnostic pop + + // In the case of a partial send, record the location to resume from + if (status == Status::Partial) + { + packet.m_sendPos += sent; + } + else if (status == Status::Done) + { + packet.m_sendPos = 0; + } + + return status; +} + + +//////////////////////////////////////////////////////////// +Socket::Status TcpSocket::receive(Packet& packet) +{ + // First clear the variables to fill + packet.clear(); + + // We start by getting the size of the incoming packet + std::uint32_t packetSize = 0; + std::size_t received = 0; + if (m_pendingPacket.sizeReceived < sizeof(m_pendingPacket.size)) + { + // Loop until we've received the entire size of the packet + // (even a 4 byte variable may be received in more than one call) + while (m_pendingPacket.sizeReceived < sizeof(m_pendingPacket.size)) + { + char* data = reinterpret_cast(&m_pendingPacket.size) + m_pendingPacket.sizeReceived; + const Status status = receive(data, sizeof(m_pendingPacket.size) - m_pendingPacket.sizeReceived, received); + m_pendingPacket.sizeReceived += received; + + if (status != Status::Done) + return status; + } + + // The packet size has been fully received + packetSize = ntohl(m_pendingPacket.size); + } + else + { + // The packet size has already been received in a previous call + packetSize = ntohl(m_pendingPacket.size); + } + + // Loop until we receive all the packet data + std::array buffer{}; + while (m_pendingPacket.data.size() < packetSize) + { + // Receive a chunk of data + const std::size_t sizeToGet = std::min(packetSize - m_pendingPacket.data.size(), buffer.size()); + const Status status = receive(buffer.data(), sizeToGet, received); + if (status != Status::Done) + return status; + + // Append it into the packet + if (received > 0) + { + m_pendingPacket.data.resize(m_pendingPacket.data.size() + received); + std::byte* begin = m_pendingPacket.data.data() + m_pendingPacket.data.size() - received; + std::memcpy(begin, buffer.data(), received); + } + } + + // We have received all the packet data: we can copy it to the user packet + if (!m_pendingPacket.data.empty()) + packet.onReceive(m_pendingPacket.data.data(), m_pendingPacket.data.size()); + + // Clear the pending packet data + m_pendingPacket = PendingPacket(); + + return Status::Done; +} + +} // namespace sf diff --git a/sfml/src/SFML/Network/UdpSocket.cpp b/sfml/src/SFML/Network/UdpSocket.cpp new file mode 100644 index 0000000..e57e55d --- /dev/null +++ b/sfml/src/SFML/Network/UdpSocket.cpp @@ -0,0 +1,223 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include + +#include + +#include + +#include + + +namespace sf +{ +//////////////////////////////////////////////////////////// +UdpSocket::UdpSocket() : Socket(Type::Udp) +{ +} + + +//////////////////////////////////////////////////////////// +unsigned short UdpSocket::getLocalPort() const +{ + if (getNativeHandle() != priv::SocketImpl::invalidSocket()) + { + // Retrieve information about the local end of the socket + sockaddr_in address{}; + priv::SocketImpl::AddrLength size = sizeof(address); + if (getsockname(getNativeHandle(), reinterpret_cast(&address), &size) != -1) + { + return ntohs(address.sin_port); + } + } + + // We failed to retrieve the port + return 0; +} + + +//////////////////////////////////////////////////////////// +Socket::Status UdpSocket::bind(unsigned short port, IpAddress address) +{ + // Close the socket if it is already bound + close(); + + // Create the internal socket if it doesn't exist + create(); + + // Check if the address is valid + if (address == IpAddress::Broadcast) + return Status::Error; + + // Bind the socket + sockaddr_in addr = priv::SocketImpl::createAddress(address.toInteger(), port); + if (::bind(getNativeHandle(), reinterpret_cast(&addr), sizeof(addr)) == -1) + { + err() << "Failed to bind socket to port " << port << std::endl; + return Status::Error; + } + + return Status::Done; +} + + +//////////////////////////////////////////////////////////// +void UdpSocket::unbind() +{ + // Simply close the socket + close(); +} + + +//////////////////////////////////////////////////////////// +Socket::Status UdpSocket::send(const void* data, std::size_t size, IpAddress remoteAddress, unsigned short remotePort) +{ + // Create the internal socket if it doesn't exist + create(); + + // Make sure that all the data will fit in one datagram + if (size > MaxDatagramSize) + { + err() << "Cannot send data over the network " + << "(the number of bytes to send is greater than sf::UdpSocket::MaxDatagramSize)" << std::endl; + return Status::Error; + } + + // Build the target address + sockaddr_in address = priv::SocketImpl::createAddress(remoteAddress.toInteger(), remotePort); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuseless-cast" + // Send the data (unlike TCP, all the data is always sent in one call) + const int sent = static_cast( + sendto(getNativeHandle(), + static_cast(data), + static_cast(size), + 0, + reinterpret_cast(&address), + sizeof(address))); +#pragma GCC diagnostic pop + + // Check for errors + if (sent < 0) + return priv::SocketImpl::getErrorStatus(); + + return Status::Done; +} + + +//////////////////////////////////////////////////////////// +Socket::Status UdpSocket::receive(void* data, + std::size_t size, + std::size_t& received, + std::optional& remoteAddress, + unsigned short& remotePort) +{ + // First clear the variables to fill + received = 0; + remoteAddress = std::nullopt; + remotePort = 0; + + // Check the destination buffer + if (!data) + { + err() << "Cannot receive data from the network (the destination buffer is invalid)" << std::endl; + return Status::Error; + } + + // Data that will be filled with the other computer's address + sockaddr_in address = priv::SocketImpl::createAddress(INADDR_ANY, 0); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuseless-cast" + // Receive a chunk of bytes + priv::SocketImpl::AddrLength addressSize = sizeof(address); + const int sizeReceived = static_cast( + recvfrom(getNativeHandle(), + static_cast(data), + static_cast(size), + 0, + reinterpret_cast(&address), + &addressSize)); +#pragma GCC diagnostic pop + + // Check for errors + if (sizeReceived < 0) + return priv::SocketImpl::getErrorStatus(); + + // Fill the sender information + received = static_cast(sizeReceived); + remoteAddress = IpAddress(ntohl(address.sin_addr.s_addr)); + remotePort = ntohs(address.sin_port); + + return Status::Done; +} + + +//////////////////////////////////////////////////////////// +Socket::Status UdpSocket::send(Packet& packet, IpAddress remoteAddress, unsigned short remotePort) +{ + // UDP is a datagram-oriented protocol (as opposed to TCP which is a stream protocol). + // Sending one datagram is almost safe: it may be lost but if it's received, then its data + // is guaranteed to be ok. However, splitting a packet into multiple datagrams would be highly + // unreliable, since datagrams may be reordered, dropped or mixed between different sources. + // That's why SFML imposes a limit on packet size so that they can be sent in a single datagram. + // This also removes the overhead associated to packets -- there's no size to send in addition + // to the packet's data. + + // Get the data to send from the packet + std::size_t size = 0; + const void* data = packet.onSend(size); + + // Send it + return send(data, size, remoteAddress, remotePort); +} + + +//////////////////////////////////////////////////////////// +Socket::Status UdpSocket::receive(Packet& packet, std::optional& remoteAddress, unsigned short& remotePort) +{ + // See the detailed comment in send(Packet) above. + + // Receive the datagram + std::size_t received = 0; + const Status status = receive(m_buffer.data(), m_buffer.size(), received, remoteAddress, remotePort); + + // If we received valid data, we can copy it to the user packet + packet.clear(); + if ((status == Status::Done) && (received > 0)) + packet.onReceive(m_buffer.data(), received); + + return status; +} + + +} // namespace sf diff --git a/sfml/src/SFML/Network/Unix/SocketImpl.cpp b/sfml/src/SFML/Network/Unix/SocketImpl.cpp new file mode 100644 index 0000000..91e2b85 --- /dev/null +++ b/sfml/src/SFML/Network/Unix/SocketImpl.cpp @@ -0,0 +1,111 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +#include + +#include +#include + +#include + + +namespace sf::priv +{ +//////////////////////////////////////////////////////////// +sockaddr_in SocketImpl::createAddress(std::uint32_t address, unsigned short port) +{ + auto addr = sockaddr_in(); + addr.sin_addr.s_addr = htonl(address); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + +#if defined(SFML_SYSTEM_MACOS) + addr.sin_len = sizeof(addr); +#endif + + return addr; +} + + +//////////////////////////////////////////////////////////// +SocketHandle SocketImpl::invalidSocket() +{ + return -1; +} + + +//////////////////////////////////////////////////////////// +void SocketImpl::close(SocketHandle sock) +{ + ::close(sock); +} + + +//////////////////////////////////////////////////////////// +void SocketImpl::setBlocking(SocketHandle sock, bool block) +{ + const int status = fcntl(sock, F_GETFL); + if (block) + { + if (fcntl(sock, F_SETFL, status & ~O_NONBLOCK) == -1) + err() << "Failed to set file status flags: " << errno << std::endl; + } + else + { + if (fcntl(sock, F_SETFL, status | O_NONBLOCK) == -1) + err() << "Failed to set file status flags: " << errno << std::endl; + } +} + + +//////////////////////////////////////////////////////////// +Socket::Status SocketImpl::getErrorStatus() +{ + // The following are sometimes equal to EWOULDBLOCK, + // so we have to make a special case for them in order + // to avoid having double values in the switch case + if ((errno == EAGAIN) || (errno == EINPROGRESS)) + return Socket::Status::NotReady; + + // clang-format off + switch (errno) + { + case EWOULDBLOCK: return Socket::Status::NotReady; + case ECONNABORTED: return Socket::Status::Disconnected; + case ECONNRESET: return Socket::Status::Disconnected; + case ETIMEDOUT: return Socket::Status::Disconnected; + case ENETRESET: return Socket::Status::Disconnected; + case ENOTCONN: return Socket::Status::Disconnected; + case EPIPE: return Socket::Status::Disconnected; + default: return Socket::Status::Error; + } + // clang-format on +} + +} // namespace sf::priv diff --git a/sfml/src/SFML/Network/Win32/SocketImpl.cpp b/sfml/src/SFML/Network/Win32/SocketImpl.cpp new file mode 100644 index 0000000..c35dd1f --- /dev/null +++ b/sfml/src/SFML/Network/Win32/SocketImpl.cpp @@ -0,0 +1,110 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +#include + + +namespace +{ +//////////////////////////////////////////////////////////// +// Windows needs some initialization and cleanup to get +// sockets working properly... so let's create a class that will +// do it automatically +//////////////////////////////////////////////////////////// +struct SocketInitializer +{ + SocketInitializer() + { + WSADATA init; + WSAStartup(MAKEWORD(2, 2), &init); + } + + ~SocketInitializer() + { + WSACleanup(); + } +} globalInitializer; +} // namespace + + +namespace sf::priv +{ +//////////////////////////////////////////////////////////// +sockaddr_in SocketImpl::createAddress(std::uint32_t address, unsigned short port) +{ + auto addr = sockaddr_in(); + addr.sin_addr.s_addr = htonl(address); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + return addr; +} + + +//////////////////////////////////////////////////////////// +SocketHandle SocketImpl::invalidSocket() +{ + return INVALID_SOCKET; +} + + +//////////////////////////////////////////////////////////// +void SocketImpl::close(SocketHandle sock) +{ + closesocket(sock); +} + + +//////////////////////////////////////////////////////////// +void SocketImpl::setBlocking(SocketHandle sock, bool block) +{ + u_long blocking = block ? 0 : 1; + ioctlsocket(sock, static_cast(FIONBIO), &blocking); +} + + +//////////////////////////////////////////////////////////// +Socket::Status SocketImpl::getErrorStatus() +{ + // clang-format off + switch (WSAGetLastError()) + { + case WSAEWOULDBLOCK: return Socket::Status::NotReady; + case WSAEALREADY: return Socket::Status::NotReady; + case WSAECONNABORTED: return Socket::Status::Disconnected; + case WSAECONNRESET: return Socket::Status::Disconnected; + case WSAETIMEDOUT: return Socket::Status::Disconnected; + case WSAENETRESET: return Socket::Status::Disconnected; + case WSAENOTCONN: return Socket::Status::Disconnected; + case WSAEISCONN: return Socket::Status::Done; // when connecting a non-blocking socket + default: return Socket::Status::Error; + } + // clang-format on +} +} // namespace sf::priv diff --git a/vorbis/build-vorbis.lua b/vorbis/build-vorbis.lua index c6ce611..f5493d0 100644 --- a/vorbis/build-vorbis.lua +++ b/vorbis/build-vorbis.lua @@ -1,70 +1,80 @@ -project"vorbis" - cppdialect"c++17" - kind"staticLib" - targetdir (libout) - staticruntime "off" - objdir(intdir) +local m = {} - links{"ogg"} +local scriptdir = path.getabsolute(path.getdirectory(_SCRIPT)) +local ogg = require("vendor/ogg/build-ogg") +function m.generateproject(liboutdir, intdir) + project"vorbis" + language"C" -- c++ will mangle names and sfml wont build + kind"staticLib" + targetdir (liboutdir) + objdir(intdir) + warnings"Off" + + ogg.link() includedirs { - "include", - "lib", - "../ogg/include" + path.join(scriptdir, "include"), + path.join(scriptdir, "lib"), } files { - "lib/envelope.h", - "lib/lpc.h", - "lib/lsp.h", - "lib/codebook.h", - "lib/misc.h", - "lib/psy.h", - "lib/masking.h", - "lib/os.h", - "lib/mdct.h", - "lib/smallft.h", - "lib/highlevel.h", - "lib/registry.h", - "lib/scales.h", - "lib/window.h", - "lib/lookup.h", - "lib/lookup_data.h", - "lib/codec_internal.h", - "lib/backends.h", - "lib/bitrate.h", - "lib/mdct.c", - "lib/smallft.c", - "lib/block.c", - "lib/envelope.c", - "lib/window.c", - "lib/lsp.c", - "lib/lpc.c", - "lib/analysis.c", - "lib/synthesis.c", - "lib/psy.c", - "lib/info.c", - "lib/floor1.c", - "lib/floor0.c", - "lib/res0.c", - "lib/mapping0.c", - "lib/registry.c", - "lib/codebook.c", - "lib/sharedbook.c", - "lib/lookup.c", - "lib/bitrate.c", - "lib/vorbisfile.c", - "lib/vorbisenc.c", + path.join(scriptdir, "lib/envelope.h"), + path.join(scriptdir, "lib/lpc.h"), + path.join(scriptdir, "lib/lsp.h"), + path.join(scriptdir, "lib/codebook.h"), + path.join(scriptdir, "lib/misc.h"), + path.join(scriptdir, "lib/psy.h"), + path.join(scriptdir, "lib/masking.h"), + path.join(scriptdir, "lib/os.h"), + path.join(scriptdir, "lib/mdct.h"), + path.join(scriptdir, "lib/smallft.h"), + path.join(scriptdir, "lib/highlevel.h"), + path.join(scriptdir, "lib/registry.h"), + path.join(scriptdir, "lib/scales.h"), + path.join(scriptdir, "lib/window.h"), + path.join(scriptdir, "lib/lookup.h"), + path.join(scriptdir, "lib/lookup_data.h"), + path.join(scriptdir, "lib/codec_internal.h"), + path.join(scriptdir, "lib/backends.h"), + path.join(scriptdir, "lib/bitrate.h"), + path.join(scriptdir, "lib/mdct.c"), + path.join(scriptdir, "lib/smallft.c"), + path.join(scriptdir, "lib/block.c"), + path.join(scriptdir, "lib/envelope.c"), + path.join(scriptdir, "lib/window.c"), + path.join(scriptdir, "lib/lsp.c"), + path.join(scriptdir, "lib/lpc.c"), + path.join(scriptdir, "lib/analysis.c"), + path.join(scriptdir, "lib/synthesis.c"), + path.join(scriptdir, "lib/psy.c"), + path.join(scriptdir, "lib/info.c"), + path.join(scriptdir, "lib/floor1.c"), + path.join(scriptdir, "lib/floor0.c"), + path.join(scriptdir, "lib/res0.c"), + path.join(scriptdir, "lib/mapping0.c"), + path.join(scriptdir, "lib/registry.c"), + path.join(scriptdir, "lib/codebook.c"), + path.join(scriptdir, "lib/sharedbook.c"), + path.join(scriptdir, "lib/lookup.c"), + path.join(scriptdir, "lib/bitrate.c"), + path.join(scriptdir, "lib/vorbisfile.c"), + path.join(scriptdir, "lib/vorbisenc.c"), } - filter "configurations:Debug" - runtime "Debug" - symbols "on" - filter "configurations:Release" - runtime "Release" - optimize "Speed" + filter"" +end - filter"" \ No newline at end of file +function m.link() + links {"vorbis", "ogg"} + externalincludedirs + { + path.join(scriptdir, "include"), + path.join(scriptdir, "lib"), + path.join(scriptdir, "../ogg/include") + } +end + +return m \ No newline at end of file