Transfering netmail via EMSI

This commit is contained in:
Deon George 2021-07-17 15:48:07 +10:00
parent 6ce4e64cb6
commit 1fa566b26c
15 changed files with 226 additions and 83 deletions

View File

@ -195,7 +195,7 @@ class Message extends FTNBase
if (($x=$o->validate($domain))->fails()) { if (($x=$o->validate($domain))->fails()) {
Log::debug('Message fails validation',['result'=>$x->errors()]); Log::debug('Message fails validation',['result'=>$x->errors()]);
throw new \Exception('Message validation fails:'.join(' ',$x->errors()->all())); //throw new \Exception('Message validation fails:'.join(' ',$x->errors()->all()));
} }
return $o; return $o;
@ -249,7 +249,7 @@ class Message extends FTNBase
case 'date': case 'date':
return Carbon::createFromFormat('d M y H:i:s O', return Carbon::createFromFormat('d M y H:i:s O',
sprintf('%s %s',chop(Arr::get($this->header,$key)),($x=$this->kludge->get('tzutc')) < 0 ? $x : '+'.$x)); sprintf('%s %s',chop(Arr::get($this->header,$key)),(is_null($x=$this->kludge->get('tzutc')) || ($x < 0)) ? $x : '+'.$x));
case 'flags': case 'flags':
case 'cost': return Arr::get($this->header,$key); case 'cost': return Arr::get($this->header,$key);

View File

@ -230,12 +230,17 @@ class Packet extends FTNBase
*/ */
public function __toString(): string public function __toString(): string
{ {
// Cache the packet creation
static $return = NULL;
if (is_null($return)) {
$return = $this->createHeader(); $return = $this->createHeader();
foreach ($this->messages as $o) foreach ($this->messages as $o)
$return .= "\02\00".(string)$o; $return .= "\02\00".(string)$o;
$return .= "\00\00"; $return .= "\00\00";
}
return $return; return $return;
} }
@ -296,7 +301,7 @@ class Packet extends FTNBase
/** /**
* When creating a new packet, set the header. * When creating a new packet, set the header.
* *
* @param array $header * @param Address $o
*/ */
private function newHeader(Address $o): void private function newHeader(Address $o): void
{ {
@ -329,8 +334,8 @@ class Packet extends FTNBase
* Parse a message in a mail packet * Parse a message in a mail packet
* *
* @param string $message * @param string $message
* @param Domain $domain * @param Domain|null $domain
* @throws \Exception * @throws InvalidPacketException
*/ */
public function parseMessage(string $message,Domain $domain=NULL): void public function parseMessage(string $message,Domain $domain=NULL): void
{ {

View File

@ -21,7 +21,7 @@ abstract class Process
*/ */
protected static function format_msg(string $text): string protected static function format_msg(string $text): string
{ {
$msg = utf8_decode(join("\n",static::msg_header()))."\n"; $msg = utf8_decode(join("\r",static::msg_header()))."\r";
$c = 0; $c = 0;
$offset = 0; $offset = 0;
@ -35,7 +35,7 @@ abstract class Process
} }
// Look for a return // Look for a return
$return = strpos($text,"\n",$offset); $return = strpos($text,"\r",$offset);
if ($return !== FALSE) if ($return !== FALSE)
$return -= $offset; $return -= $offset;
@ -55,13 +55,13 @@ abstract class Process
$subtext = substr($text,$offset,$space); $subtext = substr($text,$offset,$space);
} }
$msg .= $ll.$subtext."\n"; $msg .= $ll.$subtext."\r";
$offset += strlen($subtext)+1; $offset += strlen($subtext)+1;
} }
// In case our text is shorter than the loo // In case our text is shorter than the loo
for ($c; $c<count(static::$logo);$c++) for ($c; $c<count(static::$logo);$c++)
$msg .= utf8_decode(Arr::get(static::$logo,$c))."\n"; $msg .= utf8_decode(Arr::get(static::$logo,$c))."\r";
return $msg; return $msg;
} }

