Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moving Source Query #32

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions includes/SourceQuery/BaseSocket.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php
/**
* @author Pavel Djundik
*
* @link https://xpaw.me
* @link https://github.com/xPaw/PHP-Source-Query
*
* @license GNU Lesser General Public License, version 2.1
*
* @internal
*/

namespace xPaw\SourceQuery;

use xPaw\SourceQuery\Exception\InvalidPacketException;
use xPaw\SourceQuery\Exception\SocketException;

/**
* Base socket interface
*
* @package xPaw\SourceQuery
*
* @uses xPaw\SourceQuery\Exception\InvalidPacketException
* @uses xPaw\SourceQuery\Exception\SocketException
*/
abstract class BaseSocket
{
/** @var resource */
public $Socket;
public int $Engine;

public string $Address;
public int $Port;
public int $Timeout;

public function __destruct( )
{
$this->Close( );
}

abstract public function Close( ) : void;
abstract public function Open( string $Address, int $Port, int $Timeout, int $Engine ) : void;
abstract public function Write( int $Header, string $String = '' ) : bool;
abstract public function Read( int $Length = 1400 ) : Buffer;

protected function ReadInternal( Buffer $Buffer, int $Length, callable $SherlockFunction ) : Buffer
{
if( $Buffer->Remaining( ) === 0 )
{
throw new InvalidPacketException( 'Failed to read any data from socket', InvalidPacketException::BUFFER_EMPTY );
}

$Header = $Buffer->GetLong( );

if( $Header === -1 ) // Single packet
{
// We don't have to do anything
}
else if( $Header === -2 ) // Split packet
{
$Packets = [];
$IsCompressed = false;
$ReadMore = false;
$PacketChecksum = null;

do
{
$RequestID = $Buffer->GetLong( );

switch( $this->Engine )
{
case SourceQuery::GOLDSOURCE:
{
$PacketCountAndNumber = $Buffer->GetByte( );
$PacketCount = $PacketCountAndNumber & 0xF;
$PacketNumber = $PacketCountAndNumber >> 4;

break;
}
case SourceQuery::SOURCE:
{
$IsCompressed = ( $RequestID & 0x80000000 ) !== 0;
$PacketCount = $Buffer->GetByte( );
$PacketNumber = $Buffer->GetByte( ) + 1;

if( $IsCompressed )
{
$Buffer->GetLong( ); // Split size

$PacketChecksum = $Buffer->GetUnsignedLong( );
}
else
{
$Buffer->GetShort( ); // Split size
}

break;
}
default:
{
throw new SocketException( 'Unknown engine.', SocketException::INVALID_ENGINE );
}
}

$Packets[ $PacketNumber ] = $Buffer->Get( );

$ReadMore = $PacketCount > sizeof( $Packets );
}
while( $ReadMore && $SherlockFunction( $Buffer, $Length ) );

$Data = Implode( $Packets );

// TODO: Test this
if( $IsCompressed )
{
// Let's make sure this function exists, it's not included in PHP by default
if( !Function_Exists( 'bzdecompress' ) )
{
throw new \RuntimeException( 'Received compressed packet, PHP doesn\'t have Bzip2 library installed, can\'t decompress.' );
}

$Data = bzdecompress( $Data );

if( !is_string( $Data ) || CRC32( $Data ) !== $PacketChecksum )
{
throw new InvalidPacketException( 'CRC32 checksum mismatch of uncompressed packet data.', InvalidPacketException::CHECKSUM_MISMATCH );
}
}

$Buffer->Set( SubStr( $Data, 4 ) );
}
else
{
throw new InvalidPacketException( 'Socket read: Raw packet header mismatch. (0x' . DecHex( $Header ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
}

return $Buffer;
}
}
177 changes: 177 additions & 0 deletions includes/SourceQuery/Buffer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
<?php
/**
* @author Pavel Djundik
*
* @link https://xpaw.me
* @link https://github.com/xPaw/PHP-Source-Query
*
* @license GNU Lesser General Public License, version 2.1
*
* @internal
*/

namespace xPaw\SourceQuery;

use xPaw\SourceQuery\Exception\InvalidPacketException;

/**
* Class Buffer
*
* @package xPaw\SourceQuery
*
* @uses xPaw\SourceQuery\Exception\InvalidPacketException
*/
class Buffer
{
/**
* Buffer
*/
private string $Buffer = '';

/**
* Buffer length
*/
private int $Length = 0;

/**
* Current position in buffer
*/
private int $Position = 0;

/**
* Sets buffer
*/
public function Set( string $Buffer ) : void
{
$this->Buffer = $Buffer;
$this->Length = StrLen( $Buffer );
$this->Position = 0;
}

/**
* Get remaining bytes
*
* @return int Remaining bytes in buffer
*/
public function Remaining( ) : int
{
return $this->Length - $this->Position;
}

/**
* Gets data from buffer
*
* @param int $Length Bytes to read
*/
public function Get( int $Length = -1 ) : string
{
if( $Length === 0 )
{
return '';
}

$Remaining = $this->Remaining( );

if( $Length === -1 )
{
$Length = $Remaining;
}
else if( $Length > $Remaining )
{
return '';
}

$Data = SubStr( $this->Buffer, $this->Position, $Length );

$this->Position += $Length;

return $Data;
}

/**
* Get byte from buffer
*/
public function GetByte( ) : int
{
return Ord( $this->Get( 1 ) );
}

/**
* Get short from buffer
*/
public function GetShort( ) : int
{
if( $this->Remaining( ) < 2 )
{
throw new InvalidPacketException( 'Not enough data to unpack a short.', InvalidPacketException::BUFFER_EMPTY );
}

$Data = UnPack( 'v', $this->Get( 2 ) );

return (int)$Data[ 1 ];
}

/**
* Get long from buffer
*/
public function GetLong( ) : int
{
if( $this->Remaining( ) < 4 )
{
throw new InvalidPacketException( 'Not enough data to unpack a long.', InvalidPacketException::BUFFER_EMPTY );
}

$Data = UnPack( 'l', $this->Get( 4 ) );

return (int)$Data[ 1 ];
}

/**
* Get float from buffer
*/
public function GetFloat( ) : float
{
if( $this->Remaining( ) < 4 )
{
throw new InvalidPacketException( 'Not enough data to unpack a float.', InvalidPacketException::BUFFER_EMPTY );
}

$Data = UnPack( 'f', $this->Get( 4 ) );

return (float)$Data[ 1 ];
}

/**
* Get unsigned long from buffer
*/
public function GetUnsignedLong( ) : int
{
if( $this->Remaining( ) < 4 )
{
throw new InvalidPacketException( 'Not enough data to unpack an usigned long.', InvalidPacketException::BUFFER_EMPTY );
}

$Data = UnPack( 'V', $this->Get( 4 ) );

return (int)$Data[ 1 ];
}

/**
* Read one string from buffer ending with null byte
*/
public function GetString( ) : string
{
$ZeroBytePosition = StrPos( $this->Buffer, "\0", $this->Position );

if( $ZeroBytePosition === false )
{
return '';
}

$String = $this->Get( $ZeroBytePosition - $this->Position );

$this->Position++;

return $String;
}
}
19 changes: 19 additions & 0 deletions includes/SourceQuery/Exception/AuthenticationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php
/**
* @author Pavel Djundik
*
* @link https://xpaw.me
* @link https://github.com/xPaw/PHP-Source-Query
*
* @license GNU Lesser General Public License, version 2.1
*
* @internal
*/

namespace xPaw\SourceQuery\Exception;

class AuthenticationException extends SourceQueryException
{
const BAD_PASSWORD = 1;
const BANNED = 2;
}
18 changes: 18 additions & 0 deletions includes/SourceQuery/Exception/InvalidArgumentException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php
/**
* @author Pavel Djundik
*
* @link https://xpaw.me
* @link https://github.com/xPaw/PHP-Source-Query
*
* @license GNU Lesser General Public License, version 2.1
*
* @internal
*/

namespace xPaw\SourceQuery\Exception;

class InvalidArgumentException extends SourceQueryException
{
const TIMEOUT_NOT_INTEGER = 1;
}
21 changes: 21 additions & 0 deletions includes/SourceQuery/Exception/InvalidPacketException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php
/**
* @author Pavel Djundik
*
* @link https://xpaw.me
* @link https://github.com/xPaw/PHP-Source-Query
*
* @license GNU Lesser General Public License, version 2.1
*
* @internal
*/

namespace xPaw\SourceQuery\Exception;

class InvalidPacketException extends SourceQueryException
{
const PACKET_HEADER_MISMATCH = 1;
const BUFFER_EMPTY = 2;
const BUFFER_NOT_EMPTY = 3;
const CHECKSUM_MISMATCH = 4;
}
Loading