clrghouz/app/Classes/Protocol.php

380 lines
11 KiB
PHP
Raw Normal View History

2021-04-01 10:59:15 +00:00
<?php
namespace App\Classes;
use Exception;
use Illuminate\Support\Collection;
2021-04-01 10:59:15 +00:00
use Illuminate\Support\Facades\Log;
use App\Classes\File\{Receive,Send};
use App\Classes\Sock\SocketClient;
use App\Classes\Sock\SocketException;
use App\Models\{Address,Setup,System};
2021-04-01 10:59:15 +00:00
abstract class Protocol
{
// Enable extra debugging
protected bool $DEBUG = FALSE;
private const LOGKEY = 'P--';
2021-04-01 10:59:15 +00:00
// Return constants
protected const OK = 0;
protected const EOF = -1;
protected const TIMEOUT = -2;
protected const RCDO = -3;
protected const GCOUNT = -4;
protected const ERROR = -5;
// Our sessions Types
public const SESSION_AUTO = 0;
public const SESSION_EMSI = 1;
public const SESSION_BINKP = 2;
public const SESSION_ZMODEM = 3;
protected const MAX_PATH = 1024;
/* 9 most right bits are zeros */
private const O_BASE = 9; /* First 9 bits are protocol */
protected const O_NRQ = (1<<self::O_BASE); /* 0000 0000 0000 0000 0010 0000 0000 BOTH - No file requests accepted by this system */
protected const O_HRQ = (1<<(self::O_BASE+1)); /* 0000 0000 0000 0000 0100 0000 0000 BOTH - Hold file requests (not processed at this time). */
protected const O_FNC = (1<<(self::O_BASE+2)); /* 0000 0000 0000 0000 1000 0000 0000 - Filename conversion, transmitted files must be 8.3 */
protected const O_XMA = (1<<(self::O_BASE+3)); /* 0000 0000 0000 0001 0000 0000 0000 - Supports other forms of compressed mail */
protected const O_HAT = (1<<(self::O_BASE+4)); /* 0000 0000 0000 0010 0000 0000 0000 BOTH - Hold ALL files (Answering System) */
protected const O_HXT = (1<<(self::O_BASE+5)); /* 0000 0000 0000 0100 0000 0000 0000 BOTH - Hold Mail traffic */
protected const O_NPU = (1<<(self::O_BASE+6)); /* 0000 0000 0000 1000 0000 0000 0000 - No files pickup desired (Calling System) */
protected const O_PUP = (1<<(self::O_BASE+7)); /* 0000 0000 0001 0000 0000 0000 0000 - Pickup files for primary address only */
protected const O_PUA = (1<<(self::O_BASE+8)); /* 0000 0000 0010 0000 0000 0000 0000 EMSI - Pickup files for all presented addresses */
protected const O_PWD = (1<<(self::O_BASE+9)); /* 0000 0000 0100 0000 0000 0000 0000 BINK - Node needs to be password validated */
protected const O_BAD = (1<<(self::O_BASE+10)); /* 0000 0000 1000 0000 0000 0000 0000 BOTH - Node invalid password presented */
2021-04-01 10:59:15 +00:00
protected const O_RH1 = (1<<(self::O_BASE+11)); /* 0000 0001 0000 0000 0000 0000 0000 EMSI - Use RH1 for Hydra (files-after-freqs) */
protected const O_LST = (1<<(self::O_BASE+12)); /* 0000 0010 0000 0000 0000 0000 0000 BOTH - Node is nodelisted */
protected const O_INB = (1<<(self::O_BASE+13)); /* 0000 0100 0000 0000 0000 0000 0000 BOTH - Inbound session */
protected const O_TCP = (1<<(self::O_BASE+14)); /* 0000 1000 0000 0000 0000 0000 0000 BOTH - TCP session */
protected const O_EII = (1<<(self::O_BASE+15)); /* 0001 0000 0000 0000 0000 0000 0000 EMSI - Remote understands EMSI-II */
// Session Status
protected const S_OK = 0;
protected const S_NODIAL = 1;
protected const S_REDIAL = 2;
protected const S_BUSY = 3;
protected const S_FAILURE = 4;
protected const S_MASK = 7;
protected const S_HOLDR = 8;
protected const S_HOLDX = 16;
protected const S_HOLDA = 32;
protected const S_ADDTRY = 64;
protected const S_ANYHOLD = (self::S_HOLDR|self::S_HOLDX|self::S_HOLDA);
// File transfer status
protected const FOP_OK = 0;
protected const FOP_CONT = 1;
protected const FOP_SKIP = 2;
protected const FOP_ERROR = 3;
protected const FOP_SUSPEND = 4;
protected const MO_CHAT = 4;
protected SocketClient $client; /* Our socket details */
protected ?Setup $setup; /* Our setup */
2021-04-01 10:59:15 +00:00
protected Node $node; /* The node we are communicating with */
protected Send $send; /* The list of files we are sending */
protected Receive $recv; /* The list of files we are receiving */
private int $options; /* Our options for a session */
private int $session; /* Tracks where we are up to with this session */
protected bool $originate; /* Are we originating a connection */
private array $comms; /* Our comms details */
abstract protected function protocol_init(): int;
2021-04-01 10:59:15 +00:00
abstract protected function protocol_session(): int;
public function __construct(Setup $o=NULL)
{
if ($o && ! $o->system->addresses->count())
throw new Exception('We dont have any FTN addresses assigned');
$this->setup = $o;
}
2021-04-01 10:59:15 +00:00
/**
* @throws Exception
*/
public function __get($key)
{
switch ($key) {
case 'ls_SkipGuard': /* double-skip protection on/off */
case 'rxOptions': /* Options from ZRINIT header */
case 'socket_error':
2021-04-01 10:59:15 +00:00
return $this->comms[$key] ?? 0;
case 'ls_rxAttnStr':
return $this->comms[$key] ?? '';
default:
throw new Exception('Unknown key: '.$key);
}
}
/**
* @throws Exception
*/
public function __set($key,$value)
{
switch ($key) {
case 'ls_rxAttnStr':
case 'ls_SkipGuard':
case 'rxOptions':
case 'socket_error':
2021-04-01 10:59:15 +00:00
$this->comms[$key] = $value;
break;
default:
throw new Exception('Unknown key: '.$key);
}
}
/**
* We got an error, close anything we are have open
*
* @throws Exception
*/
protected function error_close(): void
{
if ($this->send->fd)
$this->send->close(FALSE);
2021-04-01 10:59:15 +00:00
if ($this->recv->fd)
$this->recv->close();
}
2021-04-01 10:59:15 +00:00
/**
* Incoming Protocol session
*
* @param SocketClient $client
* @return int|null
* @throws SocketException
*/
public function onConnect(SocketClient $client): ?int
{
$pid = pcntl_fork();
if ($pid == -1)
throw new SocketException(SocketException::CANT_ACCEPT,'Could not fork process');
Log::debug(sprintf('%s:= End [%d]',self::LOGKEY,$pid));
2021-04-01 10:59:15 +00:00
// Parent return ready for next connection
return $pid;
}
protected function optionClear(int $key): void
{
$this->options &= ~$key;
}
protected function optionGet(int $key): int
{
return ($this->options & $key);
}
protected function optionSet(int $key): void
{
$this->options |= $key;
}
/**
* Our addresses to send to the remote
* @return Collection
*/
protected function our_addresses(): Collection
{
if ($this->setup->optionGet(Setup::O_HIDEAKA)) {
$addresses = collect();
foreach (($this->originate ? $this->node->aka_remote_authed : $this->node->aka_remote) as $ao)
$addresses = $addresses->merge($this->setup->system->match($ao->zone,Address::NODE_ZC|Address::NODE_RC|Address::NODE_NC|Address::NODE_HC|Address::NODE_ACTIVE|Address::NODE_PVT|Address::NODE_POINT));
$addresses = $addresses->unique();
Log::debug(sprintf('%s: - Presenting limited AKAs [%s]',self::LOGKEY,$addresses->pluck('ftn')->join(',')));
} else {
$addresses = $this->setup->system->addresses;
Log::debug(sprintf('%s: - Presenting ALL our AKAs [%s]',self::LOGKEY,$addresses->pluck('ftn')->join(',')));
}
return $addresses;
}
2021-04-01 10:59:15 +00:00
/**
* Initialise our Session
*
* @param int $type
* @param SocketClient $client
* @param Address|null $o
2021-04-01 10:59:15 +00:00
* @return int
* @throws Exception
*/
public function session(int $type,SocketClient $client,Address $o=NULL): int
2021-04-01 10:59:15 +00:00
{
if ($o->exists)
Log::withContext(['ftn'=>$o->ftn]);
Log::debug(sprintf('%s:+ Start [%d]',self::LOGKEY,$type));
2021-04-01 10:59:15 +00:00
// This sessions options
$this->options = 0;
$this->session = 0;
// Our files that we are sending/receive
$this->send = new Send;
$this->recv = new Receive;
if ($o) {
// The node we are communicating with
$this->node = new Node;
$this->originate = $o->exists;
// If we are connecting to a node
if ($o->exists) {
$this->node->originate($o);
} else {
$this->optionSet(self::O_INB);
}
}
// We are an IP node
$this->optionSet(self::O_TCP);
$this->setClient($client);
switch ($type) {
/** @noinspection PhpMissingBreakStatementInspection */
case self::SESSION_AUTO:
Log::debug(sprintf('%s: - Trying EMSI',self::LOGKEY));
2021-04-01 10:59:15 +00:00
$rc = $this->protocol_init();
if ($rc < 0) {
Log::error(sprintf('%s: ! Unable to start EMSI [%d]',self::LOGKEY,$rc));
2021-04-01 10:59:15 +00:00
return self::S_REDIAL | self::S_ADDTRY;
}
case self::SESSION_EMSI:
Log::debug(sprintf('%s: - Starting EMSI',self::LOGKEY));
2021-04-01 10:59:15 +00:00
$rc = $this->protocol_session();
break;
case self::SESSION_BINKP:
Log::debug(sprintf('%s: - Starting BINKP',self::LOGKEY));
2021-04-01 10:59:15 +00:00
$rc = $this->protocol_session();
break;
case self::SESSION_ZMODEM:
Log::debug(sprintf('%s: - Starting ZMODEM',self::LOGKEY));
2021-04-01 10:59:15 +00:00
$this->client->speed = SocketClient::TCP_SPEED;
$this->originate = FALSE;
return $this->protocol_session();
default:
Log::error(sprintf('%s: ! Unsupported session type [%d]',self::LOGKEY,$type));
2021-04-01 10:59:15 +00:00
return self::S_REDIAL | self::S_ADDTRY;
}
// @todo Unlock outbounds
// @todo These flags determine when we connect to the remote.
// If the remote indicated that they dont support file requests (NRQ) or temporarily hold them (HRQ)
if (($this->node->optionGet(self::O_NRQ) && (! $this->setup->ignore_nrq)) || $this->node->optionGet(self::O_HRQ))
$rc |= self::S_HOLDR;
if ($this->optionGet(self::O_HXT))
$rc |= self::S_HOLDX;
if ($this->optionGet(self::O_HAT))
$rc |= self::S_HOLDA;
Log::info(sprintf('%s: Total: %s - %d:%02d:%02d online, (%d) %lu%s sent, (%d) %lu%s received - %s',
self::LOGKEY,
$this->node->address ? $this->node->address->ftn : 'Unknown',
2021-04-01 10:59:15 +00:00
$this->node->session_time/3600,
$this->node->session_time%3600/60,
$this->node->session_time%60,
$this->send->total_sent,$this->send->total_sent_bytes,'b',
$this->recv->total_recv,$this->recv->total_recv_bytes,'b',
(($rc & self::S_MASK) == self::S_OK) ? 'Successful' : 'Failed',
));
// Add unknown FTNs to the DB
if ($this->node->aka_remote_authed->count()) {
$so = $this->node->aka_remote_authed->first()->system;
} else {
$so = System::where('name','Discovered System')->single();
}
if ($so && $so->exists) {
foreach ($this->node->aka_other as $aka) {
Address::findFTN($aka,TRUE,$so);
}
}
2021-04-01 10:59:15 +00:00
// @todo Log to history log in the DB.
//if ($this->node->start_time && $this->setup->cfg('CFG_HISTORY')) {}
// @todo Optional after session execution event
// if ($this->node->start_time && $this->setup->cfg('CFG_AFTERSESSION')) {}
// @todo Optional after session includes mail event
// if ($this->node->start_time && $this->setup->cfg('CFG_AFTERMAIL')) {}
return ($rc & ~self::S_ADDTRY);
}
/**
* Clear a session bit
*
* @param int $key
*/
protected function sessionClear(int $key): void
{
$this->session &= ~$key;
}
/**
* Get a session bit
* @param int $key
* @return bool
*/
protected function sessionGet(int $key): bool
{
return ($this->session & $key);
}
/**
* Set a session bit (with SE_*)
*
* @param int $key
*/
protected function sessionSet(int $key): void
{
$this->session |= $key;
}
/**
* Set our client that we are communicating with
*
* @param SocketClient $client
*/
private function setClient(SocketClient $client): void
{
$this->client = $client;
}
}