diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml
index 2f0c570..016146b 100644
--- a/.idea/sqldialects.xml
+++ b/.idea/sqldialects.xml
@@ -7,6 +7,7 @@
+
diff --git a/src/Socialbox/Classes/Configuration.php b/src/Socialbox/Classes/Configuration.php
index 7b5814f..fe86406 100644
--- a/src/Socialbox/Classes/Configuration.php
+++ b/src/Socialbox/Classes/Configuration.php
@@ -60,9 +60,9 @@ class Configuration
$config->save();
self::$configuration = $config->getConfiguration();
- self::$databaseConfiguration = self::$configuration['database'];
- self::$cacheConfiguration = self::$configuration['cache'];
- self::$registrationConfiguration = self::$configuration['registration'];
+ self::$databaseConfiguration = new DatabaseConfiguration(self::$configuration['database']);
+ self::$cacheConfiguration = new CacheConfiguration(self::$configuration['cache']);
+ self::$registrationConfiguration = new RegistrationConfiguration(self::$configuration['registration']);
}
/**
diff --git a/src/Socialbox/Classes/ServerResolver.php b/src/Socialbox/Classes/ServerResolver.php
index 58bf1ff..29d25a1 100644
--- a/src/Socialbox/Classes/ServerResolver.php
+++ b/src/Socialbox/Classes/ServerResolver.php
@@ -2,51 +2,96 @@
namespace Socialbox\Classes;
+use Socialbox\Exceptions\DatabaseOperationException;
use Socialbox\Exceptions\ResolutionException;
+use Socialbox\Managers\ResolvedServersManager;
use Socialbox\Objects\ResolvedServer;
class ServerResolver
{
+ private const string PATTERN = '/v=socialbox;sb-rpc=(https?:\/\/[^;]+);sb-key=([^;]+)/';
+
/**
* Resolves a given domain to fetch the RPC endpoint and public key from its DNS TXT records.
*
* @param string $domain The domain to be resolved.
* @return ResolvedServer An instance of ResolvedServer containing the endpoint and public key.
* @throws ResolutionException If the DNS TXT records cannot be resolved or if required information is missing.
+ * @throws DatabaseOperationException
*/
public static function resolveDomain(string $domain): ResolvedServer
{
- $txtRecords = dns_get_record($domain, DNS_TXT);
+ // First query the database to check if the domain is already resolved
+ if(ResolvedServersManager::resolvedServerExists($domain))
+ {
+ // If the resolved server was updated in the last 30 minutes, return it
+ if(ResolvedServersManager::getResolvedServerUpdated($domain) > (time() - 1800))
+ {
+ return ResolvedServersManager::getResolvedServer($domain)->toResolvedServer();
+ }
+ }
+
+ $txtRecords = self::dnsGetTxtRecords($domain);
if ($txtRecords === false)
{
throw new ResolutionException(sprintf("Failed to resolve DNS TXT records for %s", $domain));
}
- $endpoint = null;
- $publicKey = null;
+ $fullRecord = self::concatenateTxtRecords($txtRecords);
+
+ if (preg_match(self::PATTERN, $fullRecord, $matches))
+ {
+ $endpoint = trim($matches[1]);
+ $publicKey = trim(str_replace(' ', '', $matches[2]));
+
+ if (empty($endpoint))
+ {
+ throw new ResolutionException(sprintf("Failed to resolve RPC endpoint for %s", $domain));
+ }
+
+ if (empty($publicKey))
+ {
+ throw new ResolutionException(sprintf("Failed to resolve public key for %s", $domain));
+ }
+
+ return new ResolvedServer($endpoint, $publicKey);
+ }
+ else
+ {
+ throw new ResolutionException(sprintf("Failed to find valid SocialBox record for %s", $domain));
+ }
+ }
+
+ /**
+ * Retrieves the TXT records for a given domain using the dns_get_record function.
+ *
+ * @param string $domain The domain name to fetch TXT records for.
+ * @return array|false An array of DNS TXT records on success, or false on failure.
+ */
+ private static function dnsGetTxtRecords(string $domain)
+ {
+ return dns_get_record($domain, DNS_TXT);
+ }
+
+ /**
+ * Concatenates an array of TXT records into a single string.
+ *
+ * @param array $txtRecords An array of TXT records, where each record is expected to have a 'txt' key.
+ * @return string A concatenated string of all TXT records.
+ */
+ private static function concatenateTxtRecords(array $txtRecords): string
+ {
+ $fullRecordBuilder = '';
+
foreach ($txtRecords as $txt)
{
- if (isset($txt['txt']) && str_starts_with($txt['txt'], 'socialbox='))
+ if (isset($txt['txt']))
{
- $endpoint = substr($txt['txt'], strlen('socialbox='));
- }
- elseif (isset($txt['txt']) && str_starts_with($txt['txt'], 'socialbox-key='))
- {
- $publicKey = substr($txt['txt'], strlen('socialbox-key='));
+ $fullRecordBuilder .= trim($txt['txt'], '" ');
}
}
- if ($endpoint === null)
- {
- throw new ResolutionException(sprintf("Failed to resolve RPC endpoint for %s", $domain));
- }
-
- if ($publicKey === null)
- {
- throw new ResolutionException(sprintf("Failed to resolve public key for %s", $domain));
- }
-
- return new ResolvedServer($endpoint, $publicKey);
+ return $fullRecordBuilder;
}
}
\ No newline at end of file
diff --git a/src/Socialbox/Managers/ResolvedServersManager.php b/src/Socialbox/Managers/ResolvedServersManager.php
new file mode 100644
index 0000000..69ae2d1
--- /dev/null
+++ b/src/Socialbox/Managers/ResolvedServersManager.php
@@ -0,0 +1,145 @@
+prepare("SELECT COUNT(*) FROM resolved_servers WHERE domain=?");
+ $statement->bindParam(1, $domain);
+ $statement->execute();
+ return $statement->fetchColumn() > 0;
+ }
+ catch(PDOException $e)
+ {
+ throw new DatabaseOperationException('Failed to check if a resolved server exists in the database', $e);
+ }
+ }
+
+ /**
+ * Deletes a resolved server from the database.
+ *
+ * @param string $domain The domain name of the server to be deleted.
+ * @return void
+ * @throws DatabaseOperationException If the deletion operation fails.
+ */
+ public static function deleteResolvedServer(string $domain): void
+ {
+ try
+ {
+ $statement = Database::getConnection()->prepare("DELETE FROM resolved_servers WHERE domain=?");
+ $statement->bindParam(1, $domain);
+ $statement->execute();
+ }
+ catch(PDOException $e)
+ {
+ throw new DatabaseOperationException('Failed to delete a resolved server from the database', $e);
+ }
+ }
+
+ /**
+ * Retrieves the last updated date of a resolved server based on its domain.
+ *
+ * @param string $domain The domain of the resolved server.
+ * @return DateTime The last updated date and time of the resolved server.
+ */
+ public static function getResolvedServerUpdated(string $domain): DateTime
+ {
+ try
+ {
+ $statement = Database::getConnection()->prepare("SELECT updated FROM resolved_servers WHERE domain=?");
+ $statement->bindParam(1, $domain);
+ $statement->execute();
+ $result = $statement->fetchColumn();
+ return new DateTime($result);
+ }
+ catch(Exception $e)
+ {
+ throw new DatabaseOperationException('Failed to get the updated date of a resolved server from the database', $e);
+ }
+ }
+
+ /**
+ * Retrieves the resolved server record from the database for a given domain.
+ *
+ * @param string $domain The domain name for which to retrieve the resolved server record.
+ * @return ResolvedServerRecord The resolved server record associated with the given domain.
+ * @throws DatabaseOperationException If there is an error retrieving the resolved server record from the database.
+ */
+ public static function getResolvedServer(string $domain): ResolvedServerRecord
+ {
+ try
+ {
+ $statement = Database::getConnection()->prepare("SELECT * FROM resolved_servers WHERE domain=?");
+ $statement->bindParam(1, $domain);
+ $statement->execute();
+ $result = $statement->fetch();
+ return new ResolvedServerRecord($result);
+ }
+ catch(PDOException $e)
+ {
+ throw new DatabaseOperationException('Failed to get a resolved server from the database', $e);
+ }
+ }
+
+ /**
+ * Adds or updates a resolved server in the database.
+ *
+ * @param string $domain The domain name of the resolved server.
+ * @param ResolvedServer $resolvedServer The resolved server object containing endpoint and public key.
+ * @return void
+ * @throws DatabaseOperationException If a database operation fails.
+ */
+ public static function addResolvedServer(string $domain, ResolvedServer $resolvedServer): void
+ {
+ $endpoint = $resolvedServer->getEndpoint();
+ $publicKey = $resolvedServer->getPublicKey();
+
+ if(self::resolvedServerExists($domain))
+ {
+ try
+ {
+ $statement = Database::getConnection()->prepare("UPDATE resolved_servers SET endpoint=?, public_key=?, updated=NOW() WHERE domain=?");
+ $statement->bindParam(1, $endpoint);
+ $statement->bindParam(2, $publicKey);
+ $statement->bindParam(3, $domain);
+ $statement->execute();
+ }
+ catch(PDOException $e)
+ {
+ throw new DatabaseOperationException('Failed to update a resolved server in the database', $e);
+ }
+ }
+
+ try
+ {
+ $statement = Database::getConnection()->prepare("INSERT INTO resolved_servers (domain, endpoint, public_key) VALUES (?, ?, ?)");
+ $statement->bindParam(1, $domain);
+ $statement->bindParam(2, $endpoint);
+ $statement->bindParam(3, $publicKey);
+ $statement->execute();
+ }
+ catch(PDOException $e)
+ {
+ throw new DatabaseOperationException('Failed to add a resolved server to the database', $e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Socialbox/Objects/Database/ResolvedServerRecord.php b/src/Socialbox/Objects/Database/ResolvedServerRecord.php
index 262a8b1..12a31fb 100644
--- a/src/Socialbox/Objects/Database/ResolvedServerRecord.php
+++ b/src/Socialbox/Objects/Database/ResolvedServerRecord.php
@@ -2,17 +2,97 @@
namespace Socialbox\Objects\Database;
+use DateTime;
use Socialbox\Interfaces\SerializableInterface;
+use Socialbox\Objects\ResolvedServer;
class ResolvedServerRecord implements SerializableInterface
{
+ private string $domain;
+ private string $endpoint;
+ private string $publicKey;
+ private DateTime $updated;
+
+ /**
+ * Constructs a new instance of the class.
+ *
+ * @param array $data An associative array containing the domain, endpoint, public_key, and updated values.
+ * @throws \DateMalformedStringException
+ */
+ public function __construct(array $data)
+ {
+ $this->domain = (string)$data['domain'];
+ $this->endpoint = (string)$data['endpoint'];
+ $this->publicKey = (string)$data['public_key'];
+
+ if(is_null($data['updated']))
+ {
+ $this->updated = new DateTime();
+ }
+ elseif (is_string($data['updated']))
+ {
+ $this->updated = new DateTime($data['updated']);
+ }
+ else
+ {
+ $this->updated = $data['updated'];
+ }
+ }
+
+ /**
+ *
+ * @return string The domain value.
+ */
+ public function getDomain(): string
+ {
+ return $this->domain;
+ }
+
+ /**
+ *
+ * @return string The endpoint value.
+ */
+ public function getEndpoint(): string
+ {
+ return $this->endpoint;
+ }
+
+ /**
+ *
+ * @return string The public key.
+ */
+ public function getPublicKey(): string
+ {
+ return $this->publicKey;
+ }
+
+ /**
+ * Retrieves the timestamp of the last update.
+ *
+ * @return DateTime The DateTime object representing the last update time.
+ */
+ public function getUpdated(): DateTime
+ {
+ return $this->updated;
+ }
+
+ /**
+ * Converts the record to a ResolvedServer object.
+ *
+ * @return ResolvedServer The ResolvedServer object.
+ */
+ public function toResolvedServer(): ResolvedServer
+ {
+ return new ResolvedServer($this->endpoint, $this->publicKey);
+ }
/**
* @inheritDoc
+ * @throws \DateMalformedStringException
*/
public static function fromArray(array $data): object
{
- // TODO: Implement fromArray() method.
+ return new self($data);
}
/**
@@ -20,6 +100,11 @@ class ResolvedServerRecord implements SerializableInterface
*/
public function toArray(): array
{
- // TODO: Implement toArray() method.
+ return [
+ 'domain' => $this->domain,
+ 'endpoint' => $this->endpoint,
+ 'public_key' => $this->publicKey,
+ 'updated' => $this->updated->format('Y-m-d H:i:s')
+ ];
}
}
\ No newline at end of file
diff --git a/tests/Socialbox/Managers/ResolvedServersManagerTest.php b/tests/Socialbox/Managers/ResolvedServersManagerTest.php
new file mode 100644
index 0000000..0813345
--- /dev/null
+++ b/tests/Socialbox/Managers/ResolvedServersManagerTest.php
@@ -0,0 +1,43 @@
+assertInstanceOf(DateTime::class, ResolvedServersManager::getResolvedServerUpdated('n64.cc'));
+ }
+
+ public function testResolvedServerExists()
+ {
+ ResolvedServersManager::addResolvedServer('n64.cc', ServerResolver::resolveDomain('n64.cc'));
+ $this->assertTrue(ResolvedServersManager::resolvedServerExists('n64.cc'));
+ }
+
+ public function testGetResolvedServer()
+ {
+ ResolvedServersManager::addResolvedServer('n64.cc', ServerResolver::resolveDomain('n64.cc'));
+ $resolvedServer = ResolvedServersManager::getResolvedServer('n64.cc');
+
+ $this->assertEquals('n64.cc', $resolvedServer->getDomain());
+ $this->assertIsString($resolvedServer->getEndpoint());
+ $this->assertIsString($resolvedServer->getPublicKey());
+ $this->assertInstanceOf(DateTime::class, $resolvedServer->getUpdated());
+ }
+}
diff --git a/tests/Socialbox/Managers/SessionManagerTest.php b/tests/Socialbox/Managers/SessionManagerTest.php
index b376361..8fea5cb 100644
--- a/tests/Socialbox/Managers/SessionManagerTest.php
+++ b/tests/Socialbox/Managers/SessionManagerTest.php
@@ -35,6 +35,6 @@ class SessionManagerTest extends TestCase
$this->assertInstanceOf(SessionRecord::class, $session);
$this->assertEquals($uuid, $session->getUuid());
- $this->assertEquals($keyPair->getPublicKey(), Utilities::base64encode($session->getPublicKey()));
+ $this->assertEquals($keyPair->getPublicKey(), $session->getPublicKey());
}
}