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;