* @version 0.0.0 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2015 Hub Developer Team * @license GNU GPL 3.0 or any newer version * @link http://www.shipsimu.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ class PeerStateLookupDatabaseWrapper extends BaseDatabaseWrapper implements LookupablePeerState { // Exception constants const EXCEPTION_PEER_ALREADY_REGISTERED = 0x300; // Constants for database table names const DB_TABLE_PEER_LOOKUP = 'peer_states'; // Constants for database column names const DB_COLUMN_PEER_IP = 'peer_ip'; const DB_COLUMN_PEER_PORT = 'peer_port'; const DB_COLUMN_PEER_SESSION_ID = 'peer_session_id'; const DB_COLUMN_PEER_STATE = 'peer_state'; const DB_COLUMN_SOCKET_ERROR_CODE = 'socket_error_code'; const DB_COLUMN_SOCKET_ERROR_MSG = 'socket_error_msg'; /** * Protected constructor * * @return void */ protected function __construct () { // Call parent constructor parent::__construct(__CLASS__); } /** * Creates an instance of this database wrapper by a provided user class * * @return $wrapperInstance An instance of the created wrapper class */ public static final function createPeerStateLookupDatabaseWrapper () { // Get a new instance $wrapperInstance = new PeerStateLookupDatabaseWrapper(); // Set (primary!) table name $wrapperInstance->setTableName(self::DB_TABLE_PEER_LOOKUP); // Return the instance return $wrapperInstance; } /** * "Getter" for a LocalSearchCriteria from given package data's sender * * @param $packageData Valid raw package data array * @return $searchInstance An instance of a LocalSearchCriteria class */ private function getSenderSearchInstanceFromPackageData (array $packageData) { // Get the instance //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE-WRAPPER: Searching for sender ' . $packageData[NetworkPackage::PACKAGE_DATA_SENDER]); $searchInstance = ObjectFactory::createObjectByConfiguredName('search_criteria_class'); // Add 'sender' as the peer's IP address $searchInstance->addCriteria(self::DB_COLUMN_PEER_SESSION_ID, $packageData[NetworkPackage::PACKAGE_DATA_SENDER]); $searchInstance->setLimit(1); // Return the instance return $searchInstance; } /** * Checks whether given 'sender' is a new peer * * @param $packageData Raw package data * @param $dataSetInstance An optional instance of a StoreableCriteria class * @return $isNewPeer Whether 'sender' is a new peer to this peer */ public function isSenderNewPeer (array $packageData, StoreableCriteria $dataSetInstance = NULL) { // Debug message //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE-WRAPPER: ' . __FUNCTION__ . ' called with packageData()=' . count($packageData) . ' - CALLED!'); // Is the package valid? if (!isset($packageData[NetworkPackage::PACKAGE_DATA_SENDER])) { // Invalid package found, please report this $this->debugBackTrace('[' . __METHOD__ . ':' . __LINE__ . ']: packageData=' . print_r($packageData, TRUE)); } // END - if // Get a search criteria instance from package data $searchInstance = $this->getSenderSearchInstanceFromPackageData($packageData); // Is the dataset instance set? if ($dataSetInstance instanceof StoreableCriteria) { // Then remember the search instance in it $dataSetInstance->setSearchInstance($searchInstance); } // END - if // Count the query $entries = $this->doSelectCountByCriteria($searchInstance, array(self::DB_COLUMN_PEER_SESSION_ID => TRUE)); // Is it there? //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE-WRAPPER: entries=' . $entries); $isNewPeer = ($entries != 1); // Return the result //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE-WRAPPER: isNewPeer=' . intval($isNewPeer) . ' - EXIT!'); return $isNewPeer; } /** * Registers a new peer with given package data. We use the session id from it. * * @param $packageData Raw package data * @param $socketResource A valid socket resource * @return void * @throws PeerAlreadyRegisteredException If a peer is already registered */ public function registerPeerByPackageData (array $packageData, $socketResource) { // Make sure only new peers can be registered with package data if (!$this->isSenderNewPeer($packageData)) { // Throw an exception because this should normally not happen throw new PeerAlreadyRegisteredException(array($this, $packageData), self::EXCEPTION_PEER_ALREADY_REGISTERED); } // END - if // Generate a dataset instance $dataSetInstance = ObjectFactory::createObjectByConfiguredName('dataset_criteria_class', array(self::DB_TABLE_PEER_LOOKUP)); // Session ids must be unique $dataSetInstance->setUniqueKey(self::DB_COLUMN_PEER_SESSION_ID); // Add session id $dataSetInstance->addCriteria(self::DB_COLUMN_PEER_SESSION_ID, $packageData[NetworkPackage::PACKAGE_DATA_SENDER]); // Get peer name if (!@socket_getpeername($socketResource, $peerName, $peerPort)) { // Get last error $lastError = socket_last_error($socketResource); // ... and cleartext message from it and put both into criteria $dataSetInstance->addCriteria(self::DB_COLUMN_SOCKET_ERROR_CODE, $lastError); $dataSetInstance->addCriteria(self::DB_COLUMN_SOCKET_ERROR_MSG , socket_strerror($lastError)); } // END - if // Add ip address and port $dataSetInstance->addCriteria(self::DB_COLUMN_PEER_IP , $peerName); $dataSetInstance->addCriteria(self::DB_COLUMN_PEER_PORT, $peerPort); // "Insert" the data set $this->queryInsertDataSet($dataSetInstance, array(self::DB_COLUMN_PEER_SESSION_ID => TRUE)); // Debug message //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE-WRAPPER: Peer ' . $packageData[NetworkPackage::PACKAGE_DATA_SENDER] . ' has been registered.'); } /** * Registers the given peer state and raw package data * * @param $stateInstance A PeerStateable class instance * @param $packageData Valid package data array * @return void * @throws PeerAlreadyRegisteredException If a peer is already registered * @todo Unfinished area */ public function registerPeerState (PeerStateable $stateInstance, array $packageData) { // Generate a dataset instance $dataSetInstance = ObjectFactory::createObjectByConfiguredName('dataset_criteria_class', array(self::DB_TABLE_PEER_LOOKUP)); // Session ids must be unique $dataSetInstance->setUniqueKey(self::DB_COLUMN_PEER_SESSION_ID); // Add session id $dataSetInstance->addCriteria(self::DB_COLUMN_PEER_SESSION_ID, $packageData[NetworkPackage::PACKAGE_DATA_SENDER]); $dataSetInstance->addCriteria(self::DB_COLUMN_PEER_STATE , $stateInstance->getStateName()); // Try to resolve sender's session id $senderData = explode(':', HubTools::resolveSessionId($packageData[NetworkPackage::PACKAGE_DATA_SENDER])); // Just make sure that 'invalid:invalid' is not being processed assert(($senderData[0] != 'invalid') && ($senderData[1] != 'invalid') && ($senderData[2] != 'invalid')); // Add ip address and port $dataSetInstance->addCriteria(self::DB_COLUMN_PEER_IP , $senderData[0]); $dataSetInstance->addCriteria(self::DB_COLUMN_PEER_PORT, $senderData[1]); // Is this a new peer? if ($this->isSenderNewPeer($packageData, $dataSetInstance)) { // "Insert" the data set $this->queryInsertDataSet($dataSetInstance, array(self::DB_COLUMN_PEER_SESSION_ID => TRUE)); } else { // Update the data set $this->queryUpdateDataSet($dataSetInstance, array(self::DB_COLUMN_PEER_SESSION_ID => TRUE)); } // Debug message //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE-WRAPPER: Peer ' . $packageData[NetworkPackage::PACKAGE_DATA_SENDER] . ' has been registered/updated with state ' . $stateInstance->getStateName()); } /** * Purges old entries of given socket resource. We use the IP address from that resource. * * @param $socketResource A valid socket resource * @return void * @throws InvalidSocketException If the socket resource was invalid * @todo Unfinished area */ public function purgeOldEntriesBySocketResource ($socketResource) { // Get peer name if (!@socket_getpeername($socketResource, $peerName, $peerPort)) { // Get last error $lastError = socket_last_error($socketResource); // Doesn't work! throw new InvalidSocketException(array($this, $socketResource, $lastError, socket_strerror($lastError)), BaseListener::EXCEPTION_INVALID_SOCKET); } // END - if // Debug message //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE-WRAPPER: peerName=' . $peerName . ',peerPort=' . $peerPort . ' - UNFINISHED!'); } /** * Checks whether a given peer state (in helper instance) is same as stored * in database compared with the one from $helperInstance. * * @param $helperInstance An instance of a ConnectionHelper class * @param $packageData Valid package data array * @return $isSamePeerState Whether the peer's state is the same */ public function isSamePeerState (ConnectionHelper $helperInstance, array $packageData) { // Debug message //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE-WRAPPER: State ' . $helperInstance->getPrintableState() . ' needs to be checked it has changed ...'); // Now get the search instance from given package data $searchInstance = $this->getSenderSearchInstanceFromPackageData($packageData); // With this search instance query the database for the peer and get a result instance $resultInstance = $this->doSelectByCriteria($searchInstance); // Do we have an entry? This should always the case assert($resultInstance->next()); // Yes, so get the current (=first) entry from it $rowData = $resultInstance->current(); // Debug message //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE-WRAPPER: rowData[' . gettype($rowData) . ']=' . print_r($rowData, TRUE)); // Assert on important elements assert(isset($rowData[self::DB_COLUMN_PEER_STATE])); // Now just compare it with given state from helper instance $isSamePeerState = ($rowData[self::DB_COLUMN_PEER_STATE] == $helperInstance->getPrintableState()); // Debug message //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE-WRAPPER: state in database: ' . $rowData[self::DB_COLUMN_PEER_STATE] . ', new state: ' . $helperInstance->getPrintableState() . ',isSamePeerState=' . intval($isSamePeerState)); // Return it return $isSamePeerState; } } // [EOF] ?>