306 lines
9.7 KiB
PHP
306 lines
9.7 KiB
PHP
<?php
|
|
namespace ManiaControl\Communication;
|
|
|
|
use ManiaControl\Callbacks\CallbackListener;
|
|
use ManiaControl\Callbacks\Callbacks;
|
|
use ManiaControl\Callbacks\Listening;
|
|
use ManiaControl\Logger;
|
|
use ManiaControl\ManiaControl;
|
|
use ManiaControl\Settings\Setting;
|
|
use ManiaControl\Settings\SettingManager;
|
|
use React\EventLoop\Factory;
|
|
use React\EventLoop\LoopInterface;
|
|
use React\Socket\Connection;
|
|
use React\Socket\ConnectionException;
|
|
use React\Socket\Server;
|
|
|
|
/**
|
|
* Class for managing Socket Callbacks
|
|
*
|
|
* @author ManiaControl Team <mail@maniacontrol.com>
|
|
* @copyright 2014-2015 ManiaControl Team
|
|
* @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;
|
|
|
|
/** @var LoopInterface $loop */
|
|
private $loop = null;
|
|
|
|
/** @var Listening[] $communicationListenings */
|
|
private $communicationListenings = array();
|
|
|
|
/** @var Server $socket */
|
|
private $socket = null;
|
|
/** @var Communication[] $communcations */
|
|
private $communications = array();
|
|
|
|
/**
|
|
* Create a new Communication Handler Instance
|
|
*
|
|
* @param ManiaControl $maniaControl
|
|
*/
|
|
public function __construct(ManiaControl $maniaControl) {
|
|
$this->maniaControl = $maniaControl;
|
|
|
|
$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
|
|
*
|
|
* @param string $callbackName
|
|
* @param CommunicationListener $listener
|
|
* @param string $method
|
|
* @return bool
|
|
*/
|
|
public function registerCommunicationListener($echoName, CommunicationListener $listener, $method) {
|
|
if (!Listening::checkValidCallback($listener, $method)) {
|
|
$listenerClass = get_class($listener);
|
|
trigger_error("Given Listener '{$listenerClass}' can't handle Callback '{$echoName}': No callable Method '{$method}'!");
|
|
return false;
|
|
}
|
|
|
|
if (!array_key_exists($echoName, $this->communicationListenings)) {
|
|
$this->communicationListenings[$echoName] = new Listening($listener, $method);
|
|
} else {
|
|
//TODO say which is already listening and other stuff
|
|
trigger_error("Only one Listener can listen on a specific Communication Message");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Trigger a specific Callback
|
|
*
|
|
* @param mixed $callback
|
|
*/
|
|
public function triggerCommuncationCallback($callbackName) {
|
|
if (!array_key_exists($callbackName, $this->communicationListenings)) {
|
|
return null;
|
|
}
|
|
|
|
$params = func_get_args();
|
|
$params = array_slice($params, 1, null, true);
|
|
|
|
$listening = $this->communicationListenings[$callbackName];
|
|
/** @var Listening $listening */
|
|
return $listening->triggerCallbackWithParams($params);
|
|
}
|
|
|
|
|
|
/**
|
|
* Unregister a Communication Listener
|
|
*
|
|
* @param CommunicationListener $listener
|
|
* @return bool
|
|
*/
|
|
public function unregisterCommunicationListener(CommunicationListener $listener) {
|
|
return $this->removeCommunicationListener($this->communicationListenings, $listener);
|
|
}
|
|
|
|
|
|
/**
|
|
* Remove the Communication Listener from the given Listeners Array
|
|
*
|
|
* @param Listening[] $listeningsArray
|
|
* @param CommunicationListener $listener
|
|
* @return bool
|
|
*/
|
|
private function removeCommunicationListener(array &$listeningsArray, CommunicationListener $listener) {
|
|
$removed = false;
|
|
foreach ($listeningsArray as &$listening) {
|
|
if ($listening->listener === $listener) {
|
|
unset($listening);
|
|
$removed = true;
|
|
}
|
|
}
|
|
return $removed;
|
|
}
|
|
|
|
/**
|
|
* Inits the Communication Manager after ManiaControl Startup
|
|
*/
|
|
public function initCommunicationManager() {
|
|
$this->maniaControl->getSettingManager()->initSetting($this, self::SETTING_SOCKET_ENABLED, false);
|
|
$this->maniaControl->getSettingManager()->initSetting($this, self::SETTING_SOCKET_PASSWORD, "");
|
|
|
|
$servers = $this->maniaControl->getServer()->getAllServers();
|
|
foreach ($servers as $server) {
|
|
$this->maniaControl->getSettingManager()->initSetting($this, self::SETTING_SOCKET_PORT . $server->login, 31500 + $server->index);
|
|
}
|
|
|
|
|
|
$this->createListeningSocket();
|
|
}
|
|
|
|
/**
|
|
* Update Setting
|
|
*
|
|
* @param Setting $setting
|
|
*/
|
|
public function updateSettings(Setting $setting) {
|
|
if (!$setting->belongsToClass($this)) {
|
|
return;
|
|
}
|
|
|
|
$socketEnabled = $this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_SOCKET_ENABLED);
|
|
|
|
if ($socketEnabled && !$this->socket) {
|
|
$this->createListeningSocket();
|
|
}
|
|
|
|
if (!$socketEnabled) {
|
|
$this->socket = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates The Socket
|
|
*/
|
|
private function createListeningSocket() {
|
|
$socketEnabled = $this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_SOCKET_ENABLED);
|
|
if ($socketEnabled) {
|
|
|
|
Logger::log("[CommunicationManager] Trying to create Socket");
|
|
|
|
// Check for MySQLi
|
|
$message = '[CommunicationManager] Checking for installed openssl ... ';
|
|
if (!extension_loaded('openssl')) {
|
|
Logger::log($message . 'NOT FOUND!');
|
|
Logger::log(" -- You don't have openssl installed! Check: http://www.php.net/manual/en/openssl.installation.php");
|
|
return;
|
|
} else {
|
|
Logger::log($message . 'FOUND!');
|
|
}
|
|
|
|
$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);
|
|
|
|
try {
|
|
$this->loop = Factory::create();
|
|
$this->socket = new Server($this->loop);
|
|
|
|
$this->socket->on('error', function ($e) {
|
|
Logger::log("[CommunicationManager] Socket Error" . $e);
|
|
});
|
|
|
|
$this->socket->on('connection', function (Connection $connection) use ($password) {
|
|
$buffer = '';
|
|
$connection->on('data', function ($data) use (&$buffer, &$connection, $password) {
|
|
$buffer .= $data;
|
|
$arr = explode("\n", $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
|
|
$buffer = substr($buffer, strlen((string) $len) + 1 /* newline */ + $len); // clip buffer
|
|
|
|
// Decode Message
|
|
$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);
|
|
//Prepare Response
|
|
if (!$answer) {
|
|
$data = array("error" => true, "data" => "No listener or response on the given Message");
|
|
} else if (!array_key_exists("error", $answer) || !array_key_exists("data", $answer)) {
|
|
$data = array("error" => true, "data" => "Invalid Response on the Message");
|
|
} else {
|
|
$data = array("error" => $answer["error"], "data" => $answer["data"]);
|
|
}
|
|
}
|
|
|
|
//Encode, Encrypt and Send Response
|
|
$data = json_encode($data);
|
|
$data = openssl_encrypt($data, self::ENCRYPTION_METHOD, $password, OPENSSL_RAW_DATA, self::ENCRYPTION_IV);
|
|
$connection->write(strlen($data) . "\n" . $data);
|
|
|
|
// next msg
|
|
$arr = explode("\n", $buffer, 2);
|
|
}
|
|
});
|
|
});
|
|
//TODO check if port is closed
|
|
$this->socket->listen($socketPort, $this->maniaControl->getServer()->ip);
|
|
|
|
Logger::log("[CommunicationManager] Socket " . getHostByName(getHostName()) . ":" . $this->socket->getPort() . " Successfully created!");
|
|
} catch (ConnectionException $e) {
|
|
Logger::log("[CommunicationManager] Exception: " . $e->getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Processes Data on every ManiaControl Tick, don't call this Method
|
|
*/
|
|
public function tick() {
|
|
if ($this->loop) {
|
|
$this->loop->tick();
|
|
}
|
|
|
|
foreach ($this->communications as $communication) {
|
|
$communication->tick();
|
|
}
|
|
}
|
|
} |