View File

@ -26,17 +26,18 @@ final class Ping extends Process
if (strtolower($msg->user_to) !== 'ping') if (strtolower($msg->user_to) !== 'ping')
return FALSE; return FALSE;
$reply = sprintf("Your ping was received here on %s and it took %s to get here.\n", $reply = sprintf("Your ping was received here on %s and it took %s to get here.\r",
Carbon::now()->toDateTimeString(), Carbon::now()->toDateTimeString(),
$msg->date->diffForHumans(['parts'=>3,'syntax'=>CarbonInterface::DIFF_ABSOLUTE]) $msg->date->diffForHumans(['parts'=>3,'syntax'=>CarbonInterface::DIFF_ABSOLUTE])
); );
$reply .= "\n"; $reply .= "\r";
$reply .= "Your message travelled along this path to get here:\n"; $reply .= "\r";
$reply .= "Your message travelled along this path on the way here:\r";
foreach ($msg->via as $path) foreach ($msg->via as $path)
$reply .= sprintf(" * %s\n",$path); $reply .= sprintf(" * %s\r",$path);
$o = new Netmail(); $o = new Netmail;
$o->to = $msg->user_from; $o->to = $msg->user_from;
$o->from = Setup::PRODUCT_NAME; $o->from = Setup::PRODUCT_NAME;
$o->subject = 'Ping Reply'; $o->subject = 'Ping Reply';

38
app/Classes/File/Mail.php Normal file
View File

@ -0,0 +1,38 @@
<?php
namespace App\Classes\File;
use Carbon\Carbon;
use App\Classes\FTN\Packet;
class Mail extends Item
{
private Packet $file;
/**
* @throws \Exception
*/
public function __construct(Packet $mail,int $action)
{
$this->action |= $action;
switch ($action) {
case self::I_SEND:
$this->file = $mail;
$this->file_name = sprintf('%08X.PKT',Carbon::now()->timestamp);
$this->file_size = strlen($mail);
$this->file_mtime = Carbon::now()->timestamp; // @todo This timestamp should be consistent incase of retries
break;
default:
throw new \Exception('Unknown action: '.$action);
}
}
public function read(int $start,int $length): string
{
return substr((string)$this->file,$start,$length);
}
}

View File

