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.
This commit is contained in:
Dylan K. Taylor 2021-07-14 14:26:32 +01:00
parent 0c2917f2aa
commit 1ad38d499c
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
12 changed files with 108 additions and 36 deletions

View File

@ -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{

View File

@ -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);
}

View File

@ -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 = [];

View File

@ -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();

View File

@ -42,8 +42,8 @@ class PacketBatch{
* @phpstan-return \Generator<int, array{Packet, string}, void, void>
* @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());
}

View File

@ -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();

View File

@ -0,0 +1,39 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol\serializer;
/**
* Contains information for a packet serializer specific to a given game session needed for packet encoding and decoding,
* such as a dictionary of item runtime IDs to their internal string IDs.
*/
final class PacketSerializerContext{
private ItemTypeDictionary $itemDictionary;
public function __construct(ItemTypeDictionary $itemDictionary){
$this->itemDictionary = $itemDictionary;
}
public function getItemDictionary() : ItemTypeDictionary{ return $this->itemDictionary; }
}

View File

@ -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();

View File

@ -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

View File

@ -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);
}

View File

@ -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
}
}

View File

@ -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);