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