Skip to content

Commit f4badb0

Browse files
committed
Ext-encoding support
1 parent 7b59c75 commit f4badb0

15 files changed

Lines changed: 170 additions & 130 deletions

.github/workflows/phpstan.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on: push
55
jobs:
66
phpstan:
77
name: PHPStan Analysis
8-
runs-on: ubuntu-latest
8+
runs-on: ubuntu-22.04
99
if: "!contains(github.event.head_commit.message, '[ci skip]')"
1010

1111
steps:
@@ -14,14 +14,14 @@ jobs:
1414
- name: Download PHP Release
1515
uses: dsaltares/fetch-gh-release-asset@1.1.2
1616
with:
17-
file: PHP-8.3-Linux-x86_64-PM5.tar.gz
17+
file: PHP-8.4-Linux-x86_64-PM5.tar.gz
1818
repo: NetherGamesMC/php-build-scripts
19-
version: "tags/pm5-php-8.3-latest"
19+
version: "tags/pm5-php-8.4-latest"
2020
token: ${{ secrets.GITHUB_TOKEN }}
2121
- name: Unpack PHP Release
22-
run: tar -xzvf PHP-8.3-Linux-x86_64-PM5.tar.gz
23-
- name: Install libFFI
24-
run: sudo apt install libffi7
22+
run: tar -xzvf PHP-8.4-Linux-x86_64-PM5.tar.gz
23+
- name: Install libffi7
24+
run: sudo apt update && sudo apt install -y --no-install-recommends libffi7
2525
- name: Download Composer
2626
run: curl -o composer.phar "https://getcomposer.org/composer-stable.phar"
2727
- name: Add Composer GitHub access token

ProxyNetworkInterface.php

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@
1515
use libproxy\protocol\LoginPacket;
1616
use libproxy\protocol\ProxyPacket;
1717
use libproxy\protocol\ProxyPacketPool;
18-
use libproxy\protocol\ProxyPacketSerializer;
18+
use pmmp\encoding\ByteBufferReader;
19+
use pmmp\encoding\ByteBufferWriter;
20+
use pmmp\encoding\DataDecodeException;
21+
use pmmp\encoding\LE;
1922
use pmmp\thread\Thread as NativeThread;
2023
use pmmp\thread\ThreadSafeArray;
24+
use pocketmine\network\FilterNoisyPacketException;
2125
use pocketmine\network\mcpe\compression\ZlibCompressor;
2226
use pocketmine\network\mcpe\convert\TypeConverter;
2327
use pocketmine\network\mcpe\EntityEventBroadcaster;
@@ -33,8 +37,6 @@
3337
use pocketmine\Server;
3438
use pocketmine\snooze\SleeperHandlerEntry;
3539
use pocketmine\thread\ThreadCrashException;
36-
use pocketmine\utils\Binary;
37-
use pocketmine\utils\BinaryDataException;
3840
use Socket;
3941
use ThreadedArray;
4042
use WeakMap;
@@ -97,11 +99,9 @@ public function __construct(PluginBase $plugin, int $port, ?string $composerPath
9799
}
98100
}
99101

100-
/** @phpstan-ignore-next-line */
101102
self::$latencyMap = new WeakMap();
102103

103-
/** @var Socket $threadNotifier */
104-
/** @var Socket $threadNotification */
104+
/** @var list{Socket, Socket} $ipc */
105105
[$threadNotifier, $threadNotification] = $ipc;
106106
$this->threadNotifier = $threadNotifier;
107107

@@ -163,27 +163,29 @@ public function start(): void
163163

