You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
429 lines
11 KiB
429 lines
11 KiB
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
use xPaw\SourceQuery\EngineType;
|
|
use xPaw\SourceQuery\Exception\AuthenticationException;
|
|
use xPaw\SourceQuery\Exception\InvalidArgumentException;
|
|
use xPaw\SourceQuery\Exception\InvalidPacketException;
|
|
use xPaw\SourceQuery\Exception\SocketException;
|
|
use xPaw\SourceQuery\Rcon\TestableRcon;
|
|
use xPaw\SourceQuery\Socket\TestableSocket;
|
|
use xPaw\SourceQuery\SourceQuery;
|
|
|
|
/**
|
|
* @internal
|
|
* @covers \xPaw\SourceQuery\SourceQuery
|
|
*
|
|
* @psalm-suppress PropertyNotSetInConstructor
|
|
*/
|
|
final class Tests extends TestCase
|
|
{
|
|
/**
|
|
* @throws InvalidArgumentException
|
|
*/
|
|
public function testInvalidTimeout(): void
|
|
{
|
|
$this->expectException(InvalidArgumentException::class);
|
|
$socket = new TestableSocket();
|
|
$sourceQuery = $this->create($socket);
|
|
$sourceQuery->disconnect();
|
|
$sourceQuery->connect('', 2, -1);
|
|
}
|
|
|
|
/**
|
|
* @throws InvalidArgumentException
|
|
* @throws InvalidPacketException
|
|
* @throws SocketException
|
|
*/
|
|
public function testNotConnectedGetInfo(): void
|
|
{
|
|
$this->expectException(SocketException::class);
|
|
$socket = new TestableSocket();
|
|
$sourceQuery = $this->create($socket);
|
|
$sourceQuery->disconnect();
|
|
$sourceQuery->getInfo();
|
|
}
|
|
|
|
/**
|
|
* @throws InvalidArgumentException
|
|
* @throws SocketException
|
|
*/
|
|
public function testNotConnectedPing(): void
|
|
{
|
|
$this->expectException(SocketException::class);
|
|
$socket = new TestableSocket();
|
|
$sourceQuery = $this->create($socket);
|
|
$sourceQuery->disconnect();
|
|
$sourceQuery->ping();
|
|
}
|
|
|
|
/**
|
|
* @throws InvalidArgumentException
|
|
* @throws InvalidPacketException
|
|
* @throws SocketException
|
|
*/
|
|
public function testNotConnectedGetPlayers(): void
|
|
{
|
|
$this->expectException(SocketException::class);
|
|
$socket = new TestableSocket();
|
|
$sourceQuery = $this->create($socket);
|
|
$sourceQuery->disconnect();
|
|
$sourceQuery->getPlayers();
|
|
}
|
|
|
|
/**
|
|
* @throws InvalidArgumentException
|
|
* @throws InvalidPacketException
|
|
* @throws SocketException
|
|
*/
|
|
public function testNotConnectedGetRules(): void
|
|
{
|
|
$this->expectException(SocketException::class);
|
|
$socket = new TestableSocket();
|
|
$sourceQuery = $this->create($socket);
|
|
$sourceQuery->disconnect();
|
|
$sourceQuery->getRules();
|
|
}
|
|
|
|
/**
|
|
* @throws InvalidArgumentException
|
|
* @throws SocketException
|
|
* @throws AuthenticationException
|
|
*/
|
|
public function testNotConnectedSetRconPassword(): void
|
|
{
|
|
$this->expectException(SocketException::class);
|
|
$socket = new TestableSocket();
|
|
$sourceQuery = $this->create($socket);
|
|
$sourceQuery->disconnect();
|
|
$sourceQuery->setRconPassword('a');
|
|
}
|
|
|
|
/**
|
|
* @throws InvalidArgumentException
|
|
* @throws AuthenticationException
|
|
* @throws InvalidPacketException
|
|
* @throws SocketException
|
|
*/
|
|
public function testNotConnectedRcon(): void
|
|
{
|
|
$this->expectException(SocketException::class);
|
|
$socket = new TestableSocket();
|
|
$sourceQuery = $this->create($socket);
|
|
$sourceQuery->disconnect();
|
|
$sourceQuery->rcon('a');
|
|
}
|
|
|
|
/**
|
|
* @throws InvalidArgumentException
|
|
* @throws AuthenticationException
|
|
* @throws InvalidPacketException
|
|
* @throws SocketException
|
|
*/
|
|
public function testRconWithoutPassword(): void
|
|
{
|
|
$this->expectException(SocketException::class);
|
|
$socket = new TestableSocket();
|
|
$sourceQuery = $this->create($socket);
|
|
$sourceQuery->rcon('a');
|
|
}
|
|
|
|
/**
|
|
* @throws InvalidArgumentException
|
|
* @throws InvalidPacketException
|
|
* @throws SocketException
|
|
*
|
|
* @dataProvider infoProvider
|
|
*/
|
|
public function testGetInfo(string $rawInput, array $expectedOutput): void
|
|
{
|
|
$socketType = isset($expectedOutput['IsMod'])
|
|
? EngineType::GOLDSOURCE
|
|
: EngineType::SOURCE;
|
|
|
|
$socket = new TestableSocket($socketType);
|
|
$sourceQuery = $this->create($socket);
|
|
|
|
$socket->queue($rawInput);
|
|
|
|
$realOutput = $sourceQuery->getInfo();
|
|
|
|
self::assertSame($expectedOutput, $realOutput);
|
|
}
|
|
|
|
/**
|
|
* @throws JsonException
|
|
*/
|
|
public function infoProvider(): array
|
|
{
|
|
return $this->getData('Info', true);
|
|
}
|
|
|
|
/**
|
|
* @throws InvalidArgumentException
|
|
* @throws InvalidPacketException
|
|
* @throws SocketException
|
|
*
|
|
* @dataProvider badPacketProvider
|
|
*/
|
|
public function testBadGetInfo(string $data): void
|
|
{
|
|
$this->expectException(InvalidPacketException::class);
|
|
$socket = new TestableSocket();
|
|
$sourceQuery = $this->create($socket);
|
|
$socket->queue($data);
|
|
|
|
$sourceQuery->getInfo();
|
|
}
|
|
|
|
/**
|
|
* @throws InvalidArgumentException
|
|
* @throws InvalidPacketException
|
|
* @throws SocketException
|
|
*
|
|
* @dataProvider badPacketProvider
|
|
*/
|
|
public function testBadGetChallengeViaPlayers(string $data): void
|
|
{
|
|
$this->expectException(InvalidPacketException::class);
|
|
$socket = new TestableSocket();
|
|
$sourceQuery = $this->create($socket);
|
|
$socket->queue($data);
|
|
|
|
$sourceQuery->getPlayers();
|
|
}
|
|
|
|
/**
|
|
* @throws InvalidArgumentException
|
|
* @throws InvalidPacketException
|
|
* @throws SocketException
|
|
*
|
|
* @dataProvider badPacketProvider
|
|
*/
|
|
public function testBadGetPlayersAfterCorrectChallenge(string $data): void
|
|
{
|
|
$this->expectException(InvalidPacketException::class);
|
|
$socket = new TestableSocket();
|
|
$sourceQuery = $this->create($socket);
|
|
$socket->queue("\xFF\xFF\xFF\xFF\x41\x11\x11\x11\x11");
|
|
$socket->queue($data);
|
|
|
|
$sourceQuery->getPlayers();
|
|
}
|
|
|
|
/**
|
|
* @throws InvalidArgumentException
|
|
* @throws InvalidPacketException
|
|
* @throws SocketException
|
|
*
|
|
* @dataProvider badPacketProvider
|
|
*/
|
|
public function testBadGetRulesAfterCorrectChallenge(string $data): void
|
|
{
|
|
$this->expectException(InvalidPacketException::class);
|
|
$socket = new TestableSocket();
|
|
$sourceQuery = $this->create($socket);
|
|
$socket->queue("\xFF\xFF\xFF\xFF\x41\x11\x11\x11\x11");
|
|
$socket->queue($data);
|
|
|
|
$sourceQuery->getRules();
|
|
}
|
|
|
|
/**
|
|
* @return string[][]
|
|
*/
|
|
public function badPacketProvider(): array
|
|
{
|
|
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.
|
|
["\xff\xff\xff\xff\x11"], // Wrong type.
|
|
["\x11\x11\x11\x11"], // Wrong header.
|
|
["\xff"], // Should be 4 bytes, but it's 1.
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @throws InvalidArgumentException
|
|
* @throws InvalidPacketException
|
|
* @throws SocketException
|
|
*/
|
|
public function testGetChallengeTwice(): void
|
|
{
|
|
$socket = new TestableSocket();
|
|
$sourceQuery = $this->create($socket);
|
|
$socket->queue("\xFF\xFF\xFF\xFF\x41\x11\x11\x11\x11");
|
|
$socket->queue("\xFF\xFF\xFF\xFF\x45\x01\x00ayy\x00lmao\x00");
|
|
self::assertSame(['ayy' => 'lmao'], $sourceQuery->getRules());
|
|
|
|
$socket->queue("\xFF\xFF\xFF\xFF\x45\x01\x00wow\x00much\x00");
|
|
self::assertSame(['wow' => 'much'], $sourceQuery->getRules());
|
|
}
|
|
|
|
/**
|
|
* @param string[] $rawInput
|
|
*
|
|
* @throws InvalidArgumentException
|
|
* @throws InvalidPacketException
|
|
* @throws SocketException
|
|
*
|
|
* @dataProvider rulesProvider
|
|
*/
|
|
public function testGetRules(array $rawInput, array $expectedOutput): void
|
|
{
|
|
$sourceQuery = $this->putDataOnSocket($rawInput);
|
|
|
|
$realOutput = $sourceQuery->getRules();
|
|
|
|
self::assertSame($expectedOutput, $realOutput);
|
|
}
|
|
|
|
/**
|
|
* @throws JsonException
|
|
*/
|
|
public function rulesProvider(): array
|
|
{
|
|
return $this->getData('Rules');
|
|
}
|
|
|
|
/**
|
|
* @param string[] $rawInput
|
|
*
|
|
* @throws InvalidArgumentException
|
|
* @throws InvalidPacketException
|
|
* @throws SocketException
|
|
*
|
|
* @dataProvider playersProvider
|
|
*/
|
|
public function testGetPlayers(array $rawInput, array $expectedOutput): void
|
|
{
|
|
$sourceQuery = $this->putDataOnSocket($rawInput);
|
|
|
|
$realOutput = $sourceQuery->getPlayers();
|
|
|
|
self::assertSame($expectedOutput, $realOutput);
|
|
}
|
|
|
|
/**
|
|
* @throws JsonException
|
|
*/
|
|
public function playersProvider(): array
|
|
{
|
|
return $this->getData('Players');
|
|
}
|
|
|
|
/**
|
|
* @throws InvalidArgumentException
|
|
* @throws SocketException
|
|
*/
|
|
public function testPing(): void
|
|
{
|
|
$socket = new TestableSocket();
|
|
$sourceQuery = $this->create($socket);
|
|
|
|
$socket->queue("\xFF\xFF\xFF\xFF\x6A\x00");
|
|
self::assertTrue($sourceQuery->ping());
|
|
|
|
$socket->queue("\xFF\xFF\xFF\xFF\xEE");
|
|
self::assertFalse($sourceQuery->ping());
|
|
}
|
|
|
|
/**
|
|
* @throws InvalidArgumentException
|
|
*/
|
|
private function create(TestableSocket $socket): SourceQuery
|
|
{
|
|
$rcon = new TestableRcon();
|
|
$sourceQuery = new SourceQuery($socket, $rcon);
|
|
$sourceQuery->connect('', 2);
|
|
|
|
return $sourceQuery;
|
|
}
|
|
|
|
/**
|
|
* @throws JsonException
|
|
*/
|
|
private function getData(string $path, bool $hexToBin = false): array
|
|
{
|
|
$dataProvider = [];
|
|
|
|
$files = glob(__DIR__ . '/' . $path . '/*.raw', GLOB_ERR);
|
|
|
|
if (!$files) {
|
|
throw new RuntimeException('Could not load test data.');
|
|
}
|
|
|
|
foreach ($files as $file) {
|
|
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(
|
|
$jsonContent,
|
|
true,
|
|
512,
|
|
JSON_THROW_ON_ERROR
|
|
),
|
|
];
|
|
}
|
|
|
|
return $dataProvider;
|
|
}
|
|
|
|
/**
|
|
* @param string[] $rawInput
|
|
*
|
|
* @throws InvalidArgumentException
|
|
* @throws InvalidPacketException
|
|
*/
|
|
private function putDataOnSocket(array $rawInput): SourceQuery
|
|
{
|
|
$data = hex2bin('ffffffff4104fce20e');
|
|
|
|
if (!$data) {
|
|
throw new InvalidPacketException('Bad packet data');
|
|
}
|
|
|
|
$socket = new TestableSocket();
|
|
$sourceQuery = $this->create($socket);
|
|
|
|
$socket->queue($data); // Challenge.
|
|
|
|
foreach ($rawInput as $packet) {
|
|
$data = hex2bin($packet);
|
|
|
|
if (!$data) {
|
|
throw new InvalidPacketException('Bad packet data');
|
|
}
|
|
|
|
$socket->queue($data);
|
|
}
|
|
|
|
return $sourceQuery;
|
|
}
|
|
}
|