summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--MCServer/Plugins/NetworkTest/Info.lua27
-rw-r--r--MCServer/Plugins/NetworkTest/NetworkTest.lua148
-rw-r--r--MCServer/Plugins/NetworkTest/splashes.txt357
-rw-r--r--src/Bindings/CMakeLists.txt2
-rw-r--r--src/Bindings/LuaNameLookup.h2
-rw-r--r--src/Bindings/LuaServerHandle.cpp213
-rw-r--r--src/Bindings/LuaServerHandle.h89
-rw-r--r--src/Bindings/LuaState.cpp33
-rw-r--r--src/Bindings/LuaState.h9
-rw-r--r--src/Bindings/LuaTCPLink.cpp97
-rw-r--r--src/Bindings/LuaTCPLink.h21
-rw-r--r--src/Bindings/ManualBindings_Network.cpp157
-rw-r--r--src/Globals.h1
-rw-r--r--src/OSSupport/Network.h3
-rw-r--r--tests/Network/EchoServer.cpp1
15 files changed, 1145 insertions, 15 deletions
diff --git a/MCServer/Plugins/NetworkTest/Info.lua b/MCServer/Plugins/NetworkTest/Info.lua
index 8c2604e31..f366fd1be 100644
--- a/MCServer/Plugins/NetworkTest/Info.lua
+++ b/MCServer/Plugins/NetworkTest/Info.lua
@@ -37,6 +37,32 @@ g_PluginInfo =
}, -- 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)",
@@ -57,6 +83,7 @@ g_PluginInfo =
},
},
}, -- lookup
+
}, -- Subcommands
}, -- net
},
diff --git a/MCServer/Plugins/NetworkTest/NetworkTest.lua b/MCServer/Plugins/NetworkTest/NetworkTest.lua
index 30f34c879..9b69ee5b3 100644
--- a/MCServer/Plugins/NetworkTest/NetworkTest.lua
+++ b/MCServer/Plugins/NetworkTest/NetworkTest.lua
@@ -7,11 +7,109 @@
-function Initialize()
+--- Map of all servers currently open
+-- g_Servers[PortNum] = cServerHandle
+local g_Servers = {}
+
+--- 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",
+}
+
+--- Map of all services that can be run as servers
+-- g_Services[ServiceName] = function() -> 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 = 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 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
+
+ -- TODO: Other services (fortune, daytime, ...)
+}
+
+
+
+
+
+function Initialize(a_Plugin)
+ -- Load the splashes.txt file into g_Fortunes:
+ for line in io.lines(a_Plugin:GetLocalFolder() .. "/splashes.txt") do
+ table.insert(g_Fortunes, line)
+ 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
@@ -62,6 +160,26 @@ 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: \"" .. Port .. "\"."
+ 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"
@@ -103,3 +221,31 @@ end
+
+function HandleConsoleNetListen(a_Split)
+ -- Get the params:
+ local Port = tonumber(a_Split[3] or 1024)
+ if not(Port) then
+ return true, "Invalid port: \"" .. Port .. "\"."
+ 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
+
+
+
+
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/src/Bindings/CMakeLists.txt b/src/Bindings/CMakeLists.txt
index 13428b5c6..40b8d4bd9 100644
--- a/src/Bindings/CMakeLists.txt
+++ b/src/Bindings/CMakeLists.txt
@@ -9,6 +9,7 @@ SET (SRCS
DeprecatedBindings.cpp
LuaChunkStay.cpp
LuaNameLookup.cpp
+ LuaServerHandle.cpp
LuaState.cpp
LuaTCPLink.cpp
LuaWindow.cpp
@@ -27,6 +28,7 @@ SET (HDRS
LuaChunkStay.h
LuaFunctions.h
LuaNameLookup.h
+ LuaServerHandle.h
LuaState.h
LuaTCPLink.h
LuaWindow.h
diff --git a/src/Bindings/LuaNameLookup.h b/src/Bindings/LuaNameLookup.h
index 8b456ad90..e4cdb9f53 100644
--- a/src/Bindings/LuaNameLookup.h
+++ b/src/Bindings/LuaNameLookup.h
@@ -25,7 +25,7 @@ public:
cLuaNameLookup(const AString & a_Query, cPluginLua & a_Plugin, int a_CallbacksTableStackPos);
protected:
- /** The plugin for which the link is created. */
+ /** The plugin for which the query is created. */
cPluginLua & m_Plugin;
/** The Lua table that holds the callbacks to be invoked. */
diff --git a/src/Bindings/LuaServerHandle.cpp b/src/Bindings/LuaServerHandle.cpp
new file mode 100644
index 000000000..7b82003bb
--- /dev/null
+++ b/src/Bindings/LuaServerHandle.cpp
@@ -0,0 +1,213 @@
+
+// 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)
+{
+ LOGD("Creating LuaServerHandle at %p.", this);
+}
+
+
+
+
+
+
+cLuaServerHandle::~cLuaServerHandle()
+{
+ // If the server handle is still open, close it explicitly:
+ LOGD("Deleting LuaServerHandle at %p.", this);
+ 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)
+{
+ LOGD("Closing LuaServerHandle at %p.", this);
+
+ // 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 4de34c88d..73b114599 100644
--- a/src/Bindings/LuaState.cpp
+++ b/src/Bindings/LuaState.cpp
@@ -656,6 +656,18 @@ 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());
@@ -970,6 +982,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
@@ -1539,6 +1560,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 0dcd248fe..f68b022ea 100644
--- a/src/Bindings/LuaState.h
+++ b/src/Bindings/LuaState.h
@@ -60,6 +60,7 @@ class cHopperEntity;
class cBlockEntity;
class cBoundingBox;
class cLuaTCPLink;
+class cLuaServerHandle;
typedef cBoundingBox * pBoundingBox;
typedef cWorld * pWorld;
@@ -84,6 +85,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();
@@ -203,6 +208,7 @@ 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(cMonster * a_Monster);
void Push(cPickup * a_Pickup);
@@ -242,6 +248,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.
diff --git a/src/Bindings/LuaTCPLink.cpp b/src/Bindings/LuaTCPLink.cpp
index f88aeff84..6b8395806 100644
--- a/src/Bindings/LuaTCPLink.cpp
+++ b/src/Bindings/LuaTCPLink.cpp
@@ -5,6 +5,7 @@
#include "Globals.h"
#include "LuaTCPLink.h"
+#include "LuaServerHandle.h"
@@ -14,6 +15,47 @@ 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();
}
@@ -107,15 +149,14 @@ UInt16 cLuaTCPLink::GetRemotePort(void) const
void cLuaTCPLink::Shutdown(void)
{
- // Safely grab a copy of the link:
+ // Safely grab a copy of the link and shut it down:
cTCPLinkPtr Link = m_Link;
- if (Link == nullptr)
+ if (Link != nullptr)
{
- return;
+ Link->Shutdown();
}
- // Shutdown:
- Link->Shutdown();
+ Terminated();
}
@@ -124,17 +165,48 @@ void cLuaTCPLink::Shutdown(void)
void cLuaTCPLink::Close(void)
{
- // Safely grab a copy of the link:
+ // If the link is still open, close it:
cTCPLinkPtr Link = m_Link;
- if (Link == nullptr)
+ if (Link != nullptr)
{
- return;
+ Link->Close();
+ }
+
+ Terminated();
+}
+
+
+
+
+
+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);
}
- // Close the link:
- Link->Close();
+ // If the link is still open, close it:
+ cTCPLinkPtr Link = m_Link;
+ if (Link != nullptr)
+ {
+ Link->Close();
+ m_Link.reset();
+ }
}
+
+
+
+
void cLuaTCPLink::OnConnected(cTCPLink & a_Link)
{
// Check if we're still valid:
@@ -171,6 +243,8 @@ void cLuaTCPLink::OnError(int a_ErrorCode, const AString & a_ErrorMsg)
m_Plugin.GetName().c_str(), a_ErrorCode, a_ErrorMsg.c_str()
);
}
+
+ Terminated();
}
@@ -221,7 +295,8 @@ void cLuaTCPLink::OnRemoteClosed(void)
{
LOGINFO("cTCPLink OnRemoteClosed() callback failed in plugin %s.", m_Plugin.GetName().c_str());
}
- m_Link.reset();
+
+ Terminated();
}
diff --git a/src/Bindings/LuaTCPLink.h b/src/Bindings/LuaTCPLink.h
index 125cc1b31..f2af911ec 100644
--- a/src/Bindings/LuaTCPLink.h
+++ b/src/Bindings/LuaTCPLink.h
@@ -16,6 +16,14 @@
+// fwd:
+class cLuaServerHandle;
+typedef WeakPtr<cLuaServerHandle> cLuaServerHandleWPtr;
+
+
+
+
+
class cLuaTCPLink:
public cNetwork::cConnectCallbacks,
public cTCPLink::cCallbacks
@@ -24,6 +32,11 @@ 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);
@@ -60,6 +73,14 @@ protected:
May be nullptr. */
cTCPLinkPtr m_Link;
+ /** The server that is responsible for this link, if any. */
+ cLuaServerHandleWPtr m_Server;
+
+
+ /** 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);
+
// cNetwork::cConnectCallbacks overrides:
virtual void OnConnected(cTCPLink & a_Link) override;
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override;
diff --git a/src/Bindings/ManualBindings_Network.cpp b/src/Bindings/ManualBindings_Network.cpp
index 3123ef885..24c8c73b8 100644
--- a/src/Bindings/ManualBindings_Network.cpp
+++ b/src/Bindings/ManualBindings_Network.cpp
@@ -10,6 +10,7 @@
#include "LuaState.h"
#include "LuaTCPLink.h"
#include "LuaNameLookup.h"
+#include "LuaServerHandle.h"
@@ -72,6 +73,7 @@ static int tolua_cNetwork_Connect(lua_State * L)
+/** Binds cNetwork::HostnameToIP */
static int tolua_cNetwork_HostnameToIP(lua_State * L)
{
// Function signature:
@@ -112,6 +114,7 @@ static int tolua_cNetwork_HostnameToIP(lua_State * L)
+/** Binds cNetwork::IPToHostname */
static int tolua_cNetwork_IPToHostname(lua_State * L)
{
// Function signature:
@@ -152,9 +155,66 @@ static int tolua_cNetwork_IPToHostname(lua_State * L)
+/** Binds cNetwork::Listen */
+static int tolua_cNetwork_Listen(lua_State * L)
+{
+ // Function signature:
+ // cNetwork:Listen(Port, Callbacks) -> bool
+
+ 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;
+}
+
+
+
+
+
////////////////////////////////////////////////////////////////////////////////
// cTCPLink bindings (routed through cLuaTCPLink):
+/** Binds cLuaTCPLink::Send */
static int tolua_cTCPLink_Send(lua_State * L)
{
// Function signature:
@@ -193,6 +253,7 @@ static int tolua_cTCPLink_Send(lua_State * L)
+/** Binds cLuaTCPLink::GetLocalIP */
static int tolua_cTCPLink_GetLocalIP(lua_State * L)
{
// Function signature:
@@ -226,6 +287,7 @@ static int tolua_cTCPLink_GetLocalIP(lua_State * L)
+/** Binds cLuaTCPLink::GetLocalPort */
static int tolua_cTCPLink_GetLocalPort(lua_State * L)
{
// Function signature:
@@ -259,6 +321,7 @@ static int tolua_cTCPLink_GetLocalPort(lua_State * L)
+/** Binds cLuaTCPLink::GetRemoteIP */
static int tolua_cTCPLink_GetRemoteIP(lua_State * L)
{
// Function signature:
@@ -292,6 +355,7 @@ static int tolua_cTCPLink_GetRemoteIP(lua_State * L)
+/** Binds cLuaTCPLink::GetRemotePort */
static int tolua_cTCPLink_GetRemotePort(lua_State * L)
{
// Function signature:
@@ -326,6 +390,90 @@ static int tolua_cTCPLink_GetRemotePort(lua_State * L)
////////////////////////////////////////////////////////////////////////////////
+// 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;
+}
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
// Register the bindings:
void ManualBindings::BindNetwork(lua_State * tolua_S)
@@ -335,15 +483,15 @@ void ManualBindings::BindNetwork(lua_State * tolua_S)
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);
// Fill in the functions (alpha-sorted):
tolua_beginmodule(tolua_S, "cNetwork");
tolua_function(tolua_S, "Connect", tolua_cNetwork_Connect);
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, "cTCPLink");
@@ -353,6 +501,11 @@ void ManualBindings::BindNetwork(lua_State * tolua_S)
tolua_function(tolua_S, "GetRemoteIP", tolua_cTCPLink_GetRemoteIP);
tolua_function(tolua_S, "GetRemotePort", tolua_cTCPLink_GetRemotePort);
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);
}
diff --git a/src/Globals.h b/src/Globals.h
index 654ede95f..29eaac871 100644
--- a/src/Globals.h
+++ b/src/Globals.h
@@ -381,6 +381,7 @@ void inline LOG(const char * a_Format, ...)
// Unified shared ptr from before C++11. Also no silly undercores.
#define SharedPtr std::shared_ptr
+#define WeakPtr std::weak_ptr
diff --git a/src/OSSupport/Network.h b/src/OSSupport/Network.h
index cdf6ba0e9..e883dfb29 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;
diff --git a/tests/Network/EchoServer.cpp b/tests/Network/EchoServer.cpp
index 5f4b7651d..49fb89122 100644
--- a/tests/Network/EchoServer.cpp
+++ b/tests/Network/EchoServer.cpp
@@ -119,6 +119,7 @@ void DoTest(void)
LOG("Server terminating.");
Server->Close();
ASSERT(!Server->IsListening());
+ Server.reset();
LOGD("Server has been closed.");
}