#include #include #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); }