diff --git a/src/Server.php b/src/Server.php index 9492aa88f0..5b590ccb1a 100644 --- a/src/Server.php +++ b/src/Server.php @@ -48,7 +48,7 @@ use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\TreeRoot; use pocketmine\network\mcpe\compression\CompressBatchPromise; use pocketmine\network\mcpe\compression\CompressBatchTask; -use pocketmine\network\mcpe\compression\Zlib as ZlibNetworkCompression; +use pocketmine\network\mcpe\compression\ZlibCompressor as ZlibNetworkCompression; use pocketmine\network\mcpe\encryption\NetworkCipher; use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\protocol\serializer\PacketBatch; @@ -912,17 +912,18 @@ class Server{ $this->asyncPool = new AsyncPool($poolSize, max(-1, (int) $this->getProperty("memory.async-worker-hard-limit", 256)), $this->autoloader, $this->logger); + $netCompressionThreshold = -1; if($this->getProperty("network.batch-threshold", 256) >= 0){ - ZlibNetworkCompression::$THRESHOLD = (int) $this->getProperty("network.batch-threshold", 256); - }else{ - ZlibNetworkCompression::$THRESHOLD = -1; + $netCompressionThreshold = (int) $this->getProperty("network.batch-threshold", 256); } - ZlibNetworkCompression::$LEVEL = $this->getProperty("network.compression-level", 7); - if(ZlibNetworkCompression::$LEVEL < 1 or ZlibNetworkCompression::$LEVEL > 9){ - $this->logger->warning("Invalid network compression level " . ZlibNetworkCompression::$LEVEL . " set, setting to default 7"); - ZlibNetworkCompression::$LEVEL = 7; + $netCompressionLevel = $this->getProperty("network.compression-level", 7); + if($netCompressionLevel < 1 or $netCompressionLevel > 9){ + $this->logger->warning("Invalid network compression level $netCompressionLevel set, setting to default 7"); + $netCompressionLevel = 7; } + ZlibNetworkCompression::setInstance(new ZlibNetworkCompression($netCompressionLevel, $netCompressionThreshold, ZlibNetworkCompression::DEFAULT_MAX_DECOMPRESSION_SIZE)); + $this->networkCompressionAsync = (bool) $this->getProperty("network.async-compression", true); NetworkCipher::$ENABLED = (bool) $this->getProperty("network.enable-encryption", true); @@ -1251,7 +1252,7 @@ class Server{ $stream = PacketBatch::fromPackets(...$ev->getPackets()); - if(ZlibNetworkCompression::$THRESHOLD < 0 or strlen($stream->getBuffer()) < ZlibNetworkCompression::$THRESHOLD){ + if(!ZlibNetworkCompression::getInstance()->willCompress($stream->getBuffer())){ foreach($recipients as $target){ foreach($ev->getPackets() as $pk){ $target->addToSendBuffer($pk); @@ -1274,19 +1275,18 @@ class Server{ try{ Timings::$playerNetworkSendCompressTimer->startTiming(); - $compressionLevel = ZlibNetworkCompression::$LEVEL; + $compressor = ZlibNetworkCompression::getInstance(); $buffer = $stream->getBuffer(); - if(ZlibNetworkCompression::$THRESHOLD < 0 or strlen($buffer) < ZlibNetworkCompression::$THRESHOLD){ - $compressionLevel = 0; //Do not compress packets under the threshold + if(!$compressor->willCompress($buffer)){ $forceSync = true; } $promise = new CompressBatchPromise(); if(!$forceSync and $this->networkCompressionAsync){ - $task = new CompressBatchTask($buffer, $compressionLevel, $promise); + $task = new CompressBatchTask($buffer, $promise, $compressor); $this->asyncPool->submitTask($task); }else{ - $promise->resolve(ZlibNetworkCompression::compress($buffer, $compressionLevel)); + $promise->resolve($compressor->compress($buffer)); } return $promise; diff --git a/src/crafting/CraftingManager.php b/src/crafting/CraftingManager.php index 2301783cf2..4abb8bc9b0 100644 --- a/src/crafting/CraftingManager.php +++ b/src/crafting/CraftingManager.php @@ -25,7 +25,7 @@ namespace pocketmine\crafting; use pocketmine\item\Item; use pocketmine\network\mcpe\compression\CompressBatchPromise; -use pocketmine\network\mcpe\compression\Zlib; +use pocketmine\network\mcpe\compression\ZlibCompressor; use pocketmine\network\mcpe\convert\TypeConverter; use pocketmine\network\mcpe\protocol\serializer\PacketBatch; use pocketmine\network\mcpe\protocol\CraftingDataPacket; @@ -165,7 +165,7 @@ class CraftingManager{ } $this->craftingDataCache = new CompressBatchPromise(); - $this->craftingDataCache->resolve(Zlib::compress(PacketBatch::fromPackets($pk)->getBuffer())); + $this->craftingDataCache->resolve(ZlibCompressor::getInstance()->compress(PacketBatch::fromPackets($pk)->getBuffer())); Timings::$craftingDataCacheRebuildTimer->stopTiming(); } diff --git a/src/network/mcpe/ChunkRequestTask.php b/src/network/mcpe/ChunkRequestTask.php index 75274e177a..25e10bda97 100644 --- a/src/network/mcpe/ChunkRequestTask.php +++ b/src/network/mcpe/ChunkRequestTask.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace pocketmine\network\mcpe; use pocketmine\network\mcpe\compression\CompressBatchPromise; -use pocketmine\network\mcpe\compression\Zlib; +use pocketmine\network\mcpe\compression\ZlibCompressor; use pocketmine\network\mcpe\protocol\LevelChunkPacket; use pocketmine\network\mcpe\protocol\serializer\PacketBatch; use pocketmine\network\mcpe\serializer\ChunkSerializer; @@ -43,8 +43,8 @@ class ChunkRequestTask extends AsyncTask{ /** @var int */ protected $chunkZ; - /** @var int */ - protected $compressionLevel; + /** @var ZlibCompressor */ + protected $compressor; /** @var string */ private $tiles = ""; @@ -53,7 +53,7 @@ class ChunkRequestTask extends AsyncTask{ * @phpstan-param (\Closure() : void)|null $onError */ public function __construct(int $chunkX, int $chunkZ, Chunk $chunk, CompressBatchPromise $promise, ?\Closure $onError = null){ - $this->compressionLevel = Zlib::$LEVEL; + $this->compressor = ZlibCompressor::getInstance(); //TODO: this should be injectable $this->chunk = FastChunkSerializer::serializeWithoutLight($chunk); $this->chunkX = $chunkX; @@ -68,7 +68,7 @@ class ChunkRequestTask extends AsyncTask{ $chunk = FastChunkSerializer::deserialize($this->chunk); $subCount = ChunkSerializer::getSubChunkCount($chunk); $payload = ChunkSerializer::serialize($chunk, $this->tiles); - $this->setResult(Zlib::compress(PacketBatch::fromPackets(LevelChunkPacket::withoutCache($this->chunkX, $this->chunkZ, $subCount, $payload))->getBuffer(), $this->compressionLevel)); + $this->setResult($this->compressor->compress(PacketBatch::fromPackets(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 5b4a069555..dec765ac1f 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -36,7 +36,7 @@ use pocketmine\math\Vector3; use pocketmine\network\BadPacketException; use pocketmine\network\mcpe\compression\CompressBatchPromise; use pocketmine\network\mcpe\compression\DecompressionException; -use pocketmine\network\mcpe\compression\Zlib; +use pocketmine\network\mcpe\compression\ZlibCompressor; use pocketmine\network\mcpe\convert\SkinAdapterSingleton; use pocketmine\network\mcpe\convert\TypeConverter; use pocketmine\network\mcpe\encryption\DecryptionException; @@ -283,7 +283,7 @@ class NetworkSession{ Timings::$playerNetworkReceiveDecompressTimer->startTiming(); try{ - $stream = new PacketBatch(Zlib::decompress($payload)); + $stream = new PacketBatch(ZlibCompressor::getInstance()->decompress($payload)); //TODO: make this dynamic }catch(DecompressionException $e){ $this->logger->debug("Failed to decompress packet: " . base64_encode($payload)); //TODO: this isn't incompatible game version if we already established protocol version diff --git a/src/network/mcpe/compression/CompressBatchTask.php b/src/network/mcpe/compression/CompressBatchTask.php index e4012d8e0a..05ce2ce2d3 100644 --- a/src/network/mcpe/compression/CompressBatchTask.php +++ b/src/network/mcpe/compression/CompressBatchTask.php @@ -29,19 +29,19 @@ class CompressBatchTask extends AsyncTask{ private const TLS_KEY_PROMISE = "promise"; - /** @var int */ - private $level; /** @var string */ private $data; + /** @var ZlibCompressor */ + private $compressor; - public function __construct(string $data, int $compressionLevel, CompressBatchPromise $promise){ + public function __construct(string $data, CompressBatchPromise $promise, ZlibCompressor $compressor){ $this->data = $data; - $this->level = $compressionLevel; + $this->compressor = $compressor; $this->storeLocal(self::TLS_KEY_PROMISE, $promise); } public function onRun() : void{ - $this->setResult(Zlib::compress($this->data, $this->level)); + $this->setResult($this->compressor->compress($this->data)); } public function onCompletion() : void{ diff --git a/src/network/mcpe/compression/Zlib.php b/src/network/mcpe/compression/Zlib.php deleted file mode 100644 index 867996f15d..0000000000 --- a/src/network/mcpe/compression/Zlib.php +++ /dev/null @@ -1,59 +0,0 @@ -level = $level; + $this->threshold = $minCompressionSize; + $this->maxDecompressionSize = $maxDecompressionSize; + } + + public function willCompress(string $data) : bool{ + return $this->threshold > -1 and strlen($data) >= $this->threshold; + } + + /** + * @throws DecompressionException + */ + public function decompress(string $payload) : string{ + $result = @zlib_decode($payload, $this->maxDecompressionSize); + if($result === false){ + throw new DecompressionException("Failed to decompress data"); + } + return $result; + } + + public function compress(string $payload) : string{ + return zlib_encode($payload, ZLIB_ENCODING_DEFLATE, $this->willCompress($payload) ? $this->level : 0); + } +} diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index fcfec4efa8..75cc5b1fce 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -70,6 +70,11 @@ parameters: count: 1 path: ../../../src/network/mcpe/protocol/types/entity/EntityMetadataCollection.php + - + message: "#^Class pocketmine\\\\network\\\\mcpe\\\\compression\\\\ZlibCompressor constructor invoked with 0 parameters, 3 required\\.$#" + count: 1 + path: ../../../src/network/mcpe/compression/ZlibCompressor.php + - message: "#^Strict comparison using \\!\\=\\= between string and false will always evaluate to true\\.$#" count: 1