[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

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;
}