2021-05-07 22:07:26 +10:00
< ? php
namespace App\Classes\Sock ;
2021-04-01 21:59:15 +11:00
use Illuminate\Support\Arr ;
2021-05-07 22:07:26 +10:00
use Illuminate\Support\Facades\Log ;
2021-04-01 21:59:15 +11:00
use Illuminate\Support\Str ;
2021-05-07 22:07:26 +10:00
2024-06-01 12:55:27 +10:00
use App\Classes\Sock\Exception\ { HAproxyException , SocketException };
2021-04-01 21:59:15 +11:00
/**
* Class SocketClient
*
* @ package App\Classes\Sock
* @ property int cps
2021-07-17 15:48:07 +10:00
* @ property int speed
2021-04-01 21:59:15 +11:00
*/
2021-05-07 22:07:26 +10:00
final class SocketClient {
2021-08-17 23:49:39 +10:00
private const LOGKEY = 'SC-' ;
2021-04-01 21:59:15 +11:00
// For deep debugging
2023-07-19 10:27:47 +10:00
private const DEBUG = FALSE ;
2021-04-01 21:59:15 +11:00
private \Socket $connection ;
2021-07-17 15:48:07 +10:00
private string $address_local = '' ;
private int $port_local = 0 ;
private string $address_remote = '' ;
private int $port_remote = 0 ;
2021-05-07 22:07:26 +10:00
2021-04-01 21:59:15 +11:00
// Our session state
private array $session = [];
private const OK = 0 ;
private const TIMEOUT = - 2 ;
private const ERROR = - 5 ;
2023-06-28 19:50:24 +10:00
/** @var string Size of our TX buffer */
private const TX_BUF_SIZE = 0xFFFF ;
/** @var string Maximum amount of data to send at a time */
private const TX_SIZE = 0xFFFF ;
/** @var string Data in the TX buffer */
2021-04-01 21:59:15 +11:00
private string $tx_buf = '' ;
2023-06-28 19:50:24 +10:00
/** @var string Size of our RX buffer */
private const RX_BUF_SIZE = 0xFFFF ;
/** @var string Maximum amount of data to received at a time */
private const RX_SIZE = 0xFFFF ;
/** @var string Data in the RX buffer */
2021-04-01 21:59:15 +11:00
private string $rx_buf = '' ;
2024-11-10 13:34:01 +11:00
public function __construct ( \Socket $connection , bool $originate = FALSE )
{
2021-05-07 22:07:26 +10:00
$this -> connection = $connection ;
2023-04-23 23:08:30 +10:00
2023-06-28 19:50:24 +10:00
if ( $this -> type === SOCK_STREAM ) {
2023-04-23 23:08:30 +10:00
socket_getsockname ( $connection , $this -> address_local , $this -> port_local );
socket_getpeername ( $connection , $this -> address_remote , $this -> port_remote );
2023-10-12 21:29:02 +11:00
// If HAPROXY is used, work get the clients address
2024-10-19 18:34:50 +11:00
if (( ! $originate ) && config ( 'fido.haproxy' )) {
2023-10-12 21:29:02 +11:00
Log :: debug ( sprintf ( '%s:+ HAPROXY connection host [%s] on port [%d] (%s)' , self :: LOGKEY , $this -> address_remote , $this -> port_remote , $this -> type ));
2024-10-26 12:15:53 +11:00
if (( $x = $this -> read ( 5 , 6 )) === 'PROXY ' )
$vers = 1 ;
2024-11-27 16:05:11 +11:00
elseif (( $x === " \x0d \x0a \x0d \x0a \x00 \x0d " ) && ( $this -> read ( 5 , 6 ) === " \x0a QUIT \x0a " ))
2024-10-26 12:15:53 +11:00
$vers = 2 ;
else
2024-06-01 12:55:27 +10:00
throw new HAproxyException ( 'Failed to initialise HAPROXY connection' );
2023-10-12 21:29:02 +11:00
2024-10-26 12:15:53 +11:00
switch ( $vers ) {
case 1 :
// Protocol/Address Family
switch ( $x = $this -> read ( 5 , 5 )) {
case 'TCP4 ' :
$p = 4 ;
break ;
case 'TCP6 ' :
$p = 6 ;
break ;
2023-10-12 21:29:02 +11:00
2024-10-26 12:15:53 +11:00
default :
throw new HAproxyException ( sprintf ( 'HAPROXY protocol [%d] is not handled' , $x ));
}
2023-10-12 21:29:02 +11:00
2024-10-26 12:15:53 +11:00
$read = $this -> read ( 5 , 104 - 11 );
2023-10-12 21:29:02 +11:00
2024-10-26 12:15:53 +11:00
// IPv4
if (( $p === 4 ) || ( $p === 6 )) {
$parse = collect ( sscanf ( $read , '%s %s %s %s' ));
2023-10-12 21:29:02 +11:00
2024-10-26 12:15:53 +11:00
$src = Arr :: get ( $parse , 0 );
$dst = Arr :: get ( $parse , 1 );
$src_port = ( int ) Arr :: get ( $parse , 2 );
$dst_port = ( int ) Arr :: get ( $parse , 3 );
$len = $parse -> map ( fn ( $item ) => strlen ( $item )) -> sum () + 3 ;
2023-10-12 21:29:02 +11:00
2024-10-26 12:15:53 +11:00
// The last 2 chars should be "\r\n"
if (( $x = substr ( $read , $len )) !== " \r \n " )
throw new HAproxyException ( sprintf ( 'HAPROXY parsing failed for version [%d] [%s] (%s)' , $p , $read , hex_dump ( $x )));
2023-10-12 21:29:02 +11:00
2024-10-26 12:15:53 +11:00
} else {
throw new HAproxyException ( sprintf ( 'HAPROXY version [%d] is not handled [%s]' , $p , $read ));
}
$this -> port_remote = $src_port ;
2023-10-12 21:29:02 +11:00
break ;
2024-10-26 12:15:53 +11:00
case 2 :
// Version/Command
$vc = $this -> read_ch ( 5 );
2023-10-12 21:29:02 +11:00
2024-10-26 12:15:53 +11:00
if (( $x = ( $vc >> 4 ) & 0x7 ) !== 2 )
throw new HAproxyException ( sprintf ( 'Unknown HAPROXY version [%d]' , $x ));
2023-10-12 21:29:02 +11:00
2024-10-26 12:15:53 +11:00
switch ( $x = ( $vc & 0x7 )) {
// HAPROXY internal
case 0 :
throw new HAproxyException ( 'HAPROXY internal health-check' );
2023-10-12 21:29:02 +11:00
2024-10-26 12:15:53 +11:00
// PROXY connection
case 1 :
break ;
2023-10-12 21:29:02 +11:00
2024-10-26 12:15:53 +11:00
default :
throw new HAproxyException ( sprintf ( 'HAPROXY command [%d] is not handled' , $x ));
}
2023-10-12 21:29:02 +11:00
2024-10-26 12:15:53 +11:00
// Protocol/Address Family
$pa = $this -> read_ch ( 5 );
2023-10-12 21:29:02 +11:00
2024-10-26 12:15:53 +11:00
switch ( $x = ( $pa >> 4 ) & 0x7 ) {
case 1 : // AF_INET
$p = 4 ;
break ;
2023-10-12 21:29:02 +11:00
2024-10-26 12:15:53 +11:00
case 2 : // AF_INET6
$p = 6 ;
break ;
}
switch ( $x = ( $pa & 0x7 )) {
case 1 : // STREAM
break ;
default :
throw new HAproxyException ( sprintf ( 'HAPROXY address family [%d] is not handled' , $x ));
}
$len = Arr :: get ( unpack ( 'n' , $this -> read ( 5 , 2 )), 1 );
// IPv4
if (( $p === 4 ) && ( $len === 12 )) {
$src = inet_ntop ( $this -> read ( 5 , 4 ));
$dst = inet_ntop ( $this -> read ( 5 , 4 ));
} elseif (( $p === 6 ) && ( $len === 36 )) {
$src = inet_ntop ( $this -> read ( 5 , 16 ));
$dst = inet_ntop ( $this -> read ( 5 , 16 ));
} else {
throw new HAproxyException ( sprintf ( 'HAPROXY address len [%d:%d] is not handled' , $p , $len ));
}
$src_port = unpack ( 'n' , $this -> read ( 5 , 2 ));
$dst_port = Arr :: get ( unpack ( 'n' , $this -> read ( 5 , 2 )), 1 );
$this -> port_remote = Arr :: get ( $src_port , 1 );
break ;
default :
throw new HAproxyException ( 'Failed to initialise HAPROXY connection' );
}
2023-10-12 21:29:02 +11:00
$this -> address_remote = $src ;
2024-09-09 23:49:04 +10:00
Log :: debug ( sprintf ( '%s:- HAPROXY src [%s:%d] dst [%s:%d]' ,
2023-10-12 21:29:02 +11:00
self :: LOGKEY ,
$this -> address_remote ,
$this -> port_remote ,
$dst ,
2024-10-26 12:15:53 +11:00
$dst_port ,
2023-10-12 21:29:02 +11:00
));
}
2024-09-09 23:49:04 +10:00
Log :: debug ( sprintf ( '%s:+ Connection host [%s] on port [%d] (%s)' , self :: LOGKEY , $this -> address_remote , $this -> port_remote , $this -> type ));
2023-04-23 23:08:30 +10:00
}
2021-05-07 22:07:26 +10:00
}
2024-11-10 13:34:01 +11:00
public function __get ( string $key ) : mixed
{
return match ( $key ) {
'address_remote' , 'port_remote' => $this -> { $key },
'cps' , 'speed' => Arr :: get ( $this -> session , $key ),
2025-01-30 23:40:50 +11:00
'iac_bin' => Arr :: get ( $this -> session , $key ),
2024-11-10 13:34:01 +11:00
'rx_free' => self :: RX_BUF_SIZE - $this -> rx_left ,
'rx_left' => strlen ( $this -> rx_buf ),
'tx_free' => self :: TX_BUF_SIZE - strlen ( $this -> tx_buf ),
'type' => socket_get_option ( $this -> connection , SOL_SOCKET , SO_TYPE ),
default => throw new \Exception ( sprintf ( '%s:! Unknown key [%s]:' , self :: LOGKEY , $key )),
};
2021-04-01 21:59:15 +11:00
}
2024-11-10 13:34:01 +11:00
public function __set ( string $key , mixed $value ) : void
{
2021-04-01 21:59:15 +11:00
switch ( $key ) {
case 'cps' :
case 'speed' :
2025-01-30 23:40:50 +11:00
case 'iac_bin' :
2024-11-10 13:34:01 +11:00
$this -> session [ $key ] = $value ;
break ;
2021-04-01 21:59:15 +11:00
default :
2021-08-17 23:49:39 +10:00
throw new \Exception ( sprintf ( '%s:! Unknown key [%s]:' , self :: LOGKEY , $key ));
2021-04-01 21:59:15 +11:00
}
}
2023-06-28 19:50:24 +10:00
/**
* Create a client socket
*
* @ param string $address
* @ param int $port
* @ return static
2024-10-19 18:34:50 +11:00
* @ throws SocketException | HAproxyException
2023-06-28 19:50:24 +10:00
*/
public static function create ( string $address , int $port ) : self
{
Log :: info ( sprintf ( '%s:+ Creating connection to [%s:%d]' , self :: LOGKEY , $address , $port ));
2024-10-25 23:28:39 +11:00
$type = collect ( config ( 'fido.ip' ))
-> filter ( fn ( $item ) => $item [ 'enabled' ]);
2023-06-28 19:50:24 +10:00
2024-05-21 21:07:11 +10:00
if ( filter_var ( $address , FILTER_VALIDATE_IP ))
$resolved = collect ([[
(( $x = filter_var ( $address , FILTER_VALIDATE_IP , FILTER_FLAG_IPV6 )) ? 'ipv6' : 'ip' ) => $address ,
'type' => $x ? 'AAAA' : 'A'
]]);
else
// We only look at AAAA/A records
2024-10-25 23:28:39 +11:00
$resolved = collect ( dns_get_record ( $address , $type -> map ( fn ( $item ) => $item [ 'type' ]) -> sum ()))
-> filter ( fn ( $item ) => $type -> has ( Arr :: get ( $item , 'type' )))
-> sort ( fn ( $a , $b ) => $type -> get ( Arr :: get ( $a , 'type' ))[ 'order' ] < $type -> get ( Arr :: get ( $b , 'type' ))[ 'order' ]);
2023-06-28 19:50:24 +10:00
if ( ! $resolved -> count ())
throw new SocketException ( SocketException :: CANT_CONNECT , sprintf ( '%s doesnt resolved to an IPv4/IPv6 address' , $address ));
$result = FALSE ;
2024-10-19 18:34:50 +11:00
$socket = NULL ;
2023-06-28 19:50:24 +10:00
foreach ( $resolved as $address ) {
try {
$try = Arr :: get ( $address , Arr :: get ( $address , 'type' ) === 'AAAA' ? 'ipv6' : 'ip' );
if ( ! $try )
continue ;
Log :: info ( sprintf ( '%s:- Trying [%s:%d]' , self :: LOGKEY , $try , $port ));
/* Create a TCP/IP socket. */
$socket = socket_create ( Arr :: get ( $address , 'type' ) === 'AAAA' ? AF_INET6 : AF_INET , SOCK_STREAM , SOL_TCP );
if ( $socket === FALSE )
throw new SocketException ( SocketException :: CANT_CREATE_SOCKET , socket_strerror ( socket_last_error ( $socket )));
$result = socket_connect ( $socket , $try , $port );
break ;
} catch ( \ErrorException $e ) {
// If 'Cannot assign requested address'
if ( socket_last_error ( $socket ) === 99 )
continue ;
throw new SocketException ( SocketException :: CANT_CONNECT , socket_strerror ( socket_last_error ( $socket )));
}
}
if ( $result === FALSE )
throw new SocketException ( SocketException :: CANT_CONNECT , socket_strerror ( socket_last_error ( $socket )));
2024-10-19 18:34:50 +11:00
return new self ( $socket , TRUE );
2023-06-28 19:50:24 +10:00
}
2021-04-01 21:59:15 +11:00
/**
* We 'll add to our transmit buffer and if doesnt have space, we' ll empty it first
*
* @ param string $data
* @ return void
* @ throws \Exception
*/
public function buffer_add ( string $data ) : void
{
$ptr = 0 ;
$num_bytes = strlen ( $data );
while ( $num_bytes ) {
2023-07-19 10:27:47 +10:00
if ( self :: DEBUG )
2023-06-28 19:50:24 +10:00
Log :: debug ( sprintf ( '%s:- To add [%d] to the TX buffer' , self :: LOGKEY , $num_bytes ));
2021-04-01 21:59:15 +11:00
if ( $num_bytes > $this -> tx_free ) {
2023-07-19 10:27:47 +10:00
if ( self :: DEBUG )
2023-06-28 19:50:24 +10:00
Log :: debug ( sprintf ( '%s:- TX buffer will be too full, draining...' , self :: LOGKEY ));
2021-04-01 21:59:15 +11:00
do {
$this -> buffer_flush ( 5 );
2023-06-28 19:50:24 +10:00
$n = min ( $this -> tx_free , $num_bytes );
$this -> tx_buf = substr ( $data , $ptr , $n );
$num_bytes -= $n ;
$ptr += $n ;
2021-04-01 21:59:15 +11:00
2023-06-28 19:50:24 +10:00
} while ( $num_bytes );
2021-04-01 21:59:15 +11:00
} else {
$this -> tx_buf .= substr ( $data , $ptr , $num_bytes );
$num_bytes = 0 ;
}
}
2023-07-19 10:27:47 +10:00
if ( self :: DEBUG )
2023-06-28 19:50:24 +10:00
Log :: debug ( sprintf ( '%s:= TX buffer has [%d] space left' , self :: LOGKEY , $this -> tx_free ));
2021-04-01 21:59:15 +11:00
}
/**
* Empty our TX buffer
*
2021-05-07 22:07:26 +10:00
* @ param int $timeout
* @ return int
2021-04-01 21:59:15 +11:00
* @ throws \Exception
*/
public function buffer_flush ( int $timeout ) : int
{
2023-07-19 10:27:47 +10:00
if ( self :: DEBUG )
2023-06-28 19:50:24 +10:00
Log :: debug ( sprintf ( '%s:+ Emptying TX buffer with [%d] chars, and timeout [%d]' , self :: LOGKEY , strlen ( $this -> tx_buf ), $timeout ));
2021-04-01 21:59:15 +11:00
2023-06-28 19:50:24 +10:00
$tm = $this -> timer_set ( $timeout );
2021-04-01 21:59:15 +11:00
$rc = self :: OK ;
2023-06-28 19:50:24 +10:00
while ( strlen ( $this -> tx_buf )) {
2021-04-01 21:59:15 +11:00
$tv = $this -> timer_rest ( $tm );
2024-11-10 13:34:01 +11:00
if ( $rc = $this -> canSend ( $tv )) {
2023-07-19 10:27:47 +10:00
if ( self :: DEBUG )
2023-06-28 19:50:24 +10:00
Log :: debug ( sprintf ( '%s:- Chars to send [%d]' , self :: LOGKEY , strlen ( $this -> tx_buf )));
2022-12-03 01:00:45 +11:00
2023-06-28 19:50:24 +10:00
$sent = $this -> send ( substr ( $this -> tx_buf , 0 , self :: TX_SIZE ), 0 );
2021-04-01 21:59:15 +11:00
2023-07-19 10:27:47 +10:00
if ( self :: DEBUG )
2023-07-17 16:36:53 +10:00
Log :: debug ( sprintf ( '%s:- Sent [%d] chars [%s]' , self :: LOGKEY , $sent , Str :: limit ( $this -> tx_buf , 15 )));
2021-04-01 21:59:15 +11:00
2023-06-28 19:50:24 +10:00
$this -> tx_buf = substr ( $this -> tx_buf , $sent );
2021-04-01 21:59:15 +11:00
} else {
return $rc ;
}
// @todo Enable a delay for slow clients
//sleep(1);
if ( $this -> timer_expired ( $tm ))
return self :: ERROR ;
}
2023-06-28 19:50:24 +10:00
$this -> tx_purge ();
2021-06-16 22:26:08 +10:00
2021-04-01 21:59:15 +11:00
return $rc ;
}
/**
* @ param int $timeout
2024-11-10 13:34:01 +11:00
* @ return bool
2021-04-01 21:59:15 +11:00
* @ throws \Exception
2021-05-07 22:07:26 +10:00
*/
2024-11-10 13:34:01 +11:00
public function canSend ( int $timeout ) : bool
2021-05-07 22:07:26 +10:00
{
$write = [ $this -> connection ];
2024-11-10 13:34:01 +11:00
return $this -> socketSelect ( NULL , $write , NULL , $timeout ) > 0 ;
2021-05-07 22:07:26 +10:00
}
/**
* Close the connection with the client
*/
public function close () : void
{
2023-06-22 17:36:22 +10:00
try {
socket_shutdown ( $this -> connection );
} catch ( \ErrorException $e ) {
2023-06-28 19:50:24 +10:00
Log :: error ( sprintf ( '%s:! Shutting down socket [%s]' , self :: LOGKEY , $e -> getMessage ()));
2023-06-22 17:36:22 +10:00
}
try {
socket_close ( $this -> connection );
} catch ( \ErrorException $e ) {
2023-06-28 19:50:24 +10:00
Log :: error ( sprintf ( '%s:! Closing socket [%s]' , self :: LOGKEY , $e -> getMessage ()));
2023-06-22 17:36:22 +10:00
}
2021-05-07 22:07:26 +10:00
2024-09-09 23:49:04 +10:00
Log :: debug ( sprintf ( '%s:= Connection closed with [%s]' , self :: LOGKEY , $this -> address_remote ));
2021-05-07 22:07:26 +10:00
}
/**
2023-06-28 19:50:24 +10:00
* We have data in the buffer or on the socket
*
2021-05-07 22:07:26 +10:00
* @ param int $timeout
2024-11-10 13:34:01 +11:00
* @ return bool
2021-04-01 21:59:15 +11:00
* @ throws \Exception
2021-05-07 22:07:26 +10:00
*/
2024-11-10 13:34:01 +11:00
public function hasData ( int $timeout ) : bool
2021-05-07 22:07:26 +10:00
{
$read = [ $this -> connection ];
2024-11-10 13:34:01 +11:00
return ( $this -> rx_left ? : $this -> socketSelect ( $read , NULL , NULL , $timeout )) > 0 ;
2021-04-01 21:59:15 +11:00
}
/**
2023-06-28 19:50:24 +10:00
* Read data , emptying from the RX buffer first , then checking the socket .
2021-04-01 21:59:15 +11:00
*
2023-06-28 19:50:24 +10:00
* @ param int $timeout How long to wait for data
* @ param int $len The amount of data we want
2024-11-10 13:34:01 +11:00
* @ param int $flags
2023-08-10 11:08:55 +10:00
* @ return string | null
2021-04-01 21:59:15 +11:00
* @ throws SocketException
*/
2024-11-10 13:34:01 +11:00
public function read ( int $timeout , int $len = 1024 , int $flags = MSG_DONTWAIT ) : ? string
2021-04-01 21:59:15 +11:00
{
2023-04-23 23:08:30 +10:00
// We have data in our buffer
if ( $this -> rx_left >= $len ) {
2023-07-19 10:27:47 +10:00
if ( self :: DEBUG )
2023-06-28 19:50:24 +10:00
Log :: debug ( sprintf ( '%s:- Returning [%d] chars from the RX buffer' , self :: LOGKEY , $len ));
2023-04-23 23:08:30 +10:00
2023-06-28 19:50:24 +10:00
$result = substr ( $this -> rx_buf , 0 , $len );
2025-01-30 23:18:49 +11:00
if ( $flags !== MSG_PEEK )
$this -> rx_buf = substr ( $this -> rx_buf , strlen ( $result ));
2023-04-23 23:08:30 +10:00
2025-01-31 17:42:21 +11:00
Log :: debug ( sprintf ( '%s:*** ' , self :: LOGKEY ),[ 'iac_bin' => $this -> iac_bin ]);
2025-01-31 17:13:12 +11:00
// In case we are in Telnet Binary Mode
if ( $this -> iac_bin ) {
if ( self :: DEBUG )
2025-01-31 17:42:21 +11:00
Log :: debug ( sprintf ( '%s:- Telnet IAC Binary Mode, looking for ff ff' , self :: LOGKEY ),[ 'result' => hex_dump ( $result )]);
2025-01-31 17:13:12 +11:00
// if the last char is ff, we need to get the next char
if ( str_ends_with ( $result , " \xff " )) {
2025-01-31 17:42:21 +11:00
Log :: debug ( sprintf ( '%s: - We have a hit' , self :: LOGKEY ));
2025-01-31 17:13:12 +11:00
// If we have it in our buffer, just get it
if ( $this -> rx_left ) {
$result .= substr ( $this -> rx_buf , 0 , 1 );
$this -> rx_buf = substr ( $this -> rx_buf , 1 );
// Else put everything back into rx_buf, and increase len by 1
} else {
$this -> rx_buf = $result ;
$len ++ ;
2025-01-31 17:42:21 +11:00
$result = '' ;
2025-01-31 17:13:12 +11:00
}
}
if ( strlen ( $result ) > 1 )
$result = str_replace ( " \xff \xff " , " \xff " , $result );
2025-01-31 17:42:21 +11:00
if ( strlen ( $result ))
2025-01-31 17:13:12 +11:00
return $result ;
} else
return $result ;
2023-04-23 23:08:30 +10:00
}
2025-01-31 15:54:46 +11:00
if ( self :: DEBUG )
2025-01-31 17:42:21 +11:00
Log :: debug ( sprintf ( '%s:- Buffer doesnt have [%d] chars, it only has [%d], or it ends with 0xff' , self :: LOGKEY , $len , strlen ( $this -> rx_buf )),[ 'rx_buf' => hex_dump ( $this -> rx_buf )]);
2025-01-31 15:54:46 +11:00
2024-11-10 13:34:01 +11:00
if ( $timeout && ( ! $this -> hasData ( $timeout )))
throw new SocketException ( SocketException :: SOCKET_TIMEOUT , $timeout );
2021-04-01 21:59:15 +11:00
$buf = '' ;
2023-06-28 19:50:24 +10:00
2021-10-07 23:32:05 +11:00
try {
2023-06-28 19:50:24 +10:00
switch ( $this -> type ) {
case SOCK_STREAM :
2024-11-10 13:34:01 +11:00
$recv = socket_recv ( $this -> connection , $buf , self :: RX_SIZE , $flags );
2023-06-28 19:50:24 +10:00
break ;
2023-04-23 23:08:30 +10:00
2023-06-28 19:50:24 +10:00
case SOCK_DGRAM :
2024-11-10 13:34:01 +11:00
$recv = socket_recvfrom ( $this -> connection , $buf , self :: RX_SIZE , $flags , $this -> address_remote , $this -> port_remote );
2023-06-28 19:50:24 +10:00
break ;
default :
throw new SocketException ( SocketException :: SOCKET_ERROR , sprintf ( 'Unhandled socket type: %s' , $this -> type ));
2023-04-23 23:08:30 +10:00
}
2021-10-07 23:32:05 +11:00
} catch ( \Exception $e ) {
2023-09-20 22:26:35 +10:00
Log :: error ( sprintf ( '%s:! socket_recv Exception [%s]' , self :: LOGKEY , $e -> getMessage ()));
2021-10-07 23:32:05 +11:00
throw new SocketException ( $x = socket_last_error ( $this -> connection ), socket_strerror ( $x ));
}
2023-06-28 19:50:24 +10:00
// If we got no data, we'll send whatever is left in the buffer
if ( $recv === FALSE ) {
2023-04-23 23:08:30 +10:00
// If we have something in the buffer, we'll send it
2023-06-28 19:50:24 +10:00
if ( $this -> rx_left ) {
2023-07-19 10:27:47 +10:00
if ( self :: DEBUG )
2023-06-28 19:50:24 +10:00
Log :: debug ( sprintf ( '%s:- Network read return an error, returning final [%d] chars from the RX buffer' , self :: LOGKEY , strlen ( $this -> rx_buf )));
2023-04-23 23:08:30 +10:00
2023-06-28 19:50:24 +10:00
$result = $this -> rx_buf ;
2023-04-23 23:08:30 +10:00
$this -> rx_buf = '' ;
2023-06-28 19:50:24 +10:00
return $result ;
2023-04-23 23:08:30 +10:00
}
2023-09-06 14:11:18 +12:00
Log :: debug ( sprintf ( '%s:! Request to read [%d] chars resulted in no data' , self :: LOGKEY , $len ));
2021-04-01 21:59:15 +11:00
throw new SocketException ( $x = socket_last_error ( $this -> connection ), socket_strerror ( $x ));
2023-04-23 23:08:30 +10:00
}
2021-06-16 22:26:08 +10:00
// If our buffer is null, see if we have any out of band data.
2021-07-04 21:47:23 +10:00
// @todo We throw an errorexception when the socket is closed by the remote I think.
2023-06-28 19:50:24 +10:00
if (( $recv === 0 ) && is_null ( $buf ) && ( $this -> hasData ( 0 ) > 0 ) && $this -> type === SOCK_STREAM ) {
2021-07-04 21:47:23 +10:00
try {
2023-06-28 19:50:24 +10:00
socket_recv ( $this -> connection , $buf , $len , MSG_OOB );
2021-07-04 21:47:23 +10:00
} catch ( \Exception $e ) {
throw new SocketException ( $x = socket_last_error ( $this -> connection ), socket_strerror ( $x ));
}
}
2021-06-16 22:26:08 +10:00
2025-01-31 16:22:43 +11:00
if ( $flags === MSG_PEEK ) {
2025-01-31 16:33:43 +11:00
Log :: debug ( sprintf ( '%s:- Returning [%d] chars as a result of a PEEK operation, buffer would have [%d], but still has [%d]' , self :: LOGKEY , $len , strlen ( $this -> rx_buf . $buf ), strlen ( $this -> rx_buf )),[ 'rx_buf' => hex_dump ( $this -> rx_buf ), 'buf' => hex_dump ( $buf )]);
2025-01-31 16:22:43 +11:00
return substr ( $this -> rx_buf . $buf , 0 , $len );
}
2025-01-31 16:33:43 +11:00
$this -> rx_buf .= $buf ;
2023-07-19 10:27:47 +10:00
if ( self :: DEBUG )
2023-07-17 16:36:53 +10:00
Log :: debug ( sprintf ( '%s:- Added [%d] chars to the RX buffer' , self :: LOGKEY , strlen ( $buf )),[ 'rx_buf' => hex_dump ( $this -> rx_buf )]);
2023-06-28 19:50:24 +10:00
// Loop again and return the data, now that it is in the RX buffer
2025-01-30 23:18:49 +11:00
return $this -> read ( $timeout , $len , $flags );
2021-04-01 21:59:15 +11:00
}
/**
* Read a character from the remote .
* We ' ll buffer everything received
*
* @ param int $timeout
* @ return int
2023-04-23 23:08:30 +10:00
* @ throws \Exception
2021-04-01 21:59:15 +11:00
*/
public function read_ch ( int $timeout ) : int
{
2024-11-10 13:34:01 +11:00
if ( $this -> hasData ( $timeout ))
2023-06-28 19:50:24 +10:00
$ch = $this -> read ( $timeout , 1 );
2021-04-01 21:59:15 +11:00
2024-11-10 13:34:01 +11:00
else
throw new SocketException ( SocketException :: SOCKET_TIMEOUT , $timeout );
2021-04-01 21:59:15 +11:00
2025-01-30 23:18:49 +11:00
if ( self :: DEBUG )
Log :: debug ( sprintf ( '%s:+ read_ch [%c] (%x)' , self :: LOGKEY , $ch , ord ( $ch )));
2023-06-28 19:50:24 +10:00
return ord ( $ch );
}
2021-04-01 21:59:15 +11:00
2023-06-28 19:50:24 +10:00
public function rx_purge () : void
{
2023-07-19 10:27:47 +10:00
if ( self :: DEBUG )
2023-06-28 19:50:24 +10:00
Log :: debug ( sprintf ( '%s:+ Discarding [%d] chars from the RX buffer' , self :: LOGKEY , strlen ( $this -> tx_buf )));
2021-04-01 21:59:15 +11:00
2023-06-28 19:50:24 +10:00
$this -> rx_buf = '' ;
2021-04-01 21:59:15 +11:00
}
2023-06-28 19:50:24 +10:00
/**
* Clear our TX buffer
*/
public function tx_purge () : void
2021-04-01 21:59:15 +11:00
{
2023-07-19 10:27:47 +10:00
if ( self :: DEBUG )
2023-06-28 19:50:24 +10:00
Log :: debug ( sprintf ( '%s:+ Discarding [%d] chars from the TX buffer' , self :: LOGKEY , strlen ( $this -> tx_buf )));
$this -> tx_buf = '' ;
2021-05-07 22:07:26 +10:00
}
/**
* Send data to the client
*
2023-07-17 16:36:53 +10:00
* @ param string $message
2021-05-07 22:07:26 +10:00
* @ param int $timeout
2024-11-10 13:34:01 +11:00
* @ return int | bool
2021-04-01 21:59:15 +11:00
* @ throws \Exception
2021-05-07 22:07:26 +10:00
*/
2024-11-10 13:34:01 +11:00
public function send ( string $message , int $timeout ) : int | bool
2021-04-01 21:59:15 +11:00
{
2024-11-10 13:34:01 +11:00
if ( $timeout && ( ! $rc = $this -> canSend ( $timeout )))
2021-05-07 22:07:26 +10:00
return $rc ;
2023-07-19 10:27:47 +10:00
if ( self :: DEBUG )
2023-06-28 19:50:24 +10:00
Log :: debug ( sprintf ( '%s:- Sending [%d] chars [%s]' , self :: LOGKEY , strlen ( $message ), Str :: limit ( $message , 15 )));
2025-01-31 17:42:21 +11:00
if ( $this -> iac_bin ) {
Log :: debug ( sprintf ( '%s:- IAC_BIN mode, looking for 0xff' , self :: LOGKEY ));
$message = str_replace ( " \xff " , " \xff \xff " , $message );
}
2023-06-28 19:50:24 +10:00
switch ( $this -> type ) {
case SOCK_STREAM :
return socket_write ( $this -> connection , $message , strlen ( $message ));
case SOCK_DGRAM :
return socket_sendto ( $this -> connection , $message , strlen ( $message ), 0 , $this -> address_remote , $this -> port_remote );
2021-05-07 22:07:26 +10:00
2023-06-28 19:50:24 +10:00
default :
throw new SocketException ( SocketException :: SOCKET_ERROR , sprintf ( 'Unhandled socket type: %s' , $this -> type ));
}
2021-05-07 22:07:26 +10:00
}
/**
2021-04-01 21:59:15 +11:00
* Wait for data on a socket
2021-05-07 22:07:26 +10:00
*
2021-04-01 21:59:15 +11:00
* @ param array | null $read
* @ param array | null $write
* @ param array | null $except
2021-05-07 22:07:26 +10:00
* @ param int $timeout
2021-04-01 21:59:15 +11:00
* @ return int
* @ throws \Exception
2021-05-07 22:07:26 +10:00
*/
2021-04-01 21:59:15 +11:00
private function socketSelect ( ? array $read , ? array $write , ? array $except , int $timeout ) : int
2021-05-07 22:07:26 +10:00
{
2021-04-01 21:59:15 +11:00
$rc = socket_select ( $read , $write , $except , $timeout );
2021-05-07 22:07:26 +10:00
2021-04-01 21:59:15 +11:00
if ( $rc === FALSE )
throw new \Exception ( 'Socket Error: ' . socket_strerror ( socket_last_error ()));
2021-05-07 22:07:26 +10:00
2023-07-19 10:27:47 +10:00
if ( self :: DEBUG )
2023-06-28 19:50:24 +10:00
Log :: debug ( sprintf ( '%s:= Socket select returned [%d] with timeout (%d)' , self :: LOGKEY , $rc , $timeout ),[ 'read' => $read , 'write' => $write , 'except' => $except ]);
2021-04-01 21:59:15 +11:00
2021-06-16 22:26:08 +10:00
return $rc ;
2021-04-01 21:59:15 +11:00
}
2021-05-07 22:07:26 +10:00
2021-04-01 21:59:15 +11:00
public function timer_expired ( int $timer ) : int
{
2023-06-28 19:50:24 +10:00
return ( time () >= $timer );
2021-04-01 21:59:15 +11:00
}
public function timer_rest ( int $timer ) : int
{
2023-06-28 19:50:24 +10:00
return $timer - time ();
2021-04-01 21:59:15 +11:00
}
public function timer_set ( int $expire ) : int
{
2023-06-28 19:50:24 +10:00
return time () + $expire ;
2021-04-01 21:59:15 +11:00
}
/**
2023-06-16 23:18:35 +10:00
* See if there is data waiting to collect , or if we can send
2021-04-01 21:59:15 +11:00
*
* @ param bool $read
* @ param bool $write
* @ param int $timeout
* @ return int
* @ throws \Exception
2023-06-28 19:50:24 +10:00
* @ deprecated use canSend or hasData
2021-04-01 21:59:15 +11:00
*/
public function ttySelect ( bool $read , bool $write , int $timeout ) : int
{
2023-06-28 19:50:24 +10:00
if ( $this -> rx_left ) {
2023-07-19 10:27:47 +10:00
if ( self :: DEBUG )
2023-06-28 19:50:24 +10:00
Log :: debug ( sprintf ( '%s:= We still have [%d] chars in the RX buffer.' , self :: LOGKEY , $this -> rx_left ));
2023-06-16 23:18:35 +10:00
return 1 ;
2023-06-28 19:50:24 +10:00
}
2023-06-16 23:18:35 +10:00
2021-04-01 21:59:15 +11:00
$read = $read ? [ $this -> connection ] : NULL ;
$write = $write ? [ $this -> connection ] : NULL ;
2021-05-07 22:07:26 +10:00
2021-04-01 21:59:15 +11:00
return $this -> socketSelect ( $read , $write , NULL , $timeout );
2021-05-07 22:07:26 +10:00
}
2024-11-27 16:05:11 +11:00
}