3 * A database wrapper for peer state lookups
5 * @author Roland Haeder <webmaster@shipsimu.org>
7 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2015 Hub Developer Team
8 * @license GNU GPL 3.0 or any newer version
9 * @link http://www.shipsimu.org
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <http://www.gnu.org/licenses/>.
24 class PeerStateLookupDatabaseWrapper extends BaseDatabaseWrapper implements LookupablePeerState {
25 // Exception constants
26 const EXCEPTION_PEER_ALREADY_REGISTERED = 0x300;
28 // Constants for database table names
29 const DB_TABLE_PEER_LOOKUP = 'peer_states';
31 // Constants for database column names
32 const DB_COLUMN_PEER_IP = 'peer_ip';
33 const DB_COLUMN_PEER_PORT = 'peer_port';
34 const DB_COLUMN_PEER_SESSION_ID = 'peer_session_id';
35 const DB_COLUMN_PEER_STATE = 'peer_state';
36 const DB_COLUMN_SOCKET_ERROR_CODE = 'socket_error_code';
37 const DB_COLUMN_SOCKET_ERROR_MSG = 'socket_error_msg';
40 * Protected constructor
44 protected function __construct () {
45 // Call parent constructor
46 parent::__construct(__CLASS__);
50 * Creates an instance of this database wrapper by a provided user class
52 * @return $wrapperInstance An instance of the created wrapper class
54 public static final function createPeerStateLookupDatabaseWrapper () {
56 $wrapperInstance = new PeerStateLookupDatabaseWrapper();
58 // Set (primary!) table name
59 $wrapperInstance->setTableName(self::DB_TABLE_PEER_LOOKUP);
61 // Return the instance
62 return $wrapperInstance;
66 * "Getter" for a LocalSearchCriteria from given package data's sender
68 * @param $packageData Valid raw package data array
69 * @return $searchInstance An instance of a LocalSearchCriteria class
71 private function getSenderSearchInstanceFromPackageData (array $packageData) {
73 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE-WRAPPER: Searching for sender ' . $packageData[NetworkPackage::PACKAGE_DATA_SENDER]);
74 $searchInstance = ObjectFactory::createObjectByConfiguredName('search_criteria_class');
76 // Add 'sender' as the peer's IP address
77 $searchInstance->addCriteria(self::DB_COLUMN_PEER_SESSION_ID, $packageData[NetworkPackage::PACKAGE_DATA_SENDER]);
78 $searchInstance->setLimit(1);
80 // Return the instance
81 return $searchInstance;
85 * Checks whether given 'sender' is a new peer
87 * @param $packageData Raw package data
88 * @param $dataSetInstance An optional instance of a StoreableCriteria class
89 * @return $isNewPeer Whether 'sender' is a new peer to this peer
91 public function isSenderNewPeer (array $packageData, StoreableCriteria $dataSetInstance = NULL) {
93 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE-WRAPPER: ' . __FUNCTION__ . ' called with packageData()=' . count($packageData) . ' - CALLED!');
95 // Is the package valid?
96 if (!isset($packageData[NetworkPackage::PACKAGE_DATA_SENDER])) {
97 // Invalid package found, please report this
98 $this->debugBackTrace('[' . __METHOD__ . ':' . __LINE__ . ']: packageData=' . print_r($packageData, TRUE));
101 // Get a search criteria instance from package data
102 $searchInstance = $this->getSenderSearchInstanceFromPackageData($packageData);
104 // Is the dataset instance set?
105 if ($dataSetInstance instanceof StoreableCriteria) {
106 // Then remember the search instance in it
107 $dataSetInstance->setSearchInstance($searchInstance);
111 $entries = $this->doSelectCountByCriteria($searchInstance, array(self::DB_COLUMN_PEER_SESSION_ID => TRUE));
114 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE-WRAPPER: entries=' . $entries);
115 $isNewPeer = ($entries != 1);
118 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE-WRAPPER: isNewPeer=' . intval($isNewPeer) . ' - EXIT!');
123 * Registers a new peer with given package data. We use the session id from it.
125 * @param $packageData Raw package data
126 * @param $socketResource A valid socket resource
128 * @throws PeerAlreadyRegisteredException If a peer is already registered
130 public function registerPeerByPackageData (array $packageData, $socketResource) {
131 // Make sure only new peers can be registered with package data
132 if (!$this->isSenderNewPeer($packageData)) {
133 // Throw an exception because this should normally not happen
134 throw new PeerAlreadyRegisteredException(array($this, $packageData), self::EXCEPTION_PEER_ALREADY_REGISTERED);
137 // Generate a dataset instance
138 $dataSetInstance = ObjectFactory::createObjectByConfiguredName('dataset_criteria_class', array(self::DB_TABLE_PEER_LOOKUP));
140 // Session ids must be unique
141 $dataSetInstance->setUniqueKey(self::DB_COLUMN_PEER_SESSION_ID);
144 $dataSetInstance->addCriteria(self::DB_COLUMN_PEER_SESSION_ID, $packageData[NetworkPackage::PACKAGE_DATA_SENDER]);
147 if (!@socket_getpeername($socketResource, $peerName, $peerPort)) {
149 $lastError = socket_last_error($socketResource);
151 // ... and cleartext message from it and put both into criteria
152 $dataSetInstance->addCriteria(self::DB_COLUMN_SOCKET_ERROR_CODE, $lastError);
153 $dataSetInstance->addCriteria(self::DB_COLUMN_SOCKET_ERROR_MSG , socket_strerror($lastError));
156 // Add ip address and port
157 $dataSetInstance->addCriteria(self::DB_COLUMN_PEER_IP , $peerName);
158 $dataSetInstance->addCriteria(self::DB_COLUMN_PEER_PORT, $peerPort);
160 // "Insert" the data set
161 $this->queryInsertDataSet($dataSetInstance, array(self::DB_COLUMN_PEER_SESSION_ID => TRUE));
164 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE-WRAPPER: Peer ' . $packageData[NetworkPackage::PACKAGE_DATA_SENDER] . ' has been registered.');
168 * Registers the given peer state and raw package data
170 * @param $stateInstance A PeerStateable class instance
171 * @param $packageData Valid package data array
173 * @throws PeerAlreadyRegisteredException If a peer is already registered
174 * @todo Unfinished area
176 public function registerPeerState (PeerStateable $stateInstance, array $packageData) {
177 // Generate a dataset instance
178 $dataSetInstance = ObjectFactory::createObjectByConfiguredName('dataset_criteria_class', array(self::DB_TABLE_PEER_LOOKUP));
180 // Session ids must be unique
181 $dataSetInstance->setUniqueKey(self::DB_COLUMN_PEER_SESSION_ID);
184 $dataSetInstance->addCriteria(self::DB_COLUMN_PEER_SESSION_ID, $packageData[NetworkPackage::PACKAGE_DATA_SENDER]);
185 $dataSetInstance->addCriteria(self::DB_COLUMN_PEER_STATE , $stateInstance->getStateName());
187 // Try to resolve sender's session id
188 $senderData = explode(':', HubTools::resolveSessionId($packageData[NetworkPackage::PACKAGE_DATA_SENDER]));
190 // Just make sure that 'invalid:invalid' is not being processed
191 assert(($senderData[0] != 'invalid') && ($senderData[1] != 'invalid') && ($senderData[2] != 'invalid'));
193 // Add ip address and port
194 $dataSetInstance->addCriteria(self::DB_COLUMN_PEER_IP , $senderData[0]);
195 $dataSetInstance->addCriteria(self::DB_COLUMN_PEER_PORT, $senderData[1]);
197 // Is this a new peer?
198 if ($this->isSenderNewPeer($packageData, $dataSetInstance)) {
199 // "Insert" the data set
200 $this->queryInsertDataSet($dataSetInstance, array(self::DB_COLUMN_PEER_SESSION_ID => TRUE));
202 // Update the data set
203 $this->queryUpdateDataSet($dataSetInstance, array(self::DB_COLUMN_PEER_SESSION_ID => TRUE));
207 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE-WRAPPER: Peer ' . $packageData[NetworkPackage::PACKAGE_DATA_SENDER] . ' has been registered/updated with state ' . $stateInstance->getStateName());
211 * Purges old entries of given socket resource. We use the IP address from that resource.
213 * @param $socketResource A valid socket resource
215 * @throws InvalidSocketException If the socket resource was invalid
216 * @todo Unfinished area
218 public function purgeOldEntriesBySocketResource ($socketResource) {
220 if (!@socket_getpeername($socketResource, $peerName, $peerPort)) {
222 $lastError = socket_last_error($socketResource);
225 throw new InvalidSocketException(array($this, $socketResource, $lastError, socket_strerror($lastError)), BaseListener::EXCEPTION_INVALID_SOCKET);
229 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE-WRAPPER: peerName=' . $peerName . ',peerPort=' . $peerPort . ' - UNFINISHED!');
233 * Checks whether a given peer state (in helper instance) is same as stored
234 * in database compared with the one from $helperInstance.
236 * @param $helperInstance An instance of a ConnectionHelper class
237 * @param $packageData Valid package data array
238 * @return $isSamePeerState Whether the peer's state is the same
240 public function isSamePeerState (ConnectionHelper $helperInstance, array $packageData) {
242 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE-WRAPPER: State ' . $helperInstance->getPrintableState() . ' needs to be checked it has changed ...');
244 // Now get the search instance from given package data
245 $searchInstance = $this->getSenderSearchInstanceFromPackageData($packageData);
247 // With this search instance query the database for the peer and get a result instance
248 $resultInstance = $this->doSelectByCriteria($searchInstance);
250 // Do we have an entry? This should always the case
251 assert($resultInstance->next());
253 // Yes, so get the current (=first) entry from it
254 $rowData = $resultInstance->current();
257 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE-WRAPPER: rowData[' . gettype($rowData) . ']=' . print_r($rowData, TRUE));
259 // Assert on important elements
260 assert(isset($rowData[self::DB_COLUMN_PEER_STATE]));
262 // Now just compare it with given state from helper instance
263 $isSamePeerState = ($rowData[self::DB_COLUMN_PEER_STATE] == $helperInstance->getPrintableState());
266 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE-WRAPPER: state in database: ' . $rowData[self::DB_COLUMN_PEER_STATE] . ', new state: ' . $helperInstance->getPrintableState() . ',isSamePeerState=' . intval($isSamePeerState));
269 return $isSamePeerState;