From 35de1b1b87e078ebfcdd13e00813f33c78c65bf8 Mon Sep 17 00:00:00 2001 From: kremsy Date: Mon, 22 Jun 2015 22:52:26 +0200 Subject: [PATCH] improvements of sockethandler, possibility for maniacontrol to maniacontrol connections --- changelog.txt | 3 +- core/Chat.php | 2 +- core/Communication/Communication.php | 103 ++++++++++++++++++ core/Communication/CommunicationManager.php | 72 ++++++++++-- core/Communication/CommunicationMethods.php | 8 +- .../{usage.txt => usage_documentation.txt} | 22 +++- 6 files changed, 192 insertions(+), 18 deletions(-) create mode 100644 core/Communication/Communication.php rename core/Communication/{usage.txt => usage_documentation.txt} (50%) diff --git a/changelog.txt b/changelog.txt index bc39a2ab..85db00dc 100644 --- a/changelog.txt +++ b/changelog.txt @@ -2,7 +2,8 @@ #Additions - added changelog -- added SocketManager which acts like a communication interface you can connect to and interact with ManiaControl (also thanks to TGYoshi for some help) +- added CommunicationManager which acts like a communication interface you can connect to and interact with ManiaControl (also thanks to TGYoshi for some help) + - You can call ManiaControl from a Website or from ManiaControl itself - added "//removerights login" command - added new EchoManager which handles Interactions between different Controllers - It is possible to send an Echo command via the Method sendEcho, as message Parameter strings, objects or arrays can get used diff --git a/core/Chat.php b/core/Chat.php index b6c182cd..601237ac 100644 --- a/core/Chat.php +++ b/core/Chat.php @@ -53,7 +53,7 @@ class Chat implements CallbackListener, CommunicationListener { $this->maniaControl->getCallbackManager()->registerCallbackListener(CallbackManager::CB_MP_PLAYERCHAT, $this, 'onPlayerChat'); //Socket Listenings - $this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::GET_SERVER_CHAT, $this, function ($data) { + $this->maniaControl->getCommunicationManager()->registerCommunicationListener(CommunicationMethods::GET_SERVER_CHAT, $this, function ($error, $data) { return $this->chatBuffer; }); } diff --git a/core/Communication/Communication.php b/core/Communication/Communication.php new file mode 100644 index 00000000..72813003 --- /dev/null +++ b/core/Communication/Communication.php @@ -0,0 +1,103 @@ + + * @copyright 2014-2015 ManiaControl Team + * @license http://www.gnu.org/licenses/ GNU General Public License, Version 3 + */ +class Communication { + private $socket; + private $ip; + private $port; + private $encryptionPassword; + + private $buffer = ""; + private $messageQueue = array(); + + public function __construct($ip, $port, $encryptionPassword) { + $this->ip = $ip; + $this->port = $port; + $this->encryptionPassword = $encryptionPassword; + } + + /** Create an Connection */ + public function createConnection() { + $errno = null; + $errstr = null; + $this->socket = @fsockopen($this->ip, $this->port, $errno, $errstr, 2); + + //socket_set_nonblock($this->socket); + stream_set_blocking($this->socket, 0); + + if ($errno != 0 || !$this->socket) { + var_dump($errstr); + return false; + } + return true; + } + + /** + * Call an Method Asynchronously + * + * @param callable $function + * @param $method + * @param string $data + * @return null + */ + public function call(callable $function, $method, $data = "") { + //TODO throw an exception or smth + if (!$this->socket) { + return null; + } + + $data = json_encode(array("method" => $method, "data" => $data)); + + $data = openssl_encrypt($data, CommunicationManager::ENCRYPTION_METHOD, $this->encryptionPassword, OPENSSL_RAW_DATA, CommunicationManager::ENCRYPTION_IV); + + array_push($this->messageQueue, $function); + + // Write Request on Socket + fwrite($this->socket, strlen($data) . "\n" . $data); + } + + /** + * Process data on every Tick + */ + public function tick() { + $data = fgets($this->socket, 1024); // reads as much as possible OR nothing at all + if (strlen($data) > 0) { // got new data + $this->buffer .= $data; // append new data to buffer + // handle the data the exact same way + $arr = explode("\n", $this->buffer, 2); + while (count($arr) == 2 && strlen($arr[1]) >= (int) $arr[0]) { + // received full message + $len = (int) $arr[0]; + $msg = substr($arr[1], 0, $len); // clip msg + $this->buffer = substr($this->buffer, strlen((string) $len) + 1 /* newline */ + $len); // clip buffer + + // Decode Message + $data = openssl_decrypt($msg, CommunicationManager::ENCRYPTION_METHOD, $this->encryptionPassword, OPENSSL_RAW_DATA, CommunicationManager::ENCRYPTION_IV); + $data = json_decode($data); + + // Received something! + //Call Function with Data + call_user_func(array_shift($this->messageQueue), $data->error, $data->data); + + // next msg + $arr = explode("\n", $this->buffer, 2); + } + } + } + + /** Closes the connection, don't call yourself, let it do the Communication Manager */ + public function closeConnection() { + if ($this->socket) { + fclose($this->socket); + } + } +} \ No newline at end of file diff --git a/core/Communication/CommunicationManager.php b/core/Communication/CommunicationManager.php index f31f8c08..621c43bd 100644 --- a/core/Communication/CommunicationManager.php +++ b/core/Communication/CommunicationManager.php @@ -22,6 +22,13 @@ use React\Socket\Server; * @license http://www.gnu.org/licenses/ GNU General Public License, Version 3 */ class CommunicationManager implements CallbackListener { + /** Constants */ + const SETTING_SOCKET_ENABLED = "Activate Socket"; + const SETTING_SOCKET_PASSWORD = "Password for the Socket Connection"; + const SETTING_SOCKET_PORT = "Socket Port for Server "; + + const ENCRYPTION_IV = "kZ2Kt0CzKUjN2MJX"; + const ENCRYPTION_METHOD = "aes-192-cbc"; /** @var ManiaControl $maniaControl */ private $maniaControl = null; @@ -34,10 +41,8 @@ class CommunicationManager implements CallbackListener { /** @var Server $socket */ private $socket = null; - - const SETTING_SOCKET_ENABLED = "Activate Socket"; - const SETTING_SOCKET_PASSWORD = "Password for the Socket Connection"; - const SETTING_SOCKET_PORT = "Socket Port for Server "; + /** @var Communication[] $communcations */ + private $communications = array(); /** * Create a new Communication Handler Instance @@ -49,8 +54,48 @@ class CommunicationManager implements CallbackListener { $this->maniaControl->getCallbackManager()->registerCallbackListener(SettingManager::CB_SETTING_CHANGED, $this, 'updateSettings'); $this->maniaControl->getCallbackManager()->registerCallbackListener(Callbacks::AFTERINIT, $this, 'initCommunicationManager'); + $this->maniaControl->getCallbackManager()->registerCallbackListener(Callbacks::ONSHUTDOWN, $this, 'onShutDown'); } + /** + * Creates a Communication to another ManiaControl + * + * @param $ip + * @param $port + * @return \ManiaControl\Communication\Communication + */ + public function createCommunication($ip, $port, $encryptionKey) { + $communication = new Communication($ip, $port, $encryptionKey); + $communication->createConnection(); + + $this->communications[] = $communication; + return $communication; + } + + + /** + * Closes a opened Communication + * Does not necessarily need be called, all connections get destroyed on ManiaControl Shutdown + * + * @param Communication $communication + * @return bool + */ + public function closeCommunication($communication) { + $key = array_search($communication, $this->communications); + if (isset($this->communications[$key])) { + $this->communications[$key]->closeConnection(); + unset($this->communications[$key]); + return true; + } + return false; + } + + /** Close all Sockets on maniaControl Shutdown */ + public function onShutDown() { + foreach ($this->communications as $communication) { + $this->closeCommunication($communication); + } + } /** * Register a new Communication Listener @@ -138,7 +183,7 @@ class CommunicationManager implements CallbackListener { } - $this->createSocket(); + $this->createListeningSocket(); } /** @@ -154,7 +199,7 @@ class CommunicationManager implements CallbackListener { $socketEnabled = $this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_SOCKET_ENABLED); if ($socketEnabled && !$this->socket) { - $this->createSocket(); + $this->createListeningSocket(); } if (!$socketEnabled) { @@ -165,7 +210,7 @@ class CommunicationManager implements CallbackListener { /** * Creates The Socket */ - private function createSocket() { + private function createListeningSocket() { $socketEnabled = $this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_SOCKET_ENABLED); if ($socketEnabled) { @@ -183,7 +228,7 @@ class CommunicationManager implements CallbackListener { $serverLogin = $this->maniaControl->getServer()->login; $socketPort = $this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_SOCKET_PORT . $serverLogin); - $password = $this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_SOCKET_PASSWORD . $serverLogin); + $password = $this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_SOCKET_PASSWORD); try { $this->loop = Factory::create(); @@ -205,15 +250,16 @@ class CommunicationManager implements CallbackListener { $buffer = substr($buffer, strlen((string) $len) + 1 /* newline */ + $len); // clip buffer // Decode Message - $data = openssl_decrypt($msg, 'aes-192-cbc', $password, OPENSSL_RAW_DATA, 'kZ2Kt0CzKUjN2MJX'); + $data = openssl_decrypt($msg, self::ENCRYPTION_METHOD, $password, OPENSSL_RAW_DATA, self::ENCRYPTION_IV); $data = json_decode($data); + if ($data == null) { $data = array("error" => true, "data" => "Data is not provided as an valid AES-196-encrypted encrypted JSON"); } else if (!property_exists($data, "method") || !property_exists($data, "data")) { $data = array("error" => true, "data" => "Invalid Message"); } else { - $answer = $this->triggerCommuncationCallback($data->method, $data->data); + $answer = $this->triggerCommuncationCallback($data->method, $data->error, $data->data); //Prepare Response if (!$answer) { $data = array("error" => true, "data" => "No listener or response on the given Message"); @@ -224,7 +270,7 @@ class CommunicationManager implements CallbackListener { //Encode, Encrypt and Send Response $data = json_encode($data); - $data = openssl_encrypt($data, 'aes-192-cbc', $password, OPENSSL_RAW_DATA, 'kZ2Kt0CzKUjN2MJX'); + $data = openssl_encrypt($data, self::ENCRYPTION_METHOD, $password, OPENSSL_RAW_DATA, self::ENCRYPTION_IV); $connection->write(strlen($data) . "\n" . $data); // next msg @@ -250,5 +296,9 @@ class CommunicationManager implements CallbackListener { if ($this->loop) { $this->loop->tick(); } + + foreach ($this->communications as $communication) { + $communication->tick(); + } } } \ No newline at end of file diff --git a/core/Communication/CommunicationMethods.php b/core/Communication/CommunicationMethods.php index 98bde49e..b34256ef 100644 --- a/core/Communication/CommunicationMethods.php +++ b/core/Communication/CommunicationMethods.php @@ -2,7 +2,13 @@ namespace ManiaControl\Communication; - +/** + * Communication Methods Interface + * + * @author ManiaControl Team + * @copyright 2014-2015 ManiaControl Team + * @license http://www.gnu.org/licenses/ GNU General Public License, Version 3 + */ interface CommunicationMethods { /** Returns the last 200 lines of the chat (inclusive player logins and nicknames) */ const GET_SERVER_CHAT = "Chat.GetServerChat"; diff --git a/core/Communication/usage.txt b/core/Communication/usage_documentation.txt similarity index 50% rename from core/Communication/usage.txt rename to core/Communication/usage_documentation.txt index 86ca84a4..e4649fe5 100644 --- a/core/Communication/usage.txt +++ b/core/Communication/usage_documentation.txt @@ -1,7 +1,21 @@ -Ingame you can activate the Communication Manager and set Password and Port of it. +The CommuncationListening of the Communcation Manager can be enabled in the ingame Settings. -Sample Web Implementation -maniaControl->getCommunicationManager()->createCommunication(IP/Domain, PORT, 'YOUR_PASSWORD'); + $communication->call(function($error, $data){ + var_dump($data); + }, CommunicationMethods::GET_SERVER_CHAT); +##php code end + +Sample Web Implementation (to call ManiaControl from a website) +##php code begin $errno = null; $errstr = null; $socket = fsockopen("xx.xxx.xx.xx", xxxxx, $errno, $errstr, 2); @@ -31,4 +45,4 @@ Sample Web Implementation //Close the Socket fclose($socket); -?> \ No newline at end of file +##php code end \ No newline at end of file