/**
  * Called whenever there is data available on the stream
  * @param $data String the raw data
  */
 public function onData($data)
 {
     //if we're in encryption stage decrypt it first
     if ($this->secret !== null) {
         mcrypt_generic_init($this->encryptor, $this->secret, $this->secret);
         $data = mdecrypt_generic($this->encryptor, $data);
         mcrypt_generic_deinit($this->encryptor);
     }
     //attempt to parse the data as a packet
     try {
         //add the data to the current buffer
         $this->packetBuffer .= $data;
         //for as long as we have data left in the buffer
         do {
             //read the packet length from the stream
             $packetLengthVarInt = VarInt::readUnsignedVarInt($this->packetBuffer);
             //not enough data to read the packet length, wait for more data
             if ($packetLengthVarInt === false) {
                 return;
             }
             //total length of the packet is the length of the varint + it's value
             $totalLength = $packetLengthVarInt->getDataLength() + $packetLengthVarInt->getValue();
             //if we don't have enough data to read the entire packet wait for more data to enter
             $bufferLength = strlen($this->packetBuffer);
             if ($bufferLength < $totalLength) {
                 return;
             }
             //remove the packet length varint from the buffer
             $this->packetBuffer = substr($this->packetBuffer, $packetLengthVarInt->getDataLength());
             //read the packet ID from the buffer
             $packetDataWithPacketID = substr($this->packetBuffer, 0, $packetLengthVarInt->getValue());
             //remove the rest of the packet from the buffer
             $this->packetBuffer = substr($this->packetBuffer, $packetLengthVarInt->getValue());
             //read the packet ID
             $packetID = VarInt::readUnsignedVarInt($packetDataWithPacketID);
             //get the raw packet data
             $packetData = substr($packetDataWithPacketID, $packetID->getDataLength());
             $this->lastPacket = $this->currentTime();
             //trigger packet processing
             $this->processPacket($packetID->getValue(), $packetData);
             //if we have buffer left run again
         } while (strlen($this->packetBuffer) > 0);
         //if any exceptions are thrown (error parsing the packets e.t.c.) send a disconnect packet
     } catch (Exception $ex) {
         echo "EXCEPTION IN PACKET PARSING {$ex->getMessage()}\n";
         echo $ex->getTraceAsString();
         $dis = new DisconnectPacket();
         $dis->setReason('Internal Server Error: ' . $ex->getMessage());
         $this->end($dis->encodePacket());
     }
 }
 /**
  * Called on event LOGIN.EncryptionResponsePacket
  * @param EncryptionResponsePacket $packet
  */
 public function onEncryptionResponsePacket(EncryptionResponsePacket $packet)
 {
     //if we don't have a verifiyToken sent yet then the packet has been sent without us sending a request, disconnect client
     if (null === $this->verifyToken) {
         $this->disconnectClient((new DisconnectPacket())->setReason('Packet received out of order'));
         return;
     }
     //get the verify token from the packet it and decrypt it using our key
     $verifyToken = $this->certificate->getPrivateKey()->decrypt($packet->getToken());
     //if it isn't the same as our token then encryption failed, disconnect the client
     if ($verifyToken != $this->verifyToken) {
         $this->disconnectClient((new DisconnectPacket())->setReason('Invalid validation token'));
         return;
     }
     //decrypt the shared secret from the packet and decrypt it using our key
     $secret = $this->certificate->getPrivateKey()->decrypt($packet->getSecret());
     //enable encryption using the client generated secret
     $this->enableAES($secret);
     //generate a login hash the same as the client for the session server request
     $publicKey = $this->certificate->getPublicKey()->getPublicKey();
     $publicKey = base64_decode(substr($publicKey, 28, -26));
     //cut out the start and end lines of the key
     $loginHash = $this->serverID . $secret . $publicKey;
     $loginHash = self::sha1($loginHash);
     //create a new Yggdrasil for checking against Mojang
     $yggdrasil = new DefaultYggdrasil();
     try {
         //ask Mojang if the user has sent a join request, throws exception on failure
         $response = $yggdrasil->hasJoined($this->username, $loginHash);
         //get and set the clients UUID from the response
         $this->uuid = $response->getUuid();
         //trigger the login success with the disconnect packet
         $disconnect = new DisconnectPacket();
         $this->emit('login_success', [$this, $disconnect]);
         //if no reason was set after the event then set a default
         if ($disconnect->getReasonJSON() === null) {
             $disconnect->setReason("No kick reason supplied");
         }
         //disconnect the client
         $this->disconnectClient($disconnect);
     } catch (Exception $ex) {
         echo "{$this->username} failed authentication with Mojang session servers\n";
         //exception occured checking against Mojang
         $this->disconnectClient((new DisconnectPacket())->setReason("Error Authenticating with Mojang servers"));
     }
 }