<?php use Carbon\Carbon; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Config; use App\Models\{Address,Domain,Setup}; /** * Calculate CCITT-CRC16 checksum */ if (! function_exists('crc16')) { function crc16($data): int { $crc = 0x0000; for ($i = 0; $i < strlen($data); $i++) { $x = (($crc >> 8) ^ ord($data[$i])) & 0xFF; $x ^= $x >> 4; $crc = (($crc << 8) ^ ($x << 12) ^ ($x << 5) ^ $x) & 0xFFFF; } return $crc; } } /** * Dump out data into a hex dump */ if (! function_exists('hex_dump')) { function hex_dump($data,$newline="\n",$width=16): string { $result = ''; $pad = '.'; # padding for non-visible characters $to = $from = ''; for ($i=0; $i<=0xFF; $i++) { $from .= chr($i); $to .= ($i >= 0x20 && $i <= 0x7E) ? chr($i) : $pad; } $hex = str_split(bin2hex($data),$width*2); $chars = str_split(strtr($data,$from,$to),$width); $offset = 0; foreach ($hex as $i => $line) { $result .= sprintf('%08X: %-48s [%s]%s', $offset, substr_replace(implode(' ',str_split($line,2)),' ',8*3,0), $chars[$i], $newline); $offset += $width; } return $result; } } /** * Send a value has hex chars */ if (! function_exists('hexstr')) { function hexstr(int $int): string { if ($int > 0xffff) throw new Exception('Int too large for hexstr'); $hexdigitslower = '0123456789abcdef'; $x = ''; if ($int > 0xff) { $x .= substr($hexdigitslower,($int&0xf000)>>12,1); $x .= substr($hexdigitslower,($int&0x0f00)>>8,1); } $x .= substr($hexdigitslower,($int&0x00f0)>>4,1); $x .= substr($hexdigitslower,($int&0x000f),1); return $x; } } /** * Return our addresses. * If domain provided, limit the list to those within the domain - returning a Collection::class * If address provided, return our address that would be used for the provided address - return Address::class * * @param Domain|Address|null $o - Domain or Address * @return Collection|Address|NULL * @throws Exception */ function our_address(Domain|Address $o=NULL): Collection|Address|NULL { if (! Config::has('setup')) Config::set('setup',Setup::findOrFail(config('app.id'))); $so = Config::get('setup'); $so->loadMissing([ 'system:id,name,sysop,location', 'system.akas:addresses.id,addresses.zone_id,region_id,host_id,node_id,point_id,addresses.system_id,addresses.active,role', 'system.akas.zone:id,domain_id,zone_id', 'system.akas.zone.domain:id,name', ]); // If we dont have any addresses if ($so->system->akas->count() === 0) return NULL; // We havent asked for an address/domain, so we'll return them all. if (is_null($o)) return $so->system->akas; // We are requesting a list of addresses for a Domain, or a specific Address, and we have more than 1 switch (get_class($o)) { // Looking for addresses in the same domain, and if fido.strict, addresses that have a higher role (ie: uplink) case Address::class: $filter = $so->system->akas ->filter(fn($item)=>$item->zone->domain_id === $o->zone->domain_id) ->sortBy('role_id'); // If we are looking for a specific address, and there is only 1 result, return it, otherwise return what we have if (config('fido.strict') && ($x=$filter->filter(fn($item)=>$item->role_id <= $o->role_id)->sortBy('role_id'))->count()) $filter = $x; return $filter->count() ? $filter->last()->unsetRelation('nodes_hub') : NULL; // Addresses in this domain case Domain::class: return $so->system->akas ->filter(fn($item)=>$item->zone->domain_id === $o->id) ->sortBy('role_id'); // We shouldnt get here default: throw new Exception('Unhandled class: '.get_class($o)); } } function our_hostname(Address $o): string { $our = our_address($o->domain)->first(); switch ($our->role_id) { case Address::NODE_ZC: $domain = collect(explode('.',gethostname()))->forget(0) ->prepend(sprintf('z%d',$our->zone->zone_id)); break; case Address::NODE_RC: case Address::NODE_NC: $domain = collect(explode('.',gethostname()))->forget(0) ->prepend(sprintf('z%d',$our->zone->zone_id)) ->prepend(sprintf('n%d',$our->host_id)); break; case Address::NODE_HC: $domain = collect(explode('.',gethostname()))->forget(0) ->prepend(sprintf('z%d',$our->zone->zone_id)) ->prepend(sprintf('n%d',$our->host_id)) ->prepend(sprintf('f%d',$our->node_id)); break; default: $domain = collect(explode('.',gethostname())); } return $domain->join('.'); } /** * 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), fn($query)=>$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 * are unique using 1/10th second precision. * * Time is: * + 02 bits least significant bits of year - giving us a 4 year timestamp * + 04 bits Month * + 05 bits Day * + 05 bits Hour * + 06 bits Min * + 06 bits Sec * + 04 bits 10th Sec * = 32 bits * * @param Carbon|null $time * @return int * @todo Since this is used as part of our msgid, we need to use the first 2 bits to get our 3 year unique msgid, ie: year&0x03 */ function timew(Carbon $time=NULL): int { static $delay = 0; // If we are not passed a time, we'll use the time now if (! $time) { // In case we are called twice, we'll delay 1/10th second so we have a unique result. if (Carbon::now()->getPreciseTimestamp(1) === $delay) usleep(100000); $time = Carbon::now(); } $delay = $time->getPreciseTimestamp(1); $t = 0; $t = ($t | $time->year & 0x3) << 4; $t = ($t | ($time->month & 0xf)) << 5; $t = ($t | ($time->day & 0x1f)) << 5; $t = ($t | ($time->hour & 0x1f)) << 6; $t = ($t | ($time->minute & 0x3f)) << 6; $t = ($t | ($time->second & 0x3f)) << 4; $t = ($t | ((int)($time->milli/100)) & 0xf); return $t; } } if (! function_exists('wtime')) { /** * Convert a 40 bit integer into a time. * We need to filter out loose bits, ie: * + year all bits valid * + month 11xx and 0000 are invalid * + day all bits valid except 0000 * + hour 11xxx are invalid * + min 1111xx are invalid * + sec 1111xx are invalid * + 1/2 11xx, 101x are invalid * @see timew() * * @param int $time * @param int|null $year * @return Carbon */ function wtime(int $time,int $year=NULL): Carbon { if (! $year) $year = Carbon::now()->year; // Does the time have milli seconds? if ($time > pow(2,26)-1) { $milli = ($time & 0xf); $time = $time >> 4; if ($milli > 9) $milli = 9; } else { $milli = 0; } $sec = ($time & 0x3f); if ($sec > 59) $sec = 59; $time = $time >> 6; $min = ($time & 0x3f); if ($min > 59) $min = 59; $time = $time >> 6; $hr = ($time & 0x1f); if ($hr > 23) $hr = 23; $time = $time >> 5; $day = ($time & 0x1f); $time = $time >> 5; $month = ($time & 0xf); if ($month > 12) $month = 12; $time = $time >> 4; return Carbon::create(($year & 0xffc)+$time,$month,$day,$hr,$min,$sec+$milli/10); } }