]> git.mxchange.org Git - hub.git/blob - application/hub/main/class_HubPeer.php
Code syncronized with shipsimu code base
[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         protected function __construct () {
136                 // Call parent constructor
137                 parent::__construct(__CLASS__);
138
139                 // Tidy up a little
140                 $this->removeSystemArray();
141                 $this->removeNumberFormaters();
142
143                 // Get a command processor
144                 $this->commandInstance = HubCommandProcessor::createHubCommandProcessor($this);
145         }
146
147         /**
148          * Creates an instance of a HubPeer by a provided valid socket resource
149          *
150          * @param               $peerSocket             The socket resource for the peer
151          * @param               $hubInstance            An instance of HubCoreLoop
152          * @return      $peerInstance           An instance of HubPeer
153          */
154         public final static function createHubPeerBySocket ($peerSocket, HubCoreLoop $hubInstance) {
155                 // Get a new instance
156                 $peerInstance = new HubPeer();
157
158                 // Is the peer socket fine?
159                 if (!is_resource($peerSocket)) {
160                         // There is a problem with the socket
161                         throw new PeerSocketException (
162                                 array(
163                                         'this'  => $peerInstance,
164                                         'type'  => gettype($peerSocket)
165                                 ), self::EXCEPTION_PEER_SOCKET_INVALID
166                         );
167                 }
168
169                 // Set the socket
170                 $peerInstance->setPeerSocket($peerSocket);
171
172                 // Set connection timestamp
173                 $peerInstance->setConnectionTimestamp();
174
175                 // Set the hub instance
176                 $peerInstance->setHubInstance($hubInstance);
177
178                 // Return the instance
179                 return $peerInstance;
180         }
181
182         //----------------------------------------------------------
183         //                      Public getter/setter
184         //----------------------------------------------------------
185
186         /**
187          * Setter for peer socket
188          *
189          * @param       $peerSocket             The peer's socket
190          * @return      void
191          */
192         public final function setPeerSocket ($peerSocket) {
193                 if (is_resource($peerSocket)) {
194                         $this->peerSocket = $peerSocket;
195                 } else {
196                         $this->peerSocket = null;
197                 }
198         }
199
200         /**
201          * Getter for peer socket
202          *
203          * @return      $peerSocket             The peer's socket
204          */
205         public final function getPeerSocket () {
206                 return $this->peerSocket;
207         }
208
209         /**
210          * Setter for connection timestamp only once
211          *
212          * @return      void
213          */
214         public final function setConnectionTimestamp () {
215                 if ($this->connectTime == 0) $this->connectTime = time();
216         }
217
218         /**
219          * Getter for a raw IP number
220          *
221          * @return      $ip                                     The peer's IP number
222          * @throws      BrokenPipeException             If a socket has lost its connection to the peer
223          */
224         public final function getRawIP () {
225                 if (is_resource($this->peerSocket)) {
226                         // Get IP from socket
227                         @socket_getpeername($this->peerSocket, $ip);
228
229                         // Connection problems?
230                         if (socket_last_error() > 0) {
231                                 // Throw an exception
232                                 throw new BrokenPipeException(
233                                         array(
234                                                 'this'  => $this,
235                                                 'code'  => socket_last_error()
236                                         ), self::EXCEPTION_PEER_SOCKET_BROKEN
237                                 );
238                         }
239                 } else {
240                         // Without a socket we cannot determine the right IP
241                         $ip = "0.0.0.0";
242                 }
243
244                 // And return it...
245                 return $ip;
246         }
247
248         /**
249          * Getter for a validated peer IP
250          *
251          * @return      $peerIP         The peer's IP number
252          */
253         public final function getValidatedIP() {
254                 // Is the socket valid and IP not set?
255                 if ((is_resource($this->peerSocket)) && (socket_last_error() == 0) && (self::$peerIP == "0.0.0.0")) {
256                         // Get peer's IP number
257                         self::$peerIP = $this->getRawIP();
258                 } elseif ((is_resource($this->peerSocket)) && (socket_last_error() == 0)) {
259                         // Get current raw IP number for validation
260                         $ip = $this->getRawIP();
261
262                         // Check if the IP has changed
263                         if ($ip !== self::$peerIP) {
264                                 // The IP number has changed!
265                                 throw new IPSpoofingException(
266                                         array(
267                                                 'this'  => $this,
268                                                 'old_ip'        => self::$peerIP,
269                                                 'new_ip'        => $ip
270                                         ), self::EXCEPTION_PEER_IP_CHANGED
271                                 );
272                         }
273                 }
274
275                 return self::$peerIP;
276         }
277
278         /**
279          * Getter for number of authorization tries
280          * @return      $authRetries    Authorization tries
281          */
282         public final function getAuthRetries () {
283                 return $this->authRetries;
284         }
285
286         //----------------------------------------------------------
287         //                      Public methods
288         //----------------------------------------------------------
289
290         /**
291          * Setter for HubCoreLoop instances
292          *
293          * @param               $hubInstance            An instance of a HubCoreLoop class
294          * @return      void
295          */
296         public final function setHubInstance(HubCoreLoop $hubInstance) {
297                 $this->hubInstance = $hubInstance;
298         }
299
300         /**
301          * Getter for HubCoreLoop instances
302          *
303          * @return      $hubInstance            An instance of a HubCoreLoop class
304          */
305         public final function getHubInstance() {
306                 return $this->hubInstance;
307         }
308
309         /**
310          * Getter for last sent message timestamp
311          *
312          * @return      $lastSentMessageStamp   Timestamp of last sent message
313          */
314         public final function getLastSentMessageStamp () {
315                 return $this->lastSentMessageStamp;
316         }
317
318         /**
319          * Getter for last sent message
320          *
321          * @return      $lastSentMessage        Last sent message to this peer
322          */
323         public final function getLastSentMessage () {
324                 return $this->lastSentMessage;
325         }
326
327         /**
328          * Determines wether the peer is authorized
329          *
330          * @return      void
331          */
332         public final function ifPeerIsAuthorized () {
333                 return (($this->isAuthorized === true) && ($this->ifPeerNeedsAuthRequest() === false));
334         }
335
336         /**
337          * Returns wether this peer needs to receive the AUTH command
338          */
339         public final function ifPeerNeedsAuthRequest () {
340                 return $this->needAuthRequest;
341         }
342
343         /**
344          * Disconnects the peer with a special reason (status)
345          *
346          * @param       $reason         The special reason
347          * @return      void
348          */
349         public final function disconnectWithReason ($reason) {
350                 // Is the message set?
351                 if (!empty($reason)) {
352                         // Send the given message
353                         $this->sendMessage($reason);
354                 } else {
355                         // Send default "good bye"
356                         $this->sendMessage($this->getConfigInstance()->readConfig("hub_msg_bye"));
357                 }
358
359                 // Disconnect the client
360                 socket_shutdown($this->peerSocket, 2);
361                 socket_close($this->peerSocket);
362         }
363
364         /**
365          * Sends a specified message to the peer's socket
366          *
367          * @param       $message                The special message
368          * @return      void
369          * @throws      BrokenPipeException             If a pipe to a socket peer is lost
370          */
371         public final function sendMessage ($message) {
372                 if (empty($message)) {
373                         // Set default message
374                         $message = $this->getConfigInstance()->readConfig("hub_msg_bye");
375                 }
376
377                 // Debug message
378                 $this->getDebugInstance()->output(sprintf("[%s] Sending message \"%s\" to peer %s.<br />\n",
379                         __METHOD__,
380                         $message,
381                         $this->getValidatedIP()
382                 ));
383
384                 // Is the socket still valid?
385                 if (!is_resource($this->peerSocket)) {
386                         // The socket is no longer valid!
387                         throw new BrokenPipeException (
388                                 array(
389                                         'this'  => $this,
390                                         'code'  => socket_last_error()
391                                 ), self::EXCEPTION_PEER_SOCKET_BROKEN
392                         );
393                 }
394
395                 // Send it to the peer
396                 if (socket_write($this->peerSocket, $message."\n") !== false) {
397                         // Set the timestamp and message
398                         $this->lastSentMessage = $message;
399                         $this->lastSentMessageStamp = time();
400                 } else {
401                         // Message could not be sent
402                         throw new BrokenPipeException (
403                                 array(
404                                         'this'  => $this,
405                                         'code'  => socket_last_error()
406                                 ), self::EXCEPTION_PEER_SOCKET_BROKEN
407                         );
408                 }
409         }
410
411         /**
412          * Asks the connected new peer for the authorization key
413          *
414          * @return      void
415          */
416         public final function askAuthorizationKey () {
417                 if ($this->ifPeerNeedsAuthRequest()) {
418                         // This peer needs to receive the AUTH command so send it to him
419                         $this->sendMessage($this->getConfigInstance()->readConfig("hub_auth_request"));
420
421                         // Set it to done
422                         $this->needAuthRequest = false;
423                 }
424         }
425
426         /**
427          * Determines wether the peer is a local admin (localhost connection)
428          *
429          * @return      $isLocalAdmin           Wether the peer is a local admin
430          */
431         public function ifPeerIsLocalAdmin () {
432                 // Get the remote IP and extract only the first three numbers
433                 $remoteArray = explode(".", self::$peerIP);
434
435                 // Is the peer a local admin?
436                 return (($remoteArray[0].".".$remoteArray[1].".".$remoteArray[2]) == "127.0.0");
437         }
438
439         /**
440          * Enables the local admin and authorizes this peer
441          *
442          * @return      void
443          */
444         public function enableLocalAdmin() {
445                 // Is this IP really local?
446                 if (($this->ifPeerIsLocalAdmin()) && (!$this->ifPeerIsAuthorized())) {
447                         // Then authorize him
448                         $this->isAuthorized = true;
449                         $this->needAuthRequest = false;
450                 } // END - if
451         }
452
453         /**
454          * Says "hi" to the local admin
455          *
456          * @return      void
457          */
458         public function sayHi2Admin () {
459                 // Send a message to him... ;-)
460                 $this->sendMessage("Local admin console is ready. Enter HELP for instructions.");
461         }
462
463         /**
464          * Enables wether the peer is authorized
465          *
466          * @param               $isAuthValid    Wether the authorization wents fine
467          * @return      void
468          */
469         public function enableIsAuthorized ($isAuthValid = true) {
470                 $this->isAuthorized = true;
471         }
472
473         /**
474          * Checks wether a HELLO has been sent and if not it will be send to the other peer
475          *
476          * @return      $helloSent      Wether a HELLO has been sent
477          */
478         public function ifHelloReceived () {
479                 // HELLO has been sent?
480                 if (!$this->helloSent) {
481                         // Read some data
482                         $read = $this->readFromSocket();
483
484                         // Is this a HELLO?
485                         if ($read == $this->getConfigInstance()->readConfig("hub_peer_hello")) {
486                                 // All right! A HELLO has been received
487                                 $this->helloSent = true;
488                                 $this->getHubInstance()->getOutputInstance()->output(sprintf("[%s] Peer %s said HELLO to us.",
489                                         __METHOD__,
490                                         $this->getValidatedIP()
491                                 ));
492                         } // END - if
493                 } // END - if
494
495                 // Return status
496                 return $this->helloSent;
497         }
498
499         /**
500          * Wether this hub has replied our HELLO request
501          *
502          * @return      $helloReplied   Wether this hub has replied our HELLO request
503          */
504         public function ifHelloReplied () {
505                 if ((!$this->helloReplied) && ($this->lastSentMessage == $this->getConfigInstance()->readConfig("hub_peer_hello"))) {
506                         // Read some data
507                         $read = $this->readFromSocket();
508
509                         // Is this a HELLO?
510                         if ($read == $this->getConfigInstance()->readConfig("hub_hello_reply")) {
511                                 // Is this the master IP?
512                                 if ($this->getValidatedIP() == $this->getConfigInstance()->readConfig("hub_master_ip")) {
513                                         // All right! A HELLO has been received
514                                         $this->helloReplied = true;
515                                         $this->getHubInstance()->getOutputInstance()->output(sprintf("[%s] The master hub at %s:%d replied our %s.",
516                                                 __METHOD__,
517                                                 $this->getValidatedIP(),
518                                                 $this->getConfigInstance()->readConfig("hub_master_port"),
519                                                 $this->getConfigInstance()->readConfig("hub_peer_hello")
520                                         ));
521                                 } else {
522                                         // ELHOs from non-masters are not valid!
523                                         $this->getHubInstance()->getOutputInstance()->output(sprintf("[%s] Peer %s replied our %s but is not the master hub!",
524                                                 __METHOD__,
525                                                 $this->getValidatedIP(),
526                                                 $this->getConfigInstance()->readConfig("hub_peer_hello")
527                                         ));
528                                 }
529                         } // END - if
530                 } // END - if
531
532                 // Return status
533                 return $this->helloReplied;
534         }
535
536         /**
537          * Returns wether a ELHO (HELLO reply has been sent)
538          *
539          * @return      $elhoSent               Wether a ELHO has been sent to the peer
540          */
541         public final function ifELHOsent () {
542                 return $this->elhoSent;
543         }
544
545         /**
546          * Replies a HELLO message with a ELHO message
547          *
548          * @return      void
549          */
550         public function replyHelloMessage () {
551                 $this->sendMessage($this->getConfigInstance()->readConfig("hub_hello_reply"));
552                 $this->elhoSent = true;
553         }
554
555         /**
556          * Handles pinging this peer
557          *
558          * @return      void
559          */
560         public function handlePingPeer () {
561                 $lost = false;
562
563                 // Do we need to ping?
564                 if ((time() - $this->lastPinged) > $this->getConfigInstance()->readConfig("hub_ping_timeout")) {
565                         // Don't ping any masters! ;-)
566                         if ($this->getValidatedIP() != $this->getConfigInstance()->readConfig("hub_master_ip")) {
567                                 // Debug message
568                                 $this->getHubInstance()->getOutputInstance()->output(sprintf("[%s] Sending ping to peer %s...",
569                                         $this->__toString(),
570                                         $this->getValidatedIP()
571                                 ));
572
573                                 // Send out a PING and await a PONG
574                                 $this->commandInstance->simpleExecute(
575                                         $this->getConfigInstance()->readConfig("hub_peer_ping"),
576                                         $this->getConfigInstance()->readConfig("hub_ping_reply")
577                                 );
578
579                                 // PONG received within last ping?
580                                 if (($this->lastPonged < time()) && (($this->lastPonged - $this->lastPinged) < 0)) {
581                                         // Not replied so far
582                                         $this->missingPongs++;
583
584                                         // Debug message
585                                         $this->getHubInstance()->getOutputInstance()->output(sprintf("[%s] Ping not replied! Try: %d",
586                                                 $this->__toString(),
587                                                 $this->missingPongs
588                                         ));
589
590                                         // Limit reached?
591                                         if ($this->missingPongs == $this->getConfigInstance()->readConfig("hub_ping_maxdrops")) {
592                                                 // This peer is lost
593                                                 $this->getHubInstance()->disconnectPeerWithReason("hub_peer_miss_pong");
594                                                 $lost = true;
595                                         } // END - if
596                                 } // END - if
597
598                                 // Last time we pinged is now.
599                                 $this->lastPinged = time();
600
601                         } // END - if
602                 } // END - if
603
604                 // Connection is lost?
605                 if ($lost === true) return false;
606
607                 // Awaiting PONG here
608                 if ($this->commandInstance->ifAwaitsCommand($this->getConfigInstance()->readConfig("hub_ping_reply"))) {
609                         // PONG received! :-) So reset all counters...
610                         $this->lastPonged = time();
611                         $this->missingPongs = 0;
612
613                         // Notify the loop about the ping-pong-time
614                         $this->getHubInstance()->updatePeerEntry($this, (time() - $this->lastPinged));
615                 } // END - if
616         }
617
618         /**
619          * Handles any incoming commands from the master hub
620          *
621          * @return      void
622          */
623         public function handleMasterRequests () {
624                 // Read the raw socket for data packages
625                 if ($this->commandInstance->awaitAnyCommand()) {
626                         // A command has been received from the master hub
627                         $command = $this->commandInstance->pull();
628
629                         // TODO Handle a command from the master here...
630                 } // END - if
631         }
632
633         /**
634          * Reads raw data from the socket and trims leading/trailing spaces away.
635          * Returns an empty string if no data has been received.
636          *
637          * @return      $data                           Raw data from the underlaying socket
638          * @throws      BrokenPipeException             If a socket has lost its connection to the peer
639          */
640         public function readFromSocket () {
641                 $data = "";
642                 $read = array($this->peerSocket);
643                 $write = null;
644                 $except = null;
645                 $num = socket_select($read, $write, $except, 0);
646                 if ($num > 0) {
647                         // Something has changed on a socket
648                         foreach ($read as $socket) {
649                                 if (is_resource($socket)) {
650                                         $data = trim(@socket_read($socket, 1024, PHP_NORMAL_READ));
651                                         if (socket_last_error() > 0) {
652                                                 // Throw an exception
653                                                 throw new BrokenPipeException(
654                                                         array(
655                                                                 'this'  => $this,
656                                                                 'code'  => socket_last_error()
657                                                         ), self::EXCEPTION_PEER_SOCKET_BROKEN
658                                                 );
659                                         }
660                                         break;
661                                 } // END - if
662                         } // END - foreach
663                 } // END - if
664                 return $data;
665         }
666
667 } // END - class
668
669 // [EOF]
670 ?>