From 1ad38d499c05de802376a701ff0211f483b3cd60 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 14 Jul 2021 14:26:32 +0100 Subject: [PATCH] Deglobalize ItemTypeDictionary usage, at least for the protocol while this is a bit hacky outside of the protocol namespace, it makes it much easier to use the protocol library for alternative purposes, such as for a client or MITM proxy. It also removes all but one remaining core dependency of the protocol library, making it very close to being able to be separated from the server core entirely. --- src/network/mcpe/ChunkRequestTask.php | 7 +++- src/network/mcpe/NetworkSession.php | 12 ++++-- .../mcpe/StandardPacketBroadcaster.php | 5 ++- .../mcpe/convert/RuntimeBlockMapping.php | 5 ++- .../mcpe/protocol/serializer/PacketBatch.php | 10 ++--- .../protocol/serializer/PacketSerializer.php | 30 ++++++++------ .../serializer/PacketSerializerContext.php | 39 +++++++++++++++++++ .../mcpe/serializer/ChunkSerializer.php | 5 ++- tests/phpstan/configs/l7-baseline.neon | 2 +- .../network/mcpe/protocol/DataPacketTest.php | 8 +++- .../network/mcpe/protocol/LoginPacketTest.php | 9 +++-- .../protocol/serializer/PacketBatchTest.php | 12 ++++-- 12 files changed, 108 insertions(+), 36 deletions(-) create mode 100644 src/network/mcpe/protocol/serializer/PacketSerializerContext.php diff --git a/src/network/mcpe/ChunkRequestTask.php b/src/network/mcpe/ChunkRequestTask.php index d90e0cd32..7395da728 100644 --- a/src/network/mcpe/ChunkRequestTask.php +++ b/src/network/mcpe/ChunkRequestTask.php @@ -25,9 +25,11 @@ namespace pocketmine\network\mcpe; use pocketmine\network\mcpe\compression\CompressBatchPromise; use pocketmine\network\mcpe\compression\Compressor; +use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary; use pocketmine\network\mcpe\convert\RuntimeBlockMapping; use pocketmine\network\mcpe\protocol\LevelChunkPacket; use pocketmine\network\mcpe\protocol\serializer\PacketBatch; +use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\network\mcpe\serializer\ChunkSerializer; use pocketmine\scheduler\AsyncTask; use pocketmine\world\format\Chunk; @@ -68,8 +70,9 @@ class ChunkRequestTask extends AsyncTask{ public function onRun() : void{ $chunk = FastChunkSerializer::deserialize($this->chunk); $subCount = ChunkSerializer::getSubChunkCount($chunk); - $payload = ChunkSerializer::serialize($chunk, RuntimeBlockMapping::getInstance(), $this->tiles); - $this->setResult($this->compressor->compress(PacketBatch::fromPackets(LevelChunkPacket::withoutCache($this->chunkX, $this->chunkZ, $subCount, $payload))->getBuffer())); + $encoderContext = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()); + $payload = ChunkSerializer::serialize($chunk, RuntimeBlockMapping::getInstance(), $encoderContext, $this->tiles); + $this->setResult($this->compressor->compress(PacketBatch::fromPackets($encoderContext, LevelChunkPacket::withoutCache($this->chunkX, $this->chunkZ, $subCount, $payload))->getBuffer())); } public function onError() : void{ diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index eab54915b..258e21ee9 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -42,6 +42,7 @@ use pocketmine\network\mcpe\cache\ChunkCache; use pocketmine\network\mcpe\compression\CompressBatchPromise; use pocketmine\network\mcpe\compression\Compressor; use pocketmine\network\mcpe\compression\DecompressionException; +use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary; use pocketmine\network\mcpe\convert\SkinAdapterSingleton; use pocketmine\network\mcpe\convert\TypeConverter; use pocketmine\network\mcpe\encryption\DecryptionException; @@ -74,6 +75,7 @@ use pocketmine\network\mcpe\protocol\PlayStatusPacket; use pocketmine\network\mcpe\protocol\RemoveActorPacket; use pocketmine\network\mcpe\protocol\serializer\PacketBatch; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\network\mcpe\protocol\ServerboundPacket; use pocketmine\network\mcpe\protocol\ServerToClientHandshakePacket; use pocketmine\network\mcpe\protocol\SetActorDataPacket; @@ -158,6 +160,7 @@ class NetworkSession{ private bool $forceAsyncCompression = true; private PacketPool $packetPool; + private PacketSerializerContext $packetSerializerContext; private ?InventoryManager $invManager = null; @@ -185,6 +188,9 @@ class NetworkSession{ $this->compressor = $compressor; $this->packetPool = $packetPool; + //TODO: allow this to be injected + $this->packetSerializerContext = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()); + $this->disposeHooks = new ObjectSet(); $this->connectTime = time(); @@ -335,7 +341,7 @@ class NetworkSession{ } try{ - foreach($stream->getPackets($this->packetPool, 500) as [$packet, $buffer]){ + foreach($stream->getPackets($this->packetPool, $this->packetSerializerContext, 500) as [$packet, $buffer]){ try{ $this->handleDataPacket($packet, $buffer); }catch(PacketHandlingException $e){ @@ -361,7 +367,7 @@ class NetworkSession{ $timings->startTiming(); try{ - $stream = new PacketSerializer($buffer); + $stream = PacketSerializer::decoder($buffer, 0, $this->packetSerializerContext); try{ $packet->decode($stream); }catch(PacketDecodeException $e){ @@ -430,7 +436,7 @@ class NetworkSession{ }elseif($this->forceAsyncCompression){ $syncMode = false; } - $promise = $this->server->prepareBatch(PacketBatch::fromPackets(...$this->sendBuffer), $this->compressor, $syncMode); + $promise = $this->server->prepareBatch(PacketBatch::fromPackets($this->packetSerializerContext, ...$this->sendBuffer), $this->compressor, $syncMode); $this->sendBuffer = []; $this->queueCompressedNoBufferFlush($promise, $immediate); } diff --git a/src/network/mcpe/StandardPacketBroadcaster.php b/src/network/mcpe/StandardPacketBroadcaster.php index 3638f04cb..1442a5bbf 100644 --- a/src/network/mcpe/StandardPacketBroadcaster.php +++ b/src/network/mcpe/StandardPacketBroadcaster.php @@ -24,7 +24,9 @@ declare(strict_types=1); namespace pocketmine\network\mcpe; use pocketmine\network\mcpe\compression\Compressor; +use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary; use pocketmine\network\mcpe\protocol\serializer\PacketBatch; +use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\Server; use function spl_object_id; @@ -38,7 +40,8 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{ } public function broadcastPackets(array $recipients, array $packets) : void{ - $stream = PacketBatch::fromPackets(...$packets); + //TODO: we should be using session-specific serializer contexts for this + $stream = PacketBatch::fromPackets(new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()), ...$packets); /** @var Compressor[] $compressors */ $compressors = []; diff --git a/src/network/mcpe/convert/RuntimeBlockMapping.php b/src/network/mcpe/convert/RuntimeBlockMapping.php index 95dbcb8fb..1185a02dd 100644 --- a/src/network/mcpe/convert/RuntimeBlockMapping.php +++ b/src/network/mcpe/convert/RuntimeBlockMapping.php @@ -29,6 +29,7 @@ use pocketmine\data\bedrock\LegacyBlockIdToStringIdMap; use pocketmine\nbt\tag\CompoundTag; use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\SingletonTrait; use Webmozart\PathUtil\Path; @@ -52,7 +53,7 @@ final class RuntimeBlockMapping{ if($canonicalBlockStatesFile === false){ throw new AssumptionFailedError("Missing required resource file"); } - $stream = new PacketSerializer($canonicalBlockStatesFile); + $stream = PacketSerializer::decoder($canonicalBlockStatesFile, 0, new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary())); $list = []; while(!$stream->feof()){ $list[] = $stream->getNbtCompoundRoot(); @@ -66,7 +67,7 @@ final class RuntimeBlockMapping{ $legacyIdMap = LegacyBlockIdToStringIdMap::getInstance(); /** @var R12ToCurrentBlockMapEntry[] $legacyStateMap */ $legacyStateMap = []; - $legacyStateMapReader = new PacketSerializer(file_get_contents(Path::join(\pocketmine\RESOURCE_PATH, "vanilla", "r12_to_current_block_map.bin"))); + $legacyStateMapReader = PacketSerializer::decoder(file_get_contents(Path::join(\pocketmine\RESOURCE_PATH, "vanilla", "r12_to_current_block_map.bin")), 0, new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary())); $nbtReader = new NetworkNbtSerializer(); while(!$legacyStateMapReader->feof()){ $id = $legacyStateMapReader->getString(); diff --git a/src/network/mcpe/protocol/serializer/PacketBatch.php b/src/network/mcpe/protocol/serializer/PacketBatch.php index 86b0a94d6..a1af98c35 100644 --- a/src/network/mcpe/protocol/serializer/PacketBatch.php +++ b/src/network/mcpe/protocol/serializer/PacketBatch.php @@ -42,8 +42,8 @@ class PacketBatch{ * @phpstan-return \Generator * @throws PacketDecodeException */ - public function getPackets(PacketPool $packetPool, int $max) : \Generator{ - $serializer = new PacketSerializer($this->buffer); + public function getPackets(PacketPool $packetPool, PacketSerializerContext $decoderContext, int $max) : \Generator{ + $serializer = PacketSerializer::decoder($this->buffer, 0, $decoderContext); for($c = 0; $c < $max and !$serializer->feof(); ++$c){ try{ $buffer = $serializer->getString(); @@ -64,10 +64,10 @@ class PacketBatch{ * * @return PacketBatch */ - public static function fromPackets(Packet ...$packets) : self{ - $serializer = new PacketSerializer(); + public static function fromPackets(PacketSerializerContext $context, Packet ...$packets) : self{ + $serializer = PacketSerializer::encoder($context); foreach($packets as $packet){ - $subSerializer = new PacketSerializer(); + $subSerializer = PacketSerializer::encoder($context); $packet->encode($subSerializer); $serializer->putString($subSerializer->getBuffer()); } diff --git a/src/network/mcpe/protocol/serializer/PacketSerializer.php b/src/network/mcpe/protocol/serializer/PacketSerializer.php index 1f2ad2278..9df467f1c 100644 --- a/src/network/mcpe/protocol/serializer/PacketSerializer.php +++ b/src/network/mcpe/protocol/serializer/PacketSerializer.php @@ -30,7 +30,6 @@ use pocketmine\nbt\LittleEndianNbtSerializer; use pocketmine\nbt\NbtDataException; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\TreeRoot; -use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary; use pocketmine\network\mcpe\protocol\PacketDecodeException; use pocketmine\network\mcpe\protocol\types\BoolGameRule; use pocketmine\network\mcpe\protocol\types\command\CommandOriginData; @@ -71,10 +70,20 @@ use function substr; class PacketSerializer extends BinaryStream{ private int $shieldItemRuntimeId; + private PacketSerializerContext $context; - public function __construct(string $buffer = "", int $offset = 0){ + protected function __construct(PacketSerializerContext $context, string $buffer = "", int $offset = 0){ parent::__construct($buffer, $offset); - $this->shieldItemRuntimeId = GlobalItemTypeDictionary::getInstance()->getDictionary()->fromStringId("minecraft:shield"); + $this->context = $context; + $this->shieldItemRuntimeId = $context->getItemDictionary()->fromStringId("minecraft:shield"); + } + + public static function encoder(PacketSerializerContext $context) : self{ + return new self($context); + } + + public static function decoder(string $buffer, int $offset, PacketSerializerContext $context) : self{ + return new self($context, $buffer, $offset); } /** @@ -248,9 +257,8 @@ class PacketSerializer extends BinaryStream{ $readExtraCrapInTheMiddle($this); $blockRuntimeId = $this->getVarInt(); - $extraData = new PacketSerializer($this->getString()); - $shieldItemRuntimeId = $this->shieldItemRuntimeId; - return (static function() use ($extraData, $id, $meta, $count, $blockRuntimeId, $shieldItemRuntimeId) : ItemStack{ + $extraData = self::decoder($this->getString(), 0, $this->context); + return (static function() use ($extraData, $id, $meta, $count, $blockRuntimeId) : ItemStack{ $nbtLen = $extraData->getLShort(); /** @var CompoundTag|null $compound */ @@ -283,7 +291,7 @@ class PacketSerializer extends BinaryStream{ } $shieldBlockingTick = null; - if($id === $shieldItemRuntimeId){ + if($id === $extraData->shieldItemRuntimeId){ $shieldBlockingTick = $extraData->getLLong(); } @@ -312,9 +320,9 @@ class PacketSerializer extends BinaryStream{ $writeExtraCrapInTheMiddle($this); $this->putVarInt($item->getBlockRuntimeId()); - $shieldItemRuntimeId = $this->shieldItemRuntimeId; - $this->putString((static function() use ($item, $shieldItemRuntimeId) : string{ - $extraData = new PacketSerializer(); + $context = $this->context; + $this->putString((static function() use ($item, $context) : string{ + $extraData = PacketSerializer::encoder($context); $nbt = $item->getNbt(); if($nbt !== null){ @@ -337,7 +345,7 @@ class PacketSerializer extends BinaryStream{ } $blockingTick = $item->getShieldBlockingTick(); - if($item->getId() === $shieldItemRuntimeId){ + if($item->getId() === $extraData->shieldItemRuntimeId){ $extraData->putLLong($blockingTick ?? 0); } return $extraData->getBuffer(); diff --git a/src/network/mcpe/protocol/serializer/PacketSerializerContext.php b/src/network/mcpe/protocol/serializer/PacketSerializerContext.php new file mode 100644 index 000000000..887adbc90 --- /dev/null +++ b/src/network/mcpe/protocol/serializer/PacketSerializerContext.php @@ -0,0 +1,39 @@ +itemDictionary = $itemDictionary; + } + + public function getItemDictionary() : ItemTypeDictionary{ return $this->itemDictionary; } +} diff --git a/src/network/mcpe/serializer/ChunkSerializer.php b/src/network/mcpe/serializer/ChunkSerializer.php index abf0e1b25..6408cd780 100644 --- a/src/network/mcpe/serializer/ChunkSerializer.php +++ b/src/network/mcpe/serializer/ChunkSerializer.php @@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe\serializer; use pocketmine\block\tile\Spawnable; use pocketmine\network\mcpe\convert\RuntimeBlockMapping; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\utils\Binary; use pocketmine\utils\BinaryStream; use pocketmine\world\format\Chunk; @@ -52,8 +53,8 @@ final class ChunkSerializer{ return 0; } - public static function serialize(Chunk $chunk, RuntimeBlockMapping $blockMapper, ?string $tiles = null) : string{ - $stream = new PacketSerializer(); + public static function serialize(Chunk $chunk, RuntimeBlockMapping $blockMapper, PacketSerializerContext $encoderContext, ?string $tiles = null) : string{ + $stream = PacketSerializer::encoder($encoderContext); $subChunkCount = self::getSubChunkCount($chunk); for($y = 0; $y < $subChunkCount; ++$y){ $layers = $chunk->getSubChunk($y)->getBlockLayers(); diff --git a/tests/phpstan/configs/l7-baseline.neon b/tests/phpstan/configs/l7-baseline.neon index a9eb81028..ff9421a41 100644 --- a/tests/phpstan/configs/l7-baseline.neon +++ b/tests/phpstan/configs/l7-baseline.neon @@ -591,7 +591,7 @@ parameters: path: ../../../src/network/mcpe/auth/ProcessLoginTask.php - - message: "#^Parameter \\#1 \\$buffer of class pocketmine\\\\network\\\\mcpe\\\\protocol\\\\serializer\\\\PacketSerializer constructor expects string, string\\|false given\\.$#" + message: "#^Parameter \\#1 \\$buffer of static method pocketmine\\\\network\\\\mcpe\\\\protocol\\\\serializer\\\\PacketSerializer\\:\\:decoder\\(\\) expects string, string\\|false given\\.$#" count: 1 path: ../../../src/network/mcpe/convert/RuntimeBlockMapping.php diff --git a/tests/phpunit/network/mcpe/protocol/DataPacketTest.php b/tests/phpunit/network/mcpe/protocol/DataPacketTest.php index 94bdcb83d..880be1e8f 100644 --- a/tests/phpunit/network/mcpe/protocol/DataPacketTest.php +++ b/tests/phpunit/network/mcpe/protocol/DataPacketTest.php @@ -24,7 +24,9 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\protocol; use PHPUnit\Framework\TestCase; +use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; class DataPacketTest extends TestCase{ @@ -32,11 +34,13 @@ class DataPacketTest extends TestCase{ $pk = new TestPacket(); $pk->senderSubId = 3; $pk->recipientSubId = 2; - $serializer = new PacketSerializer(); + + $context = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()); + $serializer = PacketSerializer::encoder($context); $pk->encode($serializer); $pk2 = new TestPacket(); - $pk2->decode(new PacketSerializer($serializer->getBuffer())); + $pk2->decode(PacketSerializer::decoder($serializer->getBuffer(), 0, $context)); self::assertSame($pk2->senderSubId, 3); self::assertSame($pk2->recipientSubId, 2); } diff --git a/tests/phpunit/network/mcpe/protocol/LoginPacketTest.php b/tests/phpunit/network/mcpe/protocol/LoginPacketTest.php index 7ddf34dbe..cdd20f840 100644 --- a/tests/phpunit/network/mcpe/protocol/LoginPacketTest.php +++ b/tests/phpunit/network/mcpe/protocol/LoginPacketTest.php @@ -24,18 +24,21 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\protocol; use PHPUnit\Framework\TestCase; +use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use function strlen; class LoginPacketTest extends TestCase{ public function testInvalidChainDataJsonHandling() : void{ - $stream = new PacketSerializer(); + $context = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()); + $stream = PacketSerializer::encoder($context); $stream->putUnsignedVarInt(ProtocolInfo::LOGIN_PACKET); $payload = '{"chain":[]'; //intentionally malformed $stream->putInt(ProtocolInfo::CURRENT_PROTOCOL); - $stream2 = new PacketSerializer(); + $stream2 = PacketSerializer::encoder($context); $stream2->putLInt(strlen($payload)); $stream2->put($payload); $stream->putString($stream2->getBuffer()); @@ -43,6 +46,6 @@ class LoginPacketTest extends TestCase{ $pk = PacketPool::getInstance()->getPacket($stream->getBuffer()); $this->expectException(PacketDecodeException::class); - $pk->decode(new PacketSerializer($stream->getBuffer())); //bang + $pk->decode(PacketSerializer::decoder($stream->getBuffer(), 0, $context)); //bang } } diff --git a/tests/phpunit/network/mcpe/protocol/serializer/PacketBatchTest.php b/tests/phpunit/network/mcpe/protocol/serializer/PacketBatchTest.php index 28d01dc8c..da90c0c0a 100644 --- a/tests/phpunit/network/mcpe/protocol/serializer/PacketBatchTest.php +++ b/tests/phpunit/network/mcpe/protocol/serializer/PacketBatchTest.php @@ -24,9 +24,11 @@ declare(strict_types=1); namespace pocketmine\mcpe\protocol\serializer; use PHPUnit\Framework\TestCase; +use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary; use pocketmine\network\mcpe\protocol\PacketDecodeException; use pocketmine\network\mcpe\protocol\PacketPool; use pocketmine\network\mcpe\protocol\serializer\PacketBatch; +use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\network\mcpe\protocol\TestPacket; use function array_fill; @@ -34,21 +36,23 @@ class PacketBatchTest extends TestCase{ public function testDecodeTooBig() : void{ $limit = 10; - $write = PacketBatch::fromPackets(...array_fill(0, $limit + 1, new TestPacket())); + $decoderContext = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()); + $write = PacketBatch::fromPackets($decoderContext, ...array_fill(0, $limit + 1, new TestPacket())); $read = new PacketBatch($write->getBuffer()); $this->expectException(PacketDecodeException::class); $readCount = 0; - foreach($read->getPackets(PacketPool::getInstance(), $limit) as $packet){ + foreach($read->getPackets(PacketPool::getInstance(), $decoderContext, $limit) as $packet){ $readCount++; } } public function testDecodeAtLimit() : void{ $limit = 10; - $write = PacketBatch::fromPackets(...array_fill(0, $limit, new TestPacket())); + $decoderContext = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()); + $write = PacketBatch::fromPackets($decoderContext, ...array_fill(0, $limit, new TestPacket())); $read = new PacketBatch($write->getBuffer()); $readCount = 0; - foreach($read->getPackets(PacketPool::getInstance(), $limit) as $packet){ + foreach($read->getPackets(PacketPool::getInstance(), $decoderContext, $limit) as $packet){ $readCount++; } self::assertSame($limit, $readCount);