[ADD/WIP] Congestion, basic ad-hoc commands

Woah, took me a while, eh? Next up, getting forms!
This commit is contained in:
LDA 2024-07-13 16:26:33 +02:00
commit 408888ef67
9 changed files with 911 additions and 14 deletions

121
src/XMPPCommand/Commands.c Normal file
View file

@ -0,0 +1,121 @@
#include <XMPPCommand.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Array.h>
#include <Cytoplasm/Str.h>
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);
}

260
src/XMPPCommand/Manager.c Normal file
View file

@ -0,0 +1,260 @@
#include <XMPPCommand.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <pthread.h>
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;
}

251
src/XMPPCommand/Options.c Normal file
View file

@ -0,0 +1,251 @@
#include <XMPPCommand.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
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);
}