Compare commits

...

16 Commits

Author SHA1 Message Date
33a97ce5b3 Fix showing 'View Packet' when the to zone is not in the same domain as the from domain
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 46s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m38s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-12-01 12:38:08 +11:00
2ea8bbd0b8 Implemented filefix %RESEND 2024-12-01 12:38:08 +11:00
ce19985c2d Implemented filefix %SCAN/%RESCAN, and some cosmetic cleanup 2024-12-01 12:38:08 +11:00
0e1086c99f Fixes for Zmodem missed in bf3fce 2024-12-01 12:38:08 +11:00
fc11700601 Remove/reduce usage of QueryCache 2024-12-01 12:38:08 +11:00
119f2cb6b9 Fix bug with HAPROXY v2 processing when implementing v1 in d2d109 2024-12-01 12:38:08 +11:00
d9817e1c2e Added filefix %FILELIST 2024-12-01 12:38:08 +11:00
08375f4995 Respond to filefix commands with Filefix/CommandsProcessed 2024-12-01 12:38:08 +11:00
d58ed8842b Added filefix %AREA 2024-12-01 12:38:08 +11:00
39034dbbb0 Batch up files when sending to a remote node 2024-12-01 12:38:08 +11:00
1296e3be40 Remove Address::downstream() for Address::downlinks() 2024-12-01 12:38:08 +11:00
9c828d65e6 Update TestNodeHierarchy to include a fuller FTN setup for testing. Update testing. 2024-12-01 12:38:08 +11:00
810e620526 SocketClient change has.. can.. functions to return boolean and other minor cosmetic changes 2024-12-01 12:38:08 +11:00
6c36f7f9aa Fix UDP services (ie: DNS) 2024-12-01 12:38:07 +11:00
38c68982ec Exclude points when choosing what to remove from the net during nodelist processing 2024-12-01 12:38:07 +11:00
a161b8fc5e Added filefix %LIST 2024-12-01 12:38:07 +11:00
49 changed files with 1734 additions and 561 deletions

View File

@ -5,6 +5,8 @@ APP_DEBUG=true
APP_URL=http://clrghouz
APP_TIMEZONE=Australia/Melbourne
CACHE_STORE=array
LOG_CHANNEL=stderr
LOG_LEVEL=debug
@ -13,7 +15,7 @@ DB_HOST=postgres-test
DB_PORT=5432
DB_DATABASE=test
DB_USERNAME=test
DB_PASSWORD=test
DB_PASSWORD=password
BROADCAST_DRIVER=log
CACHE_DRIVER=file

View File

@ -462,7 +462,7 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
}
// @todo If the message from domain (eg: $msg->fftn->zone->domain) is different to the packet address domain ($pkt->fftn->zone->domain), we'll skip this message
Log::debug(sprintf('%s:^ Message [%s] - Packet from domain [%d], Message domain [%d]',self::LOGKEY,$msg->msgid,$this->fftn->zone->domain_id,$msg->fftn->zone->domain_id));
//Log::debug(sprintf('%s:^ Message [%s] - Packet from domain [%d], Message domain [%d]',self::LOGKEY,$msg->msgid,$this->fftn->zone->domain_id,$msg->fftn->zone->domain_id));
$this->messages->push($msg);
}

View File