@ -8,6 +8,8 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use League\Flysystem\UnreadableFileException; use League\Flysystem\UnreadableFileException;
use App\Models\Address;
/** /**
* Object representing the files we are sending * Object representing the files we are sending
* *
@ -24,6 +26,7 @@ final class Send extends Item
{ {
private Collection $list; private Collection $list;
private ?Item $sending; private ?Item $sending;
private Collection $packets;
private mixed $f; // File descriptor private mixed $f; // File descriptor
private int $start; // Time we started sending private int $start; // Time we started sending
@ -33,6 +36,7 @@ final class Send extends Item
{ {
// Initialise our variables // Initialise our variables
$this->list = collect(); $this->list = collect();
$this->packets = collect();
$this->sending = NULL; $this->sending = NULL;
$this->file_pos = 0; $this->file_pos = 0;
$this->f = NULL; $this->f = NULL;
@ -60,12 +64,14 @@ final class Send extends Item
case 'mail_count': case 'mail_count':
return $this->list return $this->list
->filter(function($item) { return $item->isType(self::IS_ARC|self::IS_PKT); }) ->filter(function($item) { return $item->isType(self::IS_ARC|self::IS_PKT); })
->count(); ->count()
+ $this->packets->count();
case 'mail_size': case 'mail_size':
return $this->list return $this->list
->filter(function($item) { return $item->isType(self::IS_ARC|self::IS_PKT); }) ->filter(function($item) { return $item->isType(self::IS_ARC|self::IS_PKT); })
->sum(function($item) { return $item->file_size; }); ->sum(function($item) { return $item->file_size; })
+ $this->packets->sum(function($item) { return $item->file_size; });
case 'sendas': case 'sendas':
return $this->sending ? $this->sending->{$key} : NULL; return $this->sending ? $this->sending->{$key} : NULL;
@ -77,22 +83,32 @@ final class Send extends Item
case 'total_sent': case 'total_sent':
return $this->list return $this->list
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === TRUE; })
->count()
+ $this->packets
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === TRUE; }) ->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === TRUE; })
->count(); ->count();
case 'total_sent_bytes': case 'total_sent_bytes':
return $this->list return $this->list
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === TRUE; })
->sum(function($item) { return $item->file_size; })
+ $this->packets
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === TRUE; }) ->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === TRUE; })
->sum(function($item) { return $item->file_size; }); ->sum(function($item) { return $item->file_size; });
case 'total_count': case 'total_count':
return $this->list return $this->list
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === FALSE; })
->count()
+ $this->packets
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === FALSE; }) ->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === FALSE; })
->count(); ->count();
case 'total_size': case 'total_size':
return $this->list return $this->list
->sum(function($item) { return $item->file_size; }); ->sum(function($item) { return $item->file_size; })
+ $this->packets->sum(function($item) { return $item->file_size; });
default: default:
throw new Exception('Unknown key: '.$key); throw new Exception('Unknown key: '.$key);
@ -143,7 +159,9 @@ final class Send extends Item
Log::debug(sprintf('%s: - Closing [%s], sent in [%d]',__METHOD__,$this->sending->file_name,$end)); Log::debug(sprintf('%s: - Closing [%s], sent in [%d]',__METHOD__,$this->sending->file_name,$end));
} }
if (! $this->sending instanceof Mail)
fclose($this->f); fclose($this->f);
$this->sending = NULL; $this->sending = NULL;
$this->file_pos = 0; $this->file_pos = 0;
$this->f = NULL; $this->f = NULL;
@ -156,7 +174,7 @@ final class Send extends Item
*/ */
public function feof(): bool public function feof(): bool
{ {
return feof($this->f); return ($this->sending instanceof Mail) ? ($this->file_pos == $this->size) : feof($this->f);
} }
/** /**
@ -169,6 +187,18 @@ final class Send extends Item
{ {
Log::debug(sprintf('%s: + Start',__METHOD__)); Log::debug(sprintf('%s: + Start',__METHOD__));
// If we have mail, we'll send that first
if ($this->sending = $this->packets
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === FALSE; })
->first())
{
$this->file_pos = 0;
$this->start = time();
$this->f = TRUE;
return TRUE;
}
$this->sending = $this->list $this->sending = $this->list
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === FALSE; }) ->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === FALSE; })
->first(); ->first();
@ -189,6 +219,19 @@ final class Send extends Item
return TRUE; return TRUE;
} }
/**
* Add our mail to the send queue
*
* @param Address $ao
* @throws Exception
*/
public function mail(Address $ao): void
{
// Netmail
if ($x=$ao->getNetmail())
$this->packets->push(new Mail($x,self::I_SEND));
}
/** /**
* Read bytes of the sending file * Read bytes of the sending file
* *
@ -202,7 +245,13 @@ final class Send extends Item
if (! $this->f) if (! $this->f)
throw new Exception('No file open for read'); throw new Exception('No file open for read');
// We are sending mail
if ($this->sending instanceof Mail) {
$data = $this->sending->read($this->file_pos,$length);
} else {
$data = fread($this->f,$length); $data = fread($this->f,$length);
}
$this->file_pos += strlen($data); $this->file_pos += strlen($data);
Log::debug(sprintf('%s: - Read [%d] bytes, file pos now [%d]',__METHOD__,strlen($data),$this->file_pos)); Log::debug(sprintf('%s: - Read [%d] bytes, file pos now [%d]',__METHOD__,strlen($data),$this->file_pos));
@ -224,7 +273,13 @@ final class Send extends Item
if (! $this->f) if (! $this->f)
throw new Exception('No file open for seek'); throw new Exception('No file open for seek');
if ($this->sending instanceof Mail) {
$rc = ($pos < $this->size) ? $pos : $this->size;
} else {
$rc = (fseek($this->f,$pos,SEEK_SET) === 0); $rc = (fseek($this->f,$pos,SEEK_SET) === 0);
}
if ($rc) if ($rc)
$this->file_pos = $pos; $this->file_pos = $pos;

View File

@ -33,12 +33,14 @@ class Node
private Collection $ftns; // The FTNs of the remote system private Collection $ftns; // The FTNs of the remote system
private Collection $ftns_authed; // The FTNs we have validated private Collection $ftns_authed; // The FTNs we have validated
private bool $authed; // Have we authenticated the remote.
private int $options; // This nodes capabilities/options private int $options; // This nodes capabilities/options
public function __construct() public function __construct()
{ {
$this->options = 0; $this->options = 0;
$this->authed = FALSE;
$this->start_time = Carbon::now(); $this->start_time = Carbon::now();
$this->ftns = collect(); $this->ftns = collect();
$this->ftns_authed = collect(); $this->ftns_authed = collect();
@ -54,16 +56,20 @@ class Node
case 'aka_num': case 'aka_num':
return $this->ftns->count(); return $this->ftns->count();
// Number of AKAs we have validated // The authenticated remote addresses
case 'aka_remote':
return $this->ftns_authed;
// Have we authenticated the remote
case 'aka_authed': case 'aka_authed':
return $this->ftns_authed->count(); return $this->authed;
case 'ftn': case 'ftn':
return ($x=$this->ftns->first()) ? $x->ftn : 'Unknown'; return ($x=$this->ftns->first()) ? $x->ftn : 'Unknown';
// The nodes password // The nodes password
case 'password': case 'password':
return ($this->ftns_authed->count() && ($x=$this->ftns_authed->first()->session('sespass'))) ? $x : '-'; return ($this->ftns->count() && ($x=$this->ftns->first()->session('sespass'))) ? $x : '-';
// Return how long our session has been connected // Return how long our session has been connected
case 'session_time': case 'session_time':
@ -163,6 +169,7 @@ class Node
$o->system->last_session = Carbon::now(); $o->system->last_session = Carbon::now();
$o->system->save(); $o->system->save();
$this->ftns_authed->push($o); $this->ftns_authed->push($o);
$this->authed = TRUE;
} }
} }
@ -224,6 +231,10 @@ class Node
*/ */
public function originate_check(): bool public function originate_check(): bool
{ {
// If we have already authed, we wont do it again
if ($this->authed)
return TRUE;
if ($this->ftns_authed->count() !== 1 || ! $this->ftns->count()) if ($this->ftns_authed->count() !== 1 || ! $this->ftns->count())
return FALSE; return FALSE;
@ -233,6 +244,7 @@ class Node
if ($item->ftn == $ftn) { if ($item->ftn == $ftn) {
$item->system->last_session = Carbon::now(); $item->system->last_session = Carbon::now();
$item->system->save(); $item->system->save();
$this->authed = TRUE;
return TRUE; return TRUE;
} }
}) !== FALSE; }) !== FALSE;