164164
/**
165165
* @throws PacketHandlingException
166+
* @throws DataDecodeException
166167
*/
167168
private function onPacketReceive(string $buffer): void
168169
{
169-
$stream = new ProxyPacketSerializer($buffer);
170-
$socketId = $stream->getLInt();
170+
$stream = new ByteBufferReader($buffer);
171+
$socketId = LE::readUnsignedInt($stream);
171172

172-
if (($pk = ProxyPacketPool::getInstance()->getPacket($buffer, $stream->getOffset())) === null) {
173-
$offset = 0;
174-
throw new PacketHandlingException('Proxy packet with id (' . Binary::readUnsignedVarInt($buffer, $offset) . ') does not exist');
173+
$offset = $stream->getOffset();
174+
if (($pk = ProxyPacketPool::getInstance()->getPacket($stream)) === null) {
175+
throw new PacketHandlingException('Unknown ProxyPacket received from Proxy Thread');
175176
}
177+
$stream->setOffset($offset);
176178

177179
try {
178180
$pk->decode($stream);
179-
} catch (BinaryDataException $e) {
181+
} catch (DataDecodeException $e) {
180182
$this->server->getLogger()->debug('Closed socket with id(' . $socketId . ') because packet was invalid.');
181183
$this->close($socketId, 'Invalid Packet');
182184
return;
183185
}
184186

185-
if (!$stream->feof()) {
186-
$remains = substr($stream->getBuffer(), $stream->getOffset());
187+
if ($stream->getUnreadLength() > 0) {
188+
$remains = substr($stream->getData(), $stream->getOffset());
187189
$this->server->getLogger()->debug('Still ' . strlen($remains) . ' bytes unread in ' . $pk->pid() . ': ' . bin2hex($remains));
188190
}
189191

@@ -211,6 +213,10 @@ private function onPacketReceive(string $buffer): void
211213
break; // might be data arriving from the client after the server has closed the connection
212214
}
213215

216+
if ((fn() => $this->checkRepeatedPacketFilter($pk->payload))->call($session)) {
217+
break;
218+
}
219+
214220
$packet = PacketPool::getInstance()->getPacket($pk->payload);
215221
if ($packet === null) {
216222
$session->getLogger()->debug("Unknown packet: " . base64_encode($pk->payload));
@@ -221,6 +227,8 @@ private function onPacketReceive(string $buffer): void
221227
} catch (PacketHandlingException $e) {
222228
$session->getLogger()->debug($packet->getName() . ": " . base64_encode($pk->payload));
223229
throw PacketHandlingException::wrap($e, "Error processing " . $packet->getName());
230+
} catch (FilterNoisyPacketException) {
231+
(fn() => $this->noisyPacketBuffer = $pk->payload)->call($session);
224232
}
225233
$this->receiveBytes += strlen($pk->payload);
226234
break;
@@ -233,7 +241,7 @@ private function onPacketReceive(string $buffer): void
233241
$session->handleAckReceipt($pk->receiptId);
234242
break;
235243
}
236-
} catch (PacketHandlingException|BinaryDataException $exception) {
244+
} catch (PacketHandlingException|DataDecodeException $exception) {
237245
$this->close($socketId, 'Error handling a Packet (Server)');
238246

239247
$this->server->getLogger()->logException($exception);
@@ -285,13 +293,13 @@ public function getSession(int $socketId): ?NetworkSession
285293

286294
public function putPacket(int $socketId, ProxyPacket $pk): void
287295
{
288-
$serializer = new ProxyPacketSerializer();
289-
$serializer->putLInt($socketId);
296+
$serializer = new ByteBufferWriter();
297+
LE::writeUnsignedInt($serializer, $socketId);
290298

291299
$pk->encode($serializer);
292300

293-
$this->mainToThreadWriter->write($serializer->getBuffer());
294-
$this->sendBytes += strlen($serializer->getBuffer());
301+
$this->mainToThreadWriter->write($serializer->getData());
302+
$this->sendBytes += strlen($serializer->getData());
295303

296304
try {
297305
socket_write($this->threadNotifier, "\x00"); // wakes up the socket_select function

ProxyServer.php

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,17 @@
1212
use libproxy\protocol\LoginPacket;
1313
use libproxy\protocol\ProxyPacket;
1414
use libproxy\protocol\ProxyPacketPool;
15-
use libproxy\protocol\ProxyPacketSerializer;
1615
use NetherGames\Quiche\io\QueueWriter;
1716
use NetherGames\Quiche\QuicheConnection;
1817
use NetherGames\Quiche\socket\QuicheServerSocket;
1918
use NetherGames\Quiche\SocketAddress;
2019
use NetherGames\Quiche\stream\BiDirectionalQuicheStream;
2120
use NetherGames\Quiche\stream\QuicheStream;
21+
use pmmp\encoding\BE;
22+
use pmmp\encoding\ByteBufferReader;
23+
use pmmp\encoding\ByteBufferWriter;
24+
use pmmp\encoding\DataDecodeException;
25+
use pmmp\encoding\LE;
2226
use pmmp\thread\ThreadSafeArray;
2327
use pocketmine\network\mcpe\compression\DecompressionException;
2428
use pocketmine\network\mcpe\compression\ZlibCompressor;
@@ -31,17 +35,13 @@
3135
use pocketmine\network\mcpe\protocol\ProtocolInfo;
3236
use pocketmine\network\mcpe\protocol\RequestNetworkSettingsPacket;
3337
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
34-
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
3538
use pocketmine\network\mcpe\protocol\types\CompressionAlgorithm;
3639
use pocketmine\network\mcpe\raklib\PthreadsChannelReader;
3740
use pocketmine\network\mcpe\raklib\SnoozeAwarePthreadsChannelWriter;
3841
use pocketmine\network\PacketHandlingException;
3942
use pocketmine\snooze\SleeperHandler;
4043
use pocketmine\snooze\SleeperHandlerEntry;
4144
use pocketmine\thread\log\AttachableThreadSafeLogger;
42-
use pocketmine\utils\Binary;
43-
use pocketmine\utils\BinaryDataException;
44-
use pocketmine\utils\BinaryStream;
4545
use Socket;
4646
use function array_keys;
4747
use function base64_encode;
@@ -56,6 +56,8 @@
5656

5757
class ProxyServer
5858
{
59+
private const INCOMING_PACKET_BATCH_HARD_LIMIT = 300;
60+
5961
/** @var PthreadsChannelReader */
6062
private PthreadsChannelReader $mainToThreadReader;
6163
/** @var SnoozeAwarePthreadsChannelWriter */
@@ -221,12 +223,12 @@ private function shutdownStream(int $streamIdentifier, string $reason, bool $fro
221223

222224
private function sendToMainBuffer(int $streamIdentifier, ProxyPacket $pk): void
223225
{
224-
$serializer = new ProxyPacketSerializer();
225-
$serializer->putLInt($streamIdentifier);
226+
$serializer = new ByteBufferWriter();
227+
LE::writeUnsignedInt($serializer, $streamIdentifier);
226228

227229
$pk->encode($serializer);
228230

229-
$this->threadToMainWriter->write($serializer->getBuffer());
231+
$this->threadToMainWriter->write($serializer->getData());
230232
}
231233

232234
public function tickProcessor(): void
@@ -237,16 +239,18 @@ public function tickProcessor(): void
237239
private function pushSockets(): void
238240
{
239241
while (($payload = $this->mainToThreadReader->read()) !== null) {
240-
$stream = new ProxyPacketSerializer($payload);
241-
$streamIdentifier = $stream->getLInt();
242+
$stream = new ByteBufferReader($payload);
243+
$streamIdentifier = LE::readUnsignedInt($stream);
242244

243-
if (($pk = ProxyPacketPool::getInstance()->getPacket($payload, $stream->getOffset())) === null) {
245+
$offset = $stream->getOffset();
246+
if (($pk = ProxyPacketPool::getInstance()->getPacket($stream)) === null) {
244247
throw new PacketHandlingException('Packet does not exist');
245248
}
249+
$stream->setOffset($offset);
246250

247251
try {
248252
$pk->decode($stream);
249-
} catch (BinaryDataException $e) {
253+
} catch (DataDecodeException $e) {
250254
$this->logger->debug('Closed stream with id(' . $streamIdentifier . ') because server sent invalid packet');
251255
$this->shutdownStream($streamIdentifier, 'invalid packet', false);
252256
return;
@@ -278,7 +282,7 @@ private function sendPayloadWithReceipt(int $streamIdentifier, string $payload,
278282
return;
279283
}
280284

281-
$writer->writeWithPromise(Binary::writeInt(strlen($payload)) . $payload)->onResult(function() use ($streamIdentifier, $receiptId): void{
285+
$writer->writeWithPromise(BE::packSignedInt(strlen($payload)) . $payload)->onResult(function () use ($streamIdentifier, $receiptId): void {
282286
$pk = new AckPacket();
283287
$pk->receiptId = $receiptId;
284288

@@ -296,7 +300,7 @@ private function sendPayload(int $streamIdentifier, string $payload): void
296300
return;
297301
}
298302

299-
$writer->write(Binary::writeInt(strlen($payload)) . $payload);
303+
$writer->write(BE::packSignedInt(strlen($payload)) . $payload);
300304
}
301305

302306
/**
@@ -323,26 +327,26 @@ private function getProtocolId(int $streamIdentifier): int
323327
*/
324328
private function sendDataPacket(int $streamIdentifier, BedrockPacket $packet): void
325329
{
326-
$packetSerializer = PacketSerializer::encoder($protocolId = $this->getProtocolId($streamIdentifier));
327-
$packet->encode($packetSerializer);
330+
$packetSerializer = new ByteBufferWriter();
331+
$packet->encode($packetSerializer, $protocolId = $this->getProtocolId($streamIdentifier));
328332

329-
$stream = new BinaryStream();
330-
PacketBatch::encodeRaw($stream, [$packetSerializer->getBuffer()]);
331-
$payload = ($protocolId >= ProtocolInfo::PROTOCOL_1_20_60 ? chr(CompressionAlgorithm::ZLIB) : '') . ZlibCompressor::getInstance()->compress($stream->getBuffer());
333+
$stream = new ByteBufferWriter();
334+
PacketBatch::encodeRaw($stream, [$packetSerializer->getData()]);
335+
$payload = ($protocolId >= ProtocolInfo::PROTOCOL_1_20_60 ? chr(CompressionAlgorithm::ZLIB) : '') . ZlibCompressor::getInstance()->compress($stream->getData());
332336

333337
$this->sendPayload($streamIdentifier, $payload);
334338
}
335339

336340
private function decodePacket(int $streamIdentifier, BedrockPacket $packet, string $buffer): void
337341
{
338-
$stream = PacketSerializer::decoder($this->protocolId[$streamIdentifier] ?? ProtocolInfo::CURRENT_PROTOCOL, $buffer, 0);
342+
$stream = new ByteBufferReader($buffer);
339343
try {
340-
$packet->decode($stream);
344+
$packet->decode($stream, $this->protocolId[$streamIdentifier] ?? ProtocolInfo::CURRENT_PROTOCOL);
341345
} catch (PacketDecodeException $e) {
342346
throw PacketHandlingException::wrap($e);
343347
}
344-
if (!$stream->feof()) {
345-
$remains = substr($stream->getBuffer(), $stream->getOffset());
348+
if ($stream->getUnreadLength() > 0) {
349+
$remains = substr($stream->getData(), $stream->getOffset());
346350
$this->logger->debug("Still " . strlen($remains) . " bytes unread in " . $packet->getName() . ": " . bin2hex($remains));
347351
}
348352
}
@@ -407,14 +411,18 @@ private function onFullDataReceive(int $streamIdentifier, string $payload): void
407411
throw PacketHandlingException::wrap($e, "Compressed packet batch decode error");
408412
}
409413

414+
$count = 0;
410415
try {
411-
$stream = new BinaryStream($decompressed);
412-
$count = 0;
416+
$stream = new ByteBufferReader($decompressed);
413417
foreach (PacketBatch::decodeRaw($stream) as $buffer) {
414-
$this->getGamePacketLimiter($streamIdentifier)->decrement();
415-
if (++$count > 100) {
416-
throw new PacketHandlingException("Too many packets in batch");
418+
if(++$count >= self::INCOMING_PACKET_BATCH_HARD_LIMIT){
419+
//this should be well more than enough; under normal conditions the game packet rate limiter
420+
//will kick in well before this. This is only here to make sure we can't get huge batches of
421+
//noisy packets to bog down the server, since those aren't counted by the regular limiter.
422+
throw new PacketHandlingException("Reached hard limit of " . self::INCOMING_PACKET_BATCH_HARD_LIMIT . " per batch packet");
417423
}
424+
425+
$this->getGamePacketLimiter($streamIdentifier)->decrement();
418426
$packet = PacketPool::getInstance()->getPacket($buffer);
419427
if ($packet === null) {
420428
$this->logger->debug("Unknown packet: " . base64_encode($buffer));
@@ -429,7 +437,7 @@ private function onFullDataReceive(int $streamIdentifier, string $payload): void
429437
throw PacketHandlingException::wrap($e, "Error processing " . $packet->getName());
430438
}
431439
}
432-
} catch (PacketDecodeException|BinaryDataException $e) {
440+
} catch (PacketDecodeException|DataDecodeException $e) {
433441
$this->logger->logException($e);
434442
throw PacketHandlingException::wrap($e, "Packet batch decode error");
435443
}
@@ -457,8 +465,8 @@ private function onDataReceive(int $streamIdentifier, string $data): void
457465
return; // wait for more data
458466
} else {
459467
try {
460-
$packetLength = Binary::readInt(substr($buffer, 0, 4));
461-
} catch (BinaryDataException $exception) {
468+
$packetLength = BE::unpackSignedInt(substr($buffer, 0, 4));
469+
} catch (DataDecodeException $exception) {
462470
$this->shutdownStream($streamIdentifier, 'invalid packet', false);
463471
return;
464472
}

composer.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
"type": "project",
66
"version": "dev-pm5",
77
"require": {
8-
"php": "^8.0",
9-
"ext-sockets": "*",
8+
"php": "^8.3",
9+
"ext-encoding": "~1.0.0",
1010
"ext-pmmpthread": "^6.0.1",
11+
"ext-sockets": "*",
1112
"nethergamesmc/quiche": "dev-master"
1213
},
1314
"require-dev": {
14-
"phpstan/phpstan": "2.1.1",
15+
"phpstan/phpstan": "2.1.29",
1516
"nethergamesmc/pocketmine-mp": "dev-stable"
1617
},
1718
"repositories": [

phpstan.neon.dist

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
parameters:
2+
ignoreErrors:
3+
- identifier: function.alreadyNarrowedType
24
level: max
3-
checkMissingIterableValueType: false
45
paths:
56
- .
67
excludePaths:

protocol/AckPacket.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,23 @@
55

66
namespace libproxy\protocol;
77

8+
use pmmp\encoding\ByteBufferReader;
9+
use pmmp\encoding\ByteBufferWriter;
10+
use pmmp\encoding\VarInt;
811

912
class AckPacket extends ProxyPacket
1013
{
11-
public const NETWORK_ID = ProxyProtocolInfo::ACK_PACKET;
14+
public const int NETWORK_ID = ProxyProtocolInfo::ACK_PACKET;
1215

1316
public int $receiptId;
1417

15-
public function encodePayload(ProxyPacketSerializer $out): void
18+
public function encodePayload(ByteBufferWriter $out): void
1619
{
17-
$out->putUnsignedVarInt($this->receiptId);
20+
VarInt::writeUnsignedInt($out, $this->receiptId);
1821
}
1922

20-
public function decodePayload(ProxyPacketSerializer $in): void
23+
public function decodePayload(ByteBufferReader $in): void
2124
{
22-
$this->receiptId = $in->getUnsignedVarInt();
25+
$this->receiptId = VarInt::readUnsignedInt($in);
2326
}
2427
}

0 commit comments

Comments
 (0)