From 408888ef6784917565d4ace2ec9ce46ebdaba1e4 Mon Sep 17 00:00:00 2001 From: LDA Date: Sat, 13 Jul 2024 16:26:33 +0200 Subject: [PATCH] [ADD/WIP] Congestion, basic ad-hoc commands Woah, took me a while, eh? Next up, getting forms! --- Makefile | 4 +- src/Commands/Stats.c | 4 + src/XEP-0393.c | 19 ++- src/XMPPCommand/Commands.c | 121 +++++++++++++++++ src/XMPPCommand/Manager.c | 260 +++++++++++++++++++++++++++++++++++++ src/XMPPCommand/Options.c | 251 +++++++++++++++++++++++++++++++++++ src/XMPPThread.c | 175 +++++++++++++++++++++++-- src/include/Parsee.h | 8 ++ src/include/XMPPCommand.h | 83 ++++++++++++ 9 files changed, 911 insertions(+), 14 deletions(-) create mode 100644 src/XMPPCommand/Commands.c create mode 100644 src/XMPPCommand/Manager.c create mode 100644 src/XMPPCommand/Options.c create mode 100644 src/include/XMPPCommand.h diff --git a/Makefile b/Makefile index 781f01d..92fa82c 100644 --- a/Makefile +++ b/Makefile @@ -19,8 +19,8 @@ SOURCE=src OBJECT=build INCLUDES=src/include CC=cc -CFLAGS=-I$(INCLUDES) -I$(CYTO_INC) -DNAME="\"$(NAME)\"" -DVERSION="\"$(VERSION)\"" -DREPOSITORY=\"$(REPOSITORY)\" -O3 -g -ggdb -Wall -Werror -LDFLAGS=-L $(CYTO_LIB) -lCytoplasm -Wl,--export-dynamic -O3 -g -ggdb +CFLAGS=-I$(INCLUDES) -I$(CYTO_INC) -DNAME="\"$(NAME)\"" -DVERSION="\"$(VERSION)\"" -DREPOSITORY=\"$(REPOSITORY)\" -O3 -g -ggdb -Wall -Werror -flto +LDFLAGS=-L $(CYTO_LIB) -lCytoplasm -Wl,--export-dynamic -O3 -g -ggdb -flto BINARY=parsee # ============================ Compilation ================================= SRC_FILES:=$(shell find $(SOURCE) -name '*.c') diff --git a/src/Commands/Stats.c b/src/Commands/Stats.c index b559bf5..1102f89 100644 --- a/src/Commands/Stats.c +++ b/src/Commands/Stats.c @@ -13,6 +13,7 @@ CommandHead(CmdStats, cmd, argp) ParseeCmdArg *args = argp; ParseeData *data = args->data; HashMap *event = args->event; + size_t congestion = ParseeCongestion(); size_t alloc = MemoryAllocated(); size_t kb = alloc >> 10; size_t mb = kb >> 10; @@ -31,6 +32,9 @@ CommandHead(CmdStats, cmd, argp) ReplySprintf("- Memory used: %d%s (reported by Cytoplasm)", (int) min, unit ); + ReplySprintf("- Unprocessed stanzas: %d", + (int) congestion + ); ReplySprintf("- Source code and licensing information: %s", REPOSITORY ); diff --git a/src/XEP-0393.c b/src/XEP-0393.c index 9954580..9616981 100644 --- a/src/XEP-0393.c +++ b/src/XEP-0393.c @@ -141,20 +141,27 @@ IdentifySpan(char span_tag, StrView in, StrView *view) view->end = in.start; return true; } + + static void XEP393Decode(StrView view, XEP393Element *root) { StrView subview = view; StrView textview = view; XEP393Element *text, *span; - size_t i; bool managed = false; + char prev = '\0', curr = '\0'; textview.end = subview.start; - for (i = 0; subview.start < subview.end; subview.start++) + for (; subview.start < subview.end; subview.start++) { StrView span_view; managed = false; + curr = *subview.start; + if (prev == '\0' || prev == '\n') + { + /* TODO: Start of line, start parsing blocks. */ + } #define Spanify(xep_symbol) \ managed = true; \ textview.end = subview.start; \ @@ -193,7 +200,8 @@ XEP393Decode(StrView view, XEP393Element *root) /* Text character: update end */ textview.end = subview.start; } - (void) i; + + prev = curr; } if (!managed) @@ -215,7 +223,10 @@ XEP393(char *message) /* TODO: Parse blocks first, *then* spans. Considering the * current architecture, this shouldn't be too hard to integrate, * given how string views already manage boundaries, and elements - * can already be used to contain blocks I think. */ + * can already be used to contain blocks I think. + * + * Actually, nevermind, these would be pure pain. Nested blocks, + * unterminated ones, QUOTES. Just hell. I hate parsing this shit. */ XEP393Decode(view, root); return root; } diff --git a/src/XMPPCommand/Commands.c b/src/XMPPCommand/Commands.c new file mode 100644 index 0000000..d72b20a --- /dev/null +++ b/src/XMPPCommand/Commands.c @@ -0,0 +1,121 @@ +#include + +#include +#include +#include + +struct XMPPCommand { + XMPPCmdCallback callback; + char *node, *name; + + Array *options; +}; + +XMPPCommand * +XMPPBasicCmd(char *node, char *name, XMPPCmdCallback callback_funct) +{ + XMPPCommand *cmd; + + if (!node || !name || !callback_funct) + { + return NULL; + } + + cmd = Malloc(sizeof(*cmd)); + cmd->callback = callback_funct; + cmd->node = StrDuplicate(node); + cmd->name = StrDuplicate(name); + + /* No options -> no form required */ + cmd->options = NULL; + + return cmd; +} +void +XMPPFreeCommand(XMPPCommand *cmd) +{ + size_t i; + if (!cmd) + { + return; + } + + for (i = 0; i < ArraySize(cmd->options); i++) + { + XMPPOption *opt = ArrayGet(cmd->options, i); + XMPPFreeOption(opt); + } + ArrayFree(cmd->options); + + Free(cmd->node); + Free(cmd->name); + Free(cmd); +} +void +XMPPAddOption(XMPPCommand *cmd, XMPPOption *opt) +{ + if (!cmd || !opt) + { + return; + } + if (!cmd->options) + { + cmd->options = ArrayCreate(); + } + + ArrayAdd(cmd->options, opt); +} + +XMLElement * +XMPPFormifyCommand(XMPPCommand *cmd) +{ + XMLElement *x, *field; + size_t i; + + if (!cmd || !cmd->options) + { + return NULL; + } + + x = XMLCreateTag("x"); + XMLAddAttr(x, "xmlns", "jabber:x:data"); + XMLAddAttr(x, "type", "form"); + /* TODO: Other fields */ + + for (i = 0; i < ArraySize(cmd->options); i++) + { + XMPPOption *opt = ArrayGet(cmd->options, i); + field = XMPPOptionToXML(opt); + + XMLAddChild(x, field); + } + + return x; +} +char * +XMPPGetCommandNode(XMPPCommand *cmd) +{ + return cmd ? cmd->node : NULL; +} +char * +XMPPGetCommandDesc(XMPPCommand *cmd) +{ + return cmd ? cmd->name : NULL; +} +bool +XMPPCommandRequiresForm(XMPPCommand *cmd) +{ + return cmd ? !!cmd->options : false; +} +void +XMPPExecuteCommand(XMPPCommandManager *m, XMPPCommand *cmd, XMLElement *to, HashMap *arg_table) +{ + if (!m || !cmd || !to) + { + return; + } + cmd->callback(m, arg_table, to); + + /* TODO Free arg_table's strings */ + HashMapFree(arg_table); +} diff --git a/src/XMPPCommand/Manager.c b/src/XMPPCommand/Manager.c new file mode 100644 index 0000000..c50b585 --- /dev/null +++ b/src/XMPPCommand/Manager.c @@ -0,0 +1,260 @@ +#include + +#include +#include +#include +#include +#include + +#include + +typedef struct XMPPSession { + char *identifier; + + /* "Each session [...] SHOULD be valid only between one + * requester/responder pair." */ + /* The Parsee-managed entity managing the session. */ + char *receiver; + /* The command's issuer entity. */ + char *issuer; + + /* The issued node */ + char *node; + /* Timestamp of the session's creation, from Parsee's + * point-of-view */ + uint64_t creation; +} XMPPSession; + +struct XMPPCommandManager { + pthread_mutex_t lock; + + /* A hashmap of XMPPCommands. */ + HashMap *commands; + + HashMap *sessions; +}; +static void +XMPPDestroySession(XMPPSession *session) +{ + if (!session) + { + return; + } + + Free(session->identifier); + Free(session->receiver); + Free(session->issuer); + Free(session->node); + Free(session); +} + +static void +InvalidateSession(XMPPCommandManager *m, char *session) +{ + XMPPSession *s = HashMapDelete(m->sessions, session); + XMPPDestroySession(s); +} + +static char * +NewSession(XMPPCommandManager *m, char *node) +{ + char *session_id = NULL; + do + { + int period = (UtilTsMillis() / (10 SECONDS)); + char *rand = StrRandom(16); + char *pstr = StrInt(period); + session_id = StrConcat(5, node, "_", pstr, "_", rand); + + Free(rand); + Free(pstr); + } + while (HashMapGet(m->sessions, session_id)); + + return session_id; +} +/* NOTE: The manager MUST be locked. */ +static char * +XMPPRegisterSession(XMPPCommandManager *m, char *p_jid, char *s_jid, char *node) +{ + XMPPSession *session; + char *session_ident; + if (!m || !p_jid || !s_jid) + { + return NULL; + } + + session = Malloc(sizeof(*session)); + session->node = StrDuplicate(node); + session->issuer = StrDuplicate(s_jid); + session->receiver = StrDuplicate(p_jid); + session->creation = UtilTsMillis(); + session_ident = NewSession(m, node); + session->identifier = session_ident; + + HashMapSet(m->sessions, session_ident, session); + + return session_ident; +} + + +XMPPCommandManager * +XMPPCreateManager(void) +{ + XMPPCommandManager *ret = Malloc(sizeof(*ret)); + + pthread_mutex_init(&ret->lock, NULL); + ret->commands = HashMapCreate(); + ret->sessions = HashMapCreate(); + + return ret; +} + +void +XMPPRegisterCommand(XMPPCommandManager *m, XMPPCommand *cmd) +{ + if (!m || !cmd) + { + return; + } + + pthread_mutex_lock(&m->lock); + HashMapSet(m->commands, XMPPGetCommandNode(cmd), cmd); + pthread_mutex_unlock(&m->lock); +} + +void +XMPPFreeManager(XMPPCommandManager *manager) +{ + char *node_name, *session_id; + XMPPCommand *val; + XMPPSession *session; + if (!manager) + { + return; + } + + while (HashMapIterate(manager->commands, &node_name, (void **) &val)) + { + XMPPFreeCommand(val); + } + while (HashMapIterate(manager->sessions, &session_id, (void **) &session)) + { + XMPPDestroySession(session); + } + + pthread_mutex_destroy(&manager->lock); + HashMapFree(manager->commands); + HashMapFree(manager->sessions); + Free(manager); +} +void +XMPPShoveCommandList(XMPPCommandManager *m, char *jid, XMLElement *p) +{ + char *node_name; + XMPPCommand *val; + XMLElement *item; + if (!m || !p || !jid) + { + return; + } + + pthread_mutex_lock(&m->lock); + while (HashMapIterate(m->commands, &node_name, (void **) &val)) + { + item = XMLCreateTag("item"); + XMLAddAttr(item, "jid", jid); + XMLAddAttr(item, "node", node_name); + XMLAddAttr(item, "name", XMPPGetCommandDesc(val)); + XMLAddChild(p, item); + } + pthread_mutex_unlock(&m->lock); +} +bool +XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data) +{ + XMLElement *command; + XMPPComponent *jabber; + XMPPCommand *cmd; + char *from, *to, *id; + char *session_id = NULL; + bool good = false; + if (!m || !stanza || !data) + { + return false; + } + + jabber = data->jabber; + + from = HashMapGet(stanza->attrs, "from"); + to = HashMapGet(stanza->attrs, "to"); + id = HashMapGet(stanza->attrs, "id"); + + pthread_mutex_lock(&m->lock); + + /* TODO */ +#define CMD_NS "http://jabber.org/protocol/commands" + if ((command = XMLookForTKV(stanza, "command", "xmlns", CMD_NS))) + { + char *action = HashMapGet(command->attrs, "action"); + char *node = HashMapGet(command->attrs, "node"); + + if (!action || StrEquals(action, "execute")) + { + XMLElement *command_xml, *iq; + /* This is an execution. */ + cmd = HashMapGet(m->commands, node); + + if (!cmd) + { + Log(LOG_WARNING, + "User %s asked to execute '%s', but it doesn't exist.", + from, node + ); + goto end; + } + + if (XMPPCommandRequiresForm(cmd)) + { + Log(LOG_WARNING, + "User %s asked to execute '%s', but it requires a form, " + "which is currently UNIMPLEMENTED", from, node + ); + goto end; + } + + /* Doesn't need to be freed, as it lives with m. */ + session_id = XMPPRegisterSession(m, to, from, node); + + /* No forms, we good. */ + iq = XMLCreateTag("iq"); + XMLAddAttr(iq, "type", "result"); + XMLAddAttr(iq, "from", to); + XMLAddAttr(iq, "to", from); + XMLAddAttr(iq, "id", id); + command_xml = XMLCreateTag("command"); + XMLAddAttr(command_xml, "xmlns", CMD_NS); + XMLAddAttr(command_xml, "node", node); + XMLAddAttr(command_xml, "status", "completed"); + XMLAddAttr(command_xml, "sessionid", session_id); + XMPPExecuteCommand(m, cmd, command_xml, NULL); + XMLAddChild(iq, command_xml); + + pthread_mutex_lock(&jabber->write_lock); + XMLEncode(jabber->stream, iq); + StreamFlush(jabber->stream); + pthread_mutex_unlock(&jabber->write_lock); + XMLFreeElement(iq); + + InvalidateSession(m, session_id); + + good = true; + goto end; + } + } +#undef CMD_NS + +end: + pthread_mutex_unlock(&m->lock); + return good; +} diff --git a/src/XMPPCommand/Options.c b/src/XMPPCommand/Options.c new file mode 100644 index 0000000..f177294 --- /dev/null +++ b/src/XMPPCommand/Options.c @@ -0,0 +1,251 @@ +#include + +#include +#include + +struct XMPPOption { + enum { + XMPP_OPTION_TEXT, + XMPP_OPTION_BOOL, + XMPP_OPTION_LIST, + XMPP_OPTION_FIXED + } type; + + union { + /* Default text/value for string/fixed */ + struct { + char *text; + } as_string; + + /* Default choice for bool */ + struct { + bool choice; + } as_bool; + + struct { + Array *elements; + char *def; + bool single; + } as_arr; + } data; + + char *id, *desc; + bool required; +}; + +void +XMPPFreeOption(XMPPOption *opt) +{ + size_t i; + Array *elements; + if (!opt) + { + return; + } + + switch (opt->type) + { + case XMPP_OPTION_TEXT: + case XMPP_OPTION_FIXED: + Free(opt->data.as_string.text); + break; + case XMPP_OPTION_LIST: + elements = opt->data.as_arr.elements; + for (i = 0; i < ArraySize(elements); i++) + { + char *element = ArrayGet(elements, i); + Free(element); + } + ArrayFree(elements); + Free(opt->data.as_arr.def); + break; + default: + break; + } + + Free(opt->desc); + Free(opt->id); + Free(opt); +} + +XMPPOption * +XMPPCreateText(bool req, char *id, char *def) +{ + XMPPOption *opt; + if (!id) + { + return NULL; + } + + opt = Malloc(sizeof(*opt)); + opt->desc = NULL; + opt->type = XMPP_OPTION_TEXT; + opt->id = StrDuplicate(id); + opt->data.as_string.text = StrDuplicate(def); + opt->required = req; + return opt; +} +XMPPOption * +XMPPCreateBool(bool req, char *id, bool def) +{ + XMPPOption *opt; + if (!id) + { + return NULL; + } + + opt = Malloc(sizeof(*opt)); + opt->desc = NULL; + opt->type = XMPP_OPTION_BOOL; + opt->id = StrDuplicate(id); + opt->data.as_bool.choice = def; + opt->required = req; + return opt; + +} +XMPPOption * +XMPPCreateList(bool req, bool single, char *id, char *def) +{ + XMPPOption *opt; + if (!id) + { + return NULL; + } + + opt = Malloc(sizeof(*opt)); + opt->desc = NULL; + opt->type = XMPP_OPTION_LIST; + opt->id = StrDuplicate(id); + opt->data.as_arr.def = StrDuplicate(def); + opt->data.as_arr.single = single; + opt->data.as_arr.elements = ArrayCreate(); + opt->required = req; + + ArrayAdd(opt->data.as_arr.elements, StrDuplicate(def)); + return opt; +} +XMPPOption * +XMPPCreateFixed(char *id, char *text) +{ + XMPPOption *opt; + if (!id) + { + return NULL; + } + + opt = Malloc(sizeof(*opt)); + opt->desc = NULL; + opt->type = XMPP_OPTION_TEXT; + opt->id = StrDuplicate(id); + opt->data.as_string.text = StrDuplicate(text); + opt->required = false; + return opt; +} + + +XMLElement * +XMPPOptionToXML(XMPPOption *opt) +{ + XMLElement *elem, *req, *desc, *data; + XMLElement *value; + if (!opt) + { + return NULL; + } + + elem = XMLCreateTag("field"); + switch (opt->type) + { + case XMPP_OPTION_TEXT: XMLAddAttr(elem, "type", "text-single"); break; + case XMPP_OPTION_BOOL: XMLAddAttr(elem, "type", "boolean"); break; + + /* TODO */ + case XMPP_OPTION_LIST: XMLAddAttr(elem, "type", "list-single"); break; + case XMPP_OPTION_FIXED: XMLAddAttr(elem, "type", "fixed"); break; + } + XMLAddAttr(elem, "var", opt->id); + if (opt->required) + { + req = XMLCreateTag("required"); + XMLAddChild(elem, req); + } + if (opt->desc) + { + desc = XMLCreateTag("desc"); + data = XMLCreateText(opt->desc); + XMLAddChild(desc, data); + XMLAddChild(elem, desc); + } + + if (opt->type == XMPP_OPTION_LIST) + { + size_t i; + Array *elements = opt->data.as_arr.elements; + XMLElement *option, *value; + + for (i = 0; i < ArraySize(elements); i++) + { + char *element = ArrayGet(elements, i); + option = XMLCreateTag("option"); + value = XMLCreateTag("value"); + data = XMLCreateText(element); + + XMLAddChild(value, data); + XMLAddChild(option, value); + XMLAddChild(elem, option); + } + } + + switch (opt->type) + { + case XMPP_OPTION_TEXT: + value = XMLCreateTag("value"); + data = XMLCreateText(opt->data.as_string.text); + XMLAddChild(value, data); + XMLAddChild(elem, value); + break; + case XMPP_OPTION_LIST: + value = XMLCreateTag("value"); + data = XMLCreateText(opt->data.as_arr.def); + XMLAddChild(value, data); + XMLAddChild(elem, value); + break; + case XMPP_OPTION_BOOL: + value = XMLCreateTag("value"); + data = XMLCreateText(opt->data.as_bool.choice ? "true" : "false"); + XMLAddChild(value, data); + XMLAddChild(elem, value); + break; + default: + break; + + } + /* TODO */ + + return elem; +} + + +void +XMPPAddListOption(XMPPOption *list, char *option) +{ + Array *elements; + if (!list || !option || list->type != XMPP_OPTION_LIST) + { + return; + } + + elements = list->data.as_arr.elements; + ArrayAdd(elements, StrDuplicate(option)); +} +void +XMPPSetDescription(XMPPOption *opt, char *desc) +{ + if (!opt || !desc) + { + return; + } + + Free(opt->desc); + opt->desc = StrDuplicate(desc); +} diff --git a/src/XMPPThread.c b/src/XMPPThread.c index 5f62aa0..753ada2 100644 --- a/src/XMPPThread.c +++ b/src/XMPPThread.c @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -19,6 +20,7 @@ #define IQ_ADVERT \ AdvertiseSimple("http://jabber.org/protocol/chatstates") \ + AdvertiseSimple("http://jabber.org/protocol/commands") \ AdvertiseSimple("http://jabber.org/protocol/caps") \ AdvertiseSimple("urn:xmpp:avatar:metadata+notify") \ AdvertiseSimple("urn:xmpp:avatar:data+notify") \ @@ -293,6 +295,7 @@ typedef struct XMPPThreadInfo { ParseeData *args; XMPPComponent *jabber; + XMPPCommandManager *m; struct XMPPThread *dispatchers; size_t available_dispatchers; @@ -864,13 +867,16 @@ TrimBase64(char *b64) return ret; } static void -IQResult(ParseeData *args, XMLElement *stanza) +IQResult(ParseeData *args, XMLElement *stanza, XMPPThread *thr) { XMLElement *vcard = XMLookForTKV(stanza, "vCard", "xmlns", "vcard-temp"); XMLElement *event = XMLookForTKV(stanza, "pubsub", "xmlns", "http://jabber.org/protocol/pubsub" ); + + (void) thr; + if (event) { size_t i; @@ -1033,15 +1039,60 @@ CreateTagWithText(const char *tn, char *text) return tag; } +static bool +IQIsCommandList(ParseeData *args, XMLElement *stanza) +{ + char *parsee = NULL; + XMLElement *query = XMLookForTKV( + stanza, "query", "xmlns", + "http://jabber.org/protocol/disco#items" + ); + bool ret = false; + + if (!query || + !StrEquals(HashMapGet(query->attrs, "node"), + "http://jabber.org/protocol/commands")) + { + return false; + } + + parsee = ParseeJID(args); + ret = StrEquals(HashMapGet(stanza->attrs, "to"), parsee); + Free(parsee); + + return ret; +} static void -IQGet(ParseeData *args, XMLElement *stanza) +IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) { XMPPComponent *jabber = args->jabber; char *from = HashMapGet(stanza->attrs, "from"); char *to = HashMapGet(stanza->attrs, "to"); char *id = HashMapGet(stanza->attrs, "id"); - if (XMLookForTKV(stanza, "vCard", "xmlns", "vcard-temp")) + (void) thr; + + if (IQIsCommandList(args, stanza)) + { + XMLElement *iq_reply = XMLCreateTag("iq"); + XMLAddAttr(iq_reply, "type", "result"); + XMLAddAttr(iq_reply, "from", to); + XMLAddAttr(iq_reply, "to", from); + XMLAddAttr(iq_reply, "id", id); + { + XMLElement *q = XMLCreateTag("query"); + XMLAddAttr(q, "xmlns", "http://jabber.org/protocol/disco#items"); + XMLAddAttr(q, "node", "http://jabber.org/protocol/commands"); + XMPPShoveCommandList(thr->info->m, to, q); + XMLAddChild(iq_reply, q); + } + pthread_mutex_lock(&jabber->write_lock); + XMLEncode(jabber->stream, iq_reply); + StreamFlush(jabber->stream); + pthread_mutex_unlock(&jabber->write_lock); + XMLFreeElement(iq_reply); + } + else if (XMLookForTKV(stanza, "vCard", "xmlns", "vcard-temp")) { Log(LOG_INFO, "vCard information GET for %s", to); @@ -1127,13 +1178,22 @@ IQGet(ParseeData *args, XMLElement *stanza) } static void -IQError(ParseeData *args, XMLElement *stanza) +IQError(ParseeData *args, XMLElement *stanza, XMPPThread *thr) { /* TODO */ } +static void +IQSet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) +{ + XMPPCommandManager *manager = thr->info->m; + if (!XMPPManageCommand(manager, stanza, args)) + { + Log(LOG_WARNING, "NOT A COMMAND"); + } +} #undef DISCO static void -IQStanza(ParseeData *args, XMLElement *stanza) +IQStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr) { char *type; type = HashMapGet(stanza->attrs, "type"); @@ -1141,13 +1201,14 @@ IQStanza(ParseeData *args, XMLElement *stanza) { \ if (StrEquals(type, #ctyp)) \ { \ - callback(args, stanza); \ + callback(args, stanza, thr); \ return; \ } \ } \ while (0) OnType(get, IQGet); + OnType(set, IQSet); OnType(error, IQError); OnType(result, IQResult); #undef OnType @@ -1467,7 +1528,7 @@ XMPPDispatcher(void *argp) } else if (StrEquals(stanza->name, "iq")) { - IQStanza(args, stanza); + IQStanza(args, stanza, thread); } else { @@ -1488,21 +1549,117 @@ typedef struct XMPPAwait { XMLElement *stanza; } XMPPAwait; + static pthread_mutex_t await_lock = PTHREAD_MUTEX_INITIALIZER; static HashMap *await_table = NULL; +static XMPPThreadInfo info; +size_t +ParseeCongestion(void) +{ + size_t congestion; + pthread_mutex_lock(&info.lock); + congestion = ArraySize(info.stanzas); + pthread_mutex_unlock(&info.lock); + + return congestion; +} + +static void +StatusCallback(XMPPCommandManager *m, HashMap *data, XMLElement *out) +{ + size_t alloc = MemoryAllocated(); + size_t kb = alloc >> 10; + size_t mb = kb >> 10; + size_t gb = mb >> 10; + size_t min = gb ? gb : (mb ? mb : (kb ? kb : alloc)); + char *unit = gb ? "GB" : (mb ? "MB" : (kb ? "KB" : "B")); + XMLElement *x = XMLCreateTag("x"); + XMLElement *title = XMLCreateTag("title"); + + { + XMLElement *title_text = XMLCreateText("Parsee statistics"); + XMLAddChild(title, title_text); + } + XMLAddChild(x, title); + + XMLAddAttr(x, "xmlns", "jabber:x:data"); + XMLAddAttr(x, "type", "result"); + { + XMLElement *reported, *item, *field, *value, *txt; + reported = XMLCreateTag("reported"); + XMLAddChild(x, reported); +#define Report(id, label) do \ + { \ + field = XMLCreateTag("field"); \ + XMLAddAttr(field, "var", id); \ + XMLAddAttr(field, "label", label); \ + XMLAddChild(reported, field); \ + } \ + while(0) +#define BeginItem() item = XMLCreateTag("item") +#define EndItem() XMLAddChild(x, item) +#define SetField(id, val) do \ + { \ + field = XMLCreateTag("field"); \ + value = XMLCreateTag("value"); \ + txt = XMLCreateText(val); \ + XMLAddAttr(field, "var", id); \ + XMLAddChild(value, txt); \ + XMLAddChild(field, value); \ + XMLAddChild(item, field); \ + } \ + while(0) + + /* Report */ + Report("mem-alloc", "Heap allocated with Cytoplasm"); + Report("xml-congest", "Unprocessed stanzas(congestion)"); + + /* Set */ + BeginItem(); + { + char *min_str = StrInt(min); + char *congest = StrInt(ParseeCongestion()); + char *alloc = StrConcat(3, min_str, " ", unit); + + SetField("mem-alloc", alloc); + SetField("xml-congest", congest); + + Free(congest); + Free(min_str); + Free(alloc); + } + EndItem(); +#undef SetField +#undef EndItem +#undef BeginItem +#undef Report + } + XMLAddChild(out, x); +} void * ParseeXMPPThread(void *argp) { ParseeData *args = argp; XMPPComponent *jabber = args->jabber; XMLElement *stanza = NULL; - XMPPThreadInfo info; size_t i; /* Initialise the await table */ await_table = HashMapCreate(); + /* Initialise the command manager, and add all ad-hoc commands */ + info.m = XMPPCreateManager(); + { + XMPPCommand *cmd; + + cmd = XMPPBasicCmd( + "status", "Get status about Parsee", + StatusCallback + ); + XMPPRegisterCommand(info.m, cmd); + } + /* Initialise the FIFO */ info.stanzas = ArrayCreate(); pthread_mutex_init(&info.lock, NULL); @@ -1578,6 +1735,8 @@ ParseeXMPPThread(void *argp) HashMapFree(await_table); pthread_mutex_destroy(&info.lock); + + XMPPFreeManager(info.m); return NULL; } diff --git a/src/include/Parsee.h b/src/include/Parsee.h index 814be5f..0efa7ca 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -123,6 +123,14 @@ extern void * ParseeXMPPThread(void *data); * Modifies: NONE */ extern XMLElement * ParseeAwaitStanza(char *identifier, int64_t ts); +/** Returns the amount of unprocessed stanzas in the XMPP thread, which + * can be used by admins to guess load. + * -------- + * UB-If: called in the XMPP dispatcher thread itself + * Returns: amount of stanzas in the FIFO + * Modifies: NONE */ +extern size_t ParseeCongestion(void); + /* Finds the room a DM is associated to, from a Matrix user and a Jabber * ID. */ extern char * ParseeFindDMRoom(ParseeData *data, char *mxid, char *jid); diff --git a/src/include/XMPPCommand.h b/src/include/XMPPCommand.h new file mode 100644 index 0000000..27e7225 --- /dev/null +++ b/src/include/XMPPCommand.h @@ -0,0 +1,83 @@ +#ifndef PARSEE_XMPPCOMMAND_H +#define PARSEE_XMPPCOMMAND_H + +#include +#include + +#include +#include + +typedef struct XMPPCommandManager XMPPCommandManager; +typedef struct XMPPCommand XMPPCommand; +typedef struct XMPPOption XMPPOption; +typedef void (*XMPPCmdCallback)(XMPPCommandManager *, HashMap *, XMLElement *); + +/** Creates a simple XMPP command manager, which routes commands + * with a single-stage form system. + * ------------------------------------------- + * Returns: An opaque command manager[LA:HEAP] + * Modifies: NOTHING + * See-Also: XMPPFreeManager */ +extern XMPPCommandManager * XMPPCreateManager(void); + +/** Create a basic command with a node and name description + * ----------------------------------------------------- + * Returns: A command to be used with {XMPPRegisterCommand}[LA:HEAP] + * Modifies: NOTHING + * See-Also: XMPPRegisterCommand */ +extern XMPPCommand * XMPPBasicCmd(char *node, char *name, XMPPCmdCallback cb); +extern void XMPPAddOption(XMPPCommand *cmd, XMPPOption *opt); +extern XMLElement * XMPPFormifyCommand(XMPPCommand *cmd); +extern char * XMPPGetCommandNode(XMPPCommand *cmd); +extern char * XMPPGetCommandDesc(XMPPCommand *cmd); +extern bool XMPPCommandRequiresForm(XMPPCommand *cmd); +extern void XMPPExecuteCommand(XMPPCommandManager *m, XMPPCommand *cmd, XMLElement *to, HashMap *arg_table); + +/** Create a basic option. + * ----------------------------------------------------- + * Returns: A form option of the right type[LA:HEAP] + * Modifies: NOTHING + * See-Also: https://xmpp.org/extensions/xep-0004.html */ +extern XMPPOption * XMPPCreateText(bool req, char *id, char *def); +extern XMPPOption * XMPPCreateBool(bool req, char *id, bool def); +extern XMPPOption * XMPPCreateList(bool req, bool single, char *id, char *def); +extern XMPPOption * XMPPCreateFixed(char *id, char *text); + +extern void XMPPAddListOption(XMPPOption *list, char *option); +extern void XMPPSetDescription(XMPPOption *opt, char *descri); + +extern XMLElement * XMPPOptionToXML(XMPPOption *opt); + +extern void XMPPFreeOption(XMPPOption *opt); +extern void XMPPFreeCommand(XMPPCommand *cmd); + +/** Registers a {cmd} to the {m}anager, with no extra metadata or callback. + * It also makes {cmd} belong to {m}anager, and therefore does not belong to + * its creator anymore. + * ----------------------------------------------------- + * Returns: NOTHING + * Modifies: {m}anager + * See-Also: XMPPCreateManager, XMPPFreeManager */ +extern void XMPPRegisterCommand(XMPPCommandManager *m, XMPPCommand *cmd); + +/** Shoves all {m} commands into XML as children of {p}, and a {jid} + * ----------------------------------------------------- + * Returns: NOTHING + * Modifies: {p} + * See-Also: XMPPCreateManager */ +extern void XMPPShoveCommandList(XMPPCommandManager *m, char *jid, XMLElement *p); + +/** Destroys all memory related to the command {manager}. + * ----------------------------------------------------- + * Returns: NOTHING + * Modifies: {manager} + * See-Also: XMPPCreateManager */ +extern void XMPPFreeManager(XMPPCommandManager *manager); + +/** Manages a command given a IQ stanza and a Parsee data field + * ----------------------------------------------------- + * Returns: Whenever the command was managed properly and completed. + * Modifies: {manager} + * See-Also: XMPPCreateManager */ +extern bool XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data); +#endif