mirror of
https://github.com/xPaw/PHP-Source-Query.git
synced 2026-08-02 04:03:10 +02:00
PHPStan max level (improved error handling).
This commit is contained in:
@@ -113,6 +113,10 @@ final class Buffer
|
|||||||
|
|
||||||
$data = unpack('v', $this->get(2));
|
$data = unpack('v', $this->get(2));
|
||||||
|
|
||||||
|
if (!$data) {
|
||||||
|
throw new InvalidPacketException('Empty data from packet.');
|
||||||
|
}
|
||||||
|
|
||||||
return (int) $data[1];
|
return (int) $data[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,6 +133,10 @@ final class Buffer
|
|||||||
|
|
||||||
$data = unpack('l', $this->get(4));
|
$data = unpack('l', $this->get(4));
|
||||||
|
|
||||||
|
if (!$data) {
|
||||||
|
throw new InvalidPacketException('Empty data from packet.');
|
||||||
|
}
|
||||||
|
|
||||||
return (int) $data[1];
|
return (int) $data[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,6 +153,10 @@ final class Buffer
|
|||||||
|
|
||||||
$data = unpack('f', $this->get(4));
|
$data = unpack('f', $this->get(4));
|
||||||
|
|
||||||
|
if (!$data) {
|
||||||
|
throw new InvalidPacketException('Empty data from packet.');
|
||||||
|
}
|
||||||
|
|
||||||
return (float) $data[1];
|
return (float) $data[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,6 +173,10 @@ final class Buffer
|
|||||||
|
|
||||||
$data = unpack('V', $this->get(4));
|
$data = unpack('V', $this->get(4));
|
||||||
|
|
||||||
|
if (!$data) {
|
||||||
|
throw new InvalidPacketException('Empty data from packet.');
|
||||||
|
}
|
||||||
|
|
||||||
return (int) $data[1];
|
return (int) $data[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -173,8 +173,18 @@ final class SourceRcon extends AbstractRcon
|
|||||||
*/
|
*/
|
||||||
protected function read(): Buffer
|
protected function read(): Buffer
|
||||||
{
|
{
|
||||||
|
if (!$this->rconSocket) {
|
||||||
|
throw new InvalidPacketException('Rcon socket not open.');
|
||||||
|
}
|
||||||
|
|
||||||
$buffer = new Buffer();
|
$buffer = new Buffer();
|
||||||
$buffer->set(fread($this->rconSocket, 4));
|
$socketData = fread($this->rconSocket, 4);
|
||||||
|
|
||||||
|
if (!$socketData) {
|
||||||
|
throw new InvalidPacketException('Empty data from packet.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$buffer->set($socketData);
|
||||||
|
|
||||||
if ($buffer->remaining() < 4) {
|
if ($buffer->remaining() < 4) {
|
||||||
throw new InvalidPacketException('Rcon read: Failed to read any data from socket', InvalidPacketException::BUFFER_EMPTY);
|
throw new InvalidPacketException('Rcon read: Failed to read any data from socket', InvalidPacketException::BUFFER_EMPTY);
|
||||||
@@ -182,7 +192,13 @@ final class SourceRcon extends AbstractRcon
|
|||||||
|
|
||||||
$packetSize = $buffer->getLong();
|
$packetSize = $buffer->getLong();
|
||||||
|
|
||||||
$buffer->set(fread($this->rconSocket, $packetSize));
|
$socketData = fread($this->rconSocket, $packetSize);
|
||||||
|
|
||||||
|
if (!$socketData) {
|
||||||
|
throw new InvalidPacketException('Empty data from packet.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$buffer->set($socketData);
|
||||||
|
|
||||||
$data = $buffer->get();
|
$data = $buffer->get();
|
||||||
|
|
||||||
@@ -191,9 +207,13 @@ final class SourceRcon extends AbstractRcon
|
|||||||
while ($remaining > 0) {
|
while ($remaining > 0) {
|
||||||
$data2 = fread($this->rconSocket, $remaining);
|
$data2 = fread($this->rconSocket, $remaining);
|
||||||
|
|
||||||
|
if (!$data2) {
|
||||||
|
throw new InvalidPacketException('Empty data from packet.');
|
||||||
|
}
|
||||||
|
|
||||||
$packetSize = strlen($data2);
|
$packetSize = strlen($data2);
|
||||||
|
|
||||||
if ($packetSize === 0) {
|
if ($packetSize <= 0) {
|
||||||
throw new InvalidPacketException('Read ' . strlen($data) . ' bytes from socket, ' . $remaining . ' remaining', InvalidPacketException::BUFFER_EMPTY);
|
throw new InvalidPacketException('Read ' . strlen($data) . ' bytes from socket, ' . $remaining . ' remaining', InvalidPacketException::BUFFER_EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,9 +231,15 @@ final class SourceRcon extends AbstractRcon
|
|||||||
* @param string $string
|
* @param string $string
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
|
*
|
||||||
|
* @throws InvalidPacketException
|
||||||
*/
|
*/
|
||||||
protected function write(?int $header, string $string = ''): bool
|
protected function write(?int $header, string $string = ''): bool
|
||||||
{
|
{
|
||||||
|
if (!$this->rconSocket) {
|
||||||
|
throw new InvalidPacketException('Rcon socket not open.');
|
||||||
|
}
|
||||||
|
|
||||||
// Pack the packet together.
|
// Pack the packet together.
|
||||||
$command = pack('VV', ++$this->rconRequestId, $header) . $string . "\x00\x00";
|
$command = pack('VV', ++$this->rconRequestId, $header) . $string . "\x00\x00";
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,10 @@ abstract class AbstractSocket implements SocketInterface
|
|||||||
*/
|
*/
|
||||||
public function read(int $length = 1400): Buffer
|
public function read(int $length = 1400): Buffer
|
||||||
{
|
{
|
||||||
|
if (!$this->socket) {
|
||||||
|
throw new InvalidPacketException('Socket not open.');
|
||||||
|
}
|
||||||
|
|
||||||
$buffer = new Buffer();
|
$buffer = new Buffer();
|
||||||
$data = fread($this->socket, $length);
|
$data = fread($this->socket, $length);
|
||||||
|
|
||||||
@@ -156,9 +160,15 @@ abstract class AbstractSocket implements SocketInterface
|
|||||||
* @param string $string
|
* @param string $string
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
|
*
|
||||||
|
* @throws InvalidPacketException
|
||||||
*/
|
*/
|
||||||
public function write(int $header, string $string = ''): bool
|
public function write(int $header, string $string = ''): bool
|
||||||
{
|
{
|
||||||
|
if (!$this->socket) {
|
||||||
|
throw new InvalidPacketException('Socket not open.');
|
||||||
|
}
|
||||||
|
|
||||||
$command = pack('ccccca*', 0xFF, 0xFF, 0xFF, 0xFF, $header, $string);
|
$command = pack('ccccca*', 0xFF, 0xFF, 0xFF, 0xFF, $header, $string);
|
||||||
$length = strlen($command);
|
$length = strlen($command);
|
||||||
|
|
||||||
@@ -175,8 +185,16 @@ abstract class AbstractSocket implements SocketInterface
|
|||||||
*/
|
*/
|
||||||
public function sherlock(Buffer $buffer, int $length): bool
|
public function sherlock(Buffer $buffer, int $length): bool
|
||||||
{
|
{
|
||||||
|
if (!$this->socket) {
|
||||||
|
throw new InvalidPacketException('Socket not open.');
|
||||||
|
}
|
||||||
|
|
||||||
$data = fread($this->socket, $length);
|
$data = fread($this->socket, $length);
|
||||||
|
|
||||||
|
if (!$data) {
|
||||||
|
throw new InvalidPacketException('Empty data from packet.');
|
||||||
|
}
|
||||||
|
|
||||||
if (strlen($data) < 4) {
|
if (strlen($data) < 4) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
+63
-63
@@ -127,22 +127,6 @@ final class SourceQuery
|
|||||||
$this->connected = true;
|
$this->connected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Forces GetChallenge to use old method for challenge retrieval because some games use outdated protocol (e.g Starbound)
|
|
||||||
*
|
|
||||||
* @param bool $value Set to true to force old method
|
|
||||||
*
|
|
||||||
* @return bool Previous value
|
|
||||||
*/
|
|
||||||
public function SetUseOldGetChallengeMethod(bool $value): bool
|
|
||||||
{
|
|
||||||
$previous = $this->useOldGetChallengeMethod;
|
|
||||||
|
|
||||||
$this->useOldGetChallengeMethod = $value === true;
|
|
||||||
|
|
||||||
return $previous;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes all open connections
|
* Closes all open connections
|
||||||
*/
|
*/
|
||||||
@@ -160,6 +144,22 @@ final class SourceQuery
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces GetChallenge to use old method for challenge retrieval because some games use outdated protocol (e.g Starbound)
|
||||||
|
*
|
||||||
|
* @param bool $value Set to true to force old method
|
||||||
|
*
|
||||||
|
* @return bool Previous value
|
||||||
|
*/
|
||||||
|
public function SetUseOldGetChallengeMethod(bool $value): bool
|
||||||
|
{
|
||||||
|
$previous = $this->useOldGetChallengeMethod;
|
||||||
|
|
||||||
|
$this->useOldGetChallengeMethod = $value === true;
|
||||||
|
|
||||||
|
return $previous;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends ping packet to the server
|
* Sends ping packet to the server
|
||||||
* NOTE: This may not work on some games (TF2 for example)
|
* NOTE: This may not work on some games (TF2 for example)
|
||||||
@@ -279,7 +279,7 @@ final class SourceQuery
|
|||||||
$server[ 'Version' ] = $buffer->getString();
|
$server[ 'Version' ] = $buffer->getString();
|
||||||
|
|
||||||
// Extra Data Flags.
|
// Extra Data Flags.
|
||||||
if (!$buffer->isEmpty()) {
|
if ($buffer->remaining() > 0) {
|
||||||
$server[ 'ExtraDataFlags' ] = $Flags = $buffer->getByte();
|
$server[ 'ExtraDataFlags' ] = $Flags = $buffer->getByte();
|
||||||
|
|
||||||
// S2A_EXTRA_DATA_HAS_GAME_PORT - Next 2 bytes include the game port.
|
// S2A_EXTRA_DATA_HAS_GAME_PORT - Next 2 bytes include the game port.
|
||||||
@@ -421,52 +421,6 @@ final class SourceQuery
|
|||||||
return $rules;
|
return $rules;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get challenge (used for players/rules packets)
|
|
||||||
*
|
|
||||||
* @param int $header
|
|
||||||
* @param int $expectedResult
|
|
||||||
*
|
|
||||||
* @throws InvalidPacketException
|
|
||||||
*/
|
|
||||||
private function getChallenge(int $header, int $expectedResult): void
|
|
||||||
{
|
|
||||||
if ($this->challenge) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->useOldGetChallengeMethod) {
|
|
||||||
$header = self::A2S_SERVERQUERY_GETCHALLENGE;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->socket->write($header, "\xFF\xFF\xFF\xFF");
|
|
||||||
$buffer = $this->socket->read();
|
|
||||||
|
|
||||||
$type = $buffer->getByte();
|
|
||||||
|
|
||||||
switch ($type) {
|
|
||||||
case self::S2C_CHALLENGE:
|
|
||||||
{
|
|
||||||
$this->challenge = $buffer->get(4);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case $expectedResult:
|
|
||||||
{
|
|
||||||
// Goldsource (HLTV).
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case 0:
|
|
||||||
{
|
|
||||||
throw new InvalidPacketException('GetChallenge: Failed to get challenge.');
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
throw new InvalidPacketException('GetChallenge: Packet header mismatch. (0x' . dechex($type) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets rcon password, for future use in Rcon()
|
* Sets rcon password, for future use in Rcon()
|
||||||
*
|
*
|
||||||
@@ -528,4 +482,50 @@ final class SourceQuery
|
|||||||
|
|
||||||
return $this->rcon->command($command);
|
return $this->rcon->command($command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get challenge (used for players/rules packets)
|
||||||
|
*
|
||||||
|
* @param int $header
|
||||||
|
* @param int $expectedResult
|
||||||
|
*
|
||||||
|
* @throws InvalidPacketException
|
||||||
|
*/
|
||||||
|
private function getChallenge(int $header, int $expectedResult): void
|
||||||
|
{
|
||||||
|
if ($this->challenge) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->useOldGetChallengeMethod) {
|
||||||
|
$header = self::A2S_SERVERQUERY_GETCHALLENGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->socket->write($header, "\xFF\xFF\xFF\xFF");
|
||||||
|
$buffer = $this->socket->read();
|
||||||
|
|
||||||
|
$type = $buffer->getByte();
|
||||||
|
|
||||||
|
switch ($type) {
|
||||||
|
case self::S2C_CHALLENGE:
|
||||||
|
{
|
||||||
|
$this->challenge = $buffer->get(4);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case $expectedResult:
|
||||||
|
{
|
||||||
|
// Goldsource (HLTV).
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 0:
|
||||||
|
{
|
||||||
|
throw new InvalidPacketException('GetChallenge: Failed to get challenge.');
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw new InvalidPacketException('GetChallenge: Packet header mismatch. (0x' . dechex($type) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+53
-11
@@ -239,7 +239,7 @@ final class Tests extends TestCase
|
|||||||
{
|
{
|
||||||
return
|
return
|
||||||
[
|
[
|
||||||
[ "" ],
|
[ '' ],
|
||||||
[ "\xff\xff\xff\xff" ], // No type.
|
[ "\xff\xff\xff\xff" ], // No type.
|
||||||
[ "\xff\xff\xff\xff\x49" ], // Correct type, but no data after.
|
[ "\xff\xff\xff\xff\x49" ], // Correct type, but no data after.
|
||||||
[ "\xff\xff\xff\xff\x6D" ], // Old info packet, but tests are done for source.
|
[ "\xff\xff\xff\xff\x6D" ], // Old info packet, but tests are done for source.
|
||||||
@@ -274,10 +274,22 @@ final class Tests extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function testGetRules(array $rawInput, array $expectedOutput): void
|
public function testGetRules(array $rawInput, array $expectedOutput): void
|
||||||
{
|
{
|
||||||
$this->socket->queue(hex2bin("ffffffff4104fce20e")); // Challenge.
|
$data = hex2bin('ffffffff4104fce20e');
|
||||||
|
|
||||||
|
if (!$data) {
|
||||||
|
throw new InvalidPacketException('Bad packet data');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->socket->queue($data); // Challenge.
|
||||||
|
|
||||||
foreach ($rawInput as $packet) {
|
foreach ($rawInput as $packet) {
|
||||||
$this->socket->queue(hex2bin($packet));
|
$data = hex2bin($packet);
|
||||||
|
|
||||||
|
if (!$data) {
|
||||||
|
throw new InvalidPacketException('Bad packet data');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->socket->queue($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
$realOutput = $this->sourceQuery->getRules();
|
$realOutput = $this->sourceQuery->getRules();
|
||||||
@@ -306,10 +318,22 @@ final class Tests extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function testGetPlayers(array $rawInput, array $expectedOutput): void
|
public function testGetPlayers(array $rawInput, array $expectedOutput): void
|
||||||
{
|
{
|
||||||
$this->socket->queue(hex2bin("ffffffff4104fce20e")); // Challenge.
|
$data = hex2bin('ffffffff4104fce20e');
|
||||||
|
|
||||||
|
if (!$data) {
|
||||||
|
throw new InvalidPacketException('Bad packet data');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->socket->queue($data); // Challenge.
|
||||||
|
|
||||||
foreach ($rawInput as $packet) {
|
foreach ($rawInput as $packet) {
|
||||||
$this->socket->queue(hex2bin($packet));
|
$data = hex2bin($packet);
|
||||||
|
|
||||||
|
if (!$data) {
|
||||||
|
throw new InvalidPacketException('Bad packet data');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->socket->queue($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
$realOutput = $this->sourceQuery->getPlayers();
|
$realOutput = $this->sourceQuery->getPlayers();
|
||||||
@@ -353,18 +377,36 @@ final class Tests extends TestCase
|
|||||||
|
|
||||||
$files = glob(__DIR__ . '/' . $path . '/*.raw', GLOB_ERR);
|
$files = glob(__DIR__ . '/' . $path . '/*.raw', GLOB_ERR);
|
||||||
|
|
||||||
|
if (!$files) {
|
||||||
|
throw new RuntimeException('Could not load test data.');
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($files as $file) {
|
foreach ($files as $file) {
|
||||||
$content = $hexToBin
|
if ($hexToBin) {
|
||||||
? hex2bin(trim(file_get_contents($file)))
|
$content = file_get_contents($file);
|
||||||
: file($file, FILE_SKIP_EMPTY_LINES | FILE_IGNORE_NEW_LINES);
|
|
||||||
|
if (!$content) {
|
||||||
|
throw new RuntimeException('Could not load test data.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = hex2bin(trim($content));
|
||||||
|
} else {
|
||||||
|
$content = file($file, FILE_SKIP_EMPTY_LINES | FILE_IGNORE_NEW_LINES);
|
||||||
|
}
|
||||||
|
|
||||||
|
$jsonContent = file_get_contents(
|
||||||
|
str_replace('.raw', '.json', $file)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$jsonContent) {
|
||||||
|
throw new RuntimeException('Could not load test data.');
|
||||||
|
}
|
||||||
|
|
||||||
$dataProvider[] =
|
$dataProvider[] =
|
||||||
[
|
[
|
||||||
$content,
|
$content,
|
||||||
json_decode(
|
json_decode(
|
||||||
file_get_contents(
|
$jsonContent,
|
||||||
str_replace('.raw', '.json', $file)
|
|
||||||
),
|
|
||||||
true,
|
true,
|
||||||
512,
|
512,
|
||||||
JSON_THROW_ON_ERROR
|
JSON_THROW_ON_ERROR
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
parameters:
|
parameters:
|
||||||
checkMissingIterableValueType: false
|
checkMissingIterableValueType: false
|
||||||
checkFunctionNameCase: true
|
checkFunctionNameCase: true
|
||||||
level: 6
|
level: max
|
||||||
paths:
|
paths:
|
||||||
- .
|
- .
|
||||||
excludes_analyse:
|
excludes_analyse:
|
||||||
|
|||||||
Reference in New Issue
Block a user