40    public const STATE_CONNECTING = 0;
 
   41    public const STATE_CONNECTED = 1;
 
   42    public const STATE_DISCONNECT_PENDING = 2;
 
   43    public const STATE_DISCONNECT_NOTIFIED = 3;
 
   44    public const STATE_DISCONNECTED = 4;
 
   46    public const MIN_MTU_SIZE = 400;
 
   48    private \Logger $logger;
 
   52    protected int $state = self::STATE_CONNECTING;
 
   56    private float $lastUpdate;
 
   57    private float $disconnectionTime = 0;
 
   59    private bool $isActive = 
false;
 
   61    private float $lastPingTime = -1;
 
   63    private int $lastPingMeasure = 1;
 
   78        int $recvMaxSplitParts = PHP_INT_MAX,
 
   79        int $recvMaxConcurrentSplits = PHP_INT_MAX
 
   81        if($mtuSize < self::MIN_MTU_SIZE){
 
   82            throw new \InvalidArgumentException(
"MTU size must be at least " . self::MIN_MTU_SIZE . 
", got $mtuSize");
 
   84        $this->logger = new \PrefixedLogger($logger, 
"Session: " . $address->toString());
 
   85        $this->address = $address;
 
   86        $this->
id = $clientId;
 
   88        $this->lastUpdate = microtime(
true);
 
   93                $this->handleEncapsulatedPacketRoute($pk);
 
   99            $recvMaxConcurrentSplits
 
  103            function(
Datagram $datagram) : 
void{
 
  106            function(
int $identifierACK) : 
void{
 
 
  120    abstract protected function onPacketAck(
int $identifierACK) : void;
 
  155        return intdiv(hrtime(true), 1_000_000);
 
 
  158    public function getLogger() : \
Logger{
 
  159        return $this->logger;
 
  162    public function getAddress() : InternetAddress{
 
  163        return $this->address;
 
  166    public function getID() : int{
 
  170    public function getState() : int{
 
  174    public function isTemporary() : bool{
 
  175        return $this->state === self::STATE_CONNECTING;
 
  178    public function isConnected() : bool{
 
  180            $this->state !== self::STATE_DISCONNECT_PENDING and
 
  181            $this->state !== self::STATE_DISCONNECT_NOTIFIED and
 
  182            $this->state !== self::STATE_DISCONNECTED;
 
  185    public function update(
float $time) : void{
 
  186        if(!$this->isActive and ($this->lastUpdate + 10) < $time){
 
  187            $this->forciblyDisconnect(DisconnectReason::PEER_TIMEOUT);
 
  192        if($this->state === self::STATE_DISCONNECT_PENDING || $this->state === self::STATE_DISCONNECT_NOTIFIED){
 
  194            if(!$this->sendLayer->needsUpdate() and !$this->recvLayer->needsUpdate()){
 
  195                if($this->state === self::STATE_DISCONNECT_PENDING){
 
  196                    $this->queueConnectedPacket(
new DisconnectionNotification(), PacketReliability::RELIABLE_ORDERED, 0, 
true);
 
  197                    $this->state = self::STATE_DISCONNECT_NOTIFIED;
 
  198                    $this->logger->debug(
"All pending traffic flushed, sent disconnect notification");
 
  200                    $this->state = self::STATE_DISCONNECTED;
 
  201                    $this->logger->debug(
"Client cleanly disconnected, marking session for destruction");
 
  204            }elseif($this->disconnectionTime + 10 < $time){
 
  205                $this->state = self::STATE_DISCONNECTED;
 
  206                $this->logger->debug(
"Timeout during graceful disconnect, forcibly closing session");
 
  211        $this->isActive = 
false;
 
  213        $this->recvLayer->update();
 
  214        $this->sendLayer->update();
 
  216        if($this->lastPingTime + 5 < $time){
 
  218            $this->lastPingTime = $time;
 
  222    protected function queueConnectedPacket(ConnectedPacket $packet, 
int $reliability, 
int $orderChannel, 
bool $immediate = 
false) : void{
 
  223        $out = new PacketSerializer();  
 
  224        $packet->encode($out);
 
  226        $encapsulated = 
new EncapsulatedPacket();
 
  227        $encapsulated->reliability = $reliability;
 
  228        $encapsulated->orderChannel = $orderChannel;
 
  229        $encapsulated->buffer = $out->getBuffer();
 
  231        $this->sendLayer->addEncapsulatedToQueue($encapsulated, $immediate);
 
  234    public function addEncapsulatedToQueue(EncapsulatedPacket $packet, 
bool $immediate) : void{
 
  235        $this->sendLayer->addEncapsulatedToQueue($packet, $immediate);
 
  238    protected function sendPing(
int $reliability = PacketReliability::UNRELIABLE) : void{
 
  239        $this->queueConnectedPacket(ConnectedPing::create($this->getRakNetTimeMS()), $reliability, 0, true);
 
  242    private function handleEncapsulatedPacketRoute(EncapsulatedPacket $packet) : void{
 
  243        $id = ord($packet->buffer[0]);
 
  244        if($id < MessageIdentifiers::ID_USER_PACKET_ENUM){ 
 
  245            if($this->state === self::STATE_CONNECTING){
 
  246                $this->handleRakNetConnectionPacket($packet->buffer);
 
  247            }elseif($id === MessageIdentifiers::ID_DISCONNECTION_NOTIFICATION){
 
  248                $this->handleRemoteDisconnect();
 
  249            }elseif($id === MessageIdentifiers::ID_CONNECTED_PING){
 
  250                $dataPacket = 
new ConnectedPing();
 
  251                $dataPacket->decode(
new PacketSerializer($packet->buffer));
 
  252                $this->queueConnectedPacket(ConnectedPong::create(
 
  253                    $dataPacket->sendPingTime,
 
  254                    $this->getRakNetTimeMS()
 
  255                ), PacketReliability::UNRELIABLE, 0);
 
  256            }elseif($id === MessageIdentifiers::ID_CONNECTED_PONG){
 
  257                $dataPacket = 
new ConnectedPong();
 
  258                $dataPacket->decode(
new PacketSerializer($packet->buffer));
 
  260                $this->handlePong($dataPacket->sendPingTime, $dataPacket->sendPongTime);
 
  262        }elseif($this->state === self::STATE_CONNECTED){
 
  263            $this->onPacketReceive($packet->buffer);
 
  272    private function handlePong(
int $sendPingTime, 
int $sendPongTime) : void{
 
  273        if($sendPingTime < 0){
 
  274            $this->logger->debug(
"Received invalid pong: timestamp overflow");
 
  276            $currentTime = $this->getRakNetTimeMS();
 
  277            if($currentTime < $sendPingTime){
 
  278                $this->logger->debug(
"Received invalid pong: timestamp is in the future by " . ($sendPingTime - $currentTime) . 
" ms");
 
  280                $this->lastPingMeasure = $currentTime - $sendPingTime;
 
  281                $this->onPingMeasure($this->lastPingMeasure);
 
  290        $this->isActive = true;
 
  291        $this->lastUpdate = microtime(
true);
 
  294            $this->recvLayer->onDatagram($packet);
 
  295        }elseif($packet instanceof 
ACK){
 
  296            $this->sendLayer->onACK($packet);
 
  297        }elseif($packet instanceof NACK){
 
  298            $this->sendLayer->onNACK($packet);
 
 
  311        if($this->isConnected()){
 
  312            $this->state = self::STATE_DISCONNECT_PENDING;
 
  313            $this->disconnectionTime = microtime(
true);
 
  314            $this->onDisconnect($reason);
 
  315            $this->logger->debug(
"Requesting graceful disconnect because \"" . DisconnectReason::toString($reason) . 
"\"");
 
 
  328        $this->state = self::STATE_DISCONNECTED;
 
  329        $this->onDisconnect($reason);
 
  330        $this->logger->debug(
"Forcibly disconnecting session due to " . DisconnectReason::toString($reason));
 
 
  333    private function handleRemoteDisconnect() : void{
 
  336        $this->recvLayer->update();
 
  338        if($this->isConnected()){
 
  341            $this->onDisconnect(DisconnectReason::CLIENT_DISCONNECT);
 
  343        $this->state = self::STATE_DISCONNECTED;
 
  344        $this->logger->debug(
"Terminating session due to client disconnect");
 
  351        return $this->state === self::STATE_DISCONNECTED;