diff --git a/src/mcp/Makefile.include b/src/mcp/Makefile.include new file mode 100644 index 0000000..eec8170 --- /dev/null +++ b/src/mcp/Makefile.include @@ -0,0 +1,12 @@ +## Process this file with automake to produce Makefile.in +mcpdir = mcp + +gnoemoe_SOURCES += $(mcpdir)/gm-mcp-classes.c \ + $(mcpdir)/gm-mcp.c $(mcpdir)/gm-mcp.h \ + $(mcpdir)/gm-mcp-session.c $(mcpdir)/gm-mcp-session.h \ + $(mcpdir)/gm-mcp-package.c $(mcpdir)/gm-mcp-package.h \ + $(mcpdir)/gm-mcp-negotiate.c $(mcpdir)/gm-mcp-negotiate.h + +$(mcpdir)/gm-mcp-classes.c: $(mcpdir)/packages.defs + ( cd $(srcdir) && ./$(mcpdir)/mcpinit.rb \ + $(mcpdir)/packages.defs $(mcpdir)/gm-mcp-classes ) > $@ diff --git a/src/mcp/gm-mcp-negotiate.c b/src/mcp/gm-mcp-negotiate.c new file mode 100644 index 0000000..f8a04b7 --- /dev/null +++ b/src/mcp/gm-mcp-negotiate.c @@ -0,0 +1,295 @@ +#include +#include + +#include "gm-mcp-negotiate.h" +#include "gm-mcp.h" +#include "gm-mcp-session.h" +#include "gm-mcp-package.h" +#include "../debug.h" +#include "../gm-support.h" + +#define GM_MCP_NEGOTIATE_GET_PRIVATE(object)( \ + G_TYPE_INSTANCE_GET_PRIVATE((object), \ + GM_TYPE_MCP_NEGOTIATE, GmMcpNegotiatePrivate)) + +typedef struct _PackageInfo { + GmMcpPackageClass *klass; + gdouble version; +} PackageInfo; + +struct _GmMcpNegotiatePrivate { + GList *packages; +}; + +/* Signals + +enum { + PROTO + NUM_SIGNALS +}; + +static guint gm_mcp_negotiate_signals[NUM_SIGNALS] = {0};*/ + +void gm_mcp_negotiate_handle_simple(GmMcpPackage *package, gchar *suffix, + GList *fields); + +static void gm_mcp_negotiate_init(GmMcpNegotiate *self); +static void gm_mcp_negotiate_class_init(GmMcpNegotiateClass *klass); +//static void gm_mcp_negotiate_class_finalize(gpointer klass, +// gpointer klass_data); +static gpointer gm_mcp_negotiate_parent_class = NULL; + +static void gm_mcp_negotiate_class_intern_init(gpointer klass) { + gm_mcp_negotiate_parent_class = g_type_class_peek_parent(klass); + gm_mcp_negotiate_class_init((GmMcpNegotiateClass *) klass); +} + +GType +gm_mcp_negotiate_get_type() { + static GType g_define_type_id = 0; + + if (G_UNLIKELY(g_define_type_id == 0)) { + static const GTypeInfo g_define_type_info = { + sizeof (GmMcpNegotiateClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gm_mcp_negotiate_class_intern_init, + (GClassFinalizeFunc) NULL, //gm_mcp_negotiate_class_finalize, + NULL, /* class_data */ + sizeof (GmMcpNegotiate), + 0, /* n_preallocs */ + (GInstanceInitFunc) gm_mcp_negotiate_init, + NULL /* value_table */ + }; + + g_define_type_id = g_type_register_static(GM_TYPE_MCP_PACKAGE, + "GmMcpNegotiate", &g_define_type_info, (GTypeFlags) 0); + } + + return g_define_type_id; \ +} + +static void +gm_mcp_negotiate_finalize(GObject *object) { + //GmMcpNegotiate *obj = GM_MCP_NEGOTIATE(object); + + G_OBJECT_CLASS(gm_mcp_negotiate_parent_class)->finalize(object); +} + +/*static void +gm_mcp_negotiate_class_finalize(gpointer klass, gpointer klass_data) { + GmMcpPackageClass *pklass = GM_MCP_PACKAGE_CLASS(klass); + + g_list_free(pklass->depends); + g_list_free(pklass->overrides); +}*/ + +static void +gm_mcp_negotiate_class_init(GmMcpNegotiateClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + GmMcpPackageClass *pklass = GM_MCP_PACKAGE_CLASS(klass); + + object_class->finalize = gm_mcp_negotiate_finalize; + + /*gm_mcp_negotiate_signals[PROTO] = + g_signal_new("proto", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmMcpNegotiateClass, proto), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0);*/ + + pklass->name = "mcp-negotiate"; + pklass->handle_simple = &gm_mcp_negotiate_handle_simple; + + g_type_class_add_private(object_class, sizeof(GmMcpNegotiatePrivate)); +} + +static void +gm_mcp_negotiate_init(GmMcpNegotiate *obj) { + obj->priv = GM_MCP_NEGOTIATE_GET_PRIVATE(obj); + obj->priv->packages = NULL; +} + +PackageInfo * +gm_mcp_negotiate_find_package(GmMcpNegotiate *package, gchar *name) { + PackageInfo *pinfo; + GList *elem; + + for (elem = package->priv->packages; elem; elem = elem->next) { + pinfo = (PackageInfo *)(elem->data); + + if (strcasecmp(pinfo->klass->name, name) == 0) { + return pinfo; + } + } + + return NULL; +} + + +void +gm_mcp_negotiate_fix_overrides(GmMcpNegotiate *package) { + GList *l, *item, *over; + PackageInfo *pinfo, *pover; + gchar *name; + + l = g_list_copy(package->priv->packages); + + for (item = l; item; item = item->next) { + pinfo = (PackageInfo *)(item); + + for (over = pinfo->klass->overrides; over; over = over->next) { + name = (gchar *)(over->data); + + if ((pover = gm_mcp_negotiate_find_package(package, name))) { + debug_msg(2, "GmMcpNegotiate.FixOverrides: package %s " + "overrides %s", pinfo->klass->name, pover->klass->name); + package->priv->packages = g_list_remove( + package->priv->packages, pover); + } + } + } + + g_list_free(l); +} + +void +gm_mcp_negotiate_fix_depends(GmMcpNegotiate *package) { + PackageInfo *pinfo, *pdep; + GList *l, *item, *dep; + gchar *name; + + l = g_list_copy(package->priv->packages); + + for (item = l; item; item = item->next) { + pinfo = (PackageInfo *)(item->data); + + for (dep = pinfo->klass->depends; dep; dep = dep->next) { + name = (gchar *)(dep->data); + + if (!(pdep = gm_mcp_negotiate_find_package(package, name))) { + debug_msg(2, "GmMcpNegotiate.FixDepends: package %s depends " + "on %s, but %s is not supported", pinfo->klass->name, + name, name); + + // Remove package because depencendies are not met + package->priv->packages = g_list_remove( + package->priv->packages, pinfo); + break; + } else { + // Make sure this dependency is loaded before the package + package->priv->packages = g_list_remove( + package->priv->packages, pdep); + package->priv->packages = g_list_insert_before( + package->priv->packages, + g_list_find(package->priv->packages, pinfo), pdep); + } + } + } + + g_list_free(l); +} + +/* Public */ +GmMcpNegotiate * +gm_mcp_negotiate_new() { + GmMcpNegotiate *obj = GM_MCP_NEGOTIATE(g_object_new(GM_TYPE_MCP_NEGOTIATE, + NULL)); + + return obj; +} + +/* Private */ +gboolean +gm_mcp_negotiate_send_can(GmMcpPackageClass *klass, gpointer user_data) { + GmMcpNegotiate *package = GM_MCP_NEGOTIATE(user_data); + gchar *min_v, *max_v; + + min_v = g_strdup_printf("%'.1f", klass->min_version); + gm_fix_decimal_point_rev(min_v, strlen(min_v)); + + max_v = g_strdup_printf("%'.1f", klass->max_version); + gm_fix_decimal_point_rev(max_v, strlen(max_v)); + + gm_mcp_session_send_simple(GM_MCP_PACKAGE_SESSION(package), + "mcp-negotiate-can", "package", klass->name, "min-version", + min_v, "max-version", max_v, NULL); + + g_free(min_v); + g_free(max_v); + + return FALSE; +} + +void +gm_mcp_negotiate_handle_simple(GmMcpPackage *package, gchar *suffix, + GList *fields) { + gchar const *pname; + GmMcpPackageClass *pklass; + GmMcpNegotiate *negotiate = GM_MCP_NEGOTIATE(package); + PackageInfo *pinfo; + double version; + gchar const *min_v, *max_v; + gchar *cmin, *cmax; + GList *elem; + + if (strcasecmp(suffix, "can") == 0) { + // Fields has package, min-version, max-version + pname = gm_mcp_find_value(fields, "package"); + pklass = gm_mcp_session_find_package_class(pname); + + if (pklass) { + min_v = gm_mcp_find_value(fields, "min-version"); + cmin = gm_fix_decimal_point(g_strdup(min_v), strlen(min_v)); + + max_v = gm_mcp_find_value(fields, "max-version"); + cmax = gm_fix_decimal_point(g_strdup(max_v), strlen(max_v)); + + version = gm_mcp_get_version(pklass->min_version, + pklass->max_version, g_ascii_strtod(min_v, NULL), + g_ascii_strtod(max_v, NULL)); + + g_free(cmin); + g_free(cmax); + + if (version > 0.0) { + debug_msg(2, "GmMcpNegotiate.HandleSimple: %s, " + "package is supported", pname); + pinfo = g_new(PackageInfo, 1); + pinfo->klass = pklass; + pinfo->version = version; + + negotiate->priv->packages = g_list_append( + negotiate->priv->packages, pinfo); + } else { + debug_msg(2, "GmMcpNegotiate.HandleSimple: %s, package " + "supported but wrong version!", pname); + } + } else { + debug_msg(2, "GmMcpNegotiate.HandleSimple: %s, package is not " + "supported!", pname); + } + } else if (strcasecmp(suffix, "end") == 0) { + gm_mcp_session_package_class_for_each(gm_mcp_negotiate_send_can, + (gpointer)(negotiate)); + + gm_mcp_session_send_simple(GM_MCP_PACKAGE_SESSION(negotiate), + "mcp-negotiate-end", NULL); + + gm_mcp_negotiate_fix_overrides(negotiate); + gm_mcp_negotiate_fix_depends(negotiate); + + for (elem = negotiate->priv->packages; elem; elem = elem->next) { + pinfo = (PackageInfo *)(elem->data); + gm_mcp_session_create_package(GM_MCP_PACKAGE_SESSION(negotiate), + pinfo->klass, pinfo->version); + g_free(pinfo); + } + + g_list_free(negotiate->priv->packages); + negotiate->priv->packages = NULL; + } +} diff --git a/src/mcp/gm-mcp-negotiate.h b/src/mcp/gm-mcp-negotiate.h new file mode 100644 index 0000000..eeba443 --- /dev/null +++ b/src/mcp/gm-mcp-negotiate.h @@ -0,0 +1,57 @@ +#ifndef __GM_MCP_NEGOTIATE_H__ +#define __GM_MCP_NEGOTIATE_H__ + +#include +#include "gm-mcp-package.h" + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_MCP_NEGOTIATE (gm_mcp_negotiate_get_type()) +#define GM_MCP_NEGOTIATE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GM_TYPE_MCP_NEGOTIATE, GmMcpNegotiate)) +#define GM_MCP_NEGOTIATE_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + GM_TYPE_MCP_NEGOTIATE, GmMcpNegotiate const)) +#define GM_MCP_NEGOTIATE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + GM_TYPE_MCP_NEGOTIATE, GmMcpNegotiateClass)) +#define GM_IS_MCP_NEGOTIATE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + GM_TYPE_MCP_NEGOTIATE)) +#define GM_IS_MCP_NEGOTIATE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + GM_TYPE_MCP_NEGOTIATE)) +#define GM_MCP_NEGOTIATE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + GM_TYPE_MCP_NEGOTIATE, GmMcpNegotiateClass)) + +/* Private structure type */ +typedef struct _GmMcpNegotiatePrivate GmMcpNegotiatePrivate; + +/* + * Main object structure + */ +typedef struct _GmMcpNegotiate GmMcpNegotiate; + +struct _GmMcpNegotiate { + GmMcpPackage parent; + + /*< private > */ + GmMcpNegotiatePrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GmMcpNegotiateClass GmMcpNegotiateClass; + +struct _GmMcpNegotiateClass { + GmMcpPackageClass parent_class; + + /* Signals + void (* proto) (GmMcpNegotiate *obj); */ +}; + +GType gm_mcp_negotiate_get_type(void) G_GNUC_CONST; +GmMcpNegotiate *gm_mcp_negotiate_new(void); + +G_END_DECLS +#endif /* __GM_MCP_NEGOTIATE_H__ */ diff --git a/src/mcp/gm-mcp-package.c b/src/mcp/gm-mcp-package.c new file mode 100644 index 0000000..fecbcb4 --- /dev/null +++ b/src/mcp/gm-mcp-package.c @@ -0,0 +1,140 @@ +#include +#include "gm-mcp-session.h" +#include "gm-mcp-package.h" +#include "../gm-support.h" + +#define GM_MCP_PACKAGE_GET_PRIVATE(object)( \ + G_TYPE_INSTANCE_GET_PRIVATE((object), \ + GM_TYPE_MCP_PACKAGE, GmMcpPackagePrivate)) + +struct _GmMcpPackagePrivate { + GmMcpSession *session; + gdouble version; +}; + +/* Signals + +enum { + PROTO + NUM_SIGNALS +}; + +static guint gm_mcp_package_signals[NUM_SIGNALS] = {0};*/ + +G_DEFINE_TYPE(GmMcpPackage, gm_mcp_package, G_TYPE_OBJECT) + +static void +gm_mcp_package_finalize(GObject *object) { + //GmMcpPackage *package = GM_MCP_PACKAGE(object); + G_OBJECT_CLASS(gm_mcp_package_parent_class)->finalize(object); +} + +static void +gm_mcp_package_class_init(GmMcpPackageClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gm_mcp_package_finalize; + + /*gm_mcp_package_signals[PROTO] = + g_signal_new("proto", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmMcpPackageClass, proto), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0);*/ + + klass->handle_simple = NULL; + klass->handle_multi = NULL; + klass->depends = NULL; + klass->overrides = NULL; + klass->name = NULL; + klass->min_version = 1.0; + klass->max_version = 1.0; + + g_type_class_add_private(object_class, sizeof(GmMcpPackagePrivate)); +} + +static void +gm_mcp_package_init(GmMcpPackage *obj) { + obj->priv = GM_MCP_PACKAGE_GET_PRIVATE(obj); + obj->priv->session = NULL; + obj->priv->version = 1.0; +} + +/* Public */ +GmMcpPackage * +gm_mcp_package_new() { + GmMcpPackage *obj = GM_MCP_PACKAGE(g_object_new(GM_TYPE_MCP_PACKAGE, NULL)); + return obj; +} + +void +gm_mcp_package_handle_simple(GmMcpPackage *package, gchar *suffix, + GList *fields) { + return GM_MCP_PACKAGE_GET_CLASS(package)->handle_simple(package, + suffix, fields); +} + +gboolean +gm_mcp_package_can_handle_simple(GmMcpPackage *package) { + return (GM_MCP_PACKAGE_GET_CLASS(package)->handle_simple != NULL); +} + +gboolean +gm_mcp_package_handle_multi(GmMcpPackage *package, gchar *data_tag, + gchar *key, gchar *value, GList *allValues) { + return GM_MCP_PACKAGE_GET_CLASS(package)->handle_multi(package, + data_tag, key, value, allValues); +} + +gboolean +gm_mcp_package_can_handle_multi(GmMcpPackage *package) { + return (GM_MCP_PACKAGE_GET_CLASS(package)->handle_multi != NULL); +} + +void +gm_mcp_package_set_session(GmMcpPackage *package, GObject *session) { + package->priv->session = GM_MCP_SESSION(session); +} + +GObject * +gm_mcp_package_get_session(GmMcpPackage *package) { + return G_OBJECT(package->priv->session); +} + +void +gm_mcp_package_set_version(GmMcpPackage *package, gdouble version) { + package->priv->version = version; +} + +gdouble +gm_mcp_package_get_version(GmMcpPackage *package) { + return package->priv->version; +} + +gchar const * +gm_mcp_package_get_name(GmMcpPackage *package) { + return GM_MCP_PACKAGE_GET_CLASS(package)->name; +} + +GList const * +gm_mcp_package_get_depends(GmMcpPackage *package) { + return GM_MCP_PACKAGE_GET_CLASS(package)->depends; +} + +GList const * +gm_mcp_package_get_overrides(GmMcpPackage *package) { + return GM_MCP_PACKAGE_GET_CLASS(package)->overrides; +} + +gdouble +gm_mcp_package_get_min_version(GmMcpPackage *package) { + return GM_MCP_PACKAGE_GET_CLASS(package)->min_version; +} + +gdouble +gm_mcp_package_get_max_version(GmMcpPackage *package) { + return GM_MCP_PACKAGE_GET_CLASS(package)->max_version; +} diff --git a/src/mcp/gm-mcp-package.h b/src/mcp/gm-mcp-package.h new file mode 100644 index 0000000..871fae9 --- /dev/null +++ b/src/mcp/gm-mcp-package.h @@ -0,0 +1,89 @@ +#ifndef __GM_MCP_PACKAGE_H__ +#define __GM_MCP_PACKAGE_H__ + +#include + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_MCP_PACKAGE (gm_mcp_package_get_type()) +#define GM_MCP_PACKAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GM_TYPE_MCP_PACKAGE, GmMcpPackage)) +#define GM_MCP_PACKAGE_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + GM_TYPE_MCP_PACKAGE, GmMcpPackage const)) +#define GM_MCP_PACKAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + GM_TYPE_MCP_PACKAGE, GmMcpPackageClass)) +#define GM_IS_MCP_PACKAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + GM_TYPE_MCP_PACKAGE)) +#define GM_IS_MCP_PACKAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + GM_TYPE_MCP_PACKAGE)) +#define GM_MCP_PACKAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + GM_TYPE_MCP_PACKAGE, GmMcpPackageClass)) +#define GM_MCP_PACKAGE_SESSION(obj) (GM_MCP_SESSION( \ + gm_mcp_package_get_session(GM_MCP_PACKAGE(obj)))) + +/* Private structure type */ +typedef struct _GmMcpPackagePrivate GmMcpPackagePrivate; + +/* + * Main object structure + */ +typedef struct _GmMcpPackage GmMcpPackage; + +struct _GmMcpPackage { + GObject parent; + + /*< private > */ + GmMcpPackagePrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GmMcpPackageClass GmMcpPackageClass; + +struct _GmMcpPackageClass { + GObjectClass parent_class; + + gchar *name; + gdouble min_version; + gdouble max_version; + GList *depends; + GList *overrides; + + void (* handle_simple)(GmMcpPackage *package, gchar *suffix, GList *fields); + gboolean (* handle_multi)(GmMcpPackage *package, gchar *data_tag, + gchar *key, gchar *value, GList *allValues); + + /* Signals + void (* proto) (GmMcpPackage *obj); */ +}; + +/* Public */ +GType gm_mcp_package_get_type(void) G_GNUC_CONST; +GmMcpPackage *gm_mcp_package_new(); +void gm_mcp_package_set_session(GmMcpPackage *package, GObject *session); + +void gm_mcp_package_handle_simple(GmMcpPackage *package, gchar *suffix, + GList *fields); +gboolean gm_mcp_package_can_handle_simple(GmMcpPackage *package); + +gboolean gm_mcp_package_handle_multi(GmMcpPackage *package, gchar *data_tag, + gchar *key, gchar *value, GList *allValues); +gboolean gm_mcp_package_can_handle_multi(GmMcpPackage *package); + +void gm_mcp_package_set_version(GmMcpPackage *package, gdouble version); +gdouble gm_mcp_package_get_version(GmMcpPackage *package); +GObject *gm_mcp_package_get_session(GmMcpPackage *package); + +/* Class getters */ +gchar const *gm_mcp_package_get_name(GmMcpPackage *package); +GList const *gm_mcp_package_get_depends(GmMcpPackage *package); +GList const *gm_mcp_package_get_overrides(GmMcpPackage *package); +gdouble gm_mcp_package_get_min_version(GmMcpPackage *package); +gdouble gm_mcp_package_get_max_version(GmMcpPackage *package); + +G_END_DECLS +#endif /* __GM_MCP_PACKAGE_H__ */ diff --git a/src/mcp/gm-mcp-session.c b/src/mcp/gm-mcp-session.c new file mode 100644 index 0000000..5629fba --- /dev/null +++ b/src/mcp/gm-mcp-session.c @@ -0,0 +1,493 @@ +#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 "../debug.h" +#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); + } + } +} diff --git a/src/mcp/gm-mcp-session.h b/src/mcp/gm-mcp-session.h new file mode 100644 index 0000000..659fe37 --- /dev/null +++ b/src/mcp/gm-mcp-session.h @@ -0,0 +1,87 @@ +#ifndef __GM_MCP_SESSION_H__ +#define __GM_MCP_SESSION_H__ + +#include +#include "gm-mcp-package.h" + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_MCP_SESSION (gm_mcp_session_get_type()) +#define GM_MCP_SESSION(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GM_TYPE_MCP_SESSION, GmMcpSession)) +#define GM_MCP_SESSION_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + GM_TYPE_MCP_SESSION, GmMcpSession const)) +#define GM_MCP_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + GM_TYPE_MCP_SESSION, GmMcpSessionClass)) +#define GM_IS_MCP_SESSION(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + GM_TYPE_MCP_SESSION)) +#define GM_IS_MCP_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + GM_TYPE_MCP_SESSION)) +#define GM_MCP_SESSION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + GM_TYPE_MCP_SESSION, GmMcpSessionClass)) + +typedef struct _McpMultilineInfo { + gchar *key; + gchar *data_tag; + GList *data; + GmMcpPackage *package; +} McpMultilineInfo; + +typedef gboolean (* GmPackageClassFunc) (GmMcpPackageClass *klass, + gpointer user_data); + +/* Private structure type */ +typedef struct _GmMcpSessionPrivate GmMcpSessionPrivate; + +/* + * Main object structure + */ +typedef struct _GmMcpSession GmMcpSession; + +struct _GmMcpSession { + GObject parent; + + /*< private > */ + GmMcpSessionPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GmMcpSessionClass GmMcpSessionClass; + +struct _GmMcpSessionClass { + GObjectClass parent_class; + GList *available_packages; + + /* Signals + void (* proto) (GmMcpSession *obj); */ +}; + +GType gm_mcp_session_get_type(void) G_GNUC_CONST; +GmMcpSession *gm_mcp_session_new(GObject *world); + +GList const *gm_mcp_session_get_packages(GmMcpSession *session); +void gm_mcp_session_handle_oob(GmMcpSession *session, gchar *line); + +GmMcpPackage *gm_mcp_session_has_package_of_class(GmMcpSession *session, + GmMcpPackageClass *klass); +GmMcpPackageClass *gm_mcp_session_find_package_class(gchar const *name); +GmMcpPackage *gm_mcp_session_find_package(GmMcpSession *session, + gchar const *pname); +GmMcpPackageClass *gm_mcp_session_package_class_for_each( + GmPackageClassFunc func, gpointer user_data); + +GmMcpPackage *gm_mcp_session_create_package(GmMcpSession *session, + GmMcpPackageClass *klass, gdouble version); + +void gm_mcp_session_send_simple(GmMcpSession *session, gchar const *pname, + gchar const *first_key, ...); +void gm_mcp_session_send_multiline(GmMcpSession *session, gchar const *data_tag, + gchar const *key, GList const *lines); + +G_END_DECLS +#endif /* __GM_MCP_SESSION_H__ */ diff --git a/src/mcp/gm-mcp.c b/src/mcp/gm-mcp.c new file mode 100644 index 0000000..7567704 --- /dev/null +++ b/src/mcp/gm-mcp.c @@ -0,0 +1,266 @@ +#include +#include +#include + +#include "gm-mcp.h" +#include "../gm-support.h" + +#define MCP_MIN_VERSION 2.1 +#define MCP_MAX_VERSION 2.1 + +gdouble +gm_mcp_get_version(gdouble client_min, gdouble client_max, gdouble server_min, + gdouble server_max) { + if (client_max >= server_min && server_max >= client_min) { + if (client_max < server_max) { + return server_max; + } else { + return client_max; + } + } else { + return 0.0; + } +} + +gchar const * +gm_mcp_find_value(GList const *fields, gchar const *key) { + GmKeyValuePair *tmp; + + while (fields) { + tmp = (GmKeyValuePair *)(fields->data); + + if (strcasecmp(key, tmp->key) == 0) { + return tmp->value; + } + + fields = fields->next; + } + + return NULL; +} + +gchar * +gm_mcp_escape_if_needed(gchar const *line) { + GString *new_line; + gchar const *ptr = line; + gchar *result; + + if (*line == '\0') { + return g_strdup("\"\""); + } + + if (g_utf8_strchr(line, -1, ' ') || g_utf8_strchr(line, -1, '"') || + g_utf8_strchr(line, -1, '\\')) { + new_line = g_string_new("\""); + + while (*ptr != '\0') { + if (*ptr == '"' || *ptr == '\\') { + new_line = g_string_append_c(new_line, '\\'); + } + + new_line = g_string_append_c(new_line, *ptr); + ++ptr; + } + + new_line = g_string_append_c(new_line, '"'); + result = new_line->str; + g_string_free(new_line, FALSE); + + return result; + } else { + return g_strdup(line); + } +} + +gchar * +gm_mcp_un_escape(gchar *line) { + gchar *ptri = line, *ptrj = line; + gboolean shifted = FALSE; + + while (*ptri != '\0') { + if (*ptri == '\\') { + shifted = TRUE; + ++ptri; + } + + if (shifted) { + *ptrj = *ptri; + } + + ++ptrj; + ++ptri; + } + + *ptrj = '\0'; + return line; +} + +gchar * +gm_mcp_process_keyval(GList **info, gchar *line) { + gchar *keystart; + gchar *valuestart; + int keylen, valuelen; + GmKeyValuePair *newKeyValue; + + if (*line != ' ' || line[1] == '\0' || line[1] == ' ') { + return NULL; + } + + keystart = line + 1; + gm_string_skip_till(&line, ": "); + + if (*line != ':') { + return NULL; + } + + keylen = line - keystart - 1; + line = g_utf8_next_char(line); + + if (line[1] == '\0' || *line != ' ') { + return NULL; + } + + line = g_utf8_next_char(line); + + if (*line == ' ') { + return NULL; + } + + valuestart = line; + + if (*line == '"') { + ++valuestart; + ++line; + + while (*line != '\0' && *line != '"') { + if (*line == '\\') { + ++line; + } + + ++line; + } + + valuelen = line - valuestart; + + if (*line != '"') { + --valuelen; + } else { + ++line; + } + } else { + gm_string_skip_nonspace(&line); + valuelen = line - valuestart; + } + + newKeyValue = g_new(GmKeyValuePair, 1); + newKeyValue->key = g_strndup(keystart, keylen); + newKeyValue->value = gm_mcp_un_escape(g_strndup(valuestart, valuelen)); + + *info = g_list_append(*info, newKeyValue); + + return line; +} + +GList * +gm_mcp_process_key_values(gchar *line) { + GList *result = NULL; + + while (line && *line != '\0') { + line = gm_mcp_process_keyval(&result, line); + } + + return result; +} + +gboolean +gm_mcp_parse_line(gchar *line, McpMessageInfo *info) { + gchar *p = g_utf8_strchr(line, -1, ' '); + + if (!p || p == line) { + return FALSE; + } + + info->name = g_strndup(line, (p - line)); + line = p + 1; + + // Now there should be a authentication key! + p = g_utf8_strchr(line, -1, ' '); + + if (!p) { + p = line + strlen(line); + } + + info->authkey = g_strndup(line, (p - line)); + + // Now process the keyvals + info->fields = gm_mcp_process_key_values(p); + + return TRUE; +} + +gchar * +gm_mcp_generate_key(gint len) { + gchar *s = g_malloc(len + 1); + gchar ref[] = + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"; + gint n = strlen(ref); + gint i; + + srand((int) time(NULL)); + + for (i = 0; i < len; i++) { + s[i] = ref[rand() % n]; + } + + s[i] = 0; + return s; +} + +gchar * +gm_mcp_generate_data_tag() { + return gm_mcp_generate_key(10); +} + +gchar * +gm_mcp_generate_auth_key() { + return gm_mcp_generate_key(6); +} + +gchar const * +gm_mcp_find_multiline_tag(GList const *fields) { + GmKeyValuePair *data; + gunichar last; + + while (fields) { + data = (GmKeyValuePair *)(fields->data); + last = g_utf8_get_char(g_utf8_prev_char(data->key + strlen(data->key))); + + if (last == '*') { + return data->key; + } + + fields = fields->next; + } + + return NULL; +} + +void +gm_mcp_destroy_fields(GList *fields) { + GmKeyValuePair *tmp; + GList *elem = fields; + + while (elem) { + tmp = (GmKeyValuePair *) (elem->data); + + g_free(tmp->key); + g_free(tmp->value); + + g_free(elem->data); + elem->data = NULL; + + elem = elem->next; + } + + g_list_free(fields); +} diff --git a/src/mcp/gm-mcp.h b/src/mcp/gm-mcp.h new file mode 100644 index 0000000..821f8d0 --- /dev/null +++ b/src/mcp/gm-mcp.h @@ -0,0 +1,28 @@ +#ifndef __GM_MCP_H__ +#define __GM_MCP_H__ + +#include +#include "gm-mcp.h" + +#define MCP_MIN_VERSION 2.1 +#define MCP_MAX_VERSION 2.1 + +typedef struct _McpMessageInfo { + gchar *name; + gchar *authkey; + GList *fields; +} McpMessageInfo; + +gdouble gm_mcp_get_version(gdouble client_min, gdouble client_max, + gdouble server_min, gdouble server_max); +gchar const *gm_mcp_find_value(GList const *fields, gchar const *key); +gchar *gm_mcp_escape_if_needed(gchar const *line); +gchar *gm_mcp_un_escape(gchar *line); +GList *gm_mcp_process_key_values(gchar *line); +gboolean gm_mcp_parse_line(gchar *line, McpMessageInfo *info); +gchar *gm_mcp_generate_data_tag(); +gchar *gm_mcp_generate_auth_key(); +gchar const *gm_mcp_find_multiline_tag(GList const *fields); +void gm_mcp_destroy_fields(GList *fields); + +#endif /* __GM_MCP_H__ */ diff --git a/src/mcp/mcpinit.rb b/src/mcp/mcpinit.rb new file mode 100755 index 0000000..1787e09 --- /dev/null +++ b/src/mcp/mcpinit.rb @@ -0,0 +1,34 @@ +#!/usr/bin/ruby + +begin + defs = File.readlines(ARGV[0]) + defs.collect! {|elem| elem.chomp} + + fout = File.open(ARGV[1] + '.c', 'w') + + includes = [''] + content = "GList *\ngm_mcp_classes_initialize() {\n\tGList *result = NULL;\n\n" + + defs.each do |line| + ucase = line.gsub(/[A-Z]+[a-z]+/) do |s| + s.upcase + '_' + end + + ucase.chop! + mcase = ucase.downcase.gsub('_', '-') + + includes << '"gm-mcp-' + mcase + '.h"' + content += "\tresult = g_list_append(result, \n\t\t\tg_type_class_ref(GM_TYPE_MCP_" + ucase + "));\n" + end + + includes.each {|inc| fout.write('#include ' + inc + "\n")} + fout.write("\n" + content + "\treturn result;\n}\n") + fout.close + + fout = File.open(ARGV[1] + '.h', 'w') + fout.write("GList *gm_mcp_classes_initialize();\n") + fout.close +rescue StandardError => boom + p boom + exit(1) +end diff --git a/src/mcp/packages.defs b/src/mcp/packages.defs new file mode 100644 index 0000000..5e7babf --- /dev/null +++ b/src/mcp/packages.defs @@ -0,0 +1 @@ +Negotiate