Skip to content

Commit 850fec6

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

15 files changed

+163
-130
lines changed

.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: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
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;
22+
use pmmp\encoding\VarInt;
1923
use pmmp\thread\Thread as NativeThread;
2024
use pmmp\thread\ThreadSafeArray;
2125
use pocketmine\network\mcpe\compression\ZlibCompressor;
@@ -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,27 @@ 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+
if (($pk = ProxyPacketPool::getInstance()->getPacket($buffer)) === null) {
174+
throw new PacketHandlingException('Proxy packet with id (' . VarInt::unpackUnsignedInt($buffer) . ') does not exist');
175175
}
176176

177177
try {
178178
$pk->decode($stream);
179-
} catch (BinaryDataException $e) {
179+
} catch (DataDecodeException $e) {
180180
$this->server->getLogger()->debug('Closed socket with id(' . $socketId . ') because packet was invalid.');
181181
$this->close($socketId, 'Invalid Packet');
182182
return;
183183
}
184184

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

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

214+
if ((fn() => $this->checkRepeatedPacketFilter($buffer))->call($session)) {
215+
break;
216+
}
217+
214218
$packet = PacketPool::getInstance()->getPacket($pk->payload);
215219
if ($packet === null) {
216220
$session->getLogger()->debug("Unknown packet: " . base64_encode($pk->payload));
@@ -233,7 +237,7 @@ private function onPacketReceive(string $buffer): void
233237
$session->handleAckReceipt($pk->receiptId);
234238
break;
235239
}
236-
} catch (PacketHandlingException|BinaryDataException $exception) {
240+
} catch (PacketHandlingException|DataDecodeException $exception) {
237241
$this->close($socketId, 'Error handling a Packet (Server)');
238242

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

286290
public function putPacket(int $socketId, ProxyPacket $pk): void
287291
{
288-
$serializer = new ProxyPacketSerializer();
289-
$serializer->putLInt($socketId);
292+
$serializer = new ByteBufferWriter();
293+
LE::writeUnsignedInt($serializer, $socketId);
290294

291295
$pk->encode($serializer);
292296

293-
$this->mainToThreadWriter->write($serializer->getBuffer());
294-
$this->sendBytes += strlen($serializer->getBuffer());
297+
$this->mainToThreadWriter->write($serializer->getData());
298+
$this->sendBytes += strlen($serializer->getData());
295299

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

ProxyServer.php

Lines changed: 37 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,16 @@ 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+
if (($pk = ProxyPacketPool::getInstance()->getPacket($payload)) === null) {
244246
throw new PacketHandlingException('Packet does not exist');
245247
}
246248

247249
try {
248250
$pk->decode($stream);
249-
} catch (BinaryDataException $e) {
251+
} catch (DataDecodeException $e) {
250252
$this->logger->debug('Closed stream with id(' . $streamIdentifier . ') because server sent invalid packet');
251253
$this->shutdownStream($streamIdentifier, 'invalid packet', false);
252254
return;
@@ -278,7 +280,7 @@ private function sendPayloadWithReceipt(int $streamIdentifier, string $payload,
278280
return;
279281
}
280282

281-
$writer->writeWithPromise(Binary::writeInt(strlen($payload)) . $payload)->onResult(function() use ($streamIdentifier, $receiptId): void{
283+
$writer->writeWithPromise(BE::packSignedInt(strlen($payload)) . $payload)->onResult(function () use ($streamIdentifier, $receiptId): void {
282284
$pk = new AckPacket();
283285
$pk->receiptId = $receiptId;
284286

@@ -296,7 +298,7 @@ private function sendPayload(int $streamIdentifier, string $payload): void
296298
return;
297299
}
298300

299-
$writer->write(Binary::writeInt(strlen($payload)) . $payload);
301+
$writer->write(BE::packSignedInt(strlen($payload)) . $payload);
300302
}
301303

302304
/**
@@ -323,26 +325,26 @@ private function getProtocolId(int $streamIdentifier): int
323325
*/
324326
private function sendDataPacket(int $streamIdentifier, BedrockPacket $packet): void
325327
{
326-
$packetSerializer = PacketSerializer::encoder($protocolId = $this->getProtocolId($streamIdentifier));
327-
$packet->encode($packetSerializer);
328+
$packetSerializer = new ByteBufferWriter();
329+
$packet->encode($packetSerializer, $protocolId = $this->getProtocolId($streamIdentifier));
328330

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());
331+
$stream = new ByteBufferWriter();
332+
PacketBatch::encodeRaw($stream, [$packetSerializer->getData()]);
333+
$payload = ($protocolId >= ProtocolInfo::PROTOCOL_1_20_60 ? chr(CompressionAlgorithm::ZLIB) : '') . ZlibCompressor::getInstance()->compress($stream->getData());
332334

333335
$this->sendPayload($streamIdentifier, $payload);
334336
}
335337

336338
private function decodePacket(int $streamIdentifier, BedrockPacket $packet, string $buffer): void
337339
{
338-
$stream = PacketSerializer::decoder($this->protocolId[$streamIdentifier] ?? ProtocolInfo::CURRENT_PROTOCOL, $buffer, 0);
340+
$stream = new ByteBufferReader($buffer);
339341
try {
340-
$packet->decode($stream);
342+
$packet->decode($stream, $this->protocolId[$streamIdentifier] ?? ProtocolInfo::CURRENT_PROTOCOL);
341343
} catch (PacketDecodeException $e) {
342344
throw PacketHandlingException::wrap($e);
343345
}
344-
if (!$stream->feof()) {
345-
$remains = substr($stream->getBuffer(), $stream->getOffset());
346+
if ($stream->getUnreadLength() > 0) {
347+
$remains = substr($stream->getData(), $stream->getOffset());
346348
$this->logger->debug("Still " . strlen($remains) . " bytes unread in " . $packet->getName() . ": " . bin2hex($remains));
347349
}
348350
}
@@ -407,14 +409,18 @@ private function onFullDataReceive(int $streamIdentifier, string $payload): void
407409
throw PacketHandlingException::wrap($e, "Compressed packet batch decode error");
408410
}
409411

412+
$count = 0;
410413
try {
411-
$stream = new BinaryStream($decompressed);
412-
$count = 0;
414+
$stream = new ByteBufferReader($decompressed);
413415
foreach (PacketBatch::decodeRaw($stream) as $buffer) {
414-
$this->getGamePacketLimiter($streamIdentifier)->decrement();
415-
if (++$count > 100) {
416-
throw new PacketHandlingException("Too many packets in batch");
416+
if(++$count >= self::INCOMING_PACKET_BATCH_HARD_LIMIT){
417+
//this should be well more than enough; under normal conditions the game packet rate limiter
418+
//will kick in well before this. This is only here to make sure we can't get huge batches of
419+
//noisy packets to bog down the server, since those aren't counted by the regular limiter.
420+
throw new PacketHandlingException("Reached hard limit of " . self::INCOMING_PACKET_BATCH_HARD_LIMIT . " per batch packet");
417421
}
422+
423+
$this->getGamePacketLimiter($streamIdentifier)->decrement();
418424
$packet = PacketPool::getInstance()->getPacket($buffer);
419425
if ($packet === null) {
420426
$this->logger->debug("Unknown packet: " . base64_encode($buffer));
@@ -429,7 +435,7 @@ private function onFullDataReceive(int $streamIdentifier, string $payload): void
429435
throw PacketHandlingException::wrap($e, "Error processing " . $packet->getName());
430436
}
431437
}
432-
} catch (PacketDecodeException|BinaryDataException $e) {
438+
} catch (PacketDecodeException|DataDecodeException $e) {
433439
$this->logger->logException($e);
434440
throw PacketHandlingException::wrap($e, "Packet batch decode error");
435441
}
@@ -457,8 +463,8 @@ private function onDataReceive(int $streamIdentifier, string $data): void
457463
return; // wait for more data
458464
} else {
459465
try {
460-
$packetLength = Binary::readInt(substr($buffer, 0, 4));
461-
} catch (BinaryDataException $exception) {
466+
$packetLength = BE::unpackSignedInt(substr($buffer, 0, 4));
467+
} catch (DataDecodeException $exception) {
462468
$this->shutdownStream($streamIdentifier, 'invalid packet', false);
463469
return;
464470
}

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)