mirror of
https://forge.fsky.io/lda/Parsee.git
synced 2026-03-13 23:05:10 +00:00
[ADD/WIP] Congestion, basic ad-hoc commands
Woah, took me a while, eh? Next up, getting forms!
This commit is contained in:
parent
4007232716
commit
408888ef67
9 changed files with 911 additions and 14 deletions
121
src/XMPPCommand/Commands.c
Normal file
121
src/XMPPCommand/Commands.c
Normal 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
260
src/XMPPCommand/Manager.c
Normal 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
251
src/XMPPCommand/Options.c
Normal 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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue