ba7e7393ba27c70425a87e4bb17478d49ef48585
[hub.git] / application / hub / classes / helper / connection / ipv4 / class_BaseIpV4ConnectionHelper.php
1 <?php
2 /**
3  * A ??? connection helper class
4  *
5  * @author              Roland Haeder <webmaster@ship-simu.org>
6  * @version             0.0.0
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.ship-simu.org
10  * @todo                Find an interface for hub helper
11  *
12  * This program is free software: you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation, either version 3 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program. If not, see <http://www.gnu.org/licenses/>.
24  */
25 class BaseIpV4ConnectionHelper extends BaseConnectionHelper {
26         /**
27          * Port number used
28          */
29         private $connectionPort = 0;
30
31         /**
32          * Protected constructor
33          *
34          * @param       $className      Name of implementing class
35          * @return      void
36          */
37         protected function __construct ($className) {
38                 // Call parent constructor
39                 parent::__construct($className);
40         }
41
42         /**
43          * Getter for port number to satify HandleableProtocol
44          *
45          * @return      $connectionPort The port number
46          */
47         public final function getConnectionPort () {
48                 return $this->connectionPort;
49         }
50
51         /**
52          * Setter for port number to satify HandleableProtocol
53          *
54          * @param       $connectionPort The port number
55          * @return      void
56          */
57         protected final function setConnectionPort ($connectionPort) {
58                 $this->connectionPort = $connectionPort;
59         }
60
61         /**
62          * Initializes the current connection
63          *
64          * @return      void
65          * @throws      SocketOptionException   If setting any socket option fails
66          */
67         protected function initConnection () {
68                 // Get socket resource
69                 $socketResource = $this->getSocketResource();
70
71                 // Set the option to reuse the port
72                 if (!socket_set_option($socketResource, SOL_SOCKET, SO_REUSEADDR, 1)) {
73                         // Handle this socket error with a faked recipientData array
74                         $this->handleSocketError(__METHOD__, __LINE__, $socketResource, array('0.0.0.0', '0'));
75
76                         // And throw again
77                         // @TODO Move this to the socket error handler
78                         throw new SocketOptionException(array($this, $socketResource, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
79                 } // END - if
80
81                 /*
82                  * Set socket to non-blocking mode before trying to establish a link to
83                  * it. This is now the default behaviour for all connection helpers who
84                  * call initConnection(); .
85                  */
86                 if (!socket_set_nonblock($socketResource)) {
87                         // Handle this socket error with a faked recipientData array
88                         $helperInstance->handleSocketError(__METHOD__, __LINE__, $socketResource, array('0.0.0.0', '0'));
89
90                         // And throw again
91                         throw new SocketOptionException(array($helperInstance, $socketResource, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
92                 } // END - if
93
94                 // Last step: mark connection as initialized
95                 $this->setIsInitialized(TRUE);
96         }
97
98         /**
99          * Attempts to connect to a peer by given IP number and port from a valid
100          * unlData array with currently configured timeout.
101          *
102          * @param       $unlData                Valid UNL data array
103          * @return      $isConnected    Whether the connection went fine
104          * @see         Please see http://de.php.net/manual/en/function.socket-connect.php#84465 for original code
105          * @todo        Rewrite the while() loop to a iterator to not let the software stay very long here
106          */
107         protected function connectToPeerByUnlData (array $unlData) {
108                 // Only call this if the connection is initialized by initConnection()
109                 assert($this->isInitialized());
110
111                 // Is unlData complete?
112                 assert(isset($unlData[UniversalNodeLocator::UNL_PART_PROTOCOL]));
113                 assert(isset($unlData[UniversalNodeLocator::UNL_PART_ADDRESS]));
114                 assert(isset($unlData[UniversalNodeLocator::UNL_PART_PORT]));
115
116                 // "Cache" socket resource and timeout config
117                 $socketResource = $this->getSocketResource();
118                 $timeout = $this->getConfigInstance()->getConfigEntry('socket_timeout_seconds');
119
120                 // Debug output
121                 self::createDebugInstance(__CLASS__)->debugOutput('CONNECTION-HELPER[' . __METHOD__ . ':' . __LINE__ . ']: Trying to connect to ' . $unlData[UniversalNodeLocator::UNL_PART_ADDRESS] . ':' . $unlData[UniversalNodeLocator::UNL_PART_PORT] . ' with socketResource[' . gettype($socketResource) . ']=' . $socketResource . ' ...');
122
123                 // Get current time
124                 $time = time();
125
126                 // Try to connect until it is connected
127                 while ($isConnected = !@socket_connect($socketResource, $unlData[UniversalNodeLocator::UNL_PART_ADDRESS], $unlData[UniversalNodeLocator::UNL_PART_PORT])) {
128                         // Get last socket error
129                         $socketError = socket_last_error($socketResource);
130
131                         // Skip any errors which may happen on non-blocking connections
132                         if (($socketError == SOCKET_EINPROGRESS) || ($socketError == SOCKET_EALREADY)) {
133                                 // Now, is that attempt within parameters?
134                                 if ((time() - $time) >= $timeout) {
135                                         // Debug message
136                                         /* DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CONNECTION-HELPER[' . __METHOD__ . ':' . __LINE__ . ']: timeout=' . $timeout .' reached, connection attempt failed.');
137
138                                         // Didn't work within timeout
139                                         $isConnected = FALSE;
140                                         break;
141                                 } // END - if
142
143                                 // Sleep about one second
144                                 $this->idle(1000);
145                         } elseif ($socketError != 0) {
146                                 // Debug message
147                                 /* DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CONNECTION-HELPER[' . __METHOD__ . ':' . __LINE__ . ']: socketError=' . $socketError . ' detected.');
148
149                                 // Stop on everything else pronto
150                                 $isConnected = FALSE;
151                                 break;
152                         }
153                 } // END - while
154
155                 // Log error code
156                 /* DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CONNECTION-HELPER[' . __METHOD__ . ':' . __LINE__ . ']: socketError=' . $socketError . ',isConnected=' . intval($isConnected) . ' after while() loop.');
157
158                 // Is the peer connected?
159                 if ($isConnected === TRUE) {
160                         // Connection is fully established here, so change the state.
161                         PeerStateFactory::createPeerStateInstanceByName('connected', $this);
162                 } else {
163                         /*
164                          * There was a problem connecting to the peer (this state is a meta
165                          * state until the error handler has found the real cause).
166                          */
167                         PeerStateFactory::createPeerStateInstanceByName('problem', $this);
168                 }
169
170                 // Return status
171                 return $isConnected;
172         }
173
174         /**
175          * Marks this connection as shutted down
176          *
177          * @return      void
178          */
179         protected final function markConnectionShuttedDown () {
180                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CONNECTION-HELPER[' . __METHOD__ . ':' . __LINE__ . ']: ' . $this->__toString() . ' has been marked as shutted down');
181                 $this->shuttedDown = TRUE;
182
183                 // And remove the (now invalid) socket
184                 $this->setSocketResource(FALSE);
185         }
186
187         // ************************************************************************
188         //                 Socket error handler call-back methods
189         // ************************************************************************
190
191         /**
192          * Handles socket error 'connection timed out', but does not clear it for
193          * later debugging purposes.
194          *
195          * @param       $socketResource         A valid socket resource
196          * @param       $unlData                        A valid UNL data array
197          * @return      void
198          * @throws      SocketConnectionException       The connection attempts fails with a time-out
199          */
200         protected function socketErrorConnectionTimedOutHandler ($socketResource, array $unlData) {
201                 // Get socket error code for verification
202                 $socketError = socket_last_error($socketResource);
203
204                 // Get error message
205                 $errorMessage = socket_strerror($socketError);
206
207                 // Shutdown this socket
208                 $this->shutdownSocket($socketResource);
209
210                 // Throw it again
211                 throw new SocketConnectionException(array($this, $socketResource, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
212         }
213
214         /**
215          * Handles socket error 'resource temporary unavailable', but does not
216          * clear it for later debugging purposes.
217          *
218          * @param       $socketResource         A valid socket resource
219          * @param       $unlData                        A valid UNL data array
220          * @return      void
221          * @throws      SocketConnectionException       The connection attempts fails with a time-out
222          */
223         protected function socketErrorResourceUnavailableHandler ($socketResource, array $unlData) {
224                 // Get socket error code for verification
225                 $socketError = socket_last_error($socketResource);
226
227                 // Get error message
228                 $errorMessage = socket_strerror($socketError);
229
230                 // Shutdown this socket
231                 $this->shutdownSocket($socketResource);
232
233                 // Throw it again
234                 throw new SocketConnectionException(array($this, $socketResource, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
235         }
236
237         /**
238          * Handles socket error 'connection refused', but does not clear it for
239          * later debugging purposes.
240          *
241          * @param       $socketResource         A valid socket resource
242          * @param       $unlData                        A valid UNL data array
243          * @return      void
244          * @throws      SocketConnectionException       The connection attempts fails with a time-out
245          */
246         protected function socketErrorConnectionRefusedHandler ($socketResource, array $unlData) {
247                 // Get socket error code for verification
248                 $socketError = socket_last_error($socketResource);
249
250                 // Get error message
251                 $errorMessage = socket_strerror($socketError);
252
253                 // Shutdown this socket
254                 $this->shutdownSocket($socketResource);
255
256                 // Throw it again
257                 throw new SocketConnectionException(array($this, $socketResource, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
258         }
259
260         /**
261          * Handles socket error 'no route to host', but does not clear it for later
262          * debugging purposes.
263          *
264          * @param       $socketResource         A valid socket resource
265          * @param       $unlData                        A valid UNL data array
266          * @return      void
267          * @throws      SocketConnectionException       The connection attempts fails with a time-out
268          */
269         protected function socketErrorNoRouteToHostHandler ($socketResource, array $unlData) {
270                 // Get socket error code for verification
271                 $socketError = socket_last_error($socketResource);
272
273                 // Get error message
274                 $errorMessage = socket_strerror($socketError);
275
276                 // Shutdown this socket
277                 $this->shutdownSocket($socketResource);
278
279                 // Throw it again
280                 throw new SocketConnectionException(array($this, $socketResource, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
281         }
282
283         /**
284          * Handles socket error 'operation already in progress' which happens in
285          * method connectToPeerByUnlData() on timed out connection
286          * attempts.
287          *
288          * @param       $socketResource         A valid socket resource
289          * @param       $unlData                        A valid UNL data array
290          * @return      void
291          * @throws      SocketConnectionException       The connection attempts fails with a time-out
292          */
293         protected function socketErrorOperationAlreadyProgressHandler ($socketResource, array $unlData) {
294                 // Get socket error code for verification
295                 $socketError = socket_last_error($socketResource);
296
297                 // Get error message
298                 $errorMessage = socket_strerror($socketError);
299
300                 // Half-shutdown this socket (see there for difference to shutdownSocket())
301                 $this->halfShutdownSocket($socketResource);
302
303                 // Throw it again
304                 throw new SocketConnectionException(array($this, $socketResource, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
305         }
306
307         /**
308          * Handles socket error 'connection reset by peer', but does not clear it for
309          * later debugging purposes.
310          *
311          * @param       $socketResource         A valid socket resource
312          * @param       $unlData                        A valid UNL data array
313          * @return      void
314          * @throws      SocketConnectionException       The connection attempts fails with a time-out
315          */
316         protected function socketErrorConnectionResetByPeerHandler ($socketResource, array $unlData) {
317                 // Get socket error code for verification
318                 $socketError = socket_last_error($socketResource);
319
320                 // Get error message
321                 $errorMessage = socket_strerror($socketError);
322
323                 // Shutdown this socket
324                 $this->shutdownSocket($socketResource);
325
326                 // Throw it again
327                 throw new SocketConnectionException(array($this, $socketResource, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET);
328         }
329
330         /**
331          * Handles socket "error" 'operation now in progress' which can be safely
332          * passed on with non-blocking connections.
333          *
334          * @param       $socketResource         A valid socket resource
335          * @param       $unlData                        A valid UNL data array
336          * @return      void
337          */
338         protected function socketErrorOperationInProgressHandler ($socketResource, array $unlData) {
339                 self::createDebugInstance(__CLASS__)->debugOutput('CONNECTION-HELPER[' . __METHOD__ . ':' . __LINE__ . ']: Operation is now in progress, this is usual for non-blocking connections and is no bug.');
340         }
341 }
342
343 // [EOF]
344 ?>