diff options
author | Howaner <franzi.moos@googlemail.com> | 2015-03-10 20:05:46 +0100 |
---|---|---|
committer | Howaner <franzi.moos@googlemail.com> | 2015-03-10 20:05:46 +0100 |
commit | ab420f6cfc8519fb2ec0caa7fb242517244ffcea (patch) | |
tree | c31c3538db081516d81959ed8ad00259175441a9 | |
parent | Changed return type from AbsorbWater() to void. (diff) | |
parent | Fixed client kick/crash if many block changes happend (diff) | |
download | cuberite-ab420f6cfc8519fb2ec0caa7fb242517244ffcea.tar cuberite-ab420f6cfc8519fb2ec0caa7fb242517244ffcea.tar.gz cuberite-ab420f6cfc8519fb2ec0caa7fb242517244ffcea.tar.bz2 cuberite-ab420f6cfc8519fb2ec0caa7fb242517244ffcea.tar.lz cuberite-ab420f6cfc8519fb2ec0caa7fb242517244ffcea.tar.xz cuberite-ab420f6cfc8519fb2ec0caa7fb242517244ffcea.tar.zst cuberite-ab420f6cfc8519fb2ec0caa7fb242517244ffcea.zip |
112 files changed, 7754 insertions, 2380 deletions
diff --git a/.gitignore b/.gitignore index c7f032b00..8dc90910c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,12 @@ nbproject/ ipch/ Win32/ MCServer/MCServer +MCServer/buildinfo +MCServer/CONTRIBUTORS +MCServer/LICENSE +MCServer/Licenses MCServer/itemblacklist +Testing/ ChunkWorxSave.ini doxy/ Profiling @@ -14,6 +19,7 @@ cloc.xsl *.ncb *.user *.suo +*.sqlite /EveryNight.cmd /UploadLuaAPI.cmd AllFiles.lst diff --git a/COMPILING.md b/COMPILING.md index 34b1e976f..89f88bbb5 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -4,8 +4,8 @@ To compile MCServer from source, you need CMake and make, as well as a C compile ## Windows ## -We use Microsoft Visual Studio for Windows compilation. It is possible to use other toolchains, but it isn't tested nor supported. Visual Studio versions 2013 Express is being actively used for development. -You can find download links for VS2013 Express here: http://www.microsoft.com/en-us/download/details.aspx?id=40787 +We use Microsoft Visual Studio for Windows compilation. It is possible to use other toolchains, but it isn't tested nor supported. Visual Studio versions 2013 Express for Desktop is being actively used for development. +You can find download links for VS2013 Express here: http://go.microsoft.com/?linkid=9832280 Next, you need to download and install CMake. Download from here: http://cmake.org/cmake/resources/software.html . You should download a full installation package, so that the installer will set everything up for you (especially the paths). @@ -73,6 +73,7 @@ Install git, make, cmake and gcc or clang, using your platform's package manager ``` sudo apt-get install git make cmake gcc g++ ``` +(Ensure you have gcc/g++ 4.8 or higher installed to avoid errors during compilation) ### Getting the sources ### ``` diff --git a/Install/ThirdPartyLicenses/LibEvent-LICENSE.txt b/Install/ThirdPartyLicenses/LibEvent-LICENSE.txt new file mode 100644 index 000000000..402ca5089 --- /dev/null +++ b/Install/ThirdPartyLicenses/LibEvent-LICENSE.txt @@ -0,0 +1,99 @@ +Libevent is available for use under the following license, commonly known +as the 3-clause (or "modified") BSD license: + +============================== +Copyright (c) 2000-2007 Niels Provos <provos@citi.umich.edu> +Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +============================== + +Portions of Libevent are based on works by others, also made available by +them under the three-clause BSD license above. The copyright notices are +available in the corresponding source files; the license is as above. Here's +a list: + +log.c: + Copyright (c) 2000 Dug Song <dugsong@monkey.org> + Copyright (c) 1993 The Regents of the University of California. + +strlcpy.c: + Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + +win32select.c: + Copyright (c) 2003 Michael A. Davis <mike@datanerds.net> + +evport.c: + Copyright (c) 2007 Sun Microsystems + +ht-internal.h: + Copyright (c) 2002 Christopher Clark + +minheap-internal.h: + Copyright (c) 2006 Maxim Yegorushkin <maxim.yegorushkin@gmail.com> + +============================== + +The arc4module is available under the following, sometimes called the +"OpenBSD" license: + + Copyright (c) 1996, David Mazieres <dm@uun.org> + Copyright (c) 2008, Damien Miller <djm@openbsd.org> + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +============================== + +The Windows timer code is based on code from libutp, which is +distributed under this license, sometimes called the "MIT" license. + + +Copyright (c) 2010 BitTorrent, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. @@ -1,7 +1,7 @@ MCServer: A performant C++ Minecraft Server www: http://mc-server.org/ -Copyright 2014 MCServer Team +Copyright 2011-2015 MCServer Team ------ diff --git a/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua index b2c7108e9..a7a597fb7 100644 --- a/MCServer/Plugins/APIDump/APIDesc.lua +++ b/MCServer/Plugins/APIDump/APIDesc.lua @@ -685,6 +685,28 @@ end</pre> }, }, -- cCraftingRecipe + cCryptoHash = + { + Desc = + [[ + Provides functions for generating cryptographic hashes.</p> + <p> + Note that all functions in this class are static, so they should be called in the dot convention: +<pre class="prettyprint lang-lua"> +local Hash = cCryptoHash.sha1HexString("DataToHash") +</pre></p> + <p>Each cryptographic hash has two variants, one returns the hash as a raw binary string, the other returns the hash as a hex-encoded string twice as long as the binary string. + ]], + + Functions = + { + md5 = { Params = "Data", Return = "string", Notes = "(STATIC) Calculates the md5 hash of the data, returns it as a raw (binary) string of 16 characters." }, + md5HexString = { Params = "Data", Return = "string", Notes = "(STATIC) Calculates the md5 hash of the data, returns it as a hex-encoded string of 32 characters." }, + sha1 = { Params = "Data", Return = "string", Notes = "(STATIC) Calculates the sha1 hash of the data, returns it as a raw (binary) string of 20 characters." }, + sha1HexString = { Params = "Data", Return = "string", Notes = "(STATIC) Calculates the sha1 hash of the data, returns it as a hex-encoded string of 40 characters." }, + }, + }, -- cCryptoHash + cEnchantments = { Desc = [[ @@ -844,6 +866,7 @@ end</pre> IsMinecart = { Params = "", Return = "bool", Notes = "Returns true if the entity represents a {{cMinecart|minecart}}" }, IsMob = { Params = "", Return = "bool", Notes = "Returns true if the entity represents any {{cMonster|mob}}." }, IsOnFire = { Params = "", Return = "bool", Notes = "Returns true if the entity is on fire" }, + IsOnGround = { Params = "", Return = "bool", Notes = "Returns true if the entity is on ground (not falling, not jumping, not flying)" }, IsPainting = { Params = "", Return = "bool", Notes = "Returns if this entity is a painting." }, IsPawn = { Params = "", Return = "bool", Notes = "Returns true if the entity is a {{cPawn}} descendant." }, IsPickup = { Params = "", Return = "bool", Notes = "Returns true if the entity represents a {{cPickup|pickup}}." }, @@ -902,8 +925,8 @@ end</pre> { Params = "DamageType, AttackerEntity, RawDamage, KnockbackAmount", Return = "", Notes = "Causes this entity to take damage of the specified type, from the specified attacker (may be nil). The final damage is calculated from RawDamage using the currently equipped armor." }, { Params = "DamageType, ArrackerEntity, RawDamage, FinalDamage, KnockbackAmount", Return = "", Notes = "Causes this entity to take damage of the specified type, from the specified attacker (may be nil). The values are wrapped into a {{TakeDamageInfo}} structure and applied directly." }, }, - TeleportToCoords = { Params = "PosX, PosY, PosZ", Return = "", Notes = "Teleports the entity to the specified coords." }, - TeleportToEntity = { Params = "DestEntity", Return = "", Notes = "Teleports this entity to the specified destination entity." }, + TeleportToCoords = { Params = "PosX, PosY, PosZ", Return = "", Notes = "Teleports the entity to the specified coords. Asks plugins if the teleport is allowed." }, + TeleportToEntity = { Params = "DestEntity", Return = "", Notes = "Teleports this entity to the specified destination entity. Asks plugins if the teleport is allowed." }, }, Constants = { @@ -1832,7 +1855,6 @@ a_Player:OpenWindow(Window); IsGameModeSpectator = { Params = "", Return = "bool", Notes = "Returns true if the player is in the gmSpectator gamemode, or has their gamemode unset and the world is a gmSpectator world." }, IsGameModeSurvival = { Params = "", Return = "bool", Notes = "Returns true if the player is in the gmSurvival gamemode, or has their gamemode unset and the world is a gmSurvival world." }, IsInBed = { Params = "", Return = "bool", Notes = "Returns true if the player is currently lying in a bed." }, - IsOnGround = { Params = "", Return = "bool", Notes = "Returns true if the player is on ground (not falling, not jumping, not flying)" }, IsSatiated = { Params = "", Return = "bool", Notes = "Returns true if the player is satiated (cannot eat)." }, IsVisible = { Params = "", Return = "bool", Notes = "Returns true if the player is visible to other players" }, LoadRank = { Params = "", Return = "", Notes = "Reloads the player's rank, message visuals and permissions from the {{cRankManager}}, based on the player's current rank." }, @@ -2232,6 +2254,27 @@ end ShouldAuthenticate = { Params = "", Return = "bool", Notes = "Returns true iff the server is set to authenticate players (\"online mode\")." }, }, }, -- cServer + + cStringCompression = + { + Desc = [[ + Provides functions to compress or decompress string + <p> + All functions in this class are static, so they should be called in the dot convention: +<pre class="prettyprint lang-lua"> +local CompressedString = cStringCompression.CompressStringGZIP("DataToCompress") +</pre> + ]], + + Functions = + { + CompressStringGZIP = {Params = "string", Return = "string", Notes = "Compress a string using GZIP"}, + CompressStringZLIB = {Params = "string, factor", Return = "string", Notes = "Compresses a string using ZLIB. Factor 0 is no compression and factor 9 is maximum compression"}, + InflateString = {Params = "string", Return = "string", Notes = "Uncompresses a string using Inflate"}, + UncompressStringGZIP = {Params = "string", Return = "string", Notes = "Uncompress a string using GZIP"}, + UncompressStringZLIB = {Params = "string, uncompressed length", Return = "string", Notes = "Uncompresses a string using ZLIB"}, + }, + }, cTeam = { @@ -2929,7 +2972,7 @@ end StringToMobType = {Params = "string", Return = "{{Globals#MobType|MobType}}", Notes = "<b>DEPRECATED!</b> Please use cMonster:StringToMobType(). Converts a string representation to a {{Globals#MobType|MobType}} enumerated value"}, StripColorCodes = {Params = "string", Return = "string", Notes = "Removes all control codes used by MC for colors and styles"}, TrimString = {Params = "string", Return = "string", Notes = "Trims whitespace at both ends of the string"}, - md5 = {Params = "string", Return = "string", Notes = "converts a string to an md5 hash"}, + md5 = {Params = "string", Return = "string", Notes = "<b>OBSOLETE</b>, use the {{cCryptoHash}} functions instead.<br>Converts a string to a raw binary md5 hash."}, }, ConstantGroups = { diff --git a/MCServer/Plugins/APIDump/Classes/Network.lua b/MCServer/Plugins/APIDump/Classes/Network.lua new file mode 100644 index 000000000..797788661 --- /dev/null +++ b/MCServer/Plugins/APIDump/Classes/Network.lua @@ -0,0 +1,371 @@ + +-- Network.lua + +-- Defines the documentation for the cNetwork-related classes + + + + + +return +{ + cNetwork = + { + Desc = + [[ + This is the namespace for high-level network-related operations. Allows plugins to make TCP + connections to the outside world using a callback-based API.</p> + <p> + All functions in this namespace are static, they should be called on the cNetwork class itself: +<pre class="prettyprint lang-lua"> +local Server = cNetwork:Listen(1024, ListenCallbacks); +</pre></p> + ]], + AdditionalInfo = + { + { + Header = "Using callbacks", + Contents = + [[ + The entire Networking API is callback-based. Whenever an event happens on the network object, a + specific plugin-provided function is called. The callbacks are stored in tables which are passed + to the API functions, each table contains multiple callbacks for the various situations.</p> + <p> + There are four different callback variants used: LinkCallbacks, LookupCallbacks, ListenCallbacks + and UDPCallbacks. Each is used in the situation appropriate by its name - LinkCallbacks are used + for handling the traffic on a single network link (plus additionally creation of such link when + connecting as a client), LookupCallbacks are used when doing DNS and reverse-DNS lookups, + ListenCallbacks are used for handling incoming connections as a server and UDPCallbacks are used + for incoming UDP datagrams.</p> + <p> + LinkCallbacks have the following structure:<br/> +<pre class="prettyprint lang-lua"> +local LinkCallbacks = +{ + OnConnected = function ({{cTCPLink|a_TCPLink}}) + -- The specified {{cTCPLink|link}} has succeeded in connecting to the remote server. + -- Only called if the link is being connected as a client (using cNetwork:Connect() ) + -- Not used for incoming server links + -- All returned values are ignored + end, + + OnError = function ({{cTCPLink|a_TCPLink}}, a_ErrorCode, a_ErrorMsg) + -- The specified error has occured on the {{cTCPLink|link}} + -- No other callback will be called for this link from now on + -- For a client link being connected, this reports a connection error (destination unreachable etc.) + -- It is an Undefined Behavior to send data to a_TCPLink in or after this callback + -- All returned values are ignored + end, + + OnReceivedData = function ({{cTCPLink|a_TCPLink}}, a_Data) + -- Data has been received on the {{cTCPLink|link}} + -- Will get called whenever there's new data on the {{cTCPLink|link}} + -- a_Data contains the raw received data, as a string + -- All returned values are ignored + end, + + OnRemoteClosed = function ({{cTCPLink|a_TCPLink}}) + -- The remote peer has closed the {{cTCPLink|link}} + -- The link is already closed, any data sent to it now will be lost + -- No other callback will be called for this link from now on + -- All returned values are ignored + end, +} +</pre></p> + <p> + LookupCallbacks have the following structure:<br/> +<pre class="prettyprint lang-lua"> +local LookupCallbacks = +{ + OnError = function (a_Query, a_ErrorCode, a_ErrorMsg) + -- The specified error has occured while doing the lookup + -- a_Query is the hostname or IP being looked up + -- No other callback will be called for this lookup from now on + -- All returned values are ignored + end, + + OnFinished = function (a_Query) + -- There are no more DNS records for this query + -- a_Query is the hostname or IP being looked up + -- No other callback will be called for this lookup from now on + -- All returned values are ignored + end, + + OnNameResolved = function (a_Hostname, a_IP) + -- A DNS record has been found, the specified hostname resolves to the IP + -- Called for both Hostname -> IP and IP -> Hostname lookups + -- May be called multiple times if a hostname resolves to multiple IPs + -- All returned values are ignored + end, +} +</pre></p> + <p> + ListenCallbacks have the following structure:<br/> +<pre class="prettyprint lang-lua"> +local ListenCallbacks = +{ + OnAccepted = function ({{cTCPLink|a_TCPLink}}) + -- A new connection has been accepted and a {{cTCPLink|Link}} for it created + -- It is safe to send data to the link now + -- All returned values are ignored + end, + + OnError = function (a_ErrorCode, a_ErrorMsg) + -- The specified error has occured while trying to listen + -- No other callback will be called for this server handle from now on + -- This callback is called before the cNetwork:Listen() call returns + -- All returned values are ignored + end, + + OnIncomingConnection = function (a_RemoteIP, a_RemotePort, a_LocalPort) + -- A new connection is being accepted, from the specified remote peer + -- This function needs to return either nil to drop the connection, + -- or valid LinkCallbacks to use for the new connection's {{cTCPLink|TCPLink}} object + return SomeLinkCallbacks + end, +} +</pre></p> + <p> + UDPCallbacks have the following structure:<br/> +<pre class="prettyprint lang-lua"> +local UDPCallbacks = +{ + OnError = function (a_ErrorCode, a_ErrorMsg) + -- The specified error has occured when trying to listen for incoming UDP datagrams + -- No other callback will be called for this endpoint from now on + -- This callback is called before the cNetwork:CreateUDPEndpoint() call returns + -- All returned values are ignored + end, + + OnReceivedData = function ({{cUDPEndpoint|a_UDPEndpoint}}, a_Data, a_RemotePeer, a_RemotePort) + -- A datagram has been received on the {{cUDPEndpoint|endpoint}} from the specified remote peer + -- a_Data contains the raw received data, as a string + -- All returned values are ignored + end, +} +</pre> + ]], + }, + + { + Header = "Example client connection", + Contents = + [[ + The following example, adapted from the NetworkTest plugin, shows a simple example of a client + connection using the cNetwork API. It connects to www.google.com on port 80 (http) and sends a http + request for the front page. It dumps the received data to the console.</p> + <p> + First, the callbacks are defined in a table. The OnConnected() callback takes care of sending the http + request once the socket is connected. The OnReceivedData() callback sends all data to the console. The + OnError() callback logs any errors. Then, the connection is initiated using the cNetwork::Connect() API + function.</p> + <p> +<pre class="prettyprint lang-lua"> +-- Define the callbacks to use for the client connection: +local ConnectCallbacks = +{ + OnConnected = function (a_Link) + -- Connection succeeded, send the http request: + a_Link:Send("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n") + end, + + OnError = function (a_Link, a_ErrorCode, a_ErrorMsg) + -- Log the error to console: + LOG("An error has occurred while talking to google.com: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")") + end, + + OnReceivedData = function (a_Link, a_Data) + -- Log the received data to console: + LOG("Incoming http data:\r\n" .. a_Data) + end, + + OnRemoteClosed = function (a_Link) + -- Log the event into the console: + LOG("Connection to www.google.com closed") + end, +} + +-- Connect: +if not(cNetwork:Connect("www.google.com", 80, ConnectCallbacks)) then + -- Highly unlikely, but better check for errors + LOG("Cannot queue connection to www.google.com") +end +-- Note that the connection is being made on the background, +-- there's no guarantee that it's already connected at this point in code +</pre> + ]], + }, + + { + Header = "Example server implementation", + Contents = + [[ + The following example, adapted from the NetworkTest plugin, shows a simple example of creating a + server listening on a TCP port using the cNetwork API. The server listens on port 9876 and for + each incoming connection it sends a welcome message and then echoes back whatever the client has + sent ("echo server").</p> + <p> + First, the callbacks for the listening server are defined. The most important of those is the + OnIncomingConnection() callback that must return the LinkCallbacks that will be used for the new + connection. In our simple scenario each connection uses the same callbacks, so a predefined + callbacks table is returned; it is, however, possible to define different callbacks for each + connection. This allows the callbacks to be "personalised", for example by the remote IP or the + time of connection. The OnAccepted() and OnError() callbacks only log that they occurred, there's + no processing needed for them.</p> + <p> + Finally, the cNetwork:Listen() API function is used to create the listening server. The status of + the server is checked and if it is successfully listening, it is stored in a global variable, so + that Lua doesn't garbage-collect it until we actually want the server closed.</p> + <p> +<pre class="prettyprint lang-lua"> +-- Define the callbacks used for the incoming connections: +local EchoLinkCallbacks = +{ + OnConnected = function (a_Link) + -- This will not be called for a server connection, ever + assert(false, "Unexpected Connect callback call") + end, + + OnError = function (a_Link, a_ErrorCode, a_ErrorMsg) + -- Log the error to console: + local RemoteName = "'" .. a_Link:GetRemoteIP() .. ":" .. a_Link:GetRemotePort() .. "'" + LOG("An error has occurred while talking to " .. RemoteName .. ": " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")") + end, + + OnReceivedData = function (a_Link, a_Data) + -- Send the received data back to the remote peer + a_Link:Send(Data) + end, + + OnRemoteClosed = function (a_Link) + -- Log the event into the console: + local RemoteName = "'" .. a_Link:GetRemoteIP() .. ":" .. a_Link:GetRemotePort() .. "'" + LOG("Connection to '" .. RemoteName .. "' closed") + end, +} + +-- Define the callbacks used by the server: +local ListenCallbacks = +{ + OnAccepted = function (a_Link) + -- No processing needed, just log that this happened: + LOG("OnAccepted callback called") + end, + + OnError = function (a_ErrorCode, a_ErrorMsg) + -- An error has occured while listening for incoming connections, log it: + LOG("Cannot listen, error " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")" + end, + + OnIncomingConnection = function (a_RemoteIP, a_RemotePort, a_LocalPort) + -- A new connection is being accepted, give it the EchoCallbacks + return EchoLinkCallbacks + end, +} + +-- Start the server: +local Server = cNetwork:Listen(9876, ListenCallbacks) +if not(Server:IsListening()) then + -- The error has been already printed in the OnError() callbacks + -- Just bail out + return; +end + +-- Store the server globally, so that it stays open: +g_Server = Server + +... + +-- Elsewhere in the code, when terminating: +-- Close the server and let it be garbage-collected: +g_Server:Close() +g_Server = nil +</pre> + ]], + }, + }, -- AdditionalInfo + + Functions = + { + Connect = { Params = "Host, Port, LinkCallbacks", Return = "bool", Notes = "(STATIC) Begins establishing a (client) TCP connection to the specified host. Uses the LinkCallbacks table to report progress, success, errors and incoming data. Returns false if it fails immediately (bad port value, bad hostname format), true otherwise. Host can be either an IP address or a hostname." }, + CreateUDPEndpoint = { Params = "Port, UDPCallbacks", Return = "{{cUDPEndpoint|UDPEndpoint}}", Notes = "(STATIC) Creates a UDP endpoint that listens for incoming datagrams on the specified port, and can be used to send or broadcast datagrams. Uses the UDPCallbacks to report incoming datagrams or errors. If the endpoint cannot be created, the OnError callback is called with the error details and the returned endpoint will report IsOpen() == false. The plugin needs to store the returned endpoint object for as long as it needs the UDP port open; if the endpoint is garbage-collected by Lua, the socket will be closed and no more incoming data will be reported.<br>If the Port is zero, the OS chooses an available UDP port for the endpoint; use {{cUDPEndpoint}}:GetPort() to query the port number in such case." }, + HostnameToIP = { Params = "Host, LookupCallbacks", Return = "bool", Notes = "(STATIC) Begins a DNS lookup to find the IP address(es) for the specified host. Uses the LookupCallbacks table to report progress, success or errors. Returns false if it fails immediately (bad hostname format), true if the lookup started successfully. Host can be either a hostname or an IP address." }, + IPToHostname = { Params = "Address, LookupCallbacks", Return = "bool", Notes = "(STATIC) Begins a reverse-DNS lookup to find out the hostname for the specified IP address. Uses the LookupCallbacks table to report progress, success or errors. Returns false if it fails immediately (bad address format), true if the lookup started successfully." }, + Listen = { Params = "Port, ListenCallbacks", Return = "{{cServerHandle|ServerHandle}}", Notes = "(STATIC) Starts listening on the specified port. Uses the ListenCallbacks to report incoming connections or errors. Returns a {{cServerHandle}} object representing the server. If the listen operation failed, the OnError callback is called with the error details and the returned server handle will report IsListening() == false. The plugin needs to store the server handle object for as long as it needs the server running, if the server handle is garbage-collected by Lua, the listening socket will be closed and all current connections dropped." }, + }, + }, -- cNetwork + + cServerHandle = + { + Desc = + [[ + This class provides an interface for TCP sockets listening for a connection. In order to listen, the + plugin needs to use the {{cNetwork}}:Listen() function to create the listening socket.</p> + <p> + Note that when Lua garbage-collects this class, the listening socket is closed. Therefore the plugin + should keep it referenced in a global variable for as long as it wants the server running. + ]], + + Functions = + { + Close = { Params = "", Return = "", Notes = "Closes the listening socket. No more connections will be accepted, and all current connections will be closed." }, + IsListening = { Params = "", Return = "bool", Notes = "Returns true if the socket is listening." }, + }, + }, -- cServerHandle + + cTCPLink = + { + Desc = + [[ + This class wraps a single TCP connection, that has been established. Plugins can create these by + calling {{cNetwork}}:Connect() to connect to a remote server, or by listening using + {{cNetwork}}:Listen() and accepting incoming connections. The links are callback-based - when an event + such as incoming data or remote disconnect happens on the link, a specific callback is called. See the + additional information in {{cNetwork}} documentation for details.</p> + <p> + The link can also optionally perform TLS encryption. Plugins can use the StartTLSClient() function to + start the TLS handshake as the client side. Since that call, the OnReceivedData() callback is + overridden internally so that the data is first routed through the TLS decryptor, and the plugin's + callback is only called for the decrypted data, once it starts arriving. The Send function changes its + behavior so that the data written by the plugin is first encrypted and only then sent over the + network. Note that calling Send() before the TLS handshake finishes is supported, but the data is + queued internally and only sent once the TLS handshake is completed. + ]], + + Functions = + { + Close = { Params = "", Return = "", Notes = "Closes the link forcefully (TCP RST). There's no guarantee that the last sent data is even being delivered. See also the Shutdown() method." }, + GetLocalIP = { Params = "", Return = "string", Notes = "Returns the IP address of the local endpoint of the TCP connection." }, + GetLocalPort = { Params = "", Return = "number", Notes = "Returns the port of the local endpoint of the TCP connection." }, + GetRemoteIP = { Params = "", Return = "string", Notes = "Returns the IP address of the remote endpoint of the TCP connection." }, + GetRemotePort = { Params = "", Return = "number", Notes = "Returns the port of the remote endpoint of the TCP connection." }, + Send = { Params = "Data", Return = "", Notes = "Sends the data (raw string) to the remote peer. The data is sent asynchronously and there is no report on the success of the send operation, other than the connection being closed or reset by the underlying OS." }, + Shutdown = { Params = "", Return = "", Notes = "Shuts the socket down for sending data. Notifies the remote peer that there will be no more data coming from us (TCP FIN). The data that is in flight will still be delivered. The underlying socket will be closed when the remote end shuts down as well, or after a timeout." }, + StartTLSClient = { Params = "OwnCert, OwnPrivateKey, OwnPrivateKeyPassword", Return = "", Notes = "Starts a TLS handshake on the link, as a client side of the TLS. The Own___ parameters specify the client certificate and its corresponding private key and password; all three parameters are optional and no client certificate is presented to the remote peer if they are not used or all empty. Once the TLS handshake is started by this call, all incoming data is first decrypted before being sent to the OnReceivedData callback, and all outgoing data is queued until the TLS handshake completes, and then sent encrypted over the link." }, + StartTLSServer = { Params = "Certificate, PrivateKey, PrivateKeyPassword, StartTLSData", Return = "", Notes = "Starts a TLS handshake on the link, as a server side of the TLS. The plugin needs to specify the server certificate and its corresponding private key and password. The StartTLSData can contain data that the link has already reported as received but it should be used as part of the TLS handshake. Once the TLS handshake is started by this call, all incoming data is first decrypted before being sent to the OnReceivedData callback, and all outgoing data is queued until the TLS handshake completes, and then sent encrypted over the link." }, + }, + }, -- cTCPLink + + cUDPEndpoint = + { + Desc = + [[ + Represents a UDP socket that is listening for incoming datagrams on a UDP port and can send or broadcast datagrams to other peers on the network. Plugins can create an instance of the endpoint by calling {{cNetwork}}:CreateUDPEndpoint(). The endpoints are callback-based - when a datagram is read from the network, the OnRececeivedData() callback is called with details about the datagram. See the additional information in {{cNetwork}} documentation for details.</p> + <p> + Note that when Lua garbage-collects this class, the listening socket is closed. Therefore the plugin should keep this object referenced in a global variable for as long as it wants the endpoint open. + ]], + + Functions = + { + Close = { Params = "", Return = "", Notes = "Closes the UDP endpoint. No more datagrams will be reported through the callbacks, the UDP port will be closed." }, + EnableBroadcasts = { Params = "", Return = "", Notes = "Some OSes need this call before they allow UDP broadcasts on an endpoint." }, + GetPort = { Params = "", Return = "number", Notes = "Returns the local port number of the UDP endpoint listening for incoming datagrams. Especially useful if the UDP endpoint was created with auto-assign port (0)." }, + IsOpen = { Params = "", Return = "bool", Notes = "Returns true if the UDP endpoint is listening for incoming datagrams." }, + Send = { Params = "RawData, RemoteHost, RemotePort", Return = "bool", Notes = "Sends the specified raw data (string) to the specified remote host. The RemoteHost can be either a hostname or an IP address; if it is a hostname, the endpoint will queue a DNS lookup first, if it is an IP address, the send operation is executed immediately. Returns true if there was no immediate error, false on any failure. Note that the return value needn't represent whether the packet was actually sent, only if it was successfully queued." }, + }, + }, -- cUDPEndpoint +} + + + + diff --git a/MCServer/Plugins/APIDump/Hooks/OnEntityTeleport.lua b/MCServer/Plugins/APIDump/Hooks/OnEntityTeleport.lua new file mode 100644 index 000000000..cdeb0947f --- /dev/null +++ b/MCServer/Plugins/APIDump/Hooks/OnEntityTeleport.lua @@ -0,0 +1,29 @@ +return +{ + HOOK_ENTITY_TELEPORT = + { + CalledWhen = "Any entity teleports. Plugin may refuse teleport.", + DefaultFnName = "OnEntityTeleport", -- also used as pagename + Desc = [[ + This function is called in each server tick for each {{cEntity|Entity}} that has + teleported. Plugins may refuse the teleport. + ]], + Params = + { + { Name = "Entity", Type = "{{cEntity}}", Notes = "The entity who has teleported. New position is set in the object after successfull teleport" }, + { Name = "OldPosition", Type = "{{Vector3d}}", Notes = "The old position." }, + { Name = "NewPosition", Type = "{{Vector3d}}", Notes = "The new position." }, + }, + Returns = [[ + If the function returns true, teleport is prohibited.</p> + <p> + If the function returns false or no value, other plugins' callbacks are called and finally the new + position is permanently stored in the cEntity object.</p> + ]], + }, -- HOOK_ENTITY_TELEPORT +} + + + + + diff --git a/MCServer/Plugins/Debuggers/Debuggers.lua b/MCServer/Plugins/Debuggers/Debuggers.lua index a047488b5..c8069a411 100644 --- a/MCServer/Plugins/Debuggers/Debuggers.lua +++ b/MCServer/Plugins/Debuggers/Debuggers.lua @@ -25,13 +25,14 @@ function Initialize(Plugin) PM:AddHook(cPluginManager.HOOK_PLAYER_RIGHT_CLICKING_ENTITY, OnPlayerRightClickingEntity); PM:AddHook(cPluginManager.HOOK_WORLD_TICK, OnWorldTick); PM:AddHook(cPluginManager.HOOK_PLUGINS_LOADED, OnPluginsLoaded); - PM:AddHook(cPluginManager.HOOK_PLUGIN_MESSAGE, OnPluginMessage); PM:AddHook(cPluginManager.HOOK_PLAYER_JOINED, OnPlayerJoined); PM:AddHook(cPluginManager.HOOK_PROJECTILE_HIT_BLOCK, OnProjectileHitBlock); PM:AddHook(cPluginManager.HOOK_CHUNK_UNLOADING, OnChunkUnloading); PM:AddHook(cPluginManager.HOOK_WORLD_STARTED, OnWorldStarted); PM:AddHook(cPluginManager.HOOK_PROJECTILE_HIT_BLOCK, OnProjectileHitBlock); + -- _X: Disabled WECUI manipulation: + -- PM:AddHook(cPluginManager.HOOK_PLUGIN_MESSAGE, OnPluginMessage); -- _X: Disabled so that the normal operation doesn't interfere with anything -- PM:AddHook(cPluginManager.HOOK_CHUNK_GENERATED, OnChunkGenerated); @@ -1164,7 +1165,7 @@ function HandleSched(a_Split, a_Player) end -- Schedule a broadcast of the final message and a note to the originating player - -- Note that we CANNOT us the a_Player in the callback - what if the player disconnected? + -- Note that we CANNOT use the a_Player in the callback - what if the player disconnected? -- Therefore we store the player's EntityID local PlayerID = a_Player:GetUniqueID() World:ScheduleTask(220, @@ -1476,7 +1477,7 @@ function HandleWESel(a_Split, a_Player) SelCuboid:Expand(NumBlocks, NumBlocks, 0, 0, NumBlocks, NumBlocks) -- Set the selection: - local IsSuccess = cPluginManager:CallPlugin("WorldEdit", "SetPlayerCuboidSelection", a_Player, SelCuboid) + IsSuccess = cPluginManager:CallPlugin("WorldEdit", "SetPlayerCuboidSelection", a_Player, SelCuboid) if not(IsSuccess) then a_Player:SendMessage(cCompositeChat():SetMessageType(mtFailure):AddTextPart("Cannot adjust selection, WorldEdit reported failure while setting new selection")) return true @@ -1606,14 +1607,36 @@ end -function HandleConsoleSchedule(a_Split) - LOG("Scheduling a task for 2 seconds in the future") - cRoot:Get():GetDefaultWorld():ScheduleTask(40, - function () - LOG("Scheduled function is called.") - end - ) - return true, "Task scheduled" +-- List of hashing functions to test: +local HashFunctions = +{ + {"md5", md5 }, + {"cCryptoHash.md5", cCryptoHash.md5 }, + {"cCryptoHash.md5HexString", cCryptoHash.md5HexString }, + {"cCryptoHash.sha1", cCryptoHash.sha1 }, + {"cCryptoHash.sha1HexString", cCryptoHash.sha1HexString }, +} + +-- List of strings to try hashing: +local HashExamples = +{ + "", + "\0", + "test", +} + +function HandleConsoleHash(a_Split) + for _, str in ipairs(HashExamples) do + LOG("Hashing string \"" .. str .. "\":") + for _, hash in ipairs(HashFunctions) do + if not(hash[2]) then + LOG("Hash function " .. hash[1] .. " doesn't exist in the API!") + else + LOG(hash[1] .. "() = " .. hash[2](str)) + end + end -- for hash - HashFunctions[] + end -- for str - HashExamples[] + return true end @@ -1701,3 +1724,20 @@ end + +function HandleConsoleSchedule(a_Split) + local prev = os.clock() + LOG("Scheduling a task for 2 seconds in the future (current os.clock is " .. prev .. ")") + cRoot:Get():GetDefaultWorld():ScheduleTask(40, + function () + local current = os.clock() + local diff = current - prev + LOG("Scheduled function is called. Current os.clock is " .. current .. ", difference is " .. diff .. ")") + end + ) + return true, "Task scheduled" +end + + + + diff --git a/MCServer/Plugins/Debuggers/Info.lua b/MCServer/Plugins/Debuggers/Info.lua index b96ef3de5..63a4b9177 100644 --- a/MCServer/Plugins/Debuggers/Info.lua +++ b/MCServer/Plugins/Debuggers/Info.lua @@ -200,21 +200,29 @@ g_PluginInfo = ConsoleCommands = { - ["sched"] = + ["hash"] = { - Handler = HandleConsoleSchedule, - HelpString = "Tests the world scheduling", + Handler = HandleConsoleHash, + HelpString = "Tests the crypto hashing functions", }, + ["loadchunk"] = { Handler = HandleConsoleLoadChunk, HelpString = "Loads the specified chunk into memory", }, + ["preparechunk"] = { Handler = HandleConsolePrepareChunk, HelpString = "Prepares the specified chunk completely (load / gen / light)", - } + }, + + ["sched"] = + { + Handler = HandleConsoleSchedule, + HelpString = "Tests the world scheduling", + }, }, -- ConsoleCommands } -- g_PluginInfo diff --git a/MCServer/Plugins/HookNotify/HookNotify.lua b/MCServer/Plugins/HookNotify/HookNotify.lua index ed791090e..1d3d5088e 100644 --- a/MCServer/Plugins/HookNotify/HookNotify.lua +++ b/MCServer/Plugins/HookNotify/HookNotify.lua @@ -23,6 +23,7 @@ function Initialize(Plugin) cPluginManager.AddHook(cPluginManager.HOOK_COLLECTING_PICKUP, OnCollectingPickup); cPluginManager.AddHook(cPluginManager.HOOK_CRAFTING_NO_RECIPE, OnCraftingNoRecipe); cPluginManager.AddHook(cPluginManager.HOOK_DISCONNECT, OnDisconnect); + cPluginManager.AddHook(cPluginManager.HOOK_ENTITY_TELEPORT, OnEntityTeleport); cPluginManager.AddHook(cPluginManager.HOOK_EXECUTE_COMMAND, OnExecuteCommand); cPluginManager.AddHook(cPluginManager.HOOK_HANDSHAKE, OnHandshake); cPluginManager.AddHook(cPluginManager.HOOK_KILLING, OnKilling); @@ -179,6 +180,22 @@ end +function OnEntityTeleport(arg1,arg2,arg3) + if arg1.IsPlayer() then + -- if it's a player, get his name + LOG("OnEntityTeleport: Player: " .. arg1.GetName()); + else + -- if it's a entity, get its type + LOG("OnEntityTeleport: EntityType: " .. arg1.GetEntityType()); + end + LOG("OldPos: " .. arg2.x .. " / " .. arg2.y .. " / " .. arg2.z); + LOG("NewPos: " .. arg3.x .. " / " .. arg3.y .. " / " .. arg3.z); +end + + + + + function OnExecuteCommand(...) LogHook("OnExecuteCommand", unpack(arg)); diff --git a/MCServer/Plugins/InfoReg.lua b/MCServer/Plugins/InfoReg.lua index 92c4a2e59..e34b79564 100644 --- a/MCServer/Plugins/InfoReg.lua +++ b/MCServer/Plugins/InfoReg.lua @@ -87,8 +87,7 @@ local function MultiCommandHandler(a_Split, a_Player, a_CmdString, a_CmdInfo, a_ LOG("Cannot find handler for command " .. a_CmdString .. " " .. Verb); return false; end - MultiCommandHandler(a_Split, a_Player, a_CmdString .. " " .. Verb, Subcommand, a_Level + 1); - return true; + return MultiCommandHandler(a_Split, a_Player, a_CmdString .. " " .. Verb, Subcommand, a_Level + 1); end -- Execute: diff --git a/MCServer/Plugins/NetworkTest/Info.lua b/MCServer/Plugins/NetworkTest/Info.lua new file mode 100644 index 000000000..52422d427 --- /dev/null +++ b/MCServer/Plugins/NetworkTest/Info.lua @@ -0,0 +1,142 @@ + +-- Info.lua + +-- Implements the g_PluginInfo standard plugin description + +g_PluginInfo = +{ + Name = "NetworkTest", + Version = "1", + Date = "2015-01-28", + Description = [[Implements test code (and examples) for the cNetwork API]], + + Commands = + { + }, + + ConsoleCommands = + { + net = + { + Subcommands = + { + client = + { + HelpString = "Connects, as a client, to a specified webpage (google.com by default) and downloads its front page using HTTP", + Handler = HandleConsoleNetClient, + ParameterCombinations = + { + { + Params = "", + Help = "Connects, as a client, to google.com and downloads its front page using HTTP", + }, + { + Params = "host [port]", + Help = "Connects, as a client, to the specified host and downloads its front page using HTTP", + }, + }, -- ParameterCombinations + }, -- client + + close = + { + HelpString = "Close a listening socket", + Handler = HandleConsoleNetClose, + ParameterCombinations = + { + { + Params = "[Port]", + Help = "Closes a socket listening on the specified port [1024]", + }, + }, -- ParameterCombinations + }, -- close + + listen = + { + HelpString = "Creates a new listening socket on the specified port with the specified service attached to it", + Handler = HandleConsoleNetListen, + ParameterCombinations = + { + { + Params = "[Port] [Service]", + Help = "Starts listening on the specified port [1024] providing the specified service [echo]", + }, + }, -- ParameterCombinations + }, -- listen + + lookup = + { + HelpString = "Looks up the IP addresses corresponding to the given hostname (google.com by default)", + Handler = HandleConsoleNetLookup, + ParameterCombinations = + { + { + Params = "", + Help = "Looks up the IP addresses of google.com.", + }, + { + Params = "Hostname", + Help = "Looks up the IP addresses of the specified hostname.", + }, + { + Params = "IP", + Help = "Looks up the canonical name of the specified IP.", + }, + }, + }, -- lookup + + udp = + { + Subcommands = + { + close = + { + Handler = HandleConsoleNetUdpClose, + ParameterCombinations = + { + { + Params = "[Port]", + Help = "Closes the UDP endpoint on the specified port [1024].", + } + }, + }, -- close + + listen = + { + Handler = HandleConsoleNetUdpListen, + ParameterCombinations = + { + { + Params = "[Port]", + Help = "Listens on the specified UDP port [1024], dumping the incoming datagrams into log", + }, + }, + }, -- listen + + send = + { + Handler = HandleConsoleNetUdpSend, + ParameterCombinations = + { + { + Params = "[Host] [Port] [Message]", + Help = "Sends the message [\"hello\"] through UDP to the specified host [localhost] and port [1024]", + }, + }, + } -- send + }, -- Subcommands ("net udp") + }, -- udp + + wasc = + { + HelpString = "Requests the webadmin homepage using https", + Handler = HandleConsoleNetWasc, + }, -- wasc + + }, -- Subcommands + }, -- net + }, +} + + + + diff --git a/MCServer/Plugins/NetworkTest/NetworkTest.lua b/MCServer/Plugins/NetworkTest/NetworkTest.lua new file mode 100644 index 000000000..daab0a4bf --- /dev/null +++ b/MCServer/Plugins/NetworkTest/NetworkTest.lua @@ -0,0 +1,490 @@ + +-- NetworkTest.lua + +-- Implements a few tests for the cNetwork API + + + + + +--- Map of all servers currently open +-- g_Servers[PortNum] = cServerHandle +local g_Servers = {} + +--- Map of all UDP endpoints currently open +-- g_UDPEndpoints[PortNum] = cUDPEndpoint +local g_UDPEndpoints = {} + +--- List of fortune messages for the fortune server +-- A random message is chosen for each incoming connection +-- The contents are loaded from the splashes.txt file on plugin startup +local g_Fortunes = +{ + "Empty splashes.txt", +} + +-- HTTPS certificate to be used for the SSL server: +local g_HTTPSCert = [[ +-----BEGIN CERTIFICATE----- +MIIDfzCCAmegAwIBAgIJAOBHN+qOWodcMA0GCSqGSIb3DQEBBQUAMFYxCzAJBgNV +BAYTAmN6MQswCQYDVQQIDAJjejEMMAoGA1UEBwwDbG9jMQswCQYDVQQKDAJfWDEL +MAkGA1UECwwCT1UxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNTAxMjQwODQ2MzFa +Fw0yNTAxMjEwODQ2MzFaMFYxCzAJBgNVBAYTAmN6MQswCQYDVQQIDAJjejEMMAoG +A1UEBwwDbG9jMQswCQYDVQQKDAJfWDELMAkGA1UECwwCT1UxEjAQBgNVBAMMCWxv +Y2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJkFYSElu/jw +nxqjimmj246DejKJK8uy/l9QQibb/Z4kO/3s0gVPOYo0mKv32xUFP7wYIE3XWT61 +zyfvK+1jpnlQTCtM8T5xw/7CULKgLmuIzlQx5Dhy7d+tW46kOjFKwQajS9YzwqWu +KBOPnFamQWz6vIzuM05+7aIMXbzamInvW/1x3klIrpGQgALwSB1N+oUzTInTBRKK +21pecUE9t3qrU40Cs5bN0fQBnBjLwbgmnTh6LEplfQZHG5wLvj0IeERVU9vH7luM +e9/IxuEZluCiu5ViF3jqLPpjYOrkX7JDSKme64CCmNIf0KkrwtFjF104Qylike60 +YD3+kw8Q+DECAwEAAaNQME4wHQYDVR0OBBYEFHHIDTc7mrLDXftjQ5ejU9Udfdyo +MB8GA1UdIwQYMBaAFHHIDTc7mrLDXftjQ5ejU9UdfdyoMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAHxCJxZPmH9tvx8GKiDV3rgGY++sMItzrW5Uhf0/ +bl3DPbVz51CYF8nXiWvSJJzxhH61hKpZiqvRlpyMuovV415dYQ+Xc2d2IrTX6e+d +Z4Pmwfb4yaX+kYqIygjXMoyNxOJyhTnCbJzycV3v5tvncBWN9Wqez6ZonWDdFdAm +J+Moty+atc4afT02sUg1xz+CDr1uMbt62tHwKYCdxXCwT//bOs6W21+mQJ5bEAyA +YrHQPgX76uo8ed8rPf6y8Qj//lzq/+33EIWqf9pnbklQgIPXJU07h+5L+Y63RF4A +ComLkzas+qnQLcEN28Dg8QElXop6hfiD0xq3K0ac2bDnjoU= +-----END CERTIFICATE----- +]] + +local g_HTTPSPrivKey = [[ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCZBWEhJbv48J8a +o4ppo9uOg3oyiSvLsv5fUEIm2/2eJDv97NIFTzmKNJir99sVBT+8GCBN11k+tc8n +7yvtY6Z5UEwrTPE+ccP+wlCyoC5riM5UMeQ4cu3frVuOpDoxSsEGo0vWM8KlrigT +j5xWpkFs+ryM7jNOfu2iDF282piJ71v9cd5JSK6RkIAC8EgdTfqFM0yJ0wUSitta +XnFBPbd6q1ONArOWzdH0AZwYy8G4Jp04eixKZX0GRxucC749CHhEVVPbx+5bjHvf +yMbhGZbgoruVYhd46iz6Y2Dq5F+yQ0ipnuuAgpjSH9CpK8LRYxddOEMpYpHutGA9 +/pMPEPgxAgMBAAECggEAWxQ4m+I54BJYoSJ2YCqHpGvdb/b1emkvvsumlDqc2mP2 +0U0ENOTS+tATj0gXvotBRFOX5r0nAYx1oO9a1hFaJRsGOz+w19ofLqO6JJfzCU6E +gNixXmgJ7fjhZiWZ/XzhJ3JK0VQ9px/h+sKf63NJvfQABmJBZ5dlGe8CXEZARNin +03TnE3RUIEK+jEgwShN2OrGjwK9fjcnXMHwEnKZtCBiYEfD2N+pQmS20gIm13L1t ++ZmObIC24NqllXxl4I821qzBdhmcT7+rGmKR0OT5YKbt6wFA5FPKD9dqlzXzlKck +r2VAh+JlCtFKxcScmWtQOnVDtf5+mcKFbP4ck724AQKBgQDLk+RDhvE5ykin5R+B +dehUQZgHb2pPL7N1DAZShfzwSmyZSOPQDFr7c0CMijn6G0Pw9VX6Vrln0crfTQYz +Hli+zxlmcMAD/WC6VImM1LCUzouNRy37rSCnuPtngZyHdsyzfheGnjORH7HlPjtY +JCTLaekg0ckQvt//HpRV3DCdaQKBgQDAbLmIOTyGfne74HLswWnY/kCOfFt6eO+E +lZ724MWmVPWkxq+9rltC2CDx2i8jjdkm90dsgR5OG2EaLnUWldUpkE0zH0ATrZSV +ezJWD9SsxTm8ksbThD+pJKAVPxDAboejF7kPvpaO2wY+bf0AbO3M24rJ2tccpMv8 +AcfXBICDiQKBgQCSxp81/I3hf7HgszaC7ZLDZMOK4M6CJz847aGFUCtsyAwCfGYb +8zyJvK/WZDam14+lpA0IQAzPCJg/ZVZJ9uA/OivzCum2NrHNxfOiQRrLPxuokaBa +q5k2tA02tGE53fJ6mze1DEzbnkFxqeu5gd2xdzvpOLfBxgzT8KU8PlQiuQKBgGn5 +NvCj/QZhDhYFVaW4G1ArLmiKamL3yYluUV7LiW7CaYp29gBzzsTwfKxVqhJdo5NH +KinCrmr7vy2JGmj22a+LTkjyU/rCZQsyDxXAoDMKZ3LILwH8WocPqa4pzlL8TGzw +urXGE+rXCwhE0Mp0Mz7YRgZHJKMcy06duG5dh11pAoGBALHbsBIDihgHPyp2eKMP +K1f42MdKrTBiIXV80hv2OnvWVRCYvnhrqpeRMzCR1pmVbh+QhnwIMAdWq9PAVTTn +ypusoEsG8Y5fx8xhgjs0D2yMcrmi0L0kCgHIFNoym+4pI+sv6GgxpemfrmaPNcMx +DXi9JpaquFRJLGJ7jMCDgotL +-----END PRIVATE KEY----- +]] + +--- Map of all services that can be run as servers +-- g_Services[ServiceName] = function() -> accept-callbacks +local g_Services = +{ + -- Echo service: each connection echoes back what has been sent to it + echo = function (a_Port) + return + { + -- A new connection has come, give it new link-callbacks: + OnIncomingConnection = function (a_RemoteIP, a_RemotePort) + return + { + OnError = function (a_Link, a_ErrorCode, a_ErrorMsg) + LOG("EchoServer(" .. a_Port .. ": Connection to " .. a_Link:GetRemoteIP() .. ":" .. a_Link:GetRemotePort() .. " failed: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")") + end, + + OnReceivedData = function (a_Link, a_Data) + -- Echo the received data back to the link: + a_Link:Send(a_Data) + end, + + OnRemoteClosed = function (a_Link) + end + } -- Link callbacks + end, -- OnIncomingConnection() + + -- Send a welcome message to newly accepted connections: + OnAccepted = function (a_Link) + a_Link:Send("Hello, " .. a_Link:GetRemoteIP() .. ", welcome to the echo server @ MCServer-Lua\r\n") + end, -- OnAccepted() + + -- There was an error listening on the port: + OnError = function (a_ErrorCode, a_ErrorMsg) + LOGINFO("EchoServer(" .. a_Port .. ": Cannot listen: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")") + end, -- OnError() + } -- Listen callbacks + end, -- echo + + -- Fortune service: each incoming connection gets a welcome message plus a random fortune text; all communication is ignored afterwards + fortune = function (a_Port) + return + { + -- A new connection has come, give it new link-callbacks: + OnIncomingConnection = function (a_RemoteIP, a_RemotePort) + return + { + OnError = function (a_Link, a_ErrorCode, a_ErrorMsg) + LOG("FortuneServer(" .. a_Port .. "): Connection to " .. a_Link:GetRemoteIP() .. ":" .. a_Link:GetRemotePort() .. " failed: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")") + end, + + OnReceivedData = function (a_Link, a_Data) + -- Ignore any received data + end, + + OnRemoteClosed = function (a_Link) + end + } -- Link callbacks + end, -- OnIncomingConnection() + + -- Send a welcome message and the fortune to newly accepted connections: + OnAccepted = function (a_Link) + a_Link:Send("Hello, " .. a_Link:GetRemoteIP() .. ", welcome to the fortune server @ MCServer-Lua\r\n\r\nYour fortune:\r\n") + a_Link:Send(g_Fortunes[math.random(#g_Fortunes)] .. "\r\n") + end, -- OnAccepted() + + -- There was an error listening on the port: + OnError = function (a_ErrorCode, a_ErrorMsg) + LOGINFO("FortuneServer(" .. a_Port .. "): Cannot listen: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")") + end, -- OnError() + } -- Listen callbacks + end, -- fortune + + -- HTTPS time - serves current time for each https request received + httpstime = function (a_Port) + return + { + -- A new connection has come, give it new link-callbacks: + OnIncomingConnection = function (a_RemoteIP, a_RemotePort) + local IncomingData = "" -- accumulator for the incoming data, until processed by the http + return + { + OnError = function (a_Link, a_ErrorCode, a_ErrorMsg) + LOG("https-time server(" .. a_Port .. "): Connection to " .. a_Link:GetRemoteIP() .. ":" .. a_Link:GetRemotePort() .. " failed: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")") + end, + + OnReceivedData = function (a_Link, a_Data) + IncomingData = IncomingData .. a_Data + if (IncomingData:find("\r\n\r\n")) then + -- We have received the entire request headers, just send the response and shutdown the link: + local Content = os.date() + a_Link:Send("HTTP/1.0 200 OK\r\nContent-type: text/plain\r\nContent-length: " .. #Content .. "\r\n\r\n" .. Content) + a_Link:Shutdown() + end + end, + + OnRemoteClosed = function (a_Link) + LOG("httpstime: link closed by remote") + end + } -- Link callbacks + end, -- OnIncomingConnection() + + -- Start TLS on the new link: + OnAccepted = function (a_Link) + local res, msg = a_Link:StartTLSServer(g_HTTPSCert, g_HTTPSPrivKey, "") + if not(res) then + LOG("https-time server(" .. a_Port .. "): Cannot start TLS server: " .. msg) + a_Link:Close() + end + end, -- OnAccepted() + + -- There was an error listening on the port: + OnError = function (a_ErrorCode, a_ErrorMsg) + LOGINFO("https-time server(" .. a_Port .. "): Cannot listen: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")") + end, -- OnError() + } -- Listen callbacks + end, -- httpstime + + -- TODO: Other services (daytime, ...) +} + + + + + +function Initialize(a_Plugin) + -- Load the splashes.txt file into g_Fortunes, overwriting current content: + local idx = 1 + for line in io.lines(a_Plugin:GetLocalFolder() .. "/splashes.txt") do + g_Fortunes[idx] = line + idx = idx + 1 + end + + -- Use the InfoReg shared library to process the Info.lua file: + dofile(cPluginManager:GetPluginsPath() .. "/InfoReg.lua") + RegisterPluginInfoCommands() + RegisterPluginInfoConsoleCommands() + + -- Seed the random generator: + math.randomseed(os.time()) + + return true +end + + + + + +function HandleConsoleNetClient(a_Split) + -- Get the address to connect to: + local Host = a_Split[3] or "google.com" + local Port = a_Split[4] or 80 + + -- Create the callbacks "personalised" for the address: + local Callbacks = + { + OnConnected = function (a_Link) + LOG("Connected to " .. Host .. ":" .. Port .. ".") + LOG("Connection stats: Remote address: " .. a_Link:GetRemoteIP() .. ":" .. a_Link:GetRemotePort() .. ", Local address: " .. a_Link:GetLocalIP() .. ":" .. a_Link:GetLocalPort()) + LOG("Sending HTTP request for front page.") + a_Link:Send("GET / HTTP/1.0\r\nHost: " .. Host .. "\r\n\r\n") + end, + + OnError = function (a_Link, a_ErrorCode, a_ErrorMsg) + LOG("Connection to " .. Host .. ":" .. Port .. " failed: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")") + end, + + OnReceivedData = function (a_Link, a_Data) + LOG("Received data from " .. Host .. ":" .. Port .. ":\r\n" .. a_Data) + end, + + OnRemoteClosed = function (a_Link) + LOG("Connection to " .. Host .. ":" .. Port .. " was closed by the remote peer.") + end + } + + -- Queue a connect request: + local res = cNetwork:Connect(Host, Port, Callbacks) + if not(res) then + LOGWARNING("cNetwork:Connect call failed immediately") + return true + end + + return true, "Client connection request queued." +end + + + + + +function HandleConsoleNetClose(a_Split) + -- Get the port to close: + local Port = tonumber(a_Split[3] or 1024) + if not(Port) then + return true, "Bad port number: \"" .. a_Split[3] .. "\"." + end + + -- Close the server, if there is one: + if not(g_Servers[Port]) then + return true, "There is no server currently listening on port " .. Port .. "." + end + g_Servers[Port]:Close() + g_Servers[Port] = nil + return true, "Port " .. Port .. " closed." +end + + + + + +function HandleConsoleNetLookup(a_Split) + -- Get the name to look up: + local Addr = a_Split[3] or "google.com" + + -- Create the callbacks "personalised" for the host: + local Callbacks = + { + OnNameResolved = function (a_Hostname, a_IP) + LOG(a_Hostname .. " resolves to " .. a_IP) + end, + + OnError = function (a_Query, a_ErrorCode, a_ErrorMsg) + LOG("Failed to retrieve information for " .. a_Query .. ": " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")") + assert(a_Query == Addr) + end, + + OnFinished = function (a_Query) + LOG("Resolving " .. a_Query .. " has finished.") + assert(a_Query == Addr) + end, + } + + -- Queue both name and IP DNS queries; + -- we don't distinguish between an IP and a hostname in this command so we don't know which one to use: + local res = cNetwork:HostnameToIP(Addr, Callbacks) + if not(res) then + LOGWARNING("cNetwork:HostnameToIP call failed immediately") + return true + end + res = cNetwork:IPToHostname(Addr, Callbacks) + if not(res) then + LOGWARNING("cNetwork:IPToHostname call failed immediately") + return true + end + + return true, "DNS query has been queued." +end + + + + + +function HandleConsoleNetListen(a_Split) + -- Get the params: + local Port = tonumber(a_Split[3] or 1024) + if not(Port) then + return true, "Invalid port: \"" .. a_Split[3] .. "\"." + end + local Service = string.lower(a_Split[4] or "echo") + + -- Create the callbacks specific for the service: + if (g_Services[Service] == nil) then + return true, "No such service: " .. Service + end + local Callbacks = g_Services[Service](Port) + + -- Start the server: + local srv = cNetwork:Listen(Port, Callbacks) + if not(srv:IsListening()) then + -- The error message has already been printed in the Callbacks.OnError() + return true + end + g_Servers[Port] = srv + return true, Service .. " server started on port " .. Port +end + + + + + +function HandleConsoleNetUdpClose(a_Split) + -- Get the port to close: + local Port = tonumber(a_Split[4] or 1024) + if not(Port) then + return true, "Bad port number: \"" .. a_Split[4] .. "\"." + end + + -- Close the server, if there is one: + if not(g_UDPEndpoints[Port]) then + return true, "There is no UDP endpoint currently listening on port " .. Port .. "." + end + g_UDPEndpoints[Port]:Close() + g_UDPEndpoints[Port] = nil + return true, "UDP Port " .. Port .. " closed." +end + + + + + +function HandleConsoleNetUdpListen(a_Split) + -- Get the params: + local Port = tonumber(a_Split[4] or 1024) + if not(Port) then + return true, "Invalid port: \"" .. a_Split[4] .. "\"." + end + + local Callbacks = + { + OnReceivedData = function (a_Endpoint, a_Data, a_RemotePeer, a_RemotePort) + LOG("Incoming UDP datagram from " .. a_RemotePeer .. " port " .. a_RemotePort .. ":\r\n" .. a_Data) + end, + + OnError = function (a_Endpoint, a_ErrorCode, a_ErrorMsg) + LOG("Error in UDP endpoint: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")") + end, + } + + g_UDPEndpoints[Port] = cNetwork:CreateUDPEndpoint(Port, Callbacks) + return true, "UDP listener on port " .. Port .. " started." +end + + + + + +function HandleConsoleNetUdpSend(a_Split) + -- Get the params: + local Host = a_Split[4] or "localhost" + local Port = tonumber(a_Split[5] or 1024) + if not(Port) then + return true, "Invalid port: \"" .. a_Split[5] .. "\"." + end + local Message + if (a_Split[6]) then + Message = table.concat(a_Split, " ", 6) + else + Message = "hello" + end + + -- Define minimum callbacks for the UDP endpoint: + local Callbacks = + { + OnError = function (a_Endpoint, a_ErrorCode, a_ErrorMsg) + LOG("Error in UDP datagram sending: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")") + end, + + OnReceivedData = function () + -- ignore + end, + } + + -- Send the data: + local Endpoint = cNetwork:CreateUDPEndpoint(0, Callbacks) + Endpoint:EnableBroadcasts() + if not(Endpoint:Send(Message, Host, Port)) then + Endpoint:Close() + return true, "Sending UDP datagram failed" + end + Endpoint:Close() + return true, "UDP datagram sent" +end + + + + + +function HandleConsoleNetWasc(a_Split) + local Callbacks = + { + OnConnected = function (a_Link) + LOG("Connected to webadmin, starting TLS...") + local res, msg = a_Link:StartTLSClient("", "", "") + if not(res) then + LOG("Failed to start TLS client: " .. msg) + return + end + -- We need to send a keep-alive due to #1737 + a_Link:Send("GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n") + end, + + OnError = function (a_Link, a_ErrorCode, a_ErrorMsg) + LOG("Connection to webadmin failed: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")") + end, + + OnReceivedData = function (a_Link, a_Data) + LOG("Received data from webadmin:\r\n" .. a_Data) + + -- Close the link once all the data is received: + if (a_Data == "0\r\n\r\n") then -- Poor man's end-of-data detection; works on localhost + -- TODO: The Close() method is not yet exported to Lua + -- a_Link:Close() + end + end, + + OnRemoteClosed = function (a_Link) + LOG("Connection to webadmin was closed") + end, + } + + if not(cNetwork:Connect("localhost", "8080", Callbacks)) then + LOG("Canot connect to webadmin") + end + + return true +end + + + + diff --git a/MCServer/Plugins/NetworkTest/splashes.txt b/MCServer/Plugins/NetworkTest/splashes.txt new file mode 100644 index 000000000..50a87bd91 --- /dev/null +++ b/MCServer/Plugins/NetworkTest/splashes.txt @@ -0,0 +1,357 @@ +As seen on TV! +Awesome! +100% pure! +May contain nuts! +Better than Prey! +More polygons! +Sexy! +Limited edition! +Flashing letters! +Made by Notch! +It's here! +Best in class! +It's finished! +Kind of dragon free! +Excitement! +More than 500 sold! +One of a kind! +Heaps of hits on YouTube! +Indev! +Spiders everywhere! +Check it out! +Holy cow, man! +It's a game! +Made in Sweden! +Uses LWJGL! +Reticulating splines! +Minecraft! +Yaaay! +Singleplayer! +Keyboard compatible! +Undocumented! +Ingots! +Exploding creepers! +That's no moon! +l33t! +Create! +Survive! +Dungeon! +Exclusive! +The bee's knees! +Down with O.P.P.! +Closed source! +Classy! +Wow! +Not on steam! +Oh man! +Awesome community! +Pixels! +Teetsuuuuoooo! +Kaaneeeedaaaa! +Now with difficulty! +Enhanced! +90% bug free! +Pretty! +12 herbs and spices! +Fat free! +Absolutely no memes! +Free dental! +Ask your doctor! +Minors welcome! +Cloud computing! +Legal in Finland! +Hard to label! +Technically good! +Bringing home the bacon! +Indie! +GOTY! +Ceci n'est pas une title screen! +Euclidian! +Now in 3D! +Inspirational! +Herregud! +Complex cellular automata! +Yes, sir! +Played by cowboys! +OpenGL 2.1 (if supported)! +Thousands of colors! +Try it! +Age of Wonders is better! +Try the mushroom stew! +Sensational! +Hot tamale, hot hot tamale! +Play him off, keyboard cat! +Guaranteed! +Macroscopic! +Bring it on! +Random splash! +Call your mother! +Monster infighting! +Loved by millions! +Ultimate edition! +Freaky! +You've got a brand new key! +Water proof! +Uninflammable! +Whoa, dude! +All inclusive! +Tell your friends! +NP is not in P! +Notch <3 ez! +Music by C418! +Livestreamed! +Haunted! +Polynomial! +Terrestrial! +All is full of love! +Full of stars! +Scientific! +Cooler than Spock! +Collaborate and listen! +Never dig down! +Take frequent breaks! +Not linear! +Han shot first! +Nice to meet you! +Buckets of lava! +Ride the pig! +Larger than Earth! +sqrt(-1) love you! +Phobos anomaly! +Punching wood! +Falling off cliffs! +0% sugar! +150% hyperbole! +Synecdoche! +Let's danec! +Seecret Friday update! +Reference implementation! +Lewd with two dudes with food! +Kiss the sky! +20 GOTO 10! +Verlet intregration! +Peter Griffin! +Do not distribute! +Cogito ergo sum! +4815162342 lines of code! +A skeleton popped out! +The Work of Notch! +The sum of its parts! +BTAF used to be good! +I miss ADOM! +umop-apisdn! +OICU812! +Bring me Ray Cokes! +Finger-licking! +Thematic! +Pneumatic! +Sublime! +Octagonal! +Une baguette! +Gargamel plays it! +Rita is the new top dog! +SWM forever! +Representing Edsbyn! +Matt Damon! +Supercalifragilisticexpialidocious! +Consummate V's! +Cow Tools! +Double buffered! +Fan fiction! +Flaxkikare! +Jason! Jason! Jason! +Hotter than the sun! +Internet enabled! +Autonomous! +Engage! +Fantasy! +DRR! DRR! DRR! +Kick it root down! +Regional resources! +Woo, facepunch! +Woo, somethingawful! +Woo, /v/! +Woo, tigsource! +Woo, minecraftforum! +Woo, worldofminecraft! +Woo, reddit! +Woo, 2pp! +Google anlyticsed! +Now supports åäö! +Give us Gordon! +Tip your waiter! +Very fun! +12345 is a bad password! +Vote for net neutrality! +Lives in a pineapple under the sea! +MAP11 has two names! +Omnipotent! +Gasp! +...! +Bees, bees, bees, bees! +Jag känner en bot! +This text is hard to read if you play the game at the default resolution, but at 1080p it's fine! +Haha, LOL! +Hampsterdance! +Switches and ores! +Menger sponge! +idspispopd! +Eple (original edit)! +So fresh, so clean! +Slow acting portals! +Try the Nether! +Don't look directly at the bugs! +Oh, ok, Pigmen! +Finally with ladders! +Scary! +Play Minecraft, Watch Topgear, Get Pig! +Twittered about! +Jump up, jump up, and get down! +Joel is neat! +A riddle, wrapped in a mystery! +Huge tracts of land! +Welcome to your Doom! +Stay a while, stay forever! +Stay a while and listen! +Treatment for your rash! +"Autological" is! +Information wants to be free! +"Almost never" is an interesting concept! +Lots of truthiness! +The creeper is a spy! +Turing complete! +It's groundbreaking! +Let our battle's begin! +The sky is the limit! +Jeb has amazing hair! +Ryan also has amazing hair! +Casual gaming! +Undefeated! +Kinda like Lemmings! +Follow the train, CJ! +Leveraging synergy! +This message will never appear on the splash screen, isn't that weird? +DungeonQuest is unfair! +110813! +90210! +Check out the far lands! +Tyrion would love it! +Also try VVVVVV! +Also try Super Meat Boy! +Also try Terraria! +Also try Mount And Blade! +Also try Project Zomboid! +Also try World of Goo! +Also try Limbo! +Also try Pixeljunk Shooter! +Also try Braid! +That's super! +Bread is pain! +Read more books! +Khaaaaaaaaan! +Less addictive than TV Tropes! +More addictive than lemonade! +Bigger than a bread box! +Millions of peaches! +Fnord! +This is my true form! +Totally forgot about Dre! +Don't bother with the clones! +Pumpkinhead! +Hobo humping slobo babe! +Made by Jeb! +Has an ending! +Finally complete! +Feature packed! +Boots with the fur! +Stop, hammertime! +Testificates! +Conventional! +Homeomorphic to a 3-sphere! +Doesn't avoid double negatives! +Place ALL the blocks! +Does barrel rolls! +Meeting expectations! +PC gaming since 1873! +Ghoughpteighbteau tchoghs! +Déjà vu! +Déjà vu! +Got your nose! +Haley loves Elan! +Afraid of the big, black bat! +Doesn't use the U-word! +Child's play! +See you next Friday or so! +From the streets of Södermalm! +150 bpm for 400000 minutes! +Technologic! +Funk soul brother! +Pumpa kungen! +日本ハロー! +한국 안녕하세요! +Helo Cymru! +Cześć Polsko! +你好中国! +Привет Россия! +Γεια σου Ελλάδα! +My life for Aiur! +Lennart lennart = new Lennart(); +I see your vocabulary has improved! +Who put it there? +You can't explain that! +if not ok then return end +§1C§2o§3l§4o§5r§6m§7a§8t§9i§ac +§kFUNKY LOL +SOPA means LOSER in Swedish! +Big Pointy Teeth! +Bekarton guards the gate! +Mmmph, mmph! +Don't feed avocados to parrots! +Swords for everyone! +Plz reply to my tweet! +.party()! +Take her pillow! +Put that cookie down! +Pretty scary! +I have a suggestion. +Now with extra hugs! +Now Java 6! +Woah. +HURNERJSGER? +What's up, Doc? +Now contains 32 random daily cats! +That's Numberwang! +pls rt +Do you want to join my server? +Put a little fence around it! +Throw a blanket over it! +One day, somewhere in the future, my work will be quoted! +Now with additional stuff! +Extra things! +Yay, puppies for everyone! +So sweet, like a nice bon bon! +Popping tags! +Very influential in its circle! +Now With Multiplayer! +Rise from your grave! +Warning! A huge battleship "STEVE" is approaching fast! +Blue warrior shot the food! +Run, coward! I hunger! +Flavor with no seasoning! +Strange, but not a stranger! +Tougher than diamonds, rich like cream! +Getting ready to show! +Getting ready to know! +Getting ready to drop! +Getting ready to shock! +Getting ready to freak! +Getting ready to speak! +It swings, it jives! +Cruising streets for gold! +Take an eggbeater and beat it against a skillet! +Make me a table, a funky table! +Take the elevator to the mezzanine! +Stop being reasonable, this is the Internet! +/give @a hugs 64 +This is good for Realms. +Any computer is a laptop if you're brave enough!
\ No newline at end of file diff --git a/MCServer/webadmin/GenerateSelfSignedHTTPSCertUsingOpenssl.cmd b/MCServer/webadmin/GenerateSelfSignedHTTPSCertUsingOpenssl.cmd index 3ea6963b4..7022ef12a 100644 --- a/MCServer/webadmin/GenerateSelfSignedHTTPSCertUsingOpenssl.cmd +++ b/MCServer/webadmin/GenerateSelfSignedHTTPSCertUsingOpenssl.cmd @@ -1,3 +1,4 @@ +@echo off echo This script generates the certificate and private key for the https webadmin echo Note that the generated certificate is self-signed, and therefore not trusted by browsers echo Note that this script requires openssl to be installed and in PATH diff --git a/MCServer/webadmin/files/guest.html b/MCServer/webadmin/files/guest.html index 7ae78a3f0..4f965b75c 100644 --- a/MCServer/webadmin/files/guest.html +++ b/MCServer/webadmin/files/guest.html @@ -1,2 +1,18 @@ -Hello! Welcome to the MCServer WebAdmin.<br> -This is a default message, edit <b>files/guest.html</b> to add your own custom message. +<!DOCTYPE html> +<html lang="en"> + + <head> + <meta charset="UTF-8" /> + <title>Guest Information</title> + </head> + + <body> + <p> + Hello! Welcome to the MCServer WebAdmin. + </p> + <p> + This is a default message, edit <b>files/guest.html</b> to add your own custom message. + </p> + </body> + +</html> diff --git a/MCServer/webadmin/login_template.html b/MCServer/webadmin/login_template.html index 7a8601065..26b260bd3 100644 --- a/MCServer/webadmin/login_template.html +++ b/MCServer/webadmin/login_template.html @@ -17,7 +17,7 @@ <div class="upper"> <div class="wrapper"> <div> - <form method="get" action="webadmin/" /> + <form method="get" action="webadmin/"> <button type="submit" value="Log in" style="width:150px;height:25px;font-family:'Source Sans Pro',sans-serif;background:transparent;border:none!important;vertical-align:middle"> <strong><img src="login.gif" style="vertical-align:bottom" /> WebAdmin Log in</strong> </button> diff --git a/Tools/ProtoProxy/Server.cpp b/Tools/ProtoProxy/Server.cpp index 9545af852..5bba98057 100644 --- a/Tools/ProtoProxy/Server.cpp +++ b/Tools/ProtoProxy/Server.cpp @@ -33,7 +33,7 @@ int cServer::Init(short a_ListenPort, short a_ConnectPort) } #endif // _WIN32 - printf("Generating protocol encryption keypair...\n"); + LOGINFO("Generating protocol encryption keypair..."); m_PrivateKey.Generate(); m_PublicKeyDER = m_PrivateKey.GetPubKeyDER(); @@ -85,7 +85,7 @@ int cServer::Init(short a_ListenPort, short a_ConnectPort) void cServer::Run(void) { - printf("Server running.\n"); + LOGINFO("Server running."); while (true) { sockaddr_in Addr; @@ -97,10 +97,10 @@ void cServer::Run(void) printf("accept returned an error: %d; bailing out.\n", SocketError); return; } - printf("Client connected, proxying...\n"); + LOGINFO("Client connected, proxying..."); cConnection Connection(client, *this); Connection.Run(); - printf("Client disconnected. Ready for another connection.\n"); + LOGINFO("Client disconnected. Ready for another connection."); } } diff --git a/Tools/RCONClient/Globals.h b/Tools/RCONClient/Globals.h index a3a2f2846..dc7669270 100644 --- a/Tools/RCONClient/Globals.h +++ b/Tools/RCONClient/Globals.h @@ -22,6 +22,18 @@ #define ALIGN_8 #define ALIGN_16 + #define FORMATSTRING(formatIndex, va_argsIndex) + + // MSVC has its own custom version of zu format + #define SIZE_T_FMT "%Iu" + #define SIZE_T_FMT_PRECISION(x) "%" #x "Iu" + #define SIZE_T_FMT_HEX "%Ix" + + #define NORETURN __declspec(noreturn) + + // Use non-standard defines in <cmath> + #define _USE_MATH_DEFINES + #elif defined(__GNUC__) // TODO: Can GCC explicitly mark classes as abstract (no instances can be created)? @@ -38,6 +50,29 @@ // Some portability macros :) #define stricmp strcasecmp + #define FORMATSTRING(formatIndex, va_argsIndex) __attribute__((format (printf, formatIndex, va_argsIndex))) + + #if defined(_WIN32) + // We're compiling on MinGW, which uses an old MSVCRT library that has no support for size_t printfing. + // We need direct size formats: + #if defined(_WIN64) + #define SIZE_T_FMT "%I64u" + #define SIZE_T_FMT_PRECISION(x) "%" #x "I64u" + #define SIZE_T_FMT_HEX "%I64x" + #else + #define SIZE_T_FMT "%u" + #define SIZE_T_FMT_PRECISION(x) "%" #x "u" + #define SIZE_T_FMT_HEX "%x" + #endif + #else + // We're compiling on Linux, so we can use libc's size_t printf format: + #define SIZE_T_FMT "%zu" + #define SIZE_T_FMT_PRECISION(x) "%" #x "zu" + #define SIZE_T_FMT_HEX "%zx" + #endif + + #define NORETURN __attribute((__noreturn__)) + #else #error "You are using an unsupported compiler, you might need to #define some stuff here for your compiler" @@ -74,6 +109,8 @@ typedef unsigned long long UInt64; typedef unsigned int UInt32; typedef unsigned short UInt16; +typedef unsigned char Byte; + @@ -94,7 +131,7 @@ typedef unsigned short UInt16; #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN - #define _WIN32_WINNT 0x501 // We want to target WinXP and higher + #define _WIN32_WINNT 0x502 // We want to target WinXP SP2 and higher #include <Windows.h> #include <winsock2.h> @@ -175,7 +212,8 @@ typedef unsigned short UInt16; #include "StringUtils.h" #include "OSSupport/CriticalSection.h" #include "OSSupport/File.h" -#include "MCLogger.h" +#include "OSSupport/Event.h" +#include "Logger.h" diff --git a/Tools/RCONClient/RCONClient.cpp b/Tools/RCONClient/RCONClient.cpp index 288363a66..7d6cf6d8f 100644 --- a/Tools/RCONClient/RCONClient.cpp +++ b/Tools/RCONClient/RCONClient.cpp @@ -80,14 +80,14 @@ bool cRCONPacketizer::SendPacket(int a_PacketType, const AString & a_PacketPaylo size_t Length = Packet.size(); if (!m_Socket.Send((const char *)&Length, 4)) { - fprintf(stderr, "Network error while sending packet: %d (%s). Aborting.", + fprintf(stderr, "Network error while sending packet: %d (%s). Aborting.\n", cSocket::GetLastError(), cSocket::GetLastErrorString().c_str() ); return false; } if (!m_Socket.Send(Packet.data(), Packet.size())) { - fprintf(stderr, "Network error while sending packet: %d (%s). Aborting.", + fprintf(stderr, "Network error while sending packet: %d (%s). Aborting.\n", cSocket::GetLastError(), cSocket::GetLastErrorString().c_str() ); return false; @@ -110,12 +110,12 @@ bool cRCONPacketizer::ReceiveResponse(void) int NumReceived = m_Socket.Receive(buf, sizeof(buf), 0); if (NumReceived == 0) { - fprintf(stderr, "The remote end closed the connection. Aborting."); + fprintf(stderr, "The remote end closed the connection. Aborting.\n"); return false; } if (NumReceived < 0) { - fprintf(stderr, "Network error while receiving response: %d, %d (%s). Aborting.", + fprintf(stderr, "Network error while receiving response: %d, %d (%s). Aborting.\n", NumReceived, cSocket::GetLastError(), cSocket::GetLastErrorString().c_str() ); return false; @@ -156,13 +156,13 @@ bool cRCONPacketizer::ParsePacket(cByteBuffer & a_Buffer, int a_PacketLength) { if ((RequestID == -1) && (m_RequestID == 0)) { - fprintf(stderr, "Login failed. Aborting."); + fprintf(stderr, "Login failed. Aborting.\n"); IsValid = false; // Continue, so that the payload is printed before the program aborts. } else { - fprintf(stderr, "The server returned an invalid request ID, got %d, exp. %d. Aborting.", RequestID, m_RequestID); + fprintf(stderr, "The server returned an invalid request ID, got %d, exp. %d. Aborting.\n", RequestID, m_RequestID); return false; } } @@ -172,7 +172,7 @@ bool cRCONPacketizer::ParsePacket(cByteBuffer & a_Buffer, int a_PacketLength) VERIFY(a_Buffer.ReadLEInt(PacketType)); if (PacketType != ptCommand) { - fprintf(stderr, "The server returned an unknown packet type: %d. Aborting.", PacketType); + fprintf(stderr, "The server returned an unknown packet type: %d. Aborting.\n", PacketType); IsValid = false; // Continue, so that the payload is printed before the program aborts. } @@ -200,8 +200,8 @@ bool cRCONPacketizer::ParsePacket(cByteBuffer & a_Buffer, int a_PacketLength) int RealMain(int argc, char * argv[]) { - new cMCLogger; // Create a new logger - + cLogger::InitiateMultithreading(); + // Parse the cmdline params for server IP, port, password and the commands to send: AString ServerAddress, Password; int ServerPort = -1; @@ -301,6 +301,7 @@ int RealMain(int argc, char * argv[]) } } + // Send each command: for (AStringVector::const_iterator itr = Commands.begin(), end = Commands.end(); itr != end; ++itr) { if (g_IsVerbose) diff --git a/Tools/RCONClient/RCONClient.sln b/Tools/RCONClient/RCONClient.sln index 0a8596e43..5c977fc81 100644 --- a/Tools/RCONClient/RCONClient.sln +++ b/Tools/RCONClient/RCONClient.sln @@ -1,7 +1,9 @@ -Microsoft Visual Studio Solution File, Format Version 10.00 -# Visual C++ Express 2008 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RCONClient", "RCONClient.vcproj", "{1A48B032-07D0-4DDD-8362-66C0FC7F7849}" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Express 2013 for Windows Desktop +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RCONClient", "RCONClient.vcxproj", "{1A48B032-07D0-4DDD-8362-66C0FC7F7849}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/easyinstall.sh b/easyinstall.sh index 9b4007144..40fa85cfe 100755 --- a/easyinstall.sh +++ b/easyinstall.sh @@ -7,7 +7,7 @@ case $PLATFORM in "i686") DOWNLOADURL="http://builds.cuberite.org/job/MCServer%20Linux%20x86/lastSuccessfulBuild/artifact/MCServer.tar" ;; "x86_64") DOWNLOADURL="http://builds.cuberite.org/job/MCServer%20Linux%20x64/lastSuccessfulBuild/artifact/MCServer.tar" ;; # Assume that all arm devices are a raspi for now. - "arm*") DOWNLOADURL="http://builds.cuberite.org/job/MCServer%20Linux%20armhf/lastSuccessfulBuild/artifact/MCServer/MCServer.tar" + arm*) DOWNLOADURL="http://builds.cuberite.org/job/MCServer%20Linux%20armhf/lastSuccessfulBuild/artifact/MCServer/MCServer.tar" esac echo "Downloading precompiled binaries." diff --git a/lib/libevent b/lib/libevent -Subproject 0b49ae34594533daa82c06a506078de9e336a01 +Subproject 62eaa889cc1996a7c58a389bf2dfa5d8ce784bd diff --git a/lib/polarssl b/lib/polarssl -Subproject d6a15321ae51762098e49a976d26efa2493c94f +Subproject 38f47a8546b55e2b593bba27f03070e1e82d3c8 diff --git a/src/Bindings/CMakeLists.txt b/src/Bindings/CMakeLists.txt index d47579cd6..4cc73b350 100644 --- a/src/Bindings/CMakeLists.txt +++ b/src/Bindings/CMakeLists.txt @@ -8,9 +8,14 @@ SET (SRCS Bindings.cpp DeprecatedBindings.cpp LuaChunkStay.cpp + LuaNameLookup.cpp + LuaServerHandle.cpp LuaState.cpp + LuaTCPLink.cpp + LuaUDPEndpoint.cpp LuaWindow.cpp ManualBindings.cpp + ManualBindings_Network.cpp ManualBindings_RankManager.cpp Plugin.cpp PluginLua.cpp @@ -23,7 +28,11 @@ SET (HDRS DeprecatedBindings.h LuaChunkStay.h LuaFunctions.h + LuaNameLookup.h + LuaServerHandle.h LuaState.h + LuaTCPLink.h + LuaUDPEndpoint.h LuaWindow.h ManualBindings.h Plugin.h diff --git a/src/Bindings/LuaNameLookup.cpp b/src/Bindings/LuaNameLookup.cpp new file mode 100644 index 000000000..e52d8dbdc --- /dev/null +++ b/src/Bindings/LuaNameLookup.cpp @@ -0,0 +1,88 @@ + +// LuaNameLookup.cpp + +// Implements the cLuaNameLookup class used as the cNetwork API callbacks for name and IP lookups from Lua + +#include "Globals.h" +#include "LuaNameLookup.h" + + + + + +cLuaNameLookup::cLuaNameLookup(const AString & a_Query, cPluginLua & a_Plugin, int a_CallbacksTableStackPos): + m_Plugin(a_Plugin), + m_Callbacks(a_Plugin.GetLuaState(), a_CallbacksTableStackPos), + m_Query(a_Query) +{ +} + + + + + +void cLuaNameLookup::OnNameResolved(const AString & a_Name, const AString & a_IP) +{ + // Check if we're still valid: + if (!m_Callbacks.IsValid()) + { + return; + } + + // Call the callback: + cPluginLua::cOperation Op(m_Plugin); + if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnNameResolved"), a_Name, a_IP)) + { + LOGINFO("cNetwork name lookup OnNameResolved callback failed in plugin %s looking up %s. %s resolves to %s.", + m_Plugin.GetName().c_str(), m_Query.c_str(), a_Name.c_str(), a_IP.c_str() + ); + } +} + + + + + +void cLuaNameLookup::OnError(int a_ErrorCode, const AString & a_ErrorMsg) +{ + // Check if we're still valid: + if (!m_Callbacks.IsValid()) + { + return; + } + + // Call the callback: + cPluginLua::cOperation Op(m_Plugin); + if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnError"), m_Query, a_ErrorCode, a_ErrorMsg)) + { + LOGINFO("cNetwork name lookup OnError callback failed in plugin %s looking up %s. The error is %d (%s)", + m_Plugin.GetName().c_str(), m_Query.c_str(), a_ErrorCode, a_ErrorMsg.c_str() + ); + } +} + + + + + +void cLuaNameLookup::OnFinished(void) +{ + // Check if we're still valid: + if (!m_Callbacks.IsValid()) + { + return; + } + + // Call the callback: + cPluginLua::cOperation Op(m_Plugin); + if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnFinished"), m_Query)) + { + LOGINFO("cNetwork name lookup OnFinished callback failed in plugin %s, looking up %s.", + m_Plugin.GetName().c_str(), m_Query.c_str() + ); + } +} + + + + diff --git a/src/Bindings/LuaNameLookup.h b/src/Bindings/LuaNameLookup.h new file mode 100644 index 000000000..e4cdb9f53 --- /dev/null +++ b/src/Bindings/LuaNameLookup.h @@ -0,0 +1,46 @@ + +// LuaNameLookup.h + +// Declares the cLuaNameLookup class used as the cNetwork API callbacks for name and IP lookups from Lua + + + + + +#pragma once + +#include "../OSSupport/Network.h" +#include "PluginLua.h" + + + + + +class cLuaNameLookup: + public cNetwork::cResolveNameCallbacks +{ +public: + /** Creates a new instance of the lookup callbacks for the specified query, + attached to the specified lua plugin and wrapping the callbacks that are in a table at the specified stack pos. */ + cLuaNameLookup(const AString & a_Query, cPluginLua & a_Plugin, int a_CallbacksTableStackPos); + +protected: + /** The plugin for which the query is created. */ + cPluginLua & m_Plugin; + + /** The Lua table that holds the callbacks to be invoked. */ + cLuaState::cRef m_Callbacks; + + /** The query used to start the lookup (either hostname or IP). */ + AString m_Query; + + + // cNetwork::cResolveNameCallbacks overrides: + virtual void OnNameResolved(const AString & a_Name, const AString & a_IP) override; + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override; + virtual void OnFinished(void) override; +}; + + + + diff --git a/src/Bindings/LuaServerHandle.cpp b/src/Bindings/LuaServerHandle.cpp new file mode 100644 index 000000000..a84f894b5 --- /dev/null +++ b/src/Bindings/LuaServerHandle.cpp @@ -0,0 +1,209 @@ + +// LuaServerHandle.cpp + +// Implements the cLuaServerHandle class wrapping the cServerHandle functionality for Lua API + +#include "Globals.h" +#include "LuaServerHandle.h" +#include "LuaTCPLink.h" +#include "tolua++/include/tolua++.h" + + + + + +cLuaServerHandle::cLuaServerHandle(UInt16 a_Port, cPluginLua & a_Plugin, int a_CallbacksTableStackPos): + m_Plugin(a_Plugin), + m_Callbacks(a_Plugin.GetLuaState(), a_CallbacksTableStackPos), + m_Port(a_Port) +{ +} + + + + + + +cLuaServerHandle::~cLuaServerHandle() +{ + // If the server handle is still open, close it explicitly: + Close(); +} + + + + + +void cLuaServerHandle::SetServerHandle(cServerHandlePtr a_ServerHandle, cLuaServerHandlePtr a_Self) +{ + ASSERT(m_ServerHandle == nullptr); // The handle can be set only once + + m_ServerHandle = a_ServerHandle; + m_Self = a_Self; +} + + + + + +void cLuaServerHandle::Close(void) +{ + // Safely grab a copy of the server handle: + cServerHandlePtr ServerHandle = m_ServerHandle; + if (ServerHandle == nullptr) + { + return; + } + + // Close the underlying server handle: + ServerHandle->Close(); + + // Close all connections at this server: + cLuaTCPLinkPtrs Connections; + { + cCSLock Lock(m_CSConnections); + std::swap(Connections, m_Connections); + } + for (auto & conn: Connections) + { + conn->Close(); + } + Connections.clear(); + + // Allow the internal server handle to be deallocated: + m_ServerHandle.reset(); +} + + + + + +bool cLuaServerHandle::IsListening(void) +{ + // Safely grab a copy of the server handle: + cServerHandlePtr ServerHandle = m_ServerHandle; + if (ServerHandle == nullptr) + { + return false; + } + + // Query the underlying server handle: + return ServerHandle->IsListening(); +} + + + + + +void cLuaServerHandle::RemoveLink(cLuaTCPLink * a_Link) +{ + cCSLock Lock(m_CSConnections); + for (auto itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr) + { + if (itr->get() == a_Link) + { + m_Connections.erase(itr); + break; + } + } // for itr - m_Connections[] +} + + + + + +void cLuaServerHandle::Release(void) +{ + // Close the server, if it isn't closed yet: + Close(); + + // Allow self to deallocate: + m_Self.reset(); +} + + + + + +cTCPLink::cCallbacksPtr cLuaServerHandle::OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) +{ + // If not valid anymore, drop the connection: + if (!m_Callbacks.IsValid()) + { + return nullptr; + } + + // Ask the plugin for link callbacks: + cPluginLua::cOperation Op(m_Plugin); + cLuaState::cRef LinkCallbacks; + if ( + !Op().Call(cLuaState::cTableRef(m_Callbacks, "OnIncomingConnection"), a_RemoteIPAddress, a_RemotePort, m_Port, cLuaState::Return, LinkCallbacks) || + !LinkCallbacks.IsValid() + ) + { + LOGINFO("cNetwork server (port %d) OnIncomingConnection callback failed in plugin %s. Dropping connection.", + m_Port, m_Plugin.GetName().c_str() + ); + return nullptr; + } + + // Create the link wrapper to use with the callbacks: + auto res = std::make_shared<cLuaTCPLink>(m_Plugin, std::move(LinkCallbacks), m_Self); + + // Add the link to the list of our connections: + cCSLock Lock(m_CSConnections); + m_Connections.push_back(res); + + return res; +} + + + + + +void cLuaServerHandle::OnAccepted(cTCPLink & a_Link) +{ + // Check if we're still valid: + if (!m_Callbacks.IsValid()) + { + return; + } + + // Notify the plugin: + cPluginLua::cOperation Op(m_Plugin); + if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnAccepted"), static_cast<cLuaTCPLink *>(a_Link.GetCallbacks().get()))) + { + LOGINFO("cNetwork server (port %d) OnAccepted callback failed in plugin %s, connection to %s:%d.", + m_Port, m_Plugin.GetName().c_str(), a_Link.GetRemoteIP().c_str(), a_Link.GetRemotePort() + ); + return; + } +} + + + + + +void cLuaServerHandle::OnError(int a_ErrorCode, const AString & a_ErrorMsg) +{ + // Check if we're still valid: + if (!m_Callbacks.IsValid()) + { + return; + } + + // Notify the plugin: + cPluginLua::cOperation Op(m_Plugin); + if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnError"), a_ErrorCode, a_ErrorMsg)) + { + LOGINFO("cNetwork server (port %d) OnError callback failed in plugin %s. The error is %d (%s).", + m_Port, m_Plugin.GetName().c_str(), a_ErrorCode, a_ErrorMsg.c_str() + ); + return; + } +} + + + + + diff --git a/src/Bindings/LuaServerHandle.h b/src/Bindings/LuaServerHandle.h new file mode 100644 index 000000000..9325bca3e --- /dev/null +++ b/src/Bindings/LuaServerHandle.h @@ -0,0 +1,89 @@ + +// LuaServerHandle.h + +// Declares the cLuaServerHandle class wrapping the cServerHandle functionality for Lua API + + + + + +#pragma once + +#include "../OSSupport/Network.h" +#include "PluginLua.h" + + + + + +// fwd: +class cLuaTCPLink; +typedef SharedPtr<cLuaTCPLink> cLuaTCPLinkPtr; +typedef std::vector<cLuaTCPLinkPtr> cLuaTCPLinkPtrs; +class cLuaServerHandle; +typedef SharedPtr<cLuaServerHandle> cLuaServerHandlePtr; + + + + +class cLuaServerHandle: + public cNetwork::cListenCallbacks +{ +public: + /** Creates a new instance of the server handle, + attached to the specified lua plugin and wrapping the (listen-) callbacks that are in a table at the specified stack pos. */ + cLuaServerHandle(UInt16 a_Port, cPluginLua & a_Plugin, int a_CallbacksTableStackPos); + + ~cLuaServerHandle(); + + /** Called by cNetwork::Listen()'s binding. + Sets the server handle around which this instance is wrapped, and a self SharedPtr for link management. */ + void SetServerHandle(cServerHandlePtr a_ServerHandle, cLuaServerHandlePtr a_Self); + + /** Terminates all connections and closes the listening socket. */ + void Close(void); + + /** Returns true if the server is currently listening. */ + bool IsListening(void); + + /** Removes the link from the list of links that the server is currently tracking. */ + void RemoveLink(cLuaTCPLink * a_Link); + + /** Called when Lua garbage-collects the object. + Releases the internal SharedPtr to self, so that the instance may be deallocated. */ + void Release(void); + +protected: + /** The plugin for which the server is created. */ + cPluginLua & m_Plugin; + + /** The Lua table that holds the callbacks to be invoked. */ + cLuaState::cRef m_Callbacks; + + /** The port on which the server is listening. + Used mainly for better error reporting. */ + UInt16 m_Port; + + /** The cServerHandle around which this instance is wrapped. */ + cServerHandlePtr m_ServerHandle; + + /** Protects m_Connections against multithreaded access. */ + cCriticalSection m_CSConnections; + + /** All connections that are currently active in this server. + Protected by m_CSConnections. */ + cLuaTCPLinkPtrs m_Connections; + + /** SharedPtr to self, given out to newly created links. */ + cLuaServerHandlePtr m_Self; + + + // cNetwork::cListenCallbacks overrides: + virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) override; + virtual void OnAccepted(cTCPLink & a_Link) override; + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override; +}; + + + + diff --git a/src/Bindings/LuaState.cpp b/src/Bindings/LuaState.cpp index 01d3ac687..25c77a652 100644 --- a/src/Bindings/LuaState.cpp +++ b/src/Bindings/LuaState.cpp @@ -343,6 +343,18 @@ bool cLuaState::PushFunction(const cTableRef & a_TableRef) +void cLuaState::PushNil(void) +{ + ASSERT(IsValid()); + + lua_pushnil(m_LuaState); + m_NumCurrentFunctionArgs += 1; +} + + + + + void cLuaState::Push(const AString & a_String) { ASSERT(IsValid()); @@ -656,6 +668,42 @@ void cLuaState::Push(cItems * a_Items) +void cLuaState::Push(cLuaServerHandle * a_ServerHandle) +{ + ASSERT(IsValid()); + + tolua_pushusertype(m_LuaState, a_ServerHandle, "cServerHandle"); + m_NumCurrentFunctionArgs += 1; +} + + + + + +void cLuaState::Push(cLuaTCPLink * a_TCPLink) +{ + ASSERT(IsValid()); + + tolua_pushusertype(m_LuaState, a_TCPLink, "cTCPLink"); + m_NumCurrentFunctionArgs += 1; +} + + + + + +void cLuaState::Push(cLuaUDPEndpoint * a_UDPEndpoint) +{ + ASSERT(IsValid()); + + tolua_pushusertype(m_LuaState, a_UDPEndpoint, "cUDPEndpoint"); + m_NumCurrentFunctionArgs += 1; +} + + + + + void cLuaState::Push(cMonster * a_Monster) { ASSERT(IsValid()); @@ -958,6 +1006,15 @@ void cLuaState::GetStackValue(int a_StackPos, pWorld & a_ReturnedVal) +void cLuaState::GetStackValue(int a_StackPos, cRef & a_Ref) +{ + a_Ref.RefStack(*this, a_StackPos); +} + + + + + bool cLuaState::CallFunction(int a_NumResults) { ASSERT (m_NumCurrentFunctionArgs >= 0); // A function must be pushed to stack first @@ -1527,6 +1584,18 @@ cLuaState::cRef::cRef(cLuaState & a_LuaState, int a_StackPos) : +cLuaState::cRef::cRef(cRef && a_FromRef): + m_LuaState(a_FromRef.m_LuaState), + m_Ref(a_FromRef.m_Ref) +{ + a_FromRef.m_LuaState = nullptr; + a_FromRef.m_Ref = LUA_REFNIL; +} + + + + + cLuaState::cRef::~cRef() { if (m_LuaState != nullptr) diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h index 7ac4120e1..159483634 100644 --- a/src/Bindings/LuaState.h +++ b/src/Bindings/LuaState.h @@ -59,6 +59,9 @@ class cTNTEntity; class cHopperEntity; class cBlockEntity; class cBoundingBox; +class cLuaTCPLink; +class cLuaServerHandle; +class cLuaUDPEndpoint; typedef cBoundingBox * pBoundingBox; typedef cWorld * pWorld; @@ -83,6 +86,10 @@ public: /** Creates a reference in the specified LuaState for object at the specified StackPos */ cRef(cLuaState & a_LuaState, int a_StackPos); + + /** Moves the reference from the specified instance into a newly created instance. + The old instance is then "!IsValid()". */ + cRef(cRef && a_FromRef); ~cRef(); @@ -178,6 +185,8 @@ public: /** Returns true if a_FunctionName is a valid Lua function that can be called */ bool HasFunction(const char * a_FunctionName); + void PushNil(void); + // Push a const value onto the stack (keep alpha-sorted): void Push(const AString & a_String); void Push(const AStringVector & a_Vector); @@ -202,6 +211,9 @@ public: void Push(cHopperEntity * a_Hopper); void Push(cItem * a_Item); void Push(cItems * a_Items); + void Push(cLuaServerHandle * a_ServerHandle); + void Push(cLuaTCPLink * a_TCPLink); + void Push(cLuaUDPEndpoint * a_UDPEndpoint); void Push(cMonster * a_Monster); void Push(cPickup * a_Pickup); void Push(cPlayer * a_Player); @@ -240,6 +252,9 @@ public: /** Retrieve value at a_StackPos, if it is a valid cWorld class. If not, a_Value is unchanged */ void GetStackValue(int a_StackPos, pWorld & a_Value); + + /** Store the value at a_StackPos as a reference. */ + void GetStackValue(int a_StackPos, cRef & a_Ref); /** Call the specified Lua function. Returns true if call succeeded, false if there was an error. @@ -346,20 +361,6 @@ protected: /** Number of arguments currently pushed (for the Push / Call chain) */ int m_NumCurrentFunctionArgs; - - /** Variadic template terminator: Counting zero args returns zero. */ - int CountArgs(void) - { - return 0; - } - - /** Variadic template: Counting args means add one to the count of the rest. */ - template <typename T, typename... Args> - int CountArgs(T, Args... args) - { - return 1 + CountArgs(args...); - } - /** Variadic template terminator: If there's nothing more to push / pop, just call the function. Note that there are no return values either, because those are prefixed by a cRet value, so the arg list is never empty. */ bool PushCallPop(void) @@ -380,7 +381,7 @@ protected: bool PushCallPop(cLuaState::cRet, Args &&... args) { // Calculate the number of return values (number of args left): - int NumReturns = CountArgs(args...); + int NumReturns = sizeof...(args); // Call the function: if (!CallFunction(NumReturns)) diff --git a/src/Bindings/LuaTCPLink.cpp b/src/Bindings/LuaTCPLink.cpp new file mode 100644 index 000000000..d88c41120 --- /dev/null +++ b/src/Bindings/LuaTCPLink.cpp @@ -0,0 +1,610 @@ + +// LuaTCPLink.cpp + +// Implements the cLuaTCPLink class representing a Lua wrapper for the cTCPLink class and the callbacks it needs + +#include "Globals.h" +#include "LuaTCPLink.h" +#include "LuaServerHandle.h" + + + + + +cLuaTCPLink::cLuaTCPLink(cPluginLua & a_Plugin, int a_CallbacksTableStackPos): + m_Plugin(a_Plugin), + m_Callbacks(a_Plugin.GetLuaState(), a_CallbacksTableStackPos) +{ + // Warn if the callbacks aren't valid: + if (!m_Callbacks.IsValid()) + { + LOGWARNING("cTCPLink in plugin %s: callbacks could not be retrieved", m_Plugin.GetName().c_str()); + cPluginLua::cOperation Op(m_Plugin); + Op().LogStackTrace(); + } +} + + + + + +cLuaTCPLink::cLuaTCPLink(cPluginLua & a_Plugin, cLuaState::cRef && a_CallbacksTableRef, cLuaServerHandleWPtr a_ServerHandle): + m_Plugin(a_Plugin), + m_Callbacks(std::move(a_CallbacksTableRef)), + m_Server(std::move(a_ServerHandle)) +{ + // Warn if the callbacks aren't valid: + if (!m_Callbacks.IsValid()) + { + LOGWARNING("cTCPLink in plugin %s: callbacks could not be retrieved", m_Plugin.GetName().c_str()); + cPluginLua::cOperation Op(m_Plugin); + Op().LogStackTrace(); + } +} + + + + + +cLuaTCPLink::~cLuaTCPLink() +{ + // If the link is still open, close it: + cTCPLinkPtr Link = m_Link; + if (Link != nullptr) + { + Link->Close(); + } + + Terminated(); +} + + + + + +bool cLuaTCPLink::Send(const AString & a_Data) +{ + // If running in SSL mode, push the data into the SSL context instead: + if (m_SslContext != nullptr) + { + m_SslContext->Send(a_Data); + return true; + } + + // Safely grab a copy of the link: + cTCPLinkPtr Link = m_Link; + if (Link == nullptr) + { + return false; + } + + // Send the data: + return Link->Send(a_Data); +} + + + + + +AString cLuaTCPLink::GetLocalIP(void) const +{ + // Safely grab a copy of the link: + cTCPLinkPtr Link = m_Link; + if (Link == nullptr) + { + return ""; + } + + // Get the IP address: + return Link->GetLocalIP(); +} + + + + + +UInt16 cLuaTCPLink::GetLocalPort(void) const +{ + // Safely grab a copy of the link: + cTCPLinkPtr Link = m_Link; + if (Link == nullptr) + { + return 0; + } + + // Get the port: + return Link->GetLocalPort(); +} + + + + + +AString cLuaTCPLink::GetRemoteIP(void) const +{ + // Safely grab a copy of the link: + cTCPLinkPtr Link = m_Link; + if (Link == nullptr) + { + return ""; + } + + // Get the IP address: + return Link->GetRemoteIP(); +} + + + + + +UInt16 cLuaTCPLink::GetRemotePort(void) const +{ + // Safely grab a copy of the link: + cTCPLinkPtr Link = m_Link; + if (Link == nullptr) + { + return 0; + } + + // Get the port: + return Link->GetRemotePort(); +} + + + + + +void cLuaTCPLink::Shutdown(void) +{ + // Safely grab a copy of the link and shut it down: + cTCPLinkPtr Link = m_Link; + if (Link != nullptr) + { + if (m_SslContext != nullptr) + { + m_SslContext->NotifyClose(); + m_SslContext->ResetSelf(); + m_SslContext.reset(); + } + Link->Shutdown(); + } +} + + + + + +void cLuaTCPLink::Close(void) +{ + // If the link is still open, close it: + cTCPLinkPtr Link = m_Link; + if (Link != nullptr) + { + if (m_SslContext != nullptr) + { + m_SslContext->NotifyClose(); + m_SslContext->ResetSelf(); + m_SslContext.reset(); + } + Link->Close(); + } + + Terminated(); +} + + + + + +AString cLuaTCPLink::StartTLSClient( + const AString & a_OwnCertData, + const AString & a_OwnPrivKeyData, + const AString & a_OwnPrivKeyPassword +) +{ + // Check preconditions: + if (m_SslContext != nullptr) + { + return "TLS is already active on this link"; + } + if ( + (a_OwnCertData.empty() && !a_OwnPrivKeyData.empty()) || + (!a_OwnCertData.empty() && a_OwnPrivKeyData.empty()) + ) + { + return "Either provide both the certificate and private key, or neither"; + } + + // Create the SSL context: + m_SslContext.reset(new cLinkSslContext(*this)); + m_SslContext->Initialize(true); + + // Create the peer cert, if required: + if (!a_OwnCertData.empty() && !a_OwnPrivKeyData.empty()) + { + auto OwnCert = std::make_shared<cX509Cert>(); + int res = OwnCert->Parse(a_OwnCertData.data(), a_OwnCertData.size()); + if (res != 0) + { + m_SslContext.reset(); + return Printf("Cannot parse peer certificate: -0x%x", res); + } + auto OwnPrivKey = std::make_shared<cCryptoKey>(); + res = OwnPrivKey->ParsePrivate(a_OwnPrivKeyData.data(), a_OwnPrivKeyData.size(), a_OwnPrivKeyPassword); + if (res != 0) + { + m_SslContext.reset(); + return Printf("Cannot parse peer private key: -0x%x", res); + } + m_SslContext->SetOwnCert(OwnCert, OwnPrivKey); + } + m_SslContext->SetSelf(cLinkSslContextWPtr(m_SslContext)); + + // Start the handshake: + m_SslContext->Handshake(); + return ""; +} + + + + + +AString cLuaTCPLink::StartTLSServer( + const AString & a_OwnCertData, + const AString & a_OwnPrivKeyData, + const AString & a_OwnPrivKeyPassword, + const AString & a_StartTLSData +) +{ + // Check preconditions: + if (m_SslContext != nullptr) + { + return "TLS is already active on this link"; + } + if (a_OwnCertData.empty() || a_OwnPrivKeyData.empty()) + { + return "Provide the server certificate and private key"; + } + + // Create the SSL context: + m_SslContext.reset(new cLinkSslContext(*this)); + m_SslContext->Initialize(false); + + // Create the peer cert: + auto OwnCert = std::make_shared<cX509Cert>(); + int res = OwnCert->Parse(a_OwnCertData.data(), a_OwnCertData.size()); + if (res != 0) + { + m_SslContext.reset(); + return Printf("Cannot parse server certificate: -0x%x", res); + } + auto OwnPrivKey = std::make_shared<cCryptoKey>(); + res = OwnPrivKey->ParsePrivate(a_OwnPrivKeyData.data(), a_OwnPrivKeyData.size(), a_OwnPrivKeyPassword); + if (res != 0) + { + m_SslContext.reset(); + return Printf("Cannot parse server private key: -0x%x", res); + } + m_SslContext->SetOwnCert(OwnCert, OwnPrivKey); + m_SslContext->SetSelf(cLinkSslContextWPtr(m_SslContext)); + + // Push the initial data: + m_SslContext->StoreReceivedData(a_StartTLSData.data(), a_StartTLSData.size()); + + // Start the handshake: + m_SslContext->Handshake(); + return ""; +} + + + + + +void cLuaTCPLink::Terminated(void) +{ + // Disable the callbacks: + if (m_Callbacks.IsValid()) + { + m_Callbacks.UnRef(); + } + + // If the managing server is still alive, let it know we're terminating: + auto Server = m_Server.lock(); + if (Server != nullptr) + { + Server->RemoveLink(this); + } + + // If the link is still open, close it: + { + cTCPLinkPtr Link = m_Link; + if (Link != nullptr) + { + Link->Close(); + m_Link.reset(); + } + } + + // If the SSL context still exists, free it: + m_SslContext.reset(); +} + + + + + +void cLuaTCPLink::ReceivedCleartextData(const char * a_Data, size_t a_NumBytes) +{ + // Check if we're still valid: + if (!m_Callbacks.IsValid()) + { + return; + } + + // Call the callback: + cPluginLua::cOperation Op(m_Plugin); + if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnReceivedData"), this, AString(a_Data, a_NumBytes))) + { + LOGINFO("cTCPLink OnReceivedData callback failed in plugin %s.", m_Plugin.GetName().c_str()); + } +} + + + + + +void cLuaTCPLink::OnConnected(cTCPLink & a_Link) +{ + // Check if we're still valid: + if (!m_Callbacks.IsValid()) + { + return; + } + + // Call the callback: + cPluginLua::cOperation Op(m_Plugin); + if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnConnected"), this)) + { + LOGINFO("cTCPLink OnConnected() callback failed in plugin %s.", m_Plugin.GetName().c_str()); + } +} + + + + + +void cLuaTCPLink::OnError(int a_ErrorCode, const AString & a_ErrorMsg) +{ + // Check if we're still valid: + if (!m_Callbacks.IsValid()) + { + return; + } + + // Call the callback: + cPluginLua::cOperation Op(m_Plugin); + if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnError"), this, a_ErrorCode, a_ErrorMsg)) + { + LOGINFO("cTCPLink OnError() callback failed in plugin %s; the link error is %d (%s).", + m_Plugin.GetName().c_str(), a_ErrorCode, a_ErrorMsg.c_str() + ); + } + + Terminated(); +} + + + + + +void cLuaTCPLink::OnLinkCreated(cTCPLinkPtr a_Link) +{ + // Store the cTCPLink for later use: + m_Link = a_Link; +} + + + + + +void cLuaTCPLink::OnReceivedData(const char * a_Data, size_t a_Length) +{ + // Check if we're still valid: + if (!m_Callbacks.IsValid()) + { + return; + } + + // If we're running in SSL mode, put the data into the SSL decryptor: + if (m_SslContext != nullptr) + { + m_SslContext->StoreReceivedData(a_Data, a_Length); + return; + } + + // Call the callback: + cPluginLua::cOperation Op(m_Plugin); + if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnReceivedData"), this, AString(a_Data, a_Length))) + { + LOGINFO("cTCPLink OnReceivedData callback failed in plugin %s.", m_Plugin.GetName().c_str()); + } +} + + + + + +void cLuaTCPLink::OnRemoteClosed(void) +{ + // Check if we're still valid: + if (!m_Callbacks.IsValid()) + { + return; + } + + // Call the callback: + cPluginLua::cOperation Op(m_Plugin); + if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnRemoteClosed"), this)) + { + LOGINFO("cTCPLink OnRemoteClosed() callback failed in plugin %s.", m_Plugin.GetName().c_str()); + } + + Terminated(); +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cLuaTCPLink::cLinkSslContext: + +cLuaTCPLink::cLinkSslContext::cLinkSslContext(cLuaTCPLink & a_Link): + m_Link(a_Link) +{ +} + + + + + +void cLuaTCPLink::cLinkSslContext::SetSelf(cLinkSslContextWPtr a_Self) +{ + m_Self = a_Self; +} + + + + + +void cLuaTCPLink::cLinkSslContext::ResetSelf(void) +{ + m_Self.reset(); +} + + + + + +void cLuaTCPLink::cLinkSslContext::StoreReceivedData(const char * a_Data, size_t a_NumBytes) +{ + // Hold self alive for the duration of this function + cLinkSslContextPtr Self(m_Self); + + m_EncryptedData.append(a_Data, a_NumBytes); + + // Try to finish a pending handshake: + TryFinishHandshaking(); + + // Flush any cleartext data that can be "received": + FlushBuffers(); +} + + + + + +void cLuaTCPLink::cLinkSslContext::FlushBuffers(void) +{ + // Hold self alive for the duration of this function + cLinkSslContextPtr Self(m_Self); + + // If the handshake didn't complete yet, bail out: + if (!HasHandshaken()) + { + return; + } + + char Buffer[1024]; + int NumBytes; + while ((NumBytes = ReadPlain(Buffer, sizeof(Buffer))) > 0) + { + m_Link.ReceivedCleartextData(Buffer, static_cast<size_t>(NumBytes)); + if (m_Self.expired()) + { + // The callback closed the SSL context, bail out + return; + } + } +} + + + + + +void cLuaTCPLink::cLinkSslContext::TryFinishHandshaking(void) +{ + // Hold self alive for the duration of this function + cLinkSslContextPtr Self(m_Self); + + // If the handshake hasn't finished yet, retry: + if (!HasHandshaken()) + { + Handshake(); + } + + // If the handshake succeeded, write all the queued plaintext data: + if (HasHandshaken()) + { + WritePlain(m_CleartextData.data(), m_CleartextData.size()); + m_CleartextData.clear(); + } +} + + + + + +void cLuaTCPLink::cLinkSslContext::Send(const AString & a_Data) +{ + // Hold self alive for the duration of this function + cLinkSslContextPtr Self(m_Self); + + // If the handshake hasn't completed yet, queue the data: + if (!HasHandshaken()) + { + m_CleartextData.append(a_Data); + TryFinishHandshaking(); + return; + } + + // The connection is all set up, write the cleartext data into the SSL context: + WritePlain(a_Data.data(), a_Data.size()); + FlushBuffers(); +} + + + + + +int cLuaTCPLink::cLinkSslContext::ReceiveEncrypted(unsigned char * a_Buffer, size_t a_NumBytes) +{ + // Hold self alive for the duration of this function + cLinkSslContextPtr Self(m_Self); + + // If there's nothing queued in the buffer, report empty buffer: + if (m_EncryptedData.empty()) + { + return POLARSSL_ERR_NET_WANT_READ; + } + + // Copy as much data as possible to the provided buffer: + size_t BytesToCopy = std::min(a_NumBytes, m_EncryptedData.size()); + memcpy(a_Buffer, m_EncryptedData.data(), BytesToCopy); + m_EncryptedData.erase(0, BytesToCopy); + return static_cast<int>(BytesToCopy); +} + + + + + +int cLuaTCPLink::cLinkSslContext::SendEncrypted(const unsigned char * a_Buffer, size_t a_NumBytes) +{ + m_Link.m_Link->Send(a_Buffer, a_NumBytes); + return static_cast<int>(a_NumBytes); +} + + + + diff --git a/src/Bindings/LuaTCPLink.h b/src/Bindings/LuaTCPLink.h new file mode 100644 index 000000000..c8ae776fe --- /dev/null +++ b/src/Bindings/LuaTCPLink.h @@ -0,0 +1,181 @@ + +// LuaTCPLink.h + +// Declares the cLuaTCPLink class representing a Lua wrapper for the cTCPLink class and the callbacks it needs + + + + + +#pragma once + +#include "../OSSupport/Network.h" +#include "PluginLua.h" +#include "../PolarSSL++/SslContext.h" + + + + + +// fwd: +class cLuaServerHandle; +typedef WeakPtr<cLuaServerHandle> cLuaServerHandleWPtr; + + + + + +class cLuaTCPLink: + public cNetwork::cConnectCallbacks, + public cTCPLink::cCallbacks +{ +public: + /** Creates a new instance of the link, attached to the specified plugin and wrapping the callbacks that are in a table at the specified stack pos. */ + cLuaTCPLink(cPluginLua & a_Plugin, int a_CallbacksTableStackPos); + + /** Creates a new instance of the link, attached to the specified plugin and wrapping the callbacks that are in the specified referenced table. */ + cLuaTCPLink(cPluginLua & a_Plugin, cLuaState::cRef && a_CallbacksTableRef, cLuaServerHandleWPtr a_Server); + + ~cLuaTCPLink(); + + /** Sends the data contained in the string to the remote peer. + Returns true if successful, false on immediate failure (queueing the data failed or link not available). */ + bool Send(const AString & a_Data); + + /** Returns the IP address of the local endpoint of the connection. */ + AString GetLocalIP(void) const; + + /** Returns the port used by the local endpoint of the connection. */ + UInt16 GetLocalPort(void) const; + + /** Returns the IP address of the remote endpoint of the connection. */ + AString GetRemoteIP(void) const; + + /** Returns the port used by the remote endpoint of the connection. */ + UInt16 GetRemotePort(void) const; + + /** Closes the link gracefully. + The link will send any queued outgoing data, then it will send the FIN packet. + The link will still receive incoming data from remote until the remote closes the connection. */ + void Shutdown(void); + + /** Drops the connection without any more processing. + Sends the RST packet, queued outgoing and incoming data is lost. */ + void Close(void); + + /** Starts a TLS handshake as a client connection. + If a client certificate should be used for the connection, set the certificate into a_OwnCertData and + its corresponding private key to a_OwnPrivKeyData. If both are empty, no client cert is presented. + a_OwnPrivKeyPassword is the password to be used for decoding PrivKey, empty if not passworded. + Returns empty string on success, non-empty error description on failure. */ + AString StartTLSClient( + const AString & a_OwnCertData, + const AString & a_OwnPrivKeyData, + const AString & a_OwnPrivKeyPassword + ); + + /** Starts a TLS handshake as a server connection. + Set the server certificate into a_CertData and its corresponding private key to a_OwnPrivKeyData. + a_OwnPrivKeyPassword is the password to be used for decoding PrivKey, empty if not passworded. + a_StartTLSData is any data that should be pushed into the TLS before reading more data from the remote. + This is used mainly for protocols starting TLS in the middle of communication, when the TLS start command + can be received together with the TLS Client Hello message in one OnReceivedData() call, to re-queue the + Client Hello message into the TLS handshake buffer. + Returns empty string on success, non-empty error description on failure. */ + AString StartTLSServer( + const AString & a_OwnCertData, + const AString & a_OwnPrivKeyData, + const AString & a_OwnPrivKeyPassword, + const AString & a_StartTLSData + ); + +protected: + // fwd: + class cLinkSslContext; + typedef SharedPtr<cLinkSslContext> cLinkSslContextPtr; + typedef WeakPtr<cLinkSslContext> cLinkSslContextWPtr; + + /** Wrapper around cSslContext that is used when this link is being encrypted by SSL. */ + class cLinkSslContext : + public cSslContext + { + cLuaTCPLink & m_Link; + + /** Buffer for storing the incoming encrypted data until it is requested by the SSL decryptor. */ + AString m_EncryptedData; + + /** Buffer for storing the outgoing cleartext data until the link has finished handshaking. */ + AString m_CleartextData; + + /** Shared ownership of self, so that this object can keep itself alive for as long as it needs. */ + cLinkSslContextWPtr m_Self; + + public: + cLinkSslContext(cLuaTCPLink & a_Link); + + /** Shares ownership of self, so that this object can keep itself alive for as long as it needs. */ + void SetSelf(cLinkSslContextWPtr a_Self); + + /** Removes the self ownership so that we can detect the SSL closure. */ + void ResetSelf(void); + + /** Stores the specified block of data into the buffer of the data to be decrypted (incoming from remote). + Also flushes the SSL buffers by attempting to read any data through the SSL context. */ + void StoreReceivedData(const char * a_Data, size_t a_NumBytes); + + /** Tries to read any cleartext data available through the SSL, reports it in the link. */ + void FlushBuffers(void); + + /** Tries to finish handshaking the SSL. */ + void TryFinishHandshaking(void); + + /** Sends the specified cleartext data over the SSL to the remote peer. + If the handshake hasn't been completed yet, queues the data for sending when it completes. */ + void Send(const AString & a_Data); + + // cSslContext overrides: + virtual int ReceiveEncrypted(unsigned char * a_Buffer, size_t a_NumBytes) override; + virtual int SendEncrypted(const unsigned char * a_Buffer, size_t a_NumBytes) override; + }; + + + /** The plugin for which the link is created. */ + cPluginLua & m_Plugin; + + /** The Lua table that holds the callbacks to be invoked. */ + cLuaState::cRef m_Callbacks; + + /** The underlying link representing the connection. + May be nullptr. */ + cTCPLinkPtr m_Link; + + /** The server that is responsible for this link, if any. */ + cLuaServerHandleWPtr m_Server; + + /** The SSL context used for encryption, if this link uses SSL. + If valid, the link uses encryption through this context. */ + cLinkSslContextPtr m_SslContext; + + + /** Common code called when the link is considered as terminated. + Releases m_Link, m_Callbacks and this from m_Server, each when applicable. */ + void Terminated(void); + + /** Called by the SSL context when there's incoming data available in the cleartext. + Reports the data via the Lua callback function. */ + void ReceivedCleartextData(const char * a_Data, size_t a_NumBytes); + + // cNetwork::cConnectCallbacks overrides: + virtual void OnConnected(cTCPLink & a_Link) override; + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override; + + // cTCPLink::cCallbacks overrides: + virtual void OnLinkCreated(cTCPLinkPtr a_Link) override; + virtual void OnReceivedData(const char * a_Data, size_t a_Length) override; + virtual void OnRemoteClosed(void) override; + // The OnError() callback is shared with cNetwork::cConnectCallbacks +}; + + + + diff --git a/src/Bindings/LuaUDPEndpoint.cpp b/src/Bindings/LuaUDPEndpoint.cpp new file mode 100644 index 000000000..8637eeb4e --- /dev/null +++ b/src/Bindings/LuaUDPEndpoint.cpp @@ -0,0 +1,221 @@ + +// LuaUDPEndpoint.cpp + +// Implements the cLuaUDPEndpoint class representing a Lua wrapper for the cUDPEndpoint class and the callbacks it needs + +#include "Globals.h" +#include "LuaUDPEndpoint.h" + + + + + +cLuaUDPEndpoint::cLuaUDPEndpoint(cPluginLua & a_Plugin, int a_CallbacksTableStackPos): + m_Plugin(a_Plugin), + m_Callbacks(a_Plugin.GetLuaState(), a_CallbacksTableStackPos) +{ + // Warn if the callbacks aren't valid: + if (!m_Callbacks.IsValid()) + { + LOGWARNING("cLuaUDPEndpoint in plugin %s: callbacks could not be retrieved", m_Plugin.GetName().c_str()); + cPluginLua::cOperation Op(m_Plugin); + Op().LogStackTrace(); + } +} + + + + + +cLuaUDPEndpoint::~cLuaUDPEndpoint() +{ + // If the endpoint is still open, close it: + cUDPEndpointPtr Endpoint = m_Endpoint; + if (Endpoint != nullptr) + { + Endpoint->Close(); + } + + Terminated(); +} + + + + + +bool cLuaUDPEndpoint::Open(UInt16 a_Port, cLuaUDPEndpointPtr a_Self) +{ + ASSERT(m_Self == nullptr); // Must not be opened yet + ASSERT(m_Endpoint == nullptr); + + m_Self = a_Self; + m_Endpoint = cNetwork::CreateUDPEndpoint(a_Port, *this); + return m_Endpoint->IsOpen(); +} + + + + + +bool cLuaUDPEndpoint::Send(const AString & a_Data, const AString & a_RemotePeer, UInt16 a_RemotePort) +{ + // Safely grab a copy of the endpoint: + cUDPEndpointPtr Endpoint = m_Endpoint; + if (Endpoint == nullptr) + { + return false; + } + + // Send the data: + return Endpoint->Send(a_Data, a_RemotePeer, a_RemotePort); +} + + + + + +UInt16 cLuaUDPEndpoint::GetPort(void) const +{ + // Safely grab a copy of the endpoint: + cUDPEndpointPtr Endpoint = m_Endpoint; + if (Endpoint == nullptr) + { + return 0; + } + + // Get the port: + return Endpoint->GetPort(); +} + + + + + +bool cLuaUDPEndpoint::IsOpen(void) const +{ + // Safely grab a copy of the endpoint: + cUDPEndpointPtr Endpoint = m_Endpoint; + if (Endpoint == nullptr) + { + // No endpoint means that we're not open + return false; + } + + // Get the state: + return Endpoint->IsOpen(); +} + + + + + +void cLuaUDPEndpoint::Close(void) +{ + // If the endpoint is still open, close it: + cUDPEndpointPtr Endpoint = m_Endpoint; + if (Endpoint != nullptr) + { + Endpoint->Close(); + m_Endpoint.reset(); + } + + Terminated(); +} + + + + + +void cLuaUDPEndpoint::EnableBroadcasts(void) +{ + // Safely grab a copy of the endpoint: + cUDPEndpointPtr Endpoint = m_Endpoint; + if (Endpoint != nullptr) + { + Endpoint->EnableBroadcasts(); + } +} + + + + + +void cLuaUDPEndpoint::Release(void) +{ + // Close the endpoint, if not already closed: + Close(); + + // Allow self to deallocate: + m_Self.reset(); +} + + + + + +void cLuaUDPEndpoint::Terminated(void) +{ + // Disable the callbacks: + if (m_Callbacks.IsValid()) + { + m_Callbacks.UnRef(); + } + + // If the endpoint is still open, close it: + { + cUDPEndpointPtr Endpoint = m_Endpoint; + if (Endpoint != nullptr) + { + Endpoint->Close(); + m_Endpoint.reset(); + } + } +} + + + + + +void cLuaUDPEndpoint::OnReceivedData(const char * a_Data, size_t a_NumBytes, const AString & a_RemotePeer, UInt16 a_RemotePort) +{ + // Check if we're still valid: + if (!m_Callbacks.IsValid()) + { + return; + } + + // Call the callback: + cPluginLua::cOperation Op(m_Plugin); + if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnReceivedData"), this, AString(a_Data, a_NumBytes), a_RemotePeer, a_RemotePort)) + { + LOGINFO("cUDPEndpoint OnReceivedData callback failed in plugin %s.", m_Plugin.GetName().c_str()); + } +} + + + + + +void cLuaUDPEndpoint::OnError(int a_ErrorCode, const AString & a_ErrorMsg) +{ + // Check if we're still valid: + if (!m_Callbacks.IsValid()) + { + return; + } + + // Call the callback: + cPluginLua::cOperation Op(m_Plugin); + if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnError"), a_ErrorCode, a_ErrorMsg)) + { + LOGINFO("cUDPEndpoint OnError() callback failed in plugin %s; the endpoint error is %d (%s).", + m_Plugin.GetName().c_str(), a_ErrorCode, a_ErrorMsg.c_str() + ); + } + + Terminated(); +} + + + + diff --git a/src/Bindings/LuaUDPEndpoint.h b/src/Bindings/LuaUDPEndpoint.h new file mode 100644 index 000000000..0587491ab --- /dev/null +++ b/src/Bindings/LuaUDPEndpoint.h @@ -0,0 +1,86 @@ + +// LuaUDPEndpoint.h + +// Declares the cLuaUDPEndpoint class representing a Lua wrapper for the cUDPEndpoint class and the callbacks it needs + + + + + +#pragma once + +#include "../OSSupport/Network.h" +#include "PluginLua.h" + + + + + +// fwd: +class cLuaUDPEndpoint; +typedef SharedPtr<cLuaUDPEndpoint> cLuaUDPEndpointPtr; + + + + + +class cLuaUDPEndpoint: + public cUDPEndpoint::cCallbacks +{ +public: + /** Creates a new instance of the endpoint, attached to the specified plugin and wrapping the callbacks that are in a table at the specified stack pos. */ + cLuaUDPEndpoint(cPluginLua & a_Plugin, int a_CallbacksTableStackPos); + + ~cLuaUDPEndpoint(); + + /** Opens the endpoint so that it starts listening for incoming data on the specified port. + a_Self is the shared pointer to self that the object keeps to keep itself alive for as long as it needs (for Lua). */ + bool Open(UInt16 a_Port, cLuaUDPEndpointPtr a_Self); + + /** Sends the data contained in the string to the specified remote peer. + Returns true if successful, false on immediate failure (queueing the data failed etc.) */ + bool Send(const AString & a_Data, const AString & a_RemotePeer, UInt16 a_RemotePort); + + /** Returns the port on which endpoint is listening for incoming data. */ + UInt16 GetPort(void) const; + + /** Returns true if the endpoint is open for incoming data. */ + bool IsOpen(void) const; + + /** Closes the UDP listener. */ + void Close(void); + + /** Enables outgoing broadcasts to be made using this endpoint. */ + void EnableBroadcasts(void); + + /** Called when Lua garbage-collects the object. + Releases the internal SharedPtr to self, so that the instance may be deallocated. */ + void Release(void); + +protected: + /** The plugin for which the link is created. */ + cPluginLua & m_Plugin; + + /** The Lua table that holds the callbacks to be invoked. */ + cLuaState::cRef m_Callbacks; + + /** SharedPtr to self, so that the object can keep itself alive for as long as it needs (for Lua). */ + cLuaUDPEndpointPtr m_Self; + + /** The underlying network endpoint. + May be nullptr. */ + cUDPEndpointPtr m_Endpoint; + + + /** Common code called when the endpoint is considered as terminated. + Releases m_Endpoint and m_Callbacks, each when applicable. */ + void Terminated(void); + + // cUDPEndpoint::cCallbacks overrides: + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override; + virtual void OnReceivedData(const char * a_Data, size_t a_Size, const AString & a_RemotePeer, UInt16 a_RemotePort) override; +}; + + + + diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index 56f2e73bc..cac81f325 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -3,8 +3,11 @@ #include "ManualBindings.h" #undef TOLUA_TEMPLATE_BIND +#include <sstream> +#include <iomanip> #include "tolua++/include/tolua++.h" #include "polarssl/md5.h" +#include "polarssl/sha1.h" #include "PluginLua.h" #include "PluginManager.h" #include "LuaWindow.h" @@ -28,6 +31,7 @@ #include "../LineBlockTracer.h" #include "../WorldStorage/SchematicFileSerializer.h" #include "../CompositeChat.h" +#include "../StringCompression.h" @@ -107,6 +111,146 @@ static int tolua_Clamp(lua_State * tolua_S) +static int tolua_CompressStringZLIB(lua_State * tolua_S) +{ + cLuaState S(tolua_S); + if ( + !S.CheckParamString(1) || + ( + !S.CheckParamNumber(2) && + !S.CheckParamEnd(2) + ) + ) + { + cLuaState::LogStackTrace(tolua_S); + return 0; + } + + // Get the params: + AString ToCompress; + int CompressionLevel = 5; + S.GetStackValues(1, ToCompress, CompressionLevel); + + // Compress the string: + AString res; + CompressString(ToCompress.data(), ToCompress.size(), res, CompressionLevel); + S.Push(res); + return 1; +} + + + + + +static int tolua_UncompressStringZLIB(lua_State * tolua_S) +{ + cLuaState S(tolua_S); + if ( + !S.CheckParamString(1) || + !S.CheckParamNumber(2) + ) + { + cLuaState::LogStackTrace(tolua_S); + return 0; + } + + // Get the params: + AString ToUncompress; + int UncompressedSize; + S.GetStackValues(1, ToUncompress, UncompressedSize); + + // Compress the string: + AString res; + UncompressString(ToUncompress.data(), ToUncompress.size(), res, UncompressedSize); + S.Push(res); + return 1; +} + + + + + +static int tolua_CompressStringGZIP(lua_State * tolua_S) +{ + cLuaState S(tolua_S); + if ( + !S.CheckParamString(1) || + !S.CheckParamEnd(2) + ) + { + cLuaState::LogStackTrace(tolua_S); + return 0; + } + + // Get the params: + AString ToCompress; + S.GetStackValues(1, ToCompress); + + // Compress the string: + AString res; + CompressStringGZIP(ToCompress.data(), ToCompress.size(), res); + S.Push(res); + return 1; +} + + + + + +static int tolua_UncompressStringGZIP(lua_State * tolua_S) +{ + cLuaState S(tolua_S); + if ( + !S.CheckParamString(1) || + !S.CheckParamEnd(2) + ) + { + cLuaState::LogStackTrace(tolua_S); + return 0; + } + + // Get the params: + AString ToUncompress; + S.GetStackValues(1, ToUncompress); + + // Compress the string: + AString res; + UncompressStringGZIP(ToUncompress.data(), ToUncompress.size(), res); + S.Push(res); + return 1; +} + + + + + +static int tolua_InflateString(lua_State * tolua_S) +{ + cLuaState S(tolua_S); + if ( + !S.CheckParamString(1) || + !S.CheckParamEnd(2) + ) + { + cLuaState::LogStackTrace(tolua_S); + return 0; + } + + // Get the params: + AString ToUncompress; + S.GetStackValues(1, ToUncompress); + + // Compress the string: + AString res; + InflateString(ToUncompress.data(), ToUncompress.size(), res); + S.Push(res); + return 1; +} + + + + + static int tolua_StringSplit(lua_State * tolua_S) { cLuaState LuaState(tolua_S); @@ -165,6 +309,14 @@ static AString GetLogMessage(lua_State * tolua_S) static int tolua_LOG(lua_State * tolua_S) { + // If there's no param, spit out an error message instead of crashing: + if (lua_isnil(tolua_S, 1)) + { + LOGWARNING("Attempting to LOG a nil value!"); + cLuaState::LogStackTrace(tolua_S); + return 0; + } + // If the param is a cCompositeChat, read the log level from it: cLogger::eLogLevel LogLevel = cLogger::llRegular; tolua_Error err; @@ -184,6 +336,14 @@ static int tolua_LOG(lua_State * tolua_S) static int tolua_LOGINFO(lua_State * tolua_S) { + // If there's no param, spit out an error message instead of crashing: + if (lua_isnil(tolua_S, 1)) + { + LOGWARNING("Attempting to LOGINFO a nil value!"); + cLuaState::LogStackTrace(tolua_S); + return 0; + } + cLogger::GetInstance().LogSimple(GetLogMessage(tolua_S).c_str(), cLogger::llInfo); return 0; } @@ -194,6 +354,14 @@ static int tolua_LOGINFO(lua_State * tolua_S) static int tolua_LOGWARN(lua_State * tolua_S) { + // If there's no param, spit out an error message instead of crashing: + if (lua_isnil(tolua_S, 1)) + { + LOGWARNING("Attempting to LOGWARN a nil value!"); + cLuaState::LogStackTrace(tolua_S); + return 0; + } + cLogger::GetInstance().LogSimple(GetLogMessage(tolua_S).c_str(), cLogger::llWarning); return 0; } @@ -204,6 +372,14 @@ static int tolua_LOGWARN(lua_State * tolua_S) static int tolua_LOGERROR(lua_State * tolua_S) { + // If there's no param, spit out an error message instead of crashing: + if (lua_isnil(tolua_S, 1)) + { + LOGWARNING("Attempting to LOGERROR a nil value!"); + cLuaState::LogStackTrace(tolua_S); + return 0; + } + cLogger::GetInstance().LogSimple(GetLogMessage(tolua_S).c_str(), cLogger::llError); return 0; } @@ -256,7 +432,7 @@ static int tolua_Base64Decode(lua_State * tolua_S) -static cPluginLua * GetLuaPlugin(lua_State * L) +cPluginLua * GetLuaPlugin(lua_State * L) { // Get the plugin identification out of LuaState: lua_getglobal(L, LUA_PLUGIN_INSTANCE_VAR_NAME); @@ -2171,11 +2347,16 @@ static int tolua_cPlugin_Call(lua_State * tolua_S) -static int tolua_md5(lua_State* tolua_S) +static int tolua_md5(lua_State * tolua_S) { + // Calculate the raw md5 checksum byte array: unsigned char Output[16]; size_t len = 0; const unsigned char * SourceString = (const unsigned char *)lua_tolstring(tolua_S, 1, &len); + if (SourceString == nullptr) + { + return 0; + } md5(SourceString, len, Output); lua_pushlstring(tolua_S, (const char *)Output, ARRAYCOUNT(Output)); return 1; @@ -2185,6 +2366,91 @@ static int tolua_md5(lua_State* tolua_S) +/** Does the same as tolua_md5, but reports that the usage is obsolete and the plugin should use cCrypto.md5(). */ +static int tolua_md5_obsolete(lua_State * tolua_S) +{ + LOGWARNING("Using md5() is obsolete, please change your plugin to use cCryptoHash.md5()"); + cLuaState::LogStackTrace(tolua_S); + return tolua_md5(tolua_S); +} + + + + + +static int tolua_md5HexString(lua_State * tolua_S) +{ + // Calculate the raw md5 checksum byte array: + unsigned char md5Output[16]; + size_t len = 0; + const unsigned char * SourceString = (const unsigned char *)lua_tolstring(tolua_S, 1, &len); + if (SourceString == nullptr) + { + return 0; + } + md5(SourceString, len, md5Output); + + // Convert the md5 checksum to hex string: + std::stringstream Output; + Output << std::hex << std::setfill('0'); + for (size_t i = 0; i < ARRAYCOUNT(md5Output); i++) + { + Output << std::setw(2) << static_cast<unsigned short>(md5Output[i]); // Need to cast to a number, otherwise a char is output + } + lua_pushlstring(tolua_S, Output.str().c_str(), Output.str().size()); + return 1; +} + + + + + +static int tolua_sha1(lua_State * tolua_S) +{ + // Calculate the raw SHA1 checksum byte array from the input string: + unsigned char Output[20]; + size_t len = 0; + const unsigned char * SourceString = (const unsigned char *)lua_tolstring(tolua_S, 1, &len); + if (SourceString == nullptr) + { + return 0; + } + sha1(SourceString, len, Output); + lua_pushlstring(tolua_S, (const char *)Output, ARRAYCOUNT(Output)); + return 1; +} + + + + + +static int tolua_sha1HexString(lua_State * tolua_S) +{ + // Calculate the raw SHA1 checksum byte array from the input string: + unsigned char sha1Output[20]; + size_t len = 0; + const unsigned char * SourceString = (const unsigned char *)lua_tolstring(tolua_S, 1, &len); + if (SourceString == nullptr) + { + return 0; + } + sha1(SourceString, len, sha1Output); + + // Convert the sha1 checksum to hex string: + std::stringstream Output; + Output << std::hex << std::setfill('0'); + for (size_t i = 0; i < ARRAYCOUNT(sha1Output); i++) + { + Output << std::setw(2) << static_cast<unsigned short>(sha1Output[i]); // Need to cast to a number, otherwise a char is output + } + lua_pushlstring(tolua_S, Output.str().c_str(), Output.str().size()); + return 1; +} + + + + + static int tolua_push_StringStringMap(lua_State* tolua_S, std::map< std::string, std::string >& a_StringStringMap) { lua_newtable(tolua_S); @@ -3387,6 +3653,14 @@ static int tolua_cCompositeChat_UnderlineUrls(lua_State * tolua_S) void ManualBindings::Bind(lua_State * tolua_S) { tolua_beginmodule(tolua_S, nullptr); + + // Create the new classes: + tolua_usertype(tolua_S, "cCryptoHash"); + tolua_cclass(tolua_S, "cCryptoHash", "cCryptoHash", "", nullptr); + tolua_usertype(tolua_S, "cStringCompression"); + tolua_cclass(tolua_S, "cStringCompression", "cStringCompression", "", nullptr); + + // Globals: tolua_function(tolua_S, "Clamp", tolua_Clamp); tolua_function(tolua_S, "StringSplit", tolua_StringSplit); tolua_function(tolua_S, "StringSplitAndTrim", tolua_StringSplitAndTrim); @@ -3397,6 +3671,7 @@ void ManualBindings::Bind(lua_State * tolua_S) tolua_function(tolua_S, "LOGERROR", tolua_LOGERROR); tolua_function(tolua_S, "Base64Encode", tolua_Base64Encode); tolua_function(tolua_S, "Base64Decode", tolua_Base64Decode); + tolua_function(tolua_S, "md5", tolua_md5_obsolete); // OBSOLETE, use cCryptoHash.md5() instead tolua_beginmodule(tolua_S, "cFile"); tolua_function(tolua_S, "GetFolderContents", tolua_cFile_GetFolderContents); @@ -3553,9 +3828,23 @@ void ManualBindings::Bind(lua_State * tolua_S) tolua_function(tolua_S, "GetSlotCoords", Lua_ItemGrid_GetSlotCoords); tolua_endmodule(tolua_S); - tolua_function(tolua_S, "md5", tolua_md5); + tolua_beginmodule(tolua_S, "cCryptoHash"); + tolua_function(tolua_S, "md5", tolua_md5); + tolua_function(tolua_S, "md5HexString", tolua_md5HexString); + tolua_function(tolua_S, "sha1", tolua_sha1); + tolua_function(tolua_S, "sha1HexString", tolua_sha1HexString); + tolua_endmodule(tolua_S); + + tolua_beginmodule(tolua_S, "cStringCompression"); + tolua_function(tolua_S, "CompressStringZLIB", tolua_CompressStringZLIB); + tolua_function(tolua_S, "UncompressStringZLIB", tolua_UncompressStringZLIB); + tolua_function(tolua_S, "CompressStringGZIP", tolua_CompressStringGZIP); + tolua_function(tolua_S, "UncompressStringGZIP", tolua_UncompressStringGZIP); + tolua_function(tolua_S, "InflateString", tolua_InflateString); + tolua_endmodule(tolua_S); BindRankManager(tolua_S); + BindNetwork(tolua_S); tolua_endmodule(tolua_S); } diff --git a/src/Bindings/ManualBindings.h b/src/Bindings/ManualBindings.h index 1b6e65654..74d24d5f5 100644 --- a/src/Bindings/ManualBindings.h +++ b/src/Bindings/ManualBindings.h @@ -1,6 +1,7 @@ #pragma once struct lua_State; +class cPluginLua; @@ -17,8 +18,17 @@ protected: /** Binds the manually implemented cRankManager glue code to tolua_S. Implemented in ManualBindings_RankManager.cpp. */ static void BindRankManager(lua_State * tolua_S); + + /** Binds the manually implemented cNetwork-related API to tolua_S. + Implemented in ManualBindings_Network.cpp. */ + static void BindNetwork(lua_State * tolua_S); }; +extern cPluginLua * GetLuaPlugin(lua_State * L); + + + + diff --git a/src/Bindings/ManualBindings_Network.cpp b/src/Bindings/ManualBindings_Network.cpp new file mode 100644 index 000000000..a628eb9ca --- /dev/null +++ b/src/Bindings/ManualBindings_Network.cpp @@ -0,0 +1,942 @@ + +// ManualBindings_Network.cpp + +// Implements the cNetwork-related API bindings for Lua + +#include "Globals.h" +#include "LuaTCPLink.h" +#include "ManualBindings.h" +#include "tolua++/include/tolua++.h" +#include "LuaState.h" +#include "LuaTCPLink.h" +#include "LuaNameLookup.h" +#include "LuaServerHandle.h" +#include "LuaUDPEndpoint.h" + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cNetwork API functions: + +/** Binds cNetwork::Connect */ +static int tolua_cNetwork_Connect(lua_State * L) +{ + // Function signature: + // cNetwork:Connect(Host, Port, Callbacks) -> bool + + cLuaState S(L); + if ( + !S.CheckParamUserTable(1, "cNetwork") || + !S.CheckParamString(2) || + !S.CheckParamNumber(3) || + !S.CheckParamTable(4) || + !S.CheckParamEnd(5) + ) + { + return 0; + } + + // Get the plugin instance: + cPluginLua * Plugin = GetLuaPlugin(L); + if (Plugin == nullptr) + { + // An error message has been already printed in GetLuaPlugin() + S.Push(false); + return 1; + } + + // Read the params: + AString Host; + int Port; + S.GetStackValues(2, Host, Port); + + // Check validity: + if ((Port < 0) || (Port > 65535)) + { + LOGWARNING("cNetwork:Connect() called with invalid port (%d), failing the request.", Port); + S.Push(false); + return 1; + } + + // Create the LuaTCPLink glue class: + auto Link = std::make_shared<cLuaTCPLink>(*Plugin, 4); + + // Try to connect: + bool res = cNetwork::Connect(Host, static_cast<UInt16>(Port), Link, Link); + S.Push(res); + + return 1; +} + + + + + +/** Binds cNetwork::CreateUDPEndpoint */ +static int tolua_cNetwork_CreateUDPEndpoint(lua_State * L) +{ + // Function signature: + // cNetwork:CreateUDPEndpoint(Port, Callbacks) -> cUDPEndpoint + + cLuaState S(L); + if ( + !S.CheckParamUserTable(1, "cNetwork") || + !S.CheckParamNumber(2) || + !S.CheckParamTable(3) || + !S.CheckParamEnd(4) + ) + { + return 0; + } + + // Get the plugin instance: + cPluginLua * Plugin = GetLuaPlugin(L); + if (Plugin == nullptr) + { + // An error message has been already printed in GetLuaPlugin() + S.Push(false); + return 1; + } + + // Read the params: + int Port; + S.GetStackValues(2, Port); + + // Check validity: + if ((Port < 0) || (Port > 65535)) + { + LOGWARNING("cNetwork:CreateUDPEndpoint() called with invalid port (%d), failing the request.", Port); + S.Push(false); + return 1; + } + + // Create the LuaUDPEndpoint glue class: + auto Endpoint = std::make_shared<cLuaUDPEndpoint>(*Plugin, 3); + Endpoint->Open(Port, Endpoint); + + // Register the endpoint to be garbage-collected by Lua: + tolua_pushusertype(L, Endpoint.get(), "cUDPEndpoint"); + tolua_register_gc(L, lua_gettop(L)); + + // Return the endpoint object: + S.Push(Endpoint.get()); + return 1; +} + + + + + +/** Binds cNetwork::HostnameToIP */ +static int tolua_cNetwork_HostnameToIP(lua_State * L) +{ + // Function signature: + // cNetwork:HostnameToIP(Host, Callbacks) -> bool + + cLuaState S(L); + if ( + !S.CheckParamUserTable(1, "cNetwork") || + !S.CheckParamString(2) || + !S.CheckParamTable(3) || + !S.CheckParamEnd(4) + ) + { + return 0; + } + + // Get the plugin instance: + cPluginLua * Plugin = GetLuaPlugin(L); + if (Plugin == nullptr) + { + // An error message has been already printed in GetLuaPlugin() + S.Push(false); + return 1; + } + + // Read the params: + AString Host; + S.GetStackValue(2, Host); + + // Try to look up: + bool res = cNetwork::HostnameToIP(Host, std::make_shared<cLuaNameLookup>(Host, *Plugin, 3)); + S.Push(res); + + return 1; +} + + + + + +/** Binds cNetwork::IPToHostname */ +static int tolua_cNetwork_IPToHostname(lua_State * L) +{ + // Function signature: + // cNetwork:IPToHostname(IP, Callbacks) -> bool + + cLuaState S(L); + if ( + !S.CheckParamUserTable(1, "cNetwork") || + !S.CheckParamString(2) || + !S.CheckParamTable(3) || + !S.CheckParamEnd(4) + ) + { + return 0; + } + + // Get the plugin instance: + cPluginLua * Plugin = GetLuaPlugin(L); + if (Plugin == nullptr) + { + // An error message has been already printed in GetLuaPlugin() + S.Push(false); + return 1; + } + + // Read the params: + AString Host; + S.GetStackValue(2, Host); + + // Try to look up: + bool res = cNetwork::IPToHostName(Host, std::make_shared<cLuaNameLookup>(Host, *Plugin, 3)); + S.Push(res); + + return 1; +} + + + + + +/** Binds cNetwork::Listen */ +static int tolua_cNetwork_Listen(lua_State * L) +{ + // Function signature: + // cNetwork:Listen(Port, Callbacks) -> cServerHandle + + cLuaState S(L); + if ( + !S.CheckParamUserTable(1, "cNetwork") || + !S.CheckParamNumber(2) || + !S.CheckParamTable(3) || + !S.CheckParamEnd(4) + ) + { + return 0; + } + + // Get the plugin instance: + cPluginLua * Plugin = GetLuaPlugin(L); + if (Plugin == nullptr) + { + // An error message has been already printed in GetLuaPlugin() + S.Push(false); + return 1; + } + + // Read the params: + int Port; + S.GetStackValues(2, Port); + if ((Port < 0) || (Port > 65535)) + { + LOGWARNING("cNetwork:Listen() called with invalid port (%d), failing the request.", Port); + S.Push(false); + return 1; + } + UInt16 Port16 = static_cast<UInt16>(Port); + + // Create the LuaTCPLink glue class: + auto Srv = std::make_shared<cLuaServerHandle>(Port16, *Plugin, 3); + + // Listen: + Srv->SetServerHandle(cNetwork::Listen(Port16, Srv), Srv); + + // Register the server to be garbage-collected by Lua: + tolua_pushusertype(L, Srv.get(), "cServerHandle"); + tolua_register_gc(L, lua_gettop(L)); + + // Return the server handle wrapper: + S.Push(Srv.get()); + return 1; +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cServerHandle bindings (routed through cLuaServerHandle): + +/** Called when Lua destroys the object instance. +Close the server and let it deallocate on its own (it's in a SharedPtr). */ +static int tolua_collect_cServerHandle(lua_State * L) +{ + cLuaServerHandle * Srv = static_cast<cLuaServerHandle *>(tolua_tousertype(L, 1, nullptr)); + Srv->Release(); + return 0; +} + + + + + +/** Binds cLuaServerHandle::Close */ +static int tolua_cServerHandle_Close(lua_State * L) +{ + // Function signature: + // ServerInstance:Close() + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cServerHandle") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the server handle: + cLuaServerHandle * Srv; + if (lua_isnil(L, 1)) + { + LOGWARNING("cServerHandle:Close(): invalid server handle object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + Srv = *static_cast<cLuaServerHandle **>(lua_touserdata(L, 1)); + + // Close it: + Srv->Close(); + return 0; +} + + + + + +/** Binds cLuaServerHandle::IsListening */ +static int tolua_cServerHandle_IsListening(lua_State * L) +{ + // Function signature: + // ServerInstance:IsListening() -> bool + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cServerHandle") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the server handle: + cLuaServerHandle * Srv; + if (lua_isnil(L, 1)) + { + LOGWARNING("cServerHandle:IsListening(): invalid server handle object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + Srv = *static_cast<cLuaServerHandle **>(lua_touserdata(L, 1)); + + // Close it: + S.Push(Srv->IsListening()); + return 1; +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cTCPLink bindings (routed through cLuaTCPLink): + +/** Binds cLuaTCPLink::Close */ +static int tolua_cTCPLink_Close(lua_State * L) +{ + // Function signature: + // LinkInstance:Close() + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cTCPLink") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the link: + cLuaTCPLink * Link; + if (lua_isnil(L, 1)) + { + LOGWARNING("cTCPLink:Close(): invalid link object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1)); + + // CLose the link: + Link->Close(); + return 0; +} + + + + + +/** Binds cLuaTCPLink::GetLocalIP */ +static int tolua_cTCPLink_GetLocalIP(lua_State * L) +{ + // Function signature: + // LinkInstance:GetLocalIP() -> string + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cTCPLink") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the link: + cLuaTCPLink * Link; + if (lua_isnil(L, 1)) + { + LOGWARNING("cTCPLink:GetLocalIP(): invalid link object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1)); + + // Get the IP: + S.Push(Link->GetLocalIP()); + return 1; +} + + + + + +/** Binds cLuaTCPLink::GetLocalPort */ +static int tolua_cTCPLink_GetLocalPort(lua_State * L) +{ + // Function signature: + // LinkInstance:GetLocalPort() -> number + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cTCPLink") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the link: + cLuaTCPLink * Link; + if (lua_isnil(L, 1)) + { + LOGWARNING("cTCPLink:GetLocalPort(): invalid link object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1)); + + // Get the Port: + S.Push(Link->GetLocalPort()); + return 1; +} + + + + + +/** Binds cLuaTCPLink::GetRemoteIP */ +static int tolua_cTCPLink_GetRemoteIP(lua_State * L) +{ + // Function signature: + // LinkInstance:GetRemoteIP() -> string + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cTCPLink") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the link: + cLuaTCPLink * Link; + if (lua_isnil(L, 1)) + { + LOGWARNING("cTCPLink:GetRemoteIP(): invalid link object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1)); + + // Get the IP: + S.Push(Link->GetRemoteIP()); + return 1; +} + + + + + +/** Binds cLuaTCPLink::GetRemotePort */ +static int tolua_cTCPLink_GetRemotePort(lua_State * L) +{ + // Function signature: + // LinkInstance:GetRemotePort() -> number + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cTCPLink") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the link: + cLuaTCPLink * Link; + if (lua_isnil(L, 1)) + { + LOGWARNING("cTCPLink:GetRemotePort(): invalid link object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1)); + + // Get the Port: + S.Push(Link->GetRemotePort()); + return 1; +} + + + + + +/** Binds cLuaTCPLink::Send */ +static int tolua_cTCPLink_Send(lua_State * L) +{ + // Function signature: + // LinkInstance:Send(DataString) + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cTCPLink") || + !S.CheckParamString(2) || + !S.CheckParamEnd(3) + ) + { + return 0; + } + + // Get the link: + cLuaTCPLink * Link; + if (lua_isnil(L, 1)) + { + LOGWARNING("cTCPLink:Send(): invalid link object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1)); + + // Get the data to send: + AString Data; + S.GetStackValues(2, Data); + + // Send the data: + Link->Send(Data); + return 0; +} + + + + + +/** Binds cLuaTCPLink::Shutdown */ +static int tolua_cTCPLink_Shutdown(lua_State * L) +{ + // Function signature: + // LinkInstance:Shutdown() + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cTCPLink") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the link: + cLuaTCPLink * Link; + if (lua_isnil(L, 1)) + { + LOGWARNING("cTCPLink:Shutdown(): invalid link object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1)); + + // Shutdown the link: + Link->Shutdown(); + return 0; +} + + + + + +/** Binds cLuaTCPLink::StartTLSClient */ +static int tolua_cTCPLink_StartTLSClient(lua_State * L) +{ + // Function signature: + // LinkInstance:StartTLSClient(OwnCert, OwnPrivKey, OwnPrivKeyPassword) -> [true] or [nil, ErrMsg] + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cTCPLink") || + !S.CheckParamString(2, 4) || + !S.CheckParamEnd(5) + ) + { + return 0; + } + + // Get the link: + cLuaTCPLink * Link; + if (lua_isnil(L, 1)) + { + LOGWARNING("cTCPLink:StartTLSClient(): invalid link object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1)); + + // Read the params: + AString OwnCert, OwnPrivKey, OwnPrivKeyPassword; + S.GetStackValues(2, OwnCert, OwnPrivKey, OwnPrivKeyPassword); + + // Start the TLS handshake: + AString res = Link->StartTLSClient(OwnCert, OwnPrivKey, OwnPrivKeyPassword); + if (!res.empty()) + { + S.PushNil(); + S.Push(Printf("Cannot start TLS on link to %s:%d: %s", Link->GetRemoteIP().c_str(), Link->GetRemotePort(), res.c_str())); + return 2; + } + return 1; +} + + + + + +/** Binds cLuaTCPLink::StartTLSServer */ +static int tolua_cTCPLink_StartTLSServer(lua_State * L) +{ + // Function signature: + // LinkInstance:StartTLSServer(OwnCert, OwnPrivKey, OwnPrivKeyPassword, StartTLSData) -> [true] or [nil, ErrMsg] + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cTCPLink") || + !S.CheckParamString(2, 4) || + // Param 5 is optional, don't check + !S.CheckParamEnd(6) + ) + { + return 0; + } + + // Get the link: + cLuaTCPLink * Link; + if (lua_isnil(L, 1)) + { + LOGWARNING("cTCPLink:StartTLSServer(): invalid link object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1)); + + // Read the params: + AString OwnCert, OwnPrivKey, OwnPrivKeyPassword, StartTLSData; + S.GetStackValues(2, OwnCert, OwnPrivKey, OwnPrivKeyPassword, StartTLSData); + + // Start the TLS handshake: + AString res = Link->StartTLSServer(OwnCert, OwnPrivKey, OwnPrivKeyPassword, StartTLSData); + if (!res.empty()) + { + S.PushNil(); + S.Push(Printf("Cannot start TLS on link to %s:%d: %s", Link->GetRemoteIP().c_str(), Link->GetRemotePort(), res.c_str())); + return 2; + } + return 1; +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cUDPEndpoint bindings (routed through cLuaUDPEndpoint): + +/** Called when Lua destroys the object instance. +Close the endpoint and let it deallocate on its own (it's in a SharedPtr). */ +static int tolua_collect_cUDPEndpoint(lua_State * L) +{ + LOGD("Lua: Collecting cUDPEndpoint"); + cLuaUDPEndpoint * Endpoint = static_cast<cLuaUDPEndpoint *>(tolua_tousertype(L, 1, nullptr)); + Endpoint->Release(); + return 0; +} + + + + + +/** Binds cLuaUDPEndpoint::Close */ +static int tolua_cUDPEndpoint_Close(lua_State * L) +{ + // Function signature: + // EndpointInstance:Close() + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cUDPEndpoint") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the endpoint: + if (lua_isnil(L, 1)) + { + LOGWARNING("cUDPEndpoint:Close(): invalid endpoint object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + auto Endpoint = *static_cast<cLuaUDPEndpoint **>(lua_touserdata(L, 1)); + + // Close it: + Endpoint->Close(); + return 0; +} + + + + + +/** Binds cLuaUDPEndpoint::EnableBroadcasts */ +static int tolua_cUDPEndpoint_EnableBroadcasts(lua_State * L) +{ + // Function signature: + // EndpointInstance:EnableBroadcasts() + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cUDPEndpoint") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the endpoint: + if (lua_isnil(L, 1)) + { + LOGWARNING("cUDPEndpoint:EnableBroadcasts(): invalid endpoint object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + auto Endpoint = *static_cast<cLuaUDPEndpoint **>(lua_touserdata(L, 1)); + + // Enable the broadcasts: + Endpoint->EnableBroadcasts(); + return 0; +} + + + + + +/** Binds cLuaUDPEndpoint::GetPort */ +static int tolua_cUDPEndpoint_GetPort(lua_State * L) +{ + // Function signature: + // Endpoint:GetPort() -> number + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cUDPEndpoint") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the endpoint: + if (lua_isnil(L, 1)) + { + LOGWARNING("cUDPEndpoint:GetPort(): invalid endpoint object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + auto Endpoint = *static_cast<cLuaUDPEndpoint **>(lua_touserdata(L, 1)); + + // Get the Port: + S.Push(Endpoint->GetPort()); + return 1; +} + + + + + +/** Binds cLuaUDPEndpoint::IsOpen */ +static int tolua_cUDPEndpoint_IsOpen(lua_State * L) +{ + // Function signature: + // Endpoint:IsOpen() -> bool + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cUDPEndpoint") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the endpoint: + if (lua_isnil(L, 1)) + { + LOGWARNING("cUDPEndpoint:IsListening(): invalid server handle object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + auto Endpoint = *static_cast<cLuaUDPEndpoint **>(lua_touserdata(L, 1)); + + // Close it: + S.Push(Endpoint->IsOpen()); + return 1; +} + + + + + +/** Binds cLuaUDPEndpoint::Send */ +static int tolua_cUDPEndpoint_Send(lua_State * L) +{ + // Function signature: + // LinkInstance:Send(DataString) + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cUDPEndpoint") || + !S.CheckParamString(2, 3) || + !S.CheckParamNumber(4) || + !S.CheckParamEnd(5) + ) + { + return 0; + } + + // Get the link: + if (lua_isnil(L, 1)) + { + LOGWARNING("cUDPEndpoint:Send(): invalid endpoint object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + auto Endpoint = *static_cast<cLuaUDPEndpoint **>(lua_touserdata(L, 1)); + + // Get the data to send: + AString Data, RemotePeer; + int RemotePort; + S.GetStackValues(2, Data, RemotePeer, RemotePort); + + // Check the port: + if ((RemotePort < 0) || (RemotePort > USHRT_MAX)) + { + LOGWARNING("cUDPEndpoint:Send() called with invalid port (%d), failing.", RemotePort); + S.LogStackTrace(); + S.Push(false); + return 1; + } + + // Send the data: + S.Push(Endpoint->Send(Data, RemotePeer, static_cast<UInt16>(RemotePort))); + return 1; +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// Register the bindings: + +void ManualBindings::BindNetwork(lua_State * tolua_S) +{ + // Create the cNetwork API classes: + tolua_usertype(tolua_S, "cNetwork"); + tolua_cclass(tolua_S, "cNetwork", "cNetwork", "", nullptr); + tolua_usertype(tolua_S, "cTCPLink"); + tolua_cclass(tolua_S, "cTCPLink", "cTCPLink", "", nullptr); + tolua_usertype(tolua_S, "cServerHandle"); + tolua_cclass(tolua_S, "cServerHandle", "cServerHandle", "", tolua_collect_cServerHandle); + tolua_usertype(tolua_S, "cUDPEndpoint"); + tolua_cclass(tolua_S, "cUDPEndpoint", "cUDPEndpoint", "", tolua_collect_cUDPEndpoint); + + // Fill in the functions (alpha-sorted): + tolua_beginmodule(tolua_S, "cNetwork"); + tolua_function(tolua_S, "Connect", tolua_cNetwork_Connect); + tolua_function(tolua_S, "CreateUDPEndpoint", tolua_cNetwork_CreateUDPEndpoint); + tolua_function(tolua_S, "HostnameToIP", tolua_cNetwork_HostnameToIP); + tolua_function(tolua_S, "IPToHostname", tolua_cNetwork_IPToHostname); + tolua_function(tolua_S, "Listen", tolua_cNetwork_Listen); + tolua_endmodule(tolua_S); + + tolua_beginmodule(tolua_S, "cServerHandle"); + tolua_function(tolua_S, "Close", tolua_cServerHandle_Close); + tolua_function(tolua_S, "IsListening", tolua_cServerHandle_IsListening); + tolua_endmodule(tolua_S); + + tolua_beginmodule(tolua_S, "cTCPLink"); + tolua_function(tolua_S, "Close", tolua_cTCPLink_Close); + tolua_function(tolua_S, "GetLocalIP", tolua_cTCPLink_GetLocalIP); + tolua_function(tolua_S, "GetLocalPort", tolua_cTCPLink_GetLocalPort); + tolua_function(tolua_S, "GetRemoteIP", tolua_cTCPLink_GetRemoteIP); + tolua_function(tolua_S, "GetRemotePort", tolua_cTCPLink_GetRemotePort); + tolua_function(tolua_S, "Send", tolua_cTCPLink_Send); + tolua_function(tolua_S, "Shutdown", tolua_cTCPLink_Shutdown); + tolua_function(tolua_S, "StartTLSClient", tolua_cTCPLink_StartTLSClient); + tolua_function(tolua_S, "StartTLSServer", tolua_cTCPLink_StartTLSServer); + tolua_endmodule(tolua_S); + + tolua_beginmodule(tolua_S, "cUDPEndpoint"); + tolua_function(tolua_S, "Close", tolua_cUDPEndpoint_Close); + tolua_function(tolua_S, "EnableBroadcasts", tolua_cUDPEndpoint_EnableBroadcasts); + tolua_function(tolua_S, "GetPort", tolua_cUDPEndpoint_GetPort); + tolua_function(tolua_S, "IsOpen", tolua_cUDPEndpoint_IsOpen); + tolua_function(tolua_S, "Send", tolua_cUDPEndpoint_Send); + tolua_endmodule(tolua_S); + +} + + + + diff --git a/src/Bindings/Plugin.h b/src/Bindings/Plugin.h index 6210dbed4..6ade8ef9f 100644 --- a/src/Bindings/Plugin.h +++ b/src/Bindings/Plugin.h @@ -57,6 +57,7 @@ public: virtual bool OnCraftingNoRecipe (cPlayer & a_Player, cCraftingGrid & a_Grid, cCraftingRecipe & a_Recipe) = 0; virtual bool OnDisconnect (cClientHandle & a_Client, const AString & a_Reason) = 0; virtual bool OnEntityAddEffect (cEntity & a_Entity, int a_EffectType, int a_EffectDurationTicks, int a_EffectIntensity, double a_DistanceModifier) = 0; + virtual bool OnEntityTeleport (cEntity & a_Entity, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition) = 0; virtual bool OnExecuteCommand (cPlayer * a_Player, const AStringVector & a_Split) = 0; virtual bool OnExploded (cWorld & a_World, double a_ExplosionSize, bool a_CanCauseFire, double a_X, double a_Y, double a_Z, eExplosionSource a_Source, void * a_SourceData) = 0; virtual bool OnExploding (cWorld & a_World, double & a_ExplosionSize, bool & a_CanCauseFire, double a_X, double a_Y, double a_Z, eExplosionSource a_Source, void * a_SourceData) = 0; diff --git a/src/Bindings/PluginLua.cpp b/src/Bindings/PluginLua.cpp index 500913e76..fb7650d42 100644 --- a/src/Bindings/PluginLua.cpp +++ b/src/Bindings/PluginLua.cpp @@ -857,6 +857,26 @@ bool cPluginLua::OnPlayerMoving(cPlayer & a_Player, const Vector3d & a_OldPositi +bool cPluginLua::OnEntityTeleport(cEntity & a_Entity, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition) +{ + cCSLock Lock(m_CriticalSection); + bool res = false; + cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_ENTITY_TELEPORT]; + for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) + { + m_LuaState.Call((int)(**itr), &a_Entity, a_OldPosition, a_NewPosition, cLuaState::Return, res); + if (res) + { + return true; + } + } + return false; +} + + + + + bool cPluginLua::OnPlayerPlacedBlock(cPlayer & a_Player, const sSetBlock & a_BlockChange) { cCSLock Lock(m_CriticalSection); @@ -1577,6 +1597,7 @@ const char * cPluginLua::GetHookFnName(int a_HookType) case cPluginManager::HOOK_DISCONNECT: return "OnDisconnect"; case cPluginManager::HOOK_PLAYER_ANIMATION: return "OnPlayerAnimation"; case cPluginManager::HOOK_ENTITY_ADD_EFFECT: return "OnEntityAddEffect"; + case cPluginManager::HOOK_ENTITY_TELEPORT: return "OnEntityTeleport"; case cPluginManager::HOOK_EXECUTE_COMMAND: return "OnExecuteCommand"; case cPluginManager::HOOK_HANDSHAKE: return "OnHandshake"; case cPluginManager::HOOK_KILLING: return "OnKilling"; diff --git a/src/Bindings/PluginLua.h b/src/Bindings/PluginLua.h index f443f5fc0..7b528501b 100644 --- a/src/Bindings/PluginLua.h +++ b/src/Bindings/PluginLua.h @@ -106,6 +106,7 @@ public: virtual bool OnPlayerRightClickingEntity(cPlayer & a_Player, cEntity & a_Entity) override; virtual bool OnPlayerShooting (cPlayer & a_Player) override; virtual bool OnPlayerSpawned (cPlayer & a_Player) override; + virtual bool OnEntityTeleport (cEntity & a_Entity, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition) override; virtual bool OnPlayerTossingItem (cPlayer & a_Player) override; virtual bool OnPlayerUsedBlock (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override; virtual bool OnPlayerUsedItem (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ) override; diff --git a/src/Bindings/PluginManager.cpp b/src/Bindings/PluginManager.cpp index 9d86c64a2..41b36337e 100644 --- a/src/Bindings/PluginManager.cpp +++ b/src/Bindings/PluginManager.cpp @@ -505,6 +505,24 @@ bool cPluginManager::CallHookEntityAddEffect(cEntity & a_Entity, int a_EffectTyp +bool cPluginManager::CallHookEntityTeleport(cEntity & a_Entity, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition) +{ + FIND_HOOK(HOOK_ENTITY_TELEPORT); + VERIFY_HOOK; + + for (PluginList::iterator itr = Plugins->second.begin(); itr != Plugins->second.end(); ++itr) + { + if ((*itr)->OnEntityTeleport(a_Entity, a_OldPosition, a_NewPosition)) + { + return true; + } + } + return false; +} + + + + bool cPluginManager::CallHookExecuteCommand(cPlayer * a_Player, const AStringVector & a_Split) { FIND_HOOK(HOOK_EXECUTE_COMMAND); diff --git a/src/Bindings/PluginManager.h b/src/Bindings/PluginManager.h index 97e91c1df..c8b4de9d6 100644 --- a/src/Bindings/PluginManager.h +++ b/src/Bindings/PluginManager.h @@ -109,6 +109,7 @@ public: HOOK_PLAYER_RIGHT_CLICKING_ENTITY, HOOK_PLAYER_SHOOTING, HOOK_PLAYER_SPAWNED, + HOOK_ENTITY_TELEPORT, HOOK_PLAYER_TOSSING_ITEM, HOOK_PLAYER_USED_BLOCK, HOOK_PLAYER_USED_ITEM, @@ -190,6 +191,7 @@ public: bool CallHookCraftingNoRecipe (cPlayer & a_Player, cCraftingGrid & a_Grid, cCraftingRecipe & a_Recipe); bool CallHookDisconnect (cClientHandle & a_Client, const AString & a_Reason); bool CallHookEntityAddEffect (cEntity & a_Entity, int a_EffectType, int a_EffectDurationTicks, int a_EffectIntensity, double a_DistanceModifier); + bool CallHookEntityTeleport (cEntity & a_Entity, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition); bool CallHookExecuteCommand (cPlayer * a_Player, const AStringVector & a_Split); // If a_Player == nullptr, it is a console cmd bool CallHookExploded (cWorld & a_World, double a_ExplosionSize, bool a_CanCauseFire, double a_X, double a_Y, double a_Z, eExplosionSource a_Source, void * a_SourceData); bool CallHookExploding (cWorld & a_World, double & a_ExplosionSize, bool & a_CanCauseFire, double a_X, double a_Y, double a_Z, eExplosionSource a_Source, void * a_SourceData); diff --git a/src/BiomeDef.cpp b/src/BiomeDef.cpp index 188e06173..3e34ebdbe 100644 --- a/src/BiomeDef.cpp +++ b/src/BiomeDef.cpp @@ -222,3 +222,130 @@ bool IsBiomeCold(EMCSBiome a_Biome) + +int GetSnowStartHeight(EMCSBiome a_Biome) +{ + switch (a_Biome) + { + case biIcePlainsSpikes: + case biIcePlains: + case biIceMountains: + case biFrozenRiver: + case biColdBeach: + case biColdTaiga: + case biColdTaigaHills: + case biColdTaigaM: + { + // Always snow + return 0; + } + + case biExtremeHills: + case biExtremeHillsM: + case biExtremeHillsPlus: + case biExtremeHillsPlusM: + case biStoneBeach: + { + // Starts snowing at 96 + return 96; + } + + case biTaiga: + case biTaigaHills: + case biTaigaM: + { + // Start snowing at 130 + return 130; + } + + case biMegaTaiga: + case biMegaSpruceTaiga: + case biMegaTaigaHills: + case biMegaSpruceTaigaHills: + { + // Start snowing at 160 + return 160; + } + + case biRiver: + case biOcean: + case biDeepOcean: + { + // Starts snowing at 280 + return 280; + } + + case biBirchForest: + case biBirchForestHills: + case biBirchForestM: + case biBirchForestHillsM: + { + // Starts snowing at 335 + return 335; + } + + case biForest: + case biForestHills: + case biFlowerForest: + case biRoofedForest: + case biRoofedForestM: + { + // Starts snowing at 400 + return 400; + } + + case biPlains: + case biSunflowerPlains: + case biSwampland: + case biSwamplandM: + case biBeach: + { + // Starts snowing at 460 + return 460; + } + + case biMushroomIsland: + case biMushroomShore: + { + // Starts snowing at 520 + return 520; + } + + case biJungle: + case biJungleHills: + case biJungleM: + case biJungleEdge: + case biJungleEdgeM: + { + // Starts snowing at 550 + return 550; + } + + case biDesert: + case biDesertHills: + case biDesertM: + case biSavanna: + case biSavannaM: + case biSavannaPlateau: + case biSavannaPlateauM: + case biMesa: + case biMesaBryce: + case biMesaPlateau: + case biMesaPlateauF: + case biMesaPlateauFM: + case biMesaPlateauM: + { + // These biomes don't actualy have any downfall. + return 1000; + } + + default: + { + return 0; + } + } +} + + + + diff --git a/src/BiomeDef.h b/src/BiomeDef.h index 84751cfd7..cda12556a 100644 --- a/src/BiomeDef.h +++ b/src/BiomeDef.h @@ -129,4 +129,7 @@ extern bool IsBiomeVeryCold(EMCSBiome a_Biome); Doesn't report Very Cold biomes, use IsBiomeVeryCold() for those. */ extern bool IsBiomeCold(EMCSBiome a_Biome); +/** Returns the height when a biome when a biome starts snowing.*/ +extern int GetSnowStartHeight(EMCSBiome a_Biome); + // tolua_end diff --git a/src/Blocks/BlockDirt.h b/src/Blocks/BlockDirt.h index aae6719e2..12bca92dd 100644 --- a/src/Blocks/BlockDirt.h +++ b/src/Blocks/BlockDirt.h @@ -52,7 +52,19 @@ public: return; } } - + + // Make sure that there is enough light at the source block to spread + if (!a_Chunk.GetWorld()->IsChunkLighted(a_Chunk.GetPosX(), a_Chunk.GetPosZ())) + { + a_Chunk.GetWorld()->QueueLightChunk(a_Chunk.GetPosX(), a_Chunk.GetPosZ()); + return; + } + else if (std::max(a_Chunk.GetBlockLight(a_RelX, a_RelY + 1, a_RelZ), a_Chunk.GetTimeAlteredLight(a_Chunk.GetSkyLight(a_RelX, a_RelY + 1, a_RelZ))) < 9) + { + // Source block is not bright enough to spread + return; + } + // Grass spreads to adjacent dirt blocks: cFastRandom rand; for (int i = 0; i < 2; i++) // Pick two blocks to grow to diff --git a/src/Blocks/BlockOre.h b/src/Blocks/BlockOre.h index f6ea3aa3c..08d79f435 100644 --- a/src/Blocks/BlockOre.h +++ b/src/Blocks/BlockOre.h @@ -11,6 +11,7 @@ class cBlockOreHandler : public cBlockHandler { + typedef cBlockHandler super; public: cBlockOreHandler(BLOCKTYPE a_BlockType) : cBlockHandler(a_BlockType) @@ -56,6 +57,64 @@ public: } } } + + virtual void OnDestroyedByPlayer(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, int a_BlockX, int a_BlockY, int a_BlockZ) override + { + super::OnDestroyedByPlayer(a_ChunkInterface, a_WorldInterface, a_Player, a_BlockX, a_BlockY, a_BlockZ); + + if (a_Player->IsGameModeCreative()) + { + // Don't drop XP when the player is in creative mode. + return; + } + + if (a_Player->GetEquippedItem().m_Enchantments.GetLevel(cEnchantments::enchSilkTouch) != 0) + { + // Don't drop XP when the ore is mined with the Silk Touch enchantment + return; + } + + cFastRandom Random; + int Reward = 0; + + switch (m_BlockType) + { + case E_BLOCK_NETHER_QUARTZ_ORE: + case E_BLOCK_LAPIS_ORE: + { + // Lapis and nether quartz get 2 - 5 experience + Reward = Random.NextInt(4) + 2; + break; + } + case E_BLOCK_REDSTONE_ORE: + case E_BLOCK_REDSTONE_ORE_GLOWING: + { + // Redstone gets 1 - 5 experience + Reward = Random.NextInt(5) + 1; + break; + } + case E_BLOCK_DIAMOND_ORE: + case E_BLOCK_EMERALD_ORE: + { + // Diamond and emerald get 3 - 7 experience + Reward = Random.NextInt(5) + 3; + break; + } + case E_BLOCK_COAL_ORE: + { + // Coal gets 0 - 2 experience + Reward = Random.NextInt(3); + break; + } + + default: break; + } + + if (Reward != 0) + { + a_WorldInterface.SpawnExperienceOrb(a_BlockX, a_BlockY, a_BlockZ, Reward); + } + } } ; diff --git a/src/CheckBasicStyle.lua b/src/CheckBasicStyle.lua index 0c7b05d6d..648a5711b 100644 --- a/src/CheckBasicStyle.lua +++ b/src/CheckBasicStyle.lua @@ -167,6 +167,7 @@ local function ProcessFile(a_FileName) os.exit(1) end local all = f:read("*all") + f:close() -- Check that the last line is empty - otherwise processing won't work properly: local lastChar = string.byte(all, string.len(all)) diff --git a/src/Chunk.cpp b/src/Chunk.cpp index 979492b46..00ac1fdb1 100644 --- a/src/Chunk.cpp +++ b/src/Chunk.cpp @@ -776,10 +776,22 @@ void cChunk::BroadcastPendingBlockChanges(void) { return; } - - for (cClientHandleList::iterator itr = m_LoadedByClient.begin(), end = m_LoadedByClient.end(); itr != end; ++itr) + + if (m_PendingSendBlocks.size() >= 10240) + { + // Resend the full chunk + for (cClientHandleList::iterator itr = m_LoadedByClient.begin(), end = m_LoadedByClient.end(); itr != end; ++itr) + { + m_World->ForceSendChunkTo(m_PosX, m_PosZ, cChunkSender::E_CHUNK_PRIORITY_MEDIUM, (*itr)); + } + } + else { - (*itr)->SendBlockChanges(m_PosX, m_PosZ, m_PendingSendBlocks); + // Only send block changes + for (cClientHandleList::iterator itr = m_LoadedByClient.begin(), end = m_LoadedByClient.end(); itr != end; ++itr) + { + (*itr)->SendBlockChanges(m_PosX, m_PosZ, m_PendingSendBlocks); + } } m_PendingSendBlocks.clear(); } @@ -874,80 +886,71 @@ void cChunk::ApplyWeatherToTop() int X = m_World->GetTickRandomNumber(15); int Z = m_World->GetTickRandomNumber(15); - switch (GetBiomeAt(X, Z)) - { - case biTaiga: - case biFrozenOcean: - case biFrozenRiver: - case biIcePlains: - case biIceMountains: - case biTaigaHills: - { - // TODO: Check light levels, don't snow over when the BlockLight is higher than (7?) - int Height = GetHeight(X, Z); - BLOCKTYPE TopBlock = GetBlock(X, Height, Z); - NIBBLETYPE TopMeta = GetMeta (X, Height, Z); - if (m_World->IsDeepSnowEnabled() && (TopBlock == E_BLOCK_SNOW)) + + // TODO: Check light levels, don't snow over when the BlockLight is higher than (7?) + int Height = GetHeight(X, Z); + + if (GetSnowStartHeight(GetBiomeAt(X, Z)) > Height) + { + return; + } + + BLOCKTYPE TopBlock = GetBlock(X, Height, Z); + NIBBLETYPE TopMeta = GetMeta (X, Height, Z); + if (m_World->IsDeepSnowEnabled() && (TopBlock == E_BLOCK_SNOW)) + { + int MaxSize = 7; + BLOCKTYPE BlockType[4]; + NIBBLETYPE BlockMeta[4]; + UnboundedRelGetBlock(X - 1, Height, Z, BlockType[0], BlockMeta[0]); + UnboundedRelGetBlock(X + 1, Height, Z, BlockType[1], BlockMeta[1]); + UnboundedRelGetBlock(X, Height, Z - 1, BlockType[2], BlockMeta[2]); + UnboundedRelGetBlock(X, Height, Z + 1, BlockType[3], BlockMeta[3]); + for (int i = 0; i < 4; i++) + { + switch (BlockType[i]) { - int MaxSize = 7; - BLOCKTYPE BlockType[4]; - NIBBLETYPE BlockMeta[4]; - UnboundedRelGetBlock(X - 1, Height, Z, BlockType[0], BlockMeta[0]); - UnboundedRelGetBlock(X + 1, Height, Z, BlockType[1], BlockMeta[1]); - UnboundedRelGetBlock(X, Height, Z - 1, BlockType[2], BlockMeta[2]); - UnboundedRelGetBlock(X, Height, Z + 1, BlockType[3], BlockMeta[3]); - for (int i = 0; i < 4; i++) + case E_BLOCK_AIR: { - switch (BlockType[i]) - { - case E_BLOCK_AIR: - { - MaxSize = 0; - break; - } - case E_BLOCK_SNOW: - { - MaxSize = std::min(BlockMeta[i] + 1, MaxSize); - break; - } - } - } - if (TopMeta < MaxSize) - { - FastSetBlock(X, Height, Z, E_BLOCK_SNOW, TopMeta + 1); + MaxSize = 0; + break; } - else if (TopMeta > MaxSize) + case E_BLOCK_SNOW: { - FastSetBlock(X, Height, Z, E_BLOCK_SNOW, TopMeta - 1); + MaxSize = std::min(BlockMeta[i] + 1, MaxSize); + break; } } - else if (cBlockInfo::IsSnowable(TopBlock) && (Height + 1 < cChunkDef::Height)) - { - SetBlock(X, Height + 1, Z, E_BLOCK_SNOW, 0); - } - else if ((TopBlock == E_BLOCK_WATER) || (TopBlock == E_BLOCK_STATIONARY_WATER)) - { - SetBlock(X, Height, Z, E_BLOCK_ICE, 0); - } - else if ( - (m_World->IsDeepSnowEnabled()) && - ( - (TopBlock == E_BLOCK_RED_ROSE) || - (TopBlock == E_BLOCK_YELLOW_FLOWER) || - (TopBlock == E_BLOCK_RED_MUSHROOM) || - (TopBlock == E_BLOCK_BROWN_MUSHROOM) - ) - ) - { - SetBlock(X, Height, Z, E_BLOCK_SNOW, 0); - } - break; - } // case (snowy biomes) - default: + } + if (TopMeta < MaxSize) { - break; + FastSetBlock(X, Height, Z, E_BLOCK_SNOW, TopMeta + 1); } - } // switch (biome) + else if (TopMeta > MaxSize) + { + FastSetBlock(X, Height, Z, E_BLOCK_SNOW, TopMeta - 1); + } + } + else if (cBlockInfo::IsSnowable(TopBlock) && (Height + 1 < cChunkDef::Height)) + { + SetBlock(X, Height + 1, Z, E_BLOCK_SNOW, 0); + } + else if (IsBlockWater(TopBlock) && (TopMeta == 0)) + { + SetBlock(X, Height, Z, E_BLOCK_ICE, 0); + } + else if ( + (m_World->IsDeepSnowEnabled()) && + ( + (TopBlock == E_BLOCK_RED_ROSE) || + (TopBlock == E_BLOCK_YELLOW_FLOWER) || + (TopBlock == E_BLOCK_RED_MUSHROOM) || + (TopBlock == E_BLOCK_BROWN_MUSHROOM) + ) + ) + { + SetBlock(X, Height, Z, E_BLOCK_SNOW, 0); + } } @@ -1570,12 +1573,18 @@ void cChunk::FastSetBlock(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockT if ( a_SendToClients && // ... we are told to do so AND ... ( - (OldBlockMeta != a_BlockMeta) || // ... the meta value is different OR ... - !( // ... the old and new blocktypes AREN'T liquids (because client doesn't need to distinguish betwixt them): - ((OldBlockType == E_BLOCK_STATIONARY_WATER) && (a_BlockType == E_BLOCK_WATER)) || // Replacing stationary water with water - ((OldBlockType == E_BLOCK_WATER) && (a_BlockType == E_BLOCK_STATIONARY_WATER)) || // Replacing water with stationary water - ((OldBlockType == E_BLOCK_STATIONARY_LAVA) && (a_BlockType == E_BLOCK_LAVA)) || // Replacing stationary water with water - ((OldBlockType == E_BLOCK_LAVA) && (a_BlockType == E_BLOCK_STATIONARY_LAVA)) // Replacing water with stationary water + !( // ... the old and new blocktypes AREN'T leaves (because the client doesn't need meta updates) + ((OldBlockType == E_BLOCK_LEAVES) && (a_BlockType == E_BLOCK_LEAVES)) || + ((OldBlockType == E_BLOCK_NEW_LEAVES) && (a_BlockType == E_BLOCK_NEW_LEAVES)) + ) && // ... AND ... + ( + (OldBlockMeta != a_BlockMeta) || // ... the meta value is different OR ... + !( // ... the old and new blocktypes AREN'T liquids (because client doesn't need to distinguish betwixt them): + ((OldBlockType == E_BLOCK_STATIONARY_WATER) && (a_BlockType == E_BLOCK_WATER)) || // Replacing stationary water with water + ((OldBlockType == E_BLOCK_WATER) && (a_BlockType == E_BLOCK_STATIONARY_WATER)) || // Replacing water with stationary water + ((OldBlockType == E_BLOCK_STATIONARY_LAVA) && (a_BlockType == E_BLOCK_LAVA)) || // Replacing stationary water with water + ((OldBlockType == E_BLOCK_LAVA) && (a_BlockType == E_BLOCK_STATIONARY_LAVA)) // Replacing water with stationary water + ) ) ) ) diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 387cc4628..2e0e86653 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -15,7 +15,6 @@ #include "Item.h" #include "Mobs/Monster.h" #include "ChatColor.h" -#include "OSSupport/Socket.h" #include "Items/ItemHandler.h" #include "Blocks/BlockHandler.h" #include "Blocks/BlockSlab.h" @@ -56,16 +55,15 @@ int cClientHandle::s_ClientCount = 0; //////////////////////////////////////////////////////////////////////////////// // cClientHandle: -cClientHandle::cClientHandle(const cSocket * a_Socket, int a_ViewDistance) : +cClientHandle::cClientHandle(const AString & a_IPString, int a_ViewDistance) : m_CurrentViewDistance(a_ViewDistance), m_RequestedViewDistance(a_ViewDistance), - m_IPString(a_Socket->GetIPString()), - m_OutgoingData(64 KiB), + m_IPString(a_IPString), m_Player(nullptr), m_HasSentDC(false), m_LastStreamedChunkX(0x7fffffff), // bogus chunk coords to force streaming upon login m_LastStreamedChunkZ(0x7fffffff), - m_TimeSinceLastPacket(0), + m_TicksSinceLastPacket(0), m_Ping(1000), m_PingID(1), m_BlockDigAnimStage(-1), @@ -135,9 +133,6 @@ cClientHandle::~cClientHandle() SendDisconnect("Server shut down? Kthnxbai"); } - // Close the socket as soon as it sends all outgoing data: - cRoot::Get()->GetServer()->RemoveClient(this); - delete m_Protocol; m_Protocol = nullptr; @@ -151,6 +146,10 @@ cClientHandle::~cClientHandle() void cClientHandle::Destroy(void) { { + cCSLock Lock(m_CSOutgoingData); + m_Link.reset(); + } + { cCSLock Lock(m_CSDestroyingState); if (m_State >= csDestroying) { @@ -168,6 +167,10 @@ void cClientHandle::Destroy(void) RemoveFromAllChunks(); m_Player->GetWorld()->RemoveClientFromChunkSender(this); } + if (m_Player != nullptr) + { + m_Player->RemoveClientHandle(); + } m_State = csDestroyed; } @@ -326,7 +329,8 @@ void cClientHandle::Authenticate(const AString & a_Name, const AString & a_UUID, m_Protocol->SendLoginSuccess(); // Spawn player (only serversided, so data is loaded) - m_Player = new cPlayer(this, GetUsername()); + m_Player = new cPlayer(m_Self, GetUsername()); + m_Self.reset(); cWorld * World = cRoot::Get()->GetWorld(m_Player->GetLoadedWorldName()); if (World == nullptr) @@ -689,6 +693,47 @@ void cClientHandle::HandleCreativeInventory(short a_SlotNum, const cItem & a_Hel +void cClientHandle::HandleEnchantItem(Byte a_WindowID, Byte a_Enchantment) +{ + if (a_Enchantment > 2) + { + LOGWARNING("%s attempt to crash the server with invalid enchanting selection!", GetUsername().c_str()); + Kick("Invalid enchanting!"); + return; + } + + if ( + (m_Player->GetWindow() == nullptr) || + (m_Player->GetWindow()->GetWindowID() != a_WindowID) || + (m_Player->GetWindow()->GetWindowType() != cWindow::wtEnchantment) + ) + { + return; + } + + cEnchantingWindow * Window = reinterpret_cast<cEnchantingWindow *>(m_Player->GetWindow()); + cItem Item = *Window->m_SlotArea->GetSlot(0, *m_Player); // Make a copy of the item + short BaseEnchantmentLevel = Window->GetPropertyValue(a_Enchantment); + + if (Item.EnchantByXPLevels(BaseEnchantmentLevel)) + { + if (m_Player->IsGameModeCreative() || m_Player->DeltaExperience(-m_Player->XpForLevel(BaseEnchantmentLevel)) >= 0) + { + Window->m_SlotArea->SetSlot(0, *m_Player, Item); + Window->SendSlot(*m_Player, Window->m_SlotArea, 0); + Window->BroadcastWholeWindow(); + + Window->SetProperty(0, 0, *m_Player); + Window->SetProperty(1, 0, *m_Player); + Window->SetProperty(2, 0, *m_Player); + } + } +} + + + + + void cClientHandle::HandlePlayerAbilities(bool a_CanFly, bool a_IsFlying, float FlyingSpeed, float WalkingSpeed) { UNUSED(FlyingSpeed); // Ignore the client values for these @@ -1777,44 +1822,9 @@ void cClientHandle::SendData(const char * a_Data, size_t a_Size) // This could crash the client, because they've already unloaded the world etc., and suddenly a wild packet appears (#31) return; } - - { - cCSLock Lock(m_CSOutgoingData); - - // _X 2012_09_06: We need an overflow buffer, usually when streaming the initial chunks - if (m_OutgoingDataOverflow.empty()) - { - // No queued overflow data; if this packet fits into the ringbuffer, put it in, otherwise put it in the overflow buffer: - size_t CanFit = m_OutgoingData.GetFreeSpace(); - if (CanFit > a_Size) - { - CanFit = a_Size; - } - if (CanFit > 0) - { - m_OutgoingData.Write(a_Data, CanFit); - } - if (a_Size > CanFit) - { - m_OutgoingDataOverflow.append(a_Data + CanFit, a_Size - CanFit); - } - } - else - { - // There is a queued overflow. Append to it, then send as much from its front as possible - m_OutgoingDataOverflow.append(a_Data, a_Size); - size_t CanFit = m_OutgoingData.GetFreeSpace(); - if (CanFit > 128) - { - // No point in moving the data over if it's not large enough - too much effort for too little an effect - m_OutgoingData.Write(m_OutgoingDataOverflow.data(), CanFit); - m_OutgoingDataOverflow.erase(0, CanFit); - } - } - } // Lock(m_CSOutgoingData) - - // Notify SocketThreads that we have something to write: - cRoot::Get()->GetServer()->NotifyClientWrite(this); + + cCSLock Lock(m_CSOutgoingData); + m_OutgoingData.append(a_Data, a_Size); } @@ -1871,10 +1881,28 @@ void cClientHandle::Tick(float a_Dt) cCSLock Lock(m_CSIncomingData); std::swap(IncomingData, m_IncomingData); } - m_Protocol->DataReceived(IncomingData.data(), IncomingData.size()); + if (!IncomingData.empty()) + { + m_Protocol->DataReceived(IncomingData.data(), IncomingData.size()); + } + + // Send any queued outgoing data: + AString OutgoingData; + { + cCSLock Lock(m_CSOutgoingData); + std::swap(OutgoingData, m_OutgoingData); + } + if (!OutgoingData.empty()) + { + cTCPLinkPtr Link(m_Link); // Grab a copy of the link in a multithread-safe way + if ((Link != nullptr)) + { + Link->Send(OutgoingData.data(), OutgoingData.size()); + } + } - m_TimeSinceLastPacket += a_Dt; - if (m_TimeSinceLastPacket > 30000.f) // 30 seconds time-out + m_TicksSinceLastPacket += 1; + if (m_TicksSinceLastPacket > 600) // 30 seconds time-out { SendDisconnect("Nooooo!! You timed out! D: Come back!"); Destroy(); @@ -1955,7 +1983,21 @@ void cClientHandle::ServerTick(float a_Dt) cCSLock Lock(m_CSIncomingData); std::swap(IncomingData, m_IncomingData); } - m_Protocol->DataReceived(IncomingData.data(), IncomingData.size()); + if (!IncomingData.empty()) + { + m_Protocol->DataReceived(IncomingData.data(), IncomingData.size()); + } + + // Send any queued outgoing data: + AString OutgoingData; + { + cCSLock Lock(m_CSOutgoingData); + std::swap(OutgoingData, m_OutgoingData); + } + if ((m_Link != nullptr) && !OutgoingData.empty()) + { + m_Link->Send(OutgoingData.data(), OutgoingData.size()); + } if (m_State == csAuthenticated) { @@ -1970,8 +2012,8 @@ void cClientHandle::ServerTick(float a_Dt) return; } - m_TimeSinceLastPacket += a_Dt; - if (m_TimeSinceLastPacket > 30000.f) // 30 seconds time-out + m_TicksSinceLastPacket += 1; + if (m_TicksSinceLastPacket > 600) // 30 seconds { SendDisconnect("Nooooo!! You timed out! D: Come back!"); Destroy(); @@ -2843,94 +2885,79 @@ void cClientHandle::PacketError(UInt32 a_PacketType) -bool cClientHandle::DataReceived(const char * a_Data, size_t a_Size) +void cClientHandle::SocketClosed(void) { - // Data is received from the client, store it in the buffer to be processed by the Tick thread: - m_TimeSinceLastPacket = 0; - cCSLock Lock(m_CSIncomingData); - m_IncomingData.append(a_Data, a_Size); - return false; + // The socket has been closed for any reason + + if (!m_Username.empty()) // Ignore client pings + { + LOGD("Client %s @ %s disconnected", m_Username.c_str(), m_IPString.c_str()); + cRoot::Get()->GetPluginManager()->CallHookDisconnect(*this, "Player disconnected"); + } + + Destroy(); } -void cClientHandle::GetOutgoingData(AString & a_Data) +void cClientHandle::SetSelf(cClientHandlePtr a_Self) { - // Data can be sent to client - { - cCSLock Lock(m_CSOutgoingData); - m_OutgoingData.ReadAll(a_Data); - m_OutgoingData.CommitRead(); - a_Data.append(m_OutgoingDataOverflow); - m_OutgoingDataOverflow.clear(); - } - - // Disconnect player after all packets have been sent - if (m_HasSentDC && a_Data.empty()) - { - Destroy(); - } + ASSERT(m_Self == nullptr); + m_Self = a_Self; } -void cClientHandle::SocketClosed(void) +void cClientHandle::OnLinkCreated(cTCPLinkPtr a_Link) { - // The socket has been closed for any reason - - LOGD("Player %s @ %s disconnected", m_Username.c_str(), m_IPString.c_str()); + m_Link = a_Link; +} - if (!m_Username.empty()) // Ignore client pings - { - cRoot::Get()->GetPluginManager()->CallHookDisconnect(*this, "Player disconnected"); - } - Destroy(); + + + +void cClientHandle::OnReceivedData(const char * a_Data, size_t a_Length) +{ + // Reset the timeout: + m_TicksSinceLastPacket = 0; + + // Queue the incoming data to be processed in the tick thread: + cCSLock Lock(m_CSIncomingData); + m_IncomingData.append(a_Data, a_Length); } -void cClientHandle::HandleEnchantItem(Byte & a_WindowID, Byte & a_Enchantment) +void cClientHandle::OnRemoteClosed(void) { - if (a_Enchantment > 2) { - LOGWARNING("%s attempt to crash the server with invalid enchanting selection!", GetUsername().c_str()); - Kick("Invalid enchanting!"); - return; + cCSLock Lock(m_CSOutgoingData); + m_Link.reset(); } + SocketClosed(); +} - if ( - (m_Player->GetWindow() == nullptr) || - (m_Player->GetWindow()->GetWindowID() != a_WindowID) || - (m_Player->GetWindow()->GetWindowType() != cWindow::wtEnchantment) - ) - { - return; - } - - cEnchantingWindow * Window = (cEnchantingWindow*) m_Player->GetWindow(); - cItem Item = *Window->m_SlotArea->GetSlot(0, *m_Player); - int BaseEnchantmentLevel = Window->GetPropertyValue(a_Enchantment); - if (Item.EnchantByXPLevels(BaseEnchantmentLevel)) - { - if (m_Player->IsGameModeCreative() || m_Player->DeltaExperience(-m_Player->XpForLevel(BaseEnchantmentLevel)) >= 0) - { - Window->m_SlotArea->SetSlot(0, *m_Player, Item); - Window->SendSlot(*m_Player, Window->m_SlotArea, 0); - Window->BroadcastWholeWindow(); - Window->SetProperty(0, 0, *m_Player); - Window->SetProperty(1, 0, *m_Player); - Window->SetProperty(2, 0, *m_Player); - } + + +void cClientHandle::OnError(int a_ErrorCode, const AString & a_ErrorMsg) +{ + LOGD("An error has occurred on client link for %s @ %s: %d (%s). Client disconnected.", + m_Username.c_str(), m_IPString.c_str(), a_ErrorCode, a_ErrorMsg.c_str() + ); + { + cCSLock Lock(m_CSOutgoingData); + m_Link.reset(); } + SocketClosed(); } diff --git a/src/ClientHandle.h b/src/ClientHandle.h index 03ae38cfd..8129d6a50 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -8,12 +8,10 @@ #pragma once -#ifndef CCLIENTHANDLE_H_INCLUDED -#define CCLIENTHANDLE_H_INCLUDED +#include "OSSupport/Network.h" #include "Defines.h" #include "Vector3.h" -#include "OSSupport/SocketThreads.h" #include "ChunkDef.h" #include "ByteBuffer.h" #include "Scoreboard.h" @@ -27,6 +25,7 @@ +// fwd: class cChunkDataSerializer; class cInventory; class cMonster; @@ -42,25 +41,29 @@ class cItemHandler; class cWorld; class cCompositeChat; class cStatManager; +class cClientHandle; +typedef SharedPtr<cClientHandle> cClientHandlePtr; -class cClientHandle : // tolua_export - public cSocketThreads::cCallback +class cClientHandle // tolua_export + : public cTCPLink::cCallbacks { // tolua_export -public: - -#if defined(ANDROID_NDK) - static const int DEFAULT_VIEW_DISTANCE = 4; // The default ViewDistance (used when no value is set in Settings.ini) -#else - static const int DEFAULT_VIEW_DISTANCE = 10; -#endif +public: // tolua_export + + #if defined(ANDROID_NDK) + static const int DEFAULT_VIEW_DISTANCE = 4; // The default ViewDistance (used when no value is set in Settings.ini) + #else + static const int DEFAULT_VIEW_DISTANCE = 10; + #endif static const int MAX_VIEW_DISTANCE = 32; static const int MIN_VIEW_DISTANCE = 1; - cClientHandle(const cSocket * a_Socket, int a_ViewDistance); + /** Creates a new client with the specified IP address in its description and the specified initial view distance. */ + cClientHandle(const AString & a_IPString, int a_ViewDistance); + virtual ~cClientHandle(); const AString & GetIPString(void) const { return m_IPString; } // tolua_export @@ -276,6 +279,10 @@ public: void HandleCommandBlockEntityChange(int a_EntityID, const AString & a_NewCommand); void HandleCreativeInventory (short a_SlotNum, const cItem & a_HeldItem); + + /** Called when the player enchants an Item in the Enchanting table UI. */ + void HandleEnchantItem(Byte a_WindowID, Byte a_Enchantment); + void HandleEntityCrouch (int a_EntityID, bool a_IsCrouching); void HandleEntityLeaveBed (int a_EntityID); void HandleEntitySprinting (int a_EntityID, bool a_IsSprinting); @@ -329,9 +336,6 @@ public: Sends an UnloadChunk packet for each loaded chunk and resets the streamed chunks. */ void RemoveFromWorld(void); - /** Called when the player will enchant a Item */ - void HandleEnchantItem(Byte & a_WindowID, Byte & a_Enchantment); - /** Called by the protocol recognizer when the protocol version is known. */ void SetProtocolVersion(UInt32 a_ProtocolVersion) { m_ProtocolVersion = a_ProtocolVersion; } @@ -340,6 +344,9 @@ public: private: + friend class cServer; // Needs access to SetSelf() + + /** The type used for storing the names of registered plugin channels. */ typedef std::set<AString> cChannels; @@ -361,13 +368,20 @@ private: cChunkCoordsList m_SentChunks; // Chunks that are currently sent to the client cProtocol * m_Protocol; - + + /** Protects m_IncomingData against multithreaded access. */ cCriticalSection m_CSIncomingData; - AString m_IncomingData; - + + /** Queue for the incoming data received on the link until it is processed in Tick(). + Protected by m_CSIncomingData. */ + AString m_IncomingData; + + /** Protects m_OutgoingData against multithreaded access. */ cCriticalSection m_CSOutgoingData; - cByteBuffer m_OutgoingData; - AString m_OutgoingDataOverflow; ///< For data that didn't fit into the m_OutgoingData ringbuffer temporarily + + /** Buffer for storing outgoing data from any thread; will get sent in Tick() (to prevent deadlocks). + Protected by m_CSOutgoingData. */ + AString m_OutgoingData; Vector3d m_ConfirmPosition; @@ -379,8 +393,8 @@ private: int m_LastStreamedChunkX; int m_LastStreamedChunkZ; - /** Seconds since the last packet data was received (updated in Tick(), reset in DataReceived()) */ - float m_TimeSinceLastPacket; + /** Number of ticks since the last network packet was received (increased in Tick(), reset in OnReceivedData()) */ + int m_TicksSinceLastPacket; /** Duration of the last completed client ping. */ std::chrono::steady_clock::duration m_Ping; @@ -458,6 +472,13 @@ private: /** The version of the protocol that the client is talking, or 0 if unknown. */ UInt32 m_ProtocolVersion; + /** The link that is used for network communication. + m_CSOutgoingData is used to synchronize access for sending data. */ + cTCPLinkPtr m_Link; + + /** Shared pointer to self, so that this instance can keep itself alive when needed. */ + cClientHandlePtr m_Self; + /** Returns true if the rate block interactions is within a reasonable limit (bot protection) */ bool CheckBlockInteractionsRate(void); @@ -483,16 +504,19 @@ private: /** Removes all of the channels from the list of current plugin channels. Ignores channels that are not found. */ void UnregisterPluginChannels(const AStringVector & a_ChannelList); - // cSocketThreads::cCallback overrides: - virtual bool DataReceived (const char * a_Data, size_t a_Size) override; // Data is received from the client - virtual void GetOutgoingData(AString & a_Data) override; // Data can be sent to client - virtual void SocketClosed (void) override; // The socket has been closed for any reason -}; // tolua_export - + /** Called when the network socket has been closed. */ + void SocketClosed(void); + /** Called right after the instance is created to store its SharedPtr inside. */ + void SetSelf(cClientHandlePtr a_Self); + // cTCPLink::cCallbacks overrides: + virtual void OnLinkCreated(cTCPLinkPtr a_Link) override; + virtual void OnReceivedData(const char * a_Data, size_t a_Length) override; + virtual void OnRemoteClosed(void) override; + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override; +}; // tolua_export -#endif // CCLIENTHANDLE_H_INCLUDED diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index c51a27961..1bc4690e1 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -1632,8 +1632,12 @@ void cEntity::TeleportToEntity(cEntity & a_Entity) void cEntity::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ) { - SetPosition(a_PosX, a_PosY, a_PosZ); - m_World->BroadcastTeleportEntity(*this); + // ask the plugins to allow teleport to the new position. + if (!cRoot::Get()->GetPluginManager()->CallHookEntityTeleport(*this, m_LastPos, Vector3d(a_PosX, a_PosY, a_PosZ))) + { + SetPosition(a_PosX, a_PosY, a_PosZ); + m_World->BroadcastTeleportEntity(*this); + } } @@ -1913,10 +1917,7 @@ void cEntity::AddPosition(double a_AddPosX, double a_AddPosY, double a_AddPosZ) void cEntity::AddSpeed(double a_AddSpeedX, double a_AddSpeedY, double a_AddSpeedZ) { - m_Speed.x += a_AddSpeedX; - m_Speed.y += a_AddSpeedY; - m_Speed.z += a_AddSpeedZ; - WrapSpeed(); + DoSetSpeed(m_Speed.x + a_AddSpeedX, m_Speed.y + a_AddSpeedY, m_Speed.z + a_AddSpeedZ); } @@ -1925,8 +1926,7 @@ void cEntity::AddSpeed(double a_AddSpeedX, double a_AddSpeedY, double a_AddSpeed void cEntity::AddSpeedX(double a_AddSpeedX) { - m_Speed.x += a_AddSpeedX; - WrapSpeed(); + AddSpeed(a_AddSpeedX, 0, 0); } @@ -1935,8 +1935,7 @@ void cEntity::AddSpeedX(double a_AddSpeedX) void cEntity::AddSpeedY(double a_AddSpeedY) { - m_Speed.y += a_AddSpeedY; - WrapSpeed(); + AddSpeed(0, a_AddSpeedY, 0); } @@ -1945,8 +1944,7 @@ void cEntity::AddSpeedY(double a_AddSpeedY) void cEntity::AddSpeedZ(double a_AddSpeedZ) { - m_Speed.z += a_AddSpeedZ; - WrapSpeed(); + AddSpeed(0, 0, a_AddSpeedZ); } diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index de9b88dfb..809e974d2 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -443,6 +443,9 @@ public: /** Set the invulnerable ticks from the entity */ void SetInvulnerableTicks(int a_InvulnerableTicks) { m_InvulnerableTicks = a_InvulnerableTicks; } + + /** Returns whether the entity is on ground or not */ + virtual bool IsOnGround(void) const { return m_bOnGround; } // tolua_end diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 1ca131375..0d36d0b23 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -47,7 +47,7 @@ const int cPlayer::EATING_TICKS = 30; -cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) : +cPlayer::cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName) : super(etPlayer, 0.6, 1.8), m_bVisible(true), m_FoodLevel(MAX_FOOD_LEVEL), @@ -105,15 +105,15 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) : SetPosX(World->GetSpawnX()); SetPosY(World->GetSpawnY()); SetPosZ(World->GetSpawnZ()); - SetBedPos(Vector3i((int)World->GetSpawnX(), (int)World->GetSpawnY(), (int)World->GetSpawnZ())); + SetBedPos(Vector3i(static_cast<int>(World->GetSpawnX()), static_cast<int>(World->GetSpawnY()), static_cast<int>(World->GetSpawnZ()))); LOGD("Player \"%s\" is connecting for the first time, spawning at default world spawn {%.2f, %.2f, %.2f}", a_PlayerName.c_str(), GetPosX(), GetPosY(), GetPosZ() ); } - m_LastJumpHeight = (float)(GetPosY()); - m_LastGroundHeight = (float)(GetPosY()); + m_LastJumpHeight = static_cast<float>(GetPosY()); + m_LastGroundHeight = static_cast<float>(GetPosY()); m_Stance = GetPosY() + 1.62; if (m_GameMode == gmNotSet) @@ -174,7 +174,7 @@ void cPlayer::Destroyed() void cPlayer::SpawnOn(cClientHandle & a_Client) { - if (!m_bVisible || (m_ClientHandle == (&a_Client))) + if (!m_bVisible || (m_ClientHandle.get() == (&a_Client))) { return; } @@ -232,7 +232,7 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) } bool CanMove = true; - if (!GetPosition().EqualsEps(m_LastPos, 0.01)) // Non negligible change in position from last tick? + if (!GetPosition().EqualsEps(m_LastPos, 0.02)) // Non negligible change in position from last tick? 0.02 tp prevent continous calling while floating sometimes. { // Apply food exhaustion from movement: ApplyFoodExhaustionFromMovement(); @@ -246,7 +246,7 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) if (CanMove) { - BroadcastMovementUpdate(m_ClientHandle); + BroadcastMovementUpdate(m_ClientHandle.get()); } if (m_Health > 0) // make sure player is alive @@ -278,7 +278,7 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) if (IsFlying()) { - m_LastGroundHeight = (float)GetPosY(); + m_LastGroundHeight = static_cast<float>(GetPosY()); } if (m_TicksUntilNextSave == 0) @@ -296,7 +296,7 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) -short cPlayer::CalcLevelFromXp(short a_XpTotal) +int cPlayer::CalcLevelFromXp(int a_XpTotal) { // level 0 to 15 if (a_XpTotal <= XP_TO_LEVEL15) @@ -307,18 +307,18 @@ short cPlayer::CalcLevelFromXp(short a_XpTotal) // level 30+ if (a_XpTotal > XP_TO_LEVEL30) { - return (short) (151.5 + sqrt( 22952.25 - (14 * (2220 - a_XpTotal)))) / 7; + return static_cast<int>((151.5 + sqrt( 22952.25 - (14 * (2220 - a_XpTotal)))) / 7); } // level 16 to 30 - return (short) ( 29.5 + sqrt( 870.25 - (6 * ( 360 - a_XpTotal)))) / 3; + return static_cast<int>((29.5 + sqrt( 870.25 - (6 * ( 360 - a_XpTotal)))) / 3); } -short cPlayer::XpForLevel(short a_Level) +int cPlayer::XpForLevel(int a_Level) { // level 0 to 15 if (a_Level <= 15) @@ -329,18 +329,18 @@ short cPlayer::XpForLevel(short a_Level) // level 30+ if (a_Level >= 31) { - return (short) ( (3.5 * a_Level * a_Level) - (151.5 * a_Level) + 2220); + return static_cast<int>((3.5 * a_Level * a_Level) - (151.5 * a_Level) + 2220); } // level 16 to 30 - return (short) ( (1.5 * a_Level * a_Level) - (29.5 * a_Level) + 360); + return static_cast<int>((1.5 * a_Level * a_Level) - (29.5 * a_Level) + 360); } -short cPlayer::GetXpLevel() +int cPlayer::GetXpLevel() { return CalcLevelFromXp(m_CurrentXp); } @@ -351,20 +351,20 @@ short cPlayer::GetXpLevel() float cPlayer::GetXpPercentage() { - short int currentLevel = CalcLevelFromXp(m_CurrentXp); - short int currentLevel_XpBase = XpForLevel(currentLevel); + int currentLevel = CalcLevelFromXp(m_CurrentXp); + int currentLevel_XpBase = XpForLevel(currentLevel); - return (float)(m_CurrentXp - currentLevel_XpBase) / - (float)(XpForLevel(1+currentLevel) - currentLevel_XpBase); + return static_cast<float>(m_CurrentXp - currentLevel_XpBase) / + static_cast<float>(XpForLevel(1+currentLevel) - currentLevel_XpBase); } -bool cPlayer::SetCurrentExperience(short int a_CurrentXp) +bool cPlayer::SetCurrentExperience(int a_CurrentXp) { - if (!(a_CurrentXp >= 0) || (a_CurrentXp > (std::numeric_limits<short>().max() - m_LifetimeTotalXp))) + if (!(a_CurrentXp >= 0) || (a_CurrentXp > (std::numeric_limits<int>().max() - m_LifetimeTotalXp))) { LOGWARNING("Tried to update experiece with an invalid Xp value: %d", a_CurrentXp); return false; // oops, they gave us a dodgey number @@ -382,19 +382,20 @@ bool cPlayer::SetCurrentExperience(short int a_CurrentXp) -short cPlayer::DeltaExperience(short a_Xp_delta) +int cPlayer::DeltaExperience(int a_Xp_delta) { - if (a_Xp_delta > (std::numeric_limits<short>().max() - m_CurrentXp)) + if (a_Xp_delta > (std::numeric_limits<int>().max() - m_CurrentXp)) { // Value was bad, abort and report - LOGWARNING("Attempt was made to increment Xp by %d, which overflowed the short datatype. Ignoring.", a_Xp_delta); + LOGWARNING("Attempt was made to increment Xp by %d, which overflowed the int datatype. Ignoring.", a_Xp_delta); return -1; // Should we instead just return the current Xp? } m_CurrentXp += a_Xp_delta; // Make sure they didn't subtract too much - m_CurrentXp = std::max<short>(m_CurrentXp, 0); + m_CurrentXp = std::max(m_CurrentXp, 0); + // Update total for score calculation if (a_Xp_delta > 0) @@ -419,7 +420,7 @@ void cPlayer::StartChargingBow(void) LOGD("Player \"%s\" started charging their bow", GetName().c_str()); m_IsChargingBow = true; m_BowCharge = 0; - m_World->BroadcastEntityMetadata(*this, m_ClientHandle); + m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get()); } @@ -432,7 +433,7 @@ int cPlayer::FinishChargingBow(void) int res = m_BowCharge; m_IsChargingBow = false; m_BowCharge = 0; - m_World->BroadcastEntityMetadata(*this, m_ClientHandle); + m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get()); return res; } @@ -446,7 +447,7 @@ void cPlayer::CancelChargingBow(void) LOGD("Player \"%s\" cancelled charging their bow at a charge of %d", GetName().c_str(), m_BowCharge); m_IsChargingBow = false; m_BowCharge = 0; - m_World->BroadcastEntityMetadata(*this, m_ClientHandle); + m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get()); } @@ -466,7 +467,7 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) { if (GetPosY() > m_LastJumpHeight) { - m_LastJumpHeight = (float)GetPosY(); + m_LastJumpHeight = static_cast<float>(GetPosY()); } cWorld * World = GetWorld(); if ((GetPosY() >= 0) && (GetPosY() < cChunkDef::Height)) @@ -483,13 +484,13 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) (BlockType == E_BLOCK_VINES) ) { - m_LastGroundHeight = (float)GetPosY(); + m_LastGroundHeight = static_cast<float>(GetPosY()); } } } else { - float Dist = (float)(m_LastGroundHeight - floor(GetPosY())); + float Dist = static_cast<float>(m_LastGroundHeight - floor(GetPosY())); if (Dist >= 2.0) // At least two blocks - TODO: Use m_LastJumpHeight instead of m_LastGroundHeight above { @@ -497,12 +498,12 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) m_Stats.AddValue(statDistFallen, (StatValue)floor(Dist * 100 + 0.5)); } - int Damage = (int)(Dist - 3.f); + int Damage = static_cast<int>(Dist - 3.f); if (m_LastJumpHeight > m_LastGroundHeight) { Damage++; } - m_LastJumpHeight = (float)GetPosY(); + m_LastJumpHeight = static_cast<float>(GetPosY()); if (Damage > 0) { @@ -510,10 +511,10 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) TakeDamage(dtFalling, nullptr, Damage, Damage, 0); // Fall particles - GetWorld()->BroadcastSoundParticleEffect(2006, POSX_TOINT, (int)GetPosY() - 1, POSZ_TOINT, Damage /* Used as particle effect speed modifier */); + GetWorld()->BroadcastSoundParticleEffect(2006, POSX_TOINT, static_cast<int>(GetPosY()) - 1, POSZ_TOINT, Damage /* Used as particle effect speed modifier */); } - m_LastGroundHeight = (float)GetPosY(); + m_LastGroundHeight = static_cast<float>(GetPosY()); } } @@ -551,7 +552,7 @@ void cPlayer::SetFoodLevel(int a_FoodLevel) void cPlayer::SetFoodSaturationLevel(double a_FoodSaturationLevel) { - m_FoodSaturationLevel = Clamp(a_FoodSaturationLevel, 0.0, (double) m_FoodLevel); + m_FoodSaturationLevel = Clamp(a_FoodSaturationLevel, 0.0, static_cast<double>(m_FoodLevel)); } @@ -1274,13 +1275,17 @@ unsigned int cPlayer::AwardAchievement(const eStatistic a_Ach) void cPlayer::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ) { - SetPosition(a_PosX, a_PosY, a_PosZ); - m_LastGroundHeight = (float)a_PosY; - m_LastJumpHeight = (float)a_PosY; - m_bIsTeleporting = true; + // ask plugins to allow teleport to the new position. + if (!cRoot::Get()->GetPluginManager()->CallHookEntityTeleport(*this, m_LastPos, Vector3d(a_PosX, a_PosY, a_PosZ))) + { + SetPosition(a_PosX, a_PosY, a_PosZ); + m_LastGroundHeight = static_cast<float>(a_PosY); + m_LastJumpHeight = static_cast<float>(a_PosY); + m_bIsTeleporting = true; - m_World->BroadcastTeleportEntity(*this, GetClientHandle()); - m_ClientHandle->SendPlayerMoveLook(); + m_World->BroadcastTeleportEntity(*this, GetClientHandle()); + m_ClientHandle->SendPlayerMoveLook(); + } } @@ -1391,7 +1396,7 @@ void cPlayer::SetVisible(bool a_bVisible) if (!a_bVisible && m_bVisible) { m_bVisible = false; - m_World->BroadcastDestroyEntity(*this, m_ClientHandle); // Destroy on all clients + m_World->BroadcastDestroyEntity(*this, m_ClientHandle.get()); // Destroy on all clients } } @@ -1714,9 +1719,9 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World) Json::Value & JSON_PlayerRotation = root["rotation"]; if (JSON_PlayerRotation.size() == 3) { - SetYaw ((float)JSON_PlayerRotation[(unsigned)0].asDouble()); - SetPitch ((float)JSON_PlayerRotation[(unsigned)1].asDouble()); - SetRoll ((float)JSON_PlayerRotation[(unsigned)2].asDouble()); + SetYaw (static_cast<float>(JSON_PlayerRotation[(unsigned)0].asDouble())); + SetPitch (static_cast<float>(JSON_PlayerRotation[(unsigned)1].asDouble())); + SetRoll (static_cast<float>(JSON_PlayerRotation[(unsigned)2].asDouble())); } m_Health = root.get("health", 0).asInt(); @@ -1725,9 +1730,9 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World) m_FoodSaturationLevel = root.get("foodSaturation", MAX_FOOD_LEVEL).asDouble(); m_FoodTickTimer = root.get("foodTickTimer", 0).asInt(); m_FoodExhaustionLevel = root.get("foodExhaustion", 0).asDouble(); - m_LifetimeTotalXp = (short) root.get("xpTotal", 0).asInt(); - m_CurrentXp = (short) root.get("xpCurrent", 0).asInt(); - m_IsFlying = root.get("isflying", 0).asBool(); + m_LifetimeTotalXp = root.get("xpTotal", 0).asInt(); + m_CurrentXp = root.get("xpCurrent", 0).asInt(); + m_IsFlying = root.get("isflying", 0).asBool(); m_GameMode = (eGameMode) root.get("gamemode", eGameMode_NotSet).asInt(); @@ -1812,18 +1817,18 @@ bool cPlayer::SaveToDisk() root["world"] = m_World->GetName(); if (m_GameMode == m_World->GetGameMode()) { - root["gamemode"] = (int) eGameMode_NotSet; + root["gamemode"] = static_cast<int>(eGameMode_NotSet); } else { - root["gamemode"] = (int) m_GameMode; + root["gamemode"] = static_cast<int>(m_GameMode); } } else { // This happens if the player is saved to new format after loading from the old format root["world"] = m_LoadedWorldName; - root["gamemode"] = (int) eGameMode_NotSet; + root["gamemode"] = static_cast<int>(eGameMode_NotSet); } Json::StyledWriter writer; @@ -1839,7 +1844,7 @@ bool cPlayer::SaveToDisk() ); return false; } - if (f.Write(JsonData.c_str(), JsonData.size()) != (int)JsonData.size()) + if (f.Write(JsonData.c_str(), JsonData.size()) != static_cast<int>(JsonData.size())) { LOGWARNING("Error writing player \"%s\" to file \"%s\" - cannot save data. Player will lose their progress. ", GetName().c_str(), SourceFile.c_str() @@ -1894,7 +1899,7 @@ void cPlayer::UseEquippedItem(int a_Amount) if (GetInventory().DamageEquippedItem(a_Amount)) { - m_World->BroadcastSoundEffect("random.break", GetPosX(), GetPosY(), GetPosZ(), 0.5f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); + m_World->BroadcastSoundEffect("random.break", GetPosX(), GetPosY(), GetPosZ(), 0.5f, static_cast<float>(0.75 + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64)); } } @@ -2042,17 +2047,17 @@ void cPlayer::UpdateMovementStats(const Vector3d & a_DeltaPos) else if (IsSubmerged()) { m_Stats.AddValue(statDistDove, Value); - AddFoodExhaustion(0.00015 * (double)Value); + AddFoodExhaustion(0.00015 * static_cast<double>(Value)); } else if (IsSwimming()) { m_Stats.AddValue(statDistSwum, Value); - AddFoodExhaustion(0.00015 * (double)Value); + AddFoodExhaustion(0.00015 * static_cast<double>(Value)); } else if (IsOnGround()) { m_Stats.AddValue(statDistWalked, Value); - AddFoodExhaustion((m_IsSprinting ? 0.001 : 0.0001) * (double)Value); + AddFoodExhaustion((m_IsSprinting ? 0.001 : 0.0001) * static_cast<double>(Value)); } else { @@ -2294,6 +2299,16 @@ void cPlayer::Detach() +void cPlayer::RemoveClientHandle(void) +{ + ASSERT(m_ClientHandle != nullptr); + m_ClientHandle.reset(); +} + + + + + AString cPlayer::GetUUIDFileName(const AString & a_UUID) { AString UUID = cMojangAPI::MakeUUIDDashed(a_UUID); diff --git a/src/Entities/Player.h b/src/Entities/Player.h index d3ed46db6..e02c66bd3 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -40,7 +40,7 @@ public: CLASS_PROTODEF(cPlayer) - cPlayer(cClientHandle * a_Client, const AString & a_PlayerName); + cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName); virtual ~cPlayer(); @@ -72,22 +72,22 @@ public: Returns true on success "should" really only be called at init or player death, plugins excepted */ - bool SetCurrentExperience(short a_XpTotal); + bool SetCurrentExperience(int a_XpTotal); /* changes Xp by Xp_delta, you "shouldn't" inc more than MAX_EXPERIENCE_ORB_SIZE Wont't allow xp to go negative Returns the new current experience, -1 on error */ - short DeltaExperience(short a_Xp_delta); + int DeltaExperience(int a_Xp_delta); /** Gets the experience total - XpTotal for score on death */ - inline short GetXpLifetimeTotal(void) { return m_LifetimeTotalXp; } + inline int GetXpLifetimeTotal(void) { return m_LifetimeTotalXp; } /** Gets the currrent experience */ - inline short GetCurrentXp(void) { return m_CurrentXp; } + inline int GetCurrentXp(void) { return m_CurrentXp; } /** Gets the current level - XpLevel */ - short GetXpLevel(void); + int GetXpLevel(void); /** Gets the experience bar percentage - XpP */ float GetXpPercentage(void); @@ -95,13 +95,13 @@ public: /** Caculates the amount of XP needed for a given level Ref: http://minecraft.gamepedia.com/XP */ - static short XpForLevel(short int a_Level); + static int XpForLevel(int a_Level); /** Inverse of XpForLevel Ref: http://minecraft.gamepedia.com/XP values are as per this with pre-calculations */ - static short CalcLevelFromXp(short int a_CurrentXp); + static int CalcLevelFromXp(int a_CurrentXp); // tolua_end @@ -121,7 +121,7 @@ public: inline void SetStance( const double a_Stance) { m_Stance = a_Stance; } double GetEyeHeight(void) const; // tolua_export Vector3d GetEyePosition(void) const; // tolua_export - inline bool IsOnGround(void) const {return m_bTouchGround; } // tolua_export + virtual bool IsOnGround(void) const override { return m_bTouchGround; } inline double GetStance(void) const { return GetPosY() + 1.62; } // tolua_export // TODO: Proper stance when crouching etc. inline cInventory & GetInventory(void) { return m_Inventory; } // tolua_export inline const cInventory & GetInventory(void) const { return m_Inventory; } @@ -222,7 +222,15 @@ public: /** Closes the current window if it matches the specified ID, resets current window to m_InventoryWindow */ void CloseWindowIfID(char a_WindowID, bool a_CanRefuse = true); - cClientHandle * GetClientHandle(void) const { return m_ClientHandle; } + /** Returns the raw client handle associated with the player. */ + cClientHandle * GetClientHandle(void) const { return m_ClientHandle.get(); } + + // tolua_end + + /** Returns the SharedPtr to client handle associated with the player. */ + cClientHandlePtr GetClientHandlePtr(void) const { return m_ClientHandle; } + + // tolua_begin void SendMessage (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtCustom); } void SendMessageInfo (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtInformation); } @@ -467,6 +475,10 @@ public: virtual bool IsRclking (void) const { return IsEating() || IsChargingBow(); } virtual void Detach(void); + + /** Called by cClientHandle when the client is being destroyed. + The player removes its m_ClientHandle ownership so that the ClientHandle gets deleted. */ + void RemoveClientHandle(void); protected: @@ -537,7 +549,7 @@ protected: std::chrono::steady_clock::time_point m_LastPlayerListTime; - cClientHandle * m_ClientHandle; + cClientHandlePtr m_ClientHandle; cSlotNums m_InventoryPaintSlots; @@ -569,8 +581,8 @@ protected: Int64 m_EatingFinishTick; /** Player Xp level */ - short int m_LifetimeTotalXp; - short int m_CurrentXp; + int m_LifetimeTotalXp; + int m_CurrentXp; // flag saying we need to send a xp update to client bool m_bDirtyExperience; diff --git a/src/Generating/BioGen.cpp b/src/Generating/BioGen.cpp index 2cc810d3b..265db30ad 100644 --- a/src/Generating/BioGen.cpp +++ b/src/Generating/BioGen.cpp @@ -205,8 +205,7 @@ void cBiomeGenList::InitializeBiomes(const AString & a_Biomes) int Count = 1; if (Split2.size() >= 2) { - Count = atol(Split2[1].c_str()); - if (Count <= 0) + if (!StringToInteger(Split2[1], Count)) { LOGWARNING("Cannot decode biome count: \"%s\"; using 1.", Split2[1].c_str()); Count = 1; @@ -1037,7 +1036,7 @@ protected: //////////////////////////////////////////////////////////////////////////////// -// cBioGenGrown: +// cBioGenProtGrown: class cBioGenProtGrown: public cBiomeGen diff --git a/src/Generating/CompoGenBiomal.cpp b/src/Generating/CompoGenBiomal.cpp index 030c2baa5..3140bd754 100644 --- a/src/Generating/CompoGenBiomal.cpp +++ b/src/Generating/CompoGenBiomal.cpp @@ -542,6 +542,20 @@ protected: HasHadWater = true; } // for y a_ChunkDesc.SetBlockType(a_RelX, 0, a_RelZ, E_BLOCK_BEDROCK); + + EMCSBiome MesaVersion = a_ChunkDesc.GetBiome(a_RelX, a_RelZ); + if ((MesaVersion == biMesaPlateauF) || (MesaVersion == biMesaPlateauFM)) + { + if (Top < 95 + static_cast<int>(m_MesaFloor.CubicNoise2D(NoiseY * 2, NoiseX * 2) * 6)) + { + return; + } + + BLOCKTYPE Block = (m_MesaFloor.CubicNoise2D(NoiseX * 4, NoiseY * 4) < 0) ? E_BLOCK_DIRT : E_BLOCK_GRASS; + NIBBLETYPE Meta = (Block == E_BLOCK_GRASS) ? 0 : 1; + + a_ChunkDesc.SetBlockTypeMeta(a_RelX, Top, a_RelZ, Block, Meta); + } } diff --git a/src/Generating/ComposableGenerator.cpp b/src/Generating/ComposableGenerator.cpp index bda45ad92..4a670b064 100644 --- a/src/Generating/ComposableGenerator.cpp +++ b/src/Generating/ComposableGenerator.cpp @@ -616,6 +616,11 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) int MaxDensity = a_IniFile.GetValueSetI("Generator", "VillageMaxDensity", 80); m_FinishGens.push_back(std::make_shared<cVillageGen>(Seed, GridSize, MaxOffset, MaxDepth, MaxSize, MinDensity, MaxDensity, m_BiomeGen, m_CompositedHeightCache)); } + else if (NoCaseCompare(*itr, "Vines") == 0) + { + int Level = a_IniFile.GetValueSetI("Generator", "VinesLevel", 40); + m_FinishGens.push_back(std::make_shared<cFinishGenVines>(Seed, Level)); + } else if (NoCaseCompare(*itr, "WaterLakes") == 0) { int Probability = a_IniFile.GetValueSetI("Generator", "WaterLakesProbability", 25); diff --git a/src/Generating/FinishGen.cpp b/src/Generating/FinishGen.cpp index e10cb00f8..260253d62 100644 --- a/src/Generating/FinishGen.cpp +++ b/src/Generating/FinishGen.cpp @@ -244,6 +244,100 @@ void cFinishGenTallGrass::GenFinish(cChunkDesc & a_ChunkDesc) //////////////////////////////////////////////////////////////////////////////// +// cFinishGenVines + +bool cFinishGenVines::IsJungleVariant(EMCSBiome a_Biome) +{ + switch (a_Biome) + { + case biJungle: + case biJungleEdge: + case biJungleEdgeM: + case biJungleHills: + case biJungleM: + { + return true; + } + } + + return false; +} + + + + + +void cFinishGenVines::GenFinish(cChunkDesc & a_ChunkDesc) +{ + for (int x = 0; x < cChunkDef::Width; x++) + { + int xx = x + a_ChunkDesc.GetChunkX() * cChunkDef::Width; + for (int z = 0; z < cChunkDef::Width; z++) + { + int zz = z + a_ChunkDesc.GetChunkZ() * cChunkDef::Width; + if (!IsJungleVariant(a_ChunkDesc.GetBiome(x, z))) + { + // Current biome isn't a jungle + continue; + } + + if ((m_Noise.IntNoise2DInt(xx, zz) % 101) < 50) + { + continue; + } + + int Height = a_ChunkDesc.GetHeight(x, z); + for (int y = Height; y > m_Level; y--) + { + if (a_ChunkDesc.GetBlockType(x, y, z) != E_BLOCK_AIR) + { + // Can't place vines in non-air blocks + continue; + } + + if ((m_Noise.IntNoise3DInt(xx, y, zz) % 101) < 50) + { + continue; + } + + std::vector<NIBBLETYPE> Places; + if ((x + 1 < cChunkDef::Width) && cBlockInfo::FullyOccupiesVoxel(a_ChunkDesc.GetBlockType(x + 1, y, z))) + { + Places.push_back(8); + } + + if ((x - 1 > 0) && cBlockInfo::FullyOccupiesVoxel(a_ChunkDesc.GetBlockType(x - 1, y, z))) + { + Places.push_back(2); + } + + if ((z + 1 < cChunkDef::Width) && cBlockInfo::FullyOccupiesVoxel(a_ChunkDesc.GetBlockType(x, y, z + 1))) + { + Places.push_back(1); + } + + if ((z - 1 > 0) && cBlockInfo::FullyOccupiesVoxel(a_ChunkDesc.GetBlockType(x, y, z - 1))) + { + Places.push_back(4); + } + + if (Places.size() == 0) + { + continue; + } + + NIBBLETYPE Meta = Places[m_Noise.IntNoise3DInt(xx, y, zz) % Places.size()]; + a_ChunkDesc.SetBlockTypeMeta(x, y, z, E_BLOCK_VINES, Meta); + } + } + } +} + + + + + +//////////////////////////////////////////////////////////////////////////////// // cFinishGenSprinkleFoliage: bool cFinishGenSprinkleFoliage::TryAddSugarcane(cChunkDesc & a_ChunkDesc, int a_RelX, int a_RelY, int a_RelZ) @@ -470,30 +564,22 @@ void cFinishGenSnow::GenFinish(cChunkDesc & a_ChunkDesc) { for (int x = 0; x < cChunkDef::Width; x++) { - switch (a_ChunkDesc.GetBiome(x, z)) + int Height = a_ChunkDesc.GetHeight(x, z); + if (GetSnowStartHeight(a_ChunkDesc.GetBiome(x, z)) > Height) { - case biIcePlains: - case biIceMountains: - case biTaiga: - case biTaigaHills: - case biFrozenRiver: - case biFrozenOcean: - { - int Height = a_ChunkDesc.GetHeight(x, z); - if (cBlockInfo::IsSnowable(a_ChunkDesc.GetBlockType(x, Height, z)) && (Height < cChunkDef::Height - 1)) - { - a_ChunkDesc.SetBlockType(x, Height + 1, z, E_BLOCK_SNOW); - a_ChunkDesc.SetHeight(x, z, Height + 1); - } - break; - } - default: - { - // There's no snow in the other biomes. - break; - } + // Height isn't high enough for snow to start forming. + continue; } - } + + if (!cBlockInfo::IsSnowable(a_ChunkDesc.GetBlockType(x, Height, z)) && (Height < cChunkDef::Height - 1)) + { + // The top block can't be snown over. + continue; + } + + a_ChunkDesc.SetBlockType(x, Height + 1, z, E_BLOCK_SNOW); + a_ChunkDesc.SetHeight(x, z, Height + 1); + } // for x } // for z } @@ -511,34 +597,27 @@ void cFinishGenIce::GenFinish(cChunkDesc & a_ChunkDesc) { for (int x = 0; x < cChunkDef::Width; x++) { - switch (a_ChunkDesc.GetBiome(x, z)) + int Height = a_ChunkDesc.GetHeight(x, z); + if (GetSnowStartHeight(a_ChunkDesc.GetBiome(x, z)) > Height) { - case biIcePlains: - case biIceMountains: - case biTaiga: - case biTaigaHills: - case biFrozenRiver: - case biFrozenOcean: - { - int Height = a_ChunkDesc.GetHeight(x, z); - switch (a_ChunkDesc.GetBlockType(x, Height, z)) - { - case E_BLOCK_WATER: - case E_BLOCK_STATIONARY_WATER: - { - a_ChunkDesc.SetBlockType(x, Height, z, E_BLOCK_ICE); - break; - } - } - break; - } - default: - { - // No icy water in other biomes. - break; - } + // Height isn't high enough for snow to start forming. + continue; } - } + + if (!IsBlockWater(a_ChunkDesc.GetBlockType(x, Height, z))) + { + // The block isn't a water block. + continue; + } + + if (a_ChunkDesc.GetBlockMeta(x, Height, z) != 0) + { + // The water block isn't a source block. + continue; + } + + a_ChunkDesc.SetBlockType(x, Height, z, E_BLOCK_ICE); + } // for x } // for z } diff --git a/src/Generating/FinishGen.h b/src/Generating/FinishGen.h index ae6dee590..70696c4f8 100644 --- a/src/Generating/FinishGen.h +++ b/src/Generating/FinishGen.h @@ -118,6 +118,29 @@ protected: +class cFinishGenVines : + public cFinishGen +{ +public: + cFinishGenVines(int a_Seed, int a_Level) : + m_Noise(a_Seed), + m_Level(a_Level) + { + } + + bool IsJungleVariant(EMCSBiome a_Biome); + +protected: + cNoise m_Noise; + int m_Level; + + virtual void GenFinish(cChunkDesc & a_ChunkDesc) override; +}; + + + + + class cFinishGenSoulsandRims : public cFinishGen { diff --git a/src/Generating/HeiGen.cpp b/src/Generating/HeiGen.cpp index 61d087c17..54567cb4d 100644 --- a/src/Generating/HeiGen.cpp +++ b/src/Generating/HeiGen.cpp @@ -10,6 +10,66 @@ #include "DistortedHeightmap.h" #include "EndGen.h" #include "Noise3DGenerator.h" +#include "ProtIntGen.h" + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cHeiGenSteppy: + +class cHeiGenSteppy: + public cTerrainHeightGen +{ +public: + cHeiGenSteppy(int a_Seed) : + m_Seed(a_Seed) + { + m_Gen = + std::make_shared<cProtIntGenWeightAvg<16, 1, 0>>( + std::make_shared<cProtIntGenSmooth> (a_Seed + 1, + std::make_shared<cProtIntGenZoom> (a_Seed + 2, + std::make_shared<cProtIntGenSmooth> (a_Seed + 3, + std::make_shared<cProtIntGenZoom> (a_Seed + 4, + std::make_shared<cProtIntGenAddRnd> (a_Seed + 5, 1, + std::make_shared<cProtIntGenSmooth> (a_Seed + 6, + std::make_shared<cProtIntGenZoom> (a_Seed + 7, + std::make_shared<cProtIntGenRndBetween> (a_Seed + 8, 60, + std::make_shared<cProtIntGenAddRnd> (a_Seed + 9, 1, + std::make_shared<cProtIntGenSmooth> (a_Seed + 1, + std::make_shared<cProtIntGenZoom> (a_Seed + 2, + std::make_shared<cProtIntGenRndBetween> (a_Seed + 3, 60, + std::make_shared<cProtIntGenSmooth> (a_Seed + 4, + std::make_shared<cProtIntGenZoom> (a_Seed + 5, + std::make_shared<cProtIntGenRndBetween> (a_Seed + 6, 60, + std::make_shared<cProtIntGenRndChoice> (a_Seed + 7, 10, 50, 50, + std::make_shared<cProtIntGenSmooth> (a_Seed + 8, + std::make_shared<cProtIntGenZoom> (a_Seed + 9, + std::make_shared<cProtIntGenRndChoice> (a_Seed + 1, 10, 50, 50, + std::make_shared<cProtIntGenAddRnd> (a_Seed + 2, 2, + std::make_shared<cProtIntGenZoom> (a_Seed + 3, + std::make_shared<cProtIntGenZoom> (a_Seed + 4, + std::make_shared<cProtIntGenChoice> (a_Seed + 5, 10) + ))))))))))))))))))))))); + } + + // cTerrainHeightGen overrides: + virtual void GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) override + { + int heights[cChunkDef::Width * cChunkDef::Width]; + m_Gen->GetInts(a_ChunkX * cChunkDef::Width, a_ChunkZ * cChunkDef::Width, cChunkDef::Width, cChunkDef::Width, heights); + for (size_t i = 0; i < ARRAYCOUNT(heights); i++) + { + a_HeightMap[i] = static_cast<HEIGHTTYPE>(std::max(std::min(60 + heights[i], cChunkDef::Height - 60), 40)); + } + } + +protected: + int m_Seed; + + SharedPtr<cProtIntGen> m_Gen; +}; @@ -821,6 +881,10 @@ cTerrainHeightGenPtr cTerrainHeightGen::CreateHeightGen(cIniFile & a_IniFile, cB // Return an empty pointer, the caller will create the proper generator: return cTerrainHeightGenPtr(); } + else if (NoCaseCompare(HeightGenName, "Steppy") == 0) + { + res = std::make_shared<cHeiGenSteppy>(a_Seed); + } else if (NoCaseCompare(HeightGenName, "Noise3D") == 0) { // Not a heightmap-based generator, but it used to be accessible via HeightGen, so we need to skip making the default out of it diff --git a/src/Generating/ProtIntGen.h b/src/Generating/ProtIntGen.h index 73ed27096..e709222fe 100644 --- a/src/Generating/ProtIntGen.h +++ b/src/Generating/ProtIntGen.h @@ -318,6 +318,350 @@ protected: +/** Averages the values of the underlying 2 * 2 neighbors. */ +class cProtIntGenAvgValues : + public cProtIntGen +{ + typedef cProtIntGen super; + +public: + cProtIntGenAvgValues(Underlying a_Underlying) : + m_Underlying(a_Underlying) + { + } + + + virtual void GetInts(int a_MinX, int a_MinZ, int a_SizeX, int a_SizeZ, int * a_Values) override + { + // Generate the underlying values: + int lowerSizeX = a_SizeX + 1; + int lowerSizeZ = a_SizeZ + 1; + ASSERT(lowerSizeX * lowerSizeZ <= m_BufferSize); + int lowerData[m_BufferSize]; + m_Underlying->GetInts(a_MinX, a_MinZ, lowerSizeX, lowerSizeZ, lowerData); + + // Average - add all 4 "neighbors" and divide by 4: + for (int z = 0; z < a_SizeZ; z++) + { + for (int x = 0; x < a_SizeX; x++) + { + int idxLower = x + lowerSizeX * z; + a_Values[x + a_SizeX * z] = ( + lowerData[idxLower] + lowerData[idxLower + 1] + + lowerData[idxLower + lowerSizeX] + lowerData[idxLower + lowerSizeX + 1] + ) / 4; + } + } + } + +protected: + Underlying m_Underlying; +}; + + + + + +/** Averages the values of the underlying 4 * 4 neighbors. */ +class cProtIntGenAvg4Values : + public cProtIntGen +{ + typedef cProtIntGen super; + +public: + cProtIntGenAvg4Values(Underlying a_Underlying) : + m_Underlying(a_Underlying) + { + } + + + virtual void GetInts(int a_MinX, int a_MinZ, int a_SizeX, int a_SizeZ, int * a_Values) override + { + // Generate the underlying values: + int lowerSizeX = a_SizeX + 4; + int lowerSizeZ = a_SizeZ + 4; + ASSERT(lowerSizeX * lowerSizeZ <= m_BufferSize); + int lowerData[m_BufferSize]; + m_Underlying->GetInts(a_MinX - 1, a_MinZ - 1, lowerSizeX, lowerSizeZ, lowerData); + + // Calculate the weighted average of all 16 "neighbors": + for (int z = 0; z < a_SizeZ; z++) + { + for (int x = 0; x < a_SizeX; x++) + { + int idxLower1 = x + lowerSizeX * z; + int idxLower2 = idxLower1 + lowerSizeX; + int idxLower3 = idxLower1 + 2 * lowerSizeX; + int idxLower4 = idxLower1 + 3 * lowerSizeX; + a_Values[x + a_SizeX * z] = ( + 1 * lowerData[idxLower1] + 2 * lowerData[idxLower1 + 1] + 2 * lowerData[idxLower1 + 2] + 1 * lowerData[idxLower1 + 3] + + 2 * lowerData[idxLower2] + 32 * lowerData[idxLower2 + 1] + 32 * lowerData[idxLower2 + 2] + 2 * lowerData[idxLower2 + 3] + + 2 * lowerData[idxLower3] + 32 * lowerData[idxLower3 + 1] + 32 * lowerData[idxLower3 + 2] + 2 * lowerData[idxLower3 + 3] + + 1 * lowerData[idxLower4] + 2 * lowerData[idxLower4 + 1] + 2 * lowerData[idxLower4 + 2] + 1 * lowerData[idxLower4 + 3] + ) / 148; + } + } + } + +protected: + Underlying m_Underlying; +}; + + + + + +/** Averages the values of the underlying 3 * 3 neighbors with custom weight. */ +template <int WeightCenter, int WeightCardinal, int WeightDiagonal> +class cProtIntGenWeightAvg : + public cProtIntGen +{ + typedef cProtIntGen super; + +public: + cProtIntGenWeightAvg(Underlying a_Underlying) : + m_Underlying(a_Underlying) + { + } + + + virtual void GetInts(int a_MinX, int a_MinZ, int a_SizeX, int a_SizeZ, int * a_Values) override + { + // Generate the underlying values: + int lowerSizeX = a_SizeX + 3; + int lowerSizeZ = a_SizeZ + 3; + ASSERT(lowerSizeX * lowerSizeZ <= m_BufferSize); + int lowerData[m_BufferSize]; + m_Underlying->GetInts(a_MinX, a_MinZ, lowerSizeX, lowerSizeZ, lowerData); + + // Calculate the weighted average the neighbors: + for (int z = 0; z < a_SizeZ; z++) + { + for (int x = 0; x < a_SizeX; x++) + { + int idxLower1 = x + lowerSizeX * z; + int idxLower2 = idxLower1 + lowerSizeX; + int idxLower3 = idxLower1 + 2 * lowerSizeX; + a_Values[x + a_SizeX * z] = ( + WeightDiagonal * lowerData[idxLower1] + WeightCardinal * lowerData[idxLower1 + 1] + WeightDiagonal * lowerData[idxLower1 + 2] + + WeightCardinal * lowerData[idxLower2] + WeightCenter * lowerData[idxLower2 + 1] + WeightCardinal * lowerData[idxLower2 + 2] + + WeightDiagonal * lowerData[idxLower3] + WeightCardinal * lowerData[idxLower3 + 1] + WeightDiagonal * lowerData[idxLower3 + 2] + ) / (4 * WeightDiagonal + 4 * WeightCardinal + WeightCenter); + } + } + } + +protected: + Underlying m_Underlying; +}; + + + + + +/** Replaces random values of the underlying data with random integers in the specified range [Min .. Min + Range). */ +class cProtIntGenRndChoice : + public cProtIntGenWithNoise +{ + typedef cProtIntGenWithNoise super; + +public: + cProtIntGenRndChoice(int a_Seed, int a_ChancePct, int a_Min, int a_Range, Underlying a_Underlying) : + super(a_Seed), + m_ChancePct(a_ChancePct), + m_Min(a_Min), + m_Range(a_Range), + m_Underlying(a_Underlying) + { + } + + + virtual void GetInts(int a_MinX, int a_MinZ, int a_SizeX, int a_SizeZ, int * a_Values) override + { + // Generate the underlying values: + m_Underlying->GetInts(a_MinX, a_MinZ, a_SizeX, a_SizeZ, a_Values); + + // Replace random values: + for (int z = 0; z < a_SizeZ; z++) + { + int BaseZ = a_MinZ + z; + for (int x = 0; x < a_SizeX; x++) + { + if (((super::m_Noise.IntNoise2DInt(BaseZ, a_MinX + x) / 13) % 101) < m_ChancePct) + { + a_Values[x + a_SizeX * z] = m_Min + (super::m_Noise.IntNoise2DInt(a_MinX + x, BaseZ) / 7) % m_Range; + } + } // for x + } // for z + } + +protected: + int m_ChancePct; + int m_Min; + int m_Range; + Underlying m_Underlying; +}; + + + + + +/** Adds a random value in range [-a_HalfRange, +a_HalfRange] to each of the underlying values. */ +class cProtIntGenAddRnd : + public cProtIntGenWithNoise +{ + typedef cProtIntGenWithNoise super; + +public: + cProtIntGenAddRnd(int a_Seed, int a_HalfRange, Underlying a_Underlying) : + super(a_Seed), + m_Range(a_HalfRange * 2 + 1), + m_HalfRange(a_HalfRange), + m_Underlying(a_Underlying) + { + } + + + virtual void GetInts(int a_MinX, int a_MinZ, int a_SizeX, int a_SizeZ, int * a_Values) override + { + // Generate the underlying values: + m_Underlying->GetInts(a_MinX, a_MinZ, a_SizeX, a_SizeZ, a_Values); + + // Add the random values: + for (int z = 0; z < a_SizeZ; z++) + { + int NoiseZ = a_MinZ + z; + for (int x = 0; x < a_SizeX; x++) + { + int noiseVal = ((super::m_Noise.IntNoise2DInt(a_MinX + x, NoiseZ) / 7) % m_Range) - m_HalfRange; + a_Values[x + z * a_SizeX] += noiseVal; + } + } + } + +protected: + int m_Range; + int m_HalfRange; + Underlying m_Underlying; +}; + + + + + +/** Replaces random underlying values with the average of the neighbors. */ +class cProtIntGenRndAvg : + public cProtIntGenWithNoise +{ + typedef cProtIntGenWithNoise super; + +public: + cProtIntGenRndAvg(int a_Seed, int a_AvgChancePct, Underlying a_Underlying) : + super(a_Seed), + m_AvgChancePct(a_AvgChancePct), + m_Underlying(a_Underlying) + { + } + + + virtual void GetInts(int a_MinX, int a_MinZ, int a_SizeX, int a_SizeZ, int * a_Values) override + { + // Generate the underlying values: + int lowerSizeX = a_SizeX + 2; + int lowerSizeZ = a_SizeZ + 2; + ASSERT(lowerSizeX * lowerSizeZ <= m_BufferSize); + int lowerData[m_BufferSize]; + m_Underlying->GetInts(a_MinX - 1, a_MinZ - 1, lowerSizeX, lowerSizeZ, lowerData); + + // Average random values: + for (int z = 0; z < a_SizeZ; z++) + { + int NoiseZ = a_MinZ + z; + for (int x = 0; x < a_SizeX; x++) + { + int idxLower = x + 1 + lowerSizeX * (z + 1); + if (((super::m_Noise.IntNoise2DInt(a_MinX + x, NoiseZ) / 7) % 100) > m_AvgChancePct) + { + // Average the 4 neighbors: + a_Values[x + z * a_SizeX] = ( + lowerData[idxLower - 1] + lowerData[idxLower + 1] + + lowerData[idxLower - lowerSizeX] + lowerData[idxLower + lowerSizeX] + ) / 4; + } + else + { + // Keep the underlying value: + a_Values[x + z * a_SizeX] = lowerData[idxLower]; + } + } + } + } + +protected: + int m_AvgChancePct; + Underlying m_Underlying; +}; + + + + + +/** Replaces random underlying values with a random value in between the max and min of the neighbors. */ +class cProtIntGenRndBetween : + public cProtIntGenWithNoise +{ + typedef cProtIntGenWithNoise super; + +public: + cProtIntGenRndBetween(int a_Seed, int a_AvgChancePct, Underlying a_Underlying) : + super(a_Seed), + m_AvgChancePct(a_AvgChancePct), + m_Underlying(a_Underlying) + { + } + + + virtual void GetInts(int a_MinX, int a_MinZ, int a_SizeX, int a_SizeZ, int * a_Values) override + { + // Generate the underlying values: + int lowerSizeX = a_SizeX + 2; + int lowerSizeZ = a_SizeZ + 2; + ASSERT(lowerSizeX * lowerSizeZ <= m_BufferSize); + int lowerData[m_BufferSize]; + m_Underlying->GetInts(a_MinX - 1, a_MinZ - 1, lowerSizeX, lowerSizeZ, lowerData); + + // Average random values: + for (int z = 0; z < a_SizeZ; z++) + { + int NoiseZ = a_MinZ + z; + for (int x = 0; x < a_SizeX; x++) + { + int idxLower = x + 1 + lowerSizeX * (z + 1); + if (((super::m_Noise.IntNoise2DInt(a_MinX + x, NoiseZ) / 7) % 100) > m_AvgChancePct) + { + // Chose a value in between the min and max neighbor: + int min = std::min(std::min(lowerData[idxLower - 1], lowerData[idxLower + 1]), std::min(lowerData[idxLower - lowerSizeX], lowerData[idxLower + lowerSizeX])); + int max = std::max(std::max(lowerData[idxLower - 1], lowerData[idxLower + 1]), std::max(lowerData[idxLower - lowerSizeX], lowerData[idxLower + lowerSizeX])); + a_Values[x + z * a_SizeX] = min + ((super::m_Noise.IntNoise2DInt(a_MinX + x, NoiseZ + 10) / 7) % (max - min + 1)); + } + else + { + // Keep the underlying value: + a_Values[x + z * a_SizeX] = lowerData[idxLower]; + } + } + } + } + +protected: + int m_AvgChancePct; + Underlying m_Underlying; +}; + + + + + /** Converts land biomes at the edge of an ocean into the respective beach biome. */ class cProtIntGenBeaches : public cProtIntGen diff --git a/src/Generating/Trees.cpp b/src/Generating/Trees.cpp index a10e0f4f1..9e72a688f 100644 --- a/src/Generating/Trees.cpp +++ b/src/Generating/Trees.cpp @@ -224,9 +224,6 @@ void GetTreeImageByBiome(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_No case biMegaTaiga: case biMegaTaigaHills: case biExtremeHillsPlus: - case biMesa: - case biMesaPlateauF: - case biMesaPlateau: case biSunflowerPlains: case biDesertM: case biExtremeHillsM: @@ -239,9 +236,6 @@ void GetTreeImageByBiome(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_No case biMegaSpruceTaiga: case biMegaSpruceTaigaHills: case biExtremeHillsPlusM: - case biMesaBryce: - case biMesaPlateauFM: - case biMesaPlateauM: { // TODO: These need their special trees GetBirchTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); @@ -264,6 +258,16 @@ void GetTreeImageByBiome(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_No return; } + case biMesa: + case biMesaPlateauF: + case biMesaPlateau: + case biMesaBryce: + case biMesaPlateauFM: + case biMesaPlateauM: + { + GetSmallAppleTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); + } + case biDesert: case biDesertHills: case biRiver: diff --git a/src/Globals.h b/src/Globals.h index 654ede95f..7c2ab38d8 100644 --- a/src/Globals.h +++ b/src/Globals.h @@ -379,8 +379,10 @@ void inline LOG(const char * a_Format, ...) #define assert_test(x) ( !!(x) || (assert(!#x), exit(1), 0)) #endif -// Unified shared ptr from before C++11. Also no silly undercores. +// Unified ptr types from before C++11. Also no silly undercores. #define SharedPtr std::shared_ptr +#define WeakPtr std::weak_ptr +#define UniquePtr std::unique_ptr diff --git a/src/HTTPServer/HTTPConnection.cpp b/src/HTTPServer/HTTPConnection.cpp index d5dbf0199..de12b36ce 100644 --- a/src/HTTPServer/HTTPConnection.cpp +++ b/src/HTTPServer/HTTPConnection.cpp @@ -38,8 +38,7 @@ cHTTPConnection::~cHTTPConnection() void cHTTPConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Response) { - AppendPrintf(m_OutgoingData, "%d %s\r\nContent-Length: 0\r\n\r\n", a_StatusCode, a_Response.c_str()); - m_HTTPServer.NotifyConnectionWrite(*this); + SendData(Printf("%d %s\r\nContent-Length: 0\r\n\r\n", a_StatusCode, a_Response.c_str())); m_State = wcsRecvHeaders; } @@ -49,8 +48,7 @@ void cHTTPConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Re void cHTTPConnection::SendNeedAuth(const AString & a_Realm) { - AppendPrintf(m_OutgoingData, "HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n", a_Realm.c_str()); - m_HTTPServer.NotifyConnectionWrite(*this); + SendData(Printf("HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n", a_Realm.c_str())); m_State = wcsRecvHeaders; } @@ -61,9 +59,10 @@ void cHTTPConnection::SendNeedAuth(const AString & a_Realm) void cHTTPConnection::Send(const cHTTPResponse & a_Response) { ASSERT(m_State == wcsRecvIdle); - a_Response.AppendToData(m_OutgoingData); + AString toSend; + a_Response.AppendToData(toSend); m_State = wcsSendingResp; - m_HTTPServer.NotifyConnectionWrite(*this); + SendData(toSend); } @@ -73,10 +72,10 @@ void cHTTPConnection::Send(const cHTTPResponse & a_Response) void cHTTPConnection::Send(const void * a_Data, size_t a_Size) { ASSERT(m_State == wcsSendingResp); - AppendPrintf(m_OutgoingData, SIZE_T_FMT_HEX "\r\n", a_Size); - m_OutgoingData.append((const char *)a_Data, a_Size); - m_OutgoingData.append("\r\n"); - m_HTTPServer.NotifyConnectionWrite(*this); + // We're sending in Chunked transfer encoding + SendData(Printf(SIZE_T_FMT_HEX "\r\n", a_Size)); + SendData(a_Data, a_Size); + SendData("\r\n"); } @@ -86,9 +85,8 @@ void cHTTPConnection::Send(const void * a_Data, size_t a_Size) void cHTTPConnection::FinishResponse(void) { ASSERT(m_State == wcsSendingResp); - m_OutgoingData.append("0\r\n\r\n"); + SendData("0\r\n\r\n"); m_State = wcsRecvHeaders; - m_HTTPServer.NotifyConnectionWrite(*this); } @@ -108,8 +106,7 @@ void cHTTPConnection::AwaitNextRequest(void) case wcsRecvIdle: { // The client is waiting for a response, send an "Internal server error": - m_OutgoingData.append("HTTP/1.1 500 Internal Server Error\r\n\r\n"); - m_HTTPServer.NotifyConnectionWrite(*this); + SendData("HTTP/1.1 500 Internal Server Error\r\n\r\n"); m_State = wcsRecvHeaders; break; } @@ -117,7 +114,7 @@ void cHTTPConnection::AwaitNextRequest(void) case wcsSendingResp: { // The response headers have been sent, we need to terminate the response body: - m_OutgoingData.append("0\r\n\r\n"); + SendData("0\r\n\r\n"); m_State = wcsRecvHeaders; break; } @@ -140,15 +137,27 @@ void cHTTPConnection::Terminate(void) { m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); } - m_HTTPServer.CloseConnection(*this); + m_Link.reset(); } -bool cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size) +void cHTTPConnection::OnLinkCreated(cTCPLinkPtr a_Link) { + ASSERT(m_Link == nullptr); + m_Link = a_Link; +} + + + + + +void cHTTPConnection::OnReceivedData(const char * a_Data, size_t a_Size) +{ + ASSERT(m_Link != nullptr); + switch (m_State) { case wcsRecvHeaders: @@ -164,13 +173,14 @@ bool cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size) delete m_CurrentRequest; m_CurrentRequest = nullptr; m_State = wcsInvalid; - m_HTTPServer.CloseConnection(*this); - return true; + m_Link->Close(); + m_Link.reset(); + return; } if (m_CurrentRequest->IsInHeaders()) { // The request headers are not yet complete - return false; + return; } // The request has finished parsing its headers successfully, notify of it: @@ -186,11 +196,13 @@ bool cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size) // Process the rest of the incoming data into the request body: if (a_Size > BytesConsumed) { - return cHTTPConnection::DataReceived(a_Data + BytesConsumed, a_Size - BytesConsumed); + cHTTPConnection::OnReceivedData(a_Data + BytesConsumed, a_Size - BytesConsumed); + return; } else { - return cHTTPConnection::DataReceived("", 0); // If the request has zero body length, let it be processed right-away + cHTTPConnection::OnReceivedData("", 0); // If the request has zero body length, let it be processed right-away + return; } } @@ -210,8 +222,9 @@ bool cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size) if (!m_CurrentRequest->DoesAllowKeepAlive()) { m_State = wcsInvalid; - m_HTTPServer.CloseConnection(*this); - return true; + m_Link->Close(); + m_Link.reset(); + return; } delete m_CurrentRequest; m_CurrentRequest = nullptr; @@ -225,32 +238,39 @@ bool cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size) break; } } - return false; } -void cHTTPConnection::GetOutgoingData(AString & a_Data) +void cHTTPConnection::OnRemoteClosed(void) { - std::swap(a_Data, m_OutgoingData); + if (m_CurrentRequest != nullptr) + { + m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); + } + m_Link.reset(); } -void cHTTPConnection::SocketClosed(void) + +void cHTTPConnection::OnError(int a_ErrorCode, const AString & a_ErrorMsg) { - if (m_CurrentRequest != nullptr) - { - m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); - } - m_HTTPServer.CloseConnection(*this); + OnRemoteClosed(); } +void cHTTPConnection::SendData(const void * a_Data, size_t a_Size) +{ + m_Link->Send(a_Data, a_Size); +} + + + diff --git a/src/HTTPServer/HTTPConnection.h b/src/HTTPServer/HTTPConnection.h index ccbf26466..8ecc4a4d4 100644 --- a/src/HTTPServer/HTTPConnection.h +++ b/src/HTTPServer/HTTPConnection.h @@ -9,7 +9,7 @@ #pragma once -#include "../OSSupport/SocketThreads.h" +#include "../OSSupport/Network.h" @@ -25,7 +25,7 @@ class cHTTPRequest; class cHTTPConnection : - public cSocketThreads::cCallback + public cTCPLink::cCallbacks { public: @@ -78,9 +78,6 @@ protected: /** Status in which the request currently is */ eState m_State; - /** Data that is queued for sending, once the socket becomes writable */ - AString m_OutgoingData; - /** The request being currently received Valid only between having parsed the headers and finishing receiving the body. */ cHTTPRequest * m_CurrentRequest; @@ -88,18 +85,34 @@ protected: /** Number of bytes that remain to read for the complete body of the message to be received. Valid only in wcsRecvBody */ size_t m_CurrentRequestBodyRemaining; + + /** The network link attached to this connection. */ + cTCPLinkPtr m_Link; - // cSocketThreads::cCallback overrides: - /** Data is received from the client. - Returns true if the connection has been closed as the result of parsing the data. */ - virtual bool DataReceived(const char * a_Data, size_t a_Size) override; + // cTCPLink::cCallbacks overrides: + /** The link instance has been created, remember it. */ + virtual void OnLinkCreated(cTCPLinkPtr a_Link) override; + + /** Data is received from the client. */ + virtual void OnReceivedData(const char * a_Data, size_t a_Size) override; - /** Data can be sent to client */ - virtual void GetOutgoingData(AString & a_Data) override; + /** The socket has been closed for any reason. */ + virtual void OnRemoteClosed(void) override; - /** The socket has been closed for any reason */ - virtual void SocketClosed(void) override; + /** An error has occurred on the socket. */ + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override; + + // Overridable: + /** Called to send raw data over the link. Descendants may provide data transformations (SSL etc.) */ + virtual void SendData(const void * a_Data, size_t a_Size); + + /** Sends the raw data over the link. + Descendants may provide data transformations (SSL etc.) via the overridable SendData() function. */ + void SendData(const AString & a_Data) + { + SendData(a_Data.data(), a_Data.size()); + } } ; typedef std::vector<cHTTPConnection *> cHTTPConnections; diff --git a/src/HTTPServer/HTTPMessage.cpp b/src/HTTPServer/HTTPMessage.cpp index d59ca438e..c87b3cc8b 100644 --- a/src/HTTPServer/HTTPMessage.cpp +++ b/src/HTTPServer/HTTPMessage.cpp @@ -55,7 +55,10 @@ void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value) } else if (Key == "content-length") { - m_ContentLength = static_cast<size_t>(atol(m_Headers[Key].c_str())); + if (!StringToInteger(m_Headers[Key], m_ContentLength)) + { + m_ContentLength = 0; + } } } diff --git a/src/HTTPServer/HTTPServer.cpp b/src/HTTPServer/HTTPServer.cpp index 9ab030a1f..71f974a97 100644 --- a/src/HTTPServer/HTTPServer.cpp +++ b/src/HTTPServer/HTTPServer.cpp @@ -119,11 +119,45 @@ class cDebugCallbacks : //////////////////////////////////////////////////////////////////////////////// +// cHTTPServerListenCallbacks: + +class cHTTPServerListenCallbacks: + public cNetwork::cListenCallbacks +{ +public: + cHTTPServerListenCallbacks(cHTTPServer & a_HTTPServer, UInt16 a_Port): + m_HTTPServer(a_HTTPServer), + m_Port(a_Port) + { + } + +protected: + /** The HTTP server instance that we're attached to. */ + cHTTPServer & m_HTTPServer; + + /** The port for which this instance is responsible. */ + UInt16 m_Port; + + // cNetwork::cListenCallbacks overrides: + virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) override + { + return m_HTTPServer.OnIncomingConnection(a_RemoteIPAddress, a_RemotePort); + } + virtual void OnAccepted(cTCPLink & a_Link) override {} + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override + { + LOGWARNING("HTTP server error on port %d: %d (%s)", m_Port, a_ErrorCode, a_ErrorMsg.c_str()); + } +}; + + + + + +//////////////////////////////////////////////////////////////////////////////// // cHTTPServer: cHTTPServer::cHTTPServer(void) : - m_ListenThreadIPv4(*this, cSocket::IPv4, "WebServer"), - m_ListenThreadIPv6(*this, cSocket::IPv6, "WebServer"), m_Callbacks(nullptr) { } @@ -141,7 +175,7 @@ cHTTPServer::~cHTTPServer() -bool cHTTPServer::Initialize(const AString & a_PortsIPv4, const AString & a_PortsIPv6) +bool cHTTPServer::Initialize(void) { // Read the HTTPS cert + key: AString CertFile = cFile::ReadWholeFile("webadmin/httpscert.crt"); @@ -177,18 +211,6 @@ bool cHTTPServer::Initialize(const AString & a_PortsIPv4, const AString & a_Port { LOGINFO("WebServer: The server is running in secure HTTPS mode."); } - - // Open up requested ports: - bool HasAnyPort; - m_ListenThreadIPv4.SetReuseAddr(true); - m_ListenThreadIPv6.SetReuseAddr(true); - HasAnyPort = m_ListenThreadIPv4.Initialize(a_PortsIPv4); - HasAnyPort = m_ListenThreadIPv6.Initialize(a_PortsIPv6) || HasAnyPort; - if (!HasAnyPort) - { - return false; - } - return true; } @@ -196,19 +218,28 @@ bool cHTTPServer::Initialize(const AString & a_PortsIPv4, const AString & a_Port -bool cHTTPServer::Start(cCallbacks & a_Callbacks) +bool cHTTPServer::Start(cCallbacks & a_Callbacks, const AStringVector & a_Ports) { m_Callbacks = &a_Callbacks; - if (!m_ListenThreadIPv4.Start()) - { - return false; - } - if (!m_ListenThreadIPv6.Start()) + + // Open up requested ports: + for (auto port : a_Ports) { - m_ListenThreadIPv4.Stop(); - return false; - } - return true; + UInt16 PortNum; + if (!StringToInteger(port, PortNum)) + { + LOGWARNING("WebServer: Invalid port value: \"%s\". Ignoring.", port.c_str()); + continue; + } + auto Handle = cNetwork::Listen(PortNum, std::make_shared<cHTTPServerListenCallbacks>(*this, PortNum)); + if (Handle->IsListening()) + { + m_ServerHandles.push_back(Handle); + } + } // for port - a_Ports[] + + // Report success if at least one port opened successfully: + return !m_ServerHandles.empty(); } @@ -217,63 +248,30 @@ bool cHTTPServer::Start(cCallbacks & a_Callbacks) void cHTTPServer::Stop(void) { - m_ListenThreadIPv4.Stop(); - m_ListenThreadIPv6.Stop(); - - // Drop all current connections: - cCSLock Lock(m_CSConnections); - while (!m_Connections.empty()) + for (auto handle : m_ServerHandles) { - m_Connections.front()->Terminate(); - } // for itr - m_Connections[] + handle->Close(); + } + m_ServerHandles.clear(); } -void cHTTPServer::OnConnectionAccepted(cSocket & a_Socket) +cTCPLink::cCallbacksPtr cHTTPServer::OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) { - cHTTPConnection * Connection; + UNUSED(a_RemoteIPAddress); + UNUSED(a_RemotePort); + if (m_Cert.get() != nullptr) { - Connection = new cSslHTTPConnection(*this, m_Cert, m_CertPrivKey); + return std::make_shared<cSslHTTPConnection>(*this, m_Cert, m_CertPrivKey); } else { - Connection = new cHTTPConnection(*this); + return std::make_shared<cHTTPConnection>(*this); } - m_SocketThreads.AddClient(a_Socket, Connection); - cCSLock Lock(m_CSConnections); - m_Connections.push_back(Connection); -} - - - - - -void cHTTPServer::CloseConnection(cHTTPConnection & a_Connection) -{ - m_SocketThreads.RemoveClient(&a_Connection); - cCSLock Lock(m_CSConnections); - for (cHTTPConnections::iterator itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr) - { - if (*itr == &a_Connection) - { - m_Connections.erase(itr); - break; - } - } - delete &a_Connection; -} - - - - - -void cHTTPServer::NotifyConnectionWrite(cHTTPConnection & a_Connection) -{ - m_SocketThreads.NotifyWrite(&a_Connection); } diff --git a/src/HTTPServer/HTTPServer.h b/src/HTTPServer/HTTPServer.h index 73d4cbdd0..d626fb475 100644 --- a/src/HTTPServer/HTTPServer.h +++ b/src/HTTPServer/HTTPServer.h @@ -9,8 +9,7 @@ #pragma once -#include "../OSSupport/ListenThread.h" -#include "../OSSupport/SocketThreads.h" +#include "../OSSupport/Network.h" #include "../IniFile.h" #include "PolarSSL++/RsaPrivateKey.h" #include "PolarSSL++/CryptoKey.h" @@ -33,8 +32,7 @@ typedef std::vector<cHTTPConnection *> cHTTPConnections; -class cHTTPServer : - public cListenThread::cCallback +class cHTTPServer { public: class cCallbacks @@ -42,44 +40,39 @@ public: public: virtual ~cCallbacks() {} - /** Called when a new request arrives over a connection and its headers have been parsed. - The request body needn't have arrived yet. - */ + /** Called when a new request arrives over a connection and all its headers have been parsed. + The request body needn't have arrived yet. */ virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) = 0; /** Called when another part of request body has arrived. May be called multiple times for a single request. */ virtual void OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) = 0; - /// Called when the request body has been fully received in previous calls to OnRequestBody() + /** Called when the request body has been fully received in previous calls to OnRequestBody() */ virtual void OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) = 0; } ; cHTTPServer(void); virtual ~cHTTPServer(); - /// Initializes the server on the specified ports - bool Initialize(const AString & a_PortsIPv4, const AString & a_PortsIPv6); + /** Initializes the server - reads the cert files etc. */ + bool Initialize(void); - /// Starts the server and assigns the callbacks to use for incoming requests - bool Start(cCallbacks & a_Callbacks); + /** Starts the server and assigns the callbacks to use for incoming requests */ + bool Start(cCallbacks & a_Callbacks, const AStringVector & a_Ports); - /// Stops the server, drops all current connections + /** Stops the server, drops all current connections */ void Stop(void); protected: friend class cHTTPConnection; friend class cSslHTTPConnection; + friend class cHTTPServerListenCallbacks; - cListenThread m_ListenThreadIPv4; - cListenThread m_ListenThreadIPv6; + /** The cNetwork API handle for the listening socket. */ + cServerHandlePtrs m_ServerHandles; - cSocketThreads m_SocketThreads; - - cCriticalSection m_CSConnections; - cHTTPConnections m_Connections; ///< All the connections that are currently being serviced - - /// The callbacks to call for various events + /** The callbacks to call for various events */ cCallbacks * m_Callbacks; /** The server certificate to use for the SSL connections */ @@ -89,23 +82,18 @@ protected: cCryptoKeyPtr m_CertPrivKey; - // cListenThread::cCallback overrides: - virtual void OnConnectionAccepted(cSocket & a_Socket) override; - - /// Called by cHTTPConnection to close the connection (presumably due to an error) - void CloseConnection(cHTTPConnection & a_Connection); - - /// Called by cHTTPConnection to notify SocketThreads that there's data to be sent for the connection - void NotifyConnectionWrite(cHTTPConnection & a_Connection); - - /// Called by cHTTPConnection when it finishes parsing the request header + /** Called by cHTTPServerListenCallbacks when there's a new incoming connection. + Returns the connection instance to be used as the cTCPLink callbacks. */ + cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort); + + /** Called by cHTTPConnection when it finishes parsing the request header */ void NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request); /** Called by cHTTPConenction when it receives more data for the request body. May be called multiple times for a single request. */ void RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size); - /// Called by cHTTPConnection when it detects that the request has finished (all of its body has been received) + /** Called by cHTTPConnection when it detects that the request has finished (all of its body has been received) */ void RequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request); } ; diff --git a/src/HTTPServer/SslHTTPConnection.cpp b/src/HTTPServer/SslHTTPConnection.cpp index d237089d9..f8dea0731 100644 --- a/src/HTTPServer/SslHTTPConnection.cpp +++ b/src/HTTPServer/SslHTTPConnection.cpp @@ -25,14 +25,17 @@ cSslHTTPConnection::cSslHTTPConnection(cHTTPServer & a_HTTPServer, const cX509Ce -bool cSslHTTPConnection::DataReceived(const char * a_Data, size_t a_Size) +cSslHTTPConnection::~cSslHTTPConnection() +{ + m_Ssl.NotifyClose(); +} + + + + + +void cSslHTTPConnection::OnReceivedData(const char * a_Data, size_t a_Size) { - // If there is outgoing data in the queue, notify the server that it should write it out: - if (!m_OutgoingData.empty()) - { - m_HTTPServer.NotifyConnectionWrite(*this); - } - // Process the received data: const char * Data = a_Data; size_t Size = a_Size; @@ -52,17 +55,18 @@ bool cSslHTTPConnection::DataReceived(const char * a_Data, size_t a_Size) int NumRead = m_Ssl.ReadPlain(Buffer, sizeof(Buffer)); if (NumRead > 0) { - if (super::DataReceived(Buffer, (size_t)NumRead)) - { - // The socket has been closed, and the object is already deleted. Bail out. - return true; - } + super::OnReceivedData(Buffer, (size_t)NumRead); + } + else if (NumRead == POLARSSL_ERR_NET_WANT_READ) + { + // SSL requires us to send data to peer first, do so by "sending" empty data: + SendData(nullptr, 0); } // If both failed, bail out: if ((BytesWritten == 0) && (NumRead <= 0)) { - return false; + return; } } } @@ -71,18 +75,20 @@ bool cSslHTTPConnection::DataReceived(const char * a_Data, size_t a_Size) -void cSslHTTPConnection::GetOutgoingData(AString & a_Data) +void cSslHTTPConnection::SendData(const void * a_Data, size_t a_Size) { + const char * OutgoingData = reinterpret_cast<const char *>(a_Data); + size_t pos = 0; for (;;) { // Write as many bytes from our buffer to SSL's encryption as possible: int NumWritten = 0; - if (!m_OutgoingData.empty()) + if (pos < a_Size) { - NumWritten = m_Ssl.WritePlain(m_OutgoingData.data(), m_OutgoingData.size()); + NumWritten = m_Ssl.WritePlain(OutgoingData + pos, a_Size - pos); if (NumWritten > 0) { - m_OutgoingData.erase(0, (size_t)NumWritten); + pos += static_cast<size_t>(NumWritten); } } @@ -91,7 +97,7 @@ void cSslHTTPConnection::GetOutgoingData(AString & a_Data) size_t NumBytes = m_Ssl.ReadOutgoing(Buffer, sizeof(Buffer)); if (NumBytes > 0) { - a_Data.append(Buffer, NumBytes); + m_Link->Send(Buffer, NumBytes); } // If both failed, bail out: diff --git a/src/HTTPServer/SslHTTPConnection.h b/src/HTTPServer/SslHTTPConnection.h index c2c1585cd..c461a3a24 100644 --- a/src/HTTPServer/SslHTTPConnection.h +++ b/src/HTTPServer/SslHTTPConnection.h @@ -25,6 +25,8 @@ public: /** Creates a new connection on the specified server. Sends the specified cert as the server certificate, uses the private key for decryption. */ cSslHTTPConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey); + + ~cSslHTTPConnection(); protected: cBufferedSslContext m_Ssl; @@ -36,8 +38,8 @@ protected: cCryptoKeyPtr m_PrivateKey; // cHTTPConnection overrides: - virtual bool DataReceived (const char * a_Data, size_t a_Size) override; // Data is received from the client - virtual void GetOutgoingData(AString & a_Data) override; // Data can be sent to client + virtual void OnReceivedData(const char * a_Data, size_t a_Size) override; // Data is received from the client + virtual void SendData(const void * a_Data, size_t a_Size) override; // Data is to be sent to client } ; diff --git a/src/IniFile.cpp b/src/IniFile.cpp index ded7e4199..3a213a90e 100644 --- a/src/IniFile.cpp +++ b/src/IniFile.cpp @@ -888,3 +888,39 @@ void cIniFile::RemoveBom(AString & a_line) const + +AStringVector ReadUpgradeIniPorts( + cIniFile & a_IniFile, + const AString & a_KeyName, + const AString & a_PortsValueName, + const AString & a_OldIPv4ValueName, + const AString & a_OldIPv6ValueName, + const AString & a_DefaultValue +) +{ + // Read the regular value, but don't use the default (in order to detect missing value for upgrade): + AStringVector Ports = StringSplitAndTrim(a_IniFile.GetValue(a_KeyName, a_PortsValueName), ";,"); + + if (Ports.empty()) + { + // Historically there were two separate entries for IPv4 and IPv6, merge them and migrate: + AString Ports4 = a_IniFile.GetValue(a_KeyName, a_OldIPv4ValueName, a_DefaultValue); + AString Ports6 = a_IniFile.GetValue(a_KeyName, a_OldIPv6ValueName); + Ports = MergeStringVectors(StringSplitAndTrim(Ports4, ";,"), StringSplitAndTrim(Ports6, ";,")); + a_IniFile.DeleteValue(a_KeyName, a_OldIPv4ValueName); + a_IniFile.DeleteValue(a_KeyName, a_OldIPv6ValueName); + + // If those weren't present or were empty, use the default:" + if (Ports.empty()) + { + Ports = StringSplitAndTrim(a_DefaultValue, ";,"); + } + a_IniFile.SetValue(a_KeyName, a_PortsValueName, StringsConcat(Ports, ',')); + } + + return Ports; +} + + + + diff --git a/src/IniFile.h b/src/IniFile.h index e5879f46c..3e717723f 100644 --- a/src/IniFile.h +++ b/src/IniFile.h @@ -15,9 +15,7 @@ !! MODIFIED BY FAKETRUTH and madmaxoft!! */ -#ifndef CIniFile_H -#define CIniFile_H - +#pragma once @@ -215,4 +213,22 @@ public: // tolua_end -#endif + + + + +/** Reads the list of ports from the INI file, possibly upgrading from IPv4/IPv6-specific values into new version-agnostic value. +Reads the list of ports from a_PortsValueName. If that value doesn't exist or is empty, the list is combined from values +in a_OldIPv4ValueName and a_OldIPv6ValueName; in this case the old values are removed from the INI file. +If there is none of the three values or they are all empty, the default is used and stored in the Ports value. */ +AStringVector ReadUpgradeIniPorts( + cIniFile & a_IniFile, + const AString & a_KeyName, + const AString & a_PortsValueName, + const AString & a_OldIPv4ValueName, + const AString & a_OldIPv6ValueName, + const AString & a_DefaultValue +); + + + diff --git a/src/Items/ItemDoor.h b/src/Items/ItemDoor.h index dacf286e5..71143d5a8 100644 --- a/src/Items/ItemDoor.h +++ b/src/Items/ItemDoor.h @@ -40,7 +40,7 @@ public: } // The door needs a compatible block below it: - if ((a_BlockY > 0) && !cBlockDoorHandler::CanBeOn(a_World.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ))) + if (!cBlockDoorHandler::CanBeOn(a_World.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ))) { return false; } @@ -62,10 +62,10 @@ public: return false; } } - + // Check the two blocks that will get replaced by the door: - BLOCKTYPE LowerBlockType = a_World.GetBlock(a_BlockX, a_BlockY + 1, a_BlockZ); - BLOCKTYPE UpperBlockType = a_World.GetBlock(a_BlockX, a_BlockY + 2, a_BlockZ); + BLOCKTYPE LowerBlockType = a_World.GetBlock(a_BlockX, a_BlockY, a_BlockZ); + BLOCKTYPE UpperBlockType = a_World.GetBlock(a_BlockX, a_BlockY + 1, a_BlockZ); if ( !cBlockDoorHandler::CanReplaceBlock(LowerBlockType) || !cBlockDoorHandler::CanReplaceBlock(UpperBlockType)) @@ -77,19 +77,32 @@ public: NIBBLETYPE LowerBlockMeta = cBlockDoorHandler::PlayerYawToMetaData(a_Player.GetYaw()); Vector3i RelDirToOutside = cBlockDoorHandler::GetRelativeDirectionToOutside(LowerBlockMeta); Vector3i LeftNeighborPos = RelDirToOutside; - LeftNeighborPos.TurnCCW(); + LeftNeighborPos.TurnCW(); LeftNeighborPos.Move(a_BlockX, a_BlockY, a_BlockZ); Vector3i RightNeighborPos = RelDirToOutside; - RightNeighborPos.TurnCW(); + RightNeighborPos.TurnCCW(); RightNeighborPos.Move(a_BlockX, a_BlockY, a_BlockZ); // Decide whether the hinge is on the left (default) or on the right: NIBBLETYPE UpperBlockMeta = 0x08; + BLOCKTYPE LeftNeighborBlock = a_World.GetBlock(LeftNeighborPos); + BLOCKTYPE RightNeighborBlock = a_World.GetBlock(RightNeighborPos); + /* + // DEBUG: + LOGD("Door being placed at {%d, %d, %d}", a_BlockX, a_BlockY, a_BlockZ); + LOGD("RelDirToOutside: {%d, %d, %d}", RelDirToOutside.x, RelDirToOutside.y, RelDirToOutside.z); + LOGD("Left neighbor at {%d, %d, %d}: %d (%s)", LeftNeighborPos.x, LeftNeighborPos.y, LeftNeighborPos.z, LeftNeighborBlock, ItemTypeToString(LeftNeighborBlock).c_str()); + LOGD("Right neighbor at {%d, %d, %d}: %d (%s)", RightNeighborPos.x, RightNeighborPos.y, RightNeighborPos.z, RightNeighborBlock, ItemTypeToString(RightNeighborBlock).c_str()); + */ if ( - cBlockDoorHandler::IsDoorBlockType(a_World.GetBlock(LeftNeighborPos)) || // The block to the left is a door block - cBlockInfo::IsSolid(a_World.GetBlock(RightNeighborPos)) // The block to the right is solid + cBlockDoorHandler::IsDoorBlockType(LeftNeighborBlock) || // The block to the left is a door block + ( + cBlockInfo::IsSolid(RightNeighborBlock) && // The block to the right is solid... + !cBlockDoorHandler::IsDoorBlockType(RightNeighborBlock) // ... but not a door + ) ) { + // DEBUG: LOGD("Setting hinge to right side"); UpperBlockMeta = 0x09; // Upper block | hinge on right } @@ -106,7 +119,3 @@ public: return true; } } ; - - - - diff --git a/src/MobSpawner.cpp b/src/MobSpawner.cpp index 0a32d17ef..541135996 100644 --- a/src/MobSpawner.cpp +++ b/src/MobSpawner.cpp @@ -110,7 +110,8 @@ eMonsterType cMobSpawner::ChooseMobType(EMCSBiome a_Biome) if (allowedMobsSize > 0) { std::set<eMonsterType>::iterator itr = allowedMobs.begin(); - int iRandom = m_Random.NextInt((int)allowedMobsSize, a_Biome); + static int Counter = 0; + int iRandom = m_Random.NextInt((int)allowedMobsSize, Counter++); for (int i = 0; i < iRandom; i++) { diff --git a/src/OSSupport/CMakeLists.txt b/src/OSSupport/CMakeLists.txt index 9424b63da..81b37ef0e 100644 --- a/src/OSSupport/CMakeLists.txt +++ b/src/OSSupport/CMakeLists.txt @@ -13,14 +13,12 @@ SET (SRCS HostnameLookup.cpp IPLookup.cpp IsThread.cpp - ListenThread.cpp NetworkSingleton.cpp Semaphore.cpp ServerHandleImpl.cpp - Socket.cpp - SocketThreads.cpp StackTrace.cpp TCPLinkImpl.cpp + UDPEndpointImpl.cpp ) SET (HDRS @@ -32,16 +30,14 @@ SET (HDRS HostnameLookup.h IPLookup.h IsThread.h - ListenThread.h Network.h NetworkSingleton.h Queue.h Semaphore.h ServerHandleImpl.h - Socket.h - SocketThreads.h StackTrace.h TCPLinkImpl.h + UDPEndpointImpl.h ) if(NOT MSVC) @@ -52,6 +48,6 @@ if(NOT MSVC) target_link_libraries(OSSupport rt) endif() - target_link_libraries(OSSupport pthread) + target_link_libraries(OSSupport pthread event_core event_extra) endif() endif() diff --git a/src/OSSupport/File.h b/src/OSSupport/File.h index dfb38e839..ac6d1ab21 100644 --- a/src/OSSupport/File.h +++ b/src/OSSupport/File.h @@ -126,7 +126,7 @@ public: /** Returns the entire contents of the specified file as a string. Returns empty string on error. */ static AString ReadWholeFile(const AString & a_FileName); - + // tolua_end /** Returns the list of all items in the specified folder (files, folders, nix pipes, whatever's there). */ diff --git a/src/OSSupport/HostnameLookup.cpp b/src/OSSupport/HostnameLookup.cpp index 3a2997ffd..0944153be 100644 --- a/src/OSSupport/HostnameLookup.cpp +++ b/src/OSSupport/HostnameLookup.cpp @@ -69,12 +69,24 @@ void cHostnameLookup::Callback(int a_ErrCode, evutil_addrinfo * a_Addr, void * a case AF_INET: // IPv4 { sockaddr_in * sin = reinterpret_cast<sockaddr_in *>(a_Addr->ai_addr); + if (!Self->m_Callbacks->OnNameResolvedV4(Self->m_Hostname, sin)) + { + // Callback indicated that the IP shouldn't be serialized to a string, just continue with the next address: + HasResolved = true; + continue; + } evutil_inet_ntop(AF_INET, &(sin->sin_addr), IP, sizeof(IP)); break; } case AF_INET6: // IPv6 { sockaddr_in6 * sin = reinterpret_cast<sockaddr_in6 *>(a_Addr->ai_addr); + if (!Self->m_Callbacks->OnNameResolvedV6(Self->m_Hostname, sin)) + { + // Callback indicated that the IP shouldn't be serialized to a string, just continue with the next address: + HasResolved = true; + continue; + } evutil_inet_ntop(AF_INET6, &(sin->sin6_addr), IP, sizeof(IP)); break; } diff --git a/src/OSSupport/ListenThread.cpp b/src/OSSupport/ListenThread.cpp deleted file mode 100644 index b029634e9..000000000 --- a/src/OSSupport/ListenThread.cpp +++ /dev/null @@ -1,238 +0,0 @@ - -// ListenThread.cpp - -// Implements the cListenThread class representing the thread that listens for client connections - -#include "Globals.h" -#include "ListenThread.h" - - - - - -cListenThread::cListenThread(cCallback & a_Callback, cSocket::eFamily a_Family, const AString & a_ServiceName) : - super(Printf("ListenThread %s", a_ServiceName.c_str())), - m_Callback(a_Callback), - m_Family(a_Family), - m_ShouldReuseAddr(false), - m_ServiceName(a_ServiceName) -{ -} - - - - - -cListenThread::~cListenThread() -{ - Stop(); -} - - - - - -bool cListenThread::Initialize(const AString & a_PortsString) -{ - ASSERT(m_Sockets.empty()); // Not yet started - - if (!CreateSockets(a_PortsString)) - { - return false; - } - - return true; -} - - - - - -bool cListenThread::Start(void) -{ - if (m_Sockets.empty()) - { - // There are no sockets listening, either forgotten to initialize or the user specified no listening ports - // Report as successful, though - return true; - } - return super::Start(); -} - - - - - -void cListenThread::Stop(void) -{ - if (m_Sockets.empty()) - { - // No sockets means no thread was running in the first place - return; - } - - m_ShouldTerminate = true; - - // Close one socket to wake the thread up from the select() call - m_Sockets[0].CloseSocket(); - - // Wait for the thread to finish - super::Wait(); - - // Close all the listening sockets: - for (cSockets::iterator itr = m_Sockets.begin() + 1, end = m_Sockets.end(); itr != end; ++itr) - { - itr->CloseSocket(); - } // for itr - m_Sockets[] - m_Sockets.clear(); -} - - - - - -void cListenThread::SetReuseAddr(bool a_Reuse) -{ - ASSERT(m_Sockets.empty()); // Must not have been Initialize()d yet - - m_ShouldReuseAddr = a_Reuse; -} - - - - - -bool cListenThread::CreateSockets(const AString & a_PortsString) -{ - AStringVector Ports = StringSplitAndTrim(a_PortsString, ","); - - if (Ports.empty()) - { - return false; - } - - AString FamilyStr = m_ServiceName; - switch (m_Family) - { - case cSocket::IPv4: FamilyStr.append(" IPv4"); break; - case cSocket::IPv6: FamilyStr.append(" IPv6"); break; - default: - { - ASSERT(!"Unknown address family"); - break; - } - } - - for (AStringVector::const_iterator itr = Ports.begin(), end = Ports.end(); itr != end; ++itr) - { - int Port = atoi(itr->c_str()); - if ((Port <= 0) || (Port > 65535)) - { - LOGWARNING("%s: Invalid port specified: \"%s\".", FamilyStr.c_str(), itr->c_str()); - continue; - } - m_Sockets.push_back(cSocket::CreateSocket(m_Family)); - if (!m_Sockets.back().IsValid()) - { - LOGWARNING("%s: Cannot create listening socket for port %d: \"%s\"", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str()); - m_Sockets.pop_back(); - continue; - } - - if (m_ShouldReuseAddr) - { - if (!m_Sockets.back().SetReuseAddress()) - { - LOG("%s: Port %d cannot reuse addr, syscall failed: \"%s\".", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str()); - } - } - - // Bind to port: - bool res = false; - switch (m_Family) - { - case cSocket::IPv4: res = m_Sockets.back().BindToAnyIPv4(Port); break; - case cSocket::IPv6: res = m_Sockets.back().BindToAnyIPv6(Port); break; - default: - { - ASSERT(!"Unknown address family"); - res = false; - } - } - if (!res) - { - LOGWARNING("%s: Cannot bind port %d: \"%s\".", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str()); - m_Sockets.pop_back(); - continue; - } - - if (!m_Sockets.back().Listen()) - { - LOGWARNING("%s: Cannot listen on port %d: \"%s\".", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str()); - m_Sockets.pop_back(); - continue; - } - - LOGINFO("%s: Port %d is open for connections", FamilyStr.c_str(), Port); - } // for itr - Ports[] - - return !(m_Sockets.empty()); -} - - - - - -void cListenThread::Execute(void) -{ - if (m_Sockets.empty()) - { - LOGD("Empty cListenThread, ending thread now."); - return; - } - - // Find the highest socket number: - cSocket::xSocket Highest = m_Sockets[0].GetSocket(); - for (cSockets::iterator itr = m_Sockets.begin(), end = m_Sockets.end(); itr != end; ++itr) - { - if (itr->GetSocket() > Highest) - { - Highest = itr->GetSocket(); - } - } // for itr - m_Sockets[] - - while (!m_ShouldTerminate) - { - // Put all sockets into a FD set: - fd_set fdRead; - FD_ZERO(&fdRead); - for (cSockets::iterator itr = m_Sockets.begin(), end = m_Sockets.end(); itr != end; ++itr) - { - FD_SET(itr->GetSocket(), &fdRead); - } // for itr - m_Sockets[] - - timeval tv; // On Linux select() doesn't seem to wake up when socket is closed, so let's kinda busy-wait: - tv.tv_sec = 1; - tv.tv_usec = 0; - if (select((int)Highest + 1, &fdRead, nullptr, nullptr, &tv) == -1) - { - LOG("select(R) call failed in cListenThread: \"%s\"", cSocket::GetLastErrorString().c_str()); - continue; - } - for (cSockets::iterator itr = m_Sockets.begin(), end = m_Sockets.end(); itr != end; ++itr) - { - if (itr->IsValid() && FD_ISSET(itr->GetSocket(), &fdRead)) - { - cSocket Client = (m_Family == cSocket::IPv4) ? itr->AcceptIPv4() : itr->AcceptIPv6(); - if (Client.IsValid()) - { - m_Callback.OnConnectionAccepted(Client); - } - } - } // for itr - m_Sockets[] - } // while (!m_ShouldTerminate) -} - - - - diff --git a/src/OSSupport/ListenThread.h b/src/OSSupport/ListenThread.h deleted file mode 100644 index b2d806c82..000000000 --- a/src/OSSupport/ListenThread.h +++ /dev/null @@ -1,85 +0,0 @@ - -// ListenThread.h - -// Declares the cListenThread class representing the thread that listens for client connections - - - - - -#pragma once - -#include "IsThread.h" -#include "Socket.h" - - - - - -// fwd: -class cServer; - - - - - -class cListenThread : - public cIsThread -{ - typedef cIsThread super; - -public: - /** Used as the callback for connection events */ - class cCallback - { - public: - virtual ~cCallback() {} - - /** This callback is called whenever a socket connection is accepted */ - virtual void OnConnectionAccepted(cSocket & a_Socket) = 0; - } ; - - cListenThread(cCallback & a_Callback, cSocket::eFamily a_Family, const AString & a_ServiceName = ""); - ~cListenThread(); - - /** Creates all the sockets, returns trus if successful, false if not. */ - bool Initialize(const AString & a_PortsString); - - bool Start(void); - - void Stop(void); - - /** Call before Initialize() to set the "reuse" flag on the sockets */ - void SetReuseAddr(bool a_Reuse = true); - -protected: - typedef std::vector<cSocket> cSockets; - - /** The callback which to notify of incoming connections */ - cCallback & m_Callback; - - /** Socket address family to use */ - cSocket::eFamily m_Family; - - /** Sockets that are being monitored */ - cSockets m_Sockets; - - /** If set to true, the SO_REUSEADDR socket option is set to true */ - bool m_ShouldReuseAddr; - - /** Name of the service that's listening on the ports; for logging purposes only */ - AString m_ServiceName; - - - /** Fills in m_Sockets with individual sockets, each for one port specified in a_PortsString. - Returns true if successful and at least one socket has been created - */ - bool CreateSockets(const AString & a_PortsString); - - // cIsThread override: - virtual void Execute(void) override; -} ; - - - - diff --git a/src/OSSupport/Network.h b/src/OSSupport/Network.h index cdf6ba0e9..5dd596223 100644 --- a/src/OSSupport/Network.h +++ b/src/OSSupport/Network.h @@ -90,6 +90,9 @@ public: Sends the RST packet, queued outgoing and incoming data is lost. */ virtual void Close(void) = 0; + /** Returns the callbacks that are used. */ + cCallbacksPtr GetCallbacks(void) const { return m_Callbacks; } + protected: /** Callbacks to be used for the various situations. */ cCallbacksPtr m_Callbacks; @@ -127,6 +130,64 @@ public: +/** Interface that provides methods available on UDP communication endpoints. */ +class cUDPEndpoint +{ +public: + /** Interface for the callbacks for events that can happen on the endpoint. */ + class cCallbacks + { + public: + // Force a virtual destructor in all descendants: + virtual ~cCallbacks() {} + + /** Called when an error occurs on the endpoint. */ + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) = 0; + + /** Called when there is an incoming datagram from a remote host. */ + virtual void OnReceivedData(const char * a_Data, size_t a_Size, const AString & a_RemoteHost, UInt16 a_RemotePort) = 0; + }; + + + // Force a virtual destructor for all descendants: + virtual ~cUDPEndpoint() {} + + /** Closes the underlying socket. + Note that there still might be callbacks in-flight after this method returns. */ + virtual void Close(void) = 0; + + /** Returns true if the endpoint is open. */ + virtual bool IsOpen(void) const = 0; + + /** Returns the local port to which the underlying socket is bound. */ + virtual UInt16 GetPort(void) const = 0; + + /** Sends the specified payload in a single UDP datagram to the specified host+port combination. + Note that in order to send to a broadcast address, you need to call EnableBroadcasts() first. */ + virtual bool Send(const AString & a_Payload, const AString & a_Host, UInt16 a_Port) = 0; + + /** Marks the socket as capable of sending broadcast, using whatever OS API is needed. + Without this call, sending to a broadcast address using Send() may fail. */ + virtual void EnableBroadcasts(void) = 0; + +protected: + /** The callbacks used for various events on the endpoint. */ + cCallbacks & m_Callbacks; + + + /** Creates a new instance of an endpoint, with the specified callbacks. */ + cUDPEndpoint(cCallbacks & a_Callbacks): + m_Callbacks(a_Callbacks) + { + } +}; + +typedef SharedPtr<cUDPEndpoint> cUDPEndpointPtr; + + + + + class cNetwork { public: @@ -180,9 +241,22 @@ public: /** Called when the hostname is successfully resolved into an IP address. May be called multiple times if a name resolves to multiple addresses. - a_IP may be either an IPv4 or an IPv6 address with their proper formatting. */ + a_IP may be either an IPv4 or an IPv6 address with their proper formatting. + Each call to OnNameResolved() is preceded by a call to either OnNameResolvedV4() or OnNameResolvedV6(). */ virtual void OnNameResolved(const AString & a_Name, const AString & a_IP) = 0; + /** Called when the hostname is successfully resolved into an IPv4 address. + May be called multiple times if a name resolves to multiple addresses. + Each call to OnNameResolvedV4 is followed by OnNameResolved with the IP address serialized to a string. + If this callback returns false, the OnNameResolved() call is skipped for this address. */ + virtual bool OnNameResolvedV4(const AString & a_Name, const sockaddr_in * a_IP) { return true; } + + /** Called when the hostname is successfully resolved into an IPv6 address. + May be called multiple times if a name resolves to multiple addresses. + Each call to OnNameResolvedV4 is followed by OnNameResolved with the IP address serialized to a string. + If this callback returns false, the OnNameResolved() call is skipped for this address. */ + virtual bool OnNameResolvedV6(const AString & a_Name, const sockaddr_in6 * a_IP) { return true; } + /** Called when an error is encountered while resolving. If an error is reported, the OnFinished() callback is not called. */ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) = 0; @@ -239,6 +313,11 @@ public: const AString & a_IP, cResolveNameCallbacksPtr a_Callbacks ); + + /** Opens up an UDP endpoint for sending and receiving UDP datagrams on the specified port. + If a_Port is 0, the OS is free to assign any port number it likes to the endpoint. + Returns the endpoint object that can be interacted with. */ + static cUDPEndpointPtr CreateUDPEndpoint(UInt16 a_Port, cUDPEndpoint::cCallbacks & a_Callbacks); }; diff --git a/src/OSSupport/NetworkSingleton.cpp b/src/OSSupport/NetworkSingleton.cpp index 92f0604cd..358e24438 100644 --- a/src/OSSupport/NetworkSingleton.cpp +++ b/src/OSSupport/NetworkSingleton.cpp @@ -18,7 +18,8 @@ -cNetworkSingleton::cNetworkSingleton(void) +cNetworkSingleton::cNetworkSingleton(void): + m_HasTerminated(false) { // Windows: initialize networking: #ifdef _WIN32 @@ -62,8 +63,7 @@ cNetworkSingleton::cNetworkSingleton(void) } // Create the event loop thread: - std::thread EventLoopThread(RunEventLoop, this); - EventLoopThread.detach(); + m_EventLoopThread = std::thread(RunEventLoop, this); } @@ -72,9 +72,32 @@ cNetworkSingleton::cNetworkSingleton(void) cNetworkSingleton::~cNetworkSingleton() { + // Check that Terminate has been called already: + ASSERT(m_HasTerminated); +} + + + + + +cNetworkSingleton & cNetworkSingleton::Get(void) +{ + static cNetworkSingleton Instance; + return Instance; +} + + + + + +void cNetworkSingleton::Terminate(void) +{ + ASSERT(!m_HasTerminated); + m_HasTerminated = true; + // Wait for the LibEvent event loop to terminate: event_base_loopbreak(m_EventBase); - m_EventLoopTerminated.Wait(); + m_EventLoopThread.join(); // Remove all objects: { @@ -96,16 +119,6 @@ cNetworkSingleton::~cNetworkSingleton() -cNetworkSingleton & cNetworkSingleton::Get(void) -{ - static cNetworkSingleton Instance; - return Instance; -} - - - - - void cNetworkSingleton::LogCallback(int a_Severity, const char * a_Msg) { switch (a_Severity) @@ -129,7 +142,6 @@ void cNetworkSingleton::LogCallback(int a_Severity, const char * a_Msg) void cNetworkSingleton::RunEventLoop(cNetworkSingleton * a_Self) { event_base_loop(a_Self->m_EventBase, EVLOOP_NO_EXIT_ON_EMPTY); - a_Self->m_EventLoopTerminated.Set(); } @@ -138,6 +150,7 @@ void cNetworkSingleton::RunEventLoop(cNetworkSingleton * a_Self) void cNetworkSingleton::AddHostnameLookup(cHostnameLookupPtr a_HostnameLookup) { + ASSERT(!m_HasTerminated); cCSLock Lock(m_CS); m_HostnameLookups.push_back(a_HostnameLookup); } @@ -148,6 +161,7 @@ void cNetworkSingleton::AddHostnameLookup(cHostnameLookupPtr a_HostnameLookup) void cNetworkSingleton::RemoveHostnameLookup(const cHostnameLookup * a_HostnameLookup) { + ASSERT(!m_HasTerminated); cCSLock Lock(m_CS); for (auto itr = m_HostnameLookups.begin(), end = m_HostnameLookups.end(); itr != end; ++itr) { @@ -165,6 +179,7 @@ void cNetworkSingleton::RemoveHostnameLookup(const cHostnameLookup * a_HostnameL void cNetworkSingleton::AddIPLookup(cIPLookupPtr a_IPLookup) { + ASSERT(!m_HasTerminated); cCSLock Lock(m_CS); m_IPLookups.push_back(a_IPLookup); } @@ -175,6 +190,7 @@ void cNetworkSingleton::AddIPLookup(cIPLookupPtr a_IPLookup) void cNetworkSingleton::RemoveIPLookup(const cIPLookup * a_IPLookup) { + ASSERT(!m_HasTerminated); cCSLock Lock(m_CS); for (auto itr = m_IPLookups.begin(), end = m_IPLookups.end(); itr != end; ++itr) { @@ -192,6 +208,7 @@ void cNetworkSingleton::RemoveIPLookup(const cIPLookup * a_IPLookup) void cNetworkSingleton::AddLink(cTCPLinkImplPtr a_Link) { + ASSERT(!m_HasTerminated); cCSLock Lock(m_CS); m_Connections.push_back(a_Link); } @@ -202,6 +219,7 @@ void cNetworkSingleton::AddLink(cTCPLinkImplPtr a_Link) void cNetworkSingleton::RemoveLink(const cTCPLinkImpl * a_Link) { + ASSERT(!m_HasTerminated); cCSLock Lock(m_CS); for (auto itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr) { @@ -219,6 +237,7 @@ void cNetworkSingleton::RemoveLink(const cTCPLinkImpl * a_Link) void cNetworkSingleton::AddServer(cServerHandleImplPtr a_Server) { + ASSERT(!m_HasTerminated); cCSLock Lock(m_CS); m_Servers.push_back(a_Server); } @@ -229,6 +248,7 @@ void cNetworkSingleton::AddServer(cServerHandleImplPtr a_Server) void cNetworkSingleton::RemoveServer(const cServerHandleImpl * a_Server) { + ASSERT(!m_HasTerminated); cCSLock Lock(m_CS); for (auto itr = m_Servers.begin(), end = m_Servers.end(); itr != end; ++itr) { diff --git a/src/OSSupport/NetworkSingleton.h b/src/OSSupport/NetworkSingleton.h index 1d26fc8f4..0536a1c82 100644 --- a/src/OSSupport/NetworkSingleton.h +++ b/src/OSSupport/NetworkSingleton.h @@ -4,7 +4,8 @@ // Declares the cNetworkSingleton class representing the storage for global data pertaining to network API // such as a list of all connections, all listening sockets and the LibEvent dispatch thread. -// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead +// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead; +// the only exception being the main app entrypoint that needs to call Terminate before quitting. @@ -48,6 +49,11 @@ public: /** Returns the singleton instance of this class */ static cNetworkSingleton & Get(void); + /** Terminates all network-related threads. + To be used only on app shutdown. + MSVC runtime requires that the LibEvent networking be shut down before the main() function is exitted; this is the way to do it. */ + void Terminate(void); + /** Returns the main LibEvent handle for event registering. */ event_base * GetEventBase(void) { return m_EventBase; } @@ -110,8 +116,11 @@ protected: /** Mutex protecting all containers against multithreaded access. */ cCriticalSection m_CS; - /** Event that gets signalled when the event loop terminates. */ - cEvent m_EventLoopTerminated; + /** Set to true if Terminate has been called. */ + volatile bool m_HasTerminated; + + /** The thread in which the main LibEvent loop runs. */ + std::thread m_EventLoopThread; /** Initializes the LibEvent internals. */ diff --git a/src/OSSupport/ServerHandleImpl.cpp b/src/OSSupport/ServerHandleImpl.cpp index ba38dbf2e..44ace448b 100644 --- a/src/OSSupport/ServerHandleImpl.cpp +++ b/src/OSSupport/ServerHandleImpl.cpp @@ -83,6 +83,9 @@ void cServerHandleImpl::Close(void) // Remove the ptr to self, so that the object may be freed: m_SelfPtr.reset(); + + // Remove self from cNetworkSingleton: + cNetworkSingleton::Get().RemoveServer(this); } @@ -122,6 +125,7 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) bool NeedsTwoSockets = false; int err; evutil_socket_t MainSock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); + if (!IsValidSocket(MainSock)) { // Failed to create IPv6 socket, create an IPv4 one instead: @@ -135,6 +139,16 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) return false; } + // Allow the port to be reused right after the socket closes: + if (evutil_make_listen_socket_reuseable(MainSock) != 0) + { + m_ErrorCode = EVUTIL_SOCKET_ERROR(); + Printf(m_ErrorMsg, "Port %d cannot be made reusable: %d (%s). Restarting the server might not work.", + a_Port, m_ErrorCode, evutil_socket_error_to_string(m_ErrorCode) + ); + LOG("%s", m_ErrorMsg.c_str()); + } + // Bind to all interfaces: sockaddr_in name; memset(&name, 0, sizeof(name)); @@ -157,14 +171,20 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) int res = setsockopt(MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&Zero), sizeof(Zero)); err = EVUTIL_SOCKET_ERROR(); NeedsTwoSockets = ((res == SOCKET_ERROR) && (err == WSAENOPROTOOPT)); - LOGD("setsockopt(IPV6_V6ONLY) returned %d, err is %d (%s). %s", - res, err, evutil_socket_error_to_string(err), - NeedsTwoSockets ? "Second socket will be created" : "Second socket not needed" - ); #else setsockopt(MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&Zero), sizeof(Zero)); #endif + // Allow the port to be reused right after the socket closes: + if (evutil_make_listen_socket_reuseable(MainSock) != 0) + { + m_ErrorCode = EVUTIL_SOCKET_ERROR(); + Printf(m_ErrorMsg, "Port %d cannot be made reusable: %d (%s). Restarting the server might not work.", + a_Port, m_ErrorCode, evutil_socket_error_to_string(m_ErrorCode) + ); + LOG("%s", m_ErrorMsg.c_str()); + } + // Bind to all interfaces: sockaddr_in6 name; memset(&name, 0, sizeof(name)); @@ -194,6 +214,7 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) } m_ConnListener = evconnlistener_new(cNetworkSingleton::Get().GetEventBase(), Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, MainSock); m_IsListening = true; + if (!NeedsTwoSockets) { return true; @@ -202,6 +223,7 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) // If a secondary socket is required (WinXP dual-stack), create it here: LOGD("Creating a second socket for IPv4"); evutil_socket_t SecondSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (!IsValidSocket(SecondSock)) { err = EVUTIL_SOCKET_ERROR(); @@ -209,6 +231,16 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) return true; // Report as success, the primary socket is working } + // Allow the port to be reused right after the socket closes: + if (evutil_make_listen_socket_reuseable(SecondSock) != 0) + { + m_ErrorCode = EVUTIL_SOCKET_ERROR(); + Printf(m_ErrorMsg, "Port %d cannot be made reusable (second socket): %d (%s). Restarting the server might not work.", + a_Port, m_ErrorCode, evutil_socket_error_to_string(m_ErrorCode) + ); + LOG("%s", m_ErrorMsg.c_str()); + } + // Make the secondary socket nonblocking: if (evutil_make_socket_nonblocking(SecondSock) != 0) { @@ -234,7 +266,7 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) if (listen(SecondSock, 0) != 0) { err = EVUTIL_SOCKET_ERROR(); - LOGD("Cannot listen on on secondary socket on port %d: %d (%s)", a_Port, err, evutil_socket_error_to_string(err)); + LOGD("Cannot listen on secondary socket on port %d: %d (%s)", a_Port, err, evutil_socket_error_to_string(err)); evutil_closesocket(SecondSock); return true; // Report as success, the primary socket is working } @@ -256,19 +288,20 @@ void cServerHandleImpl::Callback(evconnlistener * a_Listener, evutil_socket_t a_ // Get the textual IP address and port number out of a_Addr: char IPAddress[128]; - evutil_inet_ntop(a_Addr->sa_family, a_Addr->sa_data, IPAddress, ARRAYCOUNT(IPAddress)); UInt16 Port = 0; switch (a_Addr->sa_family) { case AF_INET: { sockaddr_in * sin = reinterpret_cast<sockaddr_in *>(a_Addr); + evutil_inet_ntop(AF_INET, sin, IPAddress, ARRAYCOUNT(IPAddress)); Port = ntohs(sin->sin_port); break; } case AF_INET6: { sockaddr_in6 * sin6 = reinterpret_cast<sockaddr_in6 *>(a_Addr); + evutil_inet_ntop(AF_INET, sin6, IPAddress, ARRAYCOUNT(IPAddress)); Port = ntohs(sin6->sin6_port); break; } diff --git a/src/OSSupport/SocketThreads.cpp b/src/OSSupport/SocketThreads.cpp deleted file mode 100644 index 153d6ed1d..000000000 --- a/src/OSSupport/SocketThreads.cpp +++ /dev/null @@ -1,702 +0,0 @@ - -// cSocketThreads.cpp - -// Implements the cSocketThreads class representing the heart of MCS's client networking. -// This object takes care of network communication, groups sockets into threads and uses as little threads as possible for full read / write support -// For more detail, see http://forum.mc-server.org/showthread.php?tid=327 - -#include "Globals.h" -#include "SocketThreads.h" -#include "Errors.h" - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cSocketThreads: - -cSocketThreads::cSocketThreads(void) -{ -} - - - - - -cSocketThreads::~cSocketThreads() -{ - for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr) - { - delete *itr; - } // for itr - m_Threads[] - m_Threads.clear(); -} - - - - - - -bool cSocketThreads::AddClient(const cSocket & a_Socket, cCallback * a_Client) -{ - // Add a (socket, client) pair for processing, data from a_Socket is to be sent to a_Client - - // Try to add to existing threads: - cCSLock Lock(m_CS); - for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr) - { - if ((*itr)->IsValid() && (*itr)->HasEmptySlot()) - { - (*itr)->AddClient(a_Socket, a_Client); - return true; - } - } - - // No thread has free space, create a new one: - LOGD("Creating a new cSocketThread (currently have " SIZE_T_FMT ")", m_Threads.size()); - cSocketThread * Thread = new cSocketThread(this); - if (!Thread->Start()) - { - // There was an error launching the thread (but it was already logged along with the reason) - LOGERROR("A new cSocketThread failed to start"); - delete Thread; - Thread = nullptr; - return false; - } - Thread->AddClient(a_Socket, a_Client); - m_Threads.push_back(Thread); - return true; -} - - - - - -void cSocketThreads::RemoveClient(const cCallback * a_Client) -{ - // Remove the associated socket and the client from processing - - cCSLock Lock(m_CS); - for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr) - { - if ((*itr)->RemoveClient(a_Client)) - { - return; - } - } // for itr - m_Threads[] - - // This client wasn't found. - // It's not an error, because it may have been removed by a different thread in the meantime. -} - - - - - -void cSocketThreads::NotifyWrite(const cCallback * a_Client) -{ - // Notifies the thread responsible for a_Client that the client has something to write - - cCSLock Lock(m_CS); - for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr) - { - if ((*itr)->NotifyWrite(a_Client)) - { - return; - } - } // for itr - m_Threads[] - - // Cannot assert - this normally happens if a client disconnects and has pending packets, the cServer::cNotifyWriteThread will call this on invalid clients too - // ASSERT(!"Notifying write to an unknown client"); -} - - - - - -void cSocketThreads::Write(const cCallback * a_Client, const AString & a_Data) -{ - // Puts a_Data into outgoing data queue for a_Client - cCSLock Lock(m_CS); - for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr) - { - if ((*itr)->Write(a_Client, a_Data)) - { - return; - } - } // for itr - m_Threads[] - - // This may be perfectly legal, if the socket has been destroyed and the client is finishing up - // ASSERT(!"Writing to an unknown socket"); -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cSocketThreads::cSocketThread: - -cSocketThreads::cSocketThread::cSocketThread(cSocketThreads * a_Parent) : - cIsThread("cSocketThread"), - m_Parent(a_Parent), - m_NumSlots(0) -{ - // Nothing needed yet -} - - - - - -cSocketThreads::cSocketThread::~cSocketThread() -{ - m_ShouldTerminate = true; - - // Notify the thread: - ASSERT(m_ControlSocket2.IsValid()); - m_ControlSocket2.Send("a", 1); - - // Wait for the thread to finish: - Wait(); - - // Close the control sockets: - m_ControlSocket1.CloseSocket(); - m_ControlSocket2.CloseSocket(); -} - - - - - -void cSocketThreads::cSocketThread::AddClient(const cSocket & a_Socket, cCallback * a_Client) -{ - ASSERT(m_Parent->m_CS.IsLockedByCurrentThread()); - ASSERT(m_NumSlots < MAX_SLOTS); // Use HasEmptySlot() to check before adding - - m_Slots[m_NumSlots].m_Client = a_Client; - m_Slots[m_NumSlots].m_Socket = a_Socket; - m_Slots[m_NumSlots].m_Socket.SetNonBlocking(); - m_Slots[m_NumSlots].m_Outgoing.clear(); - m_Slots[m_NumSlots].m_State = sSlot::ssNormal; - m_NumSlots++; - - // Notify the thread of the change: - ASSERT(m_ControlSocket2.IsValid()); - m_ControlSocket2.Send("a", 1); -} - - - - - -bool cSocketThreads::cSocketThread::RemoveClient(const cCallback * a_Client) -{ - ASSERT(m_Parent->m_CS.IsLockedByCurrentThread()); - - if (m_NumSlots == 0) - { - return false; - } - - for (int i = m_NumSlots - 1; i >= 0 ; --i) - { - if (m_Slots[i].m_Client != a_Client) - { - continue; - } - - // Found the slot: - if (m_Slots[i].m_State == sSlot::ssRemoteClosed) - { - // The remote has already closed the socket, remove the slot altogether: - if (m_Slots[i].m_Socket.IsValid()) - { - m_Slots[i].m_Socket.CloseSocket(); - } - m_Slots[i] = m_Slots[--m_NumSlots]; - } - else - { - // Query and queue the last batch of outgoing data: - AString Data; - m_Slots[i].m_Client->GetOutgoingData(Data); - m_Slots[i].m_Outgoing.append(Data); - if (m_Slots[i].m_Outgoing.empty()) - { - // No more outgoing data, shut the socket down immediately: - m_Slots[i].m_Socket.ShutdownReadWrite(); - m_Slots[i].m_State = sSlot::ssShuttingDown; - } - else - { - // More data to send, shut down reading and wait for the rest to get sent: - m_Slots[i].m_State = sSlot::ssWritingRestOut; - } - m_Slots[i].m_Client = nullptr; - } - - // Notify the thread of the change: - ASSERT(m_ControlSocket2.IsValid()); - m_ControlSocket2.Send("r", 1); - return true; - } // for i - m_Slots[] - - // Not found - return false; -} - - - - - -bool cSocketThreads::cSocketThread::HasClient(const cCallback * a_Client) const -{ - ASSERT(m_Parent->m_CS.IsLockedByCurrentThread()); - - for (int i = m_NumSlots - 1; i >= 0; --i) - { - if (m_Slots[i].m_Client == a_Client) - { - return true; - } - } // for i - m_Slots[] - return false; -} - - - - - -bool cSocketThreads::cSocketThread::HasSocket(const cSocket * a_Socket) const -{ - for (int i = m_NumSlots - 1; i >= 0; --i) - { - if (m_Slots[i].m_Socket == *a_Socket) - { - return true; - } - } // for i - m_Slots[] - return false; -} - - - - - -bool cSocketThreads::cSocketThread::NotifyWrite(const cCallback * a_Client) -{ - ASSERT(m_Parent->m_CS.IsLockedByCurrentThread()); - - if (HasClient(a_Client)) - { - // Notify the thread that there's another packet in the queue: - ASSERT(m_ControlSocket2.IsValid()); - m_ControlSocket2.Send("q", 1); - return true; - } - return false; -} - - - - - -bool cSocketThreads::cSocketThread::Write(const cCallback * a_Client, const AString & a_Data) -{ - ASSERT(m_Parent->m_CS.IsLockedByCurrentThread()); - for (int i = m_NumSlots - 1; i >= 0; --i) - { - if (m_Slots[i].m_Client == a_Client) - { - m_Slots[i].m_Outgoing.append(a_Data); - - // Notify the thread that there's data in the queue: - ASSERT(m_ControlSocket2.IsValid()); - m_ControlSocket2.Send("q", 1); - - return true; - } - } // for i - m_Slots[] - return false; -} - - - - - -bool cSocketThreads::cSocketThread::Start(void) -{ - // Create the control socket listener - m_ControlSocket2 = cSocket::CreateSocket(cSocket::IPv4); - if (!m_ControlSocket2.IsValid()) - { - LOGERROR("Cannot create a Control socket for a cSocketThread (\"%s\"); continuing, but server may be unreachable from now on.", cSocket::GetLastErrorString().c_str()); - return false; - } - if (!m_ControlSocket2.BindToLocalhostIPv4(cSocket::ANY_PORT)) - { - LOGERROR("Cannot bind a Control socket for a cSocketThread (\"%s\"); continuing, but server may be unreachable from now on.", cSocket::GetLastErrorString().c_str()); - m_ControlSocket2.CloseSocket(); - return false; - } - if (!m_ControlSocket2.Listen(1)) - { - LOGERROR("Cannot listen on a Control socket for a cSocketThread (\"%s\"); continuing, but server may be unreachable from now on.", cSocket::GetLastErrorString().c_str()); - m_ControlSocket2.CloseSocket(); - return false; - } - if (m_ControlSocket2.GetPort() == 0) - { - LOGERROR("Cannot determine Control socket port (\"%s\"); conitnuing, but the server may be unreachable from now on.", cSocket::GetLastErrorString().c_str()); - m_ControlSocket2.CloseSocket(); - return false; - } - - // Start the thread - if (!super::Start()) - { - LOGERROR("Cannot start new cSocketThread"); - m_ControlSocket2.CloseSocket(); - return false; - } - - // Finish connecting the control socket by accepting connection from the thread's socket - cSocket tmp = m_ControlSocket2.AcceptIPv4(); - if (!tmp.IsValid()) - { - LOGERROR("Cannot link Control sockets for a cSocketThread (\"%s\"); continuing, but server may be unreachable from now on.", cSocket::GetLastErrorString().c_str()); - m_ControlSocket2.CloseSocket(); - return false; - } - m_ControlSocket2.CloseSocket(); - m_ControlSocket2 = tmp; - - return true; -} - - - - - -void cSocketThreads::cSocketThread::Execute(void) -{ - // Connect the "client" part of the Control socket: - m_ControlSocket1 = cSocket::CreateSocket(cSocket::IPv4); - ASSERT(m_ControlSocket2.GetPort() != 0); // We checked in the Start() method, but let's be sure - if (!m_ControlSocket1.ConnectToLocalhostIPv4(m_ControlSocket2.GetPort())) - { - LOGERROR("Cannot connect Control sockets for a cSocketThread (\"%s\"); continuing, but the server may be unreachable from now on.", cSocket::GetLastErrorString().c_str()); - m_ControlSocket2.CloseSocket(); - return; - } - - // The main thread loop: - while (!m_ShouldTerminate) - { - // Read outgoing data from the clients: - QueueOutgoingData(); - - // Put sockets into the sets - fd_set fdRead; - fd_set fdWrite; - cSocket::xSocket Highest = m_ControlSocket1.GetSocket(); - PrepareSets(&fdRead, &fdWrite, Highest); - - // Wait for the sockets: - timeval Timeout; - Timeout.tv_sec = 5; - Timeout.tv_usec = 0; - if (select((int)Highest + 1, &fdRead, &fdWrite, nullptr, &Timeout) == -1) - { - LOG("select() call failed in cSocketThread: \"%s\"", cSocket::GetLastErrorString().c_str()); - continue; - } - - // Perform the IO: - ReadFromSockets(&fdRead); - WriteToSockets(&fdWrite); - CleanUpShutSockets(); - } // while (!mShouldTerminate) -} - - - - - -void cSocketThreads::cSocketThread::PrepareSets(fd_set * a_Read, fd_set * a_Write, cSocket::xSocket & a_Highest) -{ - FD_ZERO(a_Read); - FD_ZERO(a_Write); - FD_SET(m_ControlSocket1.GetSocket(), a_Read); - - cCSLock Lock(m_Parent->m_CS); - for (int i = m_NumSlots - 1; i >= 0; --i) - { - if (!m_Slots[i].m_Socket.IsValid()) - { - continue; - } - if (m_Slots[i].m_State == sSlot::ssRemoteClosed) - { - // This socket won't provide nor consume any data anymore, don't put it in the Set - continue; - } - cSocket::xSocket s = m_Slots[i].m_Socket.GetSocket(); - FD_SET(s, a_Read); - if (s > a_Highest) - { - a_Highest = s; - } - if (!m_Slots[i].m_Outgoing.empty()) - { - // There's outgoing data for the socket, put it in the Write set - FD_SET(s, a_Write); - } - } // for i - m_Slots[] -} - - - - - -void cSocketThreads::cSocketThread::ReadFromSockets(fd_set * a_Read) -{ - // Read on available sockets: - - // Reset Control socket state: - if (FD_ISSET(m_ControlSocket1.GetSocket(), a_Read)) - { - char Dummy[128]; - m_ControlSocket1.Receive(Dummy, sizeof(Dummy), 0); - } - - // Read from clients: - cCSLock Lock(m_Parent->m_CS); - for (int i = m_NumSlots - 1; i >= 0; --i) - { - cSocket::xSocket Socket = m_Slots[i].m_Socket.GetSocket(); - if (!cSocket::IsValidSocket(Socket) || !FD_ISSET(Socket, a_Read)) - { - continue; - } - char Buffer[1024]; - int Received = m_Slots[i].m_Socket.Receive(Buffer, ARRAYCOUNT(Buffer), 0); - if (Received <= 0) - { - if (cSocket::GetLastError() != cSocket::ErrWouldBlock) - { - // The socket has been closed by the remote party - switch (m_Slots[i].m_State) - { - case sSlot::ssNormal: - { - // Close the socket on our side: - m_Slots[i].m_State = sSlot::ssRemoteClosed; - m_Slots[i].m_Socket.CloseSocket(); - - // Notify the callback that the remote has closed the socket, *after* removing the socket: - cCallback * client = m_Slots[i].m_Client; - m_Slots[i] = m_Slots[--m_NumSlots]; - if (client != nullptr) - { - client->SocketClosed(); - } - break; - } - case sSlot::ssWritingRestOut: - case sSlot::ssShuttingDown: - case sSlot::ssShuttingDown2: - { - // Force-close the socket and remove the slot: - m_Slots[i].m_Socket.CloseSocket(); - m_Slots[i] = m_Slots[--m_NumSlots]; - break; - } - default: - { - LOG("%s: Unexpected socket state: %d (%s)", - __FUNCTION__, m_Slots[i].m_Socket.GetSocket(), m_Slots[i].m_Socket.GetIPString().c_str() - ); - ASSERT(!"Unexpected socket state"); - break; - } - } // switch (m_Slots[i].m_State) - } - } - else - { - if (m_Slots[i].m_Client != nullptr) - { - m_Slots[i].m_Client->DataReceived(Buffer, Received); - } - } - } // for i - m_Slots[] -} - - - - - -void cSocketThreads::cSocketThread::WriteToSockets(fd_set * a_Write) -{ - // Write to available client sockets: - cCSLock Lock(m_Parent->m_CS); - for (int i = m_NumSlots - 1; i >= 0; --i) - { - cSocket::xSocket Socket = m_Slots[i].m_Socket.GetSocket(); - if (!cSocket::IsValidSocket(Socket) || !FD_ISSET(Socket, a_Write)) - { - continue; - } - if (m_Slots[i].m_Outgoing.empty()) - { - // Request another chunk of outgoing data: - if (m_Slots[i].m_Client != nullptr) - { - AString Data; - m_Slots[i].m_Client->GetOutgoingData(Data); - m_Slots[i].m_Outgoing.append(Data); - } - if (m_Slots[i].m_Outgoing.empty()) - { - // No outgoing data is ready - if (m_Slots[i].m_State == sSlot::ssWritingRestOut) - { - m_Slots[i].m_State = sSlot::ssShuttingDown; - m_Slots[i].m_Socket.ShutdownReadWrite(); - } - continue; - } - } // if (outgoing data is empty) - - if (m_Slots[i].m_State == sSlot::ssRemoteClosed) - { - continue; - } - - if (!SendDataThroughSocket(m_Slots[i].m_Socket, m_Slots[i].m_Outgoing)) - { - int Err = cSocket::GetLastError(); - LOGWARNING("Error %d while writing to client \"%s\", disconnecting. \"%s\"", Err, m_Slots[i].m_Socket.GetIPString().c_str(), GetOSErrorString(Err).c_str()); - m_Slots[i].m_Socket.CloseSocket(); - if (m_Slots[i].m_Client != nullptr) - { - m_Slots[i].m_Client->SocketClosed(); - } - continue; - } - - if (m_Slots[i].m_Outgoing.empty() && (m_Slots[i].m_State == sSlot::ssWritingRestOut)) - { - m_Slots[i].m_State = sSlot::ssShuttingDown; - m_Slots[i].m_Socket.ShutdownReadWrite(); - } - - // _X: If there's data left, it means the client is not reading fast enough, the server would unnecessarily spin in the main loop with zero actions taken; so signalling is disabled - // This means that if there's data left, it will be sent only when there's incoming data or someone queues another packet (for any socket handled by this thread) - /* - // If there's any data left, signalize the Control socket: - if (!m_Slots[i].m_Outgoing.empty()) - { - ASSERT(m_ControlSocket2.IsValid()); - m_ControlSocket2.Send("q", 1); - } - */ - } // for i - m_Slots[i] -} - - - - - -bool cSocketThreads::cSocketThread::SendDataThroughSocket(cSocket & a_Socket, AString & a_Data) -{ - // Send data in smaller chunks, so that the OS send buffers aren't overflown easily - while (!a_Data.empty()) - { - size_t NumToSend = std::min(a_Data.size(), (size_t)1024); - int Sent = a_Socket.Send(a_Data.data(), NumToSend); - if (Sent < 0) - { - int Err = cSocket::GetLastError(); - if (Err == cSocket::ErrWouldBlock) - { - // The OS send buffer is full, leave the outgoing data for the next time - return true; - } - // An error has occured - return false; - } - if (Sent == 0) - { - a_Socket.CloseSocket(); - return true; - } - a_Data.erase(0, Sent); - } - return true; -} - - - - - -void cSocketThreads::cSocketThread::CleanUpShutSockets(void) -{ - cCSLock Lock(m_Parent->m_CS); - for (int i = m_NumSlots - 1; i >= 0; i--) - { - switch (m_Slots[i].m_State) - { - case sSlot::ssShuttingDown2: - { - // The socket has reached the shutdown timeout, close it and clear its slot: - m_Slots[i].m_Socket.CloseSocket(); - m_Slots[i] = m_Slots[--m_NumSlots]; - break; - } - case sSlot::ssShuttingDown: - { - // The socket has been shut down for a single thread loop, let it loop once more before closing: - m_Slots[i].m_State = sSlot::ssShuttingDown2; - break; - } - default: break; - } - } // for i - m_Slots[] -} - - - - -void cSocketThreads::cSocketThread::QueueOutgoingData(void) -{ - cCSLock Lock(m_Parent->m_CS); - for (int i = 0; i < m_NumSlots; i++) - { - if (m_Slots[i].m_Client != nullptr) - { - AString Data; - m_Slots[i].m_Client->GetOutgoingData(Data); - m_Slots[i].m_Outgoing.append(Data); - } - if (m_Slots[i].m_Outgoing.empty()) - { - // No outgoing data is ready - if (m_Slots[i].m_State == sSlot::ssWritingRestOut) - { - // The socket doesn't want to be kept alive anymore, and doesn't have any remaining data to send. - // Shut it down and then close it after a timeout, or when the other side agrees - m_Slots[i].m_State = sSlot::ssShuttingDown; - m_Slots[i].m_Socket.ShutdownReadWrite(); - } - continue; - } - } -} - - - - diff --git a/src/OSSupport/SocketThreads.h b/src/OSSupport/SocketThreads.h deleted file mode 100644 index df819468d..000000000 --- a/src/OSSupport/SocketThreads.h +++ /dev/null @@ -1,194 +0,0 @@ - -// SocketThreads.h - -// Interfaces to the cSocketThreads class representing the heart of MCS's client networking. -// This object takes care of network communication, groups sockets into threads and uses as little threads as possible for full read / write support -// For more detail, see http://forum.mc-server.org/showthread.php?tid=327 - -/* -Additional details: -When a client wants to terminate the connection, they call the RemoveClient() function. This calls the -callback one last time to read all the available outgoing data, putting it in the slot's m_OutgoingData -buffer. Then it marks the slot as having no callback. The socket is kept alive until its outgoing data -queue is empty, then shutdown is called on it and finally the socket is closed after a timeout. -If at any time within this the remote end closes the socket, then the socket is closed directly. -As soon as the socket is closed, the slot is finally removed from the SocketThread. -The graph in $/docs/SocketThreads States.gv shows the state-machine transitions of the slot. -*/ - - - - - -/** How many clients should one thread handle? (must be less than FD_SETSIZE for your platform) */ -#define MAX_SLOTS 63 - - - - - -#pragma once - -#include "Socket.h" -#include "IsThread.h" - - - - -// Check MAX_SLOTS: -#if MAX_SLOTS >= FD_SETSIZE - #error "MAX_SLOTS must be less than FD_SETSIZE for your platform! (otherwise select() won't work)" -#endif - - - - - -// fwd: -class cSocket; -class cClientHandle; - - - - - -class cSocketThreads -{ -public: - - // Clients of cSocketThreads must implement this interface to be able to communicate - class cCallback - { - public: - // Force a virtual destructor in all subclasses: - virtual ~cCallback() {} - - /** Called when data is received from the remote party. - SocketThreads does not care about the return value, others can use it for their specific purpose - - for example HTTPServer uses it to signal if the connection was terminated as a result of the data received. */ - virtual bool DataReceived(const char * a_Data, size_t a_Size) = 0; - - /** Called when data can be sent to remote party - The function is supposed to *set* outgoing data to a_Data (overwrite) */ - virtual void GetOutgoingData(AString & a_Data) = 0; - - /** Called when the socket has been closed for any reason */ - virtual void SocketClosed(void) = 0; - } ; - - - cSocketThreads(void); - ~cSocketThreads(); - - /** Add a (socket, client) pair for processing, data from a_Socket is to be sent to a_Client; returns true if successful */ - bool AddClient(const cSocket & a_Socket, cCallback * a_Client); - - /** Remove the associated socket and the client from processing. - The socket is left to send its last outgoing data and is removed only after all its m_Outgoing is sent - and after the socket is properly shutdown (unless the remote disconnects before that) - */ - void RemoveClient(const cCallback * a_Client); - - /** Notify the thread responsible for a_Client that the client has something to write */ - void NotifyWrite(const cCallback * a_Client); - - /** Puts a_Data into outgoing data queue for a_Client */ - void Write(const cCallback * a_Client, const AString & a_Data); - -private: - - class cSocketThread : - public cIsThread - { - typedef cIsThread super; - - public: - - cSocketThread(cSocketThreads * a_Parent); - virtual ~cSocketThread(); - - // All these methods assume parent's m_CS is locked - bool HasEmptySlot(void) const {return m_NumSlots < MAX_SLOTS; } - bool IsEmpty (void) const {return m_NumSlots == 0; } - - void AddClient (const cSocket & a_Socket, cCallback * a_Client); // Takes ownership of the socket - bool RemoveClient(const cCallback * a_Client); // Returns true if removed, false if not found - bool HasClient (const cCallback * a_Client) const; - bool HasSocket (const cSocket * a_Socket) const; - bool NotifyWrite (const cCallback * a_Client); // Returns true if client handled by this thread - bool Write (const cCallback * a_Client, const AString & a_Data); // Returns true if client handled by this thread - - bool Start(void); // Hide the cIsThread's Start method, we need to provide our own startup to create the control socket - - bool IsValid(void) const {return m_ControlSocket2.IsValid(); } // If the Control socket dies, the thread is not valid anymore - - private: - - cSocketThreads * m_Parent; - - // Two ends of the control socket, the first is select()-ed, the second is written to for notifications - cSocket m_ControlSocket1; - cSocket m_ControlSocket2; - - // Socket-client-dataqueues-state quadruplets. - // Manipulation with these assumes that the parent's m_CS is locked - struct sSlot - { - /** The socket is primarily owned by this object */ - cSocket m_Socket; - - /** The callback to call for events. May be nullptr */ - cCallback * m_Client; - - /** If sending writes only partial data, the rest is stored here for another send. - Also used when the slot is being removed to store the last batch of outgoing data. */ - AString m_Outgoing; - - enum eState - { - ssNormal, ///< Normal read / write operations - ssWritingRestOut, ///< The client callback was removed, continue to send outgoing data - ssShuttingDown, ///< The last outgoing data has been sent, the socket has called shutdown() - ssShuttingDown2, ///< The shutdown has been done at least 1 thread loop ago (timeout detection) - ssRemoteClosed, ///< The remote end has closed the connection (and we still have a client callback) - } m_State; - } ; - - sSlot m_Slots[MAX_SLOTS]; - int m_NumSlots; // Number of slots actually used - - virtual void Execute(void) override; - - /** Prepares the Read and Write socket sets for select() - Puts all sockets into the read set, along with m_ControlSocket1. - Only sockets that have outgoing data queued on them are put in the write set.*/ - void PrepareSets(fd_set * a_ReadSet, fd_set * a_WriteSet, cSocket::xSocket & a_Highest); - - /** Reads from sockets indicated in a_Read */ - void ReadFromSockets(fd_set * a_Read); - - /** Writes to sockets indicated in a_Write */ - void WriteToSockets (fd_set * a_Write); - - /** Sends data through the specified socket, trying to fill the OS send buffer in chunks. - Returns true if there was no error while sending, false if an error has occured. - Modifies a_Data to contain only the unsent data. */ - bool SendDataThroughSocket(cSocket & a_Socket, AString & a_Data); - - /** Removes those slots in ssShuttingDown2 state, sets those with ssShuttingDown state to ssShuttingDown2 */ - void CleanUpShutSockets(void); - - /** Calls each client's callback to retrieve outgoing data for that client. */ - void QueueOutgoingData(void); - } ; - - typedef std::list<cSocketThread *> cSocketThreadList; - - - cCriticalSection m_CS; - cSocketThreadList m_Threads; -} ; - - - - diff --git a/src/OSSupport/TCPLinkImpl.cpp b/src/OSSupport/TCPLinkImpl.cpp index b4cefa60c..c6f1978ad 100644 --- a/src/OSSupport/TCPLinkImpl.cpp +++ b/src/OSSupport/TCPLinkImpl.cpp @@ -7,6 +7,7 @@ #include "TCPLinkImpl.h" #include "NetworkSingleton.h" #include "ServerHandleImpl.h" +#include "event2/buffer.h" @@ -17,8 +18,12 @@ cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks): super(a_LinkCallbacks), - m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), -1, BEV_OPT_CLOSE_ON_FREE)) + m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE)), + m_LocalPort(0), + m_RemotePort(0), + m_ShouldShutdown(false) { + LOGD("Created new cTCPLinkImpl at %p with BufferEvent at %p", this, m_BufferEvent); } @@ -27,9 +32,14 @@ cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks): cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_LinkCallbacks, cServerHandleImplPtr a_Server, const sockaddr * a_Address, socklen_t a_AddrLen): super(a_LinkCallbacks), - m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), a_Socket, BEV_OPT_CLOSE_ON_FREE)), - m_Server(a_Server) + m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), a_Socket, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE)), + m_Server(a_Server), + m_LocalPort(0), + m_RemotePort(0), + m_ShouldShutdown(false) { + LOGD("Created new cTCPLinkImpl at %p with BufferEvent at %p", this, m_BufferEvent); + // Update the endpoint addresses: UpdateLocalAddress(); UpdateAddress(a_Address, a_AddrLen, m_RemoteIP, m_RemotePort); @@ -41,6 +51,7 @@ cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_L cTCPLinkImpl::~cTCPLinkImpl() { + LOGD("Deleting cTCPLinkImpl at %p with BufferEvent at %p", this, m_BufferEvent); bufferevent_free(m_BufferEvent); } @@ -107,7 +118,7 @@ void cTCPLinkImpl::Enable(cTCPLinkImplPtr a_Self) m_Self = a_Self; // Set the LibEvent callbacks and enable processing: - bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this); + bufferevent_setcb(m_BufferEvent, ReadCallback, WriteCallback, EventCallback, this); bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE); } @@ -117,6 +128,11 @@ void cTCPLinkImpl::Enable(cTCPLinkImplPtr a_Self) bool cTCPLinkImpl::Send(const void * a_Data, size_t a_Length) { + if (m_ShouldShutdown) + { + LOGD("%s: Cannot send data, the link is already shut down.", __FUNCTION__); + return false; + } return (bufferevent_write(m_BufferEvent, a_Data, a_Length) == 0); } @@ -126,12 +142,15 @@ bool cTCPLinkImpl::Send(const void * a_Data, size_t a_Length) void cTCPLinkImpl::Shutdown(void) { - #ifdef _WIN32 - shutdown(bufferevent_getfd(m_BufferEvent), SD_SEND); - #else - shutdown(bufferevent_getfd(m_BufferEvent), SHUT_WR); - #endif - bufferevent_disable(m_BufferEvent, EV_WRITE); + // If there's no outgoing data, shutdown the socket directly: + if (evbuffer_get_length(bufferevent_get_output(m_BufferEvent)) == 0) + { + DoActualShutdown(); + return; + } + + // There's still outgoing data in the LibEvent buffer, schedule a shutdown when it's written to OS's TCP stack: + m_ShouldShutdown = true; } @@ -177,8 +196,28 @@ void cTCPLinkImpl::ReadCallback(bufferevent * a_BufferEvent, void * a_Self) +void cTCPLinkImpl::WriteCallback(bufferevent * a_BufferEvent, void * a_Self) +{ + ASSERT(a_Self != nullptr); + auto Self = static_cast<cTCPLinkImpl *>(a_Self); + ASSERT(Self->m_Callbacks != nullptr); + + // If there's no more data to write and the link has been scheduled for shutdown, do the shutdown: + auto OutLen = evbuffer_get_length(bufferevent_get_output(Self->m_BufferEvent)); + if ((OutLen == 0) && (Self->m_ShouldShutdown)) + { + Self->DoActualShutdown(); + } +} + + + + + void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self) { + LOGD("cTCPLink event callback for link %p, BEV %p; what = 0x%02x", a_Self, a_BufferEvent, a_What); + ASSERT(a_Self != nullptr); cTCPLinkImplPtr Self = static_cast<cTCPLinkImpl *>(a_Self)->m_Self; @@ -215,6 +254,8 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void // Pending connection succeeded, call the connection callback: if (a_What & BEV_EVENT_CONNECTED) { + Self->UpdateLocalAddress(); + Self->UpdateRemoteAddress(); if (Self->m_ConnectCallbacks != nullptr) { Self->m_ConnectCallbacks->OnConnected(*Self); @@ -222,8 +263,6 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void Self->m_ConnectCallbacks.reset(); return; } - Self->UpdateLocalAddress(); - Self->UpdateRemoteAddress(); } // If the connection has been closed, call the link callback and remove the connection: @@ -310,6 +349,20 @@ void cTCPLinkImpl::UpdateRemoteAddress(void) +void cTCPLinkImpl::DoActualShutdown(void) +{ + #ifdef _WIN32 + shutdown(bufferevent_getfd(m_BufferEvent), SD_SEND); + #else + shutdown(bufferevent_getfd(m_BufferEvent), SHUT_WR); + #endif + bufferevent_disable(m_BufferEvent, EV_WRITE); +} + + + + + //////////////////////////////////////////////////////////////////////////////// // cNetwork API: diff --git a/src/OSSupport/TCPLinkImpl.h b/src/OSSupport/TCPLinkImpl.h index 735e8ed9d..bea21aeff 100644 --- a/src/OSSupport/TCPLinkImpl.h +++ b/src/OSSupport/TCPLinkImpl.h @@ -94,6 +94,11 @@ protected: Initialized in Enable(), cleared in Close() and EventCallback(RemoteClosed). */ cTCPLinkImplPtr m_Self; + /** If true, Shutdown() has been called and is in queue. + No more data is allowed to be sent via Send() and after all the currently buffered + data is sent to the OS TCP stack, the socket gets shut down. */ + bool m_ShouldShutdown; + /** Creates a new link to be queued to connect to a specified host:port. Used for outgoing connections created using cNetwork::Connect(). @@ -104,6 +109,9 @@ protected: /** Callback that LibEvent calls when there's data available from the remote peer. */ static void ReadCallback(bufferevent * a_BufferEvent, void * a_Self); + /** Callback that LibEvent calls when the remote peer can receive more data. */ + static void WriteCallback(bufferevent * a_BufferEvent, void * a_Self); + /** Callback that LibEvent calls when there's a non-data-related event on the socket. */ static void EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self); @@ -115,6 +123,10 @@ protected: /** Updates m_RemoteIP and m_RemotePort based on the metadata read from the socket. */ void UpdateRemoteAddress(void); + + /** Calls shutdown on the link and disables LibEvent writing. + Called after all data from LibEvent buffers is sent to the OS TCP stack and shutdown() has been called before. */ + void DoActualShutdown(void); }; diff --git a/src/OSSupport/UDPEndpointImpl.cpp b/src/OSSupport/UDPEndpointImpl.cpp new file mode 100644 index 000000000..ece521ab8 --- /dev/null +++ b/src/OSSupport/UDPEndpointImpl.cpp @@ -0,0 +1,608 @@ + +// UDPEndpointImpl.cpp + +// Implements the cUDPEndpointImpl class representing an implementation of an endpoint in UDP communication + +#include "Globals.h" +#include "UDPEndpointImpl.h" +#include "NetworkSingleton.h" + + + + + +//////////////////////////////////////////////////////////////////////////////// +// Globals: + +static bool IsValidSocket(evutil_socket_t a_Socket) +{ + #ifdef _WIN32 + return (a_Socket != INVALID_SOCKET); + #else // _WIN32 + return (a_Socket >= 0); + #endif // else _WIN32 +} + + + + + +/** Converts a_SrcAddr in IPv4 format to a_DstAddr in IPv6 format (using IPv4-mapped IPv6). */ +static void ConvertIPv4ToMappedIPv6(sockaddr_in & a_SrcAddr, sockaddr_in6 & a_DstAddr) +{ + memset(&a_DstAddr, 0, sizeof(a_DstAddr)); + a_DstAddr.sin6_family = AF_INET6; + a_DstAddr.sin6_addr.s6_addr[10] = 0xff; + a_DstAddr.sin6_addr.s6_addr[11] = 0xff; + a_DstAddr.sin6_addr.s6_addr[12] = static_cast<Byte>((a_SrcAddr.sin_addr.s_addr >> 0) & 0xff); + a_DstAddr.sin6_addr.s6_addr[13] = static_cast<Byte>((a_SrcAddr.sin_addr.s_addr >> 8) & 0xff); + a_DstAddr.sin6_addr.s6_addr[14] = static_cast<Byte>((a_SrcAddr.sin_addr.s_addr >> 16) & 0xff); + a_DstAddr.sin6_addr.s6_addr[15] = static_cast<Byte>((a_SrcAddr.sin_addr.s_addr >> 24) & 0xff); + a_DstAddr.sin6_port = a_SrcAddr.sin_port; +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cUDPSendAfterLookup: + +/** A hostname-to-IP resolver callback that sends the data stored within to the resolved IP address. +This is used for sending UDP datagrams to hostnames, so that the cUDPEndpoint::Send() doesn't block. +Instead an instance of this callback is queued for resolving and the data is sent once the IP is resolved. */ +class cUDPSendAfterLookup: + public cNetwork::cResolveNameCallbacks +{ +public: + cUDPSendAfterLookup(const AString & a_Data, UInt16 a_Port, evutil_socket_t a_MainSock, evutil_socket_t a_SecondSock, bool a_IsMainSockIPv6): + m_Data(a_Data), + m_Port(a_Port), + m_MainSock(a_MainSock), + m_SecondSock(a_SecondSock), + m_IsMainSockIPv6(a_IsMainSockIPv6), + m_HasIPv4(false), + m_HasIPv6(false) + { + } + +protected: + /** The data to send after the hostname is resolved. */ + AString m_Data; + + /** The port to which to send the data. */ + UInt16 m_Port; + + /** The primary socket to use for sending. */ + evutil_socket_t m_MainSock; + + /** The secondary socket to use for sending, if needed by the OS. */ + evutil_socket_t m_SecondSock; + + /** True if m_MainSock is an IPv6 socket. */ + bool m_IsMainSockIPv6; + + /** The IPv4 address resolved, if any. */ + sockaddr_in m_AddrIPv4; + + /** Set to true if the name resolved to an IPv4 address. */ + bool m_HasIPv4; + + /** The IPv6 address resolved, if any. */ + sockaddr_in6 m_AddrIPv6; + + /** Set to true if the name resolved to an IPv6 address. */ + bool m_HasIPv6; + + + // cNetwork::cResolveNameCallbacks overrides: + virtual void OnNameResolved(const AString & a_Name, const AString & a_PI) override + { + // Not needed + } + + virtual bool OnNameResolvedV4(const AString & a_Name, const sockaddr_in * a_IP) override + { + if (!m_HasIPv4) + { + m_AddrIPv4 = *a_IP; + m_AddrIPv4.sin_port = htons(m_Port); + m_HasIPv4 = true; + } + + // Don't want OnNameResolved() callback + return false; + } + + virtual bool OnNameResolvedV6(const AString & a_Name, const sockaddr_in6 * a_IP) override + { + if (!m_HasIPv6) + { + m_AddrIPv6 = *a_IP; + m_AddrIPv6.sin6_port = htons(m_Port); + m_HasIPv6 = true; + } + + // Don't want OnNameResolved() callback + return false; + } + + virtual void OnFinished(void) override + { + // Send the actual data, through the correct socket and using the correct resolved address: + if (m_IsMainSockIPv6) + { + if (m_HasIPv6) + { + sendto(m_MainSock, m_Data.data(), static_cast<socklen_t>(m_Data.size()), 0, reinterpret_cast<const sockaddr *>(&m_AddrIPv6), static_cast<socklen_t>(sizeof(m_AddrIPv6))); + } + else if (m_HasIPv4) + { + // If the secondary socket is valid, it is an IPv4 socket, so use that: + if (m_SecondSock != -1) + { + sendto(m_SecondSock, m_Data.data(), static_cast<socklen_t>(m_Data.size()), 0, reinterpret_cast<const sockaddr *>(&m_AddrIPv4), static_cast<socklen_t>(sizeof(m_AddrIPv4))); + } + else + { + // Need an address conversion from IPv4 to IPv6-mapped-IPv4: + ConvertIPv4ToMappedIPv6(m_AddrIPv4, m_AddrIPv6); + sendto(m_MainSock, m_Data.data(), static_cast<socklen_t>(m_Data.size()), 0, reinterpret_cast<const sockaddr *>(&m_AddrIPv6), static_cast<socklen_t>(sizeof(m_AddrIPv6))); + } + } + else + { + LOGD("UDP endpoint queued sendto: Name not resolved"); + return; + } + } + else // m_IsMainSockIPv6 + { + // Main socket is IPv4 only, only allow IPv4 dst address: + if (!m_HasIPv4) + { + LOGD("UDP endpoint queued sendto: Name not resolved to IPv4 for an IPv4-only socket"); + return; + } + sendto(m_MainSock, m_Data.data(), static_cast<socklen_t>(m_Data.size()), 0, reinterpret_cast<const sockaddr *>(&m_AddrIPv4), static_cast<socklen_t>(sizeof(m_AddrIPv4))); + } + } + + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override + { + // Nothing needed + } +}; + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cUDPEndpointImpl: + +cUDPEndpointImpl::cUDPEndpointImpl(UInt16 a_Port, cUDPEndpoint::cCallbacks & a_Callbacks): + super(a_Callbacks), + m_Port(0), + m_MainSock(-1), + m_IsMainSockIPv6(true), + m_SecondarySock(-1), + m_MainEvent(nullptr), + m_SecondaryEvent(nullptr) +{ + Open(a_Port); +} + + + + + +void cUDPEndpointImpl::Close(void) +{ + if (m_Port == 0) + { + // Already closed + return; + } + + // Close the LibEvent handles: + if (m_MainEvent != nullptr) + { + event_free(m_MainEvent); + m_MainEvent = nullptr; + } + if (m_SecondaryEvent != nullptr) + { + event_free(m_SecondaryEvent); + m_SecondaryEvent = nullptr; + } + + // Close the OS sockets: + evutil_closesocket(m_MainSock); + m_MainSock = -1; + evutil_closesocket(m_SecondarySock); + m_SecondarySock = -1; + + // Mark as closed: + m_Port = 0; +} + + + + + +bool cUDPEndpointImpl::IsOpen(void) const +{ + return (m_Port != 0); +} + + + + + +UInt16 cUDPEndpointImpl::GetPort(void) const +{ + return m_Port; +} + + + + + +bool cUDPEndpointImpl::Send(const AString & a_Payload, const AString & a_Host, UInt16 a_Port) +{ + // If a_Host is an IP address, send the data directly: + sockaddr_storage sa; + int salen = static_cast<int>(sizeof(sa)); + memset(&sa, 0, sizeof(sa)); + if (evutil_parse_sockaddr_port(a_Host.c_str(), reinterpret_cast<sockaddr *>(&sa), &salen) != 0) + { + // a_Host is a hostname, we need to do a lookup first: + auto queue = std::make_shared<cUDPSendAfterLookup>(a_Payload, a_Port, m_MainSock, m_SecondarySock, m_IsMainSockIPv6); + return cNetwork::HostnameToIP(a_Host, queue); + } + + // a_Host is an IP address and has been parsed into "sa" + // Insert the correct port and send data: + int NumSent; + switch (sa.ss_family) + { + case AF_INET: + { + reinterpret_cast<sockaddr_in *>(&sa)->sin_port = htons(a_Port); + if (m_IsMainSockIPv6) + { + if (IsValidSocket(m_SecondarySock)) + { + // The secondary socket, which is always IPv4, is present: + NumSent = static_cast<int>(sendto(m_SecondarySock, a_Payload.data(), static_cast<socklen_t>(a_Payload.size()), 0, reinterpret_cast<const sockaddr *>(&sa), static_cast<socklen_t>(salen))); + } + else + { + // Need to convert IPv4 to IPv6 address before sending: + sockaddr_in6 IPv6; + ConvertIPv4ToMappedIPv6(*reinterpret_cast<sockaddr_in *>(&sa), IPv6); + NumSent = static_cast<int>(sendto(m_MainSock, a_Payload.data(), static_cast<socklen_t>(a_Payload.size()), 0, reinterpret_cast<const sockaddr *>(&IPv6), static_cast<socklen_t>(sizeof(IPv6)))); + } + } + else + { + NumSent = static_cast<int>(sendto(m_MainSock, a_Payload.data(), static_cast<socklen_t>(a_Payload.size()), 0, reinterpret_cast<const sockaddr *>(&sa), static_cast<socklen_t>(salen))); + } + break; + } + + case AF_INET6: + { + reinterpret_cast<sockaddr_in6 *>(&sa)->sin6_port = htons(a_Port); + NumSent = static_cast<int>(sendto(m_MainSock, a_Payload.data(), static_cast<socklen_t>(a_Payload.size()), 0, reinterpret_cast<const sockaddr *>(&sa), static_cast<socklen_t>(salen))); + break; + } + default: + { + LOGD("UDP sendto: Invalid address family for address \"%s\".", a_Host.c_str()); + return false; + } + } + return (NumSent > 0); +} + + + + + +void cUDPEndpointImpl::EnableBroadcasts(void) +{ + ASSERT(IsOpen()); + + // Enable broadcasts on the main socket: + // Some OSes use ints, others use chars, so we try both + int broadcastInt = 1; + char broadcastChar = 1; + // (Note that Windows uses const char * for option values, while Linux uses const void *) + if (setsockopt(m_MainSock, SOL_SOCKET, SO_BROADCAST, reinterpret_cast<const char *>(&broadcastInt), sizeof(broadcastInt)) == -1) + { + if (setsockopt(m_MainSock, SOL_SOCKET, SO_BROADCAST, &broadcastChar, sizeof(broadcastChar)) == -1) + { + int err = EVUTIL_SOCKET_ERROR(); + LOGWARNING("Cannot enable broadcasts on port %d: %d (%s)", m_Port, err, evutil_socket_error_to_string(err)); + return; + } + + // Enable broadcasts on the secondary socket, if opened (use char, it worked for primary): + if (IsValidSocket(m_SecondarySock)) + { + if (setsockopt(m_SecondarySock, SOL_SOCKET, SO_BROADCAST, &broadcastChar, sizeof(broadcastChar)) == -1) + { + int err = EVUTIL_SOCKET_ERROR(); + LOGWARNING("Cannot enable broadcasts on port %d (secondary): %d (%s)", m_Port, err, evutil_socket_error_to_string(err)); + } + } + return; + } + + // Enable broadcasts on the secondary socket, if opened (use int, it worked for primary): + if (IsValidSocket(m_SecondarySock)) + { + if (setsockopt(m_SecondarySock, SOL_SOCKET, SO_BROADCAST, reinterpret_cast<const char *>(&broadcastInt), sizeof(broadcastInt)) == -1) + { + int err = EVUTIL_SOCKET_ERROR(); + LOGWARNING("Cannot enable broadcasts on port %d (secondary): %d (%s)", m_Port, err, evutil_socket_error_to_string(err)); + } + } +} + + + + + +void cUDPEndpointImpl::Open(UInt16 a_Port) +{ + ASSERT(m_Port == 0); // Must not be already open + + // Make sure the cNetwork internals are innitialized: + cNetworkSingleton::Get(); + + // Set up the main socket: + // It should listen on IPv6 with IPv4 fallback, when available; IPv4 when IPv6 is not available. + bool NeedsTwoSockets = false; + m_IsMainSockIPv6 = true; + m_MainSock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + + int err; + if (!IsValidSocket(m_MainSock)) + { + // Failed to create IPv6 socket, create an IPv4 one instead: + m_IsMainSockIPv6 = false; + err = EVUTIL_SOCKET_ERROR(); + LOGD("Failed to create IPv6 MainSock: %d (%s)", err, evutil_socket_error_to_string(err)); + m_MainSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (!IsValidSocket(m_MainSock)) + { + err = EVUTIL_SOCKET_ERROR(); + m_Callbacks.OnError(err, Printf("Cannot create UDP socket for port %d: %s", a_Port, evutil_socket_error_to_string(err))); + return; + } + + // Allow the port to be reused right after the socket closes: + if (evutil_make_listen_socket_reuseable(m_MainSock) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + LOG("UDP Port %d cannot be made reusable: %d (%s). Restarting the server might not work.", + a_Port, err, evutil_socket_error_to_string(err) + ); + } + + // Bind to all interfaces: + sockaddr_in name; + memset(&name, 0, sizeof(name)); + name.sin_family = AF_INET; + name.sin_port = ntohs(a_Port); + if (bind(m_MainSock, reinterpret_cast<const sockaddr *>(&name), sizeof(name)) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + m_Callbacks.OnError(err, Printf("Cannot bind UDP port %d: %s", a_Port, evutil_socket_error_to_string(err))); + evutil_closesocket(m_MainSock); + return; + } + } + else + { + // IPv6 socket created, switch it into "dualstack" mode: + UInt32 Zero = 0; + #ifdef _WIN32 + // WinXP doesn't support this feature, so if the setting fails, create another socket later on: + int res = setsockopt(m_MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&Zero), sizeof(Zero)); + err = EVUTIL_SOCKET_ERROR(); + NeedsTwoSockets = ((res == SOCKET_ERROR) && (err == WSAENOPROTOOPT)); + #else + setsockopt(m_MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&Zero), sizeof(Zero)); + #endif + + // Allow the port to be reused right after the socket closes: + if (evutil_make_listen_socket_reuseable(m_MainSock) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + LOG("UDP Port %d cannot be made reusable: %d (%s). Restarting the server might not work.", + a_Port, err, evutil_socket_error_to_string(err) + ); + } + + // Bind to all interfaces: + sockaddr_in6 name; + memset(&name, 0, sizeof(name)); + name.sin6_family = AF_INET6; + name.sin6_port = ntohs(a_Port); + if (bind(m_MainSock, reinterpret_cast<const sockaddr *>(&name), sizeof(name)) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + m_Callbacks.OnError(err, Printf("Cannot bind to UDP port %d: %s", a_Port, evutil_socket_error_to_string(err))); + evutil_closesocket(m_MainSock); + return; + } + } + if (evutil_make_socket_nonblocking(m_MainSock) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + m_Callbacks.OnError(err, Printf("Cannot make socket on UDP port %d nonblocking: %s", a_Port, evutil_socket_error_to_string(err))); + evutil_closesocket(m_MainSock); + return; + } + m_MainEvent = event_new(cNetworkSingleton::Get().GetEventBase(), m_MainSock, EV_READ | EV_PERSIST, RawCallback, this); + event_add(m_MainEvent, nullptr); + + // Read the actual port number on which the socket is listening: + { + sockaddr_storage name; + socklen_t namelen = static_cast<socklen_t>(sizeof(name)); + getsockname(m_MainSock, reinterpret_cast<sockaddr *>(&name), &namelen); + switch (name.ss_family) + { + case AF_INET: + { + sockaddr_in * sin = reinterpret_cast<sockaddr_in *>(&name); + m_Port = ntohs(sin->sin_port); + break; + } + case AF_INET6: + { + sockaddr_in6 * sin6 = reinterpret_cast<sockaddr_in6 *>(&name); + m_Port = ntohs(sin6->sin6_port); + break; + } + } + } + + // If we don't need to create another socket, bail out now: + if (!NeedsTwoSockets) + { + return; + } + + // If a secondary socket is required (WinXP dual-stack), create it here: + LOGD("Creating a second UDP socket for IPv4"); + m_SecondarySock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + if (!IsValidSocket(m_SecondarySock)) + { + // Don't report as an error, the primary socket is working + err = EVUTIL_SOCKET_ERROR(); + LOGD("Socket creation failed for secondary UDP socket for port %d: %d, %s", m_Port, err, evutil_socket_error_to_string(err)); + return; + } + + // Allow the port to be reused right after the socket closes: + if (evutil_make_listen_socket_reuseable(m_SecondarySock) != 0) + { + // Don't report as an error, the primary socket is working + err = EVUTIL_SOCKET_ERROR(); + LOGD("UDP Port %d cannot be made reusable (second socket): %d (%s). Restarting the server might not work.", + a_Port, err, evutil_socket_error_to_string(err) + ); + evutil_closesocket(m_SecondarySock); + m_SecondarySock = -1; + return; + } + + // Make the secondary socket nonblocking: + if (evutil_make_socket_nonblocking(m_SecondarySock) != 0) + { + // Don't report as an error, the primary socket is working + err = EVUTIL_SOCKET_ERROR(); + LOGD("evutil_make_socket_nonblocking() failed for secondary UDP socket: %d, %s", err, evutil_socket_error_to_string(err)); + evutil_closesocket(m_SecondarySock); + m_SecondarySock = -1; + return; + } + + // Bind to all IPv4 interfaces: + sockaddr_in name; + memset(&name, 0, sizeof(name)); + name.sin_family = AF_INET; + name.sin_port = ntohs(m_Port); + if (bind(m_SecondarySock, reinterpret_cast<const sockaddr *>(&name), sizeof(name)) != 0) + { + // Don't report as an error, the primary socket is working + err = EVUTIL_SOCKET_ERROR(); + LOGD("Cannot bind secondary socket to UDP port %d: %d (%s)", m_Port, err, evutil_socket_error_to_string(err)); + evutil_closesocket(m_SecondarySock); + m_SecondarySock = -1; + return; + } + + m_SecondaryEvent = event_new(cNetworkSingleton::Get().GetEventBase(), m_SecondarySock, EV_READ | EV_PERSIST, RawCallback, this); + event_add(m_SecondaryEvent, nullptr); +} + + + + + +void cUDPEndpointImpl::RawCallback(evutil_socket_t a_Socket, short a_What, void * a_Self) +{ + cUDPEndpointImpl * Self = reinterpret_cast<cUDPEndpointImpl *>(a_Self); + Self->Callback(a_Socket, a_What); +} + + + + + +void cUDPEndpointImpl::Callback(evutil_socket_t a_Socket, short a_What) +{ + if ((a_What & EV_READ) != 0) + { + // Receive datagram from the socket: + char buf[64 KiB]; + socklen_t buflen = static_cast<socklen_t>(sizeof(buf)); + sockaddr_storage sa; + socklen_t salen = static_cast<socklen_t>(sizeof(sa)); + auto len = recvfrom(a_Socket, buf, buflen, 0, reinterpret_cast<sockaddr *>(&sa), &salen); + if (len >= 0) + { + // Convert the remote IP address to a string: + char RemoteHost[128]; + UInt16 RemotePort; + switch (sa.ss_family) + { + case AF_INET: + { + auto sin = reinterpret_cast<sockaddr_in *>(&sa); + evutil_inet_ntop(sa.ss_family, &sin->sin_addr, RemoteHost, sizeof(RemoteHost)); + RemotePort = ntohs(sin->sin_port); + break; + } + case AF_INET6: + { + auto sin = reinterpret_cast<sockaddr_in6 *>(&sa); + evutil_inet_ntop(sa.ss_family, &sin->sin6_addr, RemoteHost, sizeof(RemoteHost)); + RemotePort = ntohs(sin->sin6_port); + break; + } + default: + { + return; + } + } + + // Call the callback: + m_Callbacks.OnReceivedData(buf, static_cast<size_t>(len), RemoteHost, RemotePort); + } + } +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cNetwork API: + +cUDPEndpointPtr cNetwork::CreateUDPEndpoint(UInt16 a_Port, cUDPEndpoint::cCallbacks & a_Callbacks) +{ + return std::make_shared<cUDPEndpointImpl>(a_Port, a_Callbacks); +} + + + + diff --git a/src/OSSupport/UDPEndpointImpl.h b/src/OSSupport/UDPEndpointImpl.h new file mode 100644 index 000000000..75942b0cf --- /dev/null +++ b/src/OSSupport/UDPEndpointImpl.h @@ -0,0 +1,81 @@ + +// UDPEndpointImpl.h + +// Declares the cUDPEndpointImpl class representing an implementation of an endpoint in UDP communication + + + + + +#pragma once + +#include "Network.h" +#include <event2/event.h> + + + + + +// fwd: +class cUDPEndpointImpl; +typedef SharedPtr<cUDPEndpointImpl> cUDPEndpointImplPtr; + + + + + +class cUDPEndpointImpl: + public cUDPEndpoint +{ + typedef cUDPEndpoint super; + +public: + /** Creates a new instance of the endpoint, with the specified callbacks. + Tries to open on the specified port; if it fails, the endpoint is left in the "closed" state. + If a_Port is 0, the OS is free to assign any port number it likes to the endpoint. */ + cUDPEndpointImpl(UInt16 a_Port, cUDPEndpoint::cCallbacks & a_Callbacks); + + // cUDPEndpoint overrides: + virtual void Close(void) override; + virtual bool IsOpen(void) const override; + virtual UInt16 GetPort(void) const override; + virtual bool Send(const AString & a_Payload, const AString & a_Host, UInt16 a_Port) override; + virtual void EnableBroadcasts(void) override; + +protected: + /** The local port on which the endpoint is open. + If this is zero, it means the endpoint is closed - either opening has failed, or it has been closed explicitly. */ + UInt16 m_Port; + + /** The primary underlying OS socket. */ + evutil_socket_t m_MainSock; + + /** True if m_MainSock is in the IPv6 namespace (needs IPv6 addresses for sending). */ + bool m_IsMainSockIPv6; + + /** The secondary OS socket (if primary doesn't support dualstack). */ + evutil_socket_t m_SecondarySock; + + /** The LibEvent handle for the primary socket. */ + event * m_MainEvent; + + /** The LibEvent handle for the secondary socket. */ + event * m_SecondaryEvent; + + + /** Creates and opens the socket on the specified port. + If a_Port is 0, the OS is free to assign any port number it likes to the endpoint. + If the opening fails, the OnError() callback is called and the endpoint is left "closed" (IsOpen() returns false). */ + void Open(UInt16 a_Port); + + /** The callback that LibEvent calls when an event occurs on one of the sockets. + Calls Callback() on a_Self. */ + static void RawCallback(evutil_socket_t a_Socket, short a_What, void * a_Self); + + /** The callback that is called when an event occurs on one of the sockets. */ + void Callback(evutil_socket_t a_Socket, short a_What); +}; + + + + diff --git a/src/PolarSSL++/BlockingSslClientSocket.cpp b/src/PolarSSL++/BlockingSslClientSocket.cpp index 59e1281ac..821125b31 100644 --- a/src/PolarSSL++/BlockingSslClientSocket.cpp +++ b/src/PolarSSL++/BlockingSslClientSocket.cpp @@ -10,6 +10,80 @@ +//////////////////////////////////////////////////////////////////////////////// +// cBlockingSslClientSocketConnectCallbacks: + +class cBlockingSslClientSocketConnectCallbacks: + public cNetwork::cConnectCallbacks +{ + /** The socket object that is using this instance of the callbacks. */ + cBlockingSslClientSocket & m_Socket; + + virtual void OnConnected(cTCPLink & a_Link) override + { + m_Socket.OnConnected(); + } + + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override + { + m_Socket.OnConnectError(a_ErrorMsg); + } + +public: + cBlockingSslClientSocketConnectCallbacks(cBlockingSslClientSocket & a_Socket): + m_Socket(a_Socket) + { + } +}; + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cBlockingSslClientSocketLinkCallbacks: + +class cBlockingSslClientSocketLinkCallbacks: + public cTCPLink::cCallbacks +{ + cBlockingSslClientSocket & m_Socket; + + virtual void OnLinkCreated(cTCPLinkPtr a_Link) override + { + m_Socket.SetLink(a_Link); + } + + + virtual void OnReceivedData(const char * a_Data, size_t a_Length) + { + m_Socket.OnReceivedData(a_Data, a_Length); + } + + + virtual void OnRemoteClosed(void) + { + m_Socket.OnDisconnected(); + } + + + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) + { + m_Socket.OnDisconnected(); + } +public: + cBlockingSslClientSocketLinkCallbacks(cBlockingSslClientSocket & a_Socket): + m_Socket(a_Socket) + { + } +}; + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cBlockingSslClientSocket: + cBlockingSslClientSocket::cBlockingSslClientSocket(void) : m_Ssl(*this), m_IsConnected(false) @@ -32,10 +106,19 @@ bool cBlockingSslClientSocket::Connect(const AString & a_ServerName, UInt16 a_Po } // Connect the underlying socket: - m_Socket.CreateSocket(cSocket::IPv4); - if (!m_Socket.ConnectIPv4(a_ServerName.c_str(), a_Port)) + m_ServerName = a_ServerName; + if (!cNetwork::Connect(a_ServerName, a_Port, + std::make_shared<cBlockingSslClientSocketConnectCallbacks>(*this), + std::make_shared<cBlockingSslClientSocketLinkCallbacks>(*this)) + ) + { + return false; + } + + // Wait for the connection to succeed or fail: + m_Event.Wait(); + if (!m_IsConnected) { - Printf(m_LastErrorText, "Socket connect failed: %s", m_Socket.GetLastErrorString().c_str()); return false; } @@ -102,7 +185,7 @@ bool cBlockingSslClientSocket::Send(const void * a_Data, size_t a_NumBytes) ASSERT(m_IsConnected); // Keep sending the data until all of it is sent: - const char * Data = (const char *)a_Data; + const char * Data = reinterpret_cast<const char *>(a_Data); size_t NumBytes = a_NumBytes; for (;;) { @@ -156,7 +239,8 @@ void cBlockingSslClientSocket::Disconnect(void) } m_Ssl.NotifyClose(); - m_Socket.CloseSocket(); + m_Socket->Close(); + m_Socket.reset(); m_IsConnected = false; } @@ -166,13 +250,25 @@ void cBlockingSslClientSocket::Disconnect(void) int cBlockingSslClientSocket::ReceiveEncrypted(unsigned char * a_Buffer, size_t a_NumBytes) { - int res = m_Socket.Receive((char *)a_Buffer, a_NumBytes, 0); - if (res < 0) + // Wait for any incoming data, if there is none: + cCSLock Lock(m_CSIncomingData); + while (m_IsConnected && m_IncomingData.empty()) + { + cCSUnlock Unlock(Lock); + m_Event.Wait(); + } + + // If we got disconnected, report an error after processing all data: + if (!m_IsConnected && m_IncomingData.empty()) { - // PolarSSL's net routines distinguish between connection reset and general failure, we don't need to return POLARSSL_ERR_NET_RECV_FAILED; } - return res; + + // Copy the data from the incoming buffer into the specified space: + size_t NumToCopy = std::min(a_NumBytes, m_IncomingData.size()); + memcpy(a_Buffer, m_IncomingData.data(), NumToCopy); + m_IncomingData.erase(0, NumToCopy); + return static_cast<int>(NumToCopy); } @@ -181,13 +277,69 @@ int cBlockingSslClientSocket::ReceiveEncrypted(unsigned char * a_Buffer, size_t int cBlockingSslClientSocket::SendEncrypted(const unsigned char * a_Buffer, size_t a_NumBytes) { - int res = m_Socket.Send((const char *)a_Buffer, a_NumBytes); - if (res < 0) + cTCPLinkPtr Socket(m_Socket); // Make a copy so that multiple threads don't race on deleting the socket. + if (Socket == nullptr) + { + return POLARSSL_ERR_NET_SEND_FAILED; + } + if (!Socket->Send(a_Buffer, a_NumBytes)) { // PolarSSL's net routines distinguish between connection reset and general failure, we don't need to return POLARSSL_ERR_NET_SEND_FAILED; } - return res; + return static_cast<int>(a_NumBytes); +} + + + + +void cBlockingSslClientSocket::OnConnected(void) +{ + m_IsConnected = true; + m_Event.Set(); +} + + + + + +void cBlockingSslClientSocket::OnConnectError(const AString & a_ErrorMsg) +{ + LOG("Cannot connect to %s: %s", m_ServerName.c_str(), a_ErrorMsg.c_str()); + m_Event.Set(); +} + + + + + +void cBlockingSslClientSocket::OnReceivedData(const char * a_Data, size_t a_Size) +{ + { + cCSLock Lock(m_CSIncomingData); + m_IncomingData.append(a_Data, a_Size); + } + m_Event.Set(); +} + + + + + +void cBlockingSslClientSocket::SetLink(cTCPLinkPtr a_Link) +{ + m_Socket = a_Link; +} + + + + + +void cBlockingSslClientSocket::OnDisconnected(void) +{ + m_Socket.reset(); + m_IsConnected = false; + m_Event.Set(); } diff --git a/src/PolarSSL++/BlockingSslClientSocket.h b/src/PolarSSL++/BlockingSslClientSocket.h index 7af897582..319e82bf2 100644 --- a/src/PolarSSL++/BlockingSslClientSocket.h +++ b/src/PolarSSL++/BlockingSslClientSocket.h @@ -9,8 +9,8 @@ #pragma once +#include "OSSupport/Network.h" #include "CallbackSslContext.h" -#include "../OSSupport/Socket.h" @@ -51,25 +51,56 @@ public: const AString & GetLastErrorText(void) const { return m_LastErrorText; } protected: + friend class cBlockingSslClientSocketConnectCallbacks; + friend class cBlockingSslClientSocketLinkCallbacks; + /** The SSL context used for the socket */ cCallbackSslContext m_Ssl; /** The underlying socket to the SSL server */ - cSocket m_Socket; + cTCPLinkPtr m_Socket; + + /** The object used to signal state changes in the socket (the cause of the blocking). */ + cEvent m_Event; /** The trusted CA root cert store, if we are to verify the cert strictly. Set by SetTrustedRootCertsFromString(). */ cX509CertPtr m_CACerts; /** The expected SSL peer's name, if we are to verify the cert strictly. Set by SetTrustedRootCertsFromString(). */ AString m_ExpectedPeerName; + + /** The hostname to which the socket is connecting (stored for error reporting). */ + AString m_ServerName; /** Text of the last error that has occurred. */ AString m_LastErrorText; /** Set to true if the connection established successfully. */ bool m_IsConnected; + + /** Protects m_IncomingData against multithreaded access. */ + cCriticalSection m_CSIncomingData; + + /** Buffer for the data incoming on the network socket. + Protected by m_CSIncomingData. */ + AString m_IncomingData; + /** Called when the connection is established successfully. */ + void OnConnected(void); + + /** Called when an error occurs while connecting the socket. */ + void OnConnectError(const AString & a_ErrorMsg); + + /** Called when there's incoming data from the socket. */ + void OnReceivedData(const char * a_Data, size_t a_Size); + + /** Called when the link for the connection is created. */ + void SetLink(cTCPLinkPtr a_Link); + + /** Called when the link is disconnected, either gracefully or by an error. */ + void OnDisconnected(void); + // cCallbackSslContext::cDataCallbacks overrides: virtual int ReceiveEncrypted(unsigned char * a_Buffer, size_t a_NumBytes) override; virtual int SendEncrypted(const unsigned char * a_Buffer, size_t a_NumBytes) override; diff --git a/src/PolarSSL++/CryptoKey.cpp b/src/PolarSSL++/CryptoKey.cpp index 7c4f021b3..9354ddf50 100644 --- a/src/PolarSSL++/CryptoKey.cpp +++ b/src/PolarSSL++/CryptoKey.cpp @@ -45,7 +45,7 @@ cCryptoKey::cCryptoKey(const AString & a_PrivateKeyData, const AString & a_Passw if (res != 0) { LOGWARNING("Failed to parse private key: -0x%x", res); - ASSERT(!"Cannot parse PubKey"); + ASSERT(!"Cannot parse PrivKey"); return; } } diff --git a/src/PolarSSL++/SslContext.cpp b/src/PolarSSL++/SslContext.cpp index 902267f90..5ac4bc227 100644 --- a/src/PolarSSL++/SslContext.cpp +++ b/src/PolarSSL++/SslContext.cpp @@ -7,6 +7,7 @@ #include "SslContext.h" #include "EntropyContext.h" #include "CtrDrbgContext.h" +#include "polarssl/debug.h" @@ -69,8 +70,10 @@ int cSslContext::Initialize(bool a_IsClient, const SharedPtr<cCtrDrbgContext> & // These functions allow us to debug SSL and certificate problems, but produce way too much output, // so they're disabled until someone needs them ssl_set_dbg(&m_Ssl, &SSLDebugMessage, this); + debug_set_threshold(2); + ssl_set_verify(&m_Ssl, &SSLVerifyCert, this); - */ + //*/ /* // Set ciphersuite to the easiest one to decode, so that the connection can be wireshark-decoded: diff --git a/src/Protocol/Protocol17x.cpp b/src/Protocol/Protocol17x.cpp index 169367949..f78c2e54b 100644 --- a/src/Protocol/Protocol17x.cpp +++ b/src/Protocol/Protocol17x.cpp @@ -679,8 +679,8 @@ void cProtocol172::SendMapDecorators(int a_ID, const cMapDecoratorList & a_Decor for (cMapDecoratorList::const_iterator it = a_Decorators.begin(); it != a_Decorators.end(); ++it) { - ASSERT((it->GetPixelX() >= 0) && (it->GetPixelX() < 256)); - ASSERT((it->GetPixelZ() >= 0) && (it->GetPixelZ() < 256)); + ASSERT(it->GetPixelX() < 256); + ASSERT(it->GetPixelZ() < 256); Pkt.WriteByte(static_cast<Byte>((it->GetType() << 4) | static_cast<Byte>(it->GetRot() & 0xf))); Pkt.WriteByte(static_cast<Byte>(it->GetPixelX())); Pkt.WriteByte(static_cast<Byte>(it->GetPixelZ())); @@ -694,7 +694,7 @@ void cProtocol172::SendMapDecorators(int a_ID, const cMapDecoratorList & a_Decor void cProtocol172::SendMapInfo(int a_ID, unsigned int a_Scale) { ASSERT(m_State == 3); // In game mode? - ASSERT((a_Scale >= 0) && (a_Scale < 256)); + ASSERT(a_Scale < 256); cPacketizer Pkt(*this, 0x34); Pkt.WriteVarInt(static_cast<UInt32>(a_ID)); @@ -1757,7 +1757,10 @@ void cProtocol172::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) void cProtocol172::HandlePacketLoginEncryptionResponse(cByteBuffer & a_ByteBuffer) { short EncKeyLength, EncNonceLength; - a_ByteBuffer.ReadBEShort(EncKeyLength); + if (!a_ByteBuffer.ReadBEShort(EncKeyLength)) + { + return; + } if ((EncKeyLength < 0) || (EncKeyLength > MAX_ENC_LEN)) { LOGD("Invalid Encryption Key length: %d. Kicking client.", EncKeyLength); diff --git a/src/Protocol/Protocol18x.cpp b/src/Protocol/Protocol18x.cpp index 3c4e049bd..22280f800 100644 --- a/src/Protocol/Protocol18x.cpp +++ b/src/Protocol/Protocol18x.cpp @@ -108,8 +108,17 @@ cProtocol180::cProtocol180(cClientHandle * a_Client, const AString & a_ServerAdd { static int sCounter = 0; cFile::CreateFolder("CommLogs"); - AString FileName = Printf("CommLogs/%x_%d__%s.log", (unsigned)time(nullptr), sCounter++, a_Client->GetIPString().c_str()); - m_CommLogFile.Open(FileName, cFile::fmWrite); + AString IP(a_Client->GetIPString()); + ReplaceString(IP, ":", "_"); + AString FileName = Printf("CommLogs/%x_%d__%s.log", + static_cast<unsigned>(time(nullptr)), + sCounter++, + IP.c_str() + ); + if (!m_CommLogFile.Open(FileName, cFile::fmWrite)) + { + LOG("Cannot log communication to file, the log file \"%s\" cannot be opened for writing.", FileName.c_str()); + } } } @@ -377,7 +386,7 @@ void cProtocol180::SendEntityLook(const cEntity & a_Entity) Pkt.WriteVarInt(a_Entity.GetUniqueID()); Pkt.WriteByteAngle(a_Entity.GetYaw()); Pkt.WriteByteAngle(a_Entity.GetPitch()); - Pkt.WriteBool(true); // TODO: IsOnGround() on entities + Pkt.WriteBool(a_Entity.IsOnGround()); } @@ -420,7 +429,7 @@ void cProtocol180::SendEntityRelMove(const cEntity & a_Entity, char a_RelX, char Pkt.WriteByte(a_RelX); Pkt.WriteByte(a_RelY); Pkt.WriteByte(a_RelZ); - Pkt.WriteBool(true); // TODO: IsOnGround() on entities + Pkt.WriteBool(a_Entity.IsOnGround()); } @@ -438,7 +447,7 @@ void cProtocol180::SendEntityRelMoveLook(const cEntity & a_Entity, char a_RelX, Pkt.WriteByte(a_RelZ); Pkt.WriteByteAngle(a_Entity.GetYaw()); Pkt.WriteByteAngle(a_Entity.GetPitch()); - Pkt.WriteBool(true); // TODO: IsOnGround() on entities + Pkt.WriteBool(a_Entity.IsOnGround()); } @@ -865,11 +874,15 @@ void cProtocol180::SendPlayerListUpdatePing(const cPlayer & a_Player) { ASSERT(m_State == 3); // In game mode? - cPacketizer Pkt(*this, 0x38); // Playerlist Item packet - Pkt.WriteVarInt(2); - Pkt.WriteVarInt(1); - Pkt.WriteUUID(a_Player.GetUUID()); - Pkt.WriteVarInt((UInt32)a_Player.GetClientHandle()->GetPing()); + auto ClientHandle = a_Player.GetClientHandlePtr(); + if (ClientHandle != nullptr) + { + cPacketizer Pkt(*this, 0x38); // Playerlist Item packet + Pkt.WriteVarInt(2); + Pkt.WriteVarInt(1); + Pkt.WriteUUID(a_Player.GetUUID()); + Pkt.WriteVarInt(static_cast<UInt32>(ClientHandle->GetPing())); + } } @@ -938,7 +951,7 @@ void cProtocol180::SendPlayerMoveLook(void) Pkt.WriteDouble(Player->GetPosX()); // The "+ 0.001" is there because otherwise the player falls through the block they were standing on. - Pkt.WriteDouble(Player->GetStance() + 0.001); + Pkt.WriteDouble(Player->GetPosY() + 0.001); Pkt.WriteDouble(Player->GetPosZ()); Pkt.WriteFloat((float)Player->GetYaw()); @@ -967,7 +980,7 @@ void cProtocol180::SendPlayerSpawn(const cPlayer & a_Player) Pkt.WriteVarInt(a_Player.GetUniqueID()); Pkt.WriteUUID(cMojangAPI::MakeUUIDShort(a_Player.GetUUID())); Pkt.WriteFPInt(a_Player.GetPosX()); - Pkt.WriteFPInt(a_Player.GetPosY()); + Pkt.WriteFPInt(a_Player.GetPosY() + 0.001); // The "+ 0.001" is there because otherwise the player falls through the block they were standing on. Pkt.WriteFPInt(a_Player.GetPosZ()); Pkt.WriteByteAngle(a_Player.GetYaw()); Pkt.WriteByteAngle(a_Player.GetPitch()); @@ -1296,7 +1309,7 @@ void cProtocol180::SendTeleportEntity(const cEntity & a_Entity) Pkt.WriteFPInt(a_Entity.GetPosZ()); Pkt.WriteByteAngle(a_Entity.GetYaw()); Pkt.WriteByteAngle(a_Entity.GetPitch()); - Pkt.WriteBool(true); // TODO: IsOnGrond() on entities + Pkt.WriteBool(a_Entity.IsOnGround()); } @@ -1659,7 +1672,7 @@ void cProtocol180::FixItemFramePositions(int a_ObjectData, double & a_PosX, doub void cProtocol180::AddReceivedData(const char * a_Data, size_t a_Size) { // Write the incoming data into the comm log file: - if (g_ShouldLogCommIn) + if (g_ShouldLogCommIn && m_CommLogFile.IsOpen()) { if (m_ReceivedData.GetReadableSpace() > 0) { @@ -1764,7 +1777,7 @@ void cProtocol180::AddReceivedData(const char * a_Data, size_t a_Size) bb.Write("\0", 1); // Log the packet info into the comm log file: - if (g_ShouldLogCommIn) + if (g_ShouldLogCommIn && m_CommLogFile.IsOpen()) { AString PacketData; bb.ReadAll(PacketData); @@ -1796,7 +1809,7 @@ void cProtocol180::AddReceivedData(const char * a_Data, size_t a_Size) #endif // _DEBUG // Put a message in the comm log: - if (g_ShouldLogCommIn) + if (g_ShouldLogCommIn && m_CommLogFile.IsOpen()) { m_CommLogFile.Printf("^^^^^^ Unhandled packet ^^^^^^\n\n\n"); } @@ -1813,7 +1826,7 @@ void cProtocol180::AddReceivedData(const char * a_Data, size_t a_Size) ); // Put a message in the comm log: - if (g_ShouldLogCommIn) + if (g_ShouldLogCommIn && m_CommLogFile.IsOpen()) { m_CommLogFile.Printf("^^^^^^ Wrong number of bytes read for this packet (exp %d left, got " SIZE_T_FMT " left) ^^^^^^\n\n\n", 1, bb.GetReadableSpace() @@ -1827,7 +1840,7 @@ void cProtocol180::AddReceivedData(const char * a_Data, size_t a_Size) } // for (ever) // Log any leftover bytes into the logfile: - if (g_ShouldLogCommIn && (m_ReceivedData.GetReadableSpace() > 0)) + if (g_ShouldLogCommIn && (m_ReceivedData.GetReadableSpace() > 0) && m_CommLogFile.IsOpen()) { AString AllData; size_t OldReadableSpace = m_ReceivedData.GetReadableSpace(); @@ -2798,7 +2811,7 @@ cProtocol180::cPacketizer::~cPacketizer() } // Log the comm into logfile: - if (g_ShouldLogCommOut) + if (g_ShouldLogCommOut && m_Protocol.m_CommLogFile.IsOpen()) { AString Hex; ASSERT(PacketData.size() > 0); diff --git a/src/RCONServer.cpp b/src/RCONServer.cpp index 49ca4fc61..685bd92f5 100644 --- a/src/RCONServer.cpp +++ b/src/RCONServer.cpp @@ -39,13 +39,50 @@ enum //////////////////////////////////////////////////////////////////////////////// +// cRCONListenCallbacks: + +class cRCONListenCallbacks: + public cNetwork::cListenCallbacks +{ +public: + cRCONListenCallbacks(cRCONServer & a_RCONServer, UInt16 a_Port): + m_RCONServer(a_RCONServer), + m_Port(a_Port) + { + } + +protected: + /** The RCON server instance that we're attached to. */ + cRCONServer & m_RCONServer; + + /** The port for which this instance is responsible. */ + UInt16 m_Port; + + // cNetwork::cListenCallbacks overrides: + virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) override + { + LOG("RCON Client \"%s\" connected!", a_RemoteIPAddress.c_str()); + return std::make_shared<cRCONServer::cConnection>(m_RCONServer, a_RemoteIPAddress); + } + virtual void OnAccepted(cTCPLink & a_Link) override {} + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override + { + LOGWARNING("RCON server error on port %d: %d (%s)", m_Port, a_ErrorCode, a_ErrorMsg.c_str()); + } +}; + + + + + +//////////////////////////////////////////////////////////////////////////////// // cRCONCommandOutput: class cRCONCommandOutput : public cCommandOutputCallback { public: - cRCONCommandOutput(cRCONServer::cConnection & a_Connection, int a_RequestID) : + cRCONCommandOutput(cRCONServer::cConnection & a_Connection, UInt32 a_RequestID) : m_Connection(a_Connection), m_RequestID(a_RequestID) { @@ -59,13 +96,13 @@ public: virtual void Finished(void) override { - m_Connection.SendResponse(m_RequestID, RCON_PACKET_RESPONSE, (int)m_Buffer.size(), m_Buffer.c_str()); + m_Connection.SendResponse(m_RequestID, RCON_PACKET_RESPONSE, static_cast<UInt32>(m_Buffer.size()), m_Buffer.c_str()); delete this; } protected: cRCONServer::cConnection & m_Connection; - int m_RequestID; + UInt32 m_RequestID; AString m_Buffer; } ; @@ -77,9 +114,7 @@ protected: // cRCONServer: cRCONServer::cRCONServer(cServer & a_Server) : - m_Server(a_Server), - m_ListenThread4(*this, cSocket::IPv4, "RCON"), - m_ListenThread6(*this, cSocket::IPv6, "RCON") + m_Server(a_Server) { } @@ -89,8 +124,10 @@ cRCONServer::cRCONServer(cServer & a_Server) : cRCONServer::~cRCONServer() { - m_ListenThread4.Stop(); - m_ListenThread6.Stop(); + for (auto srv: m_ListenServers) + { + srv->Close(); + } } @@ -112,24 +149,28 @@ void cRCONServer::Initialize(cIniFile & a_IniFile) return; } - // Read and initialize both IPv4 and IPv6 ports for RCON - bool HasAnyPorts = false; - AString Ports4 = a_IniFile.GetValueSet("RCON", "PortsIPv4", "25575"); - if (m_ListenThread4.Initialize(Ports4)) - { - HasAnyPorts = true; - m_ListenThread4.Start(); - } - AString Ports6 = a_IniFile.GetValueSet("RCON", "PortsIPv6", "25575"); - if (m_ListenThread6.Initialize(Ports6)) + // Read the listening ports for RCON from config: + AStringVector Ports = ReadUpgradeIniPorts(a_IniFile, "RCON", "Ports", "PortsIPv4", "PortsIPv6", "25575"); + + // Start listening on each specified port: + for (auto port: Ports) { - HasAnyPorts = true; - m_ListenThread6.Start(); + UInt16 PortNum; + if (!StringToInteger(port, PortNum)) + { + LOGINFO("Invalid RCON port value: \"%s\". Ignoring.", port.c_str()); + continue; + } + auto Handle = cNetwork::Listen(PortNum, std::make_shared<cRCONListenCallbacks>(*this, PortNum)); + if (Handle->IsListening()) + { + m_ListenServers.push_back(Handle); + } } - if (!HasAnyPorts) + + if (m_ListenServers.empty()) { - LOGWARNING("RCON is requested, but no ports are specified. Specify at least one port in PortsIPv4 or PortsIPv6. RCON is now disabled."); - return; + LOGWARNING("RCON is enabled but no valid ports were found. RCON is not accessible."); } } @@ -137,103 +178,92 @@ void cRCONServer::Initialize(cIniFile & a_IniFile) -void cRCONServer::OnConnectionAccepted(cSocket & a_Socket) -{ - if (!a_Socket.IsValid()) - { - return; - } +//////////////////////////////////////////////////////////////////////////////// +// cRCONServer::cConnection: - LOG("RCON Client \"%s\" connected!", a_Socket.GetIPString().c_str()); - - // Create a new cConnection object, it will be deleted when the connection is closed - m_SocketThreads.AddClient(a_Socket, new cConnection(*this, a_Socket)); +cRCONServer::cConnection::cConnection(cRCONServer & a_RCONServer, const AString & a_IPAddress) : + m_IsAuthenticated(false), + m_RCONServer(a_RCONServer), + m_IPAddress(a_IPAddress) +{ } -//////////////////////////////////////////////////////////////////////////////// -// cRCONServer::cConnection: - -cRCONServer::cConnection::cConnection(cRCONServer & a_RCONServer, cSocket & a_Socket) : - m_IsAuthenticated(false), - m_RCONServer(a_RCONServer), - m_Socket(a_Socket), - m_IPAddress(a_Socket.GetIPString()) +void cRCONServer::cConnection::OnLinkCreated(cTCPLinkPtr a_Link) { + m_Link = a_Link; } -bool cRCONServer::cConnection::DataReceived(const char * a_Data, size_t a_Size) +void cRCONServer::cConnection::OnReceivedData(const char * a_Data, size_t a_Size) { + ASSERT(m_Link != nullptr); + // Append data to the buffer: m_Buffer.append(a_Data, a_Size); // Process the packets in the buffer: while (m_Buffer.size() >= 14) { - int Length = IntFromBuffer(m_Buffer.data()); + UInt32 Length = UIntFromBuffer(m_Buffer.data()); if (Length > 1500) { // Too long, drop the connection LOGWARNING("Received an invalid RCON packet length (%d), dropping RCON connection to %s.", Length, m_IPAddress.c_str() ); - m_RCONServer.m_SocketThreads.RemoveClient(this); - m_Socket.CloseSocket(); - delete this; - return false; + m_Link->Close(); + m_Link.reset(); + return; } - if (Length > (int)(m_Buffer.size() + 4)) + if (Length > static_cast<UInt32>(m_Buffer.size() + 4)) { // Incomplete packet yet, wait for more data to come - return false; + return; } - int RequestID = IntFromBuffer(m_Buffer.data() + 4); - int PacketType = IntFromBuffer(m_Buffer.data() + 8); + UInt32 RequestID = UIntFromBuffer(m_Buffer.data() + 4); + UInt32 PacketType = UIntFromBuffer(m_Buffer.data() + 8); if (!ProcessPacket(RequestID, PacketType, Length - 10, m_Buffer.data() + 12)) { - m_RCONServer.m_SocketThreads.RemoveClient(this); - m_Socket.CloseSocket(); - delete this; - return false; + m_Link->Close(); + m_Link.reset(); + return; } m_Buffer.erase(0, Length + 4); } // while (m_Buffer.size() >= 14) - return false; } -void cRCONServer::cConnection::GetOutgoingData(AString & a_Data) +void cRCONServer::cConnection::OnRemoteClosed(void) { - a_Data.assign(m_Outgoing); - m_Outgoing.clear(); + m_Link.reset(); } -void cRCONServer::cConnection::SocketClosed(void) +void cRCONServer::cConnection::OnError(int a_ErrorCode, const AString & a_ErrorMsg) { - m_RCONServer.m_SocketThreads.RemoveClient(this); - delete this; + LOGD("Error in RCON connection %s: %d (%s)", m_IPAddress.c_str(), a_ErrorCode, a_ErrorMsg.c_str()); + m_Link.reset(); } -bool cRCONServer::cConnection::ProcessPacket(int a_RequestID, int a_PacketType, int a_PayloadLength, const char * a_Payload) +bool cRCONServer::cConnection::ProcessPacket(UInt32 a_RequestID, UInt32 a_PacketType, UInt32 a_PayloadLength, const char * a_Payload) { switch (a_PacketType) { @@ -242,7 +272,7 @@ bool cRCONServer::cConnection::ProcessPacket(int a_RequestID, int a_PacketType, if (strncmp(a_Payload, m_RCONServer.m_Password.c_str(), a_PayloadLength) != 0) { LOGINFO("RCON: Invalid password from client %s, dropping connection.", m_IPAddress.c_str()); - SendResponse(-1, RCON_PACKET_RESPONSE, 0, nullptr); + SendResponse(0xffffffffU, RCON_PACKET_RESPONSE, 0, nullptr); return false; } m_IsAuthenticated = true; @@ -284,23 +314,22 @@ bool cRCONServer::cConnection::ProcessPacket(int a_RequestID, int a_PacketType, -/// Reads 4 bytes from a_Buffer and returns the int they represent -int cRCONServer::cConnection::IntFromBuffer(const char * a_Buffer) +UInt32 cRCONServer::cConnection::UIntFromBuffer(const char * a_Buffer) { - return ((unsigned char)a_Buffer[3] << 24) | ((unsigned char)a_Buffer[2] << 16) | ((unsigned char)a_Buffer[1] << 8) | (unsigned char)a_Buffer[0]; + const Byte * Buffer = reinterpret_cast<const Byte *>(a_Buffer); + return (Buffer[3] << 24) | (Buffer[2] << 16) | (Buffer[1] << 8) | Buffer[0]; } -/// Puts 4 bytes representing the int into the buffer -void cRCONServer::cConnection::IntToBuffer(int a_Value, char * a_Buffer) +void cRCONServer::cConnection::UIntToBuffer(UInt32 a_Value, char * a_Buffer) { - a_Buffer[0] = a_Value & 0xff; - a_Buffer[1] = (a_Value >> 8) & 0xff; - a_Buffer[2] = (a_Value >> 16) & 0xff; - a_Buffer[3] = (a_Value >> 24) & 0xff; + a_Buffer[0] = static_cast<char>(a_Value & 0xff); + a_Buffer[1] = static_cast<char>((a_Value >> 8) & 0xff); + a_Buffer[2] = static_cast<char>((a_Value >> 16) & 0xff); + a_Buffer[3] = static_cast<char>((a_Value >> 24) & 0xff); } @@ -308,25 +337,22 @@ void cRCONServer::cConnection::IntToBuffer(int a_Value, char * a_Buffer) /// Sends a RCON packet back to the client -void cRCONServer::cConnection::SendResponse(int a_RequestID, int a_PacketType, int a_PayloadLength, const char * a_Payload) +void cRCONServer::cConnection::SendResponse(UInt32 a_RequestID, UInt32 a_PacketType, UInt32 a_PayloadLength, const char * a_Payload) { ASSERT((a_PayloadLength == 0) || (a_Payload != nullptr)); // Either zero data to send, or a valid payload ptr + ASSERT(m_Link != nullptr); - char Buffer[4]; - int Length = a_PayloadLength + 10; - IntToBuffer(Length, Buffer); - m_Outgoing.append(Buffer, 4); - IntToBuffer(a_RequestID, Buffer); - m_Outgoing.append(Buffer, 4); - IntToBuffer(a_PacketType, Buffer); - m_Outgoing.append(Buffer, 4); + char Buffer[12]; + UInt32 Length = a_PayloadLength + 10; + UIntToBuffer(Length, Buffer); + UIntToBuffer(a_RequestID, Buffer + 4); + UIntToBuffer(a_PacketType, Buffer + 8); + m_Link->Send(Buffer, 12); if (a_PayloadLength > 0) { - m_Outgoing.append(a_Payload, a_PayloadLength); + m_Link->Send(a_Payload, a_PayloadLength); } - m_Outgoing.push_back(0); - m_Outgoing.push_back(0); - m_RCONServer.m_SocketThreads.NotifyWrite(this); + m_Link->Send("\0", 2); // Send two zero chars as the padding } diff --git a/src/RCONServer.h b/src/RCONServer.h index 47c746736..352fa7b50 100644 --- a/src/RCONServer.h +++ b/src/RCONServer.h @@ -9,8 +9,7 @@ #pragma once -#include "OSSupport/SocketThreads.h" -#include "OSSupport/ListenThread.h" +#include "OSSupport/Network.h" @@ -24,8 +23,7 @@ class cIniFile; -class cRCONServer : - public cListenThread::cCallback +class cRCONServer { public: cRCONServer(cServer & a_Server); @@ -35,72 +33,61 @@ public: protected: friend class cRCONCommandOutput; + friend class cRCONListenCallbacks; class cConnection : - public cSocketThreads::cCallback + public cTCPLink::cCallbacks { public: - cConnection(cRCONServer & a_RCONServer, cSocket & a_Socket); + cConnection(cRCONServer & a_RCONServer, const AString & a_IPAddress); protected: friend class cRCONCommandOutput; - /// Set to true if the client has successfully authenticated + /** Set to true if the client has successfully authenticated */ bool m_IsAuthenticated; - /// Buffer for the incoming data + /** Buffer for the incoming data */ AString m_Buffer; - /// Buffer for the outgoing data - AString m_Outgoing; - - /// Server that owns this connection and processes requests + /** Server that owns this connection and processes requests */ cRCONServer & m_RCONServer; - /// The socket belonging to the client - cSocket & m_Socket; + /** The TCP link to the client */ + cTCPLinkPtr m_Link; - /// Address of the client + /** Address of the client */ AString m_IPAddress; - // cSocketThreads::cCallback overrides: - virtual bool DataReceived(const char * a_Data, size_t a_Size) override; - virtual void GetOutgoingData(AString & a_Data) override; - virtual void SocketClosed(void) override; + // cTCPLink::cCallbacks overrides: + virtual void OnLinkCreated(cTCPLinkPtr a_Link); + virtual void OnReceivedData(const char * a_Data, size_t a_Length) override; + virtual void OnRemoteClosed(void) override; + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override; - /// Processes the given packet and sends the response; returns true if successful, false if the connection is to be dropped - bool ProcessPacket(int a_RequestID, int a_PacketType, int a_PayloadLength, const char * a_Payload); + /** Processes the given packet and sends the response; returns true if successful, false if the connection is to be dropped */ + bool ProcessPacket(UInt32 a_RequestID, UInt32 a_PacketType, UInt32 a_PayloadLength, const char * a_Payload); - /// Reads 4 bytes from a_Buffer and returns the int they represent - int IntFromBuffer(const char * a_Buffer); + /** Reads 4 bytes from a_Buffer and returns the LE UInt32 they represent */ + UInt32 UIntFromBuffer(const char * a_Buffer); - /// Puts 4 bytes representing the int into the buffer - void IntToBuffer(int a_Value, char * a_Buffer); + /** Puts 4 bytes representing the int into the buffer */ + void UIntToBuffer(UInt32 a_Value, char * a_Buffer); - /// Sends a RCON packet back to the client - void SendResponse(int a_RequestID, int a_PacketType, int a_PayloadLength, const char * a_Payload); + /** Sends a RCON packet back to the client */ + void SendResponse(UInt32 a_RequestID, UInt32 a_PacketType, UInt32 a_PayloadLength, const char * a_Payload); } ; - /// The server object that will process the commands received + /** The server object that will process the commands received */ cServer & m_Server; - /// The thread(s) that take care of all the traffic on the RCON ports - cSocketThreads m_SocketThreads; - - /// The thread for accepting IPv4 RCON connections - cListenThread m_ListenThread4; - - /// The thread for accepting IPv6 RCON connections - cListenThread m_ListenThread6; + /** The sockets for accepting RCON connections (one socket per port). */ + cServerHandlePtrs m_ListenServers; - /// Password for authentication + /** Password for authentication */ AString m_Password; - - - // cListenThread::cCallback overrides: - virtual void OnConnectionAccepted(cSocket & a_Socket) override; } ; diff --git a/src/Root.cpp b/src/Root.cpp index eaacf3608..27d87c717 100644 --- a/src/Root.cpp +++ b/src/Root.cpp @@ -181,43 +181,49 @@ void cRoot::Start(void) IniFile.WriteFile("settings.ini"); LOGD("Finalising startup..."); - m_Server->Start(); - - m_WebAdmin->Start(); - - #if !defined(ANDROID_NDK) - LOGD("Starting InputThread..."); - try + if (m_Server->Start()) { - m_InputThread = std::thread(InputThread, std::ref(*this)); - m_InputThread.detach(); - } - catch (std::system_error & a_Exception) - { - LOGERROR("cRoot::Start (std::thread) error %i: could not construct input thread; %s", a_Exception.code().value(), a_Exception.what()); - } - #endif + m_WebAdmin->Start(); - LOG("Startup complete, took %ldms!", static_cast<long int>(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - BeginTime).count())); - #ifdef _WIN32 - EnableMenuItem(hmenu, SC_CLOSE, MF_ENABLED); // Re-enable close button - #endif + #if !defined(ANDROID_NDK) + LOGD("Starting InputThread..."); + try + { + m_InputThread = std::thread(InputThread, std::ref(*this)); + m_InputThread.detach(); + } + catch (std::system_error & a_Exception) + { + LOGERROR("cRoot::Start (std::thread) error %i: could not construct input thread; %s", a_Exception.code().value(), a_Exception.what()); + } + #endif - while (!m_bStop && !m_bRestart && !m_TerminateEventRaised) // These are modified by external threads - { - std::this_thread::sleep_for(std::chrono::seconds(1)); - } + LOG("Startup complete, took %ldms!", static_cast<long int>(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - BeginTime).count())); + #ifdef _WIN32 + EnableMenuItem(hmenu, SC_CLOSE, MF_ENABLED); // Re-enable close button + #endif - if (m_TerminateEventRaised) + while (!m_bStop && !m_bRestart && !m_TerminateEventRaised) // These are modified by external threads + { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + if (m_TerminateEventRaised) + { + m_bStop = true; + } + + // Stop the server: + m_WebAdmin->Stop(); + + LOG("Shutting down server..."); + m_Server->Shutdown(); + } // if (m_Server->Start()) + else { m_bStop = true; } - // Stop the server: - m_WebAdmin->Stop(); - - LOG("Shutting down server..."); - m_Server->Shutdown(); delete m_MojangAPI; m_MojangAPI = nullptr; LOGD("Shutting down deadlock detector..."); diff --git a/src/Server.cpp b/src/Server.cpp index 4dbe59ac6..3f61be378 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -5,7 +5,6 @@ #include "Server.h" #include "ClientHandle.h" #include "Mobs/Monster.h" -#include "OSSupport/Socket.h" #include "Root.h" #include "World.h" #include "ChunkDef.h" @@ -58,6 +57,39 @@ typedef std::list< cClientHandle* > ClientList; //////////////////////////////////////////////////////////////////////////////// +// cServerListenCallbacks: + +class cServerListenCallbacks: + public cNetwork::cListenCallbacks +{ + cServer & m_Server; + UInt16 m_Port; + + virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) override + { + return m_Server.OnConnectionAccepted(a_RemoteIPAddress); + } + + virtual void OnAccepted(cTCPLink & a_Link) override {} + + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) + { + LOGWARNING("Cannot listen on port %d: %d (%s).", m_Port, a_ErrorCode, a_ErrorMsg.c_str()); + } + +public: + cServerListenCallbacks(cServer & a_Server, UInt16 a_Port): + m_Server(a_Server), + m_Port(a_Port) + { + } +}; + + + + + +//////////////////////////////////////////////////////////////////////////////// // cServer::cTickThread: cServer::cTickThread::cTickThread(cServer & a_Server) : @@ -100,8 +132,6 @@ void cServer::cTickThread::Execute(void) // cServer: cServer::cServer(void) : - m_ListenThreadIPv4(*this, cSocket::IPv4, "Client"), - m_ListenThreadIPv6(*this, cSocket::IPv6, "Client"), m_PlayerCount(0), m_PlayerCountDiff(0), m_ClientViewDistance(0), @@ -121,42 +151,6 @@ cServer::cServer(void) : -void cServer::ClientDestroying(const cClientHandle * a_Client) -{ - m_SocketThreads.RemoveClient(a_Client); -} - - - - - -void cServer::NotifyClientWrite(const cClientHandle * a_Client) -{ - m_NotifyWriteThread.NotifyClientWrite(a_Client); -} - - - - - -void cServer::WriteToClient(const cClientHandle * a_Client, const AString & a_Data) -{ - m_SocketThreads.Write(a_Client, a_Data); -} - - - - - -void cServer::RemoveClient(const cClientHandle * a_Client) -{ - m_SocketThreads.RemoveClient(a_Client); -} - - - - - void cServer::ClientMovedToWorld(const cClientHandle * a_Client) { cCSLock Lock(m_CSClients); @@ -211,33 +205,8 @@ bool cServer::InitServer(cIniFile & a_SettingsIni, bool a_ShouldAuth) LOGINFO("Compatible clients: %s", MCS_CLIENT_VERSIONS); LOGINFO("Compatible protocol versions %s", MCS_PROTOCOL_VERSIONS); - if (cSocket::WSAStartup() != 0) // Only does anything on Windows, but whatever - { - LOGERROR("WSAStartup() != 0"); - return false; - } - - bool HasAnyPorts = false; - AString Ports = a_SettingsIni.GetValueSet("Server", "Port", "25565"); - m_ListenThreadIPv4.SetReuseAddr(true); - if (m_ListenThreadIPv4.Initialize(Ports)) - { - HasAnyPorts = true; - } - - Ports = a_SettingsIni.GetValueSet("Server", "PortsIPv6", "25565"); - m_ListenThreadIPv6.SetReuseAddr(true); - if (m_ListenThreadIPv6.Initialize(Ports)) - { - HasAnyPorts = true; - } + m_Ports = ReadUpgradeIniPorts(a_SettingsIni, "Server", "Ports", "Port", "PortsIPv6", "25565"); - if (!HasAnyPorts) - { - LOGERROR("Couldn't open any ports. Aborting the server"); - return false; - } - m_RCONServer.Initialize(a_SettingsIni); m_bIsConnected = true; @@ -278,8 +247,6 @@ bool cServer::InitServer(cIniFile & a_SettingsIni, bool a_ShouldAuth) LOGINFO("Setting default viewdistance to the maximum of %d", m_ClientViewDistance); } - m_NotifyWriteThread.Start(this); - PrepareKeys(); return true; @@ -327,36 +294,14 @@ void cServer::PrepareKeys(void) -void cServer::OnConnectionAccepted(cSocket & a_Socket) +cTCPLink::cCallbacksPtr cServer::OnConnectionAccepted(const AString & a_RemoteIPAddress) { - if (!a_Socket.IsValid()) - { - return; - } - - const AString & ClientIP = a_Socket.GetIPString(); - if (ClientIP.empty()) - { - LOGWARN("cServer: A client connected, but didn't present its IP, disconnecting."); - a_Socket.CloseSocket(); - return; - } - - LOGD("Client \"%s\" connected!", ClientIP.c_str()); - - cClientHandle * NewHandle = new cClientHandle(&a_Socket, m_ClientViewDistance); - if (!m_SocketThreads.AddClient(a_Socket, NewHandle)) - { - // For some reason SocketThreads have rejected the handle, clean it up - LOGERROR("Client \"%s\" cannot be handled, server probably unstable", ClientIP.c_str()); - a_Socket.CloseSocket(); - delete NewHandle; - NewHandle = nullptr; - return; - } - + LOGD("Client \"%s\" connected!", a_RemoteIPAddress.c_str()); + cClientHandlePtr NewHandle = std::make_shared<cClientHandle>(a_RemoteIPAddress, m_ClientViewDistance); + NewHandle->SetSelf(NewHandle); cCSLock Lock(m_CSClients); m_Clients.push_back(NewHandle); + return NewHandle; } @@ -403,23 +348,30 @@ bool cServer::Tick(float a_Dt) void cServer::TickClients(float a_Dt) { - cClientHandleList RemoveClients; + cClientHandlePtrs RemoveClients; { cCSLock Lock(m_CSClients); // Remove clients that have moved to a world (the world will be ticking them from now on) - for (cClientHandleList::const_iterator itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr) + for (auto itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr) { - m_Clients.remove(*itr); + for (auto itrC = m_Clients.begin(), endC = m_Clients.end(); itrC != endC; ++itrC) + { + if (itrC->get() == *itr) + { + m_Clients.erase(itrC); + break; + } + } } // for itr - m_ClientsToRemove[] m_ClientsToRemove.clear(); // Tick the remaining clients, take out those that have been destroyed into RemoveClients - for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end();) + for (auto itr = m_Clients.begin(); itr != m_Clients.end();) { if ((*itr)->IsDestroyed()) { - // Remove the client later, when CS is not held, to avoid deadlock: http://forum.mc-server.org/showthread.php?tid=374 + // Delete the client later, when CS is not held, to avoid deadlock: http://forum.mc-server.org/showthread.php?tid=374 RemoveClients.push_back(*itr); itr = m_Clients.erase(itr); continue; @@ -430,10 +382,7 @@ void cServer::TickClients(float a_Dt) } // Delete the clients that have been destroyed - for (cClientHandleList::iterator itr = RemoveClients.begin(); itr != RemoveClients.end(); ++itr) - { - delete *itr; - } // for itr - RemoveClients[] + RemoveClients.clear(); } @@ -442,12 +391,23 @@ void cServer::TickClients(float a_Dt) bool cServer::Start(void) { - if (!m_ListenThreadIPv4.Start()) + for (auto port: m_Ports) { - return false; - } - if (!m_ListenThreadIPv6.Start()) + UInt16 PortNum; + if (!StringToInteger(port, PortNum)) + { + LOGWARNING("Invalid port specified for server: \"%s\". Ignoring.", port.c_str()); + continue; + } + auto Handle = cNetwork::Listen(PortNum, std::make_shared<cServerListenCallbacks>(*this, PortNum)); + if (Handle->IsListening()) + { + m_ServerHandles.push_back(Handle); + } + } // for port - Ports[] + if (m_ServerHandles.empty()) { + LOGERROR("Couldn't open any ports. Aborting the server"); return false; } if (!m_TickThread.Start()) @@ -640,7 +600,6 @@ void cServer::PrintHelp(const AStringVector & a_Split, cCommandOutputCallback & const AStringPair & cmd = *itr; a_Output.Out(Printf("%-*s%s\n", static_cast<int>(Callback.m_MaxLen), cmd.first.c_str(), cmd.second.c_str())); } // for itr - Callback.m_Commands[] - a_Output.Finished(); } @@ -670,19 +629,24 @@ void cServer::BindBuiltInConsoleCommands(void) void cServer::Shutdown(void) { - m_ListenThreadIPv4.Stop(); - m_ListenThreadIPv6.Stop(); + // Stop listening on all sockets: + for (auto srv: m_ServerHandles) + { + srv->Close(); + } + m_ServerHandles.clear(); + // Notify the tick thread and wait for it to terminate: m_bRestarting = true; m_RestartEvent.Wait(); cRoot::Get()->SaveAllChunks(); + // Remove all clients: cCSLock Lock(m_CSClients); - for (ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr) + for (auto itr = m_Clients.begin(); itr != m_Clients.end(); ++itr) { (*itr)->Destroy(); - delete *itr; } m_Clients.clear(); } @@ -694,7 +658,7 @@ void cServer::Shutdown(void) void cServer::KickUser(int a_ClientID, const AString & a_Reason) { cCSLock Lock(m_CSClients); - for (ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr) + for (auto itr = m_Clients.begin(); itr != m_Clients.end(); ++itr) { if ((*itr)->GetUniqueID() == a_ClientID) { @@ -710,7 +674,7 @@ void cServer::KickUser(int a_ClientID, const AString & a_Reason) void cServer::AuthenticateUser(int a_ClientID, const AString & a_Name, const AString & a_UUID, const Json::Value & a_Properties) { cCSLock Lock(m_CSClients); - for (ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr) + for (auto itr = m_Clients.begin(); itr != m_Clients.end(); ++itr) { if ((*itr)->GetUniqueID() == a_ClientID) { @@ -724,82 +688,3 @@ void cServer::AuthenticateUser(int a_ClientID, const AString & a_Name, const ASt -//////////////////////////////////////////////////////////////////////////////// -// cServer::cNotifyWriteThread: - -cServer::cNotifyWriteThread::cNotifyWriteThread(void) : - super("ClientPacketThread"), - m_Server(nullptr) -{ -} - - - - - -cServer::cNotifyWriteThread::~cNotifyWriteThread() -{ - m_ShouldTerminate = true; - m_Event.Set(); - Wait(); -} - - - - - -bool cServer::cNotifyWriteThread::Start(cServer * a_Server) -{ - m_Server = a_Server; - return super::Start(); -} - - - - - -void cServer::cNotifyWriteThread::Execute(void) -{ - cClientHandleList Clients; - while (!m_ShouldTerminate) - { - cCSLock Lock(m_CS); - while (m_Clients.empty()) - { - cCSUnlock Unlock(Lock); - m_Event.Wait(); - if (m_ShouldTerminate) - { - return; - } - } - - // Copy the clients to notify and unlock the CS: - Clients.splice(Clients.begin(), m_Clients); - Lock.Unlock(); - - for (cClientHandleList::iterator itr = Clients.begin(); itr != Clients.end(); ++itr) - { - m_Server->m_SocketThreads.NotifyWrite(*itr); - } // for itr - Clients[] - Clients.clear(); - } // while (!mShouldTerminate) -} - - - - - -void cServer::cNotifyWriteThread::NotifyClientWrite(const cClientHandle * a_Client) -{ - { - cCSLock Lock(m_CS); - m_Clients.remove(const_cast<cClientHandle *>(a_Client)); // Put it there only once - m_Clients.push_back(const_cast<cClientHandle *>(a_Client)); - } - m_Event.Set(); -} - - - - diff --git a/src/Server.h b/src/Server.h index aab47987f..1f30295b7 100644 --- a/src/Server.h +++ b/src/Server.h @@ -9,10 +9,9 @@ #pragma once -#include "OSSupport/SocketThreads.h" -#include "OSSupport/ListenThread.h" - #include "RCONServer.h" +#include "OSSupport/IsThread.h" +#include "OSSupport/Network.h" #ifdef _MSC_VER #pragma warning(push) @@ -36,10 +35,12 @@ // fwd: class cPlayer; class cClientHandle; +typedef SharedPtr<cClientHandle> cClientHandlePtr; +typedef std::list<cClientHandlePtr> cClientHandlePtrs; +typedef std::list<cClientHandle *> cClientHandles; class cIniFile; class cCommandOutputCallback; -typedef std::list<cClientHandle *> cClientHandleList; namespace Json { @@ -50,10 +51,11 @@ namespace Json -class cServer // tolua_export - : public cListenThread::cCallback -{ // tolua_export -public: // tolua_export +// tolua_begin +class cServer +{ +public: + // tolua_end virtual ~cServer() {} bool InitServer(cIniFile & a_SettingsIni, bool a_ShouldAuth); @@ -105,13 +107,6 @@ public: // tolua_export /** Called by cClientHandle's destructor; stop m_SocketThreads from calling back into a_Client */ void ClientDestroying(const cClientHandle * a_Client); - /** Notifies m_SocketThreads that client has something to be written */ - void NotifyClientWrite(const cClientHandle * a_Client); - - void WriteToClient(const cClientHandle * a_Client, const AString & a_Data); // Queues outgoing data for the client through m_SocketThreads - - void RemoveClient(const cClientHandle * a_Client); // Removes the clienthandle from m_SocketThreads - /** Don't tick a_Client anymore, it will be ticked from its cPlayer instead */ void ClientMovedToWorld(const cClientHandle * a_Client); @@ -147,30 +142,7 @@ public: // tolua_export private: friend class cRoot; // so cRoot can create and destroy cServer - - /** When NotifyClientWrite() is called, it is queued for this thread to process (to avoid deadlocks between cSocketThreads, cClientHandle and cChunkMap) */ - class cNotifyWriteThread : - public cIsThread - { - typedef cIsThread super; - - cEvent m_Event; // Set when m_Clients gets appended - cServer * m_Server; - - cCriticalSection m_CS; - cClientHandleList m_Clients; - - virtual void Execute(void); - - public: - - cNotifyWriteThread(void); - ~cNotifyWriteThread(); - - bool Start(cServer * a_Server); - - void NotifyClientWrite(const cClientHandle * a_Client); - } ; + friend class cServerListenCallbacks; // Accessing OnConnectionAccepted() /** The server tick thread takes care of the players who aren't yet spawned in a world */ class cTickThread : @@ -189,21 +161,29 @@ private: } ; - cNotifyWriteThread m_NotifyWriteThread; - - cListenThread m_ListenThreadIPv4; - cListenThread m_ListenThreadIPv6; - - cCriticalSection m_CSClients; ///< Locks client lists - cClientHandleList m_Clients; ///< Clients that are connected to the server and not yet assigned to a cWorld - cClientHandleList m_ClientsToRemove; ///< Clients that have just been moved into a world and are to be removed from m_Clients in the next Tick() - - mutable cCriticalSection m_CSPlayerCount; ///< Locks the m_PlayerCount - int m_PlayerCount; ///< Number of players currently playing in the server - cCriticalSection m_CSPlayerCountDiff; ///< Locks the m_PlayerCountDiff - int m_PlayerCountDiff; ///< Adjustment to m_PlayerCount to be applied in the Tick thread + /** The network sockets listening for client connections. */ + cServerHandlePtrs m_ServerHandles; + + /** Protects m_Clients and m_ClientsToRemove against multithreaded access. */ + cCriticalSection m_CSClients; + + /** Clients that are connected to the server and not yet assigned to a cWorld. */ + cClientHandlePtrs m_Clients; + + /** Clients that have just been moved into a world and are to be removed from m_Clients in the next Tick(). */ + cClientHandles m_ClientsToRemove; - cSocketThreads m_SocketThreads; + /** Protects m_PlayerCount against multithreaded access. */ + mutable cCriticalSection m_CSPlayerCount; + + /** Number of players currently playing in the server. */ + int m_PlayerCount; + + /** Protects m_PlayerCountDiff against multithreaded access. */ + cCriticalSection m_CSPlayerCountDiff; + + /** Adjustment to m_PlayerCount to be applied in the Tick thread. */ + int m_PlayerCountDiff; int m_ClientViewDistance; // The default view distance for clients; settable in Settings.ini @@ -250,19 +230,24 @@ private: /** True if BungeeCord handshake packets (with player UUID) should be accepted. */ bool m_ShouldAllowBungeeCord; + /** The list of ports on which the server should listen for connections. + Initialized in InitServer(), used in Start(). */ + AStringVector m_Ports; + cServer(void); /** Loads, or generates, if missing, RSA keys for protocol encryption */ void PrepareKeys(void); + + /** Creates a new cClientHandle instance and adds it to the list of clients. + Returns the cClientHandle reinterpreted as cTCPLink callbacks. */ + cTCPLink::cCallbacksPtr OnConnectionAccepted(const AString & a_RemoteIPAddress); bool Tick(float a_Dt); /** Ticks the clients in m_Clients, manages the list in respect to removing clients */ void TickClients(float a_Dt); - - // cListenThread::cCallback overrides: - virtual void OnConnectionAccepted(cSocket & a_Socket) override; }; // tolua_export diff --git a/src/StringUtils.cpp b/src/StringUtils.cpp index a63525356..4eb2d48b6 100644 --- a/src/StringUtils.cpp +++ b/src/StringUtils.cpp @@ -905,3 +905,47 @@ bool SplitZeroTerminatedStrings(const AString & a_Strings, AStringVector & a_Out + +AStringVector MergeStringVectors(const AStringVector & a_Strings1, const AStringVector & a_Strings2) +{ + // Initialize the resulting vector by the first vector: + AStringVector res = a_Strings1; + + // Add each item from strings2 that is not already present: + for (auto item : a_Strings2) + { + if (std::find(res.begin(), res.end(), item) == res.end()) + { + res.push_back(item); + } + } // for item - a_Strings2[] + + return res; +} + + + + + +AString StringsConcat(const AStringVector & a_Strings, char a_Separator) +{ + // If the vector is empty, return an empty string: + if (a_Strings.empty()) + { + return ""; + } + + // Concatenate the strings in the vector: + AString res; + res.append(a_Strings[0]); + for (auto itr = a_Strings.cbegin() + 1, end = a_Strings.cend(); itr != end; ++itr) + { + res.push_back(a_Separator); + res.append(*itr); + } + return res; +} + + + + diff --git a/src/StringUtils.h b/src/StringUtils.h index bfe2a41fa..bc3bb7a2c 100644 --- a/src/StringUtils.h +++ b/src/StringUtils.h @@ -115,7 +115,16 @@ a_Output is first cleared and then each separate string is pushed back into a_Ou Returns true if there are at least two strings in a_Output (there was at least one \0 separator). */ extern bool SplitZeroTerminatedStrings(const AString & a_Strings, AStringVector & a_Output); -/// Parses any integer type. Checks bounds and returns errors out of band. +/** Merges the two vectors of strings, removing duplicate entries from the second vector. +The resulting vector contains items from a_Strings1 first, then from a_Strings2. +The order of items doesn't change, only the duplicates are removed. +If a_Strings1 contains duplicates, the result will still contain those duplicates. */ +extern AStringVector MergeStringVectors(const AStringVector & a_Strings1, const AStringVector & a_Strings2); + +/** Concatenates the specified strings into a single string, separated by the specified separator. */ +extern AString StringsConcat(const AStringVector & a_Strings, char a_Separator); + +/** Parses any integer type. Checks bounds and returns errors out of band. */ template <class T> bool StringToInteger(const AString & a_str, T & a_Num) { diff --git a/src/WebAdmin.cpp b/src/WebAdmin.cpp index dbf600c25..13cf3cc41 100644 --- a/src/WebAdmin.cpp +++ b/src/WebAdmin.cpp @@ -19,7 +19,16 @@ -/// Helper class - appends all player names together in a HTML list +static const char DEFAULT_WEBADMIN_PORTS[] = "8080"; + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cPlayerAccum: + +/** Helper class - appends all player names together in an HTML list */ class cPlayerAccum : public cPlayerListCallback { @@ -40,11 +49,12 @@ public: +//////////////////////////////////////////////////////////////////////////////// +// cWebAdmin: + cWebAdmin::cWebAdmin(void) : m_IsInitialized(false), m_IsRunning(false), - m_PortsIPv4("8080"), - m_PortsIPv6(""), m_TemplateScript("<webadmin_template>") { } @@ -91,8 +101,7 @@ bool cWebAdmin::Init(void) m_IniFile.AddHeaderComment(" Password format: Password=*password*; for example:"); m_IniFile.AddHeaderComment(" [User:admin]"); m_IniFile.AddHeaderComment(" Password=admin"); - m_IniFile.SetValue("WebAdmin", "Port", m_PortsIPv4); - m_IniFile.SetValue("WebAdmin", "PortsIPv6", m_PortsIPv6); + m_IniFile.SetValue("WebAdmin", "Ports", DEFAULT_WEBADMIN_PORTS); m_IniFile.WriteFile("webadmin.ini"); } @@ -104,32 +113,6 @@ bool cWebAdmin::Init(void) LOGD("Initialising WebAdmin..."); - m_PortsIPv4 = m_IniFile.GetValueSet("WebAdmin", "Port", m_PortsIPv4); - m_PortsIPv6 = m_IniFile.GetValueSet("WebAdmin", "PortsIPv6", m_PortsIPv6); - - if (!m_HTTPServer.Initialize(m_PortsIPv4, m_PortsIPv6)) - { - return false; - } - m_IsInitialized = true; - m_IniFile.WriteFile("webadmin.ini"); - return true; -} - - - - - -bool cWebAdmin::Start(void) -{ - if (!m_IsInitialized) - { - // Not initialized - return false; - } - - LOGD("Starting WebAdmin..."); - // Initialize the WebAdmin template script and load the file m_TemplateScript.Create(); m_TemplateScript.RegisterAPILibs(); @@ -141,6 +124,7 @@ bool cWebAdmin::Start(void) return false; } + // Load the login template, provide a fallback default if not found: if (!LoadLoginTemplate()) { LOGWARN("Could not load WebAdmin login template \"%s\", using fallback template.", FILE_IO_PREFIX "webadmin/login_template.html"); @@ -155,7 +139,34 @@ bool cWebAdmin::Start(void) "</center>"; } - m_IsRunning = m_HTTPServer.Start(*this); + // Read the ports to be used: + // Note that historically the ports were stored in the "Port" and "PortsIPv6" values + m_Ports = ReadUpgradeIniPorts(m_IniFile, "WebAdmin", "Ports", "Port", "PortsIPv6", DEFAULT_WEBADMIN_PORTS); + + if (!m_HTTPServer.Initialize()) + { + return false; + } + m_IsInitialized = true; + m_IniFile.WriteFile("webadmin.ini"); + return true; +} + + + + + +bool cWebAdmin::Start(void) +{ + if (!m_IsInitialized) + { + // Not initialized + return false; + } + + LOGD("Starting WebAdmin..."); + + m_IsRunning = m_HTTPServer.Start(*this, m_Ports); return m_IsRunning; } diff --git a/src/WebAdmin.h b/src/WebAdmin.h index a85fb1f0c..86a8a9a4b 100644 --- a/src/WebAdmin.h +++ b/src/WebAdmin.h @@ -5,7 +5,6 @@ #pragma once -#include "OSSupport/Socket.h" #include "Bindings/LuaState.h" #include "IniFile.h" #include "HTTPServer/HTTPServer.h" @@ -135,8 +134,16 @@ public: /** Returns the prefix needed for making a link point to the webadmin root from the given URL ("../../../webadmin"-style) */ AString GetBaseURL(const AString & a_URL); - AString GetIPv4Ports(void) const { return m_PortsIPv4; } - AString GetIPv6Ports(void) const { return m_PortsIPv6; } + /** Returns the list of ports used for the webadmin. */ + AString GetPorts(void) const { return StringsConcat(m_Ports, ','); } + + /** OBSOLETE: Returns the list of IPv4 ports used for the webadmin. + Currently there is no distinction between IPv4 and IPv6; use GetPorts() instead. */ + AString GetIPv4Ports(void) const { return GetPorts(); } + + /** OBSOLETE: Returns the list of IPv6 ports used for the webadmin. + Currently there is no distinction between IPv4 and IPv6; use GetPorts() instead. */ + AString GetIPv6Ports(void) const { return GetPorts(); } // tolua_end @@ -205,8 +212,8 @@ protected: PluginList m_Plugins; - AString m_PortsIPv4; - AString m_PortsIPv6; + /** The ports on which the webadmin is running. */ + AStringVector m_Ports; /** The Lua template script to provide templates: */ cLuaState m_TemplateScript; diff --git a/src/World.cpp b/src/World.cpp index 24b1a9b40..474f77b81 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -815,10 +815,9 @@ void cWorld::Stop(void) // Delete the clients that have been in this world: { cCSLock Lock(m_CSClients); - for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr) + for (auto itr = m_Clients.begin(); itr != m_Clients.end(); ++itr) { (*itr)->Destroy(); - delete *itr; } // for itr - m_Clients[] m_Clients.clear(); } @@ -1093,19 +1092,26 @@ void cWorld::TickScheduledTasks(void) void cWorld::TickClients(float a_Dt) { - cClientHandleList RemoveClients; + cClientHandlePtrs RemoveClients; { cCSLock Lock(m_CSClients); // Remove clients scheduled for removal: - for (cClientHandleList::iterator itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr) + for (auto itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr) { - m_Clients.remove(*itr); + for (auto itrC = m_Clients.begin(), endC = m_Clients.end(); itrC != endC; ++itrC) + { + if (itrC->get() == *itr) + { + m_Clients.erase(itrC); + break; + } + } } // for itr - m_ClientsToRemove[] m_ClientsToRemove.clear(); // Add clients scheduled for adding: - for (cClientHandleList::iterator itr = m_ClientsToAdd.begin(), end = m_ClientsToAdd.end(); itr != end; ++itr) + for (auto itr = m_ClientsToAdd.begin(), end = m_ClientsToAdd.end(); itr != end; ++itr) { ASSERT(std::find(m_Clients.begin(), m_Clients.end(), *itr) == m_Clients.end()); m_Clients.push_back(*itr); @@ -1113,7 +1119,7 @@ void cWorld::TickClients(float a_Dt) m_ClientsToAdd.clear(); // Tick the clients, take out those that have been destroyed into RemoveClients - for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end();) + for (auto itr = m_Clients.begin(); itr != m_Clients.end();) { if ((*itr)->IsDestroyed()) { @@ -1126,12 +1132,9 @@ void cWorld::TickClients(float a_Dt) ++itr; } // for itr - m_Clients[] } - - // Delete the clients that have been destroyed - for (cClientHandleList::iterator itr = RemoveClients.begin(); itr != RemoveClients.end(); ++itr) - { - delete *itr; - } // for itr - RemoveClients[] + + // Delete the clients queued for removal: + RemoveClients.clear(); } @@ -3525,7 +3528,7 @@ void cWorld::AddQueuedPlayers(void) cCSLock Lock(m_CSClients); for (cPlayerList::iterator itr = PlayersToAdd.begin(), end = PlayersToAdd.end(); itr != end; ++itr) { - cClientHandle * Client = (*itr)->GetClientHandle(); + cClientHandlePtr Client = (*itr)->GetClientHandlePtr(); if (Client != nullptr) { m_Clients.push_back(Client); diff --git a/src/World.h b/src/World.h index e7519dab8..3cac71a36 100644 --- a/src/World.h +++ b/src/World.h @@ -38,6 +38,9 @@ class cRedstoneSimulator; class cItem; class cPlayer; class cClientHandle; +typedef SharedPtr<cClientHandle> cClientHandlePtr; +typedef std::list<cClientHandlePtr> cClientHandlePtrs; +typedef std::list<cClientHandle *> cClientHandles; class cEntity; class cBlockEntity; class cWorldGenerator; // The generator that actually generates the chunks for a single world @@ -1019,13 +1022,13 @@ private: cCriticalSection m_CSClients; /** List of clients in this world, these will be ticked by this world */ - cClientHandleList m_Clients; + cClientHandlePtrs m_Clients; /** Clients that are scheduled for removal (ticked in another world), waiting for TickClients() to remove them */ - cClientHandleList m_ClientsToRemove; + cClientHandles m_ClientsToRemove; /** Clients that are scheduled for adding, waiting for TickClients to add them */ - cClientHandleList m_ClientsToAdd; + cClientHandlePtrs m_ClientsToAdd; /** Guards m_EntitiesToAdd */ cCriticalSection m_CSEntitiesToAdd; diff --git a/src/WorldStorage/WSSAnvil.cpp b/src/WorldStorage/WSSAnvil.cpp index a76e9461a..cc8b8d3f5 100755 --- a/src/WorldStorage/WSSAnvil.cpp +++ b/src/WorldStorage/WSSAnvil.cpp @@ -1007,21 +1007,28 @@ cBlockEntity * cWSSAnvil::LoadFlowerPotFromNBT(const cParsedNBT & a_NBT, int a_T } std::unique_ptr<cFlowerPotEntity> FlowerPot(new cFlowerPotEntity(a_BlockX, a_BlockY, a_BlockZ, m_World)); - short ItemType = 0, ItemData = 0; + cItem Item; int currentLine = a_NBT.FindChildByName(a_TagIdx, "Item"); if (currentLine >= 0) { - ItemType = (short) a_NBT.GetInt(currentLine); + if (a_NBT.GetType(currentLine) == TAG_String) + { + StringToItem(a_NBT.GetString(currentLine), Item); + } + else if (a_NBT.GetType(currentLine) == TAG_Int) + { + Item.m_ItemType = (short) a_NBT.GetInt(currentLine); + } } currentLine = a_NBT.FindChildByName(a_TagIdx, "Data"); - if (currentLine >= 0) + if ((currentLine >= 0) && (a_NBT.GetType(currentLine) == TAG_Int)) { - ItemData = (short) a_NBT.GetInt(currentLine); + Item.m_ItemDamage = (short) a_NBT.GetInt(currentLine); } - FlowerPot->SetItem(cItem(ItemType, 1, ItemData)); + FlowerPot->SetItem(Item); return FlowerPot.release(); } @@ -3136,8 +3143,11 @@ bool cWSSAnvil::cMCAFile::SetChunkData(const cChunkCoords & a_Chunk, const AStri // Add padding to 4K boundary: size_t BytesWritten = a_Data.size() + MCA_CHUNK_HEADER_LENGTH; - static const char Padding[4095] = {0}; - m_File.Write(Padding, 4096 - (BytesWritten % 4096)); + if (BytesWritten % 4096 != 0) + { + static const char Padding[4095] = {0}; + m_File.Write(Padding, 4096 - (BytesWritten % 4096)); + } // Store the header: ChunkSize = ((u_long)a_Data.size() + MCA_CHUNK_HEADER_LENGTH + 4095) / 4096; // Round data size *up* to nearest 4KB sector, make it a sector number diff --git a/src/main.cpp b/src/main.cpp index d4adc1ed9..20609a2f8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,13 +11,17 @@ #include <dbghelp.h> #endif // _MSC_VER +#include "OSSupport/NetworkSingleton.h" -bool cRoot::m_TerminateEventRaised = false; // If something has told the server to stop; checked periodically in cRoot -static bool g_ServerTerminated = false; // Set to true when the server terminates, so our CTRL handler can then tell the OS to close the console +/** If something has told the server to stop; checked periodically in cRoot */ +bool cRoot::m_TerminateEventRaised = false; + +/** Set to true when the server terminates, so our CTRL handler can then tell the OS to close the console. */ +static bool g_ServerTerminated = false; /** If set to true, the protocols will log each player's incoming (C->S) communication to a per-connection logfile */ bool g_ShouldLogCommIn; @@ -305,6 +309,9 @@ int main( int argc, char **argv) g_ServerTerminated = true; + // Shutdown all of LibEvent: + cNetworkSingleton::Get().Terminate(); + return EXIT_SUCCESS; } diff --git a/tests/Network/EchoServer.cpp b/tests/Network/EchoServer.cpp index 728db0b7c..2a420fbac 100644 --- a/tests/Network/EchoServer.cpp +++ b/tests/Network/EchoServer.cpp @@ -7,6 +7,7 @@ #include <iostream> #include <string> #include "OSSupport/Network.h" +#include "OSSupport/NetworkSingleton.h" @@ -98,7 +99,7 @@ class cEchoServerCallbacks: -int main() +static void DoTest(void) { LOGD("EchoServer: starting up"); cServerHandlePtr Server = cNetwork::Listen(9876, std::make_shared<cEchoServerCallbacks>()); @@ -118,10 +119,22 @@ int main() LOG("Server terminating."); Server->Close(); ASSERT(!Server->IsListening()); + Server.reset(); LOGD("Server has been closed."); +} + + + + + +int main() +{ + DoTest(); printf("Press enter to exit test.\n"); + AString line; std::getline(std::cin, line); + cNetworkSingleton::Get().Terminate(); LOG("Network test finished."); return 0; diff --git a/tests/Network/Google.cpp b/tests/Network/Google.cpp index 2b8830c24..ab366f9e2 100644 --- a/tests/Network/Google.cpp +++ b/tests/Network/Google.cpp @@ -7,6 +7,7 @@ #include <thread> #include "OSSupport/Event.h" #include "OSSupport/Network.h" +#include "OSSupport/NetworkSingleton.h" @@ -96,7 +97,7 @@ public: -int main() +static void DoTest(void) { cEvent evtFinish; @@ -109,7 +110,19 @@ int main() LOGD("Connect request has been queued."); evtFinish.Wait(); +} + + + + + +int main() +{ + DoTest(); + + cNetworkSingleton::Get().Terminate(); LOGD("Network test finished"); + return 0; } diff --git a/tests/Network/NameLookup.cpp b/tests/Network/NameLookup.cpp index 822a96baf..4781a59ec 100644 --- a/tests/Network/NameLookup.cpp +++ b/tests/Network/NameLookup.cpp @@ -7,6 +7,7 @@ #include <thread> #include "OSSupport/Event.h" #include "OSSupport/Network.h" +#include "OSSupport/NetworkSingleton.h" @@ -45,7 +46,7 @@ public: -int main() +static void DoTest(void) { cEvent evtFinish; @@ -70,7 +71,16 @@ int main() LOGD("IP lookup has been successfully queued"); evtFinish.Wait(); LOGD("IP lookup finished."); +} + + + + +int main() +{ + DoTest(); + cNetworkSingleton::Get().Terminate(); LOGD("Network test finished"); return 0; } |