diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6e3ac62 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vscode +.ftpignore + +/config/app.php +/database/yggstate.mwb.bak \ No newline at end of file diff --git a/config/app.php.example b/config/app.php.example new file mode 100644 index 0000000..8eca5da --- /dev/null +++ b/config/app.php.example @@ -0,0 +1,54 @@ + disk_free_space('/') / 1000000) { + + exit (PHP_EOL . 'Disk quota reached.' . PHP_EOL); +} + +// Init Debug +$debug = [ + 'time' => [ + 'ISO8601' => date('c'), + 'total' => microtime(true), + ], + 'yggdrasil' => [ + 'peer' => [ + 'total' => [ + 'online' => 0, + 'insert' => 0, + ], + 'remote' => [ + 'total' => [ + 'insert' => 0, + 'update' => 0, + ] + ] + ] + ] +]; + +// Connect database +try { + + $db = new MySQL(DB_HOST, DB_PORT, DB_NAME, DB_USERNAME, DB_PASSWORD); + +} catch(Exception $e) { + + var_dump($e); + + exit; +} + +// Collect connected peers +if ($connectedPeers = Yggdrasil::getPeers()) { + + foreach ($connectedPeers as $connectedPeerAddress => $connectedPeerInfo) { + + try { + + $db->beginTransaction(); + + if ($dbPeer = $db->findPeer($connectedPeerAddress)) { + + $dbPeerId = $dbPeer->peerId; + + } else { + + $dbPeerId = $db->addPeer($connectedPeerAddress, $connectedPeerInfo->key, time()); + + if ($connectedPeerRemoteUrl = URL::parse($connectedPeerInfo->remote)) { + + if ($dbPeerRemote = $db->findPeerRemote($dbPeerId, + $connectedPeerRemoteUrl->host->scheme, + $connectedPeerRemoteUrl->host->name, + $connectedPeerRemoteUrl->host->port)) { + + // Update connection stats + if ($dbPeerRemote->received < $connectedPeerInfo->bytes_recvd) { + + $debug['yggdrasil']['peer']['remote']['total']['update'] += + $db->updatePeerRemoteReceived($dbPeerRemote->dbPeerRemoteId, $connectedPeerInfo->bytes_recvd, time()); + + } + + if ($dbPeerRemote->sent < $connectedPeerInfo->bytes_sent) { + + $debug['yggdrasil']['peer']['remote']['total']['update'] += + $db->updatePeerRemoteSent($dbPeerRemote->dbPeerRemoteId, $connectedPeerInfo->bytes_sent, time()); + } + + if ($dbPeerRemote->uptime < $connectedPeerInfo->uptime) { + + $debug['yggdrasil']['peer']['remote']['total']['update'] += + $db->updatePeerRemoteUptime($dbPeerRemote->dbPeerRemoteId, $connectedPeerInfo->uptime, time()); + } + + } else { + + $peerRemoteId = $db->addPeerRemote($dbPeerId, + $connectedPeerRemoteUrl->host->scheme, + $connectedPeerRemoteUrl->host->name, + $connectedPeerRemoteUrl->host->port, + $connectedPeerInfo->bytes_recvd, + $connectedPeerInfo->bytes_sent, + $connectedPeerInfo->uptime, + time()); + + $debug['yggdrasil']['peer']['remote']['total']['insert']++; + } + } + + $debug['yggdrasil']['peer']['total']['insert']++; + } + + $debug['yggdrasil']['peer']['total']['online']++; + + $db->commit(); + + } catch(Exception $e) { + + $db->rollBack(); + + var_dump($e); + + break; + } + } +} + +// Debug output +$debug['time']['total'] = microtime(true) - $debug['time']['total']; + +print_r( + array_merge($debug, [ + 'db' => [ + 'total' => [ + 'select' => $db->getDebug()->query->select->total, + 'insert' => $db->getDebug()->query->insert->total, + 'update' => $db->getDebug()->query->update->total, + 'delete' => $db->getDebug()->query->delete->total, + ] + ] + ]) +); \ No newline at end of file diff --git a/database/yggstate.mwb b/database/yggstate.mwb new file mode 100644 index 0000000..a8e2d67 Binary files /dev/null and b/database/yggstate.mwb differ diff --git a/library/mysql.php b/library/mysql.php new file mode 100644 index 0000000..4f9fd7e --- /dev/null +++ b/library/mysql.php @@ -0,0 +1,164 @@ +_db = new PDO('mysql:dbname=' . $database . ';host=' . $host . ';port=' . $port . ';charset=utf8', $username, $password, [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8']); + $this->_db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->_db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); + $this->_db->setAttribute(PDO::ATTR_TIMEOUT, 600); + + $this->_debug = (object) + [ + 'query' => (object) + [ + 'select' => (object) + [ + 'total' => 0 + ], + 'insert' => (object) + [ + 'total' => 0 + ], + 'update' => (object) + [ + 'total' => 0 + ], + 'delete' => (object) + [ + 'total' => 0 + ], + ] + ]; + } + + // Tools + public function beginTransaction() { + + $this->_db->beginTransaction(); + } + + public function commit() { + + $this->_db->commit(); + } + + public function rollBack() { + + $this->_db->rollBack(); + } + + public function getDebug() { + + return $this->_debug; + } + + // Peer + public function addPeer(string $address, string $key, int $timeAdded) { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `peer` SET `address` = ?, `key` = ?, `timeAdded` = ?'); + + $query->execute([$address, $key, $timeAdded]); + + return $this->_db->lastInsertId(); + } + + public function findPeer(string $address) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `peer` WHERE `address` = ? LIMIT 1'); + + $query->execute([$address]); + + return $query->fetch(); + } + + public function getPeers() { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `peer`'); + + $query->execute(); + + return $query->fetchAll(); + } + + // Peer remote + public function addPeerRemote(int $peerId, string $scheme, string $host, int $port, int $received, int $sent, float $uptime, int $timeAdded) { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `peerRemote` SET `peerId` = ?, + `scheme` = ?, + `host` = ?, + `port` = ?, + `received` = ?, + `sent` = ?, + `uptime` = ?, + `timeAdded` = ?'); + + $query->execute([$peerId, $scheme, $host, $port, $received, $sent, $uptime, $timeAdded]); + + return $this->_db->lastInsertId(); + } + + public function updatePeerRemoteReceived(int $peerRemoteId, int $received, int $timeUpdated) { + + $this->_debug->query->update->total++; + + $query = $this->_db->prepare('UPDATE `peerRemote` SET `received` = ?, `timeUpdated` = ? WHERE `peerRemoteId` = ? LIMIT 1'); + + $query->execute([$received, $timeUpdated, $peerRemoteId]); + + return $query->rowCount(); + } + + public function updatePeerRemoteSent(int $peerRemoteId, int $sent, int $timeUpdated) { + + $this->_debug->query->update->total++; + + $query = $this->_db->prepare('UPDATE `peerRemote` SET `sent` = ?, `timeUpdated` = ? WHERE `peerRemoteId` = ? LIMIT 1'); + + $query->execute([$sent, $timeUpdated, $peerRemoteId]); + + return $query->rowCount(); + } + + public function updatePeerRemoteUptime(int $peerRemoteId, float $uptime, int $timeUpdated) { + + $this->_debug->query->update->total++; + + $query = $this->_db->prepare('UPDATE `peerRemote` SET `uptime` = ?, `timeUpdated` = ? WHERE `peerRemoteId` = ? LIMIT 1'); + + $query->execute([$uptime, $timeUpdated, $peerRemoteId]); + + return $query->rowCount(); + } + + public function findPeerRemote(int $peerId, string $scheme, string $host, int $port) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `peerRemote` WHERE `peerId` = ? AND `scheme` = ? AND `host` = ? AND `port` = ? LIMIT 1'); + + $query->execute([$peerId, $scheme, $host, $port]); + + return $query->fetch(); + } + + // Other + public function optimize() { + + $this->_db->query('OPTIMIZE TABLE `peer`'); + $this->_db->query('OPTIMIZE TABLE `peerRemote`'); + } +} diff --git a/library/url.php b/library/url.php new file mode 100644 index 0000000..bada461 --- /dev/null +++ b/library/url.php @@ -0,0 +1,82 @@ + (object) + [ + 'url' => null, + 'scheme' => null, + 'name' => null, + 'port' => null, + ], + 'page' => (object) + [ + 'url' => null, + 'uri' => null, + 'path' => null, + 'query' => null, + ] + ]; + + // Validate URL + if (!self::is($url)) { + + return false; + } + + // Parse host + if ($scheme = parse_url($url, PHP_URL_SCHEME)) { + + $result->host->url = $scheme . '://'; + $result->host->scheme = $scheme; + + } else { + + return false; + } + + if ($host = parse_url($url, PHP_URL_HOST)) { + + $result->host->url .= $host; + $result->host->name = $host; + + } else { + + return false; + } + + if ($port = parse_url($url, PHP_URL_PORT)) { + + $result->host->url .= ':' . $port; + $result->host->port = $port; + + // port is optional + } + + // Parse page + if ($path = parse_url($url, PHP_URL_PATH)) { + + $result->page->uri = $path; + $result->page->path = $path; + } + + if ($query = parse_url($url, PHP_URL_QUERY)) { + + $result->page->uri .= '?' . $query; + $result->page->query = '?' . $query; + } + + $result->page->url = $result->host->url . $result->page->uri; + + return $result; + } +} \ No newline at end of file diff --git a/library/yggdrasil.php b/library/yggdrasil.php new file mode 100644 index 0000000..dd90f92 --- /dev/null +++ b/library/yggdrasil.php @@ -0,0 +1,55 @@ +peers)) { + + return false; + } + + foreach ((object) $result->peers as $peer) { + + switch (false) { + + case isset($peer->bytes_recvd): + case isset($peer->bytes_sent): + case isset($peer->remote): + case isset($peer->port): + case isset($peer->key): + case isset($peer->uptime): + case !empty($peer->coords): + + return false; + } + } + + return $result->peers; + } +} \ No newline at end of file diff --git a/media/db-prototype.png b/media/db-prototype.png new file mode 100644 index 0000000..0fce076 Binary files /dev/null and b/media/db-prototype.png differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..e69de29