2005-11-06 16:57:34 +01:00
|
|
|
#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"
|
2005-11-06 17:27:05 +01:00
|
|
|
#include "../gm-debug.h"
|
2005-11-06 16:57:34 +01:00
|
|
|
#include "../gm-options.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 {
|
|
|
|
PROTO
|
|
|
|
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);
|
|
|
|
|
|
|
|
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[PROTO] =
|
|
|
|
g_signal_new("proto",
|
|
|
|
G_OBJECT_CLASS_TYPE(object_class),
|
|
|
|
G_SIGNAL_RUN_LAST,
|
|
|
|
G_STRUCT_OFFSET(GmMcpSessionClass, proto),
|
|
|
|
NULL, NULL,
|
|
|
|
g_cclosure_marshal_VOID__VOID,
|
|
|
|
G_TYPE_NONE,
|
|
|
|
0);*/
|
|
|
|
|
|
|
|
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;
|
|
|
|
double min_v, max_v, version;
|
|
|
|
|
|
|
|
if (!(min_version && max_version)) {
|
|
|
|
debug_msg(2, "GmMcpSession.HandleOpen: invalid opening, minVersion OR "
|
|
|
|
"maxVersion 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(minc, NULL);
|
|
|
|
max_v = g_ascii_strtod(maxc, NULL);
|
|
|
|
|
|
|
|
g_free(minc);
|
|
|
|
g_free(maxc);
|
|
|
|
|
|
|
|
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 {
|
|
|
|
debug_msg(2, "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) {
|
|
|
|
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 *line) {
|
|
|
|
gchar *data_tag, *key, *value, *ptr = line, *key_start;
|
|
|
|
|
|
|
|
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) {
|
|
|
|
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 *ptr = line;
|
|
|
|
gchar *data_tag;
|
|
|
|
|
|
|
|
while (*ptr != '\0' && g_unichar_isspace(g_utf8_get_char(ptr))) {
|
|
|
|
ptr = g_utf8_next_char(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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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(session->priv->world, msg_line);
|
|
|
|
gm_world_log(session->priv->world, LOG_MCP_OUT, msg_line);
|
|
|
|
|
|
|
|
g_free(msg_line);
|
|
|
|
lines = lines->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_free(msg);
|
|
|
|
msg_line = g_strconcat("#$#: ", data_tag, NULL);
|
|
|
|
|
|
|
|
gm_world_sendln(session->priv->world, msg_line);
|
|
|
|
gm_world_log(session->priv->world, LOG_MCP_OUT, msg_line);
|
|
|
|
|
|
|
|
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(session->priv->world, msg->str);
|
|
|
|
gm_world_log(session->priv->world, LOG_MCP_OUT, msg->str);
|
|
|
|
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);
|
|
|
|
return package;
|
|
|
|
} else {
|
|
|
|
debug_msg(2, "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;
|
|
|
|
|
|
|
|
debug_msg(2, "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
|
|
|
|
debug_msg(2, "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);
|
|
|
|
|
|
|
|
debug_msg(2, "GmMcpSession.HandleOob: multiline "
|
|
|
|
"opening detected and stored "
|
|
|
|
"(data_tag = %s, key = %s)",
|
|
|
|
minfo->data_tag,
|
|
|
|
minfo->key);
|
|
|
|
} else {
|
|
|
|
debug_msg(2, "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 {
|
|
|
|
debug_msg(2, "GmMcpSession.HandleOob: unsupported package "
|
|
|
|
"%s, ignore message", info.name);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
debug_msg(2, "GmMcpSession.HandleOob: invalid authentication "
|
|
|
|
"key %s instead of %s, ignore message", info.authkey,
|
|
|
|
session->priv->authkey);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
debug_msg(2, "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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|