Defer server pubkey knowledge to post-fetch

dev
gravel 11 months ago
parent 3e22c7fc6e
commit 5b4ecdde73
Signed by: gravel
GPG Key ID: C0538F3C906B308F

@ -61,16 +61,6 @@
public readonly ?bool $write;
// Custom properties
/**
* @var string $language_flag
* Optional Unicode emoji of region matching
* the primary language of this room.
*
* Custom attribute.
*/
public readonly ?string $language_flag;
/**
* @var string[] $tags
* String tags applied to room by creator or submitter.
@ -80,8 +70,6 @@
private array $tags = [];
private function __construct(\CommunityServer $server, array $details) {
global $languages;
$this->server = $server;
$this->active_users = $details['active_users'];
$this->active_users_cutoff = $details['active_users_cutoff'];
@ -98,18 +86,26 @@
$this->write = $details['write'];
$this->upload = $details['upload'];
$this->extract_tags_from_description();
}
private const DESCRIPTION_TAGS_SPECIFICATION = '/(#[^#()@., ]+(?:,?\s*|\s+|$))+\s*$/';
/**
* Return an optional Unicode emoji of region matching
* the primary language of this room.
*/
public function get_language_flag(): string {
global $languages;
$room_identifier = $this->get_room_identifier();
$this->language_flag =
isset($languages[$room_identifier])
return isset($languages[$room_identifier])
? $languages[$room_identifier]
: "";
$this->extract_tags_from_description();
}
private const DESCRIPTION_TAGS_SPECIFICATION = '/(#[^#()@., ]+(?:,?\s*|\s+|$))+\s*$/';
/**
* Pre-processes SOGS data by treating description-trailing hashtags as room tags.
*/
@ -151,6 +147,7 @@
unset($details['server']);
$details['tags'] = $this->get_raw_tags();
$details['tags_custom'] = $this->get_derived_tags();
$details['language_flag'] = $this->get_language_flag();
return $details;
}
@ -166,7 +163,7 @@
"room" => $details,
"room_extra" => array(
"join_url" => $this->get_join_url(),
"language_flag" => $this->language_flag,
"language_flag" => $this->get_language_flag(),
"tags" => $this->get_raw_tags()
)
);
@ -288,7 +285,7 @@
*/
function get_join_url(): string {
$base_url = $this->server->base_url;
$pubkey = $this->server->pubkey;
$pubkey = $this->server->get_pubkey();
$token = $this->token;
return "$base_url/$token?public_key=$pubkey";
}
@ -314,7 +311,7 @@
*/
function get_room_identifier(): string {
$token = $this->token;
$pubkey_4 = substr($this->server->pubkey, 0, 4);
$pubkey_4 = substr($this->server->get_pubkey(), 0, 4);
return "$token+$pubkey_4";
}
@ -513,14 +510,26 @@
}
}
enum CommunityServerMergeStrategy {
case SameHostname;
case SamePublicKey;
public function should_merge_servers(\CommunityServer $a, \CommunityServer $b) {
return match ($this) {
CommunityServerMergeStrategy::SameHostname => $a->get_hostname() == $b->get_hostname(),
CommunityServerMergeStrategy::SamePublicKey => $a->get_pubkey() == $b->get_pubkey()
};
}
}
/**
* Class representing Session Community server hosting Community rooms.
*/
class CommunityServer implements JsonSerializable {
/** @var string $base_url The root URL of this server. */
public string $base_url = "";
/** @var string $pubkey The SOGS protocol pubkey of this server. */
public string $pubkey = "";
/** @var string[] $pubkey_candidates Possible SOGS protocol pubkeys for this server. */
private array $pubkey_candidates = [];
/** @var ?\CommunityRoom[] Array of rooms hosted by this server. */
public ?array $rooms = null;
/**
@ -571,7 +580,7 @@
* respectively.
*/
static function compare_by_pubkey($a, $b): int {
return strcmp($a->pubkey, $b->pubkey);
return strcmp($a->get_pubkey(), $b->get_pubkey());
}
/**
@ -579,41 +588,48 @@
* @param CommunityServer[] $servers
*/
public static function sort_by_pubkey(&$servers) {
foreach ($servers as $server) {
if (count($server->pubkey_candidates) != 1) {
$server->log_details();
$base_url = $server->base_url;
log_error("Server $base_url does not have a resolved pubkey before pubkey de-duping.");
exit(1);
}
}
usort($servers, 'CommunityServer::compare_by_pubkey');
}
private function absorb_pubkeys_from($server): void {
$this->pubkey_candidates = [
...$this->pubkey_candidates,
...$server->pubkey_candidates
];
}
/**
* Absorbs extra info from another instance of the same server.
* @param CommunityServer $server
*
* @return True if successful, false in case of mismatch.
*/
private function merge_from($server): bool {
private function merge_from($server, \CommunityServerMergeStrategy $strategy): bool {
// Merge room hint information.
$this->room_hints = [
...$this->room_hints,
...$server->room_hints
];
// Merge public key information.
// In case of error, set the `merge_error` flag.
if (!$this->set_pubkey($server->pubkey)) {
if ($this->merge_error) {
return false;
if ($strategy == CommunityServerMergeStrategy::SameHostname) {
if ($this->get_hostname() != $server->get_hostname()) {
log_error("SameHostname merging: Merged servers differ in hostname");
exit(1);
}
$this->absorb_pubkeys_from($server);
} else if ($strategy == CommunityServerMergeStrategy::SamePublicKey) {
if ($this->get_pubkey() != $server->get_pubkey()) {
log_error("SamePublicKey merging: Merged servers differ in public key");
exit(1);
}
$base_url = $this->base_url;
$other_base_url = $server->base_url;
$pubkey_old = $this->pubkey;
$pubkey_new = $server->pubkey;
log_error(
"Key collision for $base_url:" .
"Have $pubkey_old, fetched $pubkey_new" .
"from server $other_base_url"
);
$this->merge_error = true;
return false;
}
// Prefer HTTPS URLs over HTTP.
@ -643,6 +659,7 @@
// Remove duplicate room hints; does not require sorting.
foreach ($servers as $server) {
$server->room_hints = array_unique($server->room_hints);
$server->pubkey_candidates = array_unique($server->pubkey_candidates);
}
return $servers;
@ -653,13 +670,13 @@
* @param CommunityServer[] $servers Servers sorted by given attribute.
* @param string $method Method name to retrieve attribute from server.
*/
private static function merge_by(&$servers, string $method) {
private static function merge_by(&$servers, \CommunityServerMergeStrategy $strategy) {
// Backwards-merging to preserve indexing for unprocessed servers.
// Merging only makes sense for pairs, so stop at $i = 1.
for ($i = count($servers) - 1; $i >= 1; $i--) {
if ($servers[$i]->$method() == $servers[$i - 1]->$method()) {
if ($strategy->should_merge_servers($servers[$i], $servers[$i - 1])) {
// Merge this server into the previous one, discarding it.
$servers[$i - 1]->merge_from($servers[$i]);
$servers[$i - 1]->merge_from($servers[$i], $strategy);
array_splice($servers, $i, 1);
}
}
@ -672,7 +689,7 @@
$base_url = $this->base_url;
$count_rooms = count($this->rooms ?? []);
$count_room_hints = count($this->room_hints);
$pubkey = $this->pubkey ? truncate($this->pubkey, 4) : "unknown";
$pubkey = $this->has_pubkey() ? truncate($this->get_pubkey(), 4) : "unknown";
log_debug("Server $base_url"."[$count_rooms/$count_room_hints] { pubkey: $pubkey }");
}
@ -684,7 +701,7 @@
public static function dedupe_by_url($servers) {
CommunityServer::sort_by_url($servers);
CommunityServer::merge_by($servers, "get_hostname");
CommunityServer::merge_by($servers, CommunityServerMergeStrategy::SameHostname);
$servers = CommunityServer::ensure_merge_consistency($servers);
@ -699,7 +716,7 @@
public static function dedupe_by_pubkey($servers) {
CommunityServer::sort_by_pubkey($servers);
CommunityServer::merge_by($servers, "get_pubkey");
CommunityServer::merge_by($servers, CommunityServerMergeStrategy::SamePublicKey);
$servers = CommunityServer::ensure_merge_consistency($servers);
@ -713,6 +730,8 @@
$details = get_object_vars($this);
unset($details['room_hints']);
unset($details['merge_error']);
unset($details['pubkey_candidates']);
$details['pubkey'] = $this->get_pubkey();
return $details;
}
@ -738,7 +757,7 @@
throw new Error("Known server $hostname has no known pubkey");
}
$server->pubkey = $pubkeys[$hostname];
$server->set_pubkey($pubkeys[$hostname]);
$servers[] = $server;
}
@ -776,7 +795,7 @@
$server = new CommunityServer();
$server->base_url = $details['base_url'];
$server->pubkey = $details['pubkey'];
$server->set_pubkey($details['pubkey']);
$server->rooms = CommunityRoom::from_details_array($server, $details['rooms']);
return $server;
@ -911,7 +930,13 @@
* @return string SOGS pubkey as used in the Session protocol.
*/
function get_pubkey() {
return $this->pubkey;
if (!$this->has_pubkey()) {
$base_url = $this->base_url;
$count = count($this->pubkey_candidates);
log_error("Cannot get pubkey of server $base_url: has $count");
exit(1);
}
return $this->pubkey_candidates[0];
}
/**
@ -920,11 +945,11 @@
* @return bool True if successful, false in case of mismatch.
*/
function set_pubkey(string $pubkey): bool {
if ($this->has_pubkey() && $this->pubkey != $pubkey) {
if ($this->has_pubkey() && !in_array($pubkey, $this->pubkey_candidates, true)) {
return false;
}
$this->pubkey = $pubkey;
$this->pubkey_candidates = [$pubkey];
return true;
}
@ -956,10 +981,10 @@
/**
* Checks whether the current server SOGS public key is initialized.
* @return bool False if the public key is empty, true otherwise.
* @return bool False if the public key is unknown, true otherwise.
*/
function has_pubkey(): bool {
return $this->pubkey != "";
private function has_pubkey(): bool {
return count($this->pubkey_candidates) == 1;
}
/**
@ -1187,7 +1212,7 @@
// More information needs to be logged for errors
// in case of lack of context due to lower verbosity.
$base_url = $this->base_url;
$pubkey_old = $this->pubkey;
$pubkey_old = $this->get_pubkey();
$pubkey_new = url_get_pubkey($link);
log_error(
"Key collision for $base_url:" .
@ -1223,7 +1248,7 @@
global $KNOWN_PUBKEYS;
return (
$this->base_url == "https://open.getsession.org" &&
$this->pubkey == $KNOWN_PUBKEYS['open.getsession.org']
$this->get_pubkey() == $KNOWN_PUBKEYS['open.getsession.org']
);
}
}

@ -63,7 +63,7 @@
$hostname = $room->server->get_base_url();
$id = html_sanitize($room->get_room_identifier());
$language = html_sanitize($room->language_flag);
$language = html_sanitize($room->get_language_flag());
$name = html_sanitize($room->name);
$desc = html_sanitize($room->description);
$users = html_sanitize($room->active_users);

Loading…
Cancel
Save