Start of processing packets - implemented PING Responce to Netmail
This commit is contained in:
parent
fe2784f98f
commit
a0d3c8d8ab
@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Classes;
|
namespace App\Classes;
|
||||||
|
|
||||||
use Illuminate\Support\Arr;
|
use App\Models\Domain;
|
||||||
|
|
||||||
abstract class FTN
|
abstract class FTN
|
||||||
{
|
{
|
||||||
|
protected ?Domain $domain; // Domain the packet is from
|
||||||
|
|
||||||
public function __get($key)
|
public function __get($key)
|
||||||
{
|
{
|
||||||
switch ($key) {
|
switch ($key) {
|
||||||
@ -15,7 +17,7 @@ abstract class FTN
|
|||||||
$this->fn,
|
$this->fn,
|
||||||
$this->ff,
|
$this->ff,
|
||||||
$this->fp,
|
$this->fp,
|
||||||
);
|
).($this->domain ? sprintf('@%s',$this->domain->name) : '');
|
||||||
|
|
||||||
case 'tftn':
|
case 'tftn':
|
||||||
return sprintf('%d:%d/%d.%d',
|
return sprintf('%d:%d/%d.%d',
|
||||||
@ -23,7 +25,7 @@ abstract class FTN
|
|||||||
$this->tn,
|
$this->tn,
|
||||||
$this->tf,
|
$this->tf,
|
||||||
$this->tp,
|
$this->tp,
|
||||||
);
|
).($this->domain ? sprintf('@%s',$this->domain->name) : '');
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new \Exception('Unknown key: '.$key);
|
throw new \Exception('Unknown key: '.$key);
|
||||||
@ -50,7 +52,7 @@ abstract class FTN
|
|||||||
* @param array $pack
|
* @param array $pack
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function unpackheader(array $pack): string
|
protected static function unpackheader(array $pack): string
|
||||||
{
|
{
|
||||||
return join('/',
|
return join('/',
|
||||||
collect($pack)
|
collect($pack)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Classes\FTN;
|
namespace App\Classes\FTN;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
@ -9,9 +10,8 @@ use Illuminate\Support\Facades\Validator;
|
|||||||
use Illuminate\Validation\Validator as ValidatorResult;
|
use Illuminate\Validation\Validator as ValidatorResult;
|
||||||
|
|
||||||
use App\Classes\FTN as FTNBase;
|
use App\Classes\FTN as FTNBase;
|
||||||
use App\Models\Address;
|
use App\Models\{Address,Domain};
|
||||||
use App\Rules\TwoByteInteger;
|
use App\Rules\TwoByteInteger;
|
||||||
use App\Traits\GetNode;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Message
|
* Class Message
|
||||||
@ -21,14 +21,15 @@ use App\Traits\GetNode;
|
|||||||
*/
|
*/
|
||||||
class Message extends FTNBase
|
class Message extends FTNBase
|
||||||
{
|
{
|
||||||
//use GetNode;
|
private const cast_utf8 = [
|
||||||
|
'message',
|
||||||
|
];
|
||||||
|
|
||||||
// Single value kludge items
|
// Single value kludge items
|
||||||
private array $_kludge = [
|
private array $_kludge = [
|
||||||
'chrs' => 'CHRS: ',
|
'chrs' => 'CHRS: ',
|
||||||
'charset' => 'CHARSET: ',
|
'charset' => 'CHARSET: ',
|
||||||
'codepage' => 'CODEPAGE: ',
|
'codepage' => 'CODEPAGE: ',
|
||||||
'msgid' => 'MSGID: ',
|
|
||||||
'pid' => 'PID: ',
|
'pid' => 'PID: ',
|
||||||
'replyid' => 'REPLY: ',
|
'replyid' => 'REPLY: ',
|
||||||
'tid' => 'TID: ',
|
'tid' => 'TID: ',
|
||||||
@ -63,7 +64,7 @@ class Message extends FTNBase
|
|||||||
'dnet' => [0x06,'v',2], // Destination Net
|
'dnet' => [0x06,'v',2], // Destination Net
|
||||||
'flags' => [0x08,'v',2], // Message Flags
|
'flags' => [0x08,'v',2], // Message Flags
|
||||||
'cost' => [0x0a,'v',2], // Send Cost
|
'cost' => [0x0a,'v',2], // Send Cost
|
||||||
'date' => [0x0c,'A20',20] // Message Date FTS-0001.016 Date: upto 20 chars null terminated
|
'date' => [0x0c,'a20',20] // Message Date FTS-0001.016 Date: upto 20 chars null terminated
|
||||||
];
|
];
|
||||||
|
|
||||||
private const USER_FROM_LEN = 36; // FTS-0001.016 From Name: upto 36 chars null terminated
|
private const USER_FROM_LEN = 36; // FTS-0001.016 From Name: upto 36 chars null terminated
|
||||||
@ -74,20 +75,23 @@ class Message extends FTNBase
|
|||||||
private ?ValidatorResult $errors = NULL; // Packet validation
|
private ?ValidatorResult $errors = NULL; // Packet validation
|
||||||
private array $header; // Message Header
|
private array $header; // Message Header
|
||||||
private Collection $kludge; // Hold kludge items
|
private Collection $kludge; // Hold kludge items
|
||||||
|
|
||||||
private string $user_from; // User message is From
|
private string $user_from; // User message is From
|
||||||
private string $user_to; // User message is To
|
private string $user_to; // User message is To
|
||||||
private string $subject; // Message subject
|
private string $subject; // Message subject
|
||||||
|
private string $msgid; // MSG ID
|
||||||
|
private string $echoarea; // FTS-0004.001
|
||||||
|
private string $intl; // Netmail details
|
||||||
private string $message; // The actual message content
|
private string $message; // The actual message content
|
||||||
|
private string $tearline;
|
||||||
private string $origin; // FTS-0004.001
|
private string $origin; // FTS-0004.001
|
||||||
private ?string $echoarea = NULL; // FTS-0004.001
|
|
||||||
private array $zone; // Zone the message belongs to. (src/dst - for netmail)
|
private array $zone; // Zone the message belongs to. (src/dst - for netmail)
|
||||||
private array $point; // Point the message belongs to (Netmail)
|
private array $point; // Point the message belongs to (Netmail)
|
||||||
private array $netmail; // Netmail details
|
|
||||||
|
|
||||||
private Collection $path; // FTS-0004.001 The message PATH lines
|
private Collection $path; // FTS-0004.001 The message PATH lines
|
||||||
private Collection $seenby; // FTS-0004.001 The message SEEN-BY lines
|
private Collection $seenby; // FTS-0004.001 The message SEEN-BY lines
|
||||||
private Collection $via; // The path the message has gone using Via lines (Netmail)
|
private Collection $via; // The path the message has gone using Via lines (Netmail)
|
||||||
private Collection $_other; // Temporarily hold attributes we dont process yet.
|
|
||||||
private Collection $unknown; // Temporarily hold attributes we have no logic for.
|
private Collection $unknown; // Temporarily hold attributes we have no logic for.
|
||||||
|
|
||||||
// Convert characters into printable chars
|
// Convert characters into printable chars
|
||||||
@ -136,42 +140,86 @@ class Message extends FTNBase
|
|||||||
0xfc => 0x207f, 0xfd => 0x00b2, 0xfe => 0x25a0, 0xff => 0x00a0,
|
0xfc => 0x207f, 0xfd => 0x00b2, 0xfe => 0x25a0, 0xff => 0x00a0,
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct(string $msg)
|
public function __construct(Domain $domain=NULL)
|
||||||
{
|
{
|
||||||
|
$this->domain = $domain;
|
||||||
|
|
||||||
$this->kludge = collect();
|
$this->kludge = collect();
|
||||||
$this->path = collect();
|
$this->path = collect();
|
||||||
$this->seenby = collect();
|
$this->seenby = collect();
|
||||||
$this->via = collect();
|
$this->via = collect();
|
||||||
$this->_other = collect();
|
|
||||||
$this->unknown = collect();
|
$this->unknown = collect();
|
||||||
$this->zone = [];
|
$this->zone = [];
|
||||||
$this->point = [];
|
$this->point = [];
|
||||||
|
$this->tearline = '';
|
||||||
|
$this->origin = '';
|
||||||
|
$this->msgid = '';
|
||||||
|
$this->echoarea = '';
|
||||||
|
$this->intl = '';
|
||||||
|
}
|
||||||
|
|
||||||
$this->header = unpack($this->unpackheader(self::header),substr($msg,0,self::HEADER_LEN));
|
/**
|
||||||
|
* Parse a message from a packet
|
||||||
|
*
|
||||||
|
* @param string $msg
|
||||||
|
* @param Domain|null $domain
|
||||||
|
* @return static
|
||||||
|
* @throws InvalidPacketException
|
||||||
|
*/
|
||||||
|
public static function parseMessage(string $msg,Domain $domain=NULL): self
|
||||||
|
{
|
||||||
|
$o = new self($domain);
|
||||||
|
|
||||||
|
$o->header = unpack(self::unpackheader(self::header),substr($msg,0,self::HEADER_LEN));
|
||||||
|
|
||||||
$ptr = 0;
|
$ptr = 0;
|
||||||
// To User
|
// To User
|
||||||
$this->user_to = strstr(substr($msg,self::HEADER_LEN+$ptr),"\x00",TRUE);
|
$o->user_to = strstr(substr($msg,self::HEADER_LEN+$ptr),"\x00",TRUE);
|
||||||
$ptr += strlen($this->user_to)+1;
|
$ptr += strlen($o->user_to)+1;
|
||||||
|
|
||||||
// From User
|
// From User
|
||||||
$this->user_from = strstr(substr($msg,self::HEADER_LEN+$ptr),"\x00",TRUE);
|
$o->user_from = strstr(substr($msg,self::HEADER_LEN+$ptr),"\x00",TRUE);
|
||||||
$ptr += strlen($this->user_from)+1;
|
$ptr += strlen($o->user_from)+1;
|
||||||
|
|
||||||
// Subject
|
// Subject
|
||||||
$this->subject = strstr(substr($msg,self::HEADER_LEN+$ptr),"\x00",TRUE);
|
$o->subject = strstr(substr($msg,self::HEADER_LEN+$ptr),"\x00",TRUE);
|
||||||
$ptr += strlen($this->subject)+1;
|
$ptr += strlen($o->subject)+1;
|
||||||
|
|
||||||
// Check if this is an Echomail
|
// Check if this is an Echomail
|
||||||
if (! strncmp(substr($msg,self::HEADER_LEN+$ptr),'AREA:',5)) {
|
if (! strncmp(substr($msg,self::HEADER_LEN+$ptr),'AREA:',5)) {
|
||||||
$this->echoarea = substr($msg,self::HEADER_LEN+$ptr+5,strpos($msg,"\r",self::HEADER_LEN+$ptr+5)-(self::HEADER_LEN+$ptr+5));
|
$o->echoarea = substr($msg,self::HEADER_LEN+$ptr+5,strpos($msg,"\r",self::HEADER_LEN+$ptr+5)-(self::HEADER_LEN+$ptr+5));
|
||||||
$ptr += strlen($this->echoarea)+5+1;
|
$ptr += strlen($o->echoarea)+5+1;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->parseMessage(substr($msg,self::HEADER_LEN+$ptr));
|
$o->unpackMessage(substr($msg,self::HEADER_LEN+$ptr));
|
||||||
|
|
||||||
if (($x=$this->validate()->getMessageBag())->count())
|
if (($x=$o->validate($domain))->fails()) {
|
||||||
Log::debug('Message fails validation',['result'=>$x]);
|
Log::debug('Message fails validation',['result'=>$x->errors()]);
|
||||||
|
throw new \Exception('Message validation fails:'.join(' ',$x->errors()->all()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $o;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate the string into something printable via the web
|
||||||
|
*
|
||||||
|
* @param string $string
|
||||||
|
* @param array $skip
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function tr(string $string,array $skip=[0x0a,0x0d]): string
|
||||||
|
{
|
||||||
|
$tr = [];
|
||||||
|
|
||||||
|
foreach (self::CP437 as $k=>$v) {
|
||||||
|
if (in_array($k,$skip))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$tr[chr($k)] = '&#'.$v;
|
||||||
|
}
|
||||||
|
|
||||||
|
return strtr($string,$tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __get($key)
|
public function __get($key)
|
||||||
@ -194,17 +242,19 @@ class Message extends FTNBase
|
|||||||
case 'tftn':
|
case 'tftn':
|
||||||
return parent::__get($key);
|
return parent::__get($key);
|
||||||
|
|
||||||
|
case 'fftn_o':
|
||||||
|
return Address::findFTN($this->fftn);
|
||||||
|
case 'tftn_o':
|
||||||
|
return Address::findFTN($this->tftn);
|
||||||
|
|
||||||
case 'date':
|
case 'date':
|
||||||
return sprintf('%s (%s)',Arr::get($this->header,$key),$this->kludge->get('tzutc'));
|
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));
|
||||||
|
|
||||||
case 'flags':
|
case 'flags':
|
||||||
case 'cost': return Arr::get($this->header,$key);
|
case 'cost': return Arr::get($this->header,$key);
|
||||||
|
|
||||||
case 'msgid': return $this->kludge->get('msgid');
|
|
||||||
|
|
||||||
case 'message':
|
case 'message':
|
||||||
return utf8_decode($this->{$key});
|
|
||||||
|
|
||||||
case 'subject':
|
case 'subject':
|
||||||
case 'user_to':
|
case 'user_to':
|
||||||
case 'user_from':
|
case 'user_from':
|
||||||
@ -212,29 +262,122 @@ class Message extends FTNBase
|
|||||||
case 'path':
|
case 'path':
|
||||||
case 'seenby':
|
case 'seenby':
|
||||||
case 'via':
|
case 'via':
|
||||||
|
case 'msgid':
|
||||||
case 'errors':
|
case 'errors':
|
||||||
case 'echoarea':
|
case 'echoarea':
|
||||||
return $this->{$key};
|
return $this->{$key};
|
||||||
|
|
||||||
/*
|
default:
|
||||||
case 'tearline':
|
throw new \Exception('Unknown key: '.$key);
|
||||||
return '--- FTNHub';
|
}
|
||||||
*/
|
}
|
||||||
|
|
||||||
|
public function __set($key,$value)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'echoarea':
|
||||||
|
case 'header':
|
||||||
|
case 'intl':
|
||||||
|
case 'message':
|
||||||
|
case 'msgid':
|
||||||
|
case 'subject':
|
||||||
|
case 'user_from':
|
||||||
|
case 'user_to':
|
||||||
|
case 'via':
|
||||||
|
$this->{$key} = $value;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new \Exception('Unknown key: '.$key);
|
throw new \Exception('Unknown key: '.$key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When we serialise this object, we'll need to utf8_encode some values
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function __serialize(): array
|
||||||
|
{
|
||||||
|
$values = [];
|
||||||
|
|
||||||
|
$properties = (new \ReflectionClass($this))->getProperties();
|
||||||
|
|
||||||
|
$class = get_class($this);
|
||||||
|
|
||||||
|
foreach ($properties as $property) {
|
||||||
|
if ($property->isStatic()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$property->setAccessible(true);
|
||||||
|
|
||||||
|
if (! $property->isInitialized($this)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $property->getName();
|
||||||
|
$encode = in_array($name,self::cast_utf8);
|
||||||
|
|
||||||
|
if ($property->isPrivate()) {
|
||||||
|
$name = "\0{$class}\0{$name}";
|
||||||
|
} elseif ($property->isProtected()) {
|
||||||
|
$name = "\0*\0{$name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
$property->setAccessible(true);
|
||||||
|
$value = $property->getValue($this);
|
||||||
|
|
||||||
|
$values[$name] = $encode ? utf8_encode($value) : $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When we unserialize, we'll restore (utf8_decode) some values
|
||||||
|
*
|
||||||
|
* @param array $values
|
||||||
|
*/
|
||||||
|
public function __unserialize(array $values): void
|
||||||
|
{
|
||||||
|
$properties = (new \ReflectionClass($this))->getProperties();
|
||||||
|
|
||||||
|
$class = get_class($this);
|
||||||
|
|
||||||
|
foreach ($properties as $property) {
|
||||||
|
if ($property->isStatic()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $property->getName();
|
||||||
|
$decode = in_array($name,self::cast_utf8);
|
||||||
|
|
||||||
|
if ($property->isPrivate()) {
|
||||||
|
$name = "\0{$class}\0{$name}";
|
||||||
|
} elseif ($property->isProtected()) {
|
||||||
|
$name = "\0*\0{$name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! array_key_exists($name, $values)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$property->setAccessible(true);
|
||||||
|
|
||||||
|
$property->setValue(
|
||||||
|
$this, $decode ? utf8_decode($values[$name]) : $values[$name]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export an FTN message, ready for sending.
|
* Export an FTN message, ready for sending.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
* @todo To rework
|
|
||||||
*/
|
*/
|
||||||
public function __toString(): string
|
public function __toString(): string
|
||||||
{
|
{
|
||||||
// if (f->net == 65535) { /* Point packet - Get Net from auxNet */
|
|
||||||
$return = '';
|
$return = '';
|
||||||
|
|
||||||
$return .= pack(join('',collect(self::header)->pluck(1)->toArray()),
|
$return .= pack(join('',collect(self::header)->pluck(1)->toArray()),
|
||||||
@ -243,48 +386,36 @@ class Message extends FTNBase
|
|||||||
$this->fn,
|
$this->fn,
|
||||||
$this->tn,
|
$this->tn,
|
||||||
$this->flags,
|
$this->flags,
|
||||||
$this->cost
|
$this->cost,
|
||||||
|
$this->date->format('d M y H:i:s'),
|
||||||
);
|
);
|
||||||
|
|
||||||
// @todo use pack for this.
|
$return .= $this->user_to."\00";
|
||||||
$return .= $this->date->format('d M y H:i:s')."\00";
|
$return .= $this->user_from."\00";
|
||||||
$return .= $this->to."\00";
|
|
||||||
$return .= $this->from."\00";
|
|
||||||
$return .= $this->subject."\00";
|
$return .= $this->subject."\00";
|
||||||
|
|
||||||
if ($this->type == 'echomail')
|
if ($this->isNetmail())
|
||||||
|
$return .= sprintf("\01INTL %s\r",$this->intl);
|
||||||
|
else
|
||||||
$return .= "AREA:".$this->echoarea."\r";
|
$return .= "AREA:".$this->echoarea."\r";
|
||||||
|
|
||||||
// Add some kludges
|
// Add some kludges
|
||||||
$return .= "\01MSGID ".$this->_fqfa." 1"."\r";
|
$return .= sprintf("\01MSGID: %s\r",$this->msgid);
|
||||||
|
|
||||||
foreach ($this->_kludge as $k=>$v) {
|
foreach ($this->_kludge as $k=>$v) {
|
||||||
if ($x=$this->kludge->get($k))
|
if ($x=$this->kludge->get($k))
|
||||||
$return .= chr(1).$v.$x."\r";
|
$return .= sprintf("\01%s %s\r",$v,$x);
|
||||||
}
|
}
|
||||||
|
|
||||||
$return .= $this->message."\r";
|
$return .= $this->message."\r";
|
||||||
$return .= $this->tearline."\r";
|
if ($this->tearline)
|
||||||
$return .= $this->origin."\r";
|
$return .= $this->tearline."\r";
|
||||||
|
if ($this->origin)
|
||||||
|
$return .= $this->origin."\r";
|
||||||
|
|
||||||
switch ($this->type)
|
if ($this->isNetmail()) {
|
||||||
{
|
foreach ($this->via as $v)
|
||||||
case 'echomail':
|
$return .= sprintf("\01Via %s\r",$v);
|
||||||
break;
|
|
||||||
|
|
||||||
case 'netmail':
|
|
||||||
foreach ($this->via as $k=>$v)
|
|
||||||
$return .= "\01Via: ".$v."\r";
|
|
||||||
|
|
||||||
// @todo Set product name/version as var
|
|
||||||
$return .= sprintf('%sVia: %s @%s.UTC %s %i.%i',
|
|
||||||
chr(1),
|
|
||||||
'10:0/0',
|
|
||||||
now('UTC')->format('Ymd.His'),
|
|
||||||
'FTNHub',
|
|
||||||
1,1)."\r";
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$return .= "\00";
|
$return .= "\00";
|
||||||
@ -292,6 +423,16 @@ class Message extends FTNBase
|
|||||||
return $return;
|
return $return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this message doesnt have an AREATAG, then its a netmail.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isNetmail(): bool
|
||||||
|
{
|
||||||
|
return ! $this->echoarea;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an array of flag descriptions
|
* Return an array of flag descriptions
|
||||||
*
|
*
|
||||||
@ -299,26 +440,26 @@ class Message extends FTNBase
|
|||||||
*
|
*
|
||||||
* http://ftsc.org/docs/fsc-0001.000
|
* http://ftsc.org/docs/fsc-0001.000
|
||||||
* AttributeWord bit meaning
|
* AttributeWord bit meaning
|
||||||
--- --------------------
|
* --- --------------------
|
||||||
0 + Private
|
* 0 + Private
|
||||||
1 + s Crash
|
* 1 + s Crash
|
||||||
2 Recd
|
* 2 Recd
|
||||||
3 Sent
|
* 3 Sent
|
||||||
4 + FileAttached
|
* 4 + FileAttached
|
||||||
5 InTransit
|
* 5 InTransit
|
||||||
6 Orphan
|
* 6 Orphan
|
||||||
7 KillSent
|
* 7 KillSent
|
||||||
8 Local
|
* 8 Local
|
||||||
9 s HoldForPickup
|
* 9 s HoldForPickup
|
||||||
10 + unused
|
* 10 + unused
|
||||||
11 s FileRequest
|
* 11 s FileRequest
|
||||||
12 + s ReturnReceiptRequest
|
* 12 + s ReturnReceiptRequest
|
||||||
13 + s IsReturnReceipt
|
* 13 + s IsReturnReceipt
|
||||||
14 + s AuditRequest
|
* 14 + s AuditRequest
|
||||||
15 s FileUpdateReq
|
* 15 s FileUpdateReq
|
||||||
|
*
|
||||||
s - this bit is supported by SEAdog only
|
* s - this bit is supported by SEAdog only
|
||||||
+ - this bit is not zeroed before packeting
|
* + - this bit is not zeroed before packeting
|
||||||
*/
|
*/
|
||||||
/*
|
/*
|
||||||
public function flags(int $flags): array
|
public function flags(int $flags): array
|
||||||
@ -339,137 +480,6 @@ class Message extends FTNBase
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* If this message doesnt have an AREATAG, then its a netmail.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isNetmail(): bool
|
|
||||||
{
|
|
||||||
return ! $this->echoarea;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract information out of the message text.
|
|
||||||
*
|
|
||||||
* @param string $message
|
|
||||||
* @throws InvalidPacketException
|
|
||||||
*/
|
|
||||||
public function parseMessage(string $message): void
|
|
||||||
{
|
|
||||||
// Remove DOS \n\r
|
|
||||||
$message = preg_replace("/\n\r/","\r",$message);
|
|
||||||
|
|
||||||
// Split out the <SOH> lines
|
|
||||||
$result = collect(explode("\01",$message))->filter();
|
|
||||||
|
|
||||||
$this->message = '';
|
|
||||||
|
|
||||||
foreach ($result as $v) {
|
|
||||||
// Search for \r - if that is the end of the line, then its a kludge
|
|
||||||
$x = strpos($v,"\r");
|
|
||||||
$t = '';
|
|
||||||
|
|
||||||
// If there are more characters, then put the kludge back into the result, so that we process it.
|
|
||||||
if ($x != strlen($v)-1) {
|
|
||||||
/**
|
|
||||||
* Anything after the origin line is also kludge data.
|
|
||||||
*/
|
|
||||||
if ($y = strpos($v,"\r * Origin: ")) {
|
|
||||||
$this->message .= utf8_encode(substr($v,$x+1,$y-$x-1));
|
|
||||||
$this->parseOrigin(substr($v,$y));
|
|
||||||
|
|
||||||
// If this is netmail, the FQFA will have been set by the INTL line, we can skip the rest of this
|
|
||||||
$matches = [];
|
|
||||||
|
|
||||||
// Capture the fully qualified 4D name from the Origin Line - it tells us the ZONE.
|
|
||||||
preg_match('/^.*\((.*)\)$/',$this->origin,$matches);
|
|
||||||
|
|
||||||
// Double check we have an address in the origin line
|
|
||||||
if (! Arr::get($matches,1))
|
|
||||||
throw new InvalidPacketException(sprintf('No address in Origin?',$matches));
|
|
||||||
|
|
||||||
// Double check, our src and origin match
|
|
||||||
$ftn = Address::parseFTN($matches[1]);
|
|
||||||
|
|
||||||
// We'll double check our FTN
|
|
||||||
if (($ftn['n'] !== $this->fn) || ($ftn['f'] !== $this->ff)) {
|
|
||||||
Log::error(sprintf('FTN [%s] doesnt match message header',$matches[1]),['ftn'=>$ftn]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->zone['src'] = $ftn['z'];
|
|
||||||
$this->point['src'] = $ftn['p'];
|
|
||||||
|
|
||||||
// The message is the rest?
|
|
||||||
} elseif (strlen($v) > $x+1) {
|
|
||||||
$this->message .= utf8_encode(substr($v,$x+1));
|
|
||||||
}
|
|
||||||
|
|
||||||
$v = substr($v,0,$x+1);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->_kludge as $a => $b) {
|
|
||||||
if ($t = $this->kludge($b,$v)) {
|
|
||||||
$this->kludge->put($a,$t);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// There is more text.
|
|
||||||
if ($t)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// From point: <SOH>"FMPT <point number><CR>
|
|
||||||
if ($t = $this->kludge('FMPT ',$v))
|
|
||||||
$this->point['src'] = $t;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The INTL control paragraph shall be used to give information about
|
|
||||||
* the zone numbers of the original sender and the ultimate addressee
|
|
||||||
* of a message.
|
|
||||||
*
|
|
||||||
* <SOH>"INTL "<destination address>" "<origin address><CR>
|
|
||||||
*/
|
|
||||||
elseif ($t = $this->kludge('INTL ',$v)) {
|
|
||||||
$this->netmail['intl'] = $t;
|
|
||||||
|
|
||||||
// INTL kludge is in Netmail, so we'll do some validation:
|
|
||||||
list($this->netmail['dst'],$this->netmail['src']) = explode(' ',$t);
|
|
||||||
|
|
||||||
$src = Address::parseFTN($this->netmail['src']);
|
|
||||||
if (($src['n'] !== $this->fn) || ($src['f'] !== $this->ff)) {
|
|
||||||
Log::error(sprintf('INTL src address [%s] doesnt match packet',$this->netmail['src']));
|
|
||||||
} else {
|
|
||||||
// We'll set our source zone
|
|
||||||
$this->zone['src'] = $src['z'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$dst = Address::parseFTN($this->netmail['dst']);
|
|
||||||
if (($dst['n'] !== $this->tn) || ($dst['f'] !== $this->tf)) {
|
|
||||||
Log::error(sprintf('INTL dst address [%s] doesnt match packet',$this->netmail['dst']));
|
|
||||||
} else {
|
|
||||||
// We'll set our source zone
|
|
||||||
$this->zone['dst'] = $dst['z'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
elseif ($t = $this->kludge('PATH: ',$v))
|
|
||||||
$this->path->push($t);
|
|
||||||
|
|
||||||
// To Point: <SOH>TOPT <point number><CR>
|
|
||||||
elseif ($t = $this->kludge('TOPT ',$v))
|
|
||||||
$this->point['dst'] = $t;
|
|
||||||
|
|
||||||
// <SOH>Via <FTN Address> @YYYYMMDD.HHMMSS[.Precise][.Time Zone] <Program Name> <Version> [Serial Number]<CR>
|
|
||||||
elseif ($t = $this->kludge('Via ',$v))
|
|
||||||
$this->via->push($t);
|
|
||||||
|
|
||||||
// We got a kludge line we dont know about
|
|
||||||
else
|
|
||||||
$this->unknown->push(chop($v,"\r"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process the data after the ORIGIN
|
* Process the data after the ORIGIN
|
||||||
* There may be kludge lines after the origin - notably SEEN-BY
|
* There may be kludge lines after the origin - notably SEEN-BY
|
||||||
@ -505,23 +515,128 @@ class Message extends FTNBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translate the string into something printable via the web
|
* Extract information out of the message text.
|
||||||
*
|
*
|
||||||
* @param string $string
|
* @param string $message
|
||||||
* @param array $skip
|
* @throws InvalidPacketException
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
public static function tr(string $string,array $skip=[0x0a,0x0d]): string
|
public function unpackMessage(string $message): void
|
||||||
{
|
{
|
||||||
$tr = [];
|
// Remove DOS \n\r
|
||||||
foreach (self::CP437 as $k=>$v) {
|
$message = preg_replace("/\n\r/","\r",$message);
|
||||||
if (in_array($k,$skip))
|
|
||||||
|
// Split out the <SOH> lines
|
||||||
|
$result = collect(explode("\01",$message))->filter();
|
||||||
|
|
||||||
|
$this->message = '';
|
||||||
|
|
||||||
|
foreach ($result as $v) {
|
||||||
|
// Search for \r - if that is the end of the line, then its a kludge
|
||||||
|
$x = strpos($v,"\r");
|
||||||
|
$t = '';
|
||||||
|
|
||||||
|
// If there are more characters, then put the kludge back into the result, so that we process it.
|
||||||
|
if ($x != strlen($v)-1) {
|
||||||
|
// Anything after the origin line is also kludge data.
|
||||||
|
if ($y = strpos($v,"\r * Origin: ")) {
|
||||||
|
$this->message .= substr($v,$x+1,$y-$x-1);
|
||||||
|
$this->parseOrigin(substr($v,$y));
|
||||||
|
|
||||||
|
// If this is netmail, the FQFA will have been set by the INTL line, we can skip the rest of this
|
||||||
|
$matches = [];
|
||||||
|
|
||||||
|
// Capture the fully qualified 4D name from the Origin Line - it tells us the ZONE.
|
||||||
|
preg_match('/^.*\((.*)\)$/',$this->origin,$matches);
|
||||||
|
|
||||||
|
// Double check we have an address in the origin line
|
||||||
|
if (! Arr::get($matches,1))
|
||||||
|
throw new InvalidPacketException('No address in Origin?');
|
||||||
|
|
||||||
|
// Double check, our src and origin match
|
||||||
|
$ftn = Address::parseFTN($matches[1]);
|
||||||
|
|
||||||
|
// We'll double check our FTN
|
||||||
|
if (($ftn['n'] !== $this->fn) || ($ftn['f'] !== $this->ff))
|
||||||
|
Log::error(sprintf('FTN [%s] doesnt match message header',$matches[1]),['ftn'=>$ftn]);
|
||||||
|
|
||||||
|
// http://ftsc.org/docs/fsc-0068.001
|
||||||
|
// MSGID should be the basis of the source
|
||||||
|
$this->zone['src'] = $ftn['z'];
|
||||||
|
$this->point['src'] = $ftn['p'];
|
||||||
|
|
||||||
|
// The message is the rest?
|
||||||
|
} elseif (strlen($v) > $x+1) {
|
||||||
|
$this->message .= substr($v,$x+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$v = substr($v,0,$x+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->_kludge as $a => $b) {
|
||||||
|
if ($t = $this->kludge($b,$v)) {
|
||||||
|
$this->kludge->put($a,$t);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is more text.
|
||||||
|
if ($t)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
$tr[chr($k)] = '&#'.$v;
|
// From point: <SOH>"FMPT <point number><CR>
|
||||||
}
|
if ($t = $this->kludge('FMPT ',$v))
|
||||||
|
$this->point['src'] = $t;
|
||||||
|
|
||||||
return strtr($string,$tr);
|
/*
|
||||||
|
* The INTL control paragraph shall be used to give information about
|
||||||
|
* the zone numbers of the original sender and the ultimate addressee
|
||||||
|
* of a message.
|
||||||
|
*
|
||||||
|
* <SOH>"INTL "<destination address>" "<origin address><CR>
|
||||||
|
*/
|
||||||
|
elseif ($t = $this->kludge('INTL ',$v)) {
|
||||||
|
$this->intl = $t;
|
||||||
|
|
||||||
|
// INTL kludge is in Netmail, so we'll do some validation:
|
||||||
|
list($dst,$src) = explode(' ',$t);
|
||||||
|
|
||||||
|
$ftn = Address::parseFTN($src);
|
||||||
|
if (($ftn['n'] !== $this->fn) || ($ftn['f'] !== $this->ff)) {
|
||||||
|
Log::error(sprintf('INTL src address [%s] doesnt match packet',$src));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// We'll set our source zone
|
||||||
|
$this->zone['src'] = $ftn['z'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$ftn = Address::parseFTN($dst);
|
||||||
|
if (($ftn['n'] !== $this->tn) || ($ftn['f'] !== $this->tf)) {
|
||||||
|
Log::error(sprintf('INTL dst address [%s] doesnt match packet',$dst));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// We'll set our source zone
|
||||||
|
$this->zone['dst'] = $ftn['z'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
elseif ($t = $this->kludge('MSGID: ',$v))
|
||||||
|
$this->msgid = $t;
|
||||||
|
|
||||||
|
elseif ($t = $this->kludge('PATH: ',$v))
|
||||||
|
$this->path->push($t);
|
||||||
|
|
||||||
|
// To Point: <SOH>TOPT <point number><CR>
|
||||||
|
elseif ($t = $this->kludge('TOPT ',$v))
|
||||||
|
$this->point['dst'] = $t;
|
||||||
|
|
||||||
|
// <SOH>Via <FTN Address> @YYYYMMDD.HHMMSS[.Precise][.Time Zone] <Program Name> <Version> [Serial Number]<CR>
|
||||||
|
elseif ($t = $this->kludge('Via ',$v))
|
||||||
|
$this->via->push($t);
|
||||||
|
|
||||||
|
// We got a kludge line we dont know about
|
||||||
|
else
|
||||||
|
$this->unknown->push(chop($v,"\r"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -529,7 +644,7 @@ class Message extends FTNBase
|
|||||||
*
|
*
|
||||||
* @return \Illuminate\Contracts\Validation\Validator
|
* @return \Illuminate\Contracts\Validation\Validator
|
||||||
*/
|
*/
|
||||||
private function validate(): ValidatorResult
|
public function validate(Domain $domain=NULL): ValidatorResult
|
||||||
{
|
{
|
||||||
// Check lengths
|
// Check lengths
|
||||||
$validator = Validator::make([
|
$validator = Validator::make([
|
||||||
@ -543,6 +658,8 @@ class Message extends FTNBase
|
|||||||
'flags' => $this->flags,
|
'flags' => $this->flags,
|
||||||
'cost' => $this->cost,
|
'cost' => $this->cost,
|
||||||
'echoarea' => $this->echoarea,
|
'echoarea' => $this->echoarea,
|
||||||
|
'ozone' => $this->fz,
|
||||||
|
'dzone' => $this->tz,
|
||||||
],[
|
],[
|
||||||
'user_from' => 'required|min:1|max:'.self::USER_FROM_LEN,
|
'user_from' => 'required|min:1|max:'.self::USER_FROM_LEN,
|
||||||
'user_to' => 'required|min:1|max:'.self::USER_TO_LEN,
|
'user_to' => 'required|min:1|max:'.self::USER_TO_LEN,
|
||||||
@ -554,8 +671,19 @@ class Message extends FTNBase
|
|||||||
'flags' => 'required|numeric',
|
'flags' => 'required|numeric',
|
||||||
'cost' => 'required|numeric',
|
'cost' => 'required|numeric',
|
||||||
'echoarea' => 'nullable|max:'.self::AREATAG_LEN,
|
'echoarea' => 'nullable|max:'.self::AREATAG_LEN,
|
||||||
|
'ozone' => ['required',$this->domain ? 'in:'.$x=join(',',$this->domain->zones->pluck('zone_id')->toArray()): ''],
|
||||||
|
'dzone' => ['required',$this->domain ? 'in:'.$x : '']
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if ($domain) {
|
||||||
|
$validator->after(function($validator) {
|
||||||
|
if (! Address::findFTN($this->fftn))
|
||||||
|
$validator->errors()->add('from',sprintf('Undefined Node [%s] sent packet.',$this->fftn));
|
||||||
|
if (! Address::findFTN($this->tftn))
|
||||||
|
$validator->errors()->add('to',sprintf('Undefined Node [%s] for destination.',$this->fftn));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if ($validator->fails())
|
if ($validator->fails())
|
||||||
$this->errors = $validator;
|
$this->errors = $validator;
|
||||||
|
|
||||||
|
@ -9,12 +9,10 @@ use Illuminate\Support\Facades\Log;
|
|||||||
use Symfony\Component\HttpFoundation\File\File;
|
use Symfony\Component\HttpFoundation\File\File;
|
||||||
|
|
||||||
use App\Classes\FTN as FTNBase;
|
use App\Classes\FTN as FTNBase;
|
||||||
use App\Models\Software;
|
use App\Models\{Address,Domain,Setup,Software};
|
||||||
use App\Traits\GetNode;
|
|
||||||
|
|
||||||
class Packet extends FTNBase
|
class Packet extends FTNBase
|
||||||
{
|
{
|
||||||
//use GetNode;
|
|
||||||
private const LOGKEY = 'PKT';
|
private const LOGKEY = 'PKT';
|
||||||
|
|
||||||
private const HEADER_LEN = 0x3a;
|
private const HEADER_LEN = 0x3a;
|
||||||
@ -22,10 +20,6 @@ class Packet extends FTNBase
|
|||||||
private const BLOCKSIZE = 1024;
|
private const BLOCKSIZE = 1024;
|
||||||
private const PACKED_MSG_HEADER_LEN = 0x22;
|
private const PACKED_MSG_HEADER_LEN = 0x22;
|
||||||
|
|
||||||
public File $file; // Packet filename
|
|
||||||
public Collection $messages; // Messages in the Packet
|
|
||||||
private array $header; // Packet Header
|
|
||||||
|
|
||||||
// V2 Packet Header (2/2e/2+)
|
// V2 Packet Header (2/2e/2+)
|
||||||
private const v2header = [
|
private const v2header = [
|
||||||
'onode' => [0x00,'v',2], // Originating Node
|
'onode' => [0x00,'v',2], // Originating Node
|
||||||
@ -54,17 +48,113 @@ class Packet extends FTNBase
|
|||||||
'dzone' => [0x30,'v',2], // Destination Zone (Not used 2)
|
'dzone' => [0x30,'v',2], // Destination Zone (Not used 2)
|
||||||
'opoint' => [0x32,'v',2], // Originating Point (Not used 2)
|
'opoint' => [0x32,'v',2], // Originating Point (Not used 2)
|
||||||
'dpoint' => [0x34,'v',2], // Destination Point (Not used 2)
|
'dpoint' => [0x34,'v',2], // Destination Point (Not used 2)
|
||||||
'proddata' => [0x36,'A4',4], // ProdData (Not used 2) // FSC-39/FSC-48
|
'proddata' => [0x36,'a4',4], // ProdData (Not used 2) // FSC-39/FSC-48
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct(File $file)
|
private array $header; // Packet Header
|
||||||
|
|
||||||
|
public File $file; // Packet filename
|
||||||
|
public Collection $messages; // Messages in the Packet
|
||||||
|
|
||||||
|
public function __construct(Address $o=NULL)
|
||||||
{
|
{
|
||||||
$this->messages = collect();
|
$this->messages = collect();
|
||||||
|
$this->domain = NULL;
|
||||||
|
|
||||||
if ($file) {
|
// If we are creating an outbound packet, we need to set our header
|
||||||
$this->file = $file;
|
if ($o)
|
||||||
$this->open($file);
|
$this->newHeader($o);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a packet file
|
||||||
|
*
|
||||||
|
* @param File $file
|
||||||
|
* @param Domain|null $domain
|
||||||
|
* @return Packet
|
||||||
|
* @throws InvalidPacketException
|
||||||
|
*/
|
||||||
|
public static function open(File $file,Domain $domain=NULL): self
|
||||||
|
{
|
||||||
|
Log::debug(sprintf('%s:Opening Packet [%s]',self::LOGKEY,$file));
|
||||||
|
|
||||||
|
$f = fopen($file,'r');
|
||||||
|
$fstat = fstat($f);
|
||||||
|
|
||||||
|
// PKT Header
|
||||||
|
$header = fread($f,self::HEADER_LEN);
|
||||||
|
Log::debug(sprintf("%s:\n%s",self::LOGKEY,hex_dump($header)));
|
||||||
|
|
||||||
|
// Could not read header
|
||||||
|
if (strlen($header) != self::HEADER_LEN)
|
||||||
|
throw new InvalidPacketException(sprintf('Length of header [%d] too short'.strlen($header)));
|
||||||
|
|
||||||
|
// Not a type 2 packet
|
||||||
|
$version = Arr::get(unpack('vv',substr($header,self::VERSION_OFFSET)),'v');
|
||||||
|
if ($version != 2)
|
||||||
|
throw new InvalidPacketException('Not a type 2 packet: '.$version);
|
||||||
|
|
||||||
|
$o = new self;
|
||||||
|
$o->header = unpack(self::unpackheader(self::v2header),$header);
|
||||||
|
|
||||||
|
$x = fread($f,2);
|
||||||
|
|
||||||
|
// End of Packet?
|
||||||
|
if (strlen($x) == 2 and $x == "\00\00")
|
||||||
|
return new self;
|
||||||
|
|
||||||
|
// Messages start with 02H 00H
|
||||||
|
if (strlen($x) == 2 AND $x != "\02\00")
|
||||||
|
throw new InvalidPacketException('Not a valid packet: '.bin2hex($x));
|
||||||
|
|
||||||
|
// No message attached
|
||||||
|
else if (! strlen($x))
|
||||||
|
throw new InvalidPacketException('No message in packet: '.bin2hex($x));
|
||||||
|
|
||||||
|
$buf_ptr = 0;
|
||||||
|
$message = '';
|
||||||
|
$readbuf = '';
|
||||||
|
|
||||||
|
while ($buf_ptr || (! feof($f) && ($readbuf=fread($f,self::BLOCKSIZE)))) {
|
||||||
|
// A message header is atleast 0x22 chars long
|
||||||
|
if (strlen($readbuf) < self::PACKED_MSG_HEADER_LEN) {
|
||||||
|
$message .= $readbuf;
|
||||||
|
$buf_ptr = 0;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
|
||||||
|
} elseif (strlen($message) < self::PACKED_MSG_HEADER_LEN) {
|
||||||
|
$addchars = self::PACKED_MSG_HEADER_LEN-strlen($message);
|
||||||
|
$message .= substr($readbuf,$buf_ptr,$addchars);
|
||||||
|
$buf_ptr += $addchars;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didnt find a packet end, perhaps there are no more
|
||||||
|
if (($end=strpos($readbuf,"\x00\x02\x00",$buf_ptr)) === FALSE) {
|
||||||
|
$end = strpos($readbuf,"\x00\x00\x00",$buf_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if we have found the end of the packet, if not read more.
|
||||||
|
if ($end === FALSE && (ftell($f) < $fstat['size'])) {
|
||||||
|
$message .= substr($readbuf,$buf_ptr);
|
||||||
|
$buf_ptr = 0;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$message .= substr($readbuf,$buf_ptr,$end-$buf_ptr);
|
||||||
|
$buf_ptr = $end+3;
|
||||||
|
|
||||||
|
if ($buf_ptr >= strlen($readbuf))
|
||||||
|
$buf_ptr = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for the next message
|
||||||
|
$o->parseMessage($message,$domain);
|
||||||
|
$message = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $o;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -132,31 +222,14 @@ class Packet extends FTNBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @note - messages in this object have the same next destination
|
/**
|
||||||
// @todo To rework
|
* Return the packet
|
||||||
/*
|
*
|
||||||
|
* @return string
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
public function __toString(): string
|
public function __toString(): string
|
||||||
{
|
{
|
||||||
// @todo - is this appropriate to set here
|
|
||||||
$this->date = now();
|
|
||||||
$this->pktsrc = (string)$this->get_node(ftn_address_split('10:1/5.0'),TRUE);
|
|
||||||
|
|
||||||
// @todo
|
|
||||||
if ($this->messages->first()->type == 'echomail')
|
|
||||||
$this->pktdst = (string)$this->messages->first()->fqfa->uplink;
|
|
||||||
else
|
|
||||||
$this->pktdst = (string)$this->messages->first()->fqda->uplink;
|
|
||||||
|
|
||||||
$this->software['prodcode-lo'] = 0x00;
|
|
||||||
$this->software['prodcode-hi'] = 0xde;
|
|
||||||
$this->software['rev-maj'] = 0x00;
|
|
||||||
$this->software['rev-min'] = 0x01;
|
|
||||||
|
|
||||||
// Type 2+ Packet
|
|
||||||
$this->cap['valid'] = 0x0100;
|
|
||||||
$this->cap['word'] = 0x0001;
|
|
||||||
$this->pktver = 0x0002;
|
|
||||||
|
|
||||||
$return = $this->createHeader();
|
$return = $this->createHeader();
|
||||||
|
|
||||||
foreach ($this->messages as $o)
|
foreach ($this->messages as $o)
|
||||||
@ -166,143 +239,101 @@ class Packet extends FTNBase
|
|||||||
|
|
||||||
return $return;
|
return $return;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create our message packet header
|
* Create our message packet header
|
||||||
* @todo To rework
|
|
||||||
*/
|
*/
|
||||||
/*
|
|
||||||
private function createHeader(): string
|
private function createHeader(): string
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$a = pack(join('',collect($this->pack1)->pluck(1)->toArray()),
|
$a = pack(join('',collect(self::v2header)->pluck(1)->toArray()),
|
||||||
$this->ff,
|
$this->ff,
|
||||||
$this->tf,
|
$this->tf,
|
||||||
$this->date->year,
|
Arr::get($this->header,'y'),
|
||||||
$this->date->month,
|
Arr::get($this->header,'m'),
|
||||||
$this->date->day,
|
Arr::get($this->header,'d'),
|
||||||
$this->date->hour,
|
Arr::get($this->header,'H'),
|
||||||
$this->date->minute,
|
Arr::get($this->header,'M'),
|
||||||
$this->date->second,
|
Arr::get($this->header,'S'),
|
||||||
$this->baud,
|
Arr::get($this->header,'baud',0),
|
||||||
$this->pktver,
|
Arr::get($this->header,'pktver',2),
|
||||||
$this->fn, // @todo if point, this needs to be 0xff
|
$this->fn, // @todo if point, this needs to be 0xff
|
||||||
$this->tn,
|
$this->tn,
|
||||||
$this->software['prodcode-lo'], // @todo change to this software
|
Arr::get($this->header,'prodcode-lo',(Setup::PRODUCT_ID & 0xff)),
|
||||||
$this->software['rev-maj'] // @todo change to this software
|
Arr::get($this->header,'prodrev-maj',Setup::PRODUCT_VERSION_MAJ),
|
||||||
);
|
$this->password,
|
||||||
|
|
||||||
$b = pack(join('',collect($this->pack2)->pluck(1)->toArray()),
|
|
||||||
0x0000, // @note: Type 2 packet this is $this->sz,
|
|
||||||
0x0000, // @note: Type 2 packet this is $this->dz,
|
|
||||||
0x0000, // Filler $this->>sn if message to point.
|
|
||||||
$this->cap['valid'], // @todo to check
|
|
||||||
$this->software['prodcode-hi'], // @todo change to this software
|
|
||||||
$this->software['rev-min'], // @todo change to this software
|
|
||||||
$this->cap['word'], // @todo to check
|
|
||||||
$this->fz,
|
$this->fz,
|
||||||
$this->tz,
|
$this->tz,
|
||||||
$this->fp, // @note: point address, type 2+ packets
|
Arr::get($this->header,'filler',''),
|
||||||
$this->tp // @note: point address, type 2+ packets
|
Arr::get($this->header,'capvalid',1<<0),
|
||||||
|
Arr::get($this->header,'prodcode-hi',(Setup::PRODUCT_ID >> 8) & 0xff),
|
||||||
|
Arr::get($this->header,'prodrev-min',Setup::PRODUCT_VERSION_MIN),
|
||||||
|
Arr::get($this->header,'capword',1<<0),
|
||||||
|
$this->fz,
|
||||||
|
$this->tz,
|
||||||
|
$this->fp,
|
||||||
|
$this->tp,
|
||||||
|
Arr::get($this->header,'proddata','AB8D'),
|
||||||
);
|
);
|
||||||
|
|
||||||
return $a.pack('a8',strtoupper($this->password)).$b."mbse"; // @todo change to this software
|
return $a;
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return $e->getMessage();
|
return $e->getMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addMessage(FTNMessage $o)
|
/**
|
||||||
|
* Add a netmail message to this packet
|
||||||
|
*
|
||||||
|
* @param Message $o
|
||||||
|
*/
|
||||||
|
public function addNetmail(Message $o): void
|
||||||
{
|
{
|
||||||
// @todo Check that this message is for the same uplink.
|
|
||||||
$this->messages->push($o);
|
$this->messages->push($o);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a packet file
|
* When creating a new packet, set the header.
|
||||||
*
|
*
|
||||||
* @param string $file
|
* @param array $header
|
||||||
* @throws InvalidPacketException
|
|
||||||
*/
|
*/
|
||||||
private function open(string $file)
|
private function newHeader(Address $o): void
|
||||||
{
|
{
|
||||||
Log::debug(sprintf('%s:Opening Packet [%s]',self::LOGKEY,$file));
|
$date = Carbon::now();
|
||||||
|
$ao = Setup::findOrFail(config('app.id'))->system->match($o);
|
||||||
|
|
||||||
$f = fopen($file,'r');
|
// Create Header
|
||||||
$fstat = fstat($f);
|
$this->header = [
|
||||||
|
'onode' => $ao->node_id, // Originating Node
|
||||||
|
'dnode' => $o->node_id, // Destination Node
|
||||||
|
'y' => $date->format('Y'), // Year
|
||||||
|
'm' => $date->format('m')-1, // Month
|
||||||
|
'd' => $date->format('d'), // Day
|
||||||
|
'H' => $date->format('H'), // Hour
|
||||||
|
'M' => $date->format('i'), // Minute
|
||||||
|
'S' => $date->format('s'), // Second
|
||||||
|
'onet' => $ao->host_id ?: $ao->region_id, // Originating Net (0xffff when origPoint !=0 2+)
|
||||||
|
'dnet' => $o->host_id ?: $o->region_id, // Destination Net
|
||||||
|
'password' => $o->session('pktpass'), // Packet Password
|
||||||
|
'qozone' => $ao->zone->zone_id,
|
||||||
|
'qdzone' => $o->zone->zone_id,
|
||||||
|
'ozone' => $ao->zone->zone_id, // Originating Zone (Not used 2)
|
||||||
|
'dzone' => $o->zone->zone_id, // Destination Zone (Not used 2)
|
||||||
|
'opoint' => $ao->point_id, // Originating Point (Not used 2)
|
||||||
|
'dpoint' => $o->point_id, // Destination Point (Not used 2)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
// PKT Header
|
/**
|
||||||
$header = fread($f,self::HEADER_LEN);
|
* Parse a message in a mail packet
|
||||||
Log::debug(sprintf("%s:\n%s",self::LOGKEY,hex_dump($header)));
|
*
|
||||||
|
* @param string $message
|
||||||
// Could not read header
|
* @param Domain $domain
|
||||||
if (strlen($header) != self::HEADER_LEN)
|
* @throws \Exception
|
||||||
throw new InvalidPacketException(sprintf('Length of header [%d] too short'.strlen($header)));
|
*/
|
||||||
|
public function parseMessage(string $message,Domain $domain=NULL): void
|
||||||
// Not a type 2 packet
|
{
|
||||||
$version = Arr::get(unpack('vv',substr($header,self::VERSION_OFFSET)),'v');
|
$this->messages->push(Message::parseMessage($message,$domain));
|
||||||
if ($version != 2)
|
|
||||||
throw new InvalidPacketException('Not a type 2 packet: '.$version);
|
|
||||||
|
|
||||||
$this->header = unpack($this->unpackheader(self::v2header),$header);
|
|
||||||
|
|
||||||
$x = fread($f,2);
|
|
||||||
|
|
||||||
// End of Packet?
|
|
||||||
if (strlen($x) == 2 and $x == "\00\00")
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Messages start with 02H 00H
|
|
||||||
if (strlen($x) == 2 AND $x != "\02\00")
|
|
||||||
throw new InvalidPacketException('Not a valid packet: '.bin2hex($x));
|
|
||||||
|
|
||||||
// No message attached
|
|
||||||
else if (! strlen($x))
|
|
||||||
throw new InvalidPacketException('No message in packet: '.bin2hex($x));
|
|
||||||
|
|
||||||
$buf_ptr = 0;
|
|
||||||
$message = '';
|
|
||||||
$readbuf = '';
|
|
||||||
|
|
||||||
while ($buf_ptr || (! feof($f) && ($readbuf=fread($f,self::BLOCKSIZE)))) {
|
|
||||||
// A message header is atleast 0x22 chars long
|
|
||||||
if (strlen($readbuf) < self::PACKED_MSG_HEADER_LEN) {
|
|
||||||
$message .= $readbuf;
|
|
||||||
$buf_ptr = 0;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
|
|
||||||
} elseif (strlen($message) < self::PACKED_MSG_HEADER_LEN) {
|
|
||||||
$addchars = self::PACKED_MSG_HEADER_LEN-strlen($message);
|
|
||||||
$message .= substr($readbuf,$buf_ptr,$addchars);
|
|
||||||
$buf_ptr += $addchars;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we didnt find a packet end, perhaps there are no more
|
|
||||||
if (($end=strpos($readbuf,"\x00\x02\x00",$buf_ptr)) === FALSE)
|
|
||||||
$end = strpos($readbuf,"\x00\x00\x00",$buf_ptr);
|
|
||||||
|
|
||||||
// See if we have found the end of the packet, if not read more.
|
|
||||||
if ($end === FALSE && (ftell($f) < $fstat['size'])) {
|
|
||||||
$message .= substr($readbuf,$buf_ptr);
|
|
||||||
$buf_ptr = 0;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$message .= substr($readbuf,$buf_ptr,$end-$buf_ptr);
|
|
||||||
$buf_ptr += $end-$buf_ptr+3;
|
|
||||||
|
|
||||||
if ($buf_ptr >= strlen($readbuf))
|
|
||||||
$buf_ptr = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for the next message
|
|
||||||
$this->messages->push(new Message($message));
|
|
||||||
$message = '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
84
app/Classes/FTN/Process.php
Normal file
84
app/Classes/FTN/Process.php
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Classes\FTN;
|
||||||
|
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
abstract class Process
|
||||||
|
{
|
||||||
|
protected const MSG_WIDTH = 79;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return TRUE if the process class handled the message.
|
||||||
|
*
|
||||||
|
* @param Message $msg
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
abstract public static function handle(Message $msg): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function will format text to static::MSG_WIDTH, as well as adding the logo.
|
||||||
|
*/
|
||||||
|
protected static function format_msg(string $text): string
|
||||||
|
{
|
||||||
|
$msg = utf8_decode(join("\n",static::msg_header()))."\n";
|
||||||
|
$c = 0;
|
||||||
|
$offset = 0;
|
||||||
|
|
||||||
|
while ($offset < strlen($text)) {
|
||||||
|
$ll = '';
|
||||||
|
|
||||||
|
// Add our logo
|
||||||
|
if ($c<count(static::$logo)) {
|
||||||
|
$line = utf8_decode(Arr::get(static::$logo,$c++));
|
||||||
|
$ll = $line.' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for a return
|
||||||
|
$return = strpos($text,"\n",$offset);
|
||||||
|
|
||||||
|
if ($return !== FALSE)
|
||||||
|
$return -= $offset;
|
||||||
|
|
||||||
|
if (($return !== FALSE && $return < static::MSG_WIDTH-strlen($ll))) {
|
||||||
|
$subtext = substr($text,$offset,$return);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$subtext = substr($text,$offset,static::MSG_WIDTH-strlen($ll));
|
||||||
|
|
||||||
|
// Look for a space
|
||||||
|
$space = strrpos($subtext,' ');
|
||||||
|
|
||||||
|
if ($space == FALSE)
|
||||||
|
$space = strlen($subtext);
|
||||||
|
else
|
||||||
|
$subtext = substr($text,$offset,$space);
|
||||||
|
}
|
||||||
|
|
||||||
|
$msg .= $ll.$subtext."\n";
|
||||||
|
$offset += strlen($subtext)+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case our text is shorter than the loo
|
||||||
|
for ($c; $c<count(static::$logo);$c++)
|
||||||
|
$msg .= utf8_decode(Arr::get(static::$logo,$c))."\n";
|
||||||
|
|
||||||
|
return $msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Header added to messages
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
protected static function msg_header(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
' ÜÜÜ Ü ÜÜÜ ÜÜÜ ÜÜÜ Ü ÜÜÜ ÜÜÜ Ü ÜÜÜ Ü Ü ÜÜÜ',
|
||||||
|
' Û ß Û ÛÜÛ ÜÜÛ Û ß Ü Û Û ÛÜÛ ÛßÛ Û Û Û Û Üß',
|
||||||
|
' ÛÜÛ ÛÜÛ ÛÜÜ ÛÜÛ Û Û Û Û ÜÜÛ Û Û ÛÜÛ ÛÜÛ ÛÜÜ',
|
||||||
|
' FTN Mailer and Tosser',
|
||||||
|
'ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
54
app/Classes/FTN/Process/Ping.php
Normal file
54
app/Classes/FTN/Process/Ping.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Classes\FTN\Process;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Carbon\CarbonInterface;
|
||||||
|
|
||||||
|
use App\Classes\FTN\{Message,Process};
|
||||||
|
use App\Models\{Netmail,Setup};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process messages to Ping
|
||||||
|
*
|
||||||
|
* @package App\Classes\FTN\Process
|
||||||
|
*/
|
||||||
|
final class Ping extends Process
|
||||||
|
{
|
||||||
|
protected static array $logo = [
|
||||||
|
'ÚÄ¿þÚÄ¿ÚÄ¿',
|
||||||
|
'³ ³Â³ ³Àij',
|
||||||
|
'ÃÄÙÁÁ ÁÄÄÙ'
|
||||||
|
];
|
||||||
|
|
||||||
|
public static function handle(Message $msg): bool
|
||||||
|
{
|
||||||
|
if (strtolower($msg->user_to) !== 'ping')
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
$reply = sprintf("Your ping was received here on %s and it took %s to get here.\n",
|
||||||
|
Carbon::now()->toDateTimeString(),
|
||||||
|
$msg->date->diffForHumans(['parts'=>3,'syntax'=>CarbonInterface::DIFF_ABSOLUTE])
|
||||||
|
);
|
||||||
|
|
||||||
|
$reply .= "\n";
|
||||||
|
$reply .= "Your message travelled along this path to get here:\n";
|
||||||
|
foreach ($msg->via as $path)
|
||||||
|
$reply .= sprintf(" * %s\n",$path);
|
||||||
|
|
||||||
|
$o = new Netmail();
|
||||||
|
$o->to = $msg->user_from;
|
||||||
|
$o->from = Setup::PRODUCT_NAME;
|
||||||
|
$o->subject = 'Ping Reply';
|
||||||
|
$o->fftn_id = ($x=$msg->tftn_o) ? $x->id : NULL;
|
||||||
|
$o->tftn_id = ($x=$msg->fftn_o) ? $x->id : NULL;
|
||||||
|
$o->msg = static::format_msg($reply);
|
||||||
|
$o->reply = $msg->msgid;
|
||||||
|
|
||||||
|
$o->tagline = '... My ping pong opponent was not happy with my serve. He kept returning it.';
|
||||||
|
$o->tearline = sprintf('--- %s (%s)',Setup::PRODUCT_NAME,(new Setup)->version);
|
||||||
|
$o->save();
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
@ -4,13 +4,13 @@ namespace App\Console\Commands;
|
|||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
use App\Traits\{GetNode,ParseNodes};
|
use App\Traits\ParseNodes;
|
||||||
use App\Classes\FTNPacket;
|
use App\Classes\FTNPacket;
|
||||||
use App\Models\{Echomail,Netmail,Zone};
|
use App\Models\{Echomail,Netmail,Zone};
|
||||||
|
|
||||||
class ImportPacket extends Command
|
class ImportPacket extends Command
|
||||||
{
|
{
|
||||||
use GetNode,ParseNodes;
|
use ParseNodes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name and signature of the console command.
|
* The name and signature of the console command.
|
||||||
|
47
app/Console/Commands/ProcessPacket.php
Normal file
47
app/Console/Commands/ProcessPacket.php
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Symfony\Component\HttpFoundation\File\File;
|
||||||
|
|
||||||
|
use App\Classes\FTN\Packet;
|
||||||
|
use App\Jobs\ProcessPacket as Job;
|
||||||
|
use App\Models\Domain;
|
||||||
|
|
||||||
|
class ProcessPacket extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'packet:process {pkt : Packet to process} {domain : Domain the packet is from}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Process Packet';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
* @throws \App\Classes\FTN\InvalidPacketException
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$f = new File($this->argument('pkt'));
|
||||||
|
$d = Domain::where('name',$this->argument('domain'))->singleOrFail();
|
||||||
|
|
||||||
|
foreach ((Packet::open($f,$d))->messages as $msg) {
|
||||||
|
// @todo Quick check that the packet should be processed by us.
|
||||||
|
// @todo validate that the packet's zone is in the domain.
|
||||||
|
|
||||||
|
// Dispatch job.
|
||||||
|
Job::dispatchSync($msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -51,10 +51,10 @@ class HomeController extends Controller
|
|||||||
|
|
||||||
foreach ($filegroup as $file) {
|
foreach ($filegroup as $file) {
|
||||||
try {
|
try {
|
||||||
$pkt = new Packet($file);
|
$pkt = Packet::open($file);
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return redirect()->back()->withErrors($e->getMessage());
|
return redirect()->back()->withErrors(sprintf('%s (%s:%d)',$e->getMessage(),$e->getFile(),$e->getLine()));
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
78
app/Jobs/ProcessPacket.php
Normal file
78
app/Jobs/ProcessPacket.php
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
use App\Classes\FTN\Message;
|
||||||
|
use App\Models\Setup;
|
||||||
|
|
||||||
|
class ProcessPacket implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
private Message $msg;
|
||||||
|
|
||||||
|
public function __construct(Message $msg)
|
||||||
|
{
|
||||||
|
// Some checks
|
||||||
|
$this->msg = $msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When calling ProcessPacket - we assume that the packet is from a valid source
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
// Load our details
|
||||||
|
$ftns = Setup::findOrFail(config('app.id'))->system->addresses;
|
||||||
|
|
||||||
|
// If we are a netmail
|
||||||
|
if ($this->msg->isNetmail()) {
|
||||||
|
// @todo Enable checks to reject old messages
|
||||||
|
|
||||||
|
// Determine if the message is to this system, or in transit
|
||||||
|
if ($ftns->search(function($item) { dump($item->ftn);return $this->msg->tftn == $item->ftn; }) !== FALSE) {
|
||||||
|
// @todo Check if it is a duplicate message
|
||||||
|
// @todo Check if the message is from a system we know about
|
||||||
|
|
||||||
|
$processed = FALSE;
|
||||||
|
|
||||||
|
// If the message is to a bot, we'll process it
|
||||||
|
foreach (config('process.robots') as $class) {
|
||||||
|
if ($processed = $class::handle($this->msg)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not processed, no users here!
|
||||||
|
if (! $processed) {
|
||||||
|
dump('message not processed, no users here');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If in transit, store for collection
|
||||||
|
} else {
|
||||||
|
// @todo Check if the message is to a system we know about
|
||||||
|
// @todo In transit loop checking
|
||||||
|
// @todo In transit TRACE response
|
||||||
|
|
||||||
|
dump('netmail in transit');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else we are echomail
|
||||||
|
} else {
|
||||||
|
dump('echomail');
|
||||||
|
// Determine if we know about this echo area
|
||||||
|
// Can the sender create it if it doesnt exist?
|
||||||
|
// Create it, or
|
||||||
|
|
||||||
|
// Else record in bad area
|
||||||
|
|
||||||
|
// We know about this area, store it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ use Exception;
|
|||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
use App\Classes\FTN\Packet;
|
||||||
use App\Http\Controllers\DomainController;
|
use App\Http\Controllers\DomainController;
|
||||||
use App\Traits\ScopeActive;
|
use App\Traits\ScopeActive;
|
||||||
|
|
||||||
@ -28,13 +29,35 @@ class Address extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Find children dependant on this record
|
* Find children dependant on this record
|
||||||
*
|
|
||||||
* @todo While this is finding children of hubs, we are not currently finding children of Hosts or Regions.
|
|
||||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
|
||||||
*/
|
*/
|
||||||
public function children()
|
public function children()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(self::class,'id','hub_id');
|
switch (strtolower($this->role)) {
|
||||||
|
case 'region':
|
||||||
|
return $this->hasMany(self::class,'region_id','region_id')
|
||||||
|
->where('zone_id',$this->zone_id)
|
||||||
|
->where(function($q) {
|
||||||
|
return $q->where('host_id',0)
|
||||||
|
->orWhere('role',DomainController::NODE_NC);
|
||||||
|
})
|
||||||
|
->where('id','<>',$this->id);
|
||||||
|
|
||||||
|
case 'host':
|
||||||
|
return $this->hasMany(self::class,'host_id','host_id')
|
||||||
|
->where('zone_id',$this->zone_id)
|
||||||
|
->where('region_id',$this->region_id)
|
||||||
|
->whereNull('hub_id')
|
||||||
|
->where('id','<>',$this->id);
|
||||||
|
|
||||||
|
case 'hub':
|
||||||
|
return $this->hasMany(self::class,'hub_id','id');
|
||||||
|
|
||||||
|
case 'node':
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Exception('Unknown role: '.$this->role);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function system()
|
public function system()
|
||||||
@ -54,17 +77,17 @@ class Address extends Model
|
|||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getFTNAttribute()
|
public function getFTNAttribute(): string
|
||||||
{
|
{
|
||||||
return sprintf('%s@%s',$this->getFTN4DAttribute(),$this->zone->domain->name);
|
return sprintf('%s@%s',$this->getFTN4DAttribute(),$this->zone->domain->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFTN3DAttribute()
|
public function getFTN3DAttribute(): string
|
||||||
{
|
{
|
||||||
return sprintf('%d:%d/%d',$this->zone->zone_id,$this->host_id ?: $this->region_id,$this->node_id);
|
return sprintf('%d:%d/%d',$this->zone->zone_id,$this->host_id ?: $this->region_id,$this->node_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFTN4DAttribute()
|
public function getFTN4DAttribute(): string
|
||||||
{
|
{
|
||||||
return sprintf('%s.%d',$this->getFTN3DAttribute(),$this->point_id);
|
return sprintf('%s.%d',$this->getFTN3DAttribute(),$this->point_id);
|
||||||
}
|
}
|
||||||
@ -91,7 +114,7 @@ class Address extends Model
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* GENERAL METHODS */
|
/* METHODS */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a record in the DB for a node string, eg: 10:1/1.0
|
* Find a record in the DB for a node string, eg: 10:1/1.0
|
||||||
@ -126,6 +149,22 @@ class Address extends Model
|
|||||||
return ($o && $o->system->active) ? $o : NULL;
|
return ($o && $o->system->active) ? $o : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get netmail for this node (including it's children)
|
||||||
|
*/
|
||||||
|
public function getNetmail(): Packet
|
||||||
|
{
|
||||||
|
$o = new Packet($this);
|
||||||
|
|
||||||
|
foreach (Netmail::whereIn('tftn_id',$this->children->pluck('id')->push($this->id))->get() as $oo) {
|
||||||
|
$o->addNetmail($oo->packet());
|
||||||
|
|
||||||
|
// @todo We need to mark the netmail as sent
|
||||||
|
}
|
||||||
|
|
||||||
|
return $o;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a string and split it out as an FTN array
|
* Parse a string and split it out as an FTN array
|
||||||
*
|
*
|
||||||
|
@ -2,21 +2,92 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Carbon\Carbon;
|
||||||
|
use Jenssegers\Mongodb\Eloquent\Model;
|
||||||
|
use Jenssegers\Mongodb\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
use App\Classes\FTN\Message;
|
||||||
|
|
||||||
class Netmail extends Model
|
class Netmail extends Model
|
||||||
{
|
{
|
||||||
protected $dates = ['date'];
|
use SoftDeletes;
|
||||||
protected $fillable = ['date','msgid','from_ftn'];
|
|
||||||
public $timestamps = FALSE; // @todo Remove, seems an issue with cockroach updating tables.
|
|
||||||
|
|
||||||
public function kludges()
|
protected $connection = 'mongodb';
|
||||||
|
protected $dates = ['datetime'];
|
||||||
|
|
||||||
|
/* RELATIONS */
|
||||||
|
|
||||||
|
public function fftn()
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(Kludge::class);
|
return $this
|
||||||
|
->setConnection('pgsql')
|
||||||
|
->belongsTo(Address::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function paths()
|
public function tftn()
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(Path::class,NULL,NULL,'node_id');
|
return $this
|
||||||
|
->setConnection('pgsql')
|
||||||
|
->belongsTo(Address::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ATTRIBUTES */
|
||||||
|
|
||||||
|
public function getMsgAttribute($value): string
|
||||||
|
{
|
||||||
|
return utf8_decode($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMsgAttribute($value): void
|
||||||
|
{
|
||||||
|
$this->attributes['msg'] = utf8_encode($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* METHODS */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return this model as a packet
|
||||||
|
*/
|
||||||
|
public function packet(): Message
|
||||||
|
{
|
||||||
|
$o = new Message;
|
||||||
|
|
||||||
|
$o->header = [
|
||||||
|
'onode' => $this->fftn->node_id,
|
||||||
|
'dnode' => $this->tftn->node_id,
|
||||||
|
'onet' => $this->fftn->host_id,
|
||||||
|
'dnet' => $this->tftn->host_id,
|
||||||
|
'flags' => 0, // @todo?
|
||||||
|
'cost' => 0,
|
||||||
|
'date'=>$this->created_at->format('d M y H:i:s'),
|
||||||
|
];
|
||||||
|
|
||||||
|
$o->user_to = $this->to;
|
||||||
|
$o->user_from = $this->from;
|
||||||
|
$o->subject = $this->subject;
|
||||||
|
$o->message = $this->msg;
|
||||||
|
|
||||||
|
$o->msgid = sprintf('%s %08x',$this->fftn->ftn3d,crc32($this->id));
|
||||||
|
|
||||||
|
// VIA kludge
|
||||||
|
$via = $this->via ?: collect();
|
||||||
|
$via->push(
|
||||||
|
sprintf('%s @%s.UTC %s %d.%d/%s %s',
|
||||||
|
$this->fftn->ftn3d,
|
||||||
|
Carbon::now()->utc()->format('Ymd.His'),
|
||||||
|
Setup::PRODUCT_NAME,
|
||||||
|
Setup::PRODUCT_VERSION_MAJ,
|
||||||
|
Setup::PRODUCT_VERSION_MIN,
|
||||||
|
(new Setup)->version,
|
||||||
|
Carbon::now()->format('Y-m-d'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$o->via = $via;
|
||||||
|
|
||||||
|
// INTL kludge
|
||||||
|
// @todo Point handling FMPT/TOPT
|
||||||
|
$o->intl = sprintf('%s %s',$this->tftn->ftn3d,$this->fftn->ftn3d);
|
||||||
|
|
||||||
|
return $o;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -37,6 +37,11 @@ class Setup extends Model
|
|||||||
public const O_EMSI = 1<<2; /* Listen for EMSI connections */
|
public const O_EMSI = 1<<2; /* Listen for EMSI connections */
|
||||||
public const O_HIDEAKA = 1<<3; /* Hide AKAs to different Zones */
|
public const O_HIDEAKA = 1<<3; /* Hide AKAs to different Zones */
|
||||||
|
|
||||||
|
public const PRODUCT_NAME = 'Clearing Houz';
|
||||||
|
public const PRODUCT_ID = 0xAB8D;
|
||||||
|
public const PRODUCT_VERSION_MAJ = 0;
|
||||||
|
public const PRODUCT_VERSION_MIN = 0;
|
||||||
|
|
||||||
// Our non model attributes and values
|
// Our non model attributes and values
|
||||||
private array $internal = [];
|
private array $internal = [];
|
||||||
|
|
||||||
|
@ -56,7 +56,18 @@ class System extends Model
|
|||||||
return $this->hasManyThrough(Zone::class,Address::class,'system_id','id','id','zone_id');
|
return $this->hasManyThrough(Zone::class,Address::class,'system_id','id','id','zone_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
/* GENERAL METHODS */
|
/* METHODS */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the system's address in the same zone
|
||||||
|
*
|
||||||
|
* @param Address $o
|
||||||
|
* @return Address
|
||||||
|
*/
|
||||||
|
public function match(Address $o): Address
|
||||||
|
{
|
||||||
|
return $this->addresses->where('zone_id',$o->zone_id)->first();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the system name, or role name for the zone
|
* Return the system name, or role name for the zone
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Traits;
|
|
||||||
|
|
||||||
use Illuminate\Support\Arr;
|
|
||||||
|
|
||||||
use App\Models\{Node,Zone};
|
|
||||||
|
|
||||||
trait GetNode
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Get an FTN record
|
|
||||||
* If the record doesnt exist, we'll create it
|
|
||||||
*/
|
|
||||||
protected function get_node(array $address,$create=TRUE)
|
|
||||||
{
|
|
||||||
if (! $z=Arr::get($address,'z'))
|
|
||||||
throw new \Exception('Zone cannot be zero');
|
|
||||||
|
|
||||||
$zo = Zone::firstOrCreate(['id'=>$z]);
|
|
||||||
|
|
||||||
$no = Node::firstOrNew([
|
|
||||||
'zone_id'=>$zo->id,
|
|
||||||
'host_id'=>Arr::get($address,'n'),
|
|
||||||
'node_id'=>Arr::get($address,'f'),
|
|
||||||
'point_id'=>Arr::get($address,'p',0)
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (! $no->exists AND $create)
|
|
||||||
{
|
|
||||||
$no->active = FALSE;
|
|
||||||
$no->system = 'AUTO DISCOVERED';
|
|
||||||
$no->sysop = 'UNKNOWN';
|
|
||||||
$no->location = '';
|
|
||||||
$no->baud = 0;
|
|
||||||
|
|
||||||
$no->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ($no->exists) ? $no : NULL;
|
|
||||||
}
|
|
||||||
}
|
|
7
config/process.php
Normal file
7
config/process.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'robots' => [
|
||||||
|
\App\Classes\FTN\Process\Ping::class,
|
||||||
|
],
|
||||||
|
];
|
208
database/seeders/NodeHierarchy.php
Normal file
208
database/seeders/NodeHierarchy.php
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
use App\Http\Controllers\DomainController;
|
||||||
|
use App\Models\{Address,Domain,System,Zone};
|
||||||
|
|
||||||
|
class NodeHierarchy extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the database seeds.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
DB::table('domains')
|
||||||
|
->insert([
|
||||||
|
'name'=>'Domain A',
|
||||||
|
'active'=>TRUE,
|
||||||
|
'public'=>TRUE,
|
||||||
|
'default'=>FALSE,
|
||||||
|
'created_at'=>Carbon::now(),
|
||||||
|
'updated_at'=>Carbon::now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('domains')
|
||||||
|
->insert([
|
||||||
|
'name'=>'Domain B',
|
||||||
|
'active'=>TRUE,
|
||||||
|
'public'=>TRUE,
|
||||||
|
'default'=>FALSE,
|
||||||
|
'created_at'=>Carbon::now(),
|
||||||
|
'updated_at'=>Carbon::now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach (['Domain A','Domain B'] as $domain) {
|
||||||
|
$domain = Domain::where('name',$domain)->singleOrFail();
|
||||||
|
$this->hierarchy($domain,100);
|
||||||
|
$this->hierarchy($domain,101);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hierarchy(Domain $domain,int $zoneid)
|
||||||
|
{
|
||||||
|
$regions = [1,2];
|
||||||
|
$hosts = [0,1];
|
||||||
|
$hubs = [1000,2000];
|
||||||
|
$nodes = [1,2,3];
|
||||||
|
$hubnodes = [-1,+1];
|
||||||
|
|
||||||
|
$so = $this->system('ZC '.$domain->id);
|
||||||
|
DB::table('zones')
|
||||||
|
->insert([
|
||||||
|
'zone_id'=>$zoneid,
|
||||||
|
'active'=>TRUE,
|
||||||
|
'notes'=>sprintf('Zone: %03d:0/0.0@%s',$zoneid,$domain->name),
|
||||||
|
'domain_id'=>$domain->id,
|
||||||
|
'system_id'=>$so->id,
|
||||||
|
'created_at'=>Carbon::now(),
|
||||||
|
'updated_at'=>Carbon::now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$zo = Zone::where('zone_id',$zoneid)->where('domain_id',$domain->id)->singleOrFail();
|
||||||
|
|
||||||
|
// Nodes
|
||||||
|
foreach ($nodes as $nid) {
|
||||||
|
$so = $this->system(sprintf('Node %03d:%03d/%03d.0@%s (ZC Node)',$zoneid,0,$nid,$domain->name));
|
||||||
|
DB::table('addresses')
|
||||||
|
->insert([
|
||||||
|
'zone_id'=>$zo->id,
|
||||||
|
'active'=>TRUE,
|
||||||
|
'region_id'=>0,
|
||||||
|
'host_id'=>0,
|
||||||
|
'node_id'=>$nid,
|
||||||
|
'point_id'=>0,
|
||||||
|
'system_id'=>$so->id,
|
||||||
|
'created_at'=>Carbon::now(),
|
||||||
|
'updated_at'=>Carbon::now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regions
|
||||||
|
foreach ($regions as $rid) {
|
||||||
|
$so = $this->system(sprintf('Region %03d:%03d/%03d.0@%s',$zoneid,$rid,0,$domain->name));
|
||||||
|
DB::table('addresses')
|
||||||
|
->insert([
|
||||||
|
'zone_id'=>$zo->id,
|
||||||
|
'active'=>TRUE,
|
||||||
|
'region_id'=>$rid,
|
||||||
|
'host_id'=>0,
|
||||||
|
'node_id'=>0,
|
||||||
|
'point_id'=>0,
|
||||||
|
'system_id'=>$so->id,
|
||||||
|
'role'=>DomainController::NODE_RC,
|
||||||
|
'created_at'=>Carbon::now(),
|
||||||
|
'updated_at'=>Carbon::now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Nodes
|
||||||
|
foreach ($nodes as $nid) {
|
||||||
|
$so = $this->system(sprintf('Node %03d:%03d/%03d.0@%s (RC Node)',$zoneid,$rid,$nid,$domain->name));
|
||||||
|
DB::table('addresses')
|
||||||
|
->insert([
|
||||||
|
'zone_id'=>$zo->id,
|
||||||
|
'active'=>TRUE,
|
||||||
|
'region_id'=>$rid,
|
||||||
|
'host_id'=>0,
|
||||||
|
'node_id'=>$nid,
|
||||||
|
'point_id'=>0,
|
||||||
|
'system_id'=>$so->id,
|
||||||
|
'created_at'=>Carbon::now(),
|
||||||
|
'updated_at'=>Carbon::now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hosts
|
||||||
|
foreach ($hosts as $hid) {
|
||||||
|
$hostid = $rid*100+$hid;
|
||||||
|
$so = $this->system(sprintf('Host %03d:%03d/0.0@%s (Region %03d)',$zoneid,$hostid,$domain->name,$rid));
|
||||||
|
DB::table('addresses')
|
||||||
|
->insert([
|
||||||
|
'zone_id'=>$zo->id,
|
||||||
|
'active'=>TRUE,
|
||||||
|
'region_id'=>$rid,
|
||||||
|
'host_id'=>$hostid,
|
||||||
|
'node_id'=>0,
|
||||||
|
'point_id'=>0,
|
||||||
|
'system_id'=>$so->id,
|
||||||
|
'role'=>DomainController::NODE_NC,
|
||||||
|
'created_at'=>Carbon::now(),
|
||||||
|
'updated_at'=>Carbon::now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Nodes
|
||||||
|
foreach ($nodes as $nid) {
|
||||||
|
$so = $this->system(sprintf('Node %03d:%03d/%03d.0@%s (Region %03d) - Host Node',$zoneid,$hostid,$nid,$domain->name,$rid));
|
||||||
|
DB::table('addresses')
|
||||||
|
->insert([
|
||||||
|
'zone_id'=>$zo->id,
|
||||||
|
'active'=>TRUE,
|
||||||
|
'region_id'=>$rid,
|
||||||
|
'host_id'=>$hostid,
|
||||||
|
'node_id'=>$nid,
|
||||||
|
'point_id'=>0,
|
||||||
|
'system_id'=>$so->id,
|
||||||
|
'created_at'=>Carbon::now(),
|
||||||
|
'updated_at'=>Carbon::now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hubs
|
||||||
|
foreach ($hubs as $bid) {
|
||||||
|
$so = $this->system(sprintf('Hub %03d:%03d/%03d.0@%s (Region %03d)',$zoneid,$hostid,$bid,$domain->name,$rid));
|
||||||
|
$hub = new Address;
|
||||||
|
$hub->zone_id = $zo->id;
|
||||||
|
$hub->active = TRUE;
|
||||||
|
$hub->region_id = $rid;
|
||||||
|
$hub->host_id = $hostid;
|
||||||
|
$hub->node_id = $bid;
|
||||||
|
$hub->point_id = 0;
|
||||||
|
$hub->system_id = $so->id;
|
||||||
|
$hub->role = DomainController::NODE_HC;
|
||||||
|
$hub->created_at = Carbon::now();
|
||||||
|
$hub->updated_at = Carbon::now();
|
||||||
|
$hub->save();
|
||||||
|
|
||||||
|
// Nodes
|
||||||
|
foreach ($hubnodes as $nid) {
|
||||||
|
$nodeid = $bid+$nid;
|
||||||
|
$so = $this->system(sprintf('Node %03d:%03d/%03d.0@%s (Region %03d) - Hub Node',$zoneid,$hostid,$nodeid,$domain->name,$rid));
|
||||||
|
DB::table('addresses')
|
||||||
|
->insert([
|
||||||
|
'zone_id'=>$zo->id,
|
||||||
|
'active'=>TRUE,
|
||||||
|
'region_id'=>$rid,
|
||||||
|
'host_id'=>$hostid,
|
||||||
|
'node_id'=>$nodeid,
|
||||||
|
'point_id'=>0,
|
||||||
|
'system_id'=>$so->id,
|
||||||
|
'hub_id'=>$hub->id,
|
||||||
|
'created_at'=>Carbon::now(),
|
||||||
|
'updated_at'=>Carbon::now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function system(string $name): System
|
||||||
|
{
|
||||||
|
$o = new System;
|
||||||
|
$o->name = $name;
|
||||||
|
$o->sysop = 'Mr Sysop of '.$name;
|
||||||
|
$o->location = 'Some place for '.$name;
|
||||||
|
$o->active = TRUE;
|
||||||
|
$o->created_at = Carbon::now();
|
||||||
|
$o->updated_at = Carbon::now();
|
||||||
|
$o->save();
|
||||||
|
|
||||||
|
return $o;
|
||||||
|
}
|
||||||
|
}
|
@ -28,8 +28,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li>Supports BINKP network transfers</li>
|
<li>Supports BINKP network transfers</li>
|
||||||
<li>Supports EMSI network transfers</li>
|
<li>Supports EMSI network transfers</li>
|
||||||
<li>Supports PING responses <sup>To be implemented</sup></li>
|
<li>Supports PING responses</li>
|
||||||
<li>Proxy mode, if you want your BBS to have our main address <sup>To be implemented</sup></li>
|
|
||||||
<li>A consistent reliable echomail/netmail hub for your BBSes.<br>
|
<li>A consistent reliable echomail/netmail hub for your BBSes.<br>
|
||||||
If you have more than 1 BBS, then the Clearing House can receive all your mail from your uplinks and feed them to your BBSes.
|
If you have more than 1 BBS, then the Clearing House can receive all your mail from your uplinks and feed them to your BBSes.
|
||||||
</li>
|
</li>
|
||||||
|
@ -156,9 +156,7 @@
|
|||||||
paging: true,
|
paging: true,
|
||||||
pageLength: 25,
|
pageLength: 25,
|
||||||
searching: true,
|
searching: true,
|
||||||
order: [
|
order: [],
|
||||||
[3,'asc'],
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@append
|
@append
|
94
tests/Feature/RoutingTest.php
Normal file
94
tests/Feature/RoutingTest.php
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
use App\Models\Domain;
|
||||||
|
|
||||||
|
class RoutingTest extends TestCase
|
||||||
|
{
|
||||||
|
private function zone(): Collection
|
||||||
|
{
|
||||||
|
$do = Domain::where('name','Domain A')->singleOrFail();
|
||||||
|
$zo = $do->zones->where('zone_id',100)->pop();
|
||||||
|
return $zo->addresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the ZC address.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function test_zc()
|
||||||
|
{
|
||||||
|
$nodes = $this->zone();
|
||||||
|
$this->assertEquals(51,$nodes->count());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ZCs addresses are not in the address table, so we cannot workout children
|
||||||
|
$zc = $nodes->where('role',DomainController::NODE_ZC);
|
||||||
|
$this->assertEquals(1,$zc->count());
|
||||||
|
$zc = $zc->pop();
|
||||||
|
|
||||||
|
// ZC has 2 Region and 3 nodes as children
|
||||||
|
$this->assertEquals(5,$zc->children());
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the RC address.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function test_rc()
|
||||||
|
{
|
||||||
|
$nodes = $this->zone();
|
||||||
|
$rc = $nodes->where('role','Region');
|
||||||
|
$this->assertEquals(2,$rc->count());
|
||||||
|
|
||||||
|
// First RC
|
||||||
|
$rc = $rc->pop();
|
||||||
|
|
||||||
|
// RC has 3 nodes and 2 hosts as children
|
||||||
|
$this->assertEquals(5,$rc->children->count());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the NC address.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function test_nc()
|
||||||
|
{
|
||||||
|
$nodes = $this->zone();
|
||||||
|
$nc = $nodes->where('role','Host');
|
||||||
|
$this->assertEquals(4,$nc->count());
|
||||||
|
|
||||||
|
// First NC
|
||||||
|
$nc = $nc->pop();
|
||||||
|
|
||||||
|
// NC has 3 nodes and 2 hubs as children
|
||||||
|
$this->assertEquals(5,$nc->children->count());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the HC address.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function test_hc()
|
||||||
|
{
|
||||||
|
$nodes = $this->zone();
|
||||||
|
$hc = $nodes->where('role','Hub');
|
||||||
|
//dd($hc->pluck('ftn'));
|
||||||
|
$this->assertEquals(8,$hc->count());
|
||||||
|
|
||||||
|
// First HC
|
||||||
|
$hc = $hc->pop();
|
||||||
|
|
||||||
|
// HC has 2 children
|
||||||
|
$this->assertEquals(2,$hc->children->count());
|
||||||
|
}
|
||||||
|
}
|
@ -28,15 +28,15 @@ class SiteAdminTest extends TestCase
|
|||||||
$this->get('ftn/zone')
|
$this->get('ftn/zone')
|
||||||
->assertRedirect('login');
|
->assertRedirect('login');
|
||||||
|
|
||||||
$this->get('network/1')
|
$this->get('network/999')
|
||||||
->assertNotFound();
|
->assertNotFound();
|
||||||
|
|
||||||
Domain::factory()->create([
|
Domain::factory()->create([
|
||||||
'id'=>1,
|
'id'=>999,
|
||||||
'name'=>'test',
|
'name'=>'test',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->get('network/1')
|
$this->get('network/999')
|
||||||
->assertOK();
|
->assertOK();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user