Handle entity hash in server features node, make ServiceDiscovery request_info/items async, add caching has_entity_feature

This commit is contained in:
fiaxh 2020-04-23 13:53:44 +02:00
parent 9661116d78
commit e7bc68ad4d
10 changed files with 196 additions and 159 deletions

View file

@ -16,6 +16,8 @@ public class EntityCapabilitiesStorage : Xep.EntityCapabilities.Storage, Object
}
public void store_features(string entity, Gee.List<string> features) {
if (features_cache.contains(entity)) return;
foreach (string feature in features) {
db.entity_feature.insert()
.value(db.entity_feature.entity, entity)

View file

@ -386,24 +386,20 @@ public class MessageProcessor : StreamInteractionModule, Object {
return Entities.Message.Type.GROUPCHAT_PM;
}
} else {
SourceFunc callback = determine_message_type.callback;
XmppStream stream = stream_interactor.get_stream(account);
if (stream != null) stream.get_module(Xep.ServiceDiscovery.Module.IDENTITY).get_entity_categories(stream, message.counterpart.bare_jid, (stream, identities) => {
if (stream != null) {
Gee.Set<Xep.ServiceDiscovery.Identity>? identities = yield stream.get_module(Xep.ServiceDiscovery.Module.IDENTITY).get_entity_identities(stream, message.counterpart.bare_jid);
if (identities == null) {
message.type_ = Entities.Message.Type.CHAT;
Idle.add((owned) callback);
return;
return Entities.Message.Type.CHAT;
}
foreach (Xep.ServiceDiscovery.Identity identity in identities) {
if (identity.category == Xep.ServiceDiscovery.Identity.CATEGORY_CONFERENCE) {
message.type_ = Entities.Message.Type.GROUPCHAT_PM;
return Entities.Message.Type.GROUPCHAT_PM;
} else {
message.type_ = Entities.Message.Type.CHAT;
return Entities.Message.Type.CHAT;
}
}
Idle.add((owned) callback);
});
yield;
}
}
}
return Entities.Message.Type.CHAT;

View file

