From 6efd6ba8e375b25ac34224e16b191ea11fd5ca0f Mon Sep 17 00:00:00 2001 From: defanor Date: Sat, 25 Sep 2021 11:46:16 +0300 Subject: Introduce IQ caching Aiming its usage for service discovery, and possibly similar information retrieval activities. --- README | 11 +++++--- src/rexmpp.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/rexmpp.h | 22 +++++++++++++++ 3 files changed, 120 insertions(+), 4 deletions(-) diff --git a/README b/README index fe4a62f..0a4095e 100644 --- a/README +++ b/README @@ -76,17 +76,20 @@ A rough roadmap: [ ] XEP-0363: HTTP File Upload? [ ] XEP-0184: Message Delivery Receipts? [ ] OTR/OMEMO/MLS encryption? +[ ] A/V calls? - Additional state tracking: [+] XMPP IM (RFC 6121): track presences of contacts. -[+] XEP-0163 v1.2: Personal Eventing Protocol: track contacts' published items. +[+] XEP-0163 v1.2: Personal Eventing Protocol: track contacts' + published items. +[+] IQ response caching. [ ] XEP-0030: Service Discovery: track features provided by known - entities. + entities? [ ] XEP-0115: Entity Capabilities: maintain a capability database, - track capabilities of known entities. -[ ] XEP-0045: Multi-User Chat: tracking of related states/presences. + track capabilities of known entities? +[ ] XEP-0045: Multi-User Chat: tracking of related states/presences? - Various utility functions: diff --git a/src/rexmpp.c b/src/rexmpp.c index bf396ca..b0286c1 100644 --- a/src/rexmpp.c +++ b/src/rexmpp.c @@ -35,6 +35,11 @@ #include "rexmpp_openpgp.h" #include "rexmpp_console.h" +struct rexmpp_iq_cacher { + rexmpp_iq_callback_t cb; + void *cb_data; +}; + const char *rexmpp_strerror (rexmpp_err_t error) { switch (error) { case REXMPP_SUCCESS: return "No error"; @@ -420,6 +425,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, s->stanza_queue = NULL; s->stream_id = NULL; s->active_iq = NULL; + s->iq_cache = NULL; s->reconnect_number = 0; s->next_reconnect_time.tv_sec = 0; s->next_reconnect_time.tv_usec = 0; @@ -428,6 +434,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, s->stanza_queue_size = 1024; s->send_queue_size = 1024; s->iq_queue_size = 1024; + s->iq_cache_size = 1024; s->log_function = log_func; s->sasl_property_cb = NULL; s->xml_in_cb = NULL; @@ -615,6 +622,10 @@ void rexmpp_done (rexmpp_t *s) { s->active_iq = next; rexmpp_iq_finish(s, iq, 0, NULL); } + if (s->iq_cache != NULL) { + xmlFreeNodeList(s->iq_cache); + s->iq_cache = NULL; + } } void rexmpp_schedule_reconnect (rexmpp_t *s) { @@ -711,6 +722,17 @@ int rexmpp_xml_match (xmlNodePtr node, return 1; } +int rexmpp_xml_eq (xmlNodePtr n1, xmlNodePtr n2) { + /* Just serialize and compare strings for now: awkward, but + simple. */ + char *n1str = rexmpp_xml_serialize(n1); + char *n2str = rexmpp_xml_serialize(n2); + int eq = (strcmp(n1str, n2str) == 0); + free(n1str); + free(n2str); + return eq; +} + xmlNodePtr rexmpp_xml_find_child (xmlNodePtr node, const char *namespace, const char *name) @@ -1016,6 +1038,75 @@ rexmpp_err_t rexmpp_iq_new (rexmpp_t *s, return rexmpp_send(s, iq_stanza); } +void rexmpp_iq_cache_cb (rexmpp_t *s, + void *cb_data, + xmlNodePtr request, + xmlNodePtr response, + int success) +{ + if (success && response != NULL) { + xmlNodePtr prev_last = NULL, last = NULL, ciq = s->iq_cache; + uint32_t size = 0; + while (ciq != NULL && ciq->next != NULL) { + prev_last = last; + last = ciq; + size++; + ciq = ciq->next->next; + } + if (size >= s->iq_queue_size && prev_last != NULL) { + xmlFreeNode(last->next); + xmlFreeNode(last); + prev_last->next->next = NULL; + } + xmlNodePtr req = xmlCopyNode(request, 1); + xmlNodePtr resp = xmlCopyNode(response, 1); + req->next = resp; + resp->next = s->iq_cache; + s->iq_cache = req; + } + struct rexmpp_iq_cacher *cacher = cb_data; + if (cacher->cb != NULL) { + cacher->cb(s, cacher->cb_data, request, response, success); + } + free(cacher); +} + +rexmpp_err_t rexmpp_cached_iq_new (rexmpp_t *s, + const char *type, + const char *to, + xmlNodePtr payload, + rexmpp_iq_callback_t cb, + void *cb_data, + int fresh) +{ + if (! fresh) { + xmlNodePtr ciq = s->iq_cache; + while (ciq != NULL && ciq->next != NULL) { + xmlNodePtr ciq_pl = xmlFirstElementChild(ciq); + char *ciq_type = xmlGetProp(ciq, "type"); + char *ciq_to = xmlGetProp(ciq, "to"); + int matches = (rexmpp_xml_eq(ciq_pl, payload) && + strcmp(ciq_type, type) == 0 && + strcmp(ciq_to, to) == 0); + free(ciq_to); + free(ciq_type); + if (matches) { + xmlFreeNode(payload); + if (cb != NULL) { + cb(s, cb_data, ciq, ciq->next, 1); + } + return REXMPP_SUCCESS; + } + ciq = ciq->next->next; + } + } + struct rexmpp_iq_cacher *cacher = malloc(sizeof(struct rexmpp_iq_cacher)); + cacher->cb = cb; + cacher->cb_data = cb_data; + return rexmpp_iq_new(s, type, to, payload, rexmpp_iq_cache_cb, cacher); +} + + rexmpp_err_t rexmpp_sm_ack (rexmpp_t *s) { char buf[11]; xmlNodePtr ack = xmlNewNode(NULL, "a"); diff --git a/src/rexmpp.h b/src/rexmpp.h index 5e40d30..addd4d4 100644 --- a/src/rexmpp.h +++ b/src/rexmpp.h @@ -277,6 +277,7 @@ struct rexmpp uint32_t stanza_queue_size; uint32_t send_queue_size; uint32_t iq_queue_size; + uint32_t iq_cache_size; /* Callbacks. */ log_function_t log_function; @@ -300,6 +301,9 @@ struct rexmpp /* IQs we're waiting for responses to. */ rexmpp_iq_t *active_iq; + /* Cached IQ requests and responses. */ + xmlNodePtr iq_cache; + /* Connection and stream management. */ unsigned int reconnect_number; time_t reconnect_seconds; @@ -430,6 +434,19 @@ rexmpp_err_t rexmpp_iq_new (rexmpp_t *s, rexmpp_iq_callback_t cb, void *cb_data); +/** + @brief Same as ::rexmpp_iq_new, but caches responses, and can use + cached ones. + @param[in] fresh Do not read cache, make a new request. +*/ +rexmpp_err_t rexmpp_cached_iq_new (rexmpp_t *s, + const char *type, + const char *to, + xmlNodePtr payload, + rexmpp_iq_callback_t cb, + void *cb_data, + int fresh); + /** @brief Determines the maximum time to wait before the next ::rexmpp_run call. @@ -498,6 +515,11 @@ void rexmpp_log (rexmpp_t *s, int priority, const char *format, ...); */ char *rexmpp_get_name (rexmpp_t *s, const char *jid_str); +/** + @brief Compares two XML elements. +*/ +int rexmpp_xml_eq (xmlNodePtr n1, xmlNodePtr n2); + /** @brief Matches an XML node against a namespace and an element name. @param[in] node An XML node to match. -- cgit v1.2.3