PocketMine-MP 5.21.1 git-2ff647079265e7c600203af4fd902b15e99d49a4
SendReliabilityLayer.php
1<?php
2
3/*
4 * This file is part of RakLib.
5 * Copyright (C) 2014-2022 PocketMine Team <https://github.com/pmmp/RakLib>
6 *
7 * RakLib is not affiliated with Jenkins Software LLC nor RakNet.
8 *
9 * RakLib is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
13 */
14
15declare(strict_types=1);
16
17namespace raklib\generic;
18
25use function array_fill;
26use function array_push;
27use function assert;
28use function count;
29use function microtime;
30use function str_split;
31use function strlen;
32
34 private const DATAGRAM_MTU_OVERHEAD = 36 + Datagram::HEADER_SIZE; //IP header (20 bytes) + UDP header (8 bytes) + RakNet weird (8 bytes) = 36
35 private const MIN_POSSIBLE_PACKET_SIZE_LIMIT = Session::MIN_MTU_SIZE - self::DATAGRAM_MTU_OVERHEAD;
40 private const UNACKED_RETRANSMIT_DELAY = 2.0;
41
43 private array $sendQueue = [];
44
45 private int $splitID = 0;
46
47 private int $sendSeqNumber = 0;
48
49 private int $messageIndex = 0;
50
51 private int $reliableWindowStart;
52 private int $reliableWindowEnd;
57 private array $reliableWindow = [];
58
60 private array $sendOrderedIndex;
62 private array $sendSequencedIndex;
63
65 private array $reliableBacklog = [];
66
68 private array $resendQueue = [];
69
71 private array $reliableCache = [];
72
74 private array $needACK = [];
75
77 private int $maxDatagramPayloadSize;
78
84 public function __construct(
85 private int $mtuSize,
86 private \Closure $sendDatagramCallback,
87 private \Closure $onACK,
88 private int $reliableWindowSize = 512,
89 ){
90 $this->sendOrderedIndex = array_fill(0, PacketReliability::MAX_ORDER_CHANNELS, 0);
91 $this->sendSequencedIndex = array_fill(0, PacketReliability::MAX_ORDER_CHANNELS, 0);
92
93 $this->maxDatagramPayloadSize = $this->mtuSize - self::DATAGRAM_MTU_OVERHEAD;
94
95 $this->reliableWindowStart = 0;
96 $this->reliableWindowEnd = $this->reliableWindowSize;
97 }
98
102 private function sendDatagram(array $packets) : void{
103 $datagram = new Datagram();
104 $datagram->seqNumber = $this->sendSeqNumber++;
105 $datagram->packets = $packets;
106 ($this->sendDatagramCallback)($datagram);
107
108 $resendable = [];
109 foreach($datagram->packets as $pk){
110 if(PacketReliability::isReliable($pk->reliability)){
111 $resendable[] = $pk;
112 }
113 }
114 if(count($resendable) !== 0){
115 $this->reliableCache[$datagram->seqNumber] = new ReliableCacheEntry($resendable);
116 }
117 }
118
119 public function sendQueue() : void{
120 if(count($this->sendQueue) > 0){
121 $this->sendDatagram($this->sendQueue);
122 $this->sendQueue = [];
123 }
124 }
125
126 private function addToQueue(EncapsulatedPacket $pk, bool $immediate) : void{
127 if(PacketReliability::isReliable($pk->reliability)){
128 if($pk->messageIndex === null || $pk->messageIndex < $this->reliableWindowStart){
129 throw new \InvalidArgumentException("Cannot send a reliable packet with message index less than the window start ($pk->messageIndex < $this->reliableWindowStart)");
130 }
131 if($pk->messageIndex >= $this->reliableWindowEnd){
132 //If we send this now, the client's reliable window may overflow, causing the packet to need redelivery
133 $this->reliableBacklog[$pk->messageIndex] = $pk;
134 return;
135 }
136
137 $this->reliableWindow[$pk->messageIndex] = false;
138 }
139
140 if($pk->identifierACK !== null and $pk->messageIndex !== null){
141 $this->needACK[$pk->identifierACK][$pk->messageIndex] = $pk->messageIndex;
142 }
143
144 $length = 0;
145 foreach($this->sendQueue as $queued){
146 $length += $queued->getTotalLength();
147 }
148
149 if($length + $pk->getTotalLength() > $this->maxDatagramPayloadSize){
150 $this->sendQueue();
151 }
152
153 if($pk->identifierACK !== null){
154 $this->sendQueue[] = clone $pk;
155 $pk->identifierACK = null;
156 }else{
157 $this->sendQueue[] = $pk;
158 }
159
160 if($immediate){
161 // Forces pending sends to go out now, rather than waiting to the next update interval
162 $this->sendQueue();
163 }
164 }
165
166 public function addEncapsulatedToQueue(EncapsulatedPacket $packet, bool $immediate = false) : void{
167 if($packet->identifierACK !== null){
168 $this->needACK[$packet->identifierACK] = [];
169 }
170
171 if(PacketReliability::isOrdered($packet->reliability)){
172 $packet->orderIndex = $this->sendOrderedIndex[$packet->orderChannel]++;
173 }elseif(PacketReliability::isSequenced($packet->reliability)){
174 $packet->orderIndex = $this->sendOrderedIndex[$packet->orderChannel]; //sequenced packets don't increment the ordered channel index
175 $packet->sequenceIndex = $this->sendSequencedIndex[$packet->orderChannel]++;
176 }
177
178 $maxBufferSize = $this->maxDatagramPayloadSize - $packet->getHeaderLength();
179
180 if(strlen($packet->buffer) > $maxBufferSize){
181 $buffers = str_split($packet->buffer, $maxBufferSize - EncapsulatedPacket::SPLIT_INFO_LENGTH);
182 $bufferCount = count($buffers);
183
184 $splitID = ++$this->splitID % 65536;
185 foreach($buffers as $count => $buffer){
186 $pk = new EncapsulatedPacket();
187 $pk->splitInfo = new SplitPacketInfo($splitID, $count, $bufferCount);
188 $pk->reliability = $packet->reliability;
189 $pk->buffer = $buffer;
190 $pk->identifierACK = $packet->identifierACK;
191
192 if(PacketReliability::isReliable($pk->reliability)){
193 $pk->messageIndex = $this->messageIndex++;
194 }
195
196 $pk->sequenceIndex = $packet->sequenceIndex;
197 $pk->orderChannel = $packet->orderChannel;
198 $pk->orderIndex = $packet->orderIndex;
199
200 $this->addToQueue($pk, true);
201 }
202 }else{
203 if(PacketReliability::isReliable($packet->reliability)){
204 $packet->messageIndex = $this->messageIndex++;
205 }
206 $this->addToQueue($packet, $immediate);
207 }
208 }
209
210 private function updateReliableWindow() : void{
211 while(
212 isset($this->reliableWindow[$this->reliableWindowStart]) && //this messageIndex has been used
213 $this->reliableWindow[$this->reliableWindowStart] === true //we received an ack for this messageIndex
214 ){
215 unset($this->reliableWindow[$this->reliableWindowStart]);
216 $this->reliableWindowStart++;
217 $this->reliableWindowEnd++;
218 }
219 }
220
221 public function onACK(ACK $packet) : void{
222 foreach($packet->packets as $seq){
223 if(isset($this->reliableCache[$seq])){
224 foreach($this->reliableCache[$seq]->getPackets() as $pk){
225 assert($pk->messageIndex !== null && $pk->messageIndex >= $this->reliableWindowStart && $pk->messageIndex < $this->reliableWindowEnd);
226 $this->reliableWindow[$pk->messageIndex] = true;
227 $this->updateReliableWindow();
228
229 if($pk->identifierACK !== null){
230 unset($this->needACK[$pk->identifierACK][$pk->messageIndex]);
231 if(count($this->needACK[$pk->identifierACK]) === 0){
232 unset($this->needACK[$pk->identifierACK]);
233 ($this->onACK)($pk->identifierACK);
234 }
235 }
236 }
237 unset($this->reliableCache[$seq]);
238 }
239 }
240 }
241
242 public function onNACK(NACK $packet) : void{
243 foreach($packet->packets as $seq){
244 if(isset($this->reliableCache[$seq])){
245 foreach($this->reliableCache[$seq]->getPackets() as $pk){
246 $this->resendQueue[] = $pk;
247 }
248 unset($this->reliableCache[$seq]);
249 }
250 }
251 }
252
253 public function needsUpdate() : bool{
254 return (
255 count($this->sendQueue) !== 0 or
256 count($this->reliableBacklog) !== 0 or
257 count($this->resendQueue) !== 0 or
258 count($this->reliableCache) !== 0
259 );
260 }
261
262 public function update() : void{
263 $retransmitOlderThan = microtime(true) - self::UNACKED_RETRANSMIT_DELAY;
264 foreach($this->reliableCache as $seq => $pk){
265 if($pk->getTimestamp() < $retransmitOlderThan){
266 //behave as if a NACK was received
267 array_push($this->resendQueue, ...$pk->getPackets());
268 unset($this->reliableCache[$seq]);
269 }else{
270 break;
271 }
272 }
273
274 if(count($this->resendQueue) > 0){
275 foreach($this->resendQueue as $pk){
276 //resends should always be within the reliable window
277 $this->addToQueue($pk, false);
278 }
279 $this->resendQueue = [];
280 }
281
282 if(count($this->reliableBacklog) > 0){
283 foreach($this->reliableBacklog as $k => $pk){
284 assert($pk->messageIndex !== null && $pk->messageIndex >= $this->reliableWindowStart);
285 if($pk->messageIndex >= $this->reliableWindowEnd){
286 //we can't send this packet yet, the client's reliable window will drop it
287 break;
288 }
289
290 $this->addToQueue($pk, false);
291 unset($this->reliableBacklog[$k]);
292 }
293 }
294
295 $this->sendQueue();
296 }
297}
__construct(private int $mtuSize, private \Closure $sendDatagramCallback, private \Closure $onACK, private int $reliableWindowSize=512,)