mirror of
https://github.com/xPaw/PHP-Source-Query.git
synced 2026-06-10 21:23:15 +02:00
PHPStan max level (improved error handling).
This commit is contained in:
+20
-4
@@ -113,7 +113,11 @@ final class Buffer
|
||||
|
||||
$data = unpack('v', $this->get(2));
|
||||
|
||||
return (int)$data[ 1 ];
|
||||
if (!$data) {
|
||||
throw new InvalidPacketException('Empty data from packet.');
|
||||
}
|
||||
|
||||
return (int) $data[1];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,7 +133,11 @@ final class Buffer
|
||||
|
||||
$data = unpack('l', $this->get(4));
|
||||
|
||||
return (int)$data[ 1 ];
|
||||
if (!$data) {
|
||||
throw new InvalidPacketException('Empty data from packet.');
|
||||
}
|
||||
|
||||
return (int) $data[1];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,7 +153,11 @@ final class Buffer
|
||||
|
||||
$data = unpack('f', $this->get(4));
|
||||
|
||||
return (float)$data[ 1 ];
|
||||
if (!$data) {
|
||||
throw new InvalidPacketException('Empty data from packet.');
|
||||
}
|
||||
|
||||
return (float) $data[1];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,7 +173,11 @@ final class Buffer
|
||||
|
||||
$data = unpack('V', $this->get(4));
|
||||
|
||||
return (int)$data[ 1 ];
|
||||
if (!$data) {
|
||||
throw new InvalidPacketException('Empty data from packet.');
|
||||
}
|
||||
|
||||
return (int) $data[1];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -173,8 +173,18 @@ final class SourceRcon extends AbstractRcon
|
||||
*/
|
||||
protected function read(): Buffer
|
||||
{
|
||||
if (!$this->rconSocket) {
|
||||
throw new InvalidPacketException('Rcon socket not open.');
|
||||
}
|
||||
|
||||
$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) {
|
||||
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();
|
||||
|
||||
$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();
|
||||
|
||||
@@ -191,9 +207,13 @@ final class SourceRcon extends AbstractRcon
|
||||
while ($remaining > 0) {
|
||||
$data2 = fread($this->rconSocket, $remaining);
|
||||
|
||||
if (!$data2) {
|
||||
throw new InvalidPacketException('Empty data from packet.');
|
||||
}
|
||||
|
||||
$packetSize = strlen($data2);
|
||||
|
||||
if ($packetSize === 0) {
|
||||
if ($packetSize <= 0) {
|
||||
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
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws InvalidPacketException
|
||||
*/
|
||||
protected function write(?int $header, string $string = ''): bool
|
||||
{
|
||||
if (!$this->rconSocket) {
|
||||
throw new InvalidPacketException('Rcon socket not open.');
|
||||
}
|
||||
|
||||
// Pack the packet together.
|
||||
$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
|
||||
{
|
||||
if (!$this->socket) {
|
||||
throw new InvalidPacketException('Socket not open.');
|
||||
}
|
||||
|
||||
$buffer = new Buffer();
|
||||
$data = fread($this->socket, $length);
|
||||
|
||||
@@ -156,9 +160,15 @@ abstract class AbstractSocket implements SocketInterface
|
||||
* @param string $string
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws InvalidPacketException
|
||||
*/
|
||||
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);
|
||||
$length = strlen($command);
|
||||
|
||||
@@ -175,8 +185,16 @@ abstract class AbstractSocket implements SocketInterface
|
||||
*/
|
||||
public function sherlock(Buffer $buffer, int $length): bool
|
||||
{
|
||||
if (!$this->socket) {
|
||||
throw new InvalidPacketException('Socket not open.');
|
||||
}
|
||||
|
||||
$data = fread($this->socket, $length);
|
||||
|
||||
if (!$data) {
|
||||
throw new InvalidPacketException('Empty data from packet.');
|
||||
}
|
||||
|
||||
if (strlen($data) < 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+63
-63
@@ -127,22 +127,6 @@ final class SourceQuery
|
||||
$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
|
||||
*/
|
||||
@@ -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
|
||||
* NOTE: This may not work on some games (TF2 for example)
|
||||
@@ -279,7 +279,7 @@ final class SourceQuery
|
||||
$server[ 'Version' ] = $buffer->getString();
|
||||
|
||||
// Extra Data Flags.
|
||||
if (!$buffer->isEmpty()) {
|
||||
if ($buffer->remaining() > 0) {
|
||||
$server[ 'ExtraDataFlags' ] = $Flags = $buffer->getByte();
|
||||
|
||||
// S2A_EXTRA_DATA_HAS_GAME_PORT - Next 2 bytes include the game port.
|
||||
@@ -421,52 +421,6 @@ final class SourceQuery
|
||||
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()
|
||||
*
|
||||
@@ -528,4 +482,50 @@ final class SourceQuery
|
||||
|
||||
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
|
||||
[
|
||||
[ "" ],
|
||||
[ '' ],
|
||||
[ "\xff\xff\xff\xff" ], // No type.
|
||||
[ "\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.
|
||||
@@ -274,10 +274,22 @@ final class Tests extends TestCase
|
||||
*/
|
||||
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) {
|
||||
$this->socket->queue(hex2bin($packet));
|
||||
$data = hex2bin($packet);
|
||||
|
||||
if (!$data) {
|
||||
throw new InvalidPacketException('Bad packet data');
|
||||
}
|
||||
|
||||
$this->socket->queue($data);
|
||||
}
|
||||
|
||||
$realOutput = $this->sourceQuery->getRules();
|
||||
@@ -306,10 +318,22 @@ final class Tests extends TestCase
|
||||
*/
|
||||
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) {
|
||||
$this->socket->queue(hex2bin($packet));
|
||||
$data = hex2bin($packet);
|
||||
|
||||
if (!$data) {
|
||||
throw new InvalidPacketException('Bad packet data');
|
||||
}
|
||||
|
||||
$this->socket->queue($data);
|
||||
}
|
||||
|
||||
$realOutput = $this->sourceQuery->getPlayers();
|
||||
@@ -353,18 +377,36 @@ final class Tests extends TestCase
|
||||
|
||||
$files = glob(__DIR__ . '/' . $path . '/*.raw', GLOB_ERR);
|
||||
|
||||
if (!$files) {
|
||||
throw new RuntimeException('Could not load test data.');
|
||||
}
|
||||
|
||||
foreach ($files as $file) {
|
||||
$content = $hexToBin
|
||||
? hex2bin(trim(file_get_contents($file)))
|
||||
: file($file, FILE_SKIP_EMPTY_LINES | FILE_IGNORE_NEW_LINES);
|
||||
if ($hexToBin) {
|
||||
$content = file_get_contents($file);
|
||||
|
||||
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[] =
|
||||
[
|
||||
$content,
|
||||
json_decode(
|
||||
file_get_contents(
|
||||
str_replace('.raw', '.json', $file)
|
||||
),
|
||||
$jsonContent,
|
||||
true,
|
||||
512,
|
||||
JSON_THROW_ON_ERROR
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
parameters:
|
||||
checkMissingIterableValueType: false
|
||||
checkFunctionNameCase: true
|
||||
level: 6
|
||||
level: max
|
||||
paths:
|
||||
- .
|
||||
excludes_analyse:
|
||||
|
||||
Reference in New Issue
Block a user