<?php if (!defined('DASHBOARD_ROOT')) { http_response_code(403); exit; } class BloomTrust { private const BLOOM_FILE = '/var/www/private/bloom.dat'; private const BIT_SIZE = 1048576; private const HASH_COUNT = 7; private static ?string $bits = null; private static function load(): void { if (self::$bits !== null) return; if (file_exists(self::BLOOM_FILE)) { self::$bits = file_get_contents(self::BLOOM_FILE); } else { self::$bits = str_repeat("\0", (int)ceil(self::BIT_SIZE / 8)); } } private static function save(): void { if (self::$bits === null) return; $dir = dirname(self::BLOOM_FILE); if (!is_dir($dir)) @mkdir($dir, 0700, true); file_put_contents(self::BLOOM_FILE, self::$bits, LOCK_EX); } private static function positions(string $key): array { $positions = []; for ($i = 0; $i < self::HASH_COUNT; $i++) { $h = crc32($key . '|' . $i); $positions[] = abs($h) % self::BIT_SIZE; } return $positions; } public static function add(string $key): void { self::load(); foreach (self::positions($key) as $pos) { $byteIndex = (int)floor($pos / 8); $bitOffset = $pos % 8; $byte = ord(self::$bits[$byteIndex]); self::$bits[$byteIndex] = chr($byte | (1 << $bitOffset)); } self::save(); } public static function mightContain(string $key): bool { self::load(); foreach (self::positions($key) as $pos) { $byteIndex = (int)floor($pos / 8); $bitOffset = $pos % 8; $byte = ord(self::$bits[$byteIndex]); if (($byte & (1 << $bitOffset)) === 0) { return false; } } return true; } public static function syncFromDB(): int { $count = 0; try { $rows = Database::fetchAll("SELECT ip_address FROM blacklist WHERE status_code = 1"); foreach ($rows as $r) { self::add($r['ip_address']); $count++; } $devices = Database::fetchAll("SELECT device_hash FROM excluded_devices"); foreach ($devices as $d) { self::add($d['device_hash']); $count++; } } catch (\Throwable $e) {} Events::record(Events::CAT_SYSTEM, 'bloom.synced', 'security', null, ['entries' => $count]); return $count; } public static function status(): array { self::load(); $setBits = 0; $totalBytes = strlen(self::$bits); for ($i = 0; $i < $totalBytes; $i++) { $byte = ord(self::$bits[$i]); while ($byte) { $setBits++; $byte &= ($byte - 1); } } $fillRatio = $setBits / self::BIT_SIZE; return [ 'total_bits' => self::BIT_SIZE, 'set_bits' => $setBits, 'fill_ratio' => round($fillRatio, 4), 'fp_rate' => round(pow($fillRatio, self::HASH_COUNT), 6), 'hash_count' => self::HASH_COUNT, 'file_size_kb' => round($totalBytes / 1024, 1), 'file_exists' => file_exists(self::BLOOM_FILE), ]; } } 