]> git.mxchange.org Git - hub.git/blob - application/hub/main/class_HubPeer.php
some comments fixed, small fix for windows OSes and some other things fixed
[hub.git] / application / hub / main / class_HubPeer.php
1 <?php
2 /**
3  * A hub peer class for communicating with the hub peer
4  *
5  * @author              Roland Haeder <webmaster@ship-simu.org>
6  * @version             0.0
7  * @copyright   Copyright(c) 2007, 2008 Roland Haeder, this is free software
8  * @license             GNU GPL 3.0 or any newer version
9  *
10  * This program is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation, either version 3 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23 class HubPeer extends BaseFrameworkSystem {
24         /**
25          * This peer's socket resource
26          */
27         private $peerSocket = null;
28
29         /**
30          * Timestamp for connection
31          */
32         private $connectTime = 0;
33
34         /**
35          * Timestamp of last received message
36          */
37         private $lastReceivedMessageStamp = 0;
38
39         /**
40          * Last received message
41          */
42         private $lastReceivedMessage = "";
43
44         /**
45          * Timestamp of last sent message
46          */
47         private $lastSentMessageStamp = 0;
48
49         /**
50          * Last sent message
51          */
52         private $lastSentMessage = "";
53
54         /**
55          * Number of tries for authorization
56          */
57         private $authRetries = 0;
58
59         /**
60          * Wether the peer is authorized (DO NEVER SET THIS TO "true"!)
61          */
62         private $isAuthorized = false;
63
64         /**
65          * Wether this peer needs to be requested for the AUTH command
66          */
67         private $needAuthRequest = true;
68
69         /**
70          * The peer's IP number
71          */
72         private static $peerIP = "0.0.0.0";
73
74         /**
75          * Wether the peer has "hello-ed" to the other
76          */
77         private $helloSent = false;
78
79         /**
80          * Wether the peer has replied our HELLO
81          */
82         private $helloReplied = false;
83
84         /**
85          * Wether we have sent our ELHO
86          */
87         private $elhoSent = false;
88
89         /**
90          * An instance of HubCoreLoop
91          */
92         private $hubInstance = null;
93
94         /**
95          * When the last ping was
96          */
97         private $lastPinged = 0;
98
99         /**
100          * When the last pong was
101          */
102         private $lastPonged = 0;
103
104         /**
105          * Number of missing pongs from the other peer
106          */
107         private $missingPongs = 0;
108
109         /**
110          * An instance of a HubCommandProcessor class
111          */
112         private $commandInstance = null;
113
114         /**
115          * A queue for reading data in non-blocking mode
116          */
117         private $queues = "";
118
119         /**
120          * Line ends
121          */
122         const LINE_END = "\n";
123
124         //----------------------------------------------------------
125         //                              Exceptions
126         //----------------------------------------------------------
127         const EXCEPTION_PEER_SOCKET_INVALID     = 0x200;
128         const EXCEPTION_PEER_IP_CHANGED         = 0x201;
129         const EXCEPTION_PEER_SOCKET_BROKEN              = 0x202;
130         //----------------------------------------------------------
131
132         /**
133          * The private constructor
134          */
135         private function __construct () {
136                 // Call parent constructor
137                 parent::constructor(__CLASS__);
138
139                 // Set description
140                 $this->setPartDescr("Hub-Peer");
141
142                 // Set unique ID
143                 $this->createUniqueID();
144
145                 // Tidy up a little
146                 $this->removeSystemArray();
147                 $this->removeNumberFormaters();
148
149                 // Get a command processor
150                 $this->commandInstance = HubCommandProcessor::createHubCommandProcessor($this);
151         }
152
153         /**
154          * Creates an instance of a HubPeer by a provided valid socket resource
155          *
156          * @param               $peerSocket             The socket resource for the peer
157          * @param               $hubInstance            An instance of HubCoreLoop
158          * @return      $peerInstance           An instance of HubPeer
159          */
160         public final static function createHubPeerBySocket ($peerSocket, HubCoreLoop $hubInstance) {
161                 // Get a new instance
162                 $peerInstance = new HubPeer();
163
164                 // Is the peer socket fine?
165                 if (!is_resource($peerSocket)) {
166                         // There is a problem with the socket
167                         throw new PeerSocketException (
168                                 array(
169                                         'this'  => $peerInstance,
170                                         'type'  => gettype($peerSocket)
171                                 ), self::EXCEPTION_PEER_SOCKET_INVALID
172                         );
173                 }
174
175                 // Set the socket
176                 $peerInstance->setPeerSocket($peerSocket);
177
178                 // Set connection timestamp
179                 $peerInstance->setConnectionTimestamp();
180
181                 // Set the hub instance
182                 $peerInstance->setHubInstance($hubInstance);
183
184                 // Return the instance
185                 return $peerInstance;
186         }
187
188         //----------------------------------------------------------
189         //                      Public getter/setter
190         //----------------------------------------------------------
191
192         /**
193          * Setter for peer socket
194          *
195          * @param               $peerSocket             The peer's socket
196          * @return      void
197          */
198         public final function setPeerSocket ($peerSocket) {
199                 if (is_resource($peerSocket)) {
200                         $this->peerSocket = $peerSocket;
201                 } else {
202                         $this->peerSocket = null;
203                 }
204         }
205
206         /**
207          * Setter for connection timestamp only once
208          *
209          * @return      void
210          */
211         public final function setConnectionTimestamp () {
212                 if ($this->connectTime == 0) $this->connectTime = time();
213         }
214
215         /**
216          * Getter for a raw IP number
217          *
218          * @return      $ip                                     The peer's IP number
219          * @throws      BrokenPipeException             If a socket has lost its connection to the peer
220          */
221         public final function getRawIP () {
222                 if (is_resource($this->peerSocket)) {
223                         // Get IP from socket
224                         @socket_getpeername($this->peerSocket, $ip);
225
226                         // Connection problems?
227                         if (socket_last_error() > 0) {
228                                 // Throw an exception
229                                 throw new BrokenPipeException(
230                                         array(
231                                                 'this'  => $this,
232                                                 'code'  => socket_last_error()
233                                         ), self::EXCEPTION_PEER_SOCKET_BROKEN
234                                 );
235                         }
236                 } else {
237                         // Without a socket we cannot determine the right IP
238                         $ip = "0.0.0.0";
239                 }
240
241                 // And return it...
242                 return $ip;
243         }
244
245         /**
246          * Getter for a validated peer IP
247          *
248          * @return      $peerIP         The peer's IP number
249          */
250         public final function getValidatedIP() {
251                 // Is the socket valid and IP not set?
252                 if ((is_resource($this->peerSocket)) && (socket_last_error() == 0) && (self::$peerIP == "0.0.0.0")) {
253                         // Get peer's IP number
254                         self::$peerIP = $this->getRawIP();
255                 } elseif ((is_resource($this->peerSocket)) && (socket_last_error() == 0)) {
256                         // Get current raw IP number for validation
257                         $ip = $this->getRawIP();
258
259                         // Check if the IP has changed
260                         if ($ip !== self::$peerIP) {
261                                 // The IP number has changed!
262                                 throw new IPSpoofingException(
263                                         array(
264                                                 'this'  => $this,
265                                                 'old_ip'        => self::$peerIP,
266                                                 'new_ip'        => $ip
267                                         ), self::EXCEPTION_PEER_IP_CHANGED
268                                 );
269                         }
270                 }
271
272                 return self::$peerIP;
273         }
274
275         /**
276          * Getter for number of authorization tries
277          * @return      $authRetries    Authorization tries
278          */
279         public final function getAuthRetries () {
280                 return $this->authRetries;
281         }
282
283         //----------------------------------------------------------
284         //                      Public methods
285         //----------------------------------------------------------
286
287         /**
288          * Setter for HubCoreLoop instances
289          *
290          * @param               $hubInstance            An instance of a HubCoreLoop class
291          * @return      void
292          */
293         public final function setHubInstance(HubCoreLoop $hubInstance) {
294                 $this->hubInstance = $hubInstance;
295         }
296
297         /**
298          * Getter for HubCoreLoop instances
299          *
300          * @return      $hubInstance            An instance of a HubCoreLoop class
301          */
302         public final function getHubInstance() {
303                 return $this->hubInstance;
304         }
305
306         /**
307          * Getter for last sent message timestamp
308          *
309          * @return      $lastSentMessageStamp   Timestamp of last sent message
310          */
311         public final function getLastSentMessageStamp () {
312                 return $this->lastSentMessageStamp;
313         }
314
315         /**
316          * Getter for last sent message
317          *
318          * @return      $lastSentMessage        Last sent message to this peer
319          */
320         public final function getLastSentMessage () {
321                 return $this->lastSentMessage;
322         }
323
324         /**
325          * Determines wether the peer is authorized
326          *
327          * @return      void
328          */
329         public final function ifPeerIsAuthorized () {
330                 return (($this->isAuthorized === true) && ($this->ifPeerNeedsAuthRequest() === false));
331         }
332
333         /**
334          * Returns wether this peer needs to receive the AUTH command
335          */
336         public final function ifPeerNeedsAuthRequest () {
337                 return $this->needAuthRequest;
338         }
339
340         /**
341          * Disconnects the peer with a special reason (status)
342          *
343          * @param               $reason         The special reason
344          * @return      void
345          */
346         public final function disconnectWithReason ($reason) {
347                 // Is the message set?
348                 if (!empty($reason)) {
349                         // Send the given message
350                         $this->sendMessage($reason);
351                 } else {
352                         // Send default "good bye"
353                         $this->sendMessage($this->getConfigInstance()->readConfig("hub_msg_bye"));
354                 }
355
356                 // Disconnect the client
357                 socket_shutdown($this->peerSocket, 2);
358                 socket_close($this->peerSocket);
359         }
360
361         /**
362          * Sends a specified message to the peer's socket
363          *
364          * @param               $message                The special message
365          * @return      void
366          * @throws      BrokenPipeException             If a pipe to a socket peer is lost
367          */
368         public final function sendMessage ($message) {
369                 if (empty($message)) {
370                         // Set default message
371                         $message = $this->getConfigInstance()->readConfig("hub_msg_bye");
372                 }
373
374                 // Debug message
375                 $this->getDebugInstance()->output(sprintf("[%s] Sending message \"%s\" to peer %s.<br />\n",
376                         __METHOD__,
377                         $message,
378                         $this->getValidatedIP()
379                 ));
380
381                 // Send it to the peer
382                 if (socket_write($this->peerSocket, $message."\n") !== false) {
383                         // Set the timestamp and message
384                         $this->lastSentMessage = $message;
385                         $this->lastSentMessageStamp = time();
386                 } else {
387                         // Message could not be sent
388                         throw new BrokenPipeException (
389                                 array(
390                                         'this'  => $this,
391                                         'code'  => socket_last_error()
392                                 ), self::EXCEPTION_PEER_SOCKET_BROKEN
393                         );
394                 }
395         }
396
397         /**
398          * Asks the connected new peer for the authorization key
399          *
400          * @return      void
401          */
402         public final function askAuthorizationKey () {
403                 if ($this->ifPeerNeedsAuthRequest()) {
404                         // This peer needs to receive the AUTH command so send it to him
405                         $this->sendMessage($this->getConfigInstance()->readConfig("hub_auth_request"));
406
407                         // Set it to done
408                         $this->needAuthRequest = false;
409                 }
410         }
411
412         /**
413          * Determines wether the peer is a local admin (localhost connection)
414          *
415          * @return      $isLocalAdmin           Wether the peer is a local admin
416          */
417         public function ifPeerIsLocalAdmin () {
418                 // Get the remote IP and extract only the first three numbers
419                 $remoteArray = explode(".", self::$peerIP);
420
421                 // Is the peer a local admin?
422                 return (($remoteArray[0].".".$remoteArray[1].".".$remoteArray[2]) == "127.0.0");
423         }
424
425         /**
426          * Enables the local admin and authorizes this peer
427          *
428          * @return      void
429          */
430         public function enableLocalAdmin() {
431                 // Is this IP really local?
432                 if (($this->ifPeerIsLocalAdmin()) && (!$this->ifPeerIsAuthorized())) {
433                         // Then authorize him
434                         $this->isAuthorized = true;
435                         $this->needAuthRequest = false;
436                 } // END - if
437         }
438
439         /**
440          * Says "hi" to the local admin
441          *
442          * @return      void
443          */
444         public function sayHi2Admin () {
445                 // Send a message to him... ;-)
446                 $this->sendMessage("Local admin console is ready. Enter HELP for instructions.");
447         }
448
449         /**
450          * Enables wether the peer is authorized
451          *
452          * @param               $isAuthValid    Wether the authorization wents fine
453          * @return      void
454          */
455         public function enableIsAuthorized ($isAuthValid = true) {
456                 $this->isAuthorized = true;
457         }
458
459         /**
460          * Checks wether a HELLO has been sent and if not it will be send to the other peer
461          *
462          * @return      $helloSent      Wether a HELLO has been sent
463          */
464         public function ifHelloReceived () {
465                 // HELLO has been sent?
466                 if (!$this->helloSent) {
467                         // Read some data
468                         $read = $this->readFromSocket();
469
470                         // Is this a HELLO?
471                         if ($read == $this->getConfigInstance()->readConfig("hub_peer_hello")) {
472                                 // All right! A HELLO has been received
473                                 $this->helloSent = true;
474                                 $this->getHubInstance()->getOutputInstance()->output(sprintf("[%s] Peer %s said HELLO to us.",
475                                         __METHOD__,
476                                         $this->getValidatedIP()
477                                 ));
478                         } // END - if
479                 } // END - if
480
481                 // Return status
482                 return $this->helloSent;
483         }
484
485         /**
486          * Wether this hub has replied our HELLO request
487          *
488          * @return      $helloReplied   Wether this hub has replied our HELLO request
489          */
490         public function ifHelloReplied () {
491                 if ((!$this->helloReplied) && ($this->lastSentMessage == $this->getConfigInstance()->readConfig("hub_peer_hello"))) {
492                         // Read some data
493                         $read = $this->readFromSocket();
494
495                         // Is this a HELLO?
496                         if ($read == $this->getConfigInstance()->readConfig("hub_hello_reply")) {
497                                 // Is this the master IP?
498                                 if ($this->getValidatedIP() == $this->getConfigInstance()->readConfig("hub_master_ip")) {
499                                         // All right! A HELLO has been received
500                                         $this->helloReplied = true;
501                                         $this->getHubInstance()->getOutputInstance()->output(sprintf("[%s] The master hub at %s:%d replied our %s.",
502                                                 __METHOD__,
503                                                 $this->getValidatedIP(),
504                                                 $this->getConfigInstance()->readConfig("hub_master_port"),
505                                                 $this->getConfigInstance()->readConfig("hub_peer_hello")
506                                         ));
507                                 } else {
508                                         // ELHOs from non-masters are not valid!
509                                         $this->getHubInstance()->getOutputInstance()->output(sprintf("[%s] Peer %s replied our %s but is not the master hub!",
510                                                 __METHOD__,
511                                                 $this->getValidatedIP(),
512                                                 $this->getConfigInstance()->readConfig("hub_peer_hello")
513                                         ));
514                                 }
515                         } // END - if
516                 } // END - if
517
518                 // Return status
519                 return $this->helloReplied;
520         }
521
522         /**
523          * Returns wether a ELHO (HELLO reply has been sent)
524          *
525          * @return      $elhoSent               Wether a ELHO has been sent to the peer
526          */
527         public final function ifELHOsent () {
528                 return $this->elhoSent;
529         }
530
531         /**
532          * Replies a HELLO message with a ELHO message
533          *
534          * @return      void
535          */
536         public function replyHelloMessage () {
537                 $this->sendMessage($this->getConfigInstance()->readConfig("hub_hello_reply"));
538                 $this->elhoSent = true;
539         }
540
541         /**
542          * Handles pinging this peer
543          *
544          * @return      void
545          */
546         public function handlePingPeer () {
547                 $lost = false;
548
549                 // Do we need to ping?
550                 if ((time() - $this->lastPinged) > $this->getConfigInstance()->readConfig("hub_ping_timeout")) {
551                         // Don't ping any masters! ;-)
552                         if ($this->getValidatedIP() != $this->getConfigInstance()->readConfig("hub_master_ip")) {
553                                 // Debug message
554                                 $this->getHubInstance()->getOutputInstance()->output(sprintf("[%s] Sending ping to peer %s...",
555                                         $this->__toString(),
556                                         $this->getValidatedIP()
557                                 ));
558
559                                 // Send out a PING and await a PONG
560                                 $this->commandInstance->simpleExecute(
561                                         $this->getConfigInstance()->readConfig("hub_peer_ping"),
562                                         $this->getConfigInstance()->readConfig("hub_ping_reply")
563                                 );
564
565                                 // PONG received within last ping?
566                                 if (($this->lastPonged < time()) && (($this->lastPonged - $this->lastPinged) < 0)) {
567                                         // Not replied so far
568                                         $this->missingPongs++;
569
570                                         // Debug message
571                                         $this->getHubInstance()->getOutputInstance()->output(sprintf("[%s] Ping not replied! Try: %d",
572                                                 $this->__toString(),
573                                                 $this->missingPongs
574                                         ));
575
576                                         // Limit reached?
577                                         if ($this->missingPongs == $this->getConfigInstance()->readConfig("hub_ping_maxdrops")) {
578                                                 // This peer is lost
579                                                 $this->getHubInstance()->disconnectPeerWithReason("hub_peer_miss_pong");
580                                                 $lost = true;
581                                         } // END - if
582                                 } // END - if
583
584                                 // Last time we pinged is now.
585                                 $this->lastPinged = time();
586
587                         } // END - if
588                 } // END - if
589
590                 // Connection is lost?
591                 if ($lost === true) return false;
592
593                 // Awaiting PONG here
594                 if ($this->commandInstance->ifAwaitsCommand($this->getConfigInstance()->readConfig("hub_ping_reply"))) {
595                         // PONG received! :-) So reset all counters...
596                         $this->lastPonged = time();
597                         $this->missingPongs = 0;
598
599                         // Notify the loop about the ping-pong-time
600                         $this->getHubInstance()->updatePeerEntry($this, (time() - $this->lastPinged));
601                 } // END - if
602         }
603
604         /**
605          * Handles any incoming commands from the master hub
606          *
607          * @return      void
608          */
609         public function handleMasterRequests () {
610                 // Read the raw socket for data packages
611                 if ($this->commandInstance->awaitAnyCommand()) {
612                         // A command has been received from the master hub
613                         $command = $this->commandInstance->pull();
614
615                         // TODO Handle a command from the master here...
616                 } // END - if
617         }
618
619         /**
620          * Reads raw data from the socket and trims leading/trailing spaces away.
621          * Returns an empty string if no data has been received.
622          *
623          * @return      $data                           Raw data from the underlaying socket
624          * @throws      BrokenPipeException             If a socket has lost its connection to the peer
625          */
626         public function readFromSocket () {
627                 $data = "";
628                 $read = array($this->peerSocket);
629                 $write = null;
630                 $except = null;
631                 $num = socket_select($read, $write, $except, 0);
632                 if ($num > 0) {
633                         // Something has changed on a socket
634                         foreach ($read as $socket) {
635                                 if (is_resource($socket)) {
636                                         $data = trim(@socket_read($socket, 1024, PHP_NORMAL_READ));
637                                         if (socket_last_error() > 0) {
638                                                 // Throw an exception
639                                                 throw new BrokenPipeException(
640                                                         array(
641                                                                 'this'  => $this,
642                                                                 'code'  => socket_last_error()
643                                                         ), self::EXCEPTION_PEER_SOCKET_BROKEN
644                                                 );
645                                         }
646                                         break;
647                                 } // END - if
648                         } // END - foreach
649                 } // END - if
650                 return $data;
651         }
652
653 } // END - class
654
655 // [EOF]
656 ?>