@ -23,8 +23,8 @@ public class Flag : XmppStreamFlag {
owned get { return own_identities_.read_only_view; }
}
public Gee.Set<Identity>? get_entity_categories(Jid jid) {
return entity_identities.has_key(jid) ? entity_identities[jid] : null; // TODO isnt this default for hashmap
public Gee.Set<Identity>? get_entity_identities(Jid jid) {
return entity_identities.has_key(jid) ? entity_identities[jid].read_only_view : null; // TODO isnt this default for hashmap
}
public bool? has_entity_identity(Jid jid, string category, string type) {

View file

@ -9,6 +9,8 @@ public const string NS_URI_ITEMS = NS_URI + "#items";
public class Module : XmppStreamModule, Iq.Handler {
public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0030_service_discovery_module");
private HashMap<Jid, Future<InfoResult?>> active_info_requests = new HashMap<Jid, Future<InfoResult?>>(Jid.hash_func, Jid.equals_func);
public Identity own_identity;
public Module.with_identity(string category, string type, string? name = null) {
@ -39,36 +41,63 @@ public class Module : XmppStreamModule, Iq.Handler {
stream.get_flag(Flag.IDENTITY).remove_own_identity(identity);
}
public delegate void HasEntryCategoryRes(XmppStream stream, Gee.Set<Identity>? identities);
public void get_entity_categories(XmppStream stream, Jid jid, owned HasEntryCategoryRes listener) {
Gee.Set<Identity>? res = stream.get_flag(Flag.IDENTITY).get_entity_categories(jid);
if (res != null) listener(stream, res);
request_info(stream, jid, (stream, query_result) => {
listener(stream, query_result != null ? query_result.identities : null);
});
public async bool has_entity_feature(XmppStream stream, Jid jid, string feature) {
Flag flag = stream.get_flag(Flag.IDENTITY);
if (flag.has_entity_feature(jid, feature) == null) {
InfoResult? info_result = yield request_info(stream, jid);
stream.get_flag(Flag.IDENTITY).set_entity_features(info_result.iq.from, info_result != null ? info_result.features : null);
stream.get_flag(Flag.IDENTITY).set_entity_identities(info_result.iq.from, info_result != null ? info_result.identities : null);
}
return flag.has_entity_feature(jid, feature);
}
public delegate void OnInfoResult(XmppStream stream, InfoResult? query_result);
public void request_info(XmppStream stream, Jid jid, owned OnInfoResult listener) {
Iq.Stanza iq = new Iq.Stanza.get(new StanzaNode.build("query", NS_URI_INFO).add_self_xmlns());
iq.to = jid;
stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, (stream, iq) => {
InfoResult? result = InfoResult.create_from_iq(iq);
stream.get_flag(Flag.IDENTITY).set_entity_features(iq.from, result != null ? result.features : null);
stream.get_flag(Flag.IDENTITY).set_entity_identities(iq.from, result != null ? result.identities : null);
listener(stream, result);
});
public async Gee.Set<Identity>? get_entity_identities(XmppStream stream, Jid jid) {
Flag flag = stream.get_flag(Flag.IDENTITY);
if (flag.get_entity_identities(jid) == null) {
InfoResult? info_result = yield request_info(stream, jid);
stream.get_flag(Flag.IDENTITY).set_entity_features(info_result.iq.from, info_result != null ? info_result.features : null);
stream.get_flag(Flag.IDENTITY).set_entity_identities(info_result.iq.from, info_result != null ? info_result.identities : null);
}
return flag.get_entity_identities(jid);
}
public delegate void OnItemsResult(XmppStream stream, ItemsResult query_result);
public void request_items(XmppStream stream, Jid jid, owned OnItemsResult listener) {
Iq.Stanza iq = new Iq.Stanza.get(new StanzaNode.build("query", NS_URI_ITEMS).add_self_xmlns());
iq.to = jid;
stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, (stream, iq) => {
ItemsResult? result = ItemsResult.create_from_iq(iq);
stream.get_flag(Flag.IDENTITY).set_entity_items(iq.from, result != null ? result.items : null);
listener(stream, result);
});
public async InfoResult? request_info(XmppStream stream, Jid jid) {
var future = active_info_requests[jid];
if (future == null) {
var promise = new Promise<InfoResult?>();
future = promise.future;
active_info_requests[jid] = future;
Iq.Stanza iq = new Iq.Stanza.get(new StanzaNode.build("query", NS_URI_INFO).add_self_xmlns()) { to=jid };
Iq.Stanza iq_response = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq);
InfoResult? result = InfoResult.create_from_iq(iq_response);
promise.set_value(result);
active_info_requests.unset(jid);
}
try {
InfoResult? res = yield future.wait_async();
return res;
} catch (FutureError error) {
warning("Future error when waiting for info request result: %s", error.message);
return null;
}
}
public async ItemsResult? request_items(XmppStream stream, Jid jid) {
StanzaNode query_node = new StanzaNode.build("query", NS_URI_ITEMS).add_self_xmlns();
Iq.Stanza iq = new Iq.Stanza.get(query_node) { to=jid };
Iq.Stanza iq_result = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq);
ItemsResult? result = ItemsResult.create_from_iq(iq_result);
stream.get_flag(Flag.IDENTITY).set_entity_items(iq_result.from, result != null ? result.items : null);
return result;
}
public void on_iq_get(XmppStream stream, Iq.Stanza iq) {

View file

@ -99,7 +99,7 @@ public class Module : XmppStreamModule {
stream.get_flag(Flag.IDENTITY).start_muc_enter(bare_jid, presence.id);
query_room_info(stream, bare_jid);
query_room_info.begin(stream, bare_jid);
stream.get_module(Presence.Module.IDENTITY).send_presence(stream, presence);
var promise = new Promise<JoinResult?>();
@ -273,7 +273,7 @@ public class Module : XmppStreamModule {
if (status_codes.contains(StatusCode.CONFIG_CHANGE_NON_PRIVACY) ||
status_codes.contains(StatusCode.NON_ANONYMOUS) ||
status_codes.contains(StatusCode.SEMI_ANONYMOUS)) {
query_room_info(stream, message.from.bare_jid);
query_room_info.begin(stream, message.from.bare_jid);
}
}
}
@ -398,45 +398,43 @@ public class Module : XmppStreamModule {
}
}
private void query_room_info(XmppStream stream, Jid jid) {
stream.get_module(ServiceDiscovery.Module.IDENTITY).request_info(stream, jid, (stream, query_result) => {
private async void query_room_info(XmppStream stream, Jid jid) {
ServiceDiscovery.InfoResult? info_result = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).request_info(stream, jid);
if (info_result == null) return;
Gee.List<Feature> features = new ArrayList<Feature>();
if (query_result != null) {
Gee.List<Feature> features = new ArrayList<Feature>();
foreach (ServiceDiscovery.Identity identity in query_result.identities) {
if (identity.category == "conference") {
stream.get_flag(Flag.IDENTITY).set_room_name(jid, identity.name);
}
}
foreach (string feature in query_result.features) {
Feature? parsed = null;
switch (feature) {
case "http://jabber.org/protocol/muc#register": parsed = Feature.REGISTER; break;
case "http://jabber.org/protocol/muc#roomconfig": parsed = Feature.ROOMCONFIG; break;
case "http://jabber.org/protocol/muc#roominfo": parsed = Feature.ROOMINFO; break;
case "http://jabber.org/protocol/muc#stable_id": parsed = Feature.STABLE_ID; break;
case "muc_hidden": parsed = Feature.HIDDEN; break;
case "muc_membersonly": parsed = Feature.MEMBERS_ONLY; break;
case "muc_moderated": parsed = Feature.MODERATED; break;
case "muc_nonanonymous": parsed = Feature.NON_ANONYMOUS; break;
case "muc_open": parsed = Feature.OPEN; break;
case "muc_passwordprotected": parsed = Feature.PASSWORD_PROTECTED; break;
case "muc_persistent": parsed = Feature.PERSISTENT; break;
case "muc_public": parsed = Feature.PUBLIC; break;
case "muc_rooms": parsed = Feature.ROOMS; break;
case "muc_semianonymous": parsed = Feature.SEMI_ANONYMOUS; break;
case "muc_temporary": parsed = Feature.TEMPORARY; break;
case "muc_unmoderated": parsed = Feature.UNMODERATED; break;
case "muc_unsecured": parsed = Feature.UNSECURED; break;
}
if (parsed != null) features.add(parsed);
}
foreach (ServiceDiscovery.Identity identity in info_result.identities) {
if (identity.category == "conference") {
stream.get_flag(Flag.IDENTITY).set_room_name(jid, identity.name);
}
stream.get_flag(Flag.IDENTITY).set_room_features(jid, features);
room_info_updated(stream, jid);
});
}
foreach (string feature in info_result.features) {
Feature? parsed = null;
switch (feature) {
case "http://jabber.org/protocol/muc#register": parsed = Feature.REGISTER; break;
case "http://jabber.org/protocol/muc#roomconfig": parsed = Feature.ROOMCONFIG; break;
case "http://jabber.org/protocol/muc#roominfo": parsed = Feature.ROOMINFO; break;
case "http://jabber.org/protocol/muc#stable_id": parsed = Feature.STABLE_ID; break;
case "muc_hidden": parsed = Feature.HIDDEN; break;
case "muc_membersonly": parsed = Feature.MEMBERS_ONLY; break;
case "muc_moderated": parsed = Feature.MODERATED; break;
case "muc_nonanonymous": parsed = Feature.NON_ANONYMOUS; break;
case "muc_open": parsed = Feature.OPEN; break;
case "muc_passwordprotected": parsed = Feature.PASSWORD_PROTECTED; break;
case "muc_persistent": parsed = Feature.PERSISTENT; break;
case "muc_public": parsed = Feature.PUBLIC; break;
case "muc_rooms": parsed = Feature.ROOMS; break;
case "muc_semianonymous": parsed = Feature.SEMI_ANONYMOUS; break;
case "muc_temporary": parsed = Feature.TEMPORARY; break;
case "muc_unmoderated": parsed = Feature.UNMODERATED; break;
case "muc_unsecured": parsed = Feature.UNSECURED; break;
}
if (parsed != null) features.add(parsed);
}
stream.get_flag(Flag.IDENTITY).set_room_features(jid, features);
room_info_updated(stream, jid);
}
public delegate void OnAffiliationResult(XmppStream stream, Gee.List<Jid> jids);

View file

@ -23,7 +23,7 @@ public class Module : XmppStreamModule, Iq.Handler {
public override void attach(XmppStream stream) {
stream.add_flag(new Flag());
query_availability(stream);
query_availability.begin(stream);
}
public override void detach(XmppStream stream) { }
@ -33,41 +33,36 @@ public class Module : XmppStreamModule, Iq.Handler {
return stream.get_flag(Flag.IDENTITY).proxies;
}
private void query_availability(XmppStream stream) {
stream.get_module(ServiceDiscovery.Module.IDENTITY).request_items(stream, stream.remote_name, (stream, items_result) => {
foreach (Xep.ServiceDiscovery.Item item in items_result.items) {
stream.get_module(ServiceDiscovery.Module.IDENTITY).request_info(stream, item.jid, (stream, info_result) => {
foreach (string feature in info_result.features) {
if (feature == NS_URI) {
StanzaNode query_ = new StanzaNode.build("query", NS_URI).add_self_xmlns();
Iq.Stanza iq = new Iq.Stanza.get(query_) { to=item.jid };
stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, (stream, iq) => {
if (iq.is_error()) {
return;
}
StanzaNode? query = iq.stanza.get_subnode("query", NS_URI);
StanzaNode? stream_host = query != null ? query.get_subnode("streamhost", NS_URI) : null;
if (query == null || stream_host == null) {
return;
}
string? host = stream_host.get_attribute("host");
string? jid_str = stream_host.get_attribute("jid");
Jid? jid = null;
try {
jid = jid_str != null ? new Jid(jid_str) : null;
} catch (InvalidJidError ignored) {
}
int port = stream_host.get_attribute_int("port");
if (host == null || jid == null || port <= 0 || port > 65535) {
return;
}
stream.get_flag(Flag.IDENTITY).proxies.add(new Proxy(host, jid, port));
});
}
}
});
private async void query_availability(XmppStream stream) {
ServiceDiscovery.ItemsResult? items_result = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).request_items(stream, stream.remote_name);
foreach (Xep.ServiceDiscovery.Item item in items_result.items) {
bool has_feature = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).has_entity_feature(stream, item.jid, NS_URI);
if (!has_feature) continue;
StanzaNode query_node = new StanzaNode.build("query", NS_URI).add_self_xmlns();
Iq.Stanza iq = new Iq.Stanza.get(query_node) { to=item.jid };
Iq.Stanza iq_result = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq);
if (iq_result.is_error()) continue;
StanzaNode? query_result_node = iq_result.stanza.get_subnode("query", NS_URI);
StanzaNode? stream_host = query_result_node != null ? query_result_node.get_subnode("streamhost", NS_URI) : null;
if (query_result_node == null || stream_host == null) {
return;
}
});
string? host = stream_host.get_attribute("host");
string? jid_str = stream_host.get_attribute("jid");
Jid? jid = null;
try {
jid = jid_str != null ? new Jid(jid_str) : null;
} catch (InvalidJidError ignored) { }
int port = stream_host.get_attribute_int("port");
if (host == null || jid == null || port <= 0 || port > 65535) {
continue;
}
stream.get_flag(Flag.IDENTITY).proxies.add(new Proxy(host, jid, port));
}
}
public override string get_ns() { return NS_URI; }

View file

@ -5,10 +5,15 @@ namespace Xmpp.Xep.EntityCapabilities {
private Regex? sha1_base64_regex = null;
public string? get_caps_hash(Presence.Stanza presence) {
private Regex get_sha1_base64_regex() {
if (sha1_base64_regex == null) {
sha1_base64_regex = /^[A-Za-z0-9+\/]{27}=$/;
}
return sha1_base64_regex;
}
public string? get_caps_hash(Presence.Stanza presence) {
Regex sha1_base64_regex = get_sha1_base64_regex();
StanzaNode? c_node = presence.stanza.get_subnode("c", NS_URI);
if (c_node == null) return null;
string? ver_attribute = c_node.get_attribute("ver", NS_URI);
@ -37,6 +42,8 @@ namespace Xmpp.Xep.EntityCapabilities {
stream.get_module(Presence.Module.IDENTITY).pre_send_presence_stanza.connect(on_pre_send_presence_stanza);
stream.get_module(Presence.Module.IDENTITY).received_presence.connect(on_received_presence);
stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI);
check_features_node_ver(stream);
}
public override void detach(XmppStream stream) {
@ -61,14 +68,27 @@ namespace Xmpp.Xep.EntityCapabilities {
string? caps_hash = get_caps_hash(presence);
if (caps_hash == null) return;
process_hash.begin(stream, presence.from, caps_hash);
}
private void check_features_node_ver(XmppStream stream) {
StanzaNode? node = stream.features.get_subnode("c", NS_URI);
if (node == null) return;
string? ver_attribute = node.get_attribute("ver", NS_URI);
if (ver_attribute == null) return;
process_hash.begin(stream, stream.remote_name, ver_attribute);
}
private async void process_hash(XmppStream stream, Jid jid_from, string caps_hash) {
Gee.List<string> capabilities = storage.get_features(caps_hash);
ServiceDiscovery.Identity identity = storage.get_identities(caps_hash);
if (identity == null) {
stream.get_module(ServiceDiscovery.Module.IDENTITY).request_info(stream, presence.from, (stream, query_result) => {
store_entity_result(stream, caps_hash, query_result);
});
ServiceDiscovery.InfoResult? info_result = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).request_info(stream, jid_from);
store_entity_result(stream, caps_hash, info_result);
} else {
stream.get_flag(ServiceDiscovery.Flag.IDENTITY).set_entity_features(presence.from, capabilities);
stream.get_flag(ServiceDiscovery.Flag.IDENTITY).set_entity_features(jid_from, capabilities);
}
}

View file

@ -80,29 +80,24 @@ public class Module : XmppStreamModule, Iq.Handler {
public override string get_ns() { return NS_URI; }
public override string get_id() { return IDENTITY.id; }
private void on_stream_negotiated(XmppStream stream) {
stream.get_module(Xep.ServiceDiscovery.Module.IDENTITY).request_info(stream, stream.remote_name, (stream, info_result) => {
if (info_result.features.contains(NS_URI)) {
stream.add_flag(new Flag());
get_blocklist(stream, (stream, jids) => {
stream.get_flag(Flag.IDENTITY).blocklist = jids;
});
return;
}
});
private async void on_stream_negotiated(XmppStream stream) {
bool has_feature = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).has_entity_feature(stream, stream.remote_name, NS_URI);
if (has_feature) {
stream.add_flag(new Flag());
stream.get_flag(Flag.IDENTITY).blocklist = yield get_blocklist(stream);
}
}
private delegate void OnBlocklist(XmppStream stream, Gee.List<string> jids);
private void get_blocklist(XmppStream stream, owned OnBlocklist listener) {
private async Gee.List<string> get_blocklist(XmppStream stream) {
StanzaNode blocklist_node = new StanzaNode.build("blocklist", NS_URI).add_self_xmlns();
Iq.Stanza iq = new Iq.Stanza.get(blocklist_node);
stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, (stream, iq) => {
StanzaNode? node = iq.stanza.get_subnode("blocklist", NS_URI);
if (node != null) {
Gee.List<string> jids = get_jids_from_items(node);
listener(stream, jids);
}
});
Iq.Stanza result_iq = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq);
StanzaNode? node = result_iq.stanza.get_subnode("blocklist", NS_URI);
if (node != null) {
return get_jids_from_items(node);
}
return new ArrayList<string>();
}
private Gee.List<string> get_jids_from_items(StanzaNode node) {

View file

@ -108,17 +108,22 @@ public class Module : XmppStreamModule {
return result_iq;
}
private void query_availability(XmppStream stream) {
stream.get_module(Xep.ServiceDiscovery.Module.IDENTITY).request_info(stream, stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid, (stream, info_result) => {
if (info_result == null) return;
if (info_result.features.contains(NS_URI)) {
stream.add_flag(new Flag(NS_URI));
feature_available(stream);
} else if (info_result.features.contains(NS_URI_1)) {
stream.add_flag(new Flag(NS_URI_1));
feature_available(stream);
}
});
private async void query_availability(XmppStream stream) {
Jid own_jid = stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid;
bool ver_2_available = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).has_entity_feature(stream, own_jid, NS_URI);
if (ver_2_available) {
stream.add_flag(new Flag(NS_URI));
feature_available(stream);
return;
}
bool ver_1_available = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).has_entity_feature(stream, own_jid, NS_URI_1);
if (ver_1_available) {
stream.add_flag(new Flag(NS_URI_1));
feature_available(stream);
return;
}
}
}

View file

@ -105,25 +105,22 @@ public class Module : XmppStreamModule {
}
public override void detach(XmppStream stream) {
stream.get_module(Bind.Module.IDENTITY).bound_to_resource.disconnect(query_availability);
stream.stream_negotiated.disconnect(query_availability);
}
public override string get_ns() { return NS_URI; }
public override string get_id() { return IDENTITY.id; }
private void query_availability(XmppStream stream) {
stream.get_module(ServiceDiscovery.Module.IDENTITY).request_info(stream, stream.remote_name, (stream, info_result) => {
bool available = check_ns_in_info(stream, stream.remote_name, info_result);
if (!available) {
stream.get_module(ServiceDiscovery.Module.IDENTITY).request_items(stream, stream.remote_name, (stream, items_result) => {
foreach (Xep.ServiceDiscovery.Item item in items_result.items) {
stream.get_module(ServiceDiscovery.Module.IDENTITY).request_info(stream, item.jid, (stream, info_result) => {
check_ns_in_info(stream, item.jid, info_result);
});
}
});
private async void query_availability(XmppStream stream) {
ServiceDiscovery.InfoResult? info_result = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).request_info(stream, stream.remote_name);
bool available = check_ns_in_info(stream, stream.remote_name, info_result);
if (!available) {
ServiceDiscovery.ItemsResult? items_result = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).request_items(stream, stream.remote_name);
foreach (Xep.ServiceDiscovery.Item item in items_result.items) {
ServiceDiscovery.InfoResult? info_result2 = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).request_info(stream, item.jid);
check_ns_in_info(stream, item.jid, info_result2);
}
});
}
}
private bool check_ns_in_info(XmppStream stream, Jid jid, Xep.ServiceDiscovery.InfoResult info_result) {