1using Gee;
2
3namespace Xmpp.Xep.ServiceDiscovery {
4
5public const string NS_URI = "http://jabber.org/protocol/disco";
6public const string NS_URI_INFO = NS_URI + "#info";
7public const string NS_URI_ITEMS = NS_URI + "#items";
8
9public class Module : XmppStreamModule, Iq.Handler {
10    public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0030_service_discovery_module");
11
12    private HashMap<Jid, Future<InfoResult?>> active_info_requests = new HashMap<Jid, Future<InfoResult?>>(Jid.hash_func, Jid.equals_func);
13
14    public Identity own_identity;
15    public CapsCache cache;
16
17    public Module.with_identity(string category, string type, string? name = null) {
18        this.own_identity = new Identity(category, type, name);
19    }
20
21    public void add_feature(XmppStream stream, string feature) {
22        stream.get_flag(Flag.IDENTITY).add_own_feature(feature);
23    }
24
25    public void remove_feature(XmppStream stream, string feature) {
26        stream.get_flag(Flag.IDENTITY).remove_own_feature(feature);
27    }
28
29    public void add_feature_notify(XmppStream stream, string feature) {
30        add_feature(stream, feature + "+notify");
31    }
32
33    public void remove_feature_notify(XmppStream stream, string feature) {
34        remove_feature(stream, feature + "+notify");
35    }
36
37    public void add_identity(XmppStream stream, Identity identity) {
38        stream.get_flag(Flag.IDENTITY).add_own_identity(identity);
39    }
40
41    public void remove_identity(XmppStream stream, Identity identity) {
42        stream.get_flag(Flag.IDENTITY).remove_own_identity(identity);
43    }
44
45    public async bool has_entity_feature(XmppStream stream, Jid jid, string feature) {
46        return yield this.cache.has_entity_feature(jid, feature);
47    }
48
49    public async Gee.Set<Identity>? get_entity_identities(XmppStream stream, Jid jid) {
50        return yield this.cache.get_entity_identities(jid);
51    }
52
53    public async InfoResult? request_info(XmppStream stream, Jid jid) {
54        var future = active_info_requests[jid];
55        if (future == null) {
56            var promise = new Promise<InfoResult?>();
57            future = promise.future;
58            active_info_requests[jid] = future;
59
60            Iq.Stanza iq = new Iq.Stanza.get(new StanzaNode.build("query", NS_URI_INFO).add_self_xmlns()) { to=jid };
61            Iq.Stanza iq_response = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq);
62            InfoResult? result = InfoResult.create_from_iq(iq_response);
63
64            promise.set_value(result);
65            active_info_requests.unset(jid);
66        }
67
68        try {
69            InfoResult? res = yield future.wait_async();
70            return res;
71        } catch (FutureError error) {
72            warning("Future error when waiting for info request result: %s", error.message);
73            return null;
74        }
75    }
76
77    public async ItemsResult? request_items(XmppStream stream, Jid jid) {
78        StanzaNode query_node = new StanzaNode.build("query", NS_URI_ITEMS).add_self_xmlns();
79        Iq.Stanza iq = new Iq.Stanza.get(query_node) { to=jid };
80
81        Iq.Stanza iq_result = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq);
82        ItemsResult? result = ItemsResult.create_from_iq(iq_result);
83
84        return result;
85    }
86
87    public async void on_iq_get(XmppStream stream, Iq.Stanza iq) {
88        StanzaNode? query_node = iq.stanza.get_subnode("query", NS_URI_INFO);
89        if (query_node != null) {
90            send_query_result(stream, iq);
91        }
92    }
93
94    public override void attach(XmppStream stream) {
95        stream.add_flag(new Flag());
96        add_identity(stream, own_identity);
97
98        stream.get_module(Iq.Module.IDENTITY).register_for_namespace(NS_URI_INFO, this);
99        add_feature(stream, NS_URI_INFO);
100    }
101
102    public override void detach(XmppStream stream) {
103        active_info_requests.clear();
104
105        remove_identity(stream, own_identity);
106
107        stream.get_module(Iq.Module.IDENTITY).unregister_from_namespace(NS_URI_INFO, this);
108        remove_feature(stream, NS_URI_INFO);
109    }
110
111    public static void require(XmppStream stream) {
112        if (stream.get_module(IDENTITY) == null) stream.add_module(new ServiceDiscovery.Module());
113    }
114
115    public override string get_ns() { return NS_URI; }
116    public override string get_id() { return IDENTITY.id; }
117
118    private void send_query_result(XmppStream stream, Iq.Stanza iq_request) {
119        InfoResult query_result = new ServiceDiscovery.InfoResult(iq_request);
120        query_result.features = stream.get_flag(Flag.IDENTITY).own_features;
121        query_result.identities = stream.get_flag(Flag.IDENTITY).own_identities;
122        stream.get_module(Iq.Module.IDENTITY).send_iq(stream, query_result.iq);
123    }
124}
125
126public interface CapsCache : Object {
127    public abstract async bool has_entity_feature(Jid jid, string feature);
128    public abstract async Gee.Set<Identity> get_entity_identities(Jid jid);
129}
130
131}
132