This repository has been archived on 2020-04-11. You can view files and clone it, but cannot push or open issues or pull requests.
gnoemoe/gnoemoe/mcp/gm-mcp-session.c

548 lines
14 KiB
C

#include <glib-object.h>
#include <string.h>
#include "gm-mcp-session.h"
#include "gm-mcp-classes.h"
#include "gm-mcp.h"
#include "gm-world.h"
#include "gm-support.h"
#include "gm-debug.h"
#include "gm-options.h"
#include "gm-string.h"
#define GM_MCP_SESSION_GET_PRIVATE(object)( \
G_TYPE_INSTANCE_GET_PRIVATE((object), \
GM_TYPE_MCP_SESSION, GmMcpSessionPrivate))
struct _GmMcpSessionPrivate {
GmWorld *world;
gchar *authkey;
gdouble version;
GList *packages;
GList *multiline;
};
/* Signals */
enum {
PACKAGE_CREATED,
NUM_SIGNALS
};
static guint gm_mcp_session_signals[NUM_SIGNALS] = {0};
G_DEFINE_TYPE(GmMcpSession, gm_mcp_session, G_TYPE_OBJECT)
static void
gm_mcp_session_finalize(GObject *object) {
GmMcpSession *obj = GM_MCP_SESSION(object);
gm_mcp_session_reset(obj);
G_OBJECT_CLASS(gm_mcp_session_parent_class)->finalize(object);
}
static void
gm_mcp_session_class_init(GmMcpSessionClass *klass) {
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->finalize = gm_mcp_session_finalize;
klass->available_packages = gm_mcp_classes_initialize();
gm_mcp_session_signals[PACKAGE_CREATED] =
g_signal_new("package_created",
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(GmMcpSessionClass, package_created),
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE,
1,
G_TYPE_OBJECT);
g_type_class_add_private(object_class, sizeof(GmMcpSessionPrivate));
}
static void
gm_mcp_session_init(GmMcpSession *obj) {
obj->priv = GM_MCP_SESSION_GET_PRIVATE(obj);
obj->priv->packages = NULL;
obj->priv->version = -1;
obj->priv->authkey = NULL;
obj->priv->multiline = NULL;
}
void
gm_mcp_session_handle_open(GmMcpSession *session, gchar *line) {
GList *fields = gm_mcp_process_key_values(line);
GmMcpPackage *p;
GmMcpPackageClass *pklass;
gchar const *min_version = gm_mcp_find_value(fields, "version");
gchar const *max_version = gm_mcp_find_value(fields, "to");
//gchar *minc, *maxc;
gdouble min_v, max_v, version;
if (!(min_version && max_version)) {
gm_debug_msg(DEBUG_MCP, "GmMcpSession.HandleOpen: invalid opening, "
"min version or max_version not found!");
return;
}
//minc = gm_fix_decimal_point(g_strdup(min_version), strlen(min_version));
//maxc = gm_fix_decimal_point(g_strdup(max_version), strlen(max_version));
min_v = g_ascii_strtod(min_version, NULL);
max_v = g_ascii_strtod(max_version, NULL);
//g_free(minc);
//g_free(maxc);
gm_debug_msg(DEBUG_MCP, "MinMaxVersion: %fd %fd", min_v, max_v);
version = gm_mcp_get_version(MCP_MIN_VERSION, MCP_MAX_VERSION,
min_v, max_v);
if (version > 0.0) {
session->priv->authkey = gm_mcp_generate_auth_key();
session->priv->version = version;
gm_mcp_session_send_simple(session, "mcp", "authentication-key",
session->priv->authkey, "version", "2.1", "to", "2.1", NULL);
pklass = gm_mcp_session_find_package_class("mcp-negotiate");
p = gm_mcp_session_create_package(session, pklass,
pklass->max_version);
} else {
gm_debug_msg(DEBUG_MCP, "GmMcpSession.HandleOpen: mcp server version "
"does not match client version!");
}
}
void
gm_mcp_session_invoke_multiline_handle(GmMcpSession *session, gchar *data_tag,
gchar *key, gchar *value) {
GList *elem;
GmMcpPackage *p;
McpMultilineInfo *minfo;
gboolean handle_all = FALSE;
// Now find the data_tag and emit the handle_multiline
for (elem = session->priv->multiline; elem; elem = elem->next) {
minfo = (McpMultilineInfo *)(elem->data);
if (strcmp(minfo->data_tag, data_tag) == 0) {
p = minfo->package;
if (gm_mcp_package_can_handle_multi(p)) {
if (key) {
if (!gm_mcp_package_handle_multi(p, data_tag, key, value,
NULL)) {
// Not handled a single value, store it for later
minfo->data = g_list_append(minfo->data,
g_strdup(value));
}
} else {
handle_all = gm_mcp_package_handle_multi(p, data_tag, NULL,
NULL, minfo->data);
}
}
if (!key) {
// This is the end, remove the minfo
g_free(minfo->data_tag);
g_free(minfo->key);
// Lines should only be freed when allValues is not handled!
if (!handle_all) {
gm_g_list_free_simple(minfo->data);
} else {
g_list_free(minfo->data);
}
session->priv->multiline = g_list_remove(
session->priv->multiline, minfo);
g_free(minfo);
}
return;
}
}
}
void
gm_mcp_session_handle_multiline(GmMcpSession *session, gchar const *line) {
gchar *data_tag, *key, *value;
gchar const *key_start;
gchar const *ptr = line;
gm_string_skip_nonspace(&ptr);
if (*ptr == '\0') {
return;
}
data_tag = g_strndup(line, ptr - line);
gm_string_skip_space(&ptr);
key_start = ptr;
gm_string_skip_nonspace(&ptr);
if (*ptr == '\0') {
return;
}
key = g_strndup(key_start, (ptr - key_start) - 1);
value = g_strdup(ptr + 1);
gm_mcp_session_invoke_multiline_handle(session, data_tag, key, value);
g_free(data_tag);
g_free(key);
g_free(value);
}
void
gm_mcp_session_handle_multiline_end(GmMcpSession *session, gchar *line) {
gchar const *ptr = line;
gchar *data_tag;
gm_string_skip_nonspace(&ptr);
data_tag = g_strndup(line, ptr - line);
gm_mcp_session_invoke_multiline_handle(session, data_tag, NULL, NULL);
g_free(data_tag);
}
/* Public */
GmMcpSession *
gm_mcp_session_new(GObject *world) {
GmMcpSession *obj = GM_MCP_SESSION(g_object_new(GM_TYPE_MCP_SESSION, NULL));
obj->priv->world = GM_WORLD(world);
return obj;
}
void
gm_mcp_session_reset(GmMcpSession *session) {
GList *item, *l;
McpMultilineInfo *minfo;
GmMcpPackage *package;
g_free(session->priv->authkey);
session->priv->authkey = NULL;
session->priv->version = -1;
l = g_list_copy(session->priv->packages);
for (item = l; item; item = item->next) {
package = GM_MCP_PACKAGE(item->data);
g_object_unref(package);
session->priv->packages = g_list_remove(session->priv->packages,
package);
}
g_list_free(l);
g_list_free(session->priv->packages);
session->priv->packages = NULL;
for (item = session->priv->multiline; item; item = item->next) {
minfo = (McpMultilineInfo *)(item->data);
g_free(minfo->data_tag);
g_free(minfo->key);
gm_g_list_free_simple(minfo->data);
g_free(minfo);
}
g_list_free(session->priv->multiline);
session->priv->multiline = NULL;
}
gboolean
gm_mcp_session_initialized(GmMcpSession *session) {
return (session->priv->authkey != NULL);
}
GList const *
gm_mcp_session_get_packages(GmMcpSession *session) {
return session->priv->packages;
}
GmMcpPackageClass *
gm_mcp_session_package_class_for_each(GmPackageClassFunc func,
gpointer user_data) {
GmMcpSessionClass *klass = g_type_class_peek(GM_TYPE_MCP_SESSION);
GList *item;
GmMcpPackageClass *package_klass;
for (item = klass->available_packages; item; item = item->next) {
package_klass = GM_MCP_PACKAGE_CLASS(item->data);
if (func(package_klass, user_data)) {
return package_klass;
}
}
return NULL;
}
void
gm_mcp_session_send_multiline(GmMcpSession *session, gchar const *data_tag,
gchar const *key, GList const *lines) {
gchar *msg = g_strconcat("#$#* ", data_tag, " ", key, ": ", NULL);
gchar *msg_line = NULL;
while (lines) {
msg_line = g_strconcat(msg, (gchar *)(lines->data), NULL);
gm_world_sendln_log(session->priv->world, msg_line, LOG_MCP_OUT);
g_free(msg_line);
lines = lines->next;
}
g_free(msg);
msg_line = g_strconcat("#$#: ", data_tag, NULL);
gm_world_sendln_log(session->priv->world, msg_line, LOG_MCP_OUT);
g_free(msg_line);
}
void
gm_mcp_session_send_simple(GmMcpSession *session, gchar const *pname,
gchar const *first_key, ...) {
GString *msg = NULL;
va_list args;
gchar const *key, *value;
gchar *esvalue;
msg = g_string_new("#$#");
msg = g_string_append(msg, pname);
if (strcasecmp(pname, "mcp") != 0) {
msg = g_string_append(msg, " ");
msg = g_string_append(msg, session->priv->authkey);
}
msg = g_string_append(msg, " ");
if (first_key != NULL) {
va_start(args, first_key);
key = first_key;
while (key) {
value = va_arg(args, gchar *);
if (value) {
msg = g_string_append(msg, key);
msg = g_string_append(msg, ": ");
esvalue = gm_mcp_escape_if_needed(value);
msg = g_string_append(msg, esvalue);
g_free(esvalue);
msg = g_string_append(msg, " ");
key = va_arg(args, gchar *);
} else {
key = NULL;
}
}
va_end(args);
}
gm_world_sendln_log(session->priv->world, msg->str, LOG_MCP_OUT);
g_string_free(msg, TRUE);
}
GmMcpPackage *
gm_mcp_session_has_package_of_class(GmMcpSession *session,
GmMcpPackageClass *klass) {
GList *item;
GmMcpPackage *package;
GType klass_type = G_TYPE_FROM_CLASS(klass);
for (item = session->priv->packages; item; item = item->next) {
package = (GmMcpPackage *)(item->data);
if (G_TYPE_FROM_CLASS(GM_MCP_PACKAGE_GET_CLASS(package)) ==
klass_type) {
return package;
}
}
return NULL;
}
GmMcpPackage *
gm_mcp_session_find_package(GmMcpSession *session, gchar const *pname) {
GList *elem;
GmMcpPackage *package;
GmMcpPackageClass *package_klass;
for (elem = session->priv->packages; elem; elem = elem->next) {
package = (GmMcpPackage *) (elem->data);
package_klass = GM_MCP_PACKAGE_GET_CLASS(package);
if (strncasecmp(pname, package_klass->name,
strlen(package_klass->name)) == 0 && (strlen(pname) ==
strlen(package_klass->name) ||
pname[strlen(package_klass->name)] == '-')) {
return package;
}
}
return NULL;
}
GmMcpPackage *
gm_mcp_session_create_package(GmMcpSession *session, GmMcpPackageClass *klass,
gdouble version) {
GmMcpPackage *package;
if (!klass) {
return NULL;
}
if (!gm_mcp_session_has_package_of_class(session, klass)) {
package = g_object_new(G_TYPE_FROM_CLASS(G_OBJECT_CLASS(klass)), NULL);
gm_mcp_package_set_session(package, G_OBJECT(session));
gm_mcp_package_set_version(package, version);
session->priv->packages = g_list_append(session->priv->packages,
package);
g_signal_emit(session, gm_mcp_session_signals[PACKAGE_CREATED],
0, G_OBJECT(package));
return package;
} else {
gm_debug_msg(DEBUG_MCP, "GmMcpSession.CreatePackage: package (%s) is "
"already registered for %s", klass->name,
gm_options_get(gm_world_options(session->priv->world), "name"));
return NULL;
}
}
gboolean
gm_mcp_session_find_package_class_real(GmMcpPackageClass *klass,
gpointer user_data) {
gchar *name = (gchar *)(user_data);
return strcmp(name, klass->name) == 0;
}
GmMcpPackageClass *
gm_mcp_session_find_package_class(gchar const *name) {
return gm_mcp_session_package_class_for_each(
&gm_mcp_session_find_package_class_real, (gpointer)(name));
}
void
gm_mcp_session_handle_oob(GmMcpSession *session, gchar *line) {
McpMessageInfo info;
GmMcpPackage *package;
McpMultilineInfo *minfo;
gchar *suffix;
gchar const *value, *full;
gm_debug_msg(DEBUG_MCP, "GmMcpSession.HandleOob: %s", line);
info.fields = NULL;
info.authkey = NULL;
info.name = NULL;
if (strncmp(line, "mcp ", 4) == 0 && session->priv->version == -1) {
gm_mcp_session_handle_open(session, line + 3);
} else if (session->priv->version == -1) {
// No session, drop
gm_debug_msg(DEBUG_MCP, "GmMcpSession.HandleOob: No session found for %s, "
"ignoring out of bound command!", gm_options_get(
gm_world_options(session->priv->world), "name"));
} else if (strncmp(line, "* ", 2) == 0) {
// Multiline
gm_mcp_session_handle_multiline(session, line + 2);
} else if (strncmp(line, ": ", 2) == 0) {
// Multiline end
gm_mcp_session_handle_multiline_end(session, line + 2);
} else {
if (gm_mcp_parse_line(line, &info)) {
// Line is valid mcp
if (strcmp(info.authkey, session->priv->authkey) == 0) {
// Valid authentication
if ((package = gm_mcp_session_find_package(session,
info.name))) {
// Find package
if ((value = gm_mcp_find_multiline_tag(info.fields))) {
// This is multiline start!
if (gm_mcp_find_value(info.fields,
"_data-tag")) {
// This package requests a multiline opening!
minfo = g_new(McpMultilineInfo, 1);
minfo->key = g_strdup(value);
minfo->data_tag = g_strdup(
gm_mcp_find_value(info.fields,
"_data-tag"));
minfo->data = NULL;
minfo->package = package;
session->priv->multiline = g_list_append(
session->priv->multiline, minfo);
gm_debug_msg(DEBUG_MCP, "GmMcpSession.HandleOob: multiline "
"opening detected and stored "
"(data_tag = %s, key = %s)",
minfo->data_tag,
minfo->key);
} else {
gm_debug_msg(DEBUG_MCP, "GmMcpSession.HandleOob: multiline "
"opening detected, but no _data-tag specified, "
"ignore message");
}
}
if (gm_mcp_package_can_handle_simple(package)) {
full = gm_mcp_package_get_name(package);
if (strlen(full) < strlen(info.name)) {
suffix = info.name + (strlen(full) + 1);
} else {
suffix = NULL;
}
gm_mcp_package_handle_simple(package, suffix,
info.fields);
} else {
gm_debug_msg(DEBUG_MCP, "GmMcpSession.HandleOob: can't handle simple message!");
}
} else {
gm_debug_msg(DEBUG_MCP, "GmMcpSession.HandleOob: unsupported package "
"%s, ignore message", info.name);
}
} else {
gm_debug_msg(DEBUG_MCP, "GmMcpSession.HandleOob: invalid authentication "
"key %s instead of %s, ignore message", info.authkey,
session->priv->authkey);
}
} else {
gm_debug_msg(DEBUG_MCP, "GmMcpSession.HandleOob: invalid message "
"(could not parse), ignore message");
}
// Free the info
if (info.authkey) {
g_free(info.authkey);
}
if (info.name) {
g_free(info.name);
}
if (info.fields) {
gm_mcp_destroy_fields(info.fields);
}
}
}
GObject *gm_mcp_session_world(GmMcpSession *session) {
return G_OBJECT(session->priv->world);
}