View File

@ -12,11 +12,6 @@ use App\Models\{Address,Setup};
abstract class Protocol abstract class Protocol
{ {
// Our product code
// @todo Move These to a config file
protected const product_code = 'AB8D';
public const setup = 1;
// Enable extra debugging // Enable extra debugging
protected bool $DEBUG = FALSE; protected bool $DEBUG = FALSE;
@ -254,9 +249,6 @@ abstract class Protocol
$this->client->speed = SocketClient::TCP_SPEED; $this->client->speed = SocketClient::TCP_SPEED;
$this->originate = FALSE; $this->originate = FALSE;
// @todo While Debugging
$this->send->add('/tmp/aa');
return $this->protocol_session(); return $this->protocol_session();
default: default:

View File

@ -9,7 +9,7 @@ use Illuminate\Support\Facades\Log;
use App\Classes\Protocol as BaseProtocol; use App\Classes\Protocol as BaseProtocol;
use App\Classes\Sock\SocketClient; use App\Classes\Sock\SocketClient;
use App\Classes\Sock\SocketException; use App\Classes\Sock\SocketException;
use App\Models\Address; use App\Models\{Address,Setup};
use App\Interfaces\CRC as CRCInterface; use App\Interfaces\CRC as CRCInterface;
use App\Interfaces\Zmodem as ZmodemInterface; use App\Interfaces\Zmodem as ZmodemInterface;
use App\Traits\CRC as CRCTrait; use App\Traits\CRC as CRCTrait;
@ -82,7 +82,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
if (! parent::onConnect($client)) { if (! parent::onConnect($client)) {
$this->session(self::SESSION_AUTO,$client,(new Address)); $this->session(self::SESSION_AUTO,$client,(new Address));
$this->client->close(); $this->client->close();
Log::info(sprintf('%s: = End - Connection closed [%s]',__METHOD__,$client->getAddress())); Log::info(sprintf('%s: = End - Connection closed [%s]',__METHOD__,$client->address_remote));
} }
return NULL; return NULL;
@ -191,7 +191,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
// Mailer Details // Mailer Details
$makedata .= sprintf('{%s}{%s}{%s}{%s}', $makedata .= sprintf('{%s}{%s}{%s}{%s}',
self::product_code, Setup::product_id(),
config('app.name'), config('app.name'),
$this->setup->version, $this->setup->version,
'#000000' // Serial Numbers '#000000' // Serial Numbers
@ -346,7 +346,12 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
} else { } else {
$this->node->optionSet(self::O_PWD); $this->node->optionSet(self::O_PWD);
Log::info(sprintf('%s: - Remote Authed [%d] AKAs',__METHOD__,$c)); Log::info(sprintf('%s: - Remote Authed [%d] AKAs',__METHOD__,$c));
$this->send->add('/tmp/aa');
// Add our mail to the queue if we have authenticated
if ($this->node->aka_authed)
foreach ($this->node->aka_remote as $ao) {
$this->send->mail($ao);
}
} }
/* Link codes */ /* Link codes */
@ -961,9 +966,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
if ($rc < 0) if ($rc < 0)
return (self::S_REDIAL|self::S_ADDTRY); return (self::S_REDIAL|self::S_ADDTRY);
Log::info(sprintf('%s: - Starting outbound EMSI session to [%s]',__METHOD__,$this->client->getAddress())); Log::info(sprintf('%s: - Starting outbound EMSI session to [%s]',__METHOD__,$this->client->address_remote));
// @todo Lock Node AKAs
// Inbound session // Inbound session
} else { } else {
@ -975,9 +978,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
return (self::S_REDIAL|self::S_ADDTRY); return (self::S_REDIAL|self::S_ADDTRY);
} }
Log::info(sprintf('%s: - Starting inbound EMSI session from [%s]',__METHOD__,$this->client->getAddress())); Log::info(sprintf('%s: - Starting inbound EMSI session from [%s]',__METHOD__,$this->client->address_remote));
// @todo Lock Node AKAs
if ($this->node->aka_authed) { if ($this->node->aka_authed) {
$xproto = $this->is_freq_available(); $xproto = $this->is_freq_available();
@ -1003,6 +1004,8 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
return (self::S_REDIAL|self::S_ADDTRY); return (self::S_REDIAL|self::S_ADDTRY);
} }
// @todo Lock Node AKAs
Log::info(sprintf('%s: - We have %lu%s mail, %lu%s files',__METHOD__,$this->send->mail_size,'b',$this->send->file_size,'b')); Log::info(sprintf('%s: - We have %lu%s mail, %lu%s files',__METHOD__,$this->send->mail_size,'b',$this->send->file_size,'b'));
$proto = $this->originate ? $this->node->optionGet(self::P_MASK) : $this->optionGet(self::P_MASK); $proto = $this->originate ? $this->node->optionGet(self::P_MASK) : $this->optionGet(self::P_MASK);
@ -1172,6 +1175,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
if (! $z->zmodem_sendinit($this->client,$zap) && $this->send->total_count) if (! $z->zmodem_sendinit($this->client,$zap) && $this->send->total_count)
$z->zmodem_sendfile($this->send); $z->zmodem_sendfile($this->send);
Log::debug(sprintf('%s: - Finished sending',__METHOD__));
return ($z->zmodem_senddone()<0); return ($z->zmodem_senddone()<0);
} }
} }

