From caa6e629f4c8956fdbcaaebdf381425b743b0359 Mon Sep 17 00:00:00 2001 From: Deon George Date: Wed, 13 Dec 2023 08:41:15 +1100 Subject: [PATCH] Change Address::parent(),Address::children(), improved CI testing --- app/Console/Commands/ZoneCheck.php | 4 +- app/Models/Address.php | 380 +++++++++++------------ app/Models/System.php | 2 +- app/helpers.php | 30 +- resources/views/system/addedit.blade.php | 14 +- tests/Feature/RoutingTest.php | 85 ++++- 6 files changed, 302 insertions(+), 213 deletions(-) diff --git a/app/Console/Commands/ZoneCheck.php b/app/Console/Commands/ZoneCheck.php index 78f75ea..ba1e7aa 100644 --- a/app/Console/Commands/ZoneCheck.php +++ b/app/Console/Commands/ZoneCheck.php @@ -18,12 +18,12 @@ class ZoneCheck extends Command { $do = Domain::where('name',$this->argument('domain'))->singleOrFail(); - foreach ($do->zones as $zo) { + foreach ($do->zones->sortby('zone_id') as $zo) { if ($this->option('zone') && ($this->option('zone') != $zo->zone_id)) continue; $this->warn('Zone: '.$zo->zone_id); - $this->info(sprintf('- Our address(es): %s',our_address($zo)->pluck('ftn4d')->join(','))); + $this->info(sprintf('- Our address(es): %s',our_address($do)->pluck('ftn4d')->join(','))); $this->table(['id','ftn','role','parent','region_id','host_id','hub_id','system','notes'],$zo->addresses()->FTNorder()->active()->with(['system'])->get()->transform(function($item) { return [ diff --git a/app/Models/Address.php b/app/Models/Address.php index 92ee5c3..812ed5c 100644 --- a/app/Models/Address.php +++ b/app/Models/Address.php @@ -36,6 +36,7 @@ class Address extends Model public const NODE_DOWN = 1<<7; // Down public const NODE_POINT = 1<<8; // Point public const NODE_UNKNOWN = 1<<15; // Unknown + public const NODE_ALL = 0xFFF; // Mask to catch all nodes public static function boot() { @@ -148,105 +149,6 @@ class Address extends Model /* RELATIONS */ - /** - * Find children dependent on this record - */ - public function children() - { - // We have no session data for this address, by definition it has no children - if (! $this->session('sespass')) - return $this->hasMany(self::class,'id','void'); - - if (! $this->session('default')) { - switch ($this->role) { - case self::NODE_ZC: - $children = self::select('addresses.*') - ->where('zone_id',$this->zone_id); - - break; - - case self::NODE_RC: - $children = self::select('addresses.*') - ->where('zone_id',$this->zone_id) - ->where('region_id',$this->region_id); - - break; - - case self::NODE_NC: - $children = self::select('addresses.*') - ->where('zone_id',$this->zone_id) - ->where('region_id',$this->region_id) - ->where('host_id',$this->host_id); - - break; - - case self::NODE_HC: - // Identify our children. - $children = self::select('addresses.*') - ->where('zone_id',$this->zone_id) - ->where('region_id',$this->region_id) - ->where('hub_id',$this->id); - - break; - - case self::NODE_ACTIVE: - case self::NODE_PVT: - case self::NODE_HOLD: - case self::NODE_DOWN: - case self::NODE_UNKNOWN: - // Identify our children. - $children = self::select('addresses.*') - ->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); - break; - - case self::NODE_POINT: - // Points dont have children, but must return a relationship instance - return $this->hasOne(self::class,NULL,'void'); - - default: - throw new \Exception('Unknown role: '.serialize($this->role)); - } - - } else { - $children = self::select('addresses.*') - ->where('zone_id',$this->zone_id); - } - - // I cant have myself as a child, and have a high role than me - $children->where('id','<>',$this->id) - ->where('role','>',$this->role); - - // Remove any children that we have session details for (SAME AS HC) - $sessions = self::select('hubnodes.*') - ->join('system_zone',['system_zone.system_id'=>'addresses.system_id','system_zone.zone_id'=>'addresses.zone_id']) - ->join('addresses as hubnodes',['hubnodes.zone_id'=>'addresses.zone_id','hubnodes.id'=>'addresses.id']) - ->where('addresses.zone_id',$this->zone_id) - ->where('addresses.system_id','<>',$this->system_id) - ->whereIN('addresses.system_id',$children->get()->pluck('system_id')); - - // For each of the session, identify their children - $session_kids = collect(); - foreach ($sessions->get() as $so) - $session_kids = $session_kids->merge(($x=$so->children) ? $x->pluck('id') : []); - - // ZC's receive all mail, except for defined nodes, and defined hubs/hosts/rcs - return $this->hasMany(self::class,'zone_id','zone_id') - ->whereIn('id',$children->get()->pluck('id')->toArray()) - ->whereNotIn('id',$sessions->get()->pluck('id')->toArray()) - ->whereNotIn('id',$session_kids->toArray()) - ->where('system_id','<>',$this->system_id) - ->select('addresses.*') - ->orderBy('region_id') - ->orderBy('host_id') - ->orderBy('node_id') - ->orderBy('point_id') - ->with(['zone.domain']); - } - public function dynamics() { return $this->hasMany(Dynamic::class); @@ -291,7 +193,7 @@ class Address extends Model } /** - * Echoareas this address is subscribed to + * Fileareas this address is subscribed to * * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ @@ -301,96 +203,6 @@ class Address extends Model ->withPivot(['subscribed']); } - /** - * Who we send this systems mail to. - * - * @return Address|null - * @throws \Exception - */ - public function parent(): ?Address - { - // If we have session password, then we are the parent - if ($this->session('sespass')) - return $this; - - // If it is our address - if (our_address()->contains($this)) - return NULL; - - switch ($this->role) { - // ZCs dont have parents, but we may have a default - case self::NODE_ZC: - if (($x=$this->zone->systems->where('pivot.default',TRUE))->count()) - return $x->first()->match($this->zone,255)->first(); - else - return NULL; - - // RC - case self::NODE_RC: - $parent = self::where('zone_id',$this->zone_id) - ->where('region_id',0) - ->where('host_id',0) - ->where('node_id',0) - ->single(); - - break; - - // Hosts - case self::NODE_NC: - // See if we have an RC - $parent = self::where('zone_id',$this->zone_id) - ->where('region_id',$this->region_id) - ->where('host_id',$this->region_id) - ->where('node_id',0) - ->single(); - - if (! $parent) { - // See if we have an ZC - $parent = self::where('zone_id',$this->zone_id) - ->where('region_id',0) - ->where('host_id',0) - ->where('node_id',0) - ->single(); - } - - break; - - // Hubs - case self::NODE_HC: - // Normal Nodes - case self::NODE_ACTIVE: - case self::NODE_PVT: - case self::NODE_HOLD: - case self::NODE_DOWN: - case self::NODE_UNKNOWN: - // If we are a child of a hub, then check our hub - $parent = ($this->hub_id - ? self::where('id',$this->hub_id) - : self::where('zone_id',$this->zone_id) - ->where('region_id',$this->region_id) - ->where('host_id',$this->host_id) - ->where('role','<',self::NODE_HC)) - ->active() - ->single(); - break; - - case self::NODE_POINT: - $parent = self::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) - ->active() - ->single(); - break; - - default: - throw new \Exception(sprintf('Unknown role: %s (%d)',serialize($this->role),$this->id)); - } - - return $parent?->parent(); - } - public function system() { return $this->belongsTo(System::class); @@ -466,6 +278,101 @@ class Address extends Model /* METHODS */ + /** + * Find children dependent on this record + */ + public function children(): Collection + { + // We have no session data for this address, by definition it has no children + if (! $this->session('sespass') && (! our_address()->pluck('id')->contains($this->id))) + return new Collection; + + // If this system is not marked to default route for this address + if (! $this->session('default')) { + switch ($this->role) { + case self::NODE_ZC: + $children = self::select('addresses.*') + ->where('zone_id',$this->zone_id); + + break; + + case self::NODE_RC: + $children = self::select('addresses.*') + ->where('zone_id',$this->zone_id) + ->where('region_id',$this->region_id); + + break; + + case self::NODE_NC: + $children = self::select('addresses.*') + ->where('zone_id',$this->zone_id) + ->where('region_id',$this->region_id) + ->where('host_id',$this->host_id); + + break; + + case self::NODE_HC: + // Identify our children. + $children = self::select('addresses.*') + ->where('zone_id',$this->zone_id) + ->where('region_id',$this->region_id) + ->where('hub_id',$this->id); + + break; + + case self::NODE_ACTIVE: + case self::NODE_PVT: + case self::NODE_HOLD: + case self::NODE_DOWN: + case self::NODE_UNKNOWN: + // Identify our children. + $children = self::select('addresses.*') + ->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); + break; + + case self::NODE_POINT: + // Points dont have children, but must return a relationship instance + return new Collection; + + default: + throw new \Exception('Unknown role: '.serialize($this->role)); + } + + // We route everything for this domain + } else { + $children = self::select('addresses.*') + ->join('zones',['zones.id'=>'addresses.zone_id']) + ->where('domain_id',$this->zone->domain_id); + } + + // I cant have myself as a child, and have a high role than me + $children = $children->where('addresses.id','<>',$this->id) + ->where('role','>',$this->role) + ->FTNorder() + ->active() + ->get(); + + // If there are no children + if (! $children->count()) + return new Collection; + + // Exclude links and their children. + $exclude = collect(); + foreach (our_nodes($this->zone->domain)->merge(our_address($this->zone->domain)) as $o) { + // 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); + } + } + + return $children->filter(function($item) use ($exclude) { return ! $exclude->pluck('id')->contains($item->id);}); + } + /** * Create an FTN address associated with a system * @@ -892,10 +799,11 @@ class Address extends Model * Netmail waiting to be sent to this system * * @return Collection + * @throws \Exception */ public function netmailWaiting(): Collection { - return Netmail::whereIn('tftn_id',(($x=$this->children) ? $x->pluck('id') : collect())->push($this->id)) + return Netmail::whereIn('tftn_id',(($x=$this->children()) ? $x->pluck('id') : collect())->push($this->id)) ->where(function($query) { return $query->whereRaw(sprintf('(flags & %d) > 0',Message::FLAG_INTRANSIT)) ->orWhereRaw(sprintf('(flags & %d) > 0',Message::FLAG_LOCAL)); @@ -920,6 +828,96 @@ class Address extends Model ->get(); } + /** + * Who we send this systems mail to. + * + * @return Address|null + * @throws \Exception + */ + public function parent(): ?Address + { + // If we have session password, then we are the parent + if ($this->session('sespass')) + return $this; + + // If it is our address + if (our_address()->contains($this)) + return NULL; + + switch ($this->role) { + // ZCs dont have parents, but we may have a default + case self::NODE_ZC: + if (($x=$this->zone->systems->where('pivot.default',TRUE))->count()) + return $x->first()->match($this->zone,255)->first(); + else + return NULL; + + // RC + case self::NODE_RC: + $parent = self::where('zone_id',$this->zone_id) + ->where('region_id',0) + ->where('host_id',0) + ->where('node_id',0) + ->single(); + + break; + + // Hosts + case self::NODE_NC: + // See if we have an RC + $parent = self::where('zone_id',$this->zone_id) + ->where('region_id',$this->region_id) + ->where('host_id',$this->region_id) + ->where('node_id',0) + ->single(); + + if (! $parent) { + // See if we have an ZC + $parent = self::where('zone_id',$this->zone_id) + ->where('region_id',0) + ->where('host_id',0) + ->where('node_id',0) + ->single(); + } + + break; + + // Hubs + case self::NODE_HC: + // Normal Nodes + case self::NODE_ACTIVE: + case self::NODE_PVT: + case self::NODE_HOLD: + case self::NODE_DOWN: + case self::NODE_UNKNOWN: + // If we are a child of a hub, then check our hub + $parent = ($this->hub_id + ? self::where('id',$this->hub_id) + : self::where('zone_id',$this->zone_id) + ->where('region_id',$this->region_id) + ->where('host_id',$this->host_id) + ->where('role','<',self::NODE_HC)) + ->active() + ->single(); + break; + + case self::NODE_POINT: + $parent = self::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) + ->active() + ->single(); + break; + + default: + throw new \Exception(sprintf('Unknown role: %s (%d)',serialize($this->role),$this->id)); + } + + return $parent?->parent(); + } + /** * * Parse a string and split it out as an FTN array diff --git a/app/Models/System.php b/app/Models/System.php index e2b5888..d5370e9 100644 --- a/app/Models/System.php +++ b/app/Models/System.php @@ -211,7 +211,7 @@ class System extends Model * @return Collection * @todo This doesnt return sorted addresses, so it is possible that a node address is returned first, before a NC/HC, etc */ - public function match(Zone $o,int $type=(Address::NODE_HC|Address::NODE_ACTIVE|Address::NODE_PVT|Address::NODE_POINT)): Collection + public function match(Zone $o,int $type=(Address::NODE_NC|Address::NODE_HC|Address::NODE_ACTIVE|Address::NODE_PVT|Address::NODE_POINT)): Collection { $akas = $this->akas ->where(function($item) use($o) { diff --git a/app/helpers.php b/app/helpers.php index 55e92e3..5aaceea 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -3,7 +3,7 @@ use Carbon\Carbon; use Illuminate\Support\Collection; -use App\Models\{Setup,Zone}; +use App\Models\{Address,Domain,Setup,Zone}; /** * Calculate CCITT-CRC16 checksum @@ -85,18 +85,38 @@ if (! function_exists('hexstr')) { * Return our addresses. * If zone provided, limit the list to those within the zone * - * @param Zone|NULL $zo + * @param Domain|NULL $do * @return Collection */ -function our_address(Zone $zo=NULL): Collection +function our_address(Domain $do=NULL): Collection { $our = Setup::findOrFail(config('app.id'))->system->addresses; - return $zo - ? $our->filter(function($item) use ($zo) { return $item->zone_id === $zo->id; }) + return $do + ? $our->filter(function($item) use ($do) { return $item->zone->domain_id === $do->id; }) : $our; } +/** + * Return a list of nodes that collect mail directly from us + * + * @param Domain|NULL $do + * @return Collection + */ +function our_nodes(Domain $do=NULL): Collection +{ + return Address::select(['addresses.id','addresses.zone_id','region_id','host_id','node_id','point_id','addresses.system_id','role']) + ->join('system_zone',['system_zone.system_id'=>'addresses.system_id','system_zone.zone_id'=>'addresses.zone_id']) + ->when(! is_null($do),function($query) use ($do) { + return $query + ->join('zones',['zones.id'=>'addresses.zone_id']) + ->where('domain_id',$do->id); + }) + ->active() + ->FTNorder() + ->get(); +} + if (! function_exists('timew')) { /** * Convert a time into an 32 bit value. This is primarily used to create 8 character hex filenames that diff --git a/resources/views/system/addedit.blade.php b/resources/views/system/addedit.blade.php index 8b2499b..8a64cf4 100644 --- a/resources/views/system/addedit.blade.php +++ b/resources/views/system/addedit.blade.php @@ -292,7 +292,7 @@ @foreach($x as $zo) - @foreach ($o->match($zo) as $oo) + @foreach ($o->match($zo,\App\Models\Address::NODE_ALL) as $oo) {{ $oo->ftn }} @@ -312,30 +312,28 @@
-

This host collects mail for the following systems:

- @if($o->sessions->count()) +

This host collects mail for the following systems:

+ - + @foreach ($o->sessions->sortBy('zone_id') as $zo) - @foreach ($o->match($zo) as $oo) + @foreach ($o->match($zo,\App\Models\Address::NODE_ALL) as $oo) - + @endforeach @endforeach
AKADownlinkDownlink(s)
{{ $oo->ftn }}{!! (($x=$oo->children) && $x->count()) ? $x->pluck('ftn4d')->join('
') : 'None' !!}
{!! (($x=$oo->children()) && $x->count()) ? $x->pluck('ftn4d')->join('
') : 'None' !!}
- @else -

This host doesnt collect mail.

@endif
diff --git a/tests/Feature/RoutingTest.php b/tests/Feature/RoutingTest.php index 92a73d6..03d1ecc 100644 --- a/tests/Feature/RoutingTest.php +++ b/tests/Feature/RoutingTest.php @@ -110,7 +110,7 @@ class RoutingTest extends TestCase $ao = Address::findFTN('101:0/0@a'); $this->assertEquals($ao->role,Address::NODE_ZC); - $this->assertCount(0,$ao->children); + $this->assertCount(0,$ao->children()); $this->assertNull($ao->parent()); } @@ -120,7 +120,7 @@ class RoutingTest extends TestCase $this->session_zc(); $ao = Address::findFTN('101:0/0@a'); - $this->assertCount(935,$ao->children); + $this->assertCount(935,$ao->children()); } // An RC's parent should be the ZC, when we have session details with parent @@ -204,7 +204,7 @@ class RoutingTest extends TestCase $ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]); $ao = Address::findFTN('100:1/0@a'); - $this->assertCount(185-36,$ao->children); + $this->assertCount(185-36,$ao->children()); } // If we also have session details for an Hub, then there are less RC nodes @@ -216,7 +216,7 @@ class RoutingTest extends TestCase $ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]); $ao = Address::findFTN('100:1/0@a'); - $this->assertCount(185-6,$ao->children); + $this->assertCount(185-6,$ao->children()); $ao = Address::findFTN('100:10/22@a'); $this->assertEquals('100:10/20.0@a',$ao->parent()->ftn); @@ -237,7 +237,80 @@ class RoutingTest extends TestCase $this->session_nc(); $ao = Address::findFTN('100:10/7@a'); - $this->assertCount(35,$ao->children); + $this->assertCount(35,$ao->children()); + } + + public function test_complex_rc_nc_hc() + { + $this->session_rc(); + $this->session_nc(); + $this->session_hub(); + + $ao = Address::findFTN('100:1/100.0@a'); + $this->assertCount(0,$ao->children()); + $this->assertEquals('100:1/0.0@a',$ao->parent()->ftn); + + // RC + $ao = Address::findFTN('100:1/0.0@a'); + $this->assertCount(186-1-30-6,$ao->children()); + + $ao = Address::findFTN('100:11/7.0@a'); + $this->assertEquals('100:1/0.0@a',$ao->parent()->ftn); + + // NC + $ao = Address::findFTN('100:10/7.0@a'); + $this->assertCount(36-1-6,$ao->children()); + + $ao = Address::findFTN('100:10/10.0@a'); + $this->assertEquals('100:10/7.0@a',$ao->parent()->ftn); + + // HC + $ao = Address::findFTN('100:10/20.0@a'); + $this->assertCount(6-1,$ao->children()); + + $ao = Address::findFTN('100:10/22.0@a'); + $this->assertEquals('100:10/20.0@a',$ao->parent()->ftn); + } + + public function test_complex_rc_nc_hc_us() + { + $setup = Setup::findOrFail(config('app.id')); + $ao = Address::findFTN('100:10/7.0@a'); + $setup->system_id = $ao->system_id; + $setup->save(); + + /* + */ + $this->session_rc(); + //$this->session_nc(); + $this->session_hub(); + $ao = Address::findFTN('100:11/7.0'); + $ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]); + + $ao = Address::findFTN('100:1/100.0@a'); + $this->assertCount(0,$ao->children()); + $this->assertEquals('100:1/0.0@a',$ao->parent()?->ftn); + + // RC + $ao = Address::findFTN('100:1/0.0@a'); + $this->assertCount(186-36-36-1,$ao->children()); + + $ao = Address::findFTN('100:11/7.0@a'); + $this->assertEquals('100:11/7.0@a',$ao->parent()->ftn); + + // NC + $ao = Address::findFTN('100:10/7.0@a'); + $this->assertCount(36-6-1,$ao->children()); + + $ao = Address::findFTN('100:10/10.0@a'); + $this->assertNull($ao->parent()?->ftn); + + // HC + $ao = Address::findFTN('100:10/20.0@a'); + $this->assertCount(6-1,$ao->children()); + + $ao = Address::findFTN('100:10/22.0@a'); + $this->assertEquals('100:10/20.0@a',$ao->parent()->ftn); } // A points parent is the node, if we have traffic for a point and we have session details for the node @@ -264,6 +337,6 @@ class RoutingTest extends TestCase $this->assertEquals('100:10/21.0@a',$ao->parent()?->ftn); $ao = Address::findFTN('100:10/21@a'); - $this->assertCount(1,$ao->children); + $this->assertCount(1,$ao->children()); } } \ No newline at end of file