@ -25,7 +25,7 @@ final class Areafix extends Robot
if ((strtolower($mo->to) !== 'areafix') || (! ($mo instanceof Netmail)))
return FALSE;
Log::info(sprintf('%s:- Processing AREAFIX [%s] message from (%s) [%s]', self::LOGKEY, $mo->to, $mo->from, $mo->fftn->ftn));
Log::info(sprintf('%s:- Processing AREAFIX [%s] message from (%s) [%s]',self::LOGKEY,$mo->to,$mo->from,$mo->fftn->ftn));
return parent::handle($mo);
}
@ -36,21 +36,32 @@ final class Areafix extends Robot
$result->push('--> BEGIN <--');
foreach ($mo->body_lines as $command) {
Log::debug(sprintf('%s:* Processing command [%s]',self::LOGKEY,$command));
// Skip empty lines
if (! $command)
if (! $command || preg_match('/^\s+$/',$command))
continue;
$command = explode(' ',strtoupper(trim($command)));
$command = explode(' ',strtoupper(rtrim($command)));
Log::debug(sprintf('%s:* Processing command',self::LOGKEY),['command'=>$command]);
// If command starts with '...' or '---', its a tear/tag line, and we have reached the end
if (str_starts_with($command[0],'...') || str_starts_with($command[0],'---')) {
Log::debug(sprintf('%s:= We got a tearline/tagline, end of processing',self::LOGKEY));
Log::info(sprintf('%s:= We got a tearline/tagline, end of processing',self::LOGKEY));
$result->push('--> END OF PROCESSING <--');
$result->push('--> END OF PROCESSING - TEARLINE/TAGLINE <--');
break;
// If command doesnt start with %, its an area
// Lines starting with a space, we'll abort
} elseif (! $command[0]) {
Log::info(sprintf('%s:= Got a new line with a space, end of processing',self::LOGKEY));
$result->push('--> END OF PROCESSING - SPACE DETECTED <--');
break;
// If command doesnt start with %, its an area
} elseif (! str_starts_with($command[0],'%')) {
Log::info(sprintf('%s:= Assuming command [%s] is an AREA command',self::LOGKEY,$command[0]));

View File

@ -62,9 +62,9 @@ class Area extends Base
if ($nea=$this->mo->fftn->echoareas->where('name',$area)->pop()) {
// requesting to subscribe "You already are since..., arguments ignored
if ($sub) {
Log::debug(sprintf('%s:- FTN [%s] ALREADY subscribed to [%s] since [%s]',self::LOGKEY,$this->mo->fftn->ftn,$area,$nea->pivot->subscribed->format('Y-m-d H:i')));
Log::debug(sprintf('%s:- FTN [%s] ALREADY subscribed to [%s] since [%s]',self::LOGKEY,$this->mo->fftn->ftn,$area,$nea->pivot->subscribed));
return sprintf('%-25s <-- ALREADY subscribed since %s',$command,$nea->pivot->subscribed->format('Y-m-d H:i'));
return sprintf('%-25s <-- ALREADY subscribed since %s',$command,$nea->pivot->subscribed);
// requesting to unsubscribe
} else {
@ -78,7 +78,7 @@ class Area extends Base
->whereNotNull('export_at')
->whereNull('sent_at')
->orderBy('echomails.datetime')
->skip($this->mo->fftn->system->pkt_msgs)
->skip($this->mo->fftn->system->pkt_msgs) // Might already being sent in this session
->delete();
Log::debug(sprintf('%s:- FTN [%s] UNSUBSCRIBED from [%s] clearing [%s]',self::LOGKEY,$this->mo->fftn->ftn,$area,$x));

View File

@ -2,6 +2,7 @@
namespace App\Classes\FTN\Process\Netmail\Robot\Areafix;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use App\Jobs\AreafixRescan;
@ -21,7 +22,7 @@ class Rescan extends Base
' Arguments:',
' - ECHOAREA (required) name of area to subscribe or unsubscribe',
' - DAYS (optional) number of days to resend mail from this area that you',
' If DAYS is omitted, the default is 30',
' If DAYS is omitted, the default is 30.',
];
}
@ -31,19 +32,19 @@ class Rescan extends Base
$command = self::command.' '.join(' ',$this->arguments);
if (! is_numeric($this->arguments[1]))
if (! is_numeric($days=Arr::get($this->arguments,1,30)))
return sprintf('%-25s <-- INVALID, DAYS [%s] NOT NUMERIC',$command,$this->arguments[1]);
// Area exists
if ($ea=$this->mo->fftn->domain->echoareas->where('name',$this->arguments[0])->pop()) {
// If already subscribed
if ($this->mo->fftn->echoareas->pluck('name')->contains($this->arguments[0])) {
AreafixRescan::dispatch($this->mo->fftn,$ea,$this->arguments[1],TRUE)
AreafixRescan::dispatch($this->mo->fftn,$ea,$days,TRUE)
->onQueue('mail');
Log::debug(sprintf('%s:- FTN [%s] RESCAN [%s] DAYS [%d]',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0],$this->arguments[1]));
Log::debug(sprintf('%s:- FTN [%s] RESCAN [%s] DAYS [%d]',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0],$days));
return sprintf('%-25s <-- RESCAN [%d] DAYS queued',$command,$this->arguments[1]);
return sprintf('%-25s <-- RESCAN [%d] DAYS queued',$command,$days);
// If not subscribed
} else {

View File

@ -2,6 +2,7 @@
namespace App\Classes\FTN\Process\Netmail\Robot\Areafix;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use App\Jobs\AreafixRescan;
@ -22,7 +23,7 @@ class Scan extends Base
' Arguments:',
' - ECHOAREA (required) name of area to subscribe or unsubscribe',
' - DAYS (optional) number of days to resend mail from this area that you',
' If DAYS is omitted, the default is 30',
' If DAYS is omitted, the default is 30.',
];
}
@ -32,19 +33,19 @@ class Scan extends Base
$command = self::command.' '.join(' ',$this->arguments);
if (! is_numeric($this->arguments[1]))
if (! is_numeric($days=Arr::get($this->arguments,1,30)))
return sprintf('%-25s <-- INVALID, DAYS [%s] NOT NUMERIC',$command,$this->arguments[1]);
// Area exists
if ($ea=$this->mo->fftn->domain->echoareas->where('name',$this->arguments[0])->pop()) {
// If already subscribed
if ($this->mo->fftn->echoareas->pluck('name')->contains($this->arguments[0])) {
AreafixRescan::dispatch($this->mo->fftn,$ea,$this->arguments[1])
AreafixRescan::dispatch($this->mo->fftn,$ea,$days)
->onQueue('mail');
Log::debug(sprintf('%s:- FTN [%s] SCAN [%s] DAYS [%d]',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0],$this->arguments[1]));
Log::debug(sprintf('%s:- FTN [%s] SCAN [%s] DAYS [%d]',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0],$days));
return sprintf('%-25s <-- SCAN [%d] DAYS queued',$command,$this->arguments[1]);
return sprintf('%-25s <-- SCAN [%d] DAYS queued',$command,$days);
// If not subscribed
} else {

View File

@ -7,7 +7,7 @@ use Illuminate\Support\Facades\Log;
use App\Classes\FTN\Process\Netmail\Robot;
use App\Models\{Echomail,Netmail};
use App\Notifications\Netmails\Areafix\CommandsProcessed;
use App\Notifications\Netmails\Filefix\CommandsProcessed;
/**
* Process messages to Ping
@ -25,7 +25,7 @@ final class Filefix extends Robot
if ((strtolower($mo->to) !== 'filefix') || (! ($mo instanceof Netmail)))
return FALSE;
Log::info(sprintf('%s:- Processing FILEFIX [%s] message from (%s) [%s]', self::LOGKEY, $mo->to, $mo->from, $mo->fftn->ftn));
Log::info(sprintf('%s:- Processing FILEFIX [%s] message from (%s) [%s]',self::LOGKEY,$mo->to,$mo->from,$mo->fftn->ftn));
return parent::handle($mo);
}
@ -36,21 +36,32 @@ final class Filefix extends Robot
$result->push('--> BEGIN <--');
foreach ($mo->body_lines as $command) {
Log::debug(sprintf('%s:* Processing command [%s]',self::LOGKEY,$command));
// Skip empty lines
if (! $command)
if (! $command || preg_match('/^\s+$/',$command))
continue;
$command = explode(' ',strtoupper(trim($command)));
$command = explode(' ',strtoupper(rtrim($command)));
Log::debug(sprintf('%s:* Processing command',self::LOGKEY),['command'=>$command]);
// If command starts with '...' or '---', its a tear/tag line, and we have reached the end
if (str_starts_with($command[0],'...') || str_starts_with($command[0],'---')) {
Log::debug(sprintf('%s:= We got a tearline/tagline, end of processing',self::LOGKEY));
Log::info(sprintf('%s:= We got a tearline/tagline, end of processing',self::LOGKEY));
$result->push('--> END OF PROCESSING <--');
$result->push('--> END OF PROCESSING - TEARLINE/TAGLINE <--');
break;
// If command doesnt start with %, its an area
// Lines starting with a space, we'll abort
} elseif (! $command[0]) {
Log::info(sprintf('%s:= Got a new line with a space, end of processing',self::LOGKEY));
$result->push('--> END OF PROCESSING - SPACE DETECTED <--');
break;
// If command doesnt start with %, its an area
} elseif (! str_starts_with($command[0],'%')) {
Log::info(sprintf('%s:= Assuming command [%s] is an AREA command',self::LOGKEY,$command[0]));
@ -60,7 +71,7 @@ final class Filefix extends Robot
// Some commands are reserved words
switch ($x=strtolower(substr($command[0],1))) {
case 'list':
$class = self::commands.'FileList';
$class = self::commands.'AreaList';
break;
default:

View File

@ -0,0 +1,144 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Filefix;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Classes\FTN\Process\Netmail\Robot\Areafix\Base;
use App\Jobs\FilefixRescan;
// Filearea Processing Command
class Area extends Base
{
private const LOGKEY = 'FFA';
private const command = '%AREA';
public static function help(): array
{
return [
self::command.' [-|+]<FILEAREA> [R|D=<DAYS>]',
' Use the area command to subscribe (+) or unsubscribe (-) to a FILEAREA',
' Arguments:',
' - FILEAREA (required) name of area to subscribe or unsubscribe',
' - D=DAYS (optional) number of days to resend files from this area that you',
' havent already received (useful if you are resubscribing to an area and',
' have received files in the past)',
' - R=DAYS (optional) number of days to resend files from this area (even if',
' it was sent to you previously)',
' Notes:',
' * "+" is optional, and is implied if "-" is not used',
' * "R" and "D" options only apply to subscribing',
];
}
public function process(): string
{
$command = self::command.' '.join(' ',$this->arguments);
// If command starts with '-', its to unsubscribe
if (str_starts_with($this->arguments[0],'-')) {
$sub = FALSE;
$area = substr($this->arguments[0],1);
} elseif (str_starts_with($this->arguments[0],'+')) {
$sub = TRUE;
$area = substr($this->arguments[0],1);
} else {
$sub = TRUE;
$area = $this->arguments[0];
}
Log::debug(sprintf('%s:- Processing [%s] for [%s]',self::LOGKEY,$sub ? 'ADD' : 'REMOVE',$area));
// Drop the area from the arguments, the rest are options
array_shift($this->arguments);
// Area exists
if ($fa=$this->mo->fftn->domain->fileareas->where('name',$area)->pop()) {
// If already subscribed
if ($nea=$this->mo->fftn->fileareas->where('name',$area)->pop()) {
// requesting to subscribe "You already are since..., arguments ignored
if ($sub) {
Log::debug(sprintf('%s:- FTN [%s] ALREADY subscribed to [%s] since [%s]',self::LOGKEY,$this->mo->fftn->ftn,$area,$nea->pivot->subscribed));
return sprintf('%-25s <-- ALREADY subscribed since %s',$command,$nea->pivot->subscribed);
// requesting to unsubscribe
} else {
$this->mo->fftn->fileareas()->detach($fa->id);
// Remove sub, clear queue
$x = DB::table('file_seenby')
->where('address_id',$this->mo->fftn->id)
->join('files',['files.id'=>'file_seenby.file_id'])
->where('filearea_id',$nea->id)
->whereNotNull('export_at')
->whereNull('sent_at')
->orderBy('files.datetime')
->skip(1) // Might already being sent in this session
->delete();
Log::debug(sprintf('%s:- FTN [%s] UNSUBSCRIBED from [%s] clearing [%s]',self::LOGKEY,$this->mo->fftn->ftn,$area,$x));
return sprintf('%-25s <-- UNSUBSCRIBED, CLEARED [%d] FILES from queue',$command,$x);
}
// If not subscribed
} else {
// requesting to subscribe, subsubsribe and rescan if arguments
if ($sub) {
$this->mo->fftn->fileareas()->attach([$fa->id=>['subscribed'=>Carbon::now()]]);
// If we have arguments, they are to rescan
if (count($this->arguments) === 1) {
$m = [];
if (preg_match('/^([DR])=([0-9]+)$/',$this->arguments[0],$m)) {
switch ($m[1]) {
// Scan
case 'D':
FilefixRescan::dispatch($this->mo->fftn,$fa,$m[2])
->onQueue('mail');
return sprintf('%-25s <-- SUBSCRIBED, RESCAN [%d] DAYS queued',$command,$m[2]);
// Scan
case 'R':
FilefixRescan::dispatch($this->mo->fftn,$fa,$m[2],TRUE)
->onQueue('mail');
return sprintf('%-25s <-- SUBSCRIBED, FORCE RESCAN [%d] DAYS queued',$command,$m[2]);
}
}
return sprintf('%-25s <-- SUBSCRIBED, INVALID OPTIONS',$command);
} elseif (count($this->arguments) > 1) {
Log::debug(sprintf('%s:- FTN [%s] subscribed to [%s], extra commands [%s] ignored',self::LOGKEY,$this->mo->fftn->ftn,$area,join('|',$this->arguments)));
return sprintf('%-25s <-- SUBSCRIBED, OPTIONS IGNORED',$command);
} else {
Log::debug(sprintf('%s:- FTN [%s] subscribed to [%s]',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- SUBSCRIBED',$command);
}
// If not subscribed, "you arent subscribed, arguments ignored"
} else {
Log::debug(sprintf('%s:- FTN [%s] is NOT subscribed to [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- NOT subscribed, NO ACTION taken',$command);
}
}
} else {
Log::debug(sprintf('%s:- FILE [%s] area UNKNOWN [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- AREA UNKNOWN, NO ACTION TAKEN',$command);
}
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Filefix;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use App\Classes\FTN\Process\Netmail\Robot\Areafix\Base;
use App\Notifications\Netmails\Filefix\AreaList as AreaListNotification;
// LIST - List fileareas in a domain
class AreaList extends Base
{
private const LOGKEY = 'AFS';
private const command = '%LIST';
public static function help(): array
{
return [
self::command,
' List the available fileareas in this network',
];
}
public function process(): string
{
Log::debug(sprintf('%s:- Filefix [%s] for [%s] for [%s]',self::LOGKEY,self::command,$this->mo->fftn->ftn,join('|',$this->arguments)));
if (count($this->arguments) > 1)
return sprintf('%-25s <-- INVALID COMMAND',self::command);
else {
Notification::route('netmail',$this->mo->fftn)
->notify(new AreaListNotification($this->mo));
return sprintf('%-25s <-- COMMAND PROCESSED',self::command);
}
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Filefix;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use App\Classes\FTN\Process\Netmail\Robot\Areafix\Base;
use App\Notifications\Netmails\Filefix\Filelist as FilelistNotification;
// FILELIST - List files in an area
class Filelist extends Base
{
private const LOGKEY = 'AFR';
private const command = '%FILELIST';
public static function help(): array
{
return [
self::command.' [-|+]<FILEAREA> [<DAYS>]',
' Use the filelist command to list files from an filearea.',
' This is will resend files again, even if you have received them in the',
' past.',
' Arguments:',
' - FILEAREA (required) name of area',
' - DAYS (optional) number of days to resend mail from this area that you',
' If DAYS is omitted, the default is 30. The maximum is 365.',
];
}
public function process(): string
{
Log::debug(sprintf('%s:- Filefix [%s] for [%s] for [%s]',self::LOGKEY,self::command,$this->mo->fftn->ftn,join('|',$this->arguments)));
$command = self::command.' '.join(' ',$this->arguments);
if (! is_numeric($days=Arr::get($this->arguments,1,30)))
return sprintf('%-25s <-- INVALID, DAYS [%s] NOT NUMERIC',$command,$this->arguments[1]);
if ($days > 365)
$days = 365;
// Area exists
if ($fa=$this->mo->fftn->domain->fileareas->where('name',$this->arguments[0])->pop()) {
// If already subscribed
if ($this->mo->fftn->fileareas->pluck('name')->contains($this->arguments[0])) {
Notification::route('netmail',$this->mo->fftn)
->notify(new FileListNotification($this->mo,$fa,$days));
Log::debug(sprintf('%s:- FTN [%s] FILELIST [%s] DAYS [%d]',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0],$days));
return sprintf('%-25s <-- FILELIST [%d] DAYS',$command,$days);
// If not subscribed
} else {
Log::debug(sprintf('%s:- FTN [%s] is NOT subscribed to [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0]));
return sprintf('%-25s <-- NOT subscribed, NO ACTION taken',$command);
}
} else {
Log::debug(sprintf('%s:- FILE [%s] area UNKNOWN [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0]));
return sprintf('%-25s <-- AREA UNKNOWN, NO ACTION TAKEN',$command);
}
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Filefix;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use App\Classes\FTN\Process\Netmail\Robot\Areafix\Base;
use App\Jobs\FilefixRescan;
// RESCAN - Resend echomail
class Rescan extends Base
{
private const LOGKEY = 'AFR';
private const command = '%RESCAN';
public static function help(): array
{
return [
self::command.' [-|+]<FILEAREA> [<DAYS>]',
' Use the rescan command to resend files from a filearea.',
' This is will resend files again, even if you have received them in the',
' past.',
' Arguments:',
' - FILEAREA (required) name of area to subscribe or unsubscribe',
' - DAYS (optional) number of days to resend mail from this area that you',
' If DAYS is omitted, the default is 30. The maximum is 365.',
];
}
public function process(): string
{
Log::debug(sprintf('%s:- Filefix [%s] for [%s] for [%s]',self::LOGKEY,self::command,$this->mo->fftn->ftn,join('|',$this->arguments)));
$command = self::command.' '.join(' ',$this->arguments);
if (! is_numeric($days=Arr::get($this->arguments,1,30)))
return sprintf('%-25s <-- INVALID, DAYS [%s] NOT NUMERIC',$command,$this->arguments[1]);
if ($days > 365)
$days = 365;
// Area exists
if ($fa=$this->mo->fftn->domain->fileareas->where('name',$this->arguments[0])->pop()) {
// If already subscribed
if ($this->mo->fftn->fileareas->pluck('name')->contains($this->arguments[0])) {
FilefixRescan::dispatch($this->mo->fftn,$fa,$days,TRUE)
->onQueue('mail');
Log::debug(sprintf('%s:- FTN [%s] RESCAN [%s] DAYS [%d]',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0],$days));
return sprintf('%-25s <-- RESCAN [%d] DAYS queued',$command,$days);
// If not subscribed
} else {
Log::debug(sprintf('%s:- FTN [%s] is NOT subscribed to [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0]));
return sprintf('%-25s <-- NOT subscribed, NO ACTION taken',$command);
}
} else {
Log::debug(sprintf('%s:- FILE [%s] area UNKNOWN [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0]));
return sprintf('%-25s <-- AREA UNKNOWN, NO ACTION TAKEN',$command);
}
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Filefix;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use App\Classes\FTN\Process\Netmail\Robot\Areafix\Base;
// Resend a file
class Resend extends Base
{
private const LOGKEY = 'FFA';
private const command = '%RESEND';
public static function help(): array
{
return [
self::command.' <FILEAREA> <FILENAME>',
' Resend a file from a file area',
' Arguments:',
' - FILEAREA (required) name of area to subscribe or unsubscribe',
' - FILENAME (required) number of file to resend',
' Notes:',
' * You can obtain a list of files in an area with %FILELIST <FILEAREA>',
];
}
public function process(): string
{
$command = self::command.' '.join(' ',$this->arguments);
if (count($this->arguments) < 2)
return sprintf('%-25s <-- INVALID, NOT ENOUGH ARGUMENTS',$command);
elseif (count($this->arguments) > 2)
return sprintf('%-25s <-- INVALID, TOO MANU ARGUMENTS',$command);
Log::debug(sprintf('%s:- Resending [%s] from [%s] to [%s]',self::LOGKEY,$this->arguments[1],$this->arguments[0],$this->mo->fftn->ftn));
// Area exists
if ($fa=$this->mo->fftn->domain->fileareas->where('name',$this->arguments[0])->pop()) {
// If already subscribed
if ($nea=$this->mo->fftn->fileareas->where('name',$fa->name)->pop()) {
// Check the file is in the area
if ($fo=$nea->files()->where('name','ilike',$this->arguments[1])->single()) {
// File hasnt been exported before
if (! $fo->seenby->where('id',$this->mo->fftn_id)->count()) {
Log::info(sprintf('Exported [%d] FILE (%s) dated (%s) to [%s]',$fo->id,$fo->name,$fo->datetime->format('Y-m-d H:i:s'),$this->mo->fftn->ftn3d));
$fo->seenby()->attach($this->mo->fftn_id,['export_at'=>Carbon::now()]);
} else {
Log::debug(sprintf('Re-exported [%d] FILE (%s) dated (%s) to [%s]',$fo->id,$fo->name,$fo->datetime,$this->mo->fftn->ftn3d));
$fo->seenby()->updateExistingPivot($this->mo->fftn_id,['export_at'=>Carbon::now(),'sent_at'=>NULL]);
}
return sprintf('%-25s <-- FILE QUEUED TO RESEND',$command);
// No file in area
} else {
Log::debug(sprintf('%s:- FTN [%s] doesnt have a file [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[1]));
return sprintf('%-25s <-- FILE NOT FOUND, NO ACTION taken',$command);
}
// If not subscribed
} else {
Log::debug(sprintf('%s:- FTN [%s] is NOT subscribed to [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0]));
return sprintf('%-25s <-- NOT subscribed, NO ACTION taken',$command);
}
} else {
Log::debug(sprintf('%s:- FILE [%s] area UNKNOWN [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0]));
return sprintf('%-25s <-- AREA UNKNOWN, NO ACTION TAKEN',$command);
}
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Filefix;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use App\Classes\FTN\Process\Netmail\Robot\Areafix\Base;
use App\Jobs\FilefixRescan;
// SCAN - Send unsent echomail
class Scan extends Base
{
private const LOGKEY = 'AFS';
private const command = '%SCAN';
public static function help(): array
{
return [
self::command.' [-|+]<FILEAREA> [<DAYS>]',
' Use the scan command to resend files that you havent received yet from an',
' filearea. This is useful if you are rejoining an filearea, and only want',
' to get files that you dont already have.',
' Arguments:',
' - FILEAREA (required) name of area to subscribe or unsubscribe',
' - DAYS (optional) number of days to resend mail from this area that you',
' If DAYS is omitted, the default is 30. The maximum is 365.',
];
}
public function process(): string
{
Log::debug(sprintf('%s:- Filefix [%s] for [%s] for [%s]',self::LOGKEY,self::command,$this->mo->fftn->ftn,join('|',$this->arguments)));
$command = self::command.' '.join(' ',$this->arguments);
if (! is_numeric($days=Arr::get($this->arguments,1,30)))
return sprintf('%-25s <-- INVALID, DAYS [%s] NOT NUMERIC',$command,$this->arguments[1]);
if ($days > 365)
$days = 365;
// Area exists
if ($fa=$this->mo->fftn->domain->fileareas->where('name',$this->arguments[0])->pop()) {
// If already subscribed
if ($this->mo->fftn->fileareas->pluck('name')->contains($this->arguments[0])) {
FilefixRescan::dispatch($this->mo->fftn,$fa,$days)
->onQueue('mail');
Log::debug(sprintf('%s:- FTN [%s] SCAN [%s] DAYS [%d]',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0],$days));
return sprintf('%-25s <-- SCAN [%d] DAYS queued',$command,$days);
// If not subscribed
} else {
Log::debug(sprintf('%s:- FTN [%s] is NOT subscribed to [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0]));
return sprintf('%-25s <-- NOT subscribed, NO ACTION taken',$command);
}
} else {
Log::debug(sprintf('%s:- FILE [%s] area UNKNOWN [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0]));
return sprintf('%-25s <-- AREA UNKNOWN, NO ACTION TAKEN',$command);
}
}
}

View File

@ -22,24 +22,28 @@ class File extends FileBase implements \Iterator
{
parent::__construct($path,$checkPath);
switch($x=$this->guessExtension()) {
case 'zip':
$this->canHandle = TRUE;
$this->isArchive = TRUE;
$this->z = new \ZipArchive;
$this->z->open($this->getRealPath());
break;
if ($this->getExtension() === 'pkt')
$this->canHandle = TRUE;
case NULL:
case 'bin':
if ($this->isPacket() || ($path instanceof UploadedFile && (strcasecmp($path->getClientOriginalExtension(),'pkt') === 0))) {
else
switch ($x=$this->guessExtension()) {
case 'zip':
$this->canHandle = TRUE;
$this->isArchive = TRUE;
$this->z = new \ZipArchive;
$this->z->open($this->getRealPath());
break;
}
default:
Log::alert(sprintf('%s:? Unknown file received: %s (%s) [%s]',self::LOGKEY,$x,$this->getExtension(),$path instanceof UploadedFile ? $path->getClientOriginalExtension() : '-'));
}
case NULL:
case 'bin':
if ($this->isPacket() || ($path instanceof UploadedFile && (strcasecmp($path->getClientOriginalExtension(),'pkt') === 0))) {
$this->canHandle = TRUE;
break;
}
default:
Log::alert(sprintf('%s:? Unknown file received: %s (%s) [%s]',self::LOGKEY,$x,$this->getExtension(),$path instanceof UploadedFile ? $path->getClientOriginalExtension() : '-'));
}
}
/* ITERATOR */

View File

@ -58,7 +58,7 @@ final class Mail extends Send
if ($successful) {
$this->complete = TRUE;
Log::debug(sprintf('%s:- Successful close for [%d] - updating [%d] records.',self::LOGKEY,$this->type,$this->dbids->count()),['dbids'=>$this->dbids,'authd'=>$node->aka_remote_authed->pluck('id')]);
Log::info(sprintf('%s:- Successful close for [%d] - updating [%d] records.',self::LOGKEY,$this->type,$this->dbids->count()),['dbids'=>$this->dbids,'authd'=>$node->aka_remote_authed->pluck('id')]);
// Update netmail table
if (($this->type === Send::T_NETMAIL)

View File

@ -203,7 +203,7 @@ class Send extends Base
}
// Files
if (($x=$ao->filesWaiting())->count()) {
if (($x=$ao->getFiles())->count()) {
Log::info(sprintf('%s:- [%d] Files(s) added for sending to [%s]',self::LOGKEY,$x->count(),$ao->ftn));
// Add Files

View File

@ -288,6 +288,8 @@ abstract class Protocol
else {
Log::withContext(['pid'=>getmypid()]);
Log::debug(sprintf('%s:* Client session starting',self::LOGKEY));
$this->session($client,(new Address));
}
@ -360,11 +362,11 @@ abstract class Protocol
* Setup a session with a remote client
*
* @param SocketClient $client Socket details of session
* @param Address|null $o If we have an address, we originated a session to this Address
* @param Address $o If we have an address, we originated a session to this Address
* @return int
* @throws \Exception
*/
public function session(SocketClient $client,Address $o=NULL): int
public function session(SocketClient $client,Address $o): int
{
if ($o->exists)
Log::withContext(['ftn'=>$o->ftn]);
@ -395,7 +397,7 @@ abstract class Protocol
}
// We are an IP node
$this->optionSet(self::O_TCP);
$this->client = $client;
// @todo This appears to be a bug in laravel? Need to call app()->isDownForMaintenance() twice?
app()->isDownForMaintenance();
@ -405,6 +407,7 @@ abstract class Protocol
case EMSI::class:
Log::debug(sprintf('%s:- Starting EMSI',self::LOGKEY));
$this->optionSet(self::O_TCP);
$rc = $this->protocol_init();
if ($rc < 0) {
Log::error(sprintf('%s:! Unable to start EMSI [%d]',self::LOGKEY,$rc));
@ -419,10 +422,14 @@ abstract class Protocol
case Binkp::class:
Log::debug(sprintf('%s:- Starting BINKP',self::LOGKEY));
$this->optionSet(self::O_TCP);
$rc = $this->protocol_session($this->originate);
break;
case DNS::class:
return $this->protocol_session();
default:
Log::error(sprintf('%s:! Unsupported session type [%s]',self::LOGKEY,get_class($this)));

View File

@ -921,8 +921,16 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
$t1 = $this->client->timer_set(self::EMSI_HSTIMEOUT);
$t2 = $this->client->timer_set(self::EMSI_RESEND_TO);
$c = 0;
while (! $this->client->timer_expired($t1)) {
$ch = $this->client->read_ch(max( 1,min($this->client->timer_rest($t1),$this->client->timer_rest($t2))));
try {
$ch = $this->client->read_ch(max( 1,min($this->client->timer_rest($t1),$this->client->timer_rest($t2))));
} catch (SocketException $e) {
if ($c++ > 2)
return self::TIMEOUT;
}
if (static::DEBUG)
Log::debug(sprintf('%s:- Got [%x] (%c)',self::LOGKEY,$ch,$ch));
@ -1193,7 +1201,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
Log::debug(sprintf('%s:+ Start WAZOO Receive',self::LOGKEY));
// @todo If the node is not defined in the DB node->address is NULL. Need to figure out how to handle those nodes.
$rc = (new Zmodem)->zmodem_receive($this->client,$zap,$this->recv,$this->node->address,$this->force_queue);
$rc = (new Zmodem($this->setup))->zmodem_receive($this->client,$zap,$this->recv,$this->node->address,$this->force_queue);
return ($rc === self::RCDO || $rc === self::ERROR);
}
@ -1217,7 +1225,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
foreach ($this->node->aka_remote_authed as $ao) {
// Send mail
while ($this->send->mail($ao)) {
$z = new Zmodem;
$z = new Zmodem($this->setup);
if (! $z->zmodem_sendinit($this->client,$zap) && $this->send->togo_count)
$z->zmodem_sendfile($this->send,$this->node);
@ -1225,7 +1233,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
// Send files
while ($this->send->files($ao)) {
$z = new Zmodem;
$z = new Zmodem($this->setup);
if (! $z->zmodem_sendinit($this->client,$zap) && $this->send->togo_count)
$z->zmodem_sendfile($this->send,$this->node);

View File

@ -516,7 +516,9 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
return $rc;
} catch (\Exception $e) {
Log::error(sprintf('%s:! Error [%s]',self::LOGKEY,$e->getMessage()));
Log::error(sprintf('%s:! Error [%s]',self::LOGKEY,$e->getMessage()),['rc'=>$rc]);
return $rc;
}
}

View File

@ -11,6 +11,7 @@ final class SocketException extends \Exception {
public const CANT_CONNECT = 5;
public const SOCKET_ERROR = 6;
public const SOCKET_EAGAIN = 11;
public const SOCKET_TIMEOUT = 15;
public const SOCKET_READ = 22;
public const CONNECTION_RESET = 104;
@ -22,6 +23,7 @@ final class SocketException extends \Exception {
self::CANT_CONNECT => 'Can\'t connect: "%s"',
self::SOCKET_ERROR => 'Socket Error: "%s"',
self::SOCKET_EAGAIN => 'Socket Resource Temporarily Unavailable - Try again',
self::SOCKET_TIMEOUT => 'Timeout reached "%d"',
self::SOCKET_READ => 'Unable to read from socket',
self::CONNECTION_RESET => 'Connection reset by peer',
];

View File

@ -48,7 +48,8 @@ final class SocketClient {
/** @var string Data in the RX buffer */
private string $rx_buf = '';
public function __construct (\Socket $connection,bool $originate=FALSE) {
public function __construct (\Socket $connection,bool $originate=FALSE)
{
$this->connection = $connection;
if ($this->type === SOCK_STREAM) {
@ -62,7 +63,7 @@ final class SocketClient {
if (($x=$this->read(5,6)) === 'PROXY ')
$vers = 1;
elseif (($x === "\x0d\x0a\x0d\x0a\x00\x0d") && ($this->read('5,6') === "\x0aQUIT\x0a"))
elseif (($x === "\x0d\x0a\x0d\x0a\x00\x0d") && ($this->read(5,6) === "\x0aQUIT\x0a"))
$vers = 2;
else
@ -191,38 +192,26 @@ final class SocketClient {
}
}
public function __get($key) {
switch ($key) {
case 'address_remote':
case 'port_remote':
return $this->{$key};
case 'cps':
case 'speed':
return Arr::get($this->session,$key);
case 'rx_free':
return self::RX_BUF_SIZE-$this->rx_left;
case 'rx_left':
return strlen($this->rx_buf);
case 'tx_free':
return self::TX_BUF_SIZE-strlen($this->tx_buf);
case 'type':
return socket_get_option($this->connection,SOL_SOCKET,SO_TYPE);
default:
throw new \Exception(sprintf('%s:! Unknown key [%s]:',self::LOGKEY,$key));
}
public function __get(string $key): mixed
{
return match ($key) {
'address_remote', 'port_remote' => $this->{$key},
'cps', 'speed' => Arr::get($this->session,$key),
'rx_free' => self::RX_BUF_SIZE-$this->rx_left,
'rx_left' => strlen($this->rx_buf),
'tx_free' => self::TX_BUF_SIZE-strlen($this->tx_buf),
'type' => socket_get_option($this->connection,SOL_SOCKET,SO_TYPE),
default => throw new \Exception(sprintf('%s:! Unknown key [%s]:',self::LOGKEY, $key)),
};
}
public function __set($key,$value) {
public function __set(string $key,mixed $value): void
{
switch ($key) {
case 'cps':
case 'speed':
return $this->session[$key] = $value;
$this->session[$key] = $value;
break;
default:
throw new \Exception(sprintf('%s:! Unknown key [%s]:',self::LOGKEY,$key));
@ -350,7 +339,7 @@ final class SocketClient {
while (strlen($this->tx_buf)) {
$tv = $this->timer_rest($tm);
if (($rc=$this->canSend($tv)) > 0) {
if ($rc=$this->canSend($tv)) {
if (self::DEBUG)
Log::debug(sprintf('%s:- Chars to send [%d]',self::LOGKEY,strlen($this->tx_buf)));
@ -378,14 +367,14 @@ final class SocketClient {
/**
* @param int $timeout
* @return int
* @return bool
* @throws \Exception
*/
public function canSend(int $timeout): int
public function canSend(int $timeout): bool
{
$write = [$this->connection];
return $this->socketSelect(NULL,$write,NULL,$timeout);
return $this->socketSelect(NULL,$write,NULL,$timeout) > 0;
}
/**
@ -412,14 +401,14 @@ final class SocketClient {
* We have data in the buffer or on the socket
*
* @param int $timeout
* @return int
* @return bool
* @throws \Exception
*/
public function hasData(int $timeout): int
public function hasData(int $timeout): bool
{
$read = [$this->connection];
return $this->rx_left ?: $this->socketSelect($read,NULL,NULL,$timeout);
return ($this->rx_left ?: $this->socketSelect($read,NULL,NULL,$timeout)) > 0;
}
/**
@ -427,10 +416,11 @@ final class SocketClient {
*
* @param int $timeout How long to wait for data
* @param int $len The amount of data we want
* @param int $flags
* @return string|null
* @throws SocketException
*/
public function read(int $timeout,int $len=1024): ?string
public function read(int $timeout,int $len=1024,int $flags=MSG_DONTWAIT): ?string
{
// We have data in our buffer
if ($this->rx_left >= $len) {
@ -443,19 +433,19 @@ final class SocketClient {
return $result;
}
if ($timeout AND ($this->hasData($timeout) === 0))
return NULL;
if ($timeout && (! $this->hasData($timeout)))
throw new SocketException(SocketException::SOCKET_TIMEOUT,$timeout);
$buf = '';
try {
switch ($this->type) {
case SOCK_STREAM:
$recv = socket_recv($this->connection,$buf,self::RX_SIZE,MSG_DONTWAIT);
$recv = socket_recv($this->connection,$buf,self::RX_SIZE,$flags);
break;
case SOCK_DGRAM:
$recv = socket_recvfrom($this->connection,$buf,self::RX_SIZE,MSG_DONTWAIT,$this->address_remote,$this->port_remote);
$recv = socket_recvfrom($this->connection,$buf,self::RX_SIZE,$flags,$this->address_remote,$this->port_remote);
break;
default:
@ -515,12 +505,11 @@ final class SocketClient {
*/
public function read_ch(int $timeout): int
{
if ($this->hasData($timeout) > 0) {
if ($this->hasData($timeout))
$ch = $this->read($timeout,1);
} else {
return self::TIMEOUT;
}
else
throw new SocketException(SocketException::SOCKET_TIMEOUT,$timeout);
return ord($ch);
}
@ -549,12 +538,12 @@ final class SocketClient {
*
* @param string $message
* @param int $timeout
* @return int|false
* @return int|bool
* @throws \Exception
*/
public function send(string $message,int $timeout): int|false
public function send(string $message,int $timeout): int|bool
{
if ($timeout AND (! $rc=$this->canSend($timeout)))
if ($timeout && (! $rc=$this->canSend($timeout)))
return $rc;
if (self::DEBUG)
@ -634,4 +623,4 @@ final class SocketClient {
return $this->socketSelect($read,$write,NULL,$timeout);
}
}
}

View File

@ -127,6 +127,8 @@ final class SocketServer {
if (($accept = socket_accept($this->server)) === FALSE)
throw new SocketException(SocketException::CANT_ACCEPT,socket_strerror(socket_last_error($this->server)));
Log::debug(sprintf('%s:* TCP Loop Start',self::LOGKEY));
try {
$r = new SocketClient($accept);
@ -155,7 +157,8 @@ final class SocketServer {
$r = new SocketClient($this->server);
if ($r->hasData(30)) {
$this->handler[0]->{$this->handler[1]}($r);
if (! ($this->handler[0]->{$this->handler[1]}($r)))
exit(0);
// Sleep so our thread has a chance to pick up the data from our connection
usleep(50000);

View File

@ -51,6 +51,9 @@ class Rescan extends Command
$eo = Echoarea::where('name',$this->argument('area'))->sole();
if ($eo->domain_id !== $ao->zone->domain_id)
throw new \Exception(sprintf('Echo area [%s] is not in domain [%s] for FTN [%s]',$eo->name,$ao->zone->domain->name,$ao->ftn));
if ($this->option('queue'))
AreafixRescan::dispatch($ao,$eo,$this->argument('days'))->onQueue($this->option('queuename'));
else

View File

@ -24,7 +24,7 @@ class AddressCheck extends Command
$this->info(sprintf('Address: %s (%s)',$o->ftn,$o->role_name));
$this->info(sprintf("Children: \n- %s",$o->children()->pluck('ftn4d')->join("\n- ")));
$this->info(sprintf("Downstream: \n- %s",$o->downstream()->pluck('ftn4d')->join("\n- ")));
$this->info(sprintf("Downlinks: \n- %s",$o->downlinks()->pluck('ftn4d')->join("\n- ")));
$this->info(sprintf('Uplink: %s (Parent: %s)',$o->uplink()?->ftn,$o->parent()?->ftn));
$this->info(sprintf('Our Address: %s',our_address($o)?->ftn));
$this->info(sprintf('- Domain Addresses: %s',our_address($o->zone->domain)->pluck('ftn4d')->join(',')));

View File

@ -25,21 +25,22 @@ class ZoneCheck extends Command
$this->warn('Zone: '.$zo->zone_id);
$this->info(sprintf('- Our address(es): %s',our_address($do)->pluck('ftn4d')->join(',')));
$this->table(['id','ftn','role','parent','children','downlinks','uplink','send from','region_id','system','notes'],$zo->addresses()->FTNorder()->active()->with(['system'])->dontCache()->get()->transform(function($item) {
return [
'id'=>$item->id,
'ftn'=>$item->ftn4d,
'role'=>$item->role_name,
'parent'=>$item->parent()?->ftn4d,
'children'=>$item->children()->count(),
'downlinks'=>$item->downlinks()->count(),
'uplink'=>($x=$item->uplink())?->ftn4d,
'send from'=>$x ? our_address($item->uplink())?->ftn4d : '',
'region_id'=>$item->region_id,
'system'=>$item->system->name,
'notes'=>$item->isRoleOverride() ? 'Role Override' : '',
];
}));
$this->table(['id','ftn','role','parent','children','downlinks','uplink','send from','region_id','system','notes'],
$zo->addresses()->FTN()->active()->with(['system','nodes_hub'])->get()->transform(function($item) {
return [
'id'=>$item->id,
'ftn'=>$item->ftn4d,
'role'=>$item->role_name,
'parent'=>$item->parent()?->ftn4d,
'children'=>$item->children()->count(),
'downlinks'=>$item->downlinks()->count(),
'uplink'=>($x=$item->uplink())?->ftn4d,
'send from'=>$x ? our_address($item->uplink())?->ftn4d : '',
'region_id'=>$item->region_id,
'system'=>$item->system->name,
'notes'=>$item->isRoleOverride() ? 'Role Override' : '',
];
}));
}
return self::SUCCESS;

View File

@ -2,10 +2,10 @@
namespace App\Console\Commands\Filefix;
use Carbon\Carbon;
use Illuminate\Console\Command;
use App\Models\{Address,Filearea,File};
use App\Jobs\FilefixRescan;
use App\Models\{Address,Filearea};
class Rescan extends Command
{
@ -14,7 +14,13 @@ class Rescan extends Command
*
* @var string
*/
protected $signature = 'filefix:rescan {ftn} {area} {file?}';
protected $signature = 'filefix:rescan'
.' {ftn : FTN Address}'
.' {area : Echoarea Tag}'
.' {days? : Limit to files received days ago}'
.' {--j|queue : Queue the Job}'
.' {--Q|queuename=default : Queue on queue}'
.' {--R|export : Re-export previously sent files}';
/**
* The console command description.
@ -31,6 +37,9 @@ class Rescan extends Command
*/
public function handle(): int
{
if (($this->argument('days')) && (! is_numeric($this->argument('days'))))
throw new \Exception('Days must be numeric: '.$this->argument('days'));
$ao = Address::findFtn($this->argument('ftn'));
if (! $ao)
@ -41,49 +50,14 @@ class Rescan extends Command
throw new \Exception('Areaname is required');
$fao = Filearea::where('name',$this->argument('area'))->sole();
if ($fao->domain_id !== $ao->zone->domain_id)
throw new \Exception(sprintf('File area [%s] is not in domain [%s] for FTN [%s]',$fao->name,$ao->zone->domain->name,$ao->ftn));
// Check that the user is subscribed
if (! $ao->fileareas->contains($fao->id))
throw new \Exception(sprintf('FTN [%s] is not subscribed to [%s]',$ao->ftn,$fao->name));
// Check that an FTN can read the area
if (! $fao->can_read($ao->security))
throw new \Exception(sprintf('FTN [%s] doesnt have permission to receive [%s]',$ao->ftn,$fao->name));
foreach (File::select('id')
->where('filearea_id',$fao->id)
->when($this->argument('file'),function($query) {
return $query->where('name','=',$this->argument('file'));
})
->orderBy('datetime')
->cursor() as $fo) {
// File hasnt been exported before
if (! $fo->seenby->count()) {
$fo->seenby()->attach($ao->id,['export_at'=>Carbon::now()]);
$this->info(sprintf('Exported [%d] to [%s]',$fo->id,$ao->ftn3d));
} else {
$export = $fo->seenby->where('id',$ao->id)->pop();
// File is pending export
if ($export && $export->pivot->export_at && is_null($export->pivot->sent_at) && is_null($export->pivot->sent_pkt)) {
$this->warn(sprintf('Not exporting [%d] already queued for [%s]',$fo->id,$ao->ftn3d));
// File has been exported
} elseif ($export) {
$fo->seenby()->updateExistingPivot($ao,['export_at'=>Carbon::now(),'sent_at'=>NULL]);
$this->info(sprintf('Re-exported [%d] to [%s]',$fo->id,$ao->ftn3d));
// File has not been exported
} else {
$fo->seenby()->attach($ao,['export_at'=>Carbon::now(),'sent_at'=>NULL]);
$this->info(sprintf('Exported [%d] to [%s]',$fo->id,$ao->ftn3d));
}
}
}
if ($this->option('queue'))
FilefixRescan::dispatch($ao,$fao,$this->argument('days'))->onQueue($this->option('queuename'));
else
FilefixRescan::dispatchSync($ao,$fao,$this->argument('days'));
return self::SUCCESS;
}

View File

@ -85,15 +85,15 @@ class AreafixRescan implements ShouldQueue
$latest = NULL;
foreach (Echomail::select(['id','datetime'])
->where('echoarea_id',$this->eao->id)
->where('datetime','>=',
Carbon::now()
->subDays($this->days)
->startOfDay()
)
->orderBy('datetime')
->cursor() as $eo) {
->where('echoarea_id',$this->eao->id)
->where('datetime','>=',
Carbon::now()
->subDays($this->days)
->startOfDay()
)
->orderBy('datetime')
->cursor() as $eo)
{
// Echomail hasnt been exported before
if (! $eo->seenby->count()) {
$eo->seenby()->attach($this->ao->id,['export_at'=>Carbon::now()]);

168
app/Jobs/FilefixRescan.php Normal file
View File

@ -0,0 +1,168 @@
<?php
namespace App\Jobs;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\Skip;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use App\Models\{Address,Filearea,File};
use App\Notifications\Netmails\Filefix\Scan;
class FilefixRescan implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private const LOGKEY = 'JAR';
private Address $ao; // System address
private Filearea $fao; // Domain we are processing
private int $days;
private bool $rescan;
/**
* Create a new job instance.
*/
public function __construct(Address $ao,Filearea $fao,int $days=30,bool $rescan=FALSE)
{
$this->ao = $ao->withoutRelations();
$this->fao = $fao->withoutRelations();
$this->days = $days;
$this->rescan = $rescan;
}
public function __get(string $key): mixed
{
switch ($key) {
case 'jobname':
return sprintf('%s %s (%d)',$this->ao->ftn,$this->fao->name,$this->days);
default:
$this->fail('Unkown key:'.$key);
return NULL;
}
}
public function middleware(): array
{
return [
Skip::when(function(): bool {
if ($this->fao->domain_id !== $this->ao->zone->domain_id) {
Log::error(sprintf('%s:! File area [%s] is not in domain [%s] for FTN [%s]',self::LOGKEY,$this->fao->name,$this->ao->zone->domain->name,$this->ao->ftn));
return TRUE;
// Check that the user is subscribed
} elseif (! $this->ao->fileareas->contains($this->fao->id)) {
Log::error(sprintf('%s:! FTN [%s] is not subscribed to [%s]',self::LOGKEY,$this->ao->ftn,$this->fao->name));
return TRUE;
// Check that an FTN can read the area
} elseif (! $this->fao->can_read($this->ao->security)) {
Log::error(sprintf('%s:! FTN [%s] doesnt have permission to receive [%s]',self::LOGKEY,$this->ao->ftn,$this->fao->name));
return TRUE;
}
return FALSE;
})
];
}
public function handle(): void
{
$c = 0;
$s = 0;
$earliest = NULL;
$latest = NULL;
foreach (File::select(['id','datetime','name'])
->where('filearea_id',$this->fao->id)
->where('datetime','>=',
Carbon::now()
->subDays($this->days)
->startOfDay()
)
->orderBy('datetime')
->cursor() as $fo)
{
// File hasnt been exported before
if (! $fo->seenby->where('address_id',$this->ao->id)->count()) {
$fo->seenby()->attach($this->ao->id,['export_at'=>Carbon::now()]);
$c++;
if (($fo->datetime < $earliest) || (! $earliest))
$earliest = $fo->datetime;
if (($latest < $fo->datetime) || (! $latest))
$latest = $fo->datetime;
Log::debug(sprintf('Exported [%d] FILE (%s) dated (%s) to [%s]',$fo->id,$fo->name,$fo->datetime->format('Y-m-d H:i:s'),$this->ao->ftn3d));
} else {
$export = $fo->seenby->where('id',$this->ao->id)->pop();
if ($export) {
// File is pending export
if ($export->pivot->export_at && is_null($export->pivot->sent_at) && is_null($export->pivot->sent_pkt)) {
$s++;
Log::debug(sprintf('Not exporting [%d] FILE (%s) dated (%s) already queued for [%s]',$fo->id,$fo->name,$fo->datetime->format('Y-m-d H:i:s'),$this->ao->ftn3d));
// File has been exported
} elseif ($this->rescan) {
$fo->seenby()->updateExistingPivot($this->ao,['export_at'=>Carbon::now(),'sent_at'=>NULL]);
$c++;
if (($fo->datetime < $earliest) || (! $earliest))
$earliest = $fo->datetime;
if (($latest < $fo->datetime) || (! $latest))
$latest = $fo->datetime;
Log::debug(sprintf('Re-exported [%d] FILE (%s) dated (%s) to [%s]',$fo->id,$fo->name,$fo->datetime,$this->ao->ftn3d));
} else {
$s++;
Log::debug(sprintf('Not resending previously sent file [%d], FILE (%s) - sent on [%s]',
$fo->id,
$fo->name,
$export->pivot->sent_at ?: '-',
));
}
// File has not been exported
} else {
$fo->seenby()->attach($this->ao,['export_at'=>Carbon::now(),'sent_at'=>NULL]);
$c++;
if (($fo->datetime < $earliest) || (! $earliest))
$earliest = $fo->datetime;
if (($latest < $fo->datetime) || (! $latest))
$latest = $fo->datetime;
Log::debug(sprintf('Exported [%d] to [%s]',$fo->id,$this->ao->ftn3d));
}
}
}
Notification::route('netmail',$this->ao)
->notify(new Scan(collect([
'area'=>$this->fao->name,
'queued'=>$c,
'skipped'=>$s,
'earliest'=>$earliest,
'latest'=>$latest,
])));
Log::info(sprintf('%s:= Queued [%d], Skipped [%d] files for [%s] in [%s]',self::LOGKEY,$c,$s,$this->ao->ftn,$this->fao->name));
}
}

View File

@ -50,7 +50,6 @@ class MailSend #implements ShouldQueue
->join('domains',['domains.id'=>'zones.domain_id'])
->groupBy('a.system_id','a.id','a.zone_id','addresses.region_id','a.host_id','a.node_id','a.point_id','addresses.hub_id','addresses.role')
->with(['system','zone.domain'])
->dontCache()
->get();
// Return the system we poll

View File

@ -233,13 +233,6 @@ class MessageProcess implements ShouldQueue
// Check for duplicate messages
// FTS-0009.001
if ($this->mo->msgid) {
$o = ($x=Echomail::where('msgid',$this->mo->msgid)
->where('fftn_id',$this->mo->fftn_id)
->where('datetime','>=',$this->mo->datetime->clone()->subYears(3))
->where('datetime','<=',$this->mo->datetime)
->dontCache())
->single();
Log::debug(sprintf('%s:- Checking for duplicate from host id [%d], with msgid [%s] between [%s] and [%s].',
self::LOGKEY,
$this->mo->fftn_id,
@ -248,6 +241,11 @@ class MessageProcess implements ShouldQueue
$this->mo->datetime,
));
$x = Echomail::where('msgid',$this->mo->msgid)
->where('fftn_id',$this->mo->fftn_id)
->where('datetime','>=',$this->mo->datetime->clone()->subYears(3))
->where('datetime','<=',$this->mo->datetime);
if ($x->count()) {
// @todo Actually update seenby
Log::alert(sprintf('%s:! Duplicate echomail (%s) in [%s] from (%s) [%s] to (%s) - ignoring.',
@ -270,7 +268,6 @@ class MessageProcess implements ShouldQueue
$o = Echomail::where('msg_crc',$xx=md5($this->mo->msg_crc))
->where('fftn_id',$this->mo->fftn_id)
->where('datetime','>',Carbon::now()->subWeek())
->dontCache()
->get();
if ($o->count())

View File

@ -559,7 +559,9 @@ class NodelistImport implements ShouldQueue
$no->load('addresses');
$remove = $zo
->addresses->pluck('id')
->addresses
->filter(fn($item)=>(! $item->point_id))
->pluck('id')
->diff($no->addresses->pluck('id'))
->diff(our_address($do)->pluck('id'))
->diff(our_nodes($do)->pluck('id'));

View File

@ -48,7 +48,7 @@ use App\Traits\{QueryCacheableConfig,ScopeActive};
class Address extends Model
{
use QueryCacheableConfig,ScopeActive,SoftDeletes;
use ScopeActive,SoftDeletes,QueryCacheableConfig;
private const LOGKEY = 'MA-';
@ -359,8 +359,8 @@ class Address extends Model
// We can only work out region/zone if we have a domain - this is for 2D parsing
if ($matches[5] ?? NULL) {
$o = new self;
$o->host_id = $matches[2];
$o->node_id = $matches[3];
$o->host_id = (int)$matches[2];
$o->node_id = (int)$matches[3];
$o->point_id = empty($matches[4]) ? 0 : (int)$matches[4];
if ($matches[1] !== "0") {
@ -639,7 +639,7 @@ class Address extends Model
public function nodes_hub(): HasMany
{
return $this->hasMany(Address::class,'hub_id','id')
->select(['id','addresses.zone_id','host_id','node_id','point_id','system_id'])
->select(['id','addresses.zone_id','region_id','host_id','node_id','point_id','system_id'])
->active()
->FTNorder()
->with([
@ -810,7 +810,7 @@ class Address extends Model
public function getIsHostedAttribute(): bool
{
return strlen($this->getPassSessionAttribute()) > 0;
return strlen($this->getPassSessionAttribute() ?: '') > 0;
}
public function getIsHoldAttribute(): bool
@ -943,60 +943,96 @@ class Address extends Model
}
/**
* Find the immediate children dependent on this record
* This is the children of this record, as per normal FTN routing ZC -> RC -> NC -> HUB -> Node -> Point
*
* This a ZC would return all records for the Zone,
* An RC would only return records in the region, etc
*
* @return Collection
* @see self::parent()
*/
public function children(): Collection
{
// If we are a point, our parent is the boss
switch ($this->role_id) {
case self::NODE_NN: // Normal Nodes -> Points
return $this->nodes_point;
case self::NODE_NN: // Normal Nodes
$o = self::active()
->where('zone_id',$this->zone_id)
->where('region_id',$this->region_id)
->where('host_id',$this->host_id)
->where('node_id',$this->node_id);
case self::NODE_HC: // Hubs -> Normal Nodes
return $this->nodes_hub;
break;
case self::NODE_NC: // Nets -> Normal Nodes, excluding Hub's Nodes
return $this->nodes_net->diff($this
->nodes_net
->filter(function($item) { return $item->role_id === Address::NODE_HC; })
->transform(function($item) { return $item->children(); })
->flatten());
case self::NODE_HC: // Hubs
$o = self::active()
->where('zone_id',$this->zone_id)
->where('region_id',$this->region_id)
->where('host_id',$this->host_id)
->where('hub_id',$this->id)
->where('id','<>',$this->id)
->get();
case self::NODE_RC: // Regions, excluding NC's Nodes
return $this->nodes_region->diff($this
->nodes_region
->filter(function($item) { return $item->role_id === Address::NODE_NC; })
->transform(function($item) { return $item->nodes_net; })
->flatten());
// Need to add in points of this hub's nodes
return $o->merge(
self::active()
->where('zone_id',$this->zone_id)
->where('region_id',$this->region_id)
->where('host_id',$this->host_id)
->whereIn('node_id',$o->pluck('node_id'))
->where('point_id','<>',0)
->get()
);
case self::NODE_ZC: // Zones, excluding RC's Nodes
return $this->nodes_zone->diff($this
->nodes_zone
->filter(function($item) { return $item->role_id === Address::NODE_RC; })
->transform(function($item) { return $item->nodes_region; })
->flatten());
case self::NODE_NC: // Nets
$o = self::active()
->where('zone_id',$this->zone_id)
->where('region_id',$this->region_id)
->where('host_id',$this->host_id);
break;
case self::NODE_RC: // Regions
$o = self::active()
->where('zone_id',$this->zone_id)
->where('region_id',$this->region_id);
break;
case self::NODE_ZC: // Zone
$o = self::active()
->where('zone_id',$this->zone_id);
break;
default:
return new Collection;
}
return new Collection;
return $o
->where('id','<>',$this->id)
->get();
}
/**
* Find who we should forward mail onto, taking into account session details that we have
* Contrast to children(), this takes into account authentication details, and we route mail to this
* address (because we have session details) and it's children.
*
* @return Collection
* @throws \Exception
* @see self::children()
* @see self::uplink()
*/
public function downlinks(): Collection
{
// We have no session data for this address, by definition it has no children
// We have no session data for this address, (and its not our address), by definition it has no children
if (! $this->is_hosted && (! our_address()->pluck('id')->contains($this->id)))
return new Collection;
// If this system is not marked to default route for this address
if (! $this->is_default_route) {
$children = $this->children();
$children = $this->children()
->push($this);
// We route everything for this domain
} else {
@ -1016,28 +1052,17 @@ class Address extends Model
// Exclude links and their children.
$exclude = collect();
foreach (our_nodes($this->zone->domain)->merge(our_address($this->zone->domain)) as $o) {
foreach (our_nodes($this->zone->domain)->diff([$this]) as $o) {
// We only exclude downlink children
if ($o->role_id < $this->role_id)
continue;
// If this address is in our list, remove it and it's children
if ($children->contains($o)) {
$exclude = $exclude->merge($o->children());
$exclude->push($o);
}
$exclude = $exclude->merge($o->children());
$exclude->push($o);
}
return $children->filter(function($item) use ($exclude) { return ! $exclude->pluck('id')->contains($item->id);});
}
/**
* List of all our nodes and their children
*
* @return \Illuminate\Support\Collection
* @throws \Exception
*/
public function downstream(): \Illuminate\Support\Collection
{
return $this->downlinks()->transform(function($item) {
return $item->nodes()->push($item);
})->flatten();
return $children->diff($exclude);
}
/**
@ -1073,11 +1098,10 @@ class Address extends Model
'origin:id,value',
'echoarea:id,name,domain_id',
'echoarea.domain:id,name',
'fftn:id,zone_id,host_id,node_id,point_id',
'fftn:id,zone_id,region_id,host_id,node_id,point_id',
'fftn.zone:id,domain_id,zone_id',
'fftn.zone.domain:id,name',
])
->dontCache();
]);
}
/**
@ -1097,7 +1121,7 @@ class Address extends Model
->with([
'filearea:id,name,domain_id',
'filearea.domain:id,name',
'fftn:id,zone_id,host_id,node_id,point_id',
'fftn:id,zone_id,region_id,host_id,node_id,point_id',
'fftn.zone:id,domain_id,zone_id',
'fftn.zone.domain:id,name',
])
@ -1137,7 +1161,7 @@ class Address extends Model
$role = self::NODE_ZC;
}
if (is_null($role))
if (isset($this->region_id) && is_null($role))
Log::alert(sprintf('%s:! Address ROLE [%d] could not be determined for [%s]',self::LOGKEY,($this->role & Address::NODE_ALL),$this->ftn));
return $role;
@ -1166,6 +1190,21 @@ class Address extends Model
return NULL;
}
public function getFiles(): Collection
{
if ($count=($num=$this->filesWaiting())->count()) {
Log::info(sprintf('%s:= Got [%d] files for [%s] for sending',self::LOGKEY,$count,$this->ftn));
// Limit to max messages
if ($count > $this->system->batch_files)
Log::notice(sprintf('%s:= Only sending [%d] files for [%s]',self::LOGKEY,$this->system->batch_files,$this->ftn));
return $num->take($this->system->batch_files);
}
return new Collection;
}
/**
* Get netmail for this node (including it's children)
*
@ -1231,8 +1270,8 @@ class Address extends Model
// Addresses that our downstream of this address, except anybody that has session details with us
$ours = our_nodes($this->zone->domain)->pluck('id');
$addresses = $this->downstream()
->filter(fn($item)=>! $ours->contains($item->id))
$addresses = $this->downlinks()
->filter(fn($item)=>(! $ours->contains($item->id)))
->merge($this->system->match($this->zone,Address::NODE_ALL));
$netmails = $this
@ -1291,18 +1330,20 @@ class Address extends Model
}
/**
* Find the immediate parent for this node.
* Find the immediate parent for this address, as per normal FTN routing ZC <- RC <- NC <- HUB <- Node <- Point
*
* @return Address|null
* @throws \Exception
* @see self::children()
*/
public function parent(): ?Address
{
// If we are a point, our parent is the boss
switch ($this->role_id) {
case self::NODE_POINT: // BOSS Node
return Address::active()
return self::active()
->where('zone_id',$this->zone_id)
->where('region_id',$this->region_id)
->where('host_id',$this->host_id)
->where('node_id',$this->node_id)
->where('point_id',0)
@ -1314,16 +1355,17 @@ class Address extends Model
// Else fall through
case self::NODE_HC: // RC
return Address::active()
case self::NODE_HC: // NC
return self::active()
->where('zone_id',$this->zone_id)
->where('region_id',$this->region_id)
->where('host_id',$this->host_id)
->where('node_id',0)
->where('point_id',0)
->single();
case self::NODE_NC: // RC
return Address::active()
return self::active()
->where('zone_id',$this->zone_id)
->where('region_id',$this->region_id)
->where('host_id',$this->region_id)
@ -1332,7 +1374,7 @@ class Address extends Model
->single();
case self::NODE_RC: // ZC
return Address::active()
return self::active()
->where('zone_id',$this->zone_id)
->where('region_id',0)
->where('node_id',0)
@ -1356,10 +1398,13 @@ class Address extends Model
}
/**
* Get the appropriate parent for this address, taking into account who we have session information with
* Contrast to parent(), this takes into account authentication details, and we route mail to this
* address (because we have session details) with that uplink.
*
* @return Address|$this|null
* @throws \Exception
* @see self::parent()
* @see self::downlinks()
*/
public function uplink(): ?Address
{
@ -1371,15 +1416,17 @@ class Address extends Model
if ($this->is_hosted)
return $this;
// Traverse up our parents until we have one with session details
if ($x=$this->parent()?->uplink()) {
return $x;
// See if we have a node registered as the default route for this zone
} else {
$sz = SystemZone::whereIn('zone_id',$this->domain->zones->pluck('id'))
->where('default',TRUE)
->single();
return $sz?->system->addresses->sortBy('security')->last();
return $sz?->system->akas->sortBy('security')->last();
}
}
}

View File

@ -13,11 +13,11 @@ use App\Classes\FTN\Message;
use App\Events\Echomail as EchomailEvent;
use App\Interfaces\Packet;
use App\Models\Casts\{CompressedStringOrNull,CollectionOrNull,UTF8StringOrNull};
use App\Traits\{MessageAttributes,MsgID,ParseAddresses,QueryCacheableConfig};
use App\Traits\{MessageAttributes,MsgID,ParseAddresses};
final class Echomail extends Model implements Packet
{
use SoftDeletes,MessageAttributes,MsgID,ParseAddresses,QueryCacheableConfig;
use SoftDeletes,MessageAttributes,MsgID,ParseAddresses;
private const LOGKEY = 'ME-';
public const UPDATED_AT = NULL;
@ -289,7 +289,7 @@ final class Echomail extends Model implements Packet
public function seenby()
{
return $this->belongsToMany(Address::class,'echomail_seenby')
->select(['id','zone_id','host_id','node_id'])
->select(['addresses.id','zone_id','host_id','node_id'])
->withPivot(['export_at','sent_at','sent_pkt'])
->dontCache()
->FTN2DOrder();

View File

@ -190,6 +190,7 @@ class File extends Model
public function seenby()
{
return $this->belongsToMany(Address::class,'file_seenby')
->dontCache()
->ftnOrder();
}

View File

@ -32,6 +32,7 @@ class Setup extends Model
public const O_DNS = 1<<3; /* List for DNS */
public const O_HIDEAKA = 1<<4; /* Hide AKAs to different Zones */
public const MAX_BATCH_FILES = 5;
public const MAX_MSGS_PKT = 50;
protected $casts = [
@ -68,6 +69,9 @@ class Setup extends Model
case 'emsi_options':
return Arr::get($this->servers,str_replace('_','.',$key));
case 'batch_files':
return Arr::get($this->options,$key,self::MAX_BATCH_FILES);
case 'msgs_pkt':
return Arr::get($this->options,$key,self::MAX_MSGS_PKT);

View File

@ -184,21 +184,26 @@ class System extends Model
}
}
public function getBatchFilesAttribute(?int $val): int
{
return $val ?: Setup::findOrFail(config('app.id'))->batch_files;
}
public function getIsOwnedAttribute(): bool
{
return $this->users->count();
}
public function getPktMsgsAttribute(?int $val): int
{
return $val ?: Setup::findOrFail(config('app.id'))->msgs_pkt;
}
public function getLastSeenAttribute(): ?Carbon
{
return $this->logs_recent->first()?->created_at;
}
public function getPktMsgsAttribute(?int $val): int
{
return $val ?: Setup::findOrFail(config('app.id'))->msgs_pkt;
}
/* METHODS */
public function addresses_common(): Collection

View File

@ -12,12 +12,12 @@ class AreaList extends Netmails
{
use MessagePath,PageTemplate;
private const LOGKEY = 'ACH';
private const LOGKEY = 'ACL';
private Netmail $mo;
/**
* Reply to a areafix, commands unknown.
* Reply to a areafix AREALIST commands.
*
* @param Netmail $mo
*/

View File

@ -16,7 +16,7 @@ class CommandsProcessed extends Netmails
private const LOGKEY = 'ACU';
/**
* Reply to a areafix, commands unknown.
* Reply to a areafix commands.
*
* @param Netmail $mo
* @param Collection $commands

View File

@ -16,7 +16,7 @@ class Scan extends Netmails
private const LOGKEY = 'ACS';
/**
* Reply to a areafix, commands unknown.
* Notification on a (re)scan request.
*
* @param Collection $result
*/

View File

@ -0,0 +1,103 @@
<?php
namespace App\Notifications\Netmails\Filefix;
use Illuminate\Support\Facades\Log;
use App\Notifications\Netmails;
use App\Models\Netmail;
use App\Traits\{MessagePath,PageTemplate};
class AreaList extends Netmails
{
use MessagePath,PageTemplate;
private const LOGKEY = 'FCL';
private Netmail $mo;
/**
* Reply to a filefix AREALIST commands.
*
* @param Netmail $mo
*/
public function __construct(Netmail $mo)
{
parent::__construct();
$this->mo = $mo->withoutRelations();
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return Netmail
* @throws \Exception
*/
public function toNetmail(object $notifiable): Netmail
{
$o = $this->setupNetmail($notifiable);
$ao = $notifiable->routeNotificationFor(static::via);
Log::info(sprintf('%s:+ Responding to filefix for a node [%s] LIST processed',self::LOGKEY,$ao->ftn));
$o->to = $this->mo->from;
$o->replyid = $this->mo->msgid;
$o->subject = 'Filefix - List';
// Message
$msg = $this->page(FALSE,'Filefix');
$msg->addText("Here are the list of available filereas:\r\r\r\r");
$areas = $ao->domain
->fileareas
->filter(fn($item)=>$item->active && ($item->can_read($ao->security) || $item->can_write($ao->security)));
if ($areas->count()) {
$msg->addText(sprintf(":---:-%s-:-%s-:-%s-:\r",
str_repeat('-',10),
str_repeat('-',48),
str_repeat('-',5),
));
$msg->addText(sprintf(": : %-10s : %-48s : %-5s :\r",'AREA','DESCRIPTION','FILES'));
$msg->addText(sprintf(":---:-%s-:-%s-:-%s-:\r",
str_repeat('-',10),
str_repeat('-',48),
str_repeat('-',5),
));
foreach ($areas as $eao) {
$msg->addText(sprintf(":%s%s%s: %-10s : %-48s : %5d :\r",
($x=$ao->fileareas->contains($eao)) ? '*' : ' ',
(! $x ? '+' : ' '),
($eao->can_read($ao->security) && (! $eao->can_write($ao->security)))
? 'R'
: (((! $eao->can_read($ao->security)) && $eao->can_write($ao->security)) ? 'W' : ' '),
$eao->name,
$eao->description,
$eao->files()->count(),
));
}
$msg->addText(sprintf(":---:-%s-:-%s-:-%s-:\r",
str_repeat('-',10),
str_repeat('-',48),
str_repeat('-',5),
));
$msg->addText("\r'*' = Subscribed, '+' = available, 'R' = read only, 'W' = write only\r");
} else {
$msg->addText(sprintf('No areas available to you from domain [%s]',$ao->domain->name));
}
$o->msg = $msg->render();
$o->set_tagline = 'Why did the chicken cross the road? The robot programmed it.';
$o->save();
return $o;
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace App\Notifications\Netmails\Filefix;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use App\Notifications\Netmails;
use App\Models\Netmail;
use App\Traits\{MessagePath,PageTemplate};
class CommandsProcessed extends Netmails
{
use MessagePath,PageTemplate;
private const LOGKEY = 'ACU';
/**
* Reply to a files commands.
*
* @param Netmail $mo
* @param Collection $commands
*/
public function __construct(private Netmail $mo,private Collection $commands)
{
parent::__construct();
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return Netmail
* @throws \Exception
*/
public function toNetmail(object $notifiable): Netmail
{
$o = $this->setupNetmail($notifiable);
$ao = $notifiable->routeNotificationFor(static::via);
Log::info(sprintf('%s:+ Responding to filefix for a node [%s] COMMANDS PROCESSED',self::LOGKEY,$ao->ftn));
$o->to = $this->mo->from;
$o->replyid = $this->mo->msgid;
$o->subject = 'Filefix - Result';
// Message
$msg = $this->page(FALSE,'Filefix');
$msg->addText("Your filefix request has been received, here is the result:\r\r");
foreach ($this->commands as $command) {
$msg->addText("$command\r");
}
$msg->addText("\r");
$msg->addText($this->message_path($this->mo));
$o->msg = $msg->render();
$o->set_tagline = 'Why did the robot cross the road? The chicken programmed it.';
$o->save();
return $o;
}
}

View File

@ -0,0 +1,103 @@
<?php
namespace App\Notifications\Netmails\Filefix;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use App\Notifications\Netmails;
use App\Models\{Filearea,Netmail};
use Illuminate\Support\Str;
use App\Traits\{MessagePath,PageTemplate};
class Filelist extends Netmails
{
use MessagePath,PageTemplate;
private const LOGKEY = 'FCL';
private Filearea $fa;
private int $days;
/**
* Reply to a filefix FILELIST commands.
*
* @param Filearea $fa
* @param int $days
*/
public function __construct(Netmail $mo,Filearea $fa,int $days=30)
{
parent::__construct();
$this->mo = $mo->withoutRelations();
$this->fa = $fa->withoutRelations();
$this->days = $days;
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return Netmail
* @throws \Exception
*/
public function toNetmail(object $notifiable): Netmail
{
$o = $this->setupNetmail($notifiable);
$ao = $notifiable->routeNotificationFor(static::via);
Log::info(sprintf('%s:+ Responding to filefix [%s] FILE LIST processed',self::LOGKEY,$ao->ftn));
$o->to = $this->mo->from;
$o->replyid = $this->mo->msgid;
$o->subject = 'Filefix - File List';
// Message
$msg = $this->page(FALSE,'Filefix');
$msg->addText(sprintf("Filearea [%s] has the following files in the last %d days:\r\r",$this->fa->name,$this->days));
$files = $this->fa
->files()
->orderBy('datetime')
->where('datetime','>',Carbon::now()->subDays($this->days)->startOfDay());
if ($files->count()) {
$msg->addText(sprintf(":-%s-:-%s-:-%s-:\r",
str_repeat('-',15),
str_repeat('-',44),
str_repeat('-',8),
));
$msg->addText(sprintf(": %-15s : %-44s : %8s :\r",'FILE','DESCRIPTION','SIZE(mb)'));
$msg->addText(sprintf(":-%s-:-%s-:-%s-:\r",
str_repeat('-',15),
str_repeat('-',44),
str_repeat('-',8),
));
foreach ($files->get() as $fo) {
$msg->addText(sprintf(": %-15s : %-44s : %8s :\r",
$fo->name,
Str::limit($fo->desc,44-3),
number_format($fo->size/1024/1024,3),
));
}
$msg->addText(sprintf(":-%s-:-%s-:-%s-:\r",
str_repeat('-',15),
str_repeat('-',44),
str_repeat('-',8),
));
} else {
$msg->addText(sprintf('No files in [%s]',$this->fa->name));
}
$o->msg = $msg->render();
$o->set_tagline = 'Why did the robot cross the road? The chicken programmed it.';
$o->save();
return $o;
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace App\Notifications\Netmails\Filefix;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use App\Notifications\Netmails;
use App\Models\Netmail;
use App\Traits\{MessagePath,PageTemplate};
class Scan extends Netmails
{
use MessagePath,PageTemplate;
private const LOGKEY = 'FCS';
/**
* Notification on a (re)scan request.
*
* @param Collection $result
*/
public function __construct(private Collection $result)
{
parent::__construct();
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return Netmail
* @throws \Exception
*/
public function toNetmail(object $notifiable): Netmail
{
$o = $this->setupNetmail($notifiable);
$ao = $notifiable->routeNotificationFor(static::via);
Log::info(sprintf('%s:+ Responding to filefix for a node [%s] SCAN processed',self::LOGKEY,$ao->ftn));
$o->subject = 'Filefix - Scan Results';
// Message
$msg = $this->page(FALSE,'Filefix');
$msg->addText("A filefix (re)Scan has completed:\r\r");
$msg->addText(sprintf("Area: %s, Queued: %d, Skipped: %d\r\r",
$this->result->get('area'),
$this->result->get('queued'),
$this->result->get('skipped'),
));
if ($x=$this->result->get('earliest'))
$msg->addText(sprintf("The earliest file being sent: %s.\r",$x));
if (($x=$this->result->get('latest')) && ($this->result->get('earliest') !== $x))
$msg->addText(sprintf("The latest file being sent: %s.\r",$x));
$o->msg = $msg->render();
$o->set_tagline = 'Why did the chicken cross the road? The robot programmed it.';
$o->save();
return $o;
}
}

View File

@ -11,7 +11,7 @@ trait QueryCacheableConfig
{
use QueryCacheable;
public $cacheFor = 900; // cache time, in seconds
public $cacheFor = 30; // cache time, in seconds
protected static $flushCacheOnUpdate = TRUE;
public $cacheDriver = 'memcached';
}

View File

@ -2,42 +2,39 @@
namespace Database\Seeders;
/**
* Our testing heirarchy.
*
*/
use Carbon\Carbon;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use App\Models\{Address, Domain, Setup, System, Zone};
use App\Models\{Address,Domain,Setup,System,Zone};
class TestNodeHierarchy extends Seeder
{
public const DEBUG=FALSE;
public const DEBUG = TRUE;
/**
* Run the database seeds.
*
* @return void
*/
/**
* Run the database seeds.
*
* @return void
* @throws \Exception
*/
public function run()
{
DB::table('domains')
->insert([
'name'=>'a',
'active'=>TRUE,
'public'=>TRUE,
'created_at'=>Carbon::now(),
'updated_at'=>Carbon::now(),
]);
foreach (['a','b','c','d','e','f'] as $domain) {
DB::table('domains')
->insert([
'name'=>$domain,
'active'=>TRUE,
'public'=>TRUE,
'created_at'=>Carbon::now(),
'updated_at'=>Carbon::now(),
]);
DB::table('domains')
->insert([
'name'=>'b',
'active'=>TRUE,
'public'=>TRUE,
'created_at'=>Carbon::now(),
'updated_at'=>Carbon::now(),
]);
foreach (['a','b'] as $domain) {
$do = Domain::where('name',$domain)->sole();
$this->hierarchy($do,100);
$this->hierarchy($do,101);
@ -45,9 +42,38 @@ class TestNodeHierarchy extends Seeder
// Configure my addresses
$so = Setup::findOrFail(config('app.id'));
// ZC 100:0/0@a
$ao = Address::findFTN('100:0/0@a');
$so->system_id = $ao->system_id;
$so->save();
$ao->system_id = $so->system_id;
$ao->save();
// RC 100:1/0@b
$ao = Address::findFTN('100:1/0@b');
$ao->system_id = $so->system_id;
$ao->save();
// NC 100:20/0@c
$ao = Address::findFTN('100:20/0@c');
$ao->system_id = $so->system_id;
$ao->save();
// HUB 100:30/100@d
$ao = Address::findFTN('100:30/100@d');
$ao->system_id = $so->system_id;
$ao->save();
// NODE 100:40/101@e
$ao = Address::findFTN('100:40/101@e');
$ao->system_id = $so->system_id;
$ao->save();
// POINT 100:50/101.3277@f
$ao = Address::createFTN('100:50/101.3277@f',$so->system);
$so->system->name = 'Clearing Houz TEST';
$so->system->address = 'localhost';
$so->system->save();
// Add file area
DB::table('fileareas')
@ -66,9 +92,7 @@ class TestNodeHierarchy extends Seeder
private function hierarchy(Domain $domain,int $zoneid)
{
$hosts = [1,2,3,4,5];
$hubs = [10,20,30,40,50];
$nodes = [100,200,300,400,500];
$levels = [1,2,3,4,5];
$hubnodes = [-2,-1,+1,+2,+3];
$so = $this->system(sprintf('ZC %s-%s',$domain->name,$zoneid));
@ -85,185 +109,196 @@ class TestNodeHierarchy extends Seeder
]);
$zo = Zone::where('zone_id',$zoneid)->where('domain_id',$domain->id)->sole();
if (self::DEBUG)
dump(['zo'=>$zo->zone_id,'rid'=>0,'hid'=>0,'nid'=>0]);
// ZC
if (self::DEBUG)
printf("- ZC %d:%d/%d.%d@%s\n",$zo->zone_id,0,0,0,$domain->name);
$region_id = 0;
// ZC Address
DB::table('addresses')
->insert([
'zone_id'=>$zo->id,
'active'=>TRUE,
'validated'=>TRUE,
'region_id'=>0,
'host_id'=>0,
'region_id'=>$region_id,
'host_id'=>$region_id,
'node_id'=>0,
'point_id'=>0,
'system_id'=>$so->id,
'role'=>Address::NODE_ZC,
'created_at'=>Carbon::now(),
'updated_at'=>Carbon::now(),
]);
// ZC Nodes
foreach ($nodes as $nid) {
if (self::DEBUG)
dump(['rid'=>$zo->zone_id,'hid'=>$zo->zone_id,'nid'=>$nid]);
foreach ($hubnodes as $hnid) {
$host_id = $region_id*100+$hnid+3;
$so = $this->system(sprintf('ZC Node 0/%d',$nid));
if (self::DEBUG)
printf(" - NODE %d:%d/%d.%d@%s\n",$zo->zone_id,$region_id,$host_id,0,$domain->name);
$so = $this->system(sprintf('ZC Node 0/%d',$host_id));
DB::table('addresses')
->insert([
'zone_id'=>$zo->id,
'active'=>TRUE,
'validated'=>TRUE,
'region_id'=>0,
'host_id'=>0,
'node_id'=>$nid,
'region_id'=>$region_id,
'host_id'=>$region_id,
'node_id'=>$host_id,
'point_id'=>0,
'system_id'=>$so->id,
'role'=>Address::NODE_NN,
'created_at'=>Carbon::now(),
'updated_at'=>Carbon::now(),
]);
}
if (self::DEBUG)
dump(['end'=>'nodes top']);
// Regions
foreach ($hosts as $rid) {
$hostid = $rid;
// RC
foreach ($levels as $region_id) {
if (self::DEBUG)
dump(['rid'=>$rid,'hid'=>$hostid,'nid'=>0]);
printf(" - RC %d:%d/%d.%d@%s\n",$zo->zone_id,$region_id,0,0,$domain->name);
$so = $this->system(sprintf('Region %03d:%03d/%03d.0@%s',$zoneid,$rid,0,$domain->name));
$so = $this->system(sprintf('RC %03d:%03d/%03d.0@%s',$zoneid,$region_id,0,$domain->name));
DB::table('addresses')
->insert([
'zone_id'=>$zo->id,
'active'=>TRUE,
'validated'=>TRUE,
'region_id'=>$rid,
'host_id'=>$rid,
'region_id'=>$region_id,
'host_id'=>$region_id,
'node_id'=>0,
'point_id'=>0,
'system_id'=>$so->id,
'role'=>Address::NODE_RC,
'created_at'=>Carbon::now(),
'updated_at'=>Carbon::now(),
]);
// RC Nodes
foreach ($nodes as $nid) {
if (self::DEBUG)
dump(['rid'=>$rid,'hid'=>$hostid,'nid'=>$nid]);
foreach ($hubnodes as $hnid) {
$host_id = $hnid+3;
$so = $this->system(sprintf('RC Node %d/%d',$rid,$nid));
if (self::DEBUG)
printf(" - NODE %d:%d/%d.%d@%s\n",$zo->zone_id,$region_id,$host_id,0,$domain->name);
$so = $this->system(sprintf('RC Node %d/%d',$region_id,$host_id));
DB::table('addresses')
->insert([
'zone_id'=>$zo->id,
'active'=>TRUE,
'validated'=>TRUE,
'region_id'=>$rid,
'host_id'=>$rid,
'node_id'=>$nid,
'region_id'=>$region_id,
'host_id'=>$region_id,
'node_id'=>$host_id,
'point_id'=>0,
'system_id'=>$so->id,
'role'=>Address::NODE_NN,
'created_at'=>Carbon::now(),
'updated_at'=>Carbon::now(),
]);
}
if (self::DEBUG)
dump(['end'=>'NODES regions']);
// Hosts
foreach ($hosts as $rrid) {
$hostid = $rid*10+$rrid-1;
// NC
foreach ($levels as $ncid) {
$net_id = $region_id*10+$ncid-1;
if (self::DEBUG)
dump(['rid'=>$rid,'hid'=>$hostid,'nid'=>0]);
$so = $this->system(sprintf('Host %d:%d/0 (R%d)',$zoneid,$hostid,$rid));
printf(" - NC %d:%d/%d.%d@%s\n",$zo->zone_id,$net_id,0,0,$domain->name);
$so = $this->system(sprintf('NC %d:%d/0 (R%d)',$zo->zone_id,$net_id,$region_id));
DB::table('addresses')
->insert([
'zone_id'=>$zo->id,
'active'=>TRUE,
'validated'=>TRUE,
'region_id'=>$rid,
'host_id'=>$hostid,
'region_id'=>$region_id,
'host_id'=>$net_id,
'node_id'=>0,
'point_id'=>0,
'system_id'=>$so->id,
'role'=>Address::NODE_NC,
'created_at'=>Carbon::now(),
'updated_at'=>Carbon::now(),
]);
// Nodes
foreach ($nodes as $nid) {
if (self::DEBUG)
dump(['rid'=>$rid,'hid'=>$hostid,'nid'=>$nid]);
// NC Nodes
foreach ($hubnodes as $hnid) {
$host_id = $hnid+3;
$so = $this->system(sprintf('Host Node %d/%d (R%d)',$hostid,$nid,$rid));
if (self::DEBUG)
printf(" - NODE %d:%d/%d.%d@%s\n",$zo->zone_id,$net_id,$host_id,0,$domain->name);
$so = $this->system(sprintf('NC Node %d/%d (R%d)',$net_id,$host_id,$region_id));
DB::table('addresses')
->insert([
'zone_id'=>$zo->id,
'active'=>TRUE,
'validated'=>TRUE,
'region_id'=>$rid,
'host_id'=>$hostid,
'node_id'=>$nid,
'region_id'=>$region_id,
'host_id'=>$net_id,
'node_id'=>$host_id,
'point_id'=>0,
'system_id'=>$so->id,
'role'=>Address::NODE_NN,
'created_at'=>Carbon::now(),
'updated_at'=>Carbon::now(),
]);
}
// Hubs
foreach ($hubs as $bid) {
$so = $this->system(sprintf('HUB %d/%d (R%d)',$hostid,$bid,$rid));
foreach ($levels as $hbid) {
$host_id = $hbid*100;
if (self::DEBUG)
printf(" - HUB %d:%d/%d.%d@%s\n",$zo->zone_id,$net_id,$host_id,0,$domain->name);
$so = $this->system(sprintf('HUB %d/%d (R%d)',$net_id,$host_id,$region_id));
$hub = new Address;
$hub->zone_id = $zo->id;
$hub->active = TRUE;
$hub->region_id = $rid;
$hub->host_id = $hostid;
$hub->node_id = $bid;
$hub->region_id = $region_id;
$hub->host_id = $net_id;
$hub->node_id = $host_id;
$hub->point_id = 0;
$hub->system_id = $so->id;
$hub->role = Address::NODE_HC;
$hub->created_at = Carbon::now();
$hub->updated_at = Carbon::now();
$hub->save();
// Nodes
// HUB Nodes
foreach ($hubnodes as $nid) {
$nodeid = $bid+$nid;
$so = $this->system(sprintf('Hub Node %d/%d (R%d/H%d)',$hostid,$nodeid,$rid,$hub->node_id));
$host_id = $hub->node_id+$nid;
if (self::DEBUG)
printf(" - NODE %d:%d/%d.%d@%s\n",$zo->zone_id,$net_id,$host_id,0,$domain->name);
$so = $this->system(sprintf('Hub Node %d/%d (R%d/H%d)',$net_id,$host_id,$region_id,$hub->node_id));
DB::table('addresses')
->insert([
'zone_id'=>$zo->id,
'active'=>TRUE,
'validated'=>TRUE,
'region_id'=>$rid,
'host_id'=>$hostid,
'node_id'=>$nodeid,
'region_id'=>$region_id,
'host_id'=>$net_id,
'node_id'=>$host_id,
'point_id'=>0,
'system_id'=>$so->id,
'hub_id'=>$hub->id,
'role'=>Address::NODE_NN,
'created_at'=>Carbon::now(),
'updated_at'=>Carbon::now(),
]);
foreach ($hubnodes as $nid) {
$point_id = $nid+3;
if (self::DEBUG)
printf(" - POINT %d:%d/%d.%d@%s\n",$zo->zone_id,$net_id,$host_id,$point_id,$domain->name);
$so = $this->system(sprintf('Node Point %d/%d.%d (R%d/H%d)',$net_id,$host_id,$point_id,$region_id,$hub->node_id));
DB::table('addresses')
->insert([
'zone_id'=>$zo->id,
'active'=>TRUE,
'region_id'=>$region_id,
'host_id'=>$net_id,
'node_id'=>$host_id,
'point_id'=>$point_id,
'system_id'=>$so->id,
'created_at'=>Carbon::now(),
'updated_at'=>Carbon::now(),
]);
}
}
}
}
if (self::DEBUG)
dump(['end'=>'NODES normal']);
}
if (self::DEBUG)
dump(['end'=>'heirarchy']);
}
private function system(string $name): System
@ -275,8 +310,9 @@ class TestNodeHierarchy extends Seeder
$o->active = TRUE;
$o->created_at = Carbon::now();
$o->updated_at = Carbon::now();
$o->address = 'myhostname';
$o->save();
return $o;
}
}
}

View File

@ -71,7 +71,7 @@
<div id="collapse_item_{{ $loop->parent->index }}_{{ $loop->index }}" class="accordion-collapse collapse @if($loop->parent->first)show @endif" aria-labelledby="packet" data-bs-parent="#accordion_packet">
<div class="accordion-body">
<p>Packet <strong class="highlight">{{ $file }}</strong> (type <strong class="highlight">{{ $result->type }}</strong>) is from <strong class="highlight">{{ $result->fftn->ftn }}</strong> to <strong class="highlight">{{ $result->tftn->ftn }}</strong>, dated <strong class="highlight">{{ $result->date }}</strong>.</p>
<p>Packet <strong class="highlight">{{ $file }}</strong> (type <strong class="highlight">{{ $result->type }}</strong>) is from <strong class="highlight">{{ $result->fftn ? $result->fftn->ftn : $result->fftn_t }}</strong> to <strong class="highlight">{{ $result->tftn ? $result->tftn->ftn : $result->tftn_t }}</strong>, dated <strong class="highlight">{{ $result->date }}</strong>.</p>
<p>This packet has <strong class="highlight">{{ $result->messages->count() }}</strong> messages and <strong class="highlight">{{ $result->password ? 'DOES' : 'does NOT' }}</strong> have a password.</p>
<p>Tosser: <strong class="highlight">{{ $result->software->code }}</strong> (<strong class="highlight">{{ $result->software->name }}</strong>), version <strong class="highlight">{{ $result->software_ver }}</strong>. Capabilities: <strong class="highlight">{{ $result->capability }}</strong>.</p>
@if ($result->messages->count() > 1)
@ -112,10 +112,10 @@
<div class="row pb-2">
<div class="col-4">
FROM: <strong class="highlight">{!! Message::tr($msg->from) !!}</strong> (<strong class="highlight">{{ $msg->fftn->ftn }}</strong>)
FROM: <strong class="highlight">{!! Message::tr($msg->from) !!}</strong> (<strong class="highlight">{{ $msg->fftn ? $msg->fftn->ftn : $msg->fftn_t}}</strong>)
</div>
<div class="col-4">
TO: <strong class="highlight">{!! Message::tr($msg->to) !!}</strong>@if($msg instanceof Netmail) (<strong class="highlight">{{ $msg->tftn->ftn }}</strong>) @endif
TO: <strong class="highlight">{!! Message::tr($msg->to) !!}</strong>@if($msg instanceof Netmail) (<strong class="highlight">{{ $msg->tftn ? $msg->tftn->ftn : $msg->tftn_t }}</strong>) @endif
</div>
</div>

View File

@ -109,7 +109,7 @@ class PacketTest extends TestCase
$f = $this->mail_file('mail/018A-1702393055-78754407.pkt');
foreach ($f as $packet) {
$pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize(),$this->do);
$pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize());
$this->assertInstanceOf(Packet\FSC39::class,$pkt);
$this->assertEquals('ABCDEFGH',$pkt->password);
@ -136,7 +136,7 @@ class PacketTest extends TestCase
$f = $this->mail_file('mail/018A-1701955391-71c7a400.pkt');
foreach ($f as $packet) {
$pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize(),$this->do);
$pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize());
$this->assertInstanceOf(Packet\FSC39::class,$pkt);
$this->assertEquals('ABCDEFGH',$pkt->password);
@ -163,7 +163,7 @@ class PacketTest extends TestCase
$f = $this->mail_file('mail/0B39-1701919239-65713a06.pkt');
foreach ($f as $packet) {
$pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize(),$this->do);
$pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize());
$this->assertInstanceOf(Packet\FSC48::class,$pkt);
$this->assertEquals('ABCDEFG#',$pkt->password);
@ -246,7 +246,7 @@ class PacketTest extends TestCase
// This packet has an incorrect zone in the Origin
$f = $this->mail_file('mail/test_msgid_origin.pkt');
foreach ($f as $packet) {
$pkt = Packet::process($packet,$f->itemName(),$f->itemSize(),$this->do);
$pkt = Packet::process($packet,$f->itemName(),$f->itemSize());
$this->assertInstanceOf(Packet\FSC39::class,$pkt);
$this->assertEquals(1,$pkt->count());
@ -278,7 +278,7 @@ class PacketTest extends TestCase
// This packet has a SOH<char>SOH sequence
$f = $this->mail_file('mail/test_binary_content-2.pkt');
foreach ($f as $packet) {
$pkt = Packet::process($packet,$f->itemName(),$f->itemSize(),$this->do);
$pkt = Packet::process($packet,$f->itemName(),$f->itemSize());
$this->assertEquals(1,$pkt->count());

View File

@ -5,6 +5,7 @@ namespace Tests\Feature;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;
use App\Models\{Address,Domain,Setup,System};
@ -53,12 +54,17 @@ class RoutingTest extends TestCase
{
use DatabaseTransactions;
public const ZONE_ADDRS = 4061;
public const REGION_ADDRS = 811;
public const NET_ADDRS = 161;
public const HUB_ADDRS = 31;
public const NODE_ADDRS = 6;
private function zone(): Collection
{
//$this->seed(TestNodeHierarchy::class);
$do = Domain::where('name','a')->sole();
$zo = $do->zones->where('zone_id',100)->pop();
return $zo->addresses;
}
@ -70,19 +76,31 @@ class RoutingTest extends TestCase
private function session_rc(): void
{
$ao = Address::findFTN('100:1/0@a');
$ao = Address::findFTN('101:1/0@a');
$ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
}
private function session_nc(): void
{
$ao = Address::findFTN('100:10/0@a');
$ao = Address::findFTN('101:10/0@a');
$ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
}
private function session_hub(): void
{
$ao = Address::findFTN('100:10/20@a');
$ao = Address::findFTN('101:10/100@a');
$ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
}
private function session_node(): void
{
$ao = Address::findFTN('101:10/101@a');
$ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
}
private function session_point(): void
{
$ao = Address::findFTN('101:10/101.1@a');
$ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
}
@ -90,14 +108,14 @@ class RoutingTest extends TestCase
public function test_my_addresses()
{
$ftns = our_address();
$this->assertEquals(1,$ftns->count());
$this->assertEquals(6,$ftns->count());
}
// Get a list of active addresses in a Zone
public function test_zc_addresses()
public function test_zone_addresses()
{
$nodes = $this->zone();
$this->assertEquals(936,$nodes->count());
$this->assertEquals(self::ZONE_ADDRS,$nodes->count());
}
// Get a ZC with no session details, and make sure it has no parent nor children
@ -107,230 +125,302 @@ class RoutingTest extends TestCase
$ao = Address::findFTN('101:0/0@a');
$this->assertEquals($ao->role_id,Address::NODE_ZC);
$this->assertCount(0,$ao->downstream());
$this->assertCount(0,$ao->downlinks());
$this->assertNull($ao->uplink());
}
// If we have a ZC, make sure we are routing to all it's children
public function test_zc_session_children()
{
$this->session_zc();
$ao = Address::findFTN('101:0/0@a');
$this->assertCount(935,$ao->downstream());
}
// An RC's parent should be the ZC, when we have session details with parent
public function test_zc_rc_node_parent()
{
// This is a ZC
$this->assertEquals($ao->role_name,'ZC');
$this->assertEquals(Address::NODE_ZC,$ao->role_id);
// Children
$this->assertCount(self::ZONE_ADDRS-1,$ao->children());
$this->assertCount(0,$ao->downlinks());
$this->session_zc();
$ao->refresh();
$ao = Address::findFTN('101:1/0@a');
$this->assertEquals($ao->role_id,Address::NODE_RC);
$this->assertEquals('101:0/0.0@a',$ao->uplink()->ftn);
// Children
$this->assertCount(self::ZONE_ADDRS,$ao->downlinks());
}
// ZC uplink should be null, and the parent should be itself
public function test_zc_parent()
{
$ao = Address::findFTN('101:0/0@a');
// This uplink should also be null
$this->assertNull($ao->uplink()?->ftn);
$this->session_zc();
$ao->refresh();
// This parent should also be null
$this->assertNull($ao->parent()?->ftn);
// This uplink should be itself
$this->assertEquals($ao->ftn,$ao->uplink()?->ftn);
}
// Get a list of active addresses in a Region
public function test_rc_session_children()
{
$this->session_rc();
$ao = Address::findFTN('101:1/0@a');
$ao = Address::findFTN('100:1/0@a');
$this->assertCount(185,$ao->downstream());
// This is a RC
$this->assertEquals($ao->role_name,'RC');
$this->assertEquals(Address::NODE_RC,$ao->role_id);
// Children
$this->assertCount(self::REGION_ADDRS-1,$ao->children());
$this->assertCount(0,$ao->downlinks());
$this->session_rc();
$ao->refresh();
// This is a ZC
$this->assertCount(self::REGION_ADDRS,$ao->downlinks());
}
// An RCs node still collects mail from the RC
public function test_rc_node_rc()
{
$this->session_rc();
// An RCs node should still be the RC
$ao = Address::findFTN('100:1/100@a');
$this->assertEquals($ao->role_id,Address::NODE_NN);
$this->assertEquals('100:1/0.0@a',$ao->uplink()->ftn);
}
// An NC collects mail for its children
public function test_rc_nc_node_rc()
{
$this->session_rc();
// A NCs parent should still be the RC
$ao = Address::findFTN('100:10/0@a');
$this->assertEquals($ao->role_id,Address::NODE_NC);
$this->assertEquals('100:1/0.0@a',$ao->uplink()->ftn);
}
// A Hub still collects mail from NC
public function test_rc_hub_node_nc()
{
$this->session_rc();
// A Hubs parent should still be the NC
$ao = Address::findFTN('100:10/20.0@a');
$this->assertEquals($ao->role_id,Address::NODE_HC);
$this->assertEquals('100:1/0.0@a',$ao->uplink()->ftn);
}
// A Hub's node still collects mail from Hub
public function test_rc_hub_node_hub()
{
$this->session_rc();
// A Hubs node should still be the Hub
$ao = Address::findFTN('100:10/22.0@a');
$this->assertEquals($ao->role_id,Address::NODE_NN);
$this->assertEquals('100:1/0.0@a',$ao->uplink()->ftn);
}
// An RCs parent is us even if we have session details for another RC
public function test_rc_parent()
{
$this->session_rc();
$ao = Address::findFTN('101:1/0@a');
$o = Address::findFTN('101:0/0@a');
$ao = Address::findFTN('100:2/0@a');
// This uplink should also be null
$this->assertNull($ao->uplink()?->ftn);
}
// If we also have session details for an NC, then there are less RC nodes
public function test_rc_nc_session_children()
{
$this->session_rc();
$this->session_nc();
$ao->refresh();
$ao = Address::findFTN('100:1/0@a');
$this->assertCount(185-36,$ao->downstream());
// This parent should also be null
$this->assertEquals($o->ftn,$ao->parent()?->ftn);
// This uplink should be itself
$this->assertEquals($ao->ftn,$ao->uplink()?->ftn);
}
// If we also have session details for an Hub, then there are less RC nodes
public function test_rc_hub_session_children()
public function test_zc_rc_nodes()
{
$this->session_zc();
$this->session_rc();
$ao = Address::findFTN('100:10/20@a');
$ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
$ao = Address::findFTN('101:0/0@a');
$this->assertCount(self::ZONE_ADDRS-self::REGION_ADDRS,$ao->downlinks());
$ao = Address::findFTN('100:1/0@a');
$this->assertCount(185,$ao->downstream());
$ao = Address::findFTN('100:10/22@a');
$this->assertEquals('100:10/20.0@a',$ao->uplink()->ftn);
$ao = Address::findFTN('101:1/0@a');
$this->assertCount(self::REGION_ADDRS,$ao->downlinks());
}
// If we also have session details for an Hub, then there are less RC nodes
public function test_rc_hub_session_child()
{
$this->session_hub();
$ao = Address::findFTN('100:10/22@a');
$this->assertEquals('100:10/20.0@a',$ao->uplink()->ftn);
}
// When we have an RC with session details, we route to all its children
public function test_nc_session_children()
{
$this->session_nc();
$ao = Address::findFTN('101:10/0@a');
$ao = Address::findFTN('100:10/0@a');
$this->assertCount(35,$ao->downstream());
// This is a NC
$this->assertEquals($ao->role_name,'NC');
$this->assertEquals(Address::NODE_NC,$ao->role_id);
// Children
$this->assertCount(self::NET_ADDRS-1,$ao->children());
$this->assertCount(0,$ao->downlinks());
$this->session_nc();
$ao->refresh();
// This is a NC
$this->assertCount(self::NET_ADDRS,$ao->downlinks());
}
public function test_complex_rc_nc_hc()
public function test_nc_parent()
{
$this->session_rc();
$this->session_nc();
$this->session_hub();
$ao = Address::findFTN('101:10/0@a');
$o = Address::findFTN('101:1/0@a');
$ao = Address::findFTN('100:1/100.0@a');
$this->assertCount(0,$ao->downstream());
$this->assertEquals('100:1/0.0@a',$ao->uplink()->ftn);
// RC
$ao = Address::findFTN('100:1/0.0@a');
$this->assertCount(186-1-30-6,$ao->downstream());
$ao = Address::findFTN('100:11/0.0@a');
$this->assertEquals('100:1/0.0@a',$ao->uplink()->ftn);
// NC
$ao = Address::findFTN('100:10/0.0@a');
$this->assertCount(36-1-6,$ao->downstream());
$ao = Address::findFTN('100:10/10.0@a');
$this->assertEquals('100:10/0.0@a',$ao->uplink()->ftn);
// HC
$ao = Address::findFTN('100:10/20.0@a');
$this->assertCount(6-1,$ao->downstream());
$ao = Address::findFTN('100:10/22.0@a');
$this->assertEquals('100:10/20.0@a',$ao->uplink()->ftn);
}
public function test_complex_rc_nc_hc_us()
{
Cache::forget('so');
$setup = Setup::findOrFail(config('app.id'));
$ao = Address::findFTN('100:10/0.0@a');
$setup->system_id = $ao->system_id;
$setup->save();
$this->assertEquals('100:10/0.0@a',our_address($ao)?->ftn);
$this->session_rc();
//$this->session_nc();
$this->session_hub();
$ao = Address::findFTN('100:11/0.0');
$ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
$ao = Address::findFTN('100:1/100.0@a');
$this->assertCount(0,$ao->downstream());
$this->assertEquals('100:1/0.0@a',$ao->uplink()?->ftn);
// RC
$ao = Address::findFTN('100:1/0.0@a');
$this->assertCount(186-36-36-1,$ao->downstream());
$ao = Address::findFTN('100:11/0.0@a');
$this->assertEquals('100:11/0.0@a',$ao->uplink()->ftn);
// NC
$ao = Address::findFTN('100:10/0.0@a');
$this->assertCount(36-6-1,$ao->downstream());
$ao = Address::findFTN('100:10/10.0@a');
// This uplink should also be null
$this->assertNull($ao->uplink()?->ftn);
// HC
$ao = Address::findFTN('100:10/20.0@a');
$this->assertCount(6-1,$ao->downstream());
$this->session_nc();
$ao->refresh();
$ao = Address::findFTN('100:10/22.0@a');
$this->assertEquals('100:10/20.0@a',$ao->uplink()->ftn);
Cache::forget('so');
// This parent should also be null
$this->assertEquals($o->ftn,$ao->parent()?->ftn);
// This uplink should be itself
$this->assertEquals($ao->ftn,$ao->uplink()?->ftn);
}
// A points parent is the node, if we have traffic for a point and we have session details for the node
public function test_point_session_node()
public function test_zc_rc_nc_nodes()
{
//$this->session_hub();
$this->session_zc();
$this->session_rc();
$this->session_nc();
$so = new System;
$so->name = 'Point System';
$so->sysop = 'Point Sysop';
$so->location = 'Melbourne, AU';
$so->active = TRUE;
$so->save();
$ao = Address::findFTN('101:0/0@a');
$this->assertCount(self::ZONE_ADDRS-self::REGION_ADDRS,$ao->downlinks());
// Create a child
$ao = Address::createFTN('100:10/21.2@a',$so);
$ao = Address::findFTN('101:1/0@a');
$this->assertCount(self::REGION_ADDRS-self::NET_ADDRS,$ao->downlinks());
$ao = Address::findFTN('100:10/21.0@a');
$ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]);
$ao = Address::findFTN('101:10/0@a');
$this->assertCount(self::NET_ADDRS,$ao->downlinks());
}
$ao = Address::findFTN('100:10/21.2@a');
$this->assertEquals('100:10/21.0@a',$ao->uplink()?->ftn);
public function test_hub_session_children()
{
$ao = Address::findFTN('101:10/100@a');
$ao = Address::findFTN('100:10/21@a');
$this->assertCount(1,$ao->downstream());
// This is a HUB
$this->assertEquals($ao->role_name,'HUB');
$this->assertEquals(Address::NODE_HC,$ao->role_id);
// Children
$this->assertCount(self::HUB_ADDRS-1,$ao->children());
$this->assertCount(0,$ao->downlinks());
$this->session_hub();
$ao->refresh()->unsetRelations();
// This is a NC
$this->assertCount(self::HUB_ADDRS,$ao->downlinks());
}
public function test_hub_parent()
{
$ao = Address::findFTN('101:10/100@a');
$o = Address::findFTN('101:10/0@a');
// This uplink should also be null
$this->assertNull($ao->uplink()?->ftn);
$this->session_hub();
$ao->refresh();
// This parent should also be null
$this->assertEquals($o->ftn,$ao->parent()?->ftn);
// This uplink should be itself
$this->assertEquals($ao->ftn,$ao->uplink()?->ftn);
}
public function test_zc_rc_nc_hub_nodes()
{
$this->session_zc();
$this->session_rc();
$this->session_nc();
$this->session_hub();
$ao = Address::findFTN('101:0/0@a');
$this->assertCount(self::ZONE_ADDRS-self::REGION_ADDRS,$ao->downlinks());
$ao = Address::findFTN('101:1/0@a');
$this->assertCount(self::REGION_ADDRS-self::NET_ADDRS,$ao->downlinks());
$ao = Address::findFTN('101:10/0@a');
$this->assertCount(self::NET_ADDRS-self::HUB_ADDRS,$ao->downlinks());
$ao = Address::findFTN('101:10/100@a');
$this->assertCount(self::HUB_ADDRS,$ao->downlinks());
}
public function test_node_session_children()
{
$ao = Address::findFTN('101:10/101@a');
// This is a NC
$this->assertEquals($ao->role_name,'NODE');
$this->assertEquals(Address::NODE_NN,$ao->role_id);
// Children
$this->assertCount(self::NODE_ADDRS-1,$ao->children());
$this->assertCount(0,$ao->downlinks());
$this->session_node();
$ao->refresh();
// This is a NC
$this->assertCount(self::NODE_ADDRS,$ao->downlinks());
}
public function test_node_parent()
{
$ao = Address::findFTN('101:10/101@a');
$o = Address::findFTN('101:10/100@a');
// This uplink should also be null
$this->assertNull($ao->uplink()?->ftn);
$this->session_node();
$ao->refresh();
// This parent should also be null
$this->assertEquals($o->ftn,$ao->parent()?->ftn);
// This uplink should be itself
$this->assertEquals($ao->ftn,$ao->uplink()?->ftn);
}
public function test_zc_rc_nc_hub_node_nodes()
{
$this->session_zc();
$this->session_rc();
$this->session_nc();
$this->session_hub();
$this->session_node();
$ao = Address::findFTN('101:0/0@a');
$this->assertCount(self::ZONE_ADDRS-self::REGION_ADDRS,$ao->downlinks());
$ao = Address::findFTN('101:1/0@a');
$this->assertCount(self::REGION_ADDRS-self::NET_ADDRS,$ao->downlinks());
$ao = Address::findFTN('101:10/0@a');
$this->assertCount(self::NET_ADDRS-self::HUB_ADDRS,$ao->downlinks());
$ao = Address::findFTN('101:10/100@a');
$this->assertCount(self::HUB_ADDRS-self::NODE_ADDRS,$ao->downlinks());
$ao = Address::findFTN('101:10/101@a');
$this->assertCount(self::NODE_ADDRS,$ao->downlinks());
}
public function test_point_session_children()
{
$ao = Address::findFTN('101:10/101.1@a');
// This is a NC
$this->assertEquals($ao->role_name,'POINT');
$this->assertEquals(Address::NODE_POINT,$ao->role_id);
// Children
$this->assertCount(0,$ao->children());
$this->assertCount(0,$ao->downlinks());
$this->session_point();
$ao->refresh();
// This is a NC
$this->assertCount(1,$ao->downlinks());
}
public function test_point_parent()
{
$ao = Address::findFTN('101:10/101.1@a');
$o = Address::findFTN('101:10/101@a');
// This uplink should also be null
$this->assertNull($ao->uplink()?->ftn);
$this->session_point();
$ao->refresh();
// This parent should also be null
$this->assertEquals($o->ftn,$ao->parent()?->ftn);
// This uplink should be itself
$this->assertEquals($ao->ftn,$ao->uplink()?->ftn);
}
}

View File

@ -131,7 +131,7 @@ class TicProcessingTest extends TestCase
$file->refresh();
$this->assertEquals('100:1/0',$file->fftn->ftn3d);
$this->assertEquals('100:10/11',$file->origin->ftn3d);
$this->assertCount(12,$file->seenby);
$this->assertCount(7,$file->seenby);
$this->assertCount(4,$file->path);
}
}