Define and detect different packet types (2,2e,2+,2.2)
This commit is contained in:
parent
fa3653a94b
commit
a26f61d75d
@ -59,12 +59,22 @@ abstract class FTN
|
||||
*/
|
||||
protected static function unpackheader(array $pack): string
|
||||
{
|
||||
return join('/',
|
||||
collect($pack)
|
||||
->sortBy(function($k,$v) {return $k[0];})
|
||||
->transform(function($k,$v) {return $k[1].$v;})
|
||||
->values()
|
||||
->toArray()
|
||||
);
|
||||
// Check that our header is correct
|
||||
if (app()->environment('local')) {
|
||||
$c = 0;
|
||||
foreach (collect($pack) as $item) {
|
||||
if ($c !== $item[0])
|
||||
throw new \Exception('Invalid header');
|
||||
|
||||
$c += $item[2];
|
||||
}
|
||||
}
|
||||
|
||||
return collect($pack)
|
||||
->sortBy(function($k,$v) {return $k[0];})
|
||||
->transform(function($k,$v) {return $k[1].$v;})
|
||||
->values()
|
||||
->join('/');
|
||||
;
|
||||
}
|
||||
}
|
@ -71,7 +71,7 @@ class Message extends FTNBase
|
||||
public const FLAG_ECHOMAIL = 1<<16;
|
||||
|
||||
// FTS-0001.016 Message header 32 bytes node, net, flags, cost, date
|
||||
private const HEADER_LEN = 0x20; // Length of message header
|
||||
public const HEADER_LEN = 0x20; // Length of message header
|
||||
private const header = [ // Struct of message header
|
||||
'onode' => [0x00,'v',2], // Originating Node
|
||||
'dnode' => [0x02,'v',2], // Destination Node
|
||||
|
@ -10,7 +10,7 @@ use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
|
||||
use App\Classes\FTN as FTNBase;
|
||||
use App\Models\{Address,Setup,Software,System,Zone};
|
||||
use App\Models\{Address,Software,System,Zone};
|
||||
|
||||
/**
|
||||
* Represents the structure of a packet
|
||||
@ -19,102 +19,152 @@ class Packet extends FTNBase implements \Iterator, \Countable
|
||||
{
|
||||
private const LOGKEY = 'PKT';
|
||||
|
||||
private const HEADER_LEN = 0x3a;
|
||||
private const VERSION_OFFSET = 0x12;
|
||||
private const BLOCKSIZE = 1024;
|
||||
private const PACKED_MSG_HEADER_LEN = 0x22;
|
||||
protected const PACKED_MSG_LEAD = "\02\00";
|
||||
|
||||
// V2 Packet Header (2/2e/2+)
|
||||
private const v2header = [
|
||||
'onode' => [0x00,'v',2], // Originating Node
|
||||
'dnode' => [0x02,'v',2], // Destination Node
|
||||
'y' => [0x04,'v',2], // Year
|
||||
'm' => [0x06,'v',2], // Month
|
||||
'd' => [0x08,'v',2], // Day
|
||||
'H' => [0x0a,'v',2], // Hour
|
||||
'M' => [0x0c,'v',2], // Minute
|
||||
'S' => [0x0e,'v',2], // Second
|
||||
'baud' => [0x10,'v',2], // Baud
|
||||
'pktver' => [0x12,'v',2], // Packet Version
|
||||
'onet' => [0x14,'v',2], // Originating Net (0xffff when origPoint !=0 2+)
|
||||
'dnet' => [0x16,'v',2], // Destination Net
|
||||
'prodcode-lo' => [0x18,'C',1],
|
||||
'prodrev-maj' => [0x19,'C',1], // Product Version Major (serialNum 2)
|
||||
'password' => [0x1a,'Z8',8], // Packet Password
|
||||
'qozone' => [0x22,'v',2],
|
||||
'qdzone' => [0x24,'v',2],
|
||||
'filler' => [0x26,'v',2], // Reserved (auxnet 2+ - contains Orignet if Origin is a point) fsc-0048.001
|
||||
'capvalid' => [0x28,'n',2], // fsc-0039.004 (Not used 2) (copy of 0x2c)
|
||||
'prodcode-hi' => [0x2a,'C',1], // (Not used 2)
|
||||
'prodrev-min' => [0x2b,'C',1], // (Not used 2)
|
||||
'capword' => [0x2c,'v',2], // fsc-0039.001 (Not used 2)
|
||||
'ozone' => [0x2e,'v',2], // Originating Zone (Not used 2)
|
||||
'dzone' => [0x30,'v',2], // Destination Zone (Not used 2)
|
||||
'opoint' => [0x32,'v',2], // Originating 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
|
||||
private const PACKET_TYPES = [
|
||||
FTNBase\Packet\FSC45::class,
|
||||
FTNBase\Packet\FSC48::class,
|
||||
FTNBase\Packet\FSC39::class,
|
||||
FTNBase\Packet\FTS1::class,
|
||||
];
|
||||
|
||||
private array $header; // Packet Header
|
||||
protected array $header; // Packet Header
|
||||
protected ?string $name; // Packet name
|
||||
|
||||
public File $file; // Packet filename
|
||||
public Collection $messages; // Messages in the Packet
|
||||
public Collection $errors; // Messages that fail validation
|
||||
private string $name; // Packet name
|
||||
public bool $use_cache = FALSE; // Use a cache for messages.
|
||||
private int $index; // Our array index
|
||||
protected int $index; // Our array index
|
||||
|
||||
/**
|
||||
* Number of messages in this packet
|
||||
* @param string|null $header
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return $this->messages->count();
|
||||
}
|
||||
|
||||
public function current(): Message
|
||||
{
|
||||
return $this->use_cache ? unserialize(Cache::pull($this->key())) : $this->messages->get($this->index);
|
||||
}
|
||||
|
||||
public function key(): mixed
|
||||
{
|
||||
return $this->use_cache ? $this->messages->get($this->index) : $this->index;
|
||||
}
|
||||
|
||||
public function next(): void
|
||||
{
|
||||
$this->index++;
|
||||
}
|
||||
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->index = 0;
|
||||
}
|
||||
|
||||
public function valid(): bool
|
||||
{
|
||||
return (! is_null($this->key())) && ($this->use_cache ? Cache::has($this->key()) : $this->messages->has($this->key()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Address|NULL $oo Origin Address
|
||||
* @param Address|NULL $o Destination Address
|
||||
*/
|
||||
public function __construct(Address $oo=NULL,Address $o=NULL)
|
||||
public function __construct(string $header=NULL)
|
||||
{
|
||||
$this->messages = collect();
|
||||
$this->errors = collect();
|
||||
$this->domain = NULL;
|
||||
$this->name = NULL;
|
||||
|
||||
// If we are creating an outbound packet, we need to set our header
|
||||
if ($oo && $o) {
|
||||
$this->name = sprintf('%08x',timew());
|
||||
Log::debug(sprintf('%s:Creating packet [%s]',self::LOGKEY,$this->name));
|
||||
$this->newHeader($oo,$o);
|
||||
if ($header)
|
||||
$this->header = unpack(self::unpackheader(static::HEADER),$header);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
switch ($key) {
|
||||
// From Addresses
|
||||
case 'fz': return Arr::get($this->header,'ozone');
|
||||
case 'fn': return Arr::get($this->header,'onet');
|
||||
case 'ff': return Arr::get($this->header,'onode');
|
||||
case 'fp': return Arr::get($this->header,'opoint');
|
||||
case 'fd': return rtrim(Arr::get($this->header,'odomain',"\x00"));
|
||||
|
||||
// To Addresses
|
||||
case 'tz': return Arr::get($this->header,'dzone');
|
||||
case 'tn': return Arr::get($this->header,'dnet');
|
||||
case 'tf': return Arr::get($this->header,'dnode');
|
||||
case 'tp': return Arr::get($this->header,'dpoint');
|
||||
case 'td': return rtrim(Arr::get($this->header,'ddomain',"\x00"));
|
||||
|
||||
case 'date':
|
||||
return Carbon::create(
|
||||
Arr::get($this->header,'y'),
|
||||
Arr::get($this->header,'m')+1,
|
||||
Arr::get($this->header,'d'),
|
||||
Arr::get($this->header,'H'),
|
||||
Arr::get($this->header,'M'),
|
||||
Arr::get($this->header,'S')
|
||||
);
|
||||
|
||||
case 'password':
|
||||
return rtrim(Arr::get($this->header,$key),"\x00");
|
||||
|
||||
case 'fftn':
|
||||
case 'fftn_o':
|
||||
case 'tftn':
|
||||
case 'tftn_o':
|
||||
return parent::__get($key);
|
||||
|
||||
case 'software':
|
||||
$code = Arr::get($this->header,'prodcode-hi')<<8|Arr::get($this->header,'prodcode-lo');
|
||||
Software::unguard();
|
||||
$o = Software::singleOrNew(['code'=>$code,'type'=>Software::SOFTWARE_TOSSER]);
|
||||
Software::reguard();
|
||||
|
||||
return $o;
|
||||
|
||||
case 'software_ver':
|
||||
return sprintf('%d.%d',Arr::get($this->header,'prodrev-maj'),Arr::get($this->header,'prodrev-min'));
|
||||
|
||||
case 'capability':
|
||||
// This needs to be defined in child classes, since not all children have it
|
||||
return NULL;
|
||||
|
||||
// Packet Type
|
||||
case 'type':
|
||||
return static::TYPE;
|
||||
|
||||
// Packet name:
|
||||
case 'name':
|
||||
return $this->{$key} ?: sprintf('%08x',timew());
|
||||
|
||||
default:
|
||||
throw new \Exception('Unknown key: '.$key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the packet
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
$return = $this->header();
|
||||
|
||||
foreach ($this->messages as $o) {
|
||||
if ($o->packed)
|
||||
$return .= self::PACKED_MSG_LEAD.$o;
|
||||
}
|
||||
|
||||
$return .= "\00\00";
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/* STATIC */
|
||||
|
||||
/**
|
||||
* Site of the packet header
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function header_len(): int
|
||||
{
|
||||
return collect(static::HEADER)->sum(function($item) { return Arr::get($item,2); });
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is intended to be implemented in child classes to test if the packet
|
||||
* is defined by the child object
|
||||
*
|
||||
* @see self::PACKET_TYPES
|
||||
* @param string $header
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_type(string $header): bool
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a packet file
|
||||
*
|
||||
@ -126,40 +176,48 @@ class Packet extends FTNBase implements \Iterator, \Countable
|
||||
* @return Packet
|
||||
* @throws InvalidPacketException
|
||||
*/
|
||||
|
||||
public static function process(mixed $f,string $name,int $size,System $system=NULL,bool $use_cache=FALSE): self
|
||||
{
|
||||
Log::debug(sprintf('%s:+ Opening Packet [%s] with size [%d]',self::LOGKEY,$name,$size));
|
||||
|
||||
$o = FALSE;
|
||||
$header = '';
|
||||
$read_ptr = 0;
|
||||
|
||||
// PKT Header
|
||||
$header = fread($f,self::HEADER_LEN);
|
||||
$read_ptr += strlen($header);
|
||||
// Determine the type of packet
|
||||
foreach (self::PACKET_TYPES as $type) {
|
||||
$header_len = $type::header_len();
|
||||
|
||||
// Could not read header
|
||||
if (strlen($header) != self::HEADER_LEN)
|
||||
throw new InvalidPacketException(sprintf('Length of header [%d] too short'.strlen($header)));
|
||||
// PKT Header
|
||||
if ($read_ptr < $header_len) {
|
||||
$header .= fread($f,$header_len-$read_ptr);
|
||||
$read_ptr = ftell($f);
|
||||
}
|
||||
|
||||
// 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);
|
||||
// Could not read header
|
||||
if (strlen($header) !== $header_len)
|
||||
throw new InvalidPacketException(sprintf('Length of header [%d] too short',strlen($header)));
|
||||
|
||||
if ($type::is_type($header)) {
|
||||
$o = new $type($header);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $o)
|
||||
throw new InvalidPacketException('Cannot determine type of packet.');
|
||||
|
||||
$o = new self;
|
||||
$o->use_cache = $use_cache;
|
||||
$o->name = $name;
|
||||
$o->header = unpack(self::unpackheader(self::v2header),$header);
|
||||
|
||||
$x = fread($f,2);
|
||||
$read_ptr += strlen($x);
|
||||
|
||||
// End of Packet?
|
||||
if (strlen($x) == 2 and $x == "\00\00")
|
||||
return new self;
|
||||
if ((strlen($x) === 2) && ($x === "\00\00"))
|
||||
return $o;
|
||||
|
||||
// Messages start with 02H 00H
|
||||
if (strlen($x) == 2 AND $x != "\02\00")
|
||||
// Messages start with self::PACKED_MSG_LEAD
|
||||
if ((strlen($x) === 2) && ($x !== self::PACKED_MSG_LEAD))
|
||||
throw new InvalidPacketException('Not a valid packet: '.bin2hex($x));
|
||||
|
||||
// No message attached
|
||||
@ -179,10 +237,11 @@ class Packet extends FTNBase implements \Iterator, \Countable
|
||||
|
||||
while ($buf_ptr || (! feof($f) && ($readbuf=fread($f,self::BLOCKSIZE)))) {
|
||||
if (! $buf_ptr)
|
||||
$read_ptr += strlen($readbuf); // Could use ftell()
|
||||
$read_ptr = ftell($f);
|
||||
|
||||
if (strlen($message) < self::PACKED_MSG_HEADER_LEN) {
|
||||
$addchars = self::PACKED_MSG_HEADER_LEN-strlen($message);
|
||||
// Packed messages are Message::HEADER_LEN, prefixed with self::PACKED_MSG_LEAD
|
||||
if (strlen($message) < (Message::HEADER_LEN+strlen(self::PACKED_MSG_LEAD))) {
|
||||
$addchars = (Message::HEADER_LEN+strlen(self::PACKED_MSG_LEAD))-strlen($message);
|
||||
$message .= substr($readbuf,$buf_ptr,$addchars);
|
||||
$buf_ptr += $addchars;
|
||||
|
||||
@ -197,7 +256,7 @@ class Packet extends FTNBase implements \Iterator, \Countable
|
||||
if ($last && ($buf_ptr == 0)) {
|
||||
$last .= substr($readbuf,0,2);
|
||||
|
||||
if (($end=strpos($last,"\x00\x02\x00",$buf_ptr)) !== FALSE) {
|
||||
if (($end=strpos($last,"\x00".self::PACKED_MSG_LEAD,$buf_ptr)) !== FALSE) {
|
||||
$o->parseMessage(substr($message,0,$end-2));
|
||||
$last = '';
|
||||
$message = '';
|
||||
@ -210,7 +269,7 @@ class Packet extends FTNBase implements \Iterator, \Countable
|
||||
$last = '';
|
||||
}
|
||||
|
||||
if (($end=strpos($readbuf,"\x00\x02\x00",$buf_ptr)) === FALSE) {
|
||||
if (($end=strpos($readbuf,"\x00".self::PACKED_MSG_LEAD,$buf_ptr)) === FALSE) {
|
||||
// Just in case our packet break is at the end of the buffer
|
||||
$last = substr($readbuf,-2);
|
||||
|
||||
@ -253,137 +312,89 @@ class Packet extends FTNBase implements \Iterator, \Countable
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
switch ($key) {
|
||||
// From Addresses
|
||||
case 'fz': return Arr::get($this->header,'ozone');
|
||||
case 'fn': return Arr::get($this->header,'onet');
|
||||
case 'ff': return Arr::get($this->header,'onode');
|
||||
case 'fp': return Arr::get($this->header,'opoint');
|
||||
case 'fd': return Arr::get($this->header,'odomain');
|
||||
|
||||
// To Addresses
|
||||
case 'tz': return Arr::get($this->header,'dzone');
|
||||
case 'tn': return Arr::get($this->header,'dnet');
|
||||
case 'tf': return Arr::get($this->header,'dnode');
|
||||
case 'tp': return Arr::get($this->header,'dpoint');
|
||||
case 'td': return Arr::get($this->header,'ddomain');
|
||||
|
||||
case 'date':
|
||||
return Carbon::create(
|
||||
Arr::get($this->header,'y'),
|
||||
Arr::get($this->header,'m')+1,
|
||||
Arr::get($this->header,'d'),
|
||||
Arr::get($this->header,'H'),
|
||||
Arr::get($this->header,'M'),
|
||||
Arr::get($this->header,'S')
|
||||
);
|
||||
|
||||
case 'capability':
|
||||
return Arr::get($this->header,'capword') == Arr::get($this->header,'capvalid') ? sprintf('%016b',Arr::get($this->header,'capword')) : 'FTS-1';
|
||||
|
||||
case 'password':
|
||||
return Arr::get($this->header,$key);
|
||||
|
||||
case 'fftn':
|
||||
case 'fftn_o':
|
||||
case 'tftn':
|
||||
case 'tftn_o':
|
||||
return parent::__get($key);
|
||||
|
||||
case 'software':
|
||||
$code = Arr::get($this->header,'prodcode-hi')<<8|Arr::get($this->header,'prodcode-lo');
|
||||
Software::unguard();
|
||||
$o = Software::singleOrNew(['code'=>$code,'type'=>Software::SOFTWARE_TOSSER]);
|
||||
Software::reguard();
|
||||
|
||||
return $o;
|
||||
|
||||
case 'software_ver':
|
||||
return sprintf('%d.%d',Arr::get($this->header,'prodrev-maj'),Arr::get($this->header,'prodrev-min'));
|
||||
|
||||
// Packet Type
|
||||
case 'type':
|
||||
if ((Arr::get($this->header,'onet') == 0xffff) && (Arr::get($this->header,'opoint') != 0) && Arr::get($this->header,'filler'))
|
||||
return '2+';
|
||||
elseif (Arr::get($this->header,'prodrev-maj') && ! Arr::get($this->header,'capword'))
|
||||
return '2';
|
||||
else
|
||||
return '2e';
|
||||
|
||||
// Packet name:
|
||||
case 'name':
|
||||
return $this->{$key};
|
||||
|
||||
default:
|
||||
throw new \Exception('Unknown key: '.$key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the packet
|
||||
* Location of the version
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
* @return int
|
||||
*/
|
||||
public function __toString(): string
|
||||
public static function version_offset(): int
|
||||
{
|
||||
$return = $this->createHeader();
|
||||
|
||||
foreach ($this->messages as $o) {
|
||||
if ($o->packed)
|
||||
$return .= "\02\00".(string)$o;
|
||||
}
|
||||
|
||||
$return .= "\00\00";
|
||||
|
||||
return $return;
|
||||
return Arr::get(collect(static::HEADER)->get('type'),0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create our message packet header
|
||||
*/
|
||||
private function createHeader(): string
|
||||
public static function version_offset_len(): int
|
||||
{
|
||||
try {
|
||||
$a = pack(collect(self::v2header)->merge(['password' => [0x1a,'a8',8]])->pluck(1)->join(''),
|
||||
$this->ff,
|
||||
$this->tf,
|
||||
Arr::get($this->header,'y'),
|
||||
Arr::get($this->header,'m'),
|
||||
Arr::get($this->header,'d'),
|
||||
Arr::get($this->header,'H'),
|
||||
Arr::get($this->header,'M'),
|
||||
Arr::get($this->header,'S'),
|
||||
Arr::get($this->header,'baud',0),
|
||||
Arr::get($this->header,'pktver',2),
|
||||
$this->fn, // @todo if point, this needs to be 0xff
|
||||
$this->tn,
|
||||
Arr::get($this->header,'prodcode-lo',(Setup::PRODUCT_ID & 0xff)),
|
||||
Arr::get($this->header,'prodrev-maj',Setup::PRODUCT_VERSION_MAJ),
|
||||
$this->password,
|
||||
$this->fz,
|
||||
$this->tz,
|
||||
Arr::get($this->header,'filler',''),
|
||||
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 Arr::get(collect(static::HEADER)->get('type'),2);
|
||||
}
|
||||
|
||||
return $a;
|
||||
/* INTERFACE */
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
/**
|
||||
* Number of messages in this packet
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return $this->messages->count();
|
||||
}
|
||||
|
||||
public function current(): Message
|
||||
{
|
||||
return $this->use_cache ? unserialize(Cache::pull($this->key())) : $this->messages->get($this->index);
|
||||
}
|
||||
|
||||
public function key(): mixed
|
||||
{
|
||||
return $this->use_cache ? $this->messages->get($this->index) : $this->index;
|
||||
}
|
||||
|
||||
public function next(): void
|
||||
{
|
||||
$this->index++;
|
||||
}
|
||||
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->index = 0;
|
||||
}
|
||||
|
||||
public function valid(): bool
|
||||
{
|
||||
return (! is_null($this->key())) && ($this->use_cache ? Cache::has($this->key()) : $this->messages->has($this->key()));
|
||||
}
|
||||
|
||||
/* METHODS */
|
||||
|
||||
/**
|
||||
* When creating a new packet, set the header.
|
||||
*
|
||||
* @param Address $oo
|
||||
* @param Address $o
|
||||
*/
|
||||
public function addressHeader(Address $oo,Address $o): void
|
||||
{
|
||||
Log::debug(sprintf('%s:Creating packet [%s]',self::LOGKEY,$this->name));
|
||||
|
||||
$date = Carbon::now();
|
||||
|
||||
// Create Header
|
||||
$this->header = [
|
||||
'ozone' => $oo->zone->zone_id, // Orig Zone
|
||||
'dzone' => $o->zone->zone_id, // Dest Zone
|
||||
'onet' => $oo->host_id ?: $oo->region_id, // Orig Net
|
||||
'dnet' => $o->host_id ?: $o->region_id, // Dest Net
|
||||
'onode' => $oo->node_id, // Orig Node
|
||||
'dnode' => $o->node_id, // Dest Node
|
||||
'opoint' => $oo->point_id, // Orig Point
|
||||
'dpoint' => $o->point_id, // Dest Point
|
||||
'odomain' => $oo->zone->domain->name, // Orig Domain
|
||||
'ddomain' => $o->zone->domain->name, // Dest Domain
|
||||
'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
|
||||
'password' => $o->session('pktpass'), // Packet Password
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -396,38 +407,6 @@ class Packet extends FTNBase implements \Iterator, \Countable
|
||||
$this->messages->push($o);
|
||||
}
|
||||
|
||||
/**
|
||||
* When creating a new packet, set the header.
|
||||
*
|
||||
* @param Address $oo
|
||||
* @param Address $o
|
||||
*/
|
||||
private function newHeader(Address $oo,Address $o): void
|
||||
{
|
||||
$date = Carbon::now();
|
||||
|
||||
// Create Header
|
||||
$this->header = [
|
||||
'onode' => $oo->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' => $oo->host_id ?: $oo->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' => $oo->zone->zone_id,
|
||||
'qdzone' => $o->zone->zone_id,
|
||||
'ozone' => $oo->zone->zone_id, // Originating Zone (Not used 2)
|
||||
'dzone' => $o->zone->zone_id, // Destination Zone (Not used 2)
|
||||
'opoint' => $oo->point_id, // Originating Point (Not used 2)
|
||||
'dpoint' => $o->point_id, // Destination Point (Not used 2)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a message in a mail packet
|
||||
*
|
||||
|
121
app/Classes/FTN/Packet/FSC39.php
Normal file
121
app/Classes/FTN/Packet/FSC39.php
Normal file
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\FTN\Packet;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
use App\Classes\FTN\Packet;
|
||||
use App\Models\Setup;
|
||||
|
||||
/**
|
||||
* FSC-0039 http://ftsc.org/docs/fsc-0039.004
|
||||
*
|
||||
* Commonly known as Type 2e packets, or extended type 2 packets
|
||||
* Supports 4D addressing
|
||||
*/
|
||||
final class FSC39 extends Packet
|
||||
{
|
||||
protected const HEADER = [
|
||||
'onode' => [0x00,'v',2], // Orig Node
|
||||
'dnode' => [0x02,'v',2], // Dest Node
|
||||
'y' => [0x04,'v',2], // Year
|
||||
'm' => [0x06,'v',2], // Month
|
||||
'd' => [0x08,'v',2], // Day
|
||||
'H' => [0x0a,'v',2], // Hour
|
||||
'M' => [0x0c,'v',2], // Minute
|
||||
'S' => [0x0e,'v',2], // Second
|
||||
'baud' => [0x10,'v',2], // Baud
|
||||
'type' => [0x12,'v',2], // Packet Version (should be 2)
|
||||
'onet' => [0x14,'v',2], // Orig Net
|
||||
'dnet' => [0x16,'v',2], // Dest Net
|
||||
'prodcode-lo' => [0x18,'C',1], // Product Code
|
||||
'prodrev-maj' => [0x19,'C',1], // Product Version Major
|
||||
'password' => [0x1a,'a8',8], // Packet Password
|
||||
'ozone' => [0x22,'v',2], // Orig Zone
|
||||
'dzone' => [0x24,'v',2], // Dest Zone
|
||||
'reserved' => [0x26,'a2',2], // Reserved
|
||||
'capvalid' => [0x28,'n',2], // fsc-0039.004 (copy of 0x2c)
|
||||
'prodcode-hi' => [0x2a,'C',1], // Product Code Hi
|
||||
'prodrev-min' => [0x2b,'C',1], // Product Version Minor
|
||||
'capword' => [0x2c,'v',2], // Capability Word
|
||||
'dozone' => [0x2e,'v',2], // Orig Zone
|
||||
'ddzone' => [0x30,'v',2], // Dest Zone
|
||||
'opoint' => [0x32,'v',2], // Orig Point
|
||||
'dpoint' => [0x34,'v',2], // Dest Point
|
||||
'proddata' => [0x36,'a4',4], // ProdData
|
||||
];
|
||||
|
||||
public const TYPE = '2e';
|
||||
|
||||
public function __get($key)
|
||||
{
|
||||
switch ($key) {
|
||||
case 'capability':
|
||||
return sprintf('%016b',Arr::get($this->header,'capword'));
|
||||
|
||||
default:
|
||||
return parent::__get($key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create our message packet header
|
||||
*/
|
||||
protected function header(): string
|
||||
{
|
||||
try {
|
||||
return pack(collect(self::HEADER)->pluck(1)->join(''),
|
||||
$this->ff, // Orig Node
|
||||
$this->tf, // Dest Node
|
||||
Arr::get($this->header,'y'), // Year
|
||||
Arr::get($this->header,'m'), // Month
|
||||
Arr::get($this->header,'d'), // Day
|
||||
Arr::get($this->header,'H'), // Hour
|
||||
Arr::get($this->header,'M'), // Minute
|
||||
Arr::get($this->header,'S'), // Second
|
||||
0, // Baud
|
||||
2, // Packet Version (should be 2)
|
||||
$this->fn, // Orig Net
|
||||
$this->tn, // Dest Net
|
||||
(Setup::PRODUCT_ID & 0xff), // Product Code Lo
|
||||
Setup::PRODUCT_VERSION_MAJ, // Product Version Major
|
||||
$this->password, // Packet Password
|
||||
$this->fz, // Orig Zone
|
||||
$this->tz, // Dest Zone
|
||||
'', // Reserved
|
||||
Arr::get($this->header,'capvalid',1<<0), // fsc-0039.004 (copy of 0x2c)
|
||||
((Setup::PRODUCT_ID >> 8) & 0xff), // Product Code Hi
|
||||
Setup::PRODUCT_VERSION_MIN, // Product Version Minor
|
||||
Arr::get($this->header,'capword',1<<0), // Capability Word
|
||||
$this->fz, // Orig Zone
|
||||
$this->tz, // Dest Zone
|
||||
$this->fp, // Orig Point
|
||||
$this->tp, // Dest Point
|
||||
strtoupper(hexstr(Setup::PRODUCT_ID)), // ProdData
|
||||
);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this is a fsc-0045 packet
|
||||
*
|
||||
* @param string $header
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function is_type(string $header): bool
|
||||
{
|
||||
$head = unpack(self::unpackheader(self::HEADER),$header);
|
||||
|
||||
return (
|
||||
(Arr::get($head,'type') === 2)
|
||||
&& (strlen(rtrim(Arr::get($head,'reserved'),"\x00")) === 0)
|
||||
&& (Arr::get($head,'capword') === Arr::get($head,'capvalid'))
|
||||
&& (Arr::get($head,'ozone') === Arr::get($head,'dozone'))
|
||||
&& (Arr::get($head,'dzone') === Arr::get($head,'ddzone'))
|
||||
);
|
||||
}
|
||||
}
|
91
app/Classes/FTN/Packet/FSC45.php
Normal file
91
app/Classes/FTN/Packet/FSC45.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\FTN\Packet;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
use App\Classes\FTN\Packet;
|
||||
use App\Models\Setup;
|
||||
|
||||
/**
|
||||
* FSC-0045 http://ftsc.org/docs/fsc-0045.001
|
||||
*
|
||||
* Commonly known as Type 2.2 packets
|
||||
* Supports 5D addressing
|
||||
*/
|
||||
final class FSC45 extends Packet
|
||||
{
|
||||
protected const HEADER = [
|
||||
'onode' => [0x00,'v',2], // Orig Node
|
||||
'dnode' => [0x02,'v',2], // Dest Node
|
||||
'opoint' => [0x04,'v',2], // Orig Point
|
||||
'dpoint' => [0x06,'v',2], // Dest Point
|
||||
'reserved' => [0x08,'a8',8], // Reserved
|
||||
'subver' => [0x10,'v',2], // Sub Version (should be 2)
|
||||
'type' => [0x12,'v',2], // Packet Version (should be 2)
|
||||
'onet' => [0x14,'v',2], // Orig Net
|
||||
'dnet' => [0x16,'v',2], // Dest Net
|
||||
'prodcode' => [0x18,'C',1], // Product Code
|
||||
'prodrev-maj' => [0x19,'C',1], // Product Version
|
||||
'password' => [0x1a,'a8',8], // Packet Password
|
||||
'ozone' => [0x22,'v',2], // Orig Zone
|
||||
'dzone' => [0x24,'v',2], // Dest Zone
|
||||
'odomain' => [0x26,'a8',8], // Orig Domain
|
||||
'ddomain' => [0x2e,'a8',8], // Dest Domain
|
||||
'proddata' => [0x36,'a4',4], // ProdData
|
||||
];
|
||||
|
||||
public const TYPE = '2.2';
|
||||
|
||||
/**
|
||||
* Create our message packet header
|
||||
*/
|
||||
protected function header(): string
|
||||
{
|
||||
try {
|
||||
return pack(collect(self::HEADER)->pluck(1)->join(''),
|
||||
$this->ff, // Orig Node
|
||||
$this->tf, // Dest Node
|
||||
$this->fp, // Orig Point
|
||||
$this->tp, // Dest Point
|
||||
'', // Reserved
|
||||
2, // Sub Version (should be 2)
|
||||
2, // Packet Version (should be 2)
|
||||
$this->fn, // Orig Net
|
||||
$this->tn, // Dest Net
|
||||
(Setup::PRODUCT_ID & 0xff), // Product Code
|
||||
Setup::PRODUCT_VERSION_MAJ, // Product Version
|
||||
$this->password, // Packet Password
|
||||
$this->fz, // Orig Zone
|
||||
$this->tz, // Dest Zone
|
||||
$this->fd, // Orig Domain
|
||||
$this->td, // Dest Domain
|
||||
strtoupper(hexstr(Setup::PRODUCT_ID)), // ProdData
|
||||
);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this is a fsc-0045 packet
|
||||
*
|
||||
* @param string $header
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function is_type(string $header): bool
|
||||
{
|
||||
$head = unpack(self::unpackheader(self::HEADER),$header);
|
||||
|
||||
return (
|
||||
(Arr::get($head,'type') === 2)
|
||||
&& ((Arr::get($head,'opoint') === 0) || (Arr::get($head,'onet') !== 0xffff))
|
||||
&& (strlen(rtrim(Arr::get($head,'reserved'),"\x00")) === 0)
|
||||
&& (Arr::get($head,'subver') === 2)
|
||||
&& (Arr::get($head,'odomain'))
|
||||
&& (Arr::get($head,'ddomain'))
|
||||
);
|
||||
}
|
||||
}
|
119
app/Classes/FTN/Packet/FSC48.php
Normal file
119
app/Classes/FTN/Packet/FSC48.php
Normal file
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\FTN\Packet;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
use App\Classes\FTN\Packet;
|
||||
use App\Models\Setup;
|
||||
|
||||
/**
|
||||
* FSC-0048 http://ftsc.org/docs/fsc-0048.002
|
||||
*
|
||||
* Commonly known as Type 2+ packets, based on FSC-0039 with improved support for FTS-0001
|
||||
*/
|
||||
final class FSC48 extends Packet
|
||||
{
|
||||
protected const HEADER = [
|
||||
'onode' => [0x00,'v',2], // Orig Node
|
||||
'dnode' => [0x02,'v',2], // Dest Node
|
||||
'y' => [0x04,'v',2], // Year
|
||||
'm' => [0x06,'v',2], // Month
|
||||
'd' => [0x08,'v',2], // Day
|
||||
'H' => [0x0a,'v',2], // Hour
|
||||
'M' => [0x0c,'v',2], // Minute
|
||||
'S' => [0x0e,'v',2], // Second
|
||||
'baud' => [0x10,'v',2], // Baud
|
||||
'type' => [0x12,'v',2], // Packet Version (should be 2)
|
||||
'onet' => [0x14,'v',2], // Orig Net (0xFFFF when OrigPoint != 0)
|
||||
'dnet' => [0x16,'v',2], // Dest Net
|
||||
'prodcode-lo' => [0x18,'C',1], // Product Code Lo
|
||||
'prodrev-maj' => [0x19,'C',1], // Product Version Major
|
||||
'password' => [0x1a,'a8',8], // Packet Password
|
||||
'ozone' => [0x22,'v',2], // Orig Zone
|
||||
'dzone' => [0x24,'v',2], // Dest Zone
|
||||
'auxnet' => [0x26,'v',2], // Aux Net
|
||||
'capvalid' => [0x28,'n',2], // fsc-0039.004 (copy of 0x2c)
|
||||
'prodcode-hi' => [0x2a,'C',1], // Product Code Hi
|
||||
'prodrev-min' => [0x2b,'C',1], // Product Version Minor
|
||||
'capword' => [0x2c,'v',2], // Capability Word
|
||||
'dozone' => [0x2e,'v',2], // Orig Zone
|
||||
'ddzone' => [0x30,'v',2], // Dest Zone
|
||||
'opoint' => [0x32,'v',2], // Orig Point
|
||||
'dpoint' => [0x34,'v',2], // Dest Point
|
||||
'proddata' => [0x36,'a4',4], // ProdData
|
||||
];
|
||||
|
||||
public const TYPE = '2+';
|
||||
|
||||
public function __get($key)
|
||||
{
|
||||
switch ($key) {
|
||||
case 'capability':
|
||||
return sprintf('%016b',Arr::get($this->header,'capword'));
|
||||
|
||||
default:
|
||||
return parent::__get($key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create our message packet header
|
||||
*/
|
||||
protected function header(): string
|
||||
{
|
||||
try {
|
||||
return pack(collect(self::HEADER)->pluck(1)->join(''),
|
||||
$this->ff, // Orig Node
|
||||
$this->tf, // Dest Node
|
||||
Arr::get($this->header,'y'), // Year
|
||||
Arr::get($this->header,'m'), // Month
|
||||
Arr::get($this->header,'d'), // Day
|
||||
Arr::get($this->header,'H'), // Hour
|
||||
Arr::get($this->header,'M'), // Minute
|
||||
Arr::get($this->header,'S'), // Second
|
||||
0, // Baud
|
||||
2, // Packet Version (should be 2)
|
||||
$this->fp ? 0xffff : $this->fn, // Orig Net (0xFFFF when OrigPoint != 0)
|
||||
$this->tn, // Dest Net
|
||||
(Setup::PRODUCT_ID & 0xff), // Product Code Lo
|
||||
Setup::PRODUCT_VERSION_MAJ, // Product Version Major
|
||||
$this->password, // Packet Password
|
||||
$this->fz, // Orig Zone
|
||||
$this->tz, // Dest Zone
|
||||
$this->fp ? $this->fn : 0x00, // Aux Net
|
||||
Arr::get($this->header,'capvalid',1<<0), // fsc-0039.004 (copy of 0x2c)
|
||||
((Setup::PRODUCT_ID >> 8) & 0xff), // Product Code Hi
|
||||
Setup::PRODUCT_VERSION_MIN, // Product Version Minor
|
||||
Arr::get($this->header,'capword',1<<0), // Capability Word
|
||||
$this->fz, // Orig Zone
|
||||
$this->tz, // Dest Zone
|
||||
$this->fp, // Orig Point
|
||||
$this->tp, // Dest Point
|
||||
strtoupper(hexstr(Setup::PRODUCT_ID)), // ProdData
|
||||
);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this is a fsc-0045 packet
|
||||
*
|
||||
* @param string $header
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function is_type(string $header): bool
|
||||
{
|
||||
$head = unpack(self::unpackheader(self::HEADER),$header);
|
||||
|
||||
return (
|
||||
(Arr::get($head,'type') === 2)
|
||||
&& (Arr::get($head,'capword') === Arr::get($head,'capvalid'))
|
||||
&& ((Arr::get($head,'opoint') === 0) || (Arr::get($head,'onet') === 0xffff))
|
||||
&& Arr::get($head,'auxnet')
|
||||
);
|
||||
}
|
||||
}
|
89
app/Classes/FTN/Packet/FTS1.php
Normal file
89
app/Classes/FTN/Packet/FTS1.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\FTN\Packet;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
use App\Classes\FTN\Packet;
|
||||
use App\Models\Setup;
|
||||
|
||||
/**
|
||||
* FTS-0001 http://ftsc.org/docs/fts-0001.016
|
||||
*
|
||||
* Commonly known as Type 2 packets
|
||||
* Supports 3D addresses
|
||||
*/
|
||||
final class FTS1 extends Packet
|
||||
{
|
||||
protected const HEADER = [
|
||||
'onode' => [0x00,'v',2], // Orig Node
|
||||
'dnode' => [0x02,'v',2], // Dest Node
|
||||
'y' => [0x04,'v',2], // Year
|
||||
'm' => [0x06,'v',2], // Month
|
||||
'd' => [0x08,'v',2], // Day
|
||||
'H' => [0x0a,'v',2], // Hour
|
||||
'M' => [0x0c,'v',2], // Minute
|
||||
'S' => [0x0e,'v',2], // Second
|
||||
'baud' => [0x10,'v',2], // Baud
|
||||
'type' => [0x12,'v',2], // Packet Version (should be 2)
|
||||
'onet' => [0x14,'v',2], // Orig Net
|
||||
'dnet' => [0x16,'v',2], // Dest Net
|
||||
'prodcode' => [0x18,'C',1], // Product Code
|
||||
'serial' => [0x19,'C',1], // Serial Number
|
||||
'password' => [0x1a,'a8',8], // Packet Password
|
||||
'ozone' => [0x22,'v',2], // Orig Zone
|
||||
'dzone' => [0x24,'v',2], // Dest Zone
|
||||
'reserved' => [0x26,'a20',20], // Reserved
|
||||
];
|
||||
|
||||
public const TYPE = '2';
|
||||
|
||||
/**
|
||||
* Create our message packet header
|
||||
*/
|
||||
protected function header(): string
|
||||
{
|
||||
try {
|
||||
return pack(collect(self::HEADER)->pluck(1)->join(''),
|
||||
$this->ff, // Orig Node
|
||||
$this->tf, // Dest Node
|
||||
Arr::get($this->header,'y'), // Year
|
||||
Arr::get($this->header,'m'), // Month
|
||||
Arr::get($this->header,'d'), // Day
|
||||
Arr::get($this->header,'H'), // Hour
|
||||
Arr::get($this->header,'M'), // Minute
|
||||
Arr::get($this->header,'S'), // Second
|
||||
0, // Baud
|
||||
2, // Packet Version (should be 2)
|
||||
$this->fn, // Orig Net
|
||||
$this->tn, // Dest Net
|
||||
(Setup::PRODUCT_ID & 0xff), // Product Code Lo
|
||||
Setup::PRODUCT_VERSION_MAJ, // Product Version Major
|
||||
$this->password, // Packet Password
|
||||
$this->fz, // Orig Zone
|
||||
$this->tz, // Dest Zone
|
||||
'', // Reserved
|
||||
);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this is a fsc-0045 packet
|
||||
*
|
||||
* @param string $header
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function is_type(string $header): bool
|
||||
{
|
||||
$head = unpack(self::unpackheader(self::HEADER),$header);
|
||||
|
||||
return (
|
||||
(Arr::get($head,'type') === 2)
|
||||
&& (strlen(rtrim(Arr::get($head,'reserved'),"\x00")) === 0)
|
||||
);
|
||||
}
|
||||
}
|
@ -42,7 +42,7 @@ class PacketInfo extends Command
|
||||
|
||||
$this->alert(sprintf('File Name: %s',$x));
|
||||
|
||||
$this->info(sprintf('Packet Type : %s',$pkt->type));
|
||||
$this->info(sprintf('Packet Type : %s (%s)',$pkt->type,get_class($pkt)));
|
||||
$this->info(sprintf('From : %s to %s',$pkt->fftn,$pkt->tftn));
|
||||
$this->info(sprintf('Dated : %s',$pkt->date));
|
||||
$this->info(sprintf('Password : %s (%s)',$pkt->password,$pkt->password ? 'SET' : 'NOT set'));
|
||||
|
@ -650,7 +650,9 @@ class Address extends Model
|
||||
if (! $ao)
|
||||
return NULL;
|
||||
|
||||
$o = new Packet($ao,$this);
|
||||
// @todo make packet type configurable by each system
|
||||
$o = new Packet\FSC48;
|
||||
$o->addressHeader($ao,$this);
|
||||
|
||||
// $oo = Netmail/Echomail Model
|
||||
$c = 0;
|
||||
|
@ -53,7 +53,8 @@ If you have more than 1 BBS, then the Clearing Houz can receive all your mail fr
|
||||
<li>Nodelist Management</li>
|
||||
<li>Self service FTN Network Applications <sup>being implemented</sup></li>
|
||||
<li>Dynamic mail bundling for upstream and downstream nodes (no more "inbounds" and "outbounds")</li>
|
||||
<li>Automatic delisting of idle nodes</li>
|
||||
<li>Support for Fidonet Packet formats <a href="http://ftsc.org/docs/fts-0001.016">FTS-0001</a>,<a href="http://ftsc.org/docs/fsc-0039.004">FSC-0039</a>,<a href="http://ftsc.org/docs/fsc-0045.001">FSC-0045</a>,<a href="http://ftsc.org/docs/fsc-0048.002">FSC-0048</a></li>
|
||||
<li>Automatic delisting of idle nodes <sup>(to be implemented)</sup></li>
|
||||
<li>DNS server, to enable resolving of registered nodes using domain dns names <strong class="highlight">p<em>N</em>.f<em>N</em>.n<em>N</em>.z<em>N</em>.<em>domain</em>.ftn</strong>, or <strong class="highlight">p<em>N</em>.f<em>N</em>.n<em>N</em>.z<em>N</em>.<em>[domain dns zone]</em></strong></li>
|
||||
</ul>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user