View File

@ -500,13 +500,14 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
{ {
Log::debug(sprintf('%s: + Start',__METHOD__)); Log::debug(sprintf('%s: + Start',__METHOD__));
while ($send->total_count && $send->open()) {
try { try {
$send->open();
$rc = $this->ls_zsendfile($send,$this->ls_SerialNum++,$send->total_count,$send->total_size); $rc = $this->ls_zsendfile($send,$this->ls_SerialNum++,$send->total_count,$send->total_size);
switch ($rc) { switch ($rc) {
case self::LSZ_OK: case self::LSZ_OK:
$send->close(TRUE); $send->close(TRUE);
break;
case self::ZSKIP: case self::ZSKIP:
case self::ZFERR: case self::ZFERR:
@ -524,6 +525,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
} catch (\Exception $e) { } catch (\Exception $e) {
Log::error(sprintf('%s: ! Error [%s]',__METHOD__,$e->getMessage())); Log::error(sprintf('%s: ! Error [%s]',__METHOD__,$e->getMessage()));
} }
}
return self::OK; return self::OK;
} }

View File

@ -10,16 +10,18 @@ use Illuminate\Support\Str;
* Class SocketClient * Class SocketClient
* *
* @package App\Classes\Sock * @package App\Classes\Sock
* @property int speed
* @property int cps * @property int cps
* @property int speed
*/ */
final class SocketClient { final class SocketClient {
// For deep debugging // For deep debugging
private bool $DEBUG = FALSE; private bool $DEBUG = FALSE;
private \Socket $connection; private \Socket $connection;
private string $address = ''; private string $address_local = '';
private int $port = 0; private int $port_local = 0;
private string $address_remote = '';
private int $port_remote = 0;
// Our session state // Our session state
private array $session = []; private array $session = [];
@ -52,14 +54,19 @@ final class SocketClient {
private string $rx_buf = ''; private string $rx_buf = '';
public function __construct (\Socket $connection,int $speed=self::TCP_SPEED) { public function __construct (\Socket $connection,int $speed=self::TCP_SPEED) {
socket_getsockname($connection,$this->address,$this->port); socket_getsockname($connection,$this->address_local,$this->port_local);
Log::info(sprintf('%s: + Connection from [%s] on port [%d]',__METHOD__,$this->address,$this->port)); socket_getpeername($connection,$this->address_remote,$this->port_remote);
Log::info(sprintf('%s: + Connection from [%s] on port [%d]',__METHOD__,$this->address_remote,$this->port_remote));
$this->connection = $connection; $this->connection = $connection;
} }
public function __get($key) { public function __get($key) {
switch ($key) { switch ($key) {
case 'address_remote':
case 'port_remote':
return $this->{$key};
case 'cps': case 'cps':
case 'speed': case 'speed':
return Arr::get($this->session,$key); return Arr::get($this->session,$key);
@ -244,6 +251,7 @@ final class SocketClient {
* *
* @return string * @return string
* @todo change to __get() * @todo change to __get()
* @deprecated
*/ */
public function getAddress(): string public function getAddress(): string
{ {
@ -255,6 +263,7 @@ final class SocketClient {
* *
* @return int * @return int
* @todo change to __get() * @todo change to __get()
* @deprecated
*/ */
public function getPort(): int public function getPort(): int
{ {

View File

@ -51,6 +51,6 @@ class EMSISend extends Command
$o = new EMSI(Setup::findOrFail(config('app.id'))); $o = new EMSI(Setup::findOrFail(config('app.id')));
$o->session(EMSI::SESSION_AUTO,$client,$no); $o->session(EMSI::SESSION_AUTO,$client,$no);
Log::info(sprintf('Connection ended: %s',$client->getAddress()),['m'=>__METHOD__]); Log::info(sprintf('Connection ended: %s',$client->address_remote),['m'=>__METHOD__]);
} }
} }

View File

@ -53,7 +53,8 @@ class Address extends Model
return $this->hasMany(self::class,'hub_id','id'); return $this->hasMany(self::class,'hub_id','id');
case 'node': case 'node':
return NULL; // Nodes dont have children, but must return a relationship instance
return $this->hasOne(self::class,NULL,'void');
default: default:
throw new Exception('Unknown role: '.$this->role); throw new Exception('Unknown role: '.$this->role);
@ -151,12 +152,15 @@ class Address extends Model
/** /**
* Get netmail for this node (including it's children) * Get netmail for this node (including it's children)
*
* @return Packet|null
*/ */
public function getNetmail(): Packet public function getNetmail(): ?Packet
{ {
if (($x=Netmail::whereIn('tftn_id',$this->children->pluck('id')->push($this->id)))->count()) {
$o = new Packet($this); $o = new Packet($this);
foreach (Netmail::whereIn('tftn_id',$this->children->pluck('id')->push($this->id))->get() as $oo) { foreach ($x->get() as $oo) {
$o->addNetmail($oo->packet()); $o->addNetmail($oo->packet());
// @todo We need to mark the netmail as sent // @todo We need to mark the netmail as sent
@ -165,6 +169,9 @@ class Address extends Model
return $o; return $o;
} }
return NULL;
}
/** /**
* Parse a string and split it out as an FTN array * Parse a string and split it out as an FTN array
* *

View File

@ -21,7 +21,8 @@ class Netmail extends Model
{ {
return $this return $this
->setConnection('pgsql') ->setConnection('pgsql')
->belongsTo(Address::class); ->belongsTo(Address::class)
->withTrashed();
} }
public function tftn() public function tftn()
@ -52,6 +53,8 @@ class Netmail extends Model
{ {
$o = new Message; $o = new Message;
// @todo Dont bundle mail to nodes that have been disabled, or addresses that have been deleted
$o->header = [ $o->header = [
'onode' => $this->fftn->node_id, 'onode' => $this->fftn->node_id,
'dnode' => $this->tftn->node_id, 'dnode' => $this->tftn->node_id,
@ -88,6 +91,9 @@ class Netmail extends Model
// @todo Point handling FMPT/TOPT // @todo Point handling FMPT/TOPT
$o->intl = sprintf('%s %s',$this->tftn->ftn3d,$this->fftn->ftn3d); $o->intl = sprintf('%s %s',$this->tftn->ftn3d,$this->fftn->ftn3d);
// TZUTC
$o->kludge->put('tzutc',str_replace('+','',$this->created_at->getOffsetString('')));
return $o; return $o;
} }
} }

View File

@ -42,9 +42,21 @@ class Setup extends Model
public const PRODUCT_VERSION_MAJ = 0; public const PRODUCT_VERSION_MAJ = 0;
public const PRODUCT_VERSION_MIN = 0; public const PRODUCT_VERSION_MIN = 0;
public const hexdigitslower = '0123456789abcdef';
// Our non model attributes and values // Our non model attributes and values
private array $internal = []; private array $internal = [];
public static function product_id(int $c=self::PRODUCT_ID): string
{
$x = substr(static::hexdigitslower,($c&0xf000)>>12,1);
$x .= substr(static::hexdigitslower,($c&0x0f00)>>8,1);
$x .= substr(static::hexdigitslower,($c&0x00f0)>>4,1);
$x .= substr(static::hexdigitslower,($c&0x000f),1);
return $x;
}
/* RELATIONS */ /* RELATIONS */
public function system() public function system()