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