Add Zmodem/BINKP/EMSI
This commit is contained in:
@@ -2,23 +2,199 @@
|
||||
|
||||
namespace App\Classes\Sock;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Class SocketClient
|
||||
*
|
||||
* @package App\Classes\Sock
|
||||
* @property int speed
|
||||
* @property int cps
|
||||
*/
|
||||
final class SocketClient {
|
||||
public \Socket $connection; // @todo make private
|
||||
// For deep debugging
|
||||
private bool $DEBUG = FALSE;
|
||||
|
||||
private \Socket $connection;
|
||||
private string $address = '';
|
||||
private int $port = 0;
|
||||
|
||||
public function __construct (\Socket $connection) {
|
||||
// Our session state
|
||||
private array $session = [];
|
||||
|
||||
private const OK = 0;
|
||||
private const EOF = -1;
|
||||
private const TIMEOUT = -2;
|
||||
private const RCDO = -3;
|
||||
private const GCOUNT = -4;
|
||||
private const ERROR = -5;
|
||||
|
||||
private const TTY_SUCCESS = self::OK;
|
||||
private const TTY_TIMEOUT = self::TIMEOUT;
|
||||
private const TTY_HANGUP = self::RCDO;
|
||||
private const TTY_ERROR = self::ERROR;
|
||||
|
||||
public const TCP_SPEED = 115200;
|
||||
|
||||
// Buffer for sending
|
||||
private const TX_BUF_SIZE = (0x8100);
|
||||
private int $tx_ptr = 0;
|
||||
private int $tx_free = self::TX_BUF_SIZE;
|
||||
private int $tty_status = 0;
|
||||
private string $tx_buf = '';
|
||||
|
||||
// Buffer for receiving
|
||||
private const RX_BUF_SIZE = (0x8100);
|
||||
private int $rx_ptr = 0;
|
||||
private int $rx_left = 0;
|
||||
private string $rx_buf = '';
|
||||
|
||||
public function __construct (\Socket $connection,int $speed=self::TCP_SPEED) {
|
||||
socket_getsockname($connection,$this->address,$this->port);
|
||||
Log::info(sprintf('Connection from [%s] on port [%d]',$this->address,$this->port),['m'=>__METHOD__]);
|
||||
Log::info(sprintf('%s: + Connection from [%s] on port [%d]',__METHOD__,$this->address,$this->port));
|
||||
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
public function __get($key) {
|
||||
switch ($key) {
|
||||
case 'cps':
|
||||
case 'speed':
|
||||
return Arr::get($this->session,$key);
|
||||
|
||||
default:
|
||||
throw new \Exception(sprintf('%s: Unknown key [%s]:',__METHOD__,$key));
|
||||
}
|
||||
}
|
||||
|
||||
public function __set($key,$value) {
|
||||
switch ($key) {
|
||||
case 'cps':
|
||||
case 'speed':
|
||||
return $this->session[$key] = $value;
|
||||
|
||||
default:
|
||||
throw new \Exception(sprintf('%s: Unknown key [%s]:',__METHOD__,$key));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
if ($this->DEBUG)
|
||||
Log::debug(sprintf('%s: + Start [%s] (%d)',__METHOD__,$data,strlen($data)));
|
||||
|
||||
//$rc = self::OK;
|
||||
//$tx_ptr = self::TX_BUF_SIZE-$this->tx_free;
|
||||
$ptr = 0;
|
||||
$num_bytes = strlen($data);
|
||||
$this->tty_status = self::TTY_SUCCESS;
|
||||
|
||||
while ($num_bytes) {
|
||||
if ($this->DEBUG)
|
||||
Log::debug(sprintf('%s: - Num Bytes [%d]: TX Free [%d]',__METHOD__,$num_bytes,$this->tx_free));
|
||||
|
||||
if ($num_bytes > $this->tx_free) {
|
||||
do {
|
||||
$this->buffer_flush(5);
|
||||
|
||||
if ($this->tty_status == self::TTY_SUCCESS) {
|
||||
$n = min($this->tx_free,$num_bytes);
|
||||
$this->tx_buf = substr($data,$ptr,$n);
|
||||
$this->tx_free -= $n;
|
||||
$num_bytes -= $n;
|
||||
$ptr += $n;
|
||||
}
|
||||
|
||||
} while ($this->tty_status != self::TTY_SUCCESS);
|
||||
|
||||
} else {
|
||||
if ($this->DEBUG)
|
||||
Log::debug(sprintf('%s: - Remaining data to send [%d]',__METHOD__,$num_bytes));
|
||||
|
||||
$this->tx_buf .= substr($data,$ptr,$num_bytes);
|
||||
$this->tx_free -= $num_bytes;
|
||||
$num_bytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->DEBUG)
|
||||
Log::debug(sprintf('%s: = End [%s]',__METHOD__,strlen($this->tx_buf)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear our TX buffer
|
||||
*/
|
||||
public function buffer_clear(): void
|
||||
{
|
||||
$this->tx_buf = '';
|
||||
$this->tx_free = self::TX_BUF_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty our TX buffer
|
||||
*
|
||||
* @param int $timeout
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function buffer_flush(int $timeout): int
|
||||
{
|
||||
if ($this->DEBUG)
|
||||
Log::debug(sprintf('%s: + Start [%d]',__METHOD__,$timeout));
|
||||
|
||||
$rc = self::OK;
|
||||
$tx_ptr = 0;
|
||||
$restsize = self::TX_BUF_SIZE-$this->tx_free;
|
||||
|
||||
$tm = $this->timer_set($timeout);
|
||||
while (self::TX_BUF_SIZE != $this->tx_free) {
|
||||
$tv = $this->timer_rest($tm);
|
||||
|
||||
if ($rc = $this->canSend($tv)>0) {
|
||||
if ($this->DEBUG)
|
||||
Log::debug(sprintf('%s: - Sending [%d]',__METHOD__,$restsize));
|
||||
$rc = $this->send(substr($this->tx_buf,$tx_ptr,$restsize),0);
|
||||
Log::debug(sprintf('%s: - Sent [%d] (%s)',__METHOD__,$rc,Str::limit($this->tx_buf,15)));
|
||||
|
||||
if ($rc == $restsize) {
|
||||
$this->tx_buf = '';
|
||||
$tx_ptr = 0;
|
||||
$this->tx_free += $rc;
|
||||
$this->buffer_clear();
|
||||
|
||||
} else if ($rc > 0) {
|
||||
$tx_ptr += $rc;
|
||||
$restsize -= $rc;
|
||||
}
|
||||
|
||||
} else {
|
||||
return $rc;
|
||||
}
|
||||
|
||||
// @todo Enable a delay for slow clients
|
||||
//sleep(1);
|
||||
if ($this->timer_expired($tm))
|
||||
return self::ERROR;
|
||||
}
|
||||
|
||||
if ($this->DEBUG)
|
||||
Log::debug(sprintf('%s: = End [%d]',__METHOD__,$rc));
|
||||
return $rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $timeout
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function canSend(int $timeout): int
|
||||
{
|
||||
@@ -38,11 +214,15 @@ final class SocketClient {
|
||||
|
||||
/**
|
||||
* Create a client socket
|
||||
* @param string $address
|
||||
* @param int $port
|
||||
* @param int $speed
|
||||
* @return static
|
||||
* @throws SocketException
|
||||
*/
|
||||
public static function create(string $address,int $port): self
|
||||
public static function create(string $address,int $port,int $speed=self::TCP_SPEED): self
|
||||
{
|
||||
Log::debug(sprintf('Creating connection to [%s:%d]',$address,$port));
|
||||
Log::debug(sprintf('%s: + Creating connection to [%s:%d]',__METHOD__,$address,$port));
|
||||
|
||||
$address = gethostbyname($address);
|
||||
|
||||
@@ -55,13 +235,14 @@ final class SocketClient {
|
||||
if ($result === FALSE)
|
||||
throw new SocketException(SocketException::CANT_CONNECT,socket_strerror(socket_last_error($socket)));
|
||||
|
||||
return new self($socket);
|
||||
return new self($socket,$speed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the client's address
|
||||
*
|
||||
* @return string
|
||||
* @todo change to __get()
|
||||
*/
|
||||
public function getAddress(): string
|
||||
{
|
||||
@@ -72,6 +253,7 @@ final class SocketClient {
|
||||
* Return the port in use
|
||||
*
|
||||
* @return int
|
||||
* @todo change to __get()
|
||||
*/
|
||||
public function getPort(): int
|
||||
{
|
||||
@@ -82,17 +264,96 @@ final class SocketClient {
|
||||
* @param int $timeout
|
||||
* @return int
|
||||
* @note use socketSelect()
|
||||
* @todo Node used by bink yet?
|
||||
* @todo to test
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function hasData(int $timeout): int
|
||||
{
|
||||
$read = [$this->connection];
|
||||
$write = $except = NULL;
|
||||
|
||||
//$rc = socket_select($read,$write,$except,$timeout);
|
||||
//return $rc;
|
||||
return $this->socketSelect($read,NULL,NULL,$timeout);
|
||||
return $this->rx_left ?: $this->socketSelect($read,NULL,NULL,$timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read data from the socket.
|
||||
* If we only want 1 character, we'll return the ASCII value of the data received
|
||||
*
|
||||
* @param int $timeout
|
||||
* @param int $len
|
||||
* @return int|string
|
||||
* @throws SocketException
|
||||
*/
|
||||
public function read(int $timeout,int $len=1024)
|
||||
{
|
||||
if ($this->DEBUG)
|
||||
Log::debug(sprintf('%s: + Start [%d] (%d)',__METHOD__,$len,$timeout));
|
||||
|
||||
if ($timeout AND ($this->hasData($timeout) === 0))
|
||||
return '';
|
||||
|
||||
$buf = '';
|
||||
$rc = socket_recv($this->connection,$buf, $len,MSG_DONTWAIT);
|
||||
if ($this->DEBUG)
|
||||
Log::debug(sprintf('%s: - Read [%d]',__METHOD__,$rc));
|
||||
|
||||
if ($rc === FALSE)
|
||||
throw new SocketException($x=socket_last_error($this->connection),socket_strerror($x));
|
||||
|
||||
return is_null($buf) ? '' : $buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a character from the remote.
|
||||
* We'll buffer everything received
|
||||
*
|
||||
* @param int $timeout
|
||||
* @return int
|
||||
* @throws SocketException
|
||||
*/
|
||||
public function read_ch(int $timeout): int
|
||||
{
|
||||
if ($this->DEBUG)
|
||||
Log::debug(sprintf('%s: + Start [%d]',__METHOD__,$timeout),['rx_left'=>$this->rx_left,'rx_ptr'=>$this->rx_ptr]);
|
||||
|
||||
// If our buffer is empty, we'll try and read from the remote
|
||||
if ($this->rx_left == 0) {
|
||||
if ($this->hasData($timeout) > 0) {
|
||||
try {
|
||||
if (! strlen($this->rx_buf = $this->read(0,self::RX_BUF_SIZE))) {
|
||||
Log::debug(sprintf('%s: - Nothing read',__METHOD__));
|
||||
|
||||
return self::TTY_TIMEOUT;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ($e->getCode() == 11) ? self::TTY_TIMEOUT : self::ERROR;
|
||||
}
|
||||
|
||||
if ($this->DEBUG)
|
||||
Log::info(sprintf('%s: - Read [%d] bytes',__METHOD__,strlen($this->rx_buf)));
|
||||
|
||||
$this->rx_ptr = 0;
|
||||
$this->rx_left = strlen($this->rx_buf);
|
||||
|
||||
} else {
|
||||
return self::TTY_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
$rc = ord(substr($this->rx_buf,$this->rx_ptr,1));
|
||||
|
||||
$this->rx_left--;
|
||||
$this->rx_ptr++;
|
||||
|
||||
if ($this->DEBUG)
|
||||
Log::debug(sprintf('%s: = Return [%x] (%c)',__METHOD__,$rc,$rc));
|
||||
|
||||
return $rc;
|
||||
}
|
||||
|
||||
public function rx_purge(): void
|
||||
{
|
||||
$this->rx_ptr = $this->rx_left = 0;
|
||||
$this->rx_buf = '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,8 +363,10 @@ final class SocketClient {
|
||||
* @param int $timeout
|
||||
* @param null $length
|
||||
* @return false|int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function send($message,int $timeout,$length=NULL) {
|
||||
public function send($message,int $timeout,$length=NULL)
|
||||
{
|
||||
if ($timeout AND (! $rc = $this->canSend($timeout)))
|
||||
return $rc;
|
||||
|
||||
@@ -113,33 +376,77 @@ final class SocketClient {
|
||||
return socket_write($this->connection,$message,$length);
|
||||
}
|
||||
|
||||
private function socketSelect(?array $read,?array $write,?array $except,int $timeout): int
|
||||
/**
|
||||
* Set our speed
|
||||
*
|
||||
* @param int $value
|
||||
* @todo change to __set()
|
||||
*/
|
||||
public function setSpeed(int $value): void
|
||||
{
|
||||
return socket_select($read,$write,$except,$timeout);
|
||||
$this->speed = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read data from the socket.
|
||||
* If we only want 1 character, we'll return the ASCII value of the data received
|
||||
* Wait for data on a socket
|
||||
*
|
||||
* @param array|null $read
|
||||
* @param array|null $write
|
||||
* @param array|null $except
|
||||
* @param int $timeout
|
||||
* @param int $len
|
||||
* @return false|int|string|null
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function read(int $timeout,int $len=1024)
|
||||
private function socketSelect(?array $read,?array $write,?array $except,int $timeout): int
|
||||
{
|
||||
Log::debug(sprintf('+ Start [%d]',$len),['m'=>__METHOD__]);
|
||||
$rc = socket_select($read,$write,$except,$timeout);
|
||||
|
||||
if ($timeout AND (! $rc = $this->hasData($timeout)))
|
||||
return $rc;
|
||||
if ($rc === FALSE)
|
||||
throw new \Exception('Socket Error: '.socket_strerror(socket_last_error()));
|
||||
|
||||
if (($buf=socket_read($this->connection,$len,PHP_BINARY_READ)) === FALSE) {
|
||||
return NULL;
|
||||
}
|
||||
return $rc;
|
||||
}
|
||||
|
||||
Log::debug(sprintf(' - Read [%d]',strlen($buf)),['m'=>__METHOD__]);
|
||||
/**
|
||||
* Return our speed in bps
|
||||
*
|
||||
* @return int
|
||||
* @todo change to __get()
|
||||
*/
|
||||
public function speed(): int
|
||||
{
|
||||
return $this->speed;
|
||||
}
|
||||
|
||||
// For single character reads, we'll return the ASCII value of the buf
|
||||
return ($len == 1 and (ord($buf) != 0)) ? ord($buf) : $buf;
|
||||
public function timer_expired(int $timer): int
|
||||
{
|
||||
return (time()>=$timer);
|
||||
}
|
||||
|
||||
public function timer_rest(int $timer): int
|
||||
{
|
||||
return (($timer)-time());
|
||||
}
|
||||
|
||||
public function timer_set(int $expire): int
|
||||
{
|
||||
return (time()+$expire);
|
||||
}
|
||||
|
||||
/**
|
||||
* See if we there is data waiting to collect, or if we can send
|
||||
*
|
||||
* @param bool $read
|
||||
* @param bool $write
|
||||
* @param int $timeout
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function ttySelect(bool $read,bool $write, int $timeout): int
|
||||
{
|
||||
$read = $read ? [$this->connection] : NULL;
|
||||
$write = $write ? [$this->connection] : NULL;
|
||||
|
||||
return $this->socketSelect($read,$write,NULL,$timeout);
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,8 @@ final class SocketException extends \Exception {
|
||||
public const CANT_LISTEN = 3;
|
||||
public const CANT_ACCEPT = 4;
|
||||
public const CANT_CONNECT = 5;
|
||||
public const SOCKET_ERROR = 6;
|
||||
public const SOCKET_EAGAIN = 11;
|
||||
|
||||
private array $messages = [
|
||||
self::CANT_CREATE_SOCKET => 'Can\'t create socket: "%s"',
|
||||
@@ -15,6 +17,8 @@ final class SocketException extends \Exception {
|
||||
self::CANT_LISTEN => 'Can\'t listen: "%s"',
|
||||
self::CANT_ACCEPT => 'Can\'t accept connections: "%s"',
|
||||
self::CANT_CONNECT => 'Can\'t connect: "%s"',
|
||||
self::SOCKET_ERROR => 'Socket Error: "%s"',
|
||||
self::SOCKET_EAGAIN => 'Socket Resource Temporarily Unavailable - Try again',
|
||||
];
|
||||
|
||||
public function __construct(int $code,string $params=NULL) {
|
||||
|
Reference in New Issue
Block a user