Skip to content

Commit b2fbcf3

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

13 files changed

+144
-110
lines changed

.github/workflows/phpstan.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ 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
22+
run: tar -xzvf PHP-8.4-Linux-x86_64-PM5.tar.gz
2323
- name: Install libFFI
2424
run: sudo apt install libffi7
2525
- name: Download Composer

ProxyNetworkInterface.php

Lines changed: 22 additions & 16 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;
@@ -163,27 +165,27 @@ public function start(): void
163165

164166
/**
165167
* @throws PacketHandlingException
168+
* @throws DataDecodeException
166169
*/
167170
private function onPacketReceive(string $buffer): void
168171
{
169-
$stream = new ProxyPacketSerializer($buffer);
170-
$socketId = $stream->getLInt();
172+
$stream = new ByteBufferReader($buffer);
173+
$socketId = LE::readUnsignedInt($stream);
171174

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');
175+
if (($pk = ProxyPacketPool::getInstance()->getPacket($buffer)) === null) {
176+
throw new PacketHandlingException('Proxy packet with id (' . VarInt::unpackUnsignedInt($buffer) . ') does not exist');
175177
}
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 ($session?->checkRepeatedPacketFilter($buffer)) {
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));
@@ -233,7 +239,7 @@ private function onPacketReceive(string $buffer): void
233239
$session->handleAckReceipt($pk->receiptId);
234240
break;
235241
}
236-
} catch (PacketHandlingException|BinaryDataException $exception) {
242+
} catch (PacketHandlingException|DataDecodeException $exception) {
237243
$this->close($socketId, 'Error handling a Packet (Server)');
238244

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

286292
public function putPacket(int $socketId, ProxyPacket $pk): void
287293
{
288-
$serializer = new ProxyPacketSerializer();
289-
$serializer->putLInt($socketId);
294+
$serializer = new ByteBufferWriter();
295+
LE::writeUnsignedInt($serializer, $socketId);
290296

291297
$pk->encode($serializer);
292298

293-
$this->mainToThreadWriter->write($serializer->getBuffer());
294-
$this->sendBytes += strlen($serializer->getBuffer());
299+
$this->mainToThreadWriter->write($serializer->getData());
300+
$this->sendBytes += strlen($serializer->getData());
295301

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

ProxyServer.php

Lines changed: 36 additions & 30 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+
$stream = new ByteBufferWriter();
332+
PacketBatch::encodeRaw($stream, [$packetSerializer->getData()]);
331333
$payload = ($protocolId >= ProtocolInfo::PROTOCOL_1_20_60 ? chr(CompressionAlgorithm::ZLIB) : '') . ZlibCompressor::getInstance()->compress($stream->getBuffer());
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: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
"version": "dev-pm5",
77
"require": {
88
"php": "^8.0",
9-
"ext-sockets": "*",
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": [

protocol/AckPacket.php

Lines changed: 7 additions & 4 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
{
1114
public const 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
}

protocol/CommonTypes.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace libproxy\protocol;
6+
7+
use pmmp\encoding\ByteBufferReader;
8+
use pmmp\encoding\ByteBufferWriter;
9+
use pmmp\encoding\DataDecodeException;
10+
use pmmp\encoding\LE;
11+
use function strlen;
12+
13+
class CommonTypes
14+
{
15+
/**
16+
* @throws DataDecodeException
17+
*/
18+
public static function getIp(ByteBufferReader $in): string
19+
{
20+
return $in->readByteArray(LE::readUnsignedShort($in));
21+
}
22+
23+
public static function putIp(ByteBufferWriter $out, string $ip): void
24+
{
25+
LE::writeUnsignedShort($out, strlen($ip));
26+
$out->writeByteArray($ip);
27+
}
28+
}

protocol/DisconnectPacket.php

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

66
namespace libproxy\protocol;
77

8+
use pmmp\encoding\ByteBufferReader;
9+
use pmmp\encoding\ByteBufferWriter;
810

911
class DisconnectPacket extends ProxyPacket
1012
{
@@ -13,13 +15,13 @@ class DisconnectPacket extends ProxyPacket
1315
/** @var string */
1416
public string $reason;
1517

16-
public function encodePayload(ProxyPacketSerializer $out): void
18+
public function encodePayload(ByteBufferWriter $out): void
1719
{
18-
$out->put($this->reason);
20+
$out->writeByteArray($this->reason);
1921
}
2022

21-
public function decodePayload(ProxyPacketSerializer $in): void
23+
public function decodePayload(ByteBufferReader $in): void
2224
{
23-
$this->reason = $in->getRemaining();
25+
$this->reason = $in->readByteArray($in->getUnreadLength());
2426
}
2527
}

0 commit comments

Comments
 (0)