[gnome-builder/wip/chergert/bugbug] contrib: add mi2-glib



commit 850e35df9d4adcc0c2153a31e2eb3821a32d9366
Author: Christian Hergert <chergert redhat com>
Date:   Wed Aug 9 15:42:46 2017 -0700

    contrib: add mi2-glib
    
    This is the beginning of a glib'ish API we can use to communicate with
    gdb using data structures from Gio which bind well with Gtk.

 contrib/mi2/Makefile.am            |  136 ++++
 contrib/mi2/data/test-stream-1.txt |   56 ++
 contrib/mi2/meson.build            |   84 +++
 contrib/mi2/mi2-breakpoint.c       |  329 ++++++++++
 contrib/mi2/mi2-breakpoint.h       |   52 ++
 contrib/mi2/mi2-client.c           | 1185 ++++++++++++++++++++++++++++++++++++
 contrib/mi2/mi2-client.h           |  155 +++++
 contrib/mi2/mi2-command-message.c  |  168 +++++
 contrib/mi2/mi2-command-message.h  |   37 ++
 contrib/mi2/mi2-console-message.c  |  166 +++++
 contrib/mi2/mi2-console-message.h  |   37 ++
 contrib/mi2/mi2-enums.c.in         |   39 ++
 contrib/mi2/mi2-enums.h.in         |   24 +
 contrib/mi2/mi2-error.c            |   25 +
 contrib/mi2/mi2-error.h            |   39 ++
 contrib/mi2/mi2-event-message.c    |  152 +++++
 contrib/mi2/mi2-event-message.h    |   37 ++
 contrib/mi2/mi2-glib.h             |   36 ++
 contrib/mi2/mi2-info-message.c     |  142 +++++
 contrib/mi2/mi2-info-message.h     |   37 ++
 contrib/mi2/mi2-input-stream.c     |  141 +++++
 contrib/mi2/mi2-input-stream.h     |   57 ++
 contrib/mi2/mi2-message.c          |  257 ++++++++
 contrib/mi2/mi2-message.h          |   66 ++
 contrib/mi2/mi2-output-stream.c    |  106 ++++
 contrib/mi2/mi2-output-stream.h    |   58 ++
 contrib/mi2/mi2-reply-message.c    |  220 +++++++
 contrib/mi2/mi2-reply-message.h    |   39 ++
 contrib/mi2/mi2-util.c             |  262 ++++++++
 contrib/mi2/mi2-util.h             |   37 ++
 contrib/mi2/test-client.c          |  305 +++++++++
 contrib/mi2/test-stream.c          |  132 ++++
 meson.build                        |    1 +
 33 files changed, 4617 insertions(+), 0 deletions(-)
---
diff --git a/contrib/mi2/Makefile.am b/contrib/mi2/Makefile.am
new file mode 100644
index 0000000..ab7d112
--- /dev/null
+++ b/contrib/mi2/Makefile.am
@@ -0,0 +1,136 @@
+CLEANFILES =
+DISTCLEANFILES =
+BUILT_SOURCES =
+
+EXTRA_DIST =              \
+       test-stream-1.txt \
+       $(NULL)
+
+pkglibdir = $(libdir)/gnome-builder
+pkglib_LTLIBRARIES = libmi2-glib.la
+
+libmi2_glib_la_public_sources =          \
+       mi2-breakpoint.c                 \
+       mi2-breakpoint.h                 \
+       mi2-client.c                     \
+       mi2-client.h                     \
+       mi2-command-message.c            \
+       mi2-command-message.h            \
+       mi2-console-message.c            \
+       mi2-console-message.h            \
+       mi2-event-message.c              \
+       mi2-event-message.h              \
+       mi2-error.c                      \
+       mi2-error.h                      \
+       mi2-info-message.c               \
+       mi2-info-message.h               \
+       mi2-input-stream.c               \
+       mi2-input-stream.h               \
+       mi2-message.c                    \
+       mi2-message.h                    \
+       mi2-reply-message.c              \
+       mi2-reply-message.h              \
+       mi2-output-stream.c              \
+       mi2-output-stream.h              \
+       mi2-util.c                       \
+       mi2-util.h                       \
+       $(NULL)
+
+libmi2_glib_la_SOURCES =                 \
+       $(libmi2_glib_la_public_sources) \
+       mi2-glib.h                       \
+       mi2-enums.c                      \
+       mi2-enums.h                      \
+       $(NULL)
+
+libmi2_glib_la_CFLAGS =                     \
+       -DMI2_GLIB_COMPILATION              \
+       $(DEBUG_CFLAGS)                     \
+       $(MI2_CFLAGS)                       \
+       $(OPTIMIZE_CFLAGS)                  \
+       $(NULL)
+
+libmi2_glib_la_LIBADD = $(MI2_LIBS)
+libmi2_glib_la_LDFLAGS = $(OPTIMIZE_LDFLAGS)
+
+glib_enum_headers =  \
+       mi2-error.h \
+       $(NULL)
+
+glib_enum_h = mi2-enums.h
+glib_enum_c = mi2-enums.c
+
+include $(top_srcdir)/build/autotools/Makefile.am.enums
+
+if HAVE_INTROSPECTION
+-include $(INTROSPECTION_MAKEFILE)
+
+INTROSPECTION_GIRS =
+INTROSPECTION_SCANNER_ARGS = --add-include-path=$(srcdir) --warn-all
+INTROSPECTION_COMPILER_ARGS = --includedir=$(srcdir)
+
+Mi2-1.0.gir: libmi2-glib.la
+Mi2_1_0_gir_INCLUDES = Gio-2.0
+Mi2_1_0_gir_CFLAGS = $(libmi2_glib_la_CFLAGS)
+Mi2_1_0_gir_LIBS = libmi2-glib.la
+Mi2_1_0_gir_FILES = $(libmi2_glib_la_public_sources)
+Mi2_1_0_gir_SCANNERFLAGS =       \
+       --c-include="mi2-glib.h" \
+       -n Mi2                   \
+       $(NULL)
+INTROSPECTION_GIRS += Mi2-1.0.gir
+
+girdir = $(datadir)/gnome-builder/gir-1.0
+dist_gir_DATA = $(INTROSPECTION_GIRS)
+
+typelibdir = $(pkglibdir)/girepository-1.0
+typelib_DATA = $(INTROSPECTION_GIRS:.gir=.typelib)
+
+CLEANFILES += $(dist_gir_DATA) $(typelib_DATA)
+endif
+
+if ENABLE_VAPIGEN
+-include $(VAPIGEN_MAKEFILE)
+
+mi2-glib.vapi: Mi2-1.0.gir
+
+VAPIGEN_VAPIS = mi2-glib.vapi
+
+mi2_glib_vapi_DEPS = gio-2.0 json-glib-1.0
+mi2_glib_vapi_METADATADIRS = $(srcdir)
+mi2_glib_vapi_FILES = Mi2-1.0.gir
+
+mi2-glib.deps: Makefile
+       $(AM_V_GEN) echo $(libmi2_glib_vapi_DEPS) | tr ' ' '\n' > $@
+
+vapidir = $(datadir)/gnome-builder/vapi
+vapi_DATA = $(VAPIGEN_VAPIS) $(VAPIGEN_VAPIS:.vapi=.deps)
+
+EXTRA_DIST += mi2-glib.deps
+
+DISTCLEANFILES += $(vapi_DATA)
+endif
+
+noinst_PROGRAMS =
+TESTS =
+
+tests_cflags =                              \
+       -DTEST_DATA_DIR="\"$(abs_srcdir)\"" \
+       $(MI2_CFLAGS)                       \
+       $(NULL)
+
+tests_libs =           \
+       $(MI2_LIBS)    \
+       libmi2-glib.la \
+       $(NULL)
+
+noinst_PROGRAMS += test-stream
+TESTS += test-stream
+test_stream_CFLAGS = $(tests_cflags)
+test_stream_LDADD = $(tests_libs)
+
+noinst_PROGRAMS += test-client
+test_client_CFLAGS = $(tests_cflags)
+test_client_LDADD = $(tests_libs)
+
+-include $(top_srcdir)/git.mk
diff --git a/contrib/mi2/data/test-stream-1.txt b/contrib/mi2/data/test-stream-1.txt
new file mode 100644
index 0000000..7cfd226
--- /dev/null
+++ b/contrib/mi2/data/test-stream-1.txt
@@ -0,0 +1,56 @@
+=thread-group-added,id="i1"
+~"GNU gdb (GDB) Fedora 7.12.50.20170226-4.fc26\n"
+~"Copyright (C) 2017 Free Software Foundation, Inc.\n"
+~"License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\nThis is free software: you 
are free to change and redistribute it.\nThere is NO WARRANTY, to the extent permitted by law.  Type \"show 
copying\"\nand \"show warranty\" for details.\n"
+~"This GDB was configured as \"x86_64-redhat-linux-gnu\".\nType \"show configuration\" for configuration 
details."
+~"\nFor bug reporting instructions, please see:\n"
+~"<http://www.gnu.org/software/gdb/bugs/>.\n"
+~"Find the GDB manual and other documentation resources online 
at:\n<http://www.gnu.org/software/gdb/documentation/>.\n"
+~"For help, type \"help\".\n"
+~"Type \"apropos word\" to search for commands related to \"word\"...\n"
+~"Reading symbols from ls..."
+~"Reading symbols from /home/christian/Projects/gnome-builder/ls..."
+~"(no debugging symbols found)...done.\n"
+~"(no debugging symbols found)...done.\n"
+(gdb) 
+&"b main\n"
+~"Breakpoint 1 at 0x38b0\n"
+=breakpoint-created,bkpt={number="1",type="breakpoint",disp="keep",enabled="y",addr="0x00000000000038b0",at="<main>",thread-groups=["i1"],times="0",original-location="main"}
+^done
+(gdb) 
+&"r\n"
+~"Starting program: /usr/bin/ls \n"
+=thread-group-started,id="i1",pid="4633"
+=thread-created,id="1",group-id="i1"
+=library-loaded,id="/lib64/ld-linux-x86-64.so.2",target-name="/lib64/ld-linux-x86-64.so.2",host-name="/lib64/ld-linux-x86-64.so.2",symbols-loaded="0",thread-group="i1"
+^running
+*running,thread-id="all"
+(gdb) 
+=library-loaded,id="/lib64/libselinux.so.1",target-name="/lib64/libselinux.so.1",host-name="/lib64/libselinux.so.1",symbols-loaded="0",thread-group="i1"
+=library-loaded,id="/lib64/libcap.so.2",target-name="/lib64/libcap.so.2",host-name="/lib64/libcap.so.2",symbols-loaded="0",thread-group="i1"
+=library-loaded,id="/lib64/libc.so.6",target-name="/lib64/libc.so.6",host-name="/lib64/libc.so.6",symbols-loaded="0",thread-group="i1"
+=library-loaded,id="/lib64/libpcre.so.1",target-name="/lib64/libpcre.so.1",host-name="/lib64/libpcre.so.1",symbols-loaded="0",thread-group="i1"
+=library-loaded,id="/lib64/libdl.so.2",target-name="/lib64/libdl.so.2",host-name="/lib64/libdl.so.2",symbols-loaded="0",thread-group="i1"
+=library-loaded,id="/lib64/libpthread.so.0",target-name="/lib64/libpthread.so.0",host-name="/lib64/libpthread.so.0",symbols-loaded="0",thread-group="i1"
+~"[Thread debugging using libthread_db enabled]\n"
+~"Using host libthread_db library \"/lib64/libthread_db.so.1\".\n"
+=breakpoint-modified,bkpt={number="1",type="breakpoint",disp="keep",enabled="y",addr="0x00005555555578b0",at="<main>",thread-groups=["i1"],times="1",original-location="main"}
+~"\n"
+~"Breakpoint 1, 0x00005555555578b0 in main ()\n"
+*stopped,reason="breakpoint-hit",disp="keep",bkptno="1",frame={addr="0x00005555555578b0",func="main",args=[]},thread-id="1",stopped-threads="all",core="1"
+*stopped,reason="breakpoint-hit",disp="del",bkptno="1",frame={addr="0x00000000004009e0",func="main",args=[{name="argc",value="1"},{name="argv",value="0x7fffffffe9a8"}],file="main.c",fullname="/home/christian/Projects/rtfm/src/main.c",line="26"},thread-id="1",stopped-threads="all",core="2"
+(gdb) 
+&"c\n"
+~"Continuing.\n"
+^running
+*running,thread-id="all"
+(gdb) 
+~"[Inferior 1 (process 4633) exited normally]\n"
+=thread-exited,id="1",group-id="i1"
+=thread-group-exited,id="i1",exit-code="0"
+*stopped,reason="exited-normally"
+(gdb) 
+&"c\n"
+&"The program is not being run.\n"
+^error,msg="The program is not being run."
+(gdb) 
diff --git a/contrib/mi2/meson.build b/contrib/mi2/meson.build
new file mode 100644
index 0000000..b479533
--- /dev/null
+++ b/contrib/mi2/meson.build
@@ -0,0 +1,84 @@
+libmi2_header_dir = pkgincludedir + '/contrib/mi2'
+
+libmi2_enum_headers = [
+  'mi2-error.h',
+]
+
+libmi2_glib_enums = gnome.mkenums('mi2-enums',
+       h_template: 'mi2-enums.h.in',
+       c_template: 'mi2-enums.c.in',
+          sources: libmi2_enum_headers,
+   install_header: true,
+      install_dir: libmi2_header_dir,
+)
+
+libmi2_glib_sources = [
+  'mi2-breakpoint.c',
+  'mi2-breakpoint.h',
+  'mi2-client.c',
+  'mi2-client.h',
+  'mi2-command-message.c',
+  'mi2-command-message.h',
+  'mi2-console-message.c',
+  'mi2-console-message.h',
+  'mi2-error.c',
+  'mi2-error.h',
+  'mi2-event-message.c',
+  'mi2-event-message.h',
+  'mi2-glib.h',
+  'mi2-info-message.c',
+  'mi2-info-message.h',
+  'mi2-input-stream.c',
+  'mi2-input-stream.h',
+  'mi2-message.c',
+  'mi2-message.h',
+  'mi2-output-stream.c',
+  'mi2-output-stream.h',
+  'mi2-reply-message.c',
+  'mi2-reply-message.h',
+  'mi2-util.c',
+  'mi2-util.h',
+   libmi2_glib_enums[0],
+   libmi2_glib_enums[1],
+]
+
+libmi2_deps = [
+  libgiounix_dep,
+  libgtk_dep,
+]
+
+libmi2_glib = static_library('mi2-glib', libmi2_glib_sources,
+  dependencies: libmi2_deps,
+  pic: true,
+)
+
+libmi2_glib_dep = declare_dependency(
+  link_with: libmi2_glib,
+  include_directories: include_directories('.'),
+)
+
+#TODO: vapi, gir
+
+mi2_test_env = [
+  'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
+  'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()),
+  'G_DEBUG=gc-friendly',
+  'GSETTINGS_BACKEND=memory',
+  'MALLOC_CHECK_=2',
+  'MALLOC_PERTURB_=$((${RANDOM:-256} % 256))',
+]
+mi2_test_cflags = [
+  '-DTEST_DATA_DIR="@0@/data/"'.format(meson.current_source_dir()),
+]
+
+test_client = executable('test-client', 'test-client.c',
+        c_args: mi2_test_cflags,
+  dependencies: [libmi2_deps, libmi2_glib_dep],
+)
+test('test-client', test_client, env: mi2_test_env)
+
+test_stream = executable('test-stream', 'test-stream.c',
+        c_args: mi2_test_cflags,
+  dependencies: [libmi2_deps, libmi2_glib_dep],
+)
+test('test-stream', test_stream, env: mi2_test_env)
diff --git a/contrib/mi2/mi2-breakpoint.c b/contrib/mi2/mi2-breakpoint.c
new file mode 100644
index 0000000..8a5554e
--- /dev/null
+++ b/contrib/mi2/mi2-breakpoint.c
@@ -0,0 +1,329 @@
+/* mi2-breakpoint.c
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "mi2-breakpoint"
+
+#include "mi2-breakpoint.h"
+
+struct _Mi2Breakpoint
+{
+  GObject  parent_instance;
+  gchar   *address;
+  gchar   *linespec;
+  gchar   *filename;
+  gchar   *function;
+  gint     line_offset;
+  gint     id;
+};
+
+enum {
+  PROP_0,
+  PROP_ADDRESS,
+  PROP_FILENAME,
+  PROP_FUNCTION,
+  PROP_ID,
+  PROP_LINE_OFFSET,
+  PROP_LINESPEC,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (Mi2Breakpoint, mi2_breakpoint, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+mi2_breakpoint_finalize (GObject *object)
+{
+  Mi2Breakpoint *self = (Mi2Breakpoint *)object;
+
+  g_clear_pointer (&self->address, g_free);
+  g_clear_pointer (&self->filename, g_free);
+  g_clear_pointer (&self->function, g_free);
+  g_clear_pointer (&self->linespec, g_free);
+
+  G_OBJECT_CLASS (mi2_breakpoint_parent_class)->finalize (object);
+}
+
+static void
+mi2_breakpoint_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  Mi2Breakpoint *self = MI2_BREAKPOINT (object);
+
+  switch (prop_id)
+    {
+    case PROP_ADDRESS:
+      g_value_set_string (value, mi2_breakpoint_get_address (self));
+      break;
+
+    case PROP_ID:
+      g_value_set_int (value, mi2_breakpoint_get_id (self));
+      break;
+
+    case PROP_FILENAME:
+      g_value_set_string (value, mi2_breakpoint_get_filename (self));
+      break;
+
+    case PROP_FUNCTION:
+      g_value_set_string (value, mi2_breakpoint_get_function (self));
+      break;
+
+    case PROP_LINESPEC:
+      g_value_set_string (value, mi2_breakpoint_get_linespec (self));
+      break;
+
+    case PROP_LINE_OFFSET:
+      g_value_set_int (value, mi2_breakpoint_get_line_offset (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_breakpoint_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  Mi2Breakpoint *self = MI2_BREAKPOINT (object);
+
+  switch (prop_id)
+    {
+    case PROP_ADDRESS:
+      mi2_breakpoint_set_address (self, g_value_get_string (value));
+      break;
+
+    case PROP_ID:
+      mi2_breakpoint_set_id (self, g_value_get_int (value));
+      break;
+
+    case PROP_FILENAME:
+      mi2_breakpoint_set_filename (self, g_value_get_string (value));
+      break;
+
+    case PROP_FUNCTION:
+      mi2_breakpoint_set_function (self, g_value_get_string (value));
+      break;
+
+    case PROP_LINESPEC:
+      mi2_breakpoint_set_linespec (self, g_value_get_string (value));
+      break;
+
+    case PROP_LINE_OFFSET:
+      mi2_breakpoint_set_line_offset (self, g_value_get_int (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_breakpoint_class_init (Mi2BreakpointClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = mi2_breakpoint_finalize;
+  object_class->get_property = mi2_breakpoint_get_property;
+  object_class->set_property = mi2_breakpoint_set_property;
+
+  properties [PROP_ID] =
+    g_param_spec_int ("id",
+                      "Id",
+                      "Id",
+                      0, G_MAXINT, 0,
+                      (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_ADDRESS] =
+    g_param_spec_string ("address",
+                         "Address",
+                         "Address",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_FILENAME] =
+    g_param_spec_string ("filename",
+                         "Filename",
+                         "Filename",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_FUNCTION] =
+    g_param_spec_string ("function",
+                         "Function",
+                         "Function",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_LINESPEC] =
+    g_param_spec_string ("linespec",
+                         "Linespec",
+                         "Linespec",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_LINE_OFFSET] =
+    g_param_spec_int ("line-offset",
+                      "Line Offset",
+                      "The relative offset from the function or from the file",
+                      G_MININT, G_MAXINT, 0,
+                      (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+mi2_breakpoint_init (Mi2Breakpoint *self)
+{
+}
+
+gint
+mi2_breakpoint_get_id (Mi2Breakpoint *self)
+{
+  g_return_val_if_fail (MI2_IS_BREAKPOINT (self), 0);
+
+  return self->id;
+}
+
+void
+mi2_breakpoint_set_id (Mi2Breakpoint *self,
+                       gint           id)
+{
+  g_return_if_fail (MI2_IS_BREAKPOINT (self));
+  g_return_if_fail (id >= 0);
+
+  if (id != self->id)
+    {
+      self->id = id;
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ID]);
+    }
+}
+
+gint
+mi2_breakpoint_get_line_offset (Mi2Breakpoint *self)
+{
+  g_return_val_if_fail (MI2_IS_BREAKPOINT (self), 0);
+
+  return self->line_offset;
+}
+
+void
+mi2_breakpoint_set_line_offset (Mi2Breakpoint *self,
+                                gint           line_offset)
+{
+  g_return_if_fail (MI2_IS_BREAKPOINT (self));
+  g_return_if_fail (line_offset >= 0);
+
+  if (line_offset != self->line_offset)
+    {
+      self->line_offset = line_offset;
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LINE_OFFSET]);
+    }
+}
+
+const gchar *
+mi2_breakpoint_get_filename (Mi2Breakpoint *self)
+{
+  g_return_val_if_fail (MI2_IS_BREAKPOINT (self), NULL);
+
+  return self->filename;
+}
+
+void
+mi2_breakpoint_set_filename (Mi2Breakpoint *self,
+                             const gchar   *filename)
+{
+  g_return_if_fail (MI2_IS_BREAKPOINT (self));
+
+  if (g_strcmp0 (self->filename, filename) != 0)
+    {
+      g_free (self->filename);
+      self->filename = g_strdup (filename);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FILENAME]);
+    }
+}
+
+const gchar *
+mi2_breakpoint_get_function (Mi2Breakpoint *self)
+{
+  g_return_val_if_fail (MI2_IS_BREAKPOINT (self), NULL);
+
+  return self->function;
+}
+
+void
+mi2_breakpoint_set_function (Mi2Breakpoint *self,
+                             const gchar   *function)
+{
+  if (g_strcmp0 (self->function, function) != 0)
+    {
+      g_free (self->function);
+      self->function = g_strdup (function);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FUNCTION]);
+    }
+}
+
+const gchar *
+mi2_breakpoint_get_linespec (Mi2Breakpoint *self)
+{
+  g_return_val_if_fail (MI2_IS_BREAKPOINT (self), NULL);
+
+  return self->linespec;
+}
+
+void
+mi2_breakpoint_set_linespec (Mi2Breakpoint *self,
+                             const gchar   *linespec)
+{
+  if (g_strcmp0 (self->linespec, linespec) != 0)
+    {
+      g_free (self->linespec);
+      self->linespec = g_strdup (linespec);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LINESPEC]);
+    }
+}
+
+const gchar *
+mi2_breakpoint_get_address (Mi2Breakpoint *self)
+{
+  g_return_val_if_fail (MI2_IS_BREAKPOINT (self), NULL);
+
+  return self->address;
+}
+
+void
+mi2_breakpoint_set_address (Mi2Breakpoint *self,
+                            const gchar   *address)
+{
+  if (g_strcmp0 (self->address, address) != 0)
+    {
+      g_free (self->address);
+      self->address = g_strdup (address);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ADDRESS]);
+    }
+}
+
+Mi2Breakpoint *
+mi2_breakpoint_new (void)
+{
+  return g_object_new (MI2_TYPE_BREAKPOINT, NULL);
+}
diff --git a/contrib/mi2/mi2-breakpoint.h b/contrib/mi2/mi2-breakpoint.h
new file mode 100644
index 0000000..969b03f
--- /dev/null
+++ b/contrib/mi2/mi2-breakpoint.h
@@ -0,0 +1,52 @@
+/* mi2-breakpoint.h
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MI2_BREAKPOINT_H
+#define MI2_BREAKPOINT_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define MI2_TYPE_BREAKPOINT (mi2_breakpoint_get_type())
+
+G_DECLARE_FINAL_TYPE (Mi2Breakpoint, mi2_breakpoint, MI2, BREAKPOINT, GObject)
+
+Mi2Breakpoint *mi2_breakpoint_new             (void);
+gint           mi2_breakpoint_get_id          (Mi2Breakpoint *self);
+void           mi2_breakpoint_set_id          (Mi2Breakpoint *self,
+                                               gint           id);
+gint           mi2_breakpoint_get_line_offset (Mi2Breakpoint *self);
+void           mi2_breakpoint_set_line_offset (Mi2Breakpoint *self,
+                                               gint           line_offset);
+const gchar   *mi2_breakpoint_get_address     (Mi2Breakpoint *self);
+void           mi2_breakpoint_set_address     (Mi2Breakpoint *self,
+                                               const gchar   *address);
+const gchar   *mi2_breakpoint_get_linespec    (Mi2Breakpoint *self);
+void           mi2_breakpoint_set_linespec    (Mi2Breakpoint *self,
+                                               const gchar   *linespec);
+const gchar   *mi2_breakpoint_get_filename    (Mi2Breakpoint *self);
+void           mi2_breakpoint_set_filename    (Mi2Breakpoint *self,
+                                               const gchar   *filename);
+const gchar   *mi2_breakpoint_get_function    (Mi2Breakpoint *self);
+void           mi2_breakpoint_set_function    (Mi2Breakpoint *self,
+                                               const gchar   *function);
+
+G_END_DECLS
+
+#endif /* MI2_BREAKPOINT_H */
diff --git a/contrib/mi2/mi2-client.c b/contrib/mi2/mi2-client.c
new file mode 100644
index 0000000..9f676bc
--- /dev/null
+++ b/contrib/mi2/mi2-client.c
@@ -0,0 +1,1185 @@
+/* mi2-client.c
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "mi2-client"
+
+#include "mi2-client.h"
+#include "mi2-command-message.h"
+#include "mi2-console-message.h"
+#include "mi2-enums.h"
+#include "mi2-error.h"
+#include "mi2-event-message.h"
+#include "mi2-input-stream.h"
+#include "mi2-output-stream.h"
+#include "mi2-reply-message.h"
+
+typedef struct
+{
+  GIOStream       *io_stream;
+  Mi2InputStream  *input_stream;
+  Mi2OutputStream *output_stream;
+  GQueue           exec_tasks;
+  GQueue           exec_commands;
+} Mi2ClientPrivate;
+
+enum {
+  PROP_0,
+  PROP_IO_STREAM,
+  N_PROPS
+};
+
+enum {
+  BREAKPOINT_INSERTED,
+  BREAKPOINT_REMOVED,
+  EVENT,
+  LOG,
+  STOPPED,
+  N_SIGNALS
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (Mi2Client, mi2_client, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+static guint signals [N_SIGNALS];
+
+static void
+mi2_client_cancel_all_tasks (Mi2Client *self)
+{
+  Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+  GList *list;
+
+  g_assert (MI2_IS_CLIENT (self));
+
+  g_queue_foreach (&priv->exec_commands, (GFunc)g_object_unref, NULL);
+  g_queue_clear (&priv->exec_commands);
+
+  list = priv->exec_tasks.head;
+
+  priv->exec_tasks.head = NULL;
+  priv->exec_tasks.tail = NULL;
+  priv->exec_tasks.length = 0;
+
+  for (const GList *iter = list; iter != NULL; iter = iter->next)
+    {
+      g_autoptr(GTask) task = iter->data;
+
+      g_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_CANCELLED,
+                               "The operation was cancelled");
+    }
+
+  g_list_free (list);
+}
+
+static gboolean
+mi2_client_check_ready (Mi2Client  *self,
+                        GError    **error)
+{
+  Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+
+  g_return_val_if_fail (MI2_IS_CLIENT (self), FALSE);
+
+  if (priv->io_stream == NULL || priv->input_stream == NULL || priv->output_stream == NULL)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_NOT_CONNECTED,
+                   "Not connected to gdb");
+      return FALSE;
+    }
+
+  if (g_io_stream_is_closed (priv->io_stream))
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_CLOSED,
+                   "The client has already been shutdown");
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static void
+mi2_client_set_io_stream (Mi2Client *self,
+                          GIOStream *io_stream)
+{
+  Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+
+  g_assert (MI2_IS_CLIENT (self));
+  g_assert (!io_stream || G_IS_IO_STREAM (io_stream));
+
+  if (g_set_object (&priv->io_stream, io_stream))
+    {
+      priv->input_stream = mi2_input_stream_new (g_io_stream_get_input_stream (io_stream));
+      priv->output_stream = mi2_output_stream_new (g_io_stream_get_output_stream (io_stream));
+    }
+}
+
+static void
+mi2_client_real_event (Mi2Client       *self,
+                       Mi2EventMessage *message)
+{
+  const gchar *name;
+
+  g_assert (MI2_IS_CLIENT (self));
+  g_assert (MI2_IS_EVENT_MESSAGE (message));
+
+  name = mi2_event_message_get_name (message);
+
+  if (g_strcmp0 (name, "stopped") == 0)
+    {
+      const gchar *reasonstr;
+      Mi2StopReason reason;
+
+      reasonstr = mi2_message_get_param_string (MI2_MESSAGE (message), "reason");
+      reason = mi2_stop_reason_parse (reasonstr);
+
+      g_signal_emit (self, signals [STOPPED], 0, reason, message);
+    }
+}
+
+static void
+mi2_client_dispose (GObject *object)
+{
+  Mi2Client *self = (Mi2Client *)object;
+  Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+
+  g_clear_object (&priv->io_stream);
+  g_clear_object (&priv->input_stream);
+  g_clear_object (&priv->output_stream);
+
+  G_OBJECT_CLASS (mi2_client_parent_class)->dispose (object);
+}
+
+static void
+mi2_client_finalize (GObject *object)
+{
+  Mi2Client *self = (Mi2Client *)object;
+  Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+
+  g_assert (priv->exec_commands.length == 0);
+  g_assert (priv->exec_commands.head == NULL);
+  g_assert (priv->exec_commands.tail == NULL);
+
+  g_assert (priv->exec_tasks.length == 0);
+  g_assert (priv->exec_tasks.head == NULL);
+  g_assert (priv->exec_tasks.tail == NULL);
+
+  G_OBJECT_CLASS (mi2_client_parent_class)->finalize (object);
+}
+
+static void
+mi2_client_get_property (GObject    *object,
+                         guint       prop_id,
+                         GValue     *value,
+                         GParamSpec *pspec)
+{
+  Mi2Client *self = MI2_CLIENT (object);
+  Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_IO_STREAM:
+      g_value_set_object (value, priv->io_stream);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_client_set_property (GObject      *object,
+                         guint         prop_id,
+                         const GValue *value,
+                         GParamSpec   *pspec)
+{
+  Mi2Client *self = MI2_CLIENT (object);
+
+  switch (prop_id)
+    {
+    case PROP_IO_STREAM:
+      mi2_client_set_io_stream (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_client_class_init (Mi2ClientClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = mi2_client_dispose;
+  object_class->finalize = mi2_client_finalize;
+  object_class->get_property = mi2_client_get_property;
+  object_class->set_property = mi2_client_set_property;
+
+  klass->event = mi2_client_real_event;
+
+  properties [PROP_IO_STREAM] =
+    g_param_spec_object ("io-stream",
+                         "IO Stream",
+                         "The undelrying stream to communicate with",
+                         G_TYPE_IO_STREAM,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  signals [BREAKPOINT_INSERTED] =
+    g_signal_new ("breakpoint-inserted",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (Mi2ClientClass, breakpoint_inserted),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, MI2_TYPE_BREAKPOINT);
+
+  signals [BREAKPOINT_REMOVED] =
+    g_signal_new ("breakpoint-removed",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (Mi2ClientClass, breakpoint_removed),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, G_TYPE_INT);
+
+  signals [EVENT] =
+    g_signal_new ("event",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+                  G_STRUCT_OFFSET (Mi2ClientClass, event),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, MI2_TYPE_EVENT_MESSAGE);
+
+  signals [LOG] =
+    g_signal_new ("log",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (Mi2ClientClass, log),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+  signals [STOPPED] =
+    g_signal_new ("stopped",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (Mi2ClientClass, stopped),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 2, MI2_TYPE_STOP_REASON, MI2_TYPE_MESSAGE);
+}
+
+static void
+mi2_client_init (Mi2Client *self)
+{
+  Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+
+  g_queue_init (&priv->exec_commands);
+  g_queue_init (&priv->exec_tasks);
+}
+
+Mi2Client *
+mi2_client_new (GIOStream *io_stream)
+{
+  return g_object_new (MI2_TYPE_CLIENT,
+                       "io-stream", io_stream,
+                       NULL);
+}
+
+static void
+mi2_client_exec_write_message_cb (GObject      *object,
+                                  GAsyncResult *result,
+                                  gpointer      user_data)
+{
+  Mi2OutputStream *stream = (Mi2OutputStream *)object;
+  g_autoptr(Mi2Message) message = NULL;
+  g_autoptr(Mi2Client) self = user_data;
+  g_autoptr(GError) error = NULL;
+  Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+
+  g_assert (MI2_IS_OUTPUT_STREAM (stream));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (MI2_IS_CLIENT (self));
+
+  if (!mi2_output_stream_write_message_finish (stream, result, &error))
+    {
+      g_autoptr(GTask) task = NULL;
+
+      task = g_queue_pop_head (&priv->exec_tasks);
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  /*
+   * Do not successfully complete request here.
+   *
+   * Successful completion of the task must come from a reply
+   * sent to us by the peer looking something like ^running.
+   */
+}
+
+/**
+ * mi2_client_exec_async:
+ * @self: a #Mi2Client
+ * @command: the command to execute
+ * @cancellable: (nullable): optional #GCancellable object or %NULL.
+ * @callback: (scope async) (closure user_data): a callback to execute
+ * @user_data: user data for @callback
+ *
+ * Executes @command asynchronously.
+ *
+ * If another command is in-flight, the command will be queued until the
+ * reply has been received from the in-flight command.
+ *
+ * Call mi2_client_exec_finish() to get the response to the command.
+ */
+void
+mi2_client_exec_async (Mi2Client           *self,
+                       const gchar         *command,
+                       GCancellable        *cancellable,
+                       GAsyncReadyCallback  callback,
+                       gpointer             user_data)
+{
+  Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+  g_autoptr(Mi2Message) message = NULL;
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_return_if_fail (MI2_IS_CLIENT (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, mi2_client_exec_async);
+
+  if (!mi2_client_check_ready (self, &error))
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  message = g_object_new (MI2_TYPE_COMMAND_MESSAGE,
+                          "command", command,
+                          NULL);
+
+  g_queue_push_tail (&priv->exec_tasks, g_object_ref (task));
+
+  if (priv->exec_tasks.length > 1)
+    g_queue_push_tail (&priv->exec_commands, g_steal_pointer (&message));
+  else
+    mi2_output_stream_write_message_async (priv->output_stream,
+                                           message,
+                                           cancellable,
+                                           mi2_client_exec_write_message_cb,
+                                           g_object_ref (self));
+}
+
+/**
+ * mi2_client_exec_finish:
+ * @self: An #Mi2Client
+ * @result: A #GAsyncResult
+ * @reply: (optional) (out) (transfer full): A location for a reply.
+ * @error: a location for a #GError or %NULL
+ *
+ * Completes a request to mi2_client_exec_async(). The reply from the
+ * gdb instance will be provided to @message.
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
+ */
+gboolean
+mi2_client_exec_finish (Mi2Client        *self,
+                        GAsyncResult     *result,
+                        Mi2ReplyMessage **reply,
+                        GError          **error)
+{
+  g_autoptr(Mi2ReplyMessage) local_message = NULL;
+  gboolean ret;
+
+  g_return_val_if_fail (MI2_IS_CLIENT (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  local_message = g_task_propagate_pointer (G_TASK (result), error);
+  ret = !!local_message;
+
+  if (reply)
+    *reply = g_steal_pointer (&local_message);
+
+  return ret;
+}
+
+static void
+mi2_client_dispatch (Mi2Client  *self,
+                     Mi2Message *message)
+{
+  Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+
+  g_return_if_fail (MI2_IS_CLIENT (self));
+  g_return_if_fail (MI2_IS_MESSAGE (message));
+
+  if (MI2_IS_CONSOLE_MESSAGE (message))
+    {
+      const gchar *str;
+
+      str = mi2_console_message_get_message (MI2_CONSOLE_MESSAGE (message));
+      g_signal_emit (self, signals [LOG], 0, str);
+    }
+  else if (MI2_IS_EVENT_MESSAGE (message))
+    {
+      const gchar *name = mi2_event_message_get_name (MI2_EVENT_MESSAGE (message));
+      GQuark detail = g_quark_try_string (name);
+
+      g_signal_emit (self, signals [EVENT], detail, message);
+    }
+  else if (MI2_IS_REPLY_MESSAGE (message))
+    {
+      g_autoptr(GTask) task = g_queue_pop_head (&priv->exec_tasks);
+      g_autoptr(Mi2Message) next_message = NULL;
+
+      if (task != NULL)
+        {
+          g_autoptr(GError) error = NULL;
+
+          if (mi2_reply_message_check_error (MI2_REPLY_MESSAGE (message), &error))
+            g_task_return_error (task, g_steal_pointer (&error));
+          else
+            g_task_return_pointer (task, g_object_ref (message), g_object_unref);
+        }
+
+      /*
+       * Move forward to the next asynchronous command to execute.
+       * We do this here so that we don't have multiple requests on
+       * the wire at one time, as gdb cannot handle that.
+       */
+      if (NULL != (next_message = g_queue_pop_head (&priv->exec_commands)))
+        {
+          GCancellable *cancellable;
+
+          cancellable = g_task_get_cancellable (priv->exec_tasks.head->data);
+          mi2_output_stream_write_message_async (priv->output_stream,
+                                                 next_message,
+                                                 cancellable,
+                                                 mi2_client_exec_write_message_cb,
+                                                 g_steal_pointer (&self));
+        }
+    }
+  else
+    {
+      g_warning ("Got message of type %s", G_OBJECT_TYPE_NAME (message));
+    }
+}
+
+static void
+mi2_client_read_loop_cb (GObject      *object,
+                         GAsyncResult *result,
+                         gpointer      user_data)
+{
+  Mi2InputStream *stream = (Mi2InputStream *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(Mi2Message) message = NULL;
+  g_autoptr(GError) error = NULL;
+  GCancellable *cancellable;
+  Mi2Client *self;
+
+  g_assert (MI2_IS_INPUT_STREAM (stream));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
+
+  message = mi2_input_stream_read_message_finish (stream, result, &error);
+  g_assert (!message || MI2_IS_MESSAGE (message));
+
+  if (message == NULL)
+    {
+      if (error != NULL)
+        g_task_return_error (task, g_steal_pointer (&error));
+      else
+        g_task_return_boolean (task, TRUE);
+      return;
+    }
+
+  self = g_task_get_source_object (task);
+  g_assert (MI2_IS_CLIENT (self));
+
+  mi2_client_dispatch (self, message);
+  g_clear_object (&message);
+
+  if (g_task_return_error_if_cancelled (task))
+    return;
+
+  cancellable = g_task_get_cancellable (task);
+  mi2_input_stream_read_message_async (stream,
+                                       cancellable,
+                                       mi2_client_read_loop_cb,
+                                       g_steal_pointer (&task));
+}
+
+/**
+ * mi2_client_listen_async:
+ * @self: a #Mi2Client
+ * @cancellable: (nullable): an optional #GCancellable, or %NULL
+ * @callback: (scope async) (closure user_data): a callback to execute upon completion
+ * @user_data: the data to pass to @callback function
+ *
+ * Starts listening to the gdb process using the configured streams.
+ *
+ * This will hold a reference to the client (preventing finalization) until
+ * mi2_client_shutdown_async() has been called.
+ *
+ * Call mi2_client_listen_finish() from @callback to complete the operation.
+ */
+void
+mi2_client_listen_async (Mi2Client           *self,
+                         GCancellable        *cancellable,
+                         GAsyncReadyCallback  callback,
+                         gpointer             user_data)
+{
+  Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (MI2_IS_CLIENT (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, mi2_client_listen_async);
+  g_task_set_return_on_cancel (task, TRUE);
+
+  mi2_input_stream_read_message_async (priv->input_stream,
+                                       NULL,
+                                       mi2_client_read_loop_cb,
+                                       g_steal_pointer (&task));
+}
+
+/**
+ * mi2_client_listen_finish:
+ * @self: a #Mi2Client
+ * @result: the #GAsyncResult provided to the callback function
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to mi2_client_listen_async().
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
+ */
+gboolean
+mi2_client_listen_finish (Mi2Client     *self,
+                          GAsyncResult  *result,
+                          GError       **error)
+{
+  g_autoptr(GError) local_error = NULL;
+
+  g_return_val_if_fail (MI2_IS_CLIENT (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  if (!g_task_propagate_boolean (G_TASK (result), &local_error))
+    {
+      if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CLOSED))
+        return TRUE;
+      g_propagate_error (error, g_steal_pointer (&local_error));
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static void
+mi2_client_shutdown_cb (GObject      *object,
+                        GAsyncResult *result,
+                        gpointer      user_data)
+{
+  GIOStream *io_stream = (GIOStream *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_return_if_fail (G_IS_IO_STREAM (io_stream));
+  g_return_if_fail (G_IS_ASYNC_RESULT (result));
+  g_return_if_fail (G_IS_TASK (task));
+
+  if (!g_io_stream_close_finish (io_stream, result, &error))
+    g_task_return_error (task, g_steal_pointer (&error));
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+/**
+ * mi2_client_shutdown_async:
+ * @self: a #Mi2Client
+ * @cancellable: (nullable): an optional #GCancellable, or %NULL
+ * @callback: (scope async) (closure user_data): a callback to execute upon completion
+ * @user_data: the data to pass to @callback function
+ *
+ * Asynchronously requests the shutdown of the client, cancelling any in-flight
+ * requests and breaking the incoming event processing loop.
+ *
+ * Call mi2_client_shutdown_finish() to complete the operation.
+ */
+void
+mi2_client_shutdown_async (Mi2Client           *self,
+                           GCancellable        *cancellable,
+                           GAsyncReadyCallback  callback,
+                           gpointer             user_data)
+{
+  Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (MI2_IS_CLIENT (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_return_if_fail (G_IS_IO_STREAM (priv->io_stream));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, mi2_client_shutdown_async);
+
+  mi2_client_cancel_all_tasks (self);
+
+  g_io_stream_close_async (priv->io_stream,
+                           G_PRIORITY_LOW,
+                           NULL,
+                           mi2_client_shutdown_cb,
+                           g_steal_pointer (&task));
+}
+
+/**
+ * mi2_client_shutdown_finish:
+ * @self: a #Mi2Client
+ * @result: the #GAsyncResult provided to the callback function
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to mi2_client_shutdown_async().
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
+ */
+gboolean
+mi2_client_shutdown_finish (Mi2Client     *self,
+                            GAsyncResult  *result,
+                            GError       **error)
+{
+  g_return_val_if_fail (MI2_IS_CLIENT (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+mi2_client_insert_breakpoint_cb (GObject      *object,
+                                 GAsyncResult *result,
+                                 gpointer      user_data)
+{
+  Mi2Client *self = (Mi2Client *)object;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(Mi2ReplyMessage) message = NULL;
+  Mi2Breakpoint *breakpoint;
+  GVariant *bkpt;
+
+  g_assert (MI2_IS_CLIENT (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (!mi2_client_exec_finish (self, result, &message, &error))
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  breakpoint = g_task_get_task_data (task);
+
+  bkpt = mi2_message_get_param (MI2_MESSAGE (message), "bkpt");
+
+  if (bkpt != NULL)
+    {
+      GVariantDict dict;
+      const gchar *number = NULL;
+
+      g_variant_dict_init (&dict, bkpt);
+      g_variant_dict_lookup (&dict, "number", "&s", &number);
+      g_variant_dict_clear (&dict);
+
+      mi2_breakpoint_set_id (breakpoint, g_ascii_strtoll (number, NULL, 10));
+    }
+
+  g_signal_emit (self, signals [BREAKPOINT_INSERTED], 0, breakpoint);
+
+  g_task_return_boolean (task, TRUE);
+}
+
+/**
+ * mi2_client_insert_breakpoint_async:
+ * @self: A #Mi2Client
+ * @breakpoint: An #Mi2Breakpoint
+ * @cancellable: (nullable): A #GCancellable or %NULL
+ * @callback: (scope async) (closure user_data): A callback to execute
+ * @user_data: user data for @callback
+ *
+ * Adds a breakpoint at @function. If @filename is specified, the function
+ * will be resolved within that file.
+ *
+ * Call mi2_client_insert_breakpoint_async() to complete the operation.
+ */
+void
+mi2_client_insert_breakpoint_async (Mi2Client           *self,
+                                    Mi2Breakpoint       *breakpoint,
+                                    GCancellable        *cancellable,
+                                    GAsyncReadyCallback  callback,
+                                    gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GString) str = NULL;
+  const gchar *address;
+  const gchar *filename;
+  const gchar *function;
+  const gchar *linespec;
+  gint line_offset;
+
+  g_return_if_fail (MI2_IS_CLIENT (self));
+  g_return_if_fail (MI2_IS_BREAKPOINT (breakpoint));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, mi2_client_insert_breakpoint_async);
+  g_task_set_task_data (task, g_object_ref (breakpoint), g_object_unref);
+
+  str = g_string_new ("-break-insert");
+
+  line_offset = mi2_breakpoint_get_line_offset (breakpoint);
+  linespec = mi2_breakpoint_get_linespec (breakpoint);
+  function = mi2_breakpoint_get_function (breakpoint);
+  filename = mi2_breakpoint_get_filename (breakpoint);
+  address = mi2_breakpoint_get_address (breakpoint);
+
+  if (linespec)
+    g_string_append_printf (str, " %s", linespec);
+
+  if (filename)
+    g_string_append_printf (str, " --source %s", filename);
+
+  if (function)
+    g_string_append_printf (str, " --function %s", function);
+
+  if (line_offset)
+    g_string_append_printf (str, " --line %d", line_offset);
+
+  if (address)
+    g_string_append_printf (str, " %s", address);
+
+  mi2_client_exec_async (self,
+                         str->str,
+                         cancellable,
+                         mi2_client_insert_breakpoint_cb,
+                         g_steal_pointer (&task));
+}
+
+/**
+ * mi2_client_insert_breakpoint_finish:
+ * @self: a #Mi2Client
+ * @result: the #GAsyncResult provided to the callback function
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to mi2_client_insert_breakpoint_async().
+ *
+ * Returns: a positive integer if successful, otherwise zero and @error is set.
+ */
+gint
+mi2_client_insert_breakpoint_finish (Mi2Client     *self,
+                                     GAsyncResult  *result,
+                                     GError       **error)
+{
+  g_return_val_if_fail (MI2_IS_CLIENT (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_int (G_TASK (result), error);
+}
+
+static void
+mi2_client_remove_breakpoint_cb (GObject      *object,
+                                 GAsyncResult *result,
+                                 gpointer      user_data)
+{
+  Mi2Client *self = (Mi2Client *)object;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GTask) task = user_data;
+  gint id;
+
+  g_assert (MI2_IS_CLIENT (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (!mi2_client_exec_finish (self, result, NULL, &error))
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  id = GPOINTER_TO_INT (g_task_get_task_data (task));
+  if (id != 0)
+    g_signal_emit (self, signals [BREAKPOINT_REMOVED], 0, id);
+
+  g_task_return_boolean (task, TRUE);
+}
+
+/**
+ * mi2_client_remove_breakpoint_async:
+ * @self: A #Mi2Client
+ * @breakpoint_id: The id of the breakpoint
+ * @cancellable: (nullable): A #GCancellable or %NULL
+ * @callback: (scope async) (closure user_data): A callback to execute
+ * @user_data: user data for @callback
+ *
+ * Removes a breakpoint that was previously added.
+ *
+ * Call mi2_client_remove_breakpoint_finish() to complete the operation.
+ */
+void
+mi2_client_remove_breakpoint_async (Mi2Client           *self,
+                                    gint                 breakpoint_id,
+                                    GCancellable        *cancellable,
+                                    GAsyncReadyCallback  callback,
+                                    gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  g_autofree gchar *str = NULL;
+
+  g_return_if_fail (MI2_IS_CLIENT (self));
+  g_return_if_fail (breakpoint_id > 0);
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, mi2_client_remove_breakpoint_async);
+  g_task_set_task_data (task, GINT_TO_POINTER (breakpoint_id), NULL);
+
+  str = g_strdup_printf ("-break-delete %d", breakpoint_id);
+
+  mi2_client_exec_async (self,
+                         str,
+                         cancellable,
+                         mi2_client_remove_breakpoint_cb,
+                         g_steal_pointer (&task));
+}
+
+/**
+ * mi2_client_remote_breakpoint_finish:
+ * @self: a #Mi2Client
+ * @result: the #GAsyncResult provided to the callback function
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to mi2_client_remote_breakpoint_async().
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
+ */
+gboolean
+mi2_client_remove_breakpoint_finish (Mi2Client     *self,
+                                     GAsyncResult  *result,
+                                     GError       **error)
+{
+  g_return_val_if_fail (MI2_IS_CLIENT (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+mi2_client_exec_cb (GObject      *object,
+                    GAsyncResult *result,
+                    gpointer      user_data)
+{
+  Mi2Client *self = (Mi2Client *)object;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GTask) task = user_data;
+
+  g_assert (MI2_IS_CLIENT (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (!mi2_client_exec_finish (self, result, NULL, &error))
+    g_task_return_error (task, g_steal_pointer (&error));
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+/**
+ * mi2_client_run_async:
+ * @self: a #Mi2Client
+ * @cancellable: (nullable): an optional #GCancellable, or %NULL
+ * @callback: (scope async) (closure user_data): a callback to execute
+ * @user_data: user data for @callback
+ *
+ * Asynchronously requests that the inferior program be run.
+ *
+ * Call mi2_client_run_finish() from @callback to get the result.
+ */
+void
+mi2_client_run_async (Mi2Client           *self,
+                      GCancellable        *cancellable,
+                      GAsyncReadyCallback  callback,
+                      gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (MI2_IS_CLIENT (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, mi2_client_run_async);
+
+  mi2_client_exec_async (self,
+                         "-exec-run --start",
+                         cancellable,
+                         mi2_client_exec_cb,
+                         g_steal_pointer (&task));
+}
+
+/**
+ * mi2_client_func_name_finish:
+ * @self: a #Mi2Client
+ * @result: the #GAsyncResult provided to the callback function
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to mi2_client_func_name_async().
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
+ */
+gboolean
+mi2_client_run_finish (Mi2Client     *self,
+                       GAsyncResult  *result,
+                       GError       **error)
+{
+  g_return_val_if_fail (MI2_IS_CLIENT (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * mi2_client_step_async:
+ * @self: a #Mi2Client
+ * @cancellable: (nullable): an optional #GCancellable, or %NULL
+ * @callback: (scope async) (closure user_data): a callback to execute
+ * @user_data: user data for @callback
+ *
+ * Asynchronously executes the continue command.
+ *
+ * Call mi2_client_step_finish() from @callback to get the result.
+ */
+void
+mi2_client_step_async (Mi2Client           *self,
+                       gboolean             reverse,
+                       GCancellable        *cancellable,
+                       GAsyncReadyCallback  callback,
+                       gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (MI2_IS_CLIENT (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, mi2_client_step_async);
+
+  mi2_client_exec_async (self,
+                         reverse ? "-exec-step --reverse" : "-exec-step",
+                         cancellable,
+                         mi2_client_exec_cb,
+                         g_steal_pointer (&task));
+}
+
+/**
+ * mi2_client_step_finish:
+ * @self: a #Mi2Client
+ * @result: the #GAsyncResult provided to the callback function
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to mi2_client_step_async().
+ *
+ * Returns: %TRUE if successful, otherwise %FALSE and @error is est.
+ */
+gboolean
+mi2_client_step_finish (Mi2Client     *self,
+                        GAsyncResult  *result,
+                        GError       **error)
+{
+  g_return_val_if_fail (MI2_IS_CLIENT (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * mi2_client_next_async:
+ * @self: a #Mi2Client
+ * @cancellable: (nullable): an optional #GCancellable, or %NULL
+ * @callback: (scope async) (closure user_data): a callback to execute
+ * @user_data: user data for @callback
+ *
+ * Asynchronously executes the continue command.
+ *
+ * Call mi2_client_next_finish() from @callback to get the result.
+ */
+void
+mi2_client_next_async (Mi2Client           *self,
+                       gboolean             reverse,
+                       GCancellable        *cancellable,
+                       GAsyncReadyCallback  callback,
+                       gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (MI2_IS_CLIENT (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, mi2_client_next_async);
+
+  mi2_client_exec_async (self,
+                         reverse ? "-exec-next --reverse" : "-exec-next",
+                         cancellable,
+                         mi2_client_exec_cb,
+                         g_steal_pointer (&task));
+}
+
+/**
+ * mi2_client_next_finish:
+ * @self: a #Mi2Client
+ * @result: the #GAsyncResult provided to the callback function
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to mi2_client_next_async().
+ *
+ * Returns: %TRUE if successful, otherwise %FALSE and @error is est.
+ */
+gboolean
+mi2_client_next_finish (Mi2Client     *self,
+                        GAsyncResult  *result,
+                        GError       **error)
+{
+  g_return_val_if_fail (MI2_IS_CLIENT (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * mi2_client_continue_async:
+ * @self: a #Mi2Client
+ * @cancellable: (nullable): an optional #GCancellable, or %NULL
+ * @callback: (scope async) (closure user_data): a callback to execute
+ * @user_data: user data for @callback
+ *
+ * Asynchronously executes the continue command.
+ *
+ * Call mi2_client_continue_finish() from @callback to get the result.
+ */
+void
+mi2_client_continue_async (Mi2Client           *self,
+                           gboolean             reverse,
+                           GCancellable        *cancellable,
+                           GAsyncReadyCallback  callback,
+                           gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (MI2_IS_CLIENT (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, mi2_client_continue_async);
+
+  mi2_client_exec_async (self,
+                         reverse ? "-exec-continue --reverse" : "-exec-continue",
+                         cancellable,
+                         mi2_client_exec_cb,
+                         g_steal_pointer (&task));
+}
+
+/**
+ * mi2_client_continue_finish:
+ * @self: a #Mi2Client
+ * @result: the #GAsyncResult provided to the callback function
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to mi2_client_continue_async().
+ *
+ * Returns: %TRUE if successful, otherwise %FALSE and @error is est.
+ */
+gboolean
+mi2_client_continue_finish (Mi2Client     *self,
+                            GAsyncResult  *result,
+                            GError       **error)
+{
+  g_return_val_if_fail (MI2_IS_CLIENT (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+#if 0
+void
+mi2_client_async (Mi2Client           *self,
+                  GCancellable        *cancellable,
+                  GAsyncReadyCallback  callback,
+                  gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (MI2_IS_CLIENT (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+}
+
+gboolean
+mi2_client_finish (Mi2Client     *self,
+                   GAsyncResult  *result,
+                   GError       **error)
+{
+  g_return_val_if_fail (MI2_IS_CLIENT (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+#endif
+
+GType
+mi2_stop_reason_get_type (void)
+{
+  static GType type_id;
+
+  if (g_once_init_enter (&type_id))
+    {
+      GType _type_id;
+      static const GEnumValue values[] = {
+        { MI2_STOP_UNKNOWN, "MI2_STOP_UNKNOWN", "unknown" },
+        { MI2_STOP_END_STEPPING_RANGE, "MI2_STOP_END_STEPPING_RANGE", "end-stepping-range" },
+        { MI2_STOP_EXITED_NORMALLY, "MI2_STOP_EXITED_NORMALLY", "exited-normally" },
+        { MI2_STOP_BREAKPOINT_HIT, "MI2_STOP_BREAKPOINT_HIT", "breakpoint-hit" },
+        { 0 }
+      };
+
+      _type_id = g_enum_register_static ("Mi2StopReason", values);
+      g_once_init_leave (&type_id, _type_id);
+    }
+
+  return type_id;
+}
+
+Mi2StopReason
+mi2_stop_reason_parse (const gchar *reason)
+{
+  GEnumClass *klass;
+  GEnumValue *value = NULL;
+
+  if (reason)
+    {
+      klass = g_type_class_ref (MI2_TYPE_STOP_REASON);
+      value = g_enum_get_value_by_nick (klass, reason);
+      g_type_class_unref (klass);
+    }
+
+  return value ? value->value : 0;
+}
diff --git a/contrib/mi2/mi2-client.h b/contrib/mi2/mi2-client.h
new file mode 100644
index 0000000..0654fa8
--- /dev/null
+++ b/contrib/mi2/mi2-client.h
@@ -0,0 +1,155 @@
+/* mi2-client.h
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MI2_CLIENT_H
+#define MI2_CLIENT_H
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#include "mi2-breakpoint.h"
+#include "mi2-message.h"
+#include "mi2-event-message.h"
+#include "mi2-reply-message.h"
+
+#define MI2_TYPE_CLIENT      (mi2_client_get_type())
+#define MI2_TYPE_STOP_REASON (mi2_stop_reason_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (Mi2Client, mi2_client, MI2, CLIENT, GObject)
+
+typedef enum
+{
+  MI2_STOP_UNKNOWN,
+  MI2_STOP_END_STEPPING_RANGE,
+  MI2_STOP_EXITED_NORMALLY,
+  MI2_STOP_BREAKPOINT_HIT,
+} Mi2StopReason;
+
+struct _Mi2ClientClass
+{
+  GObjectClass parent_instance;
+
+  void (*log)                 (Mi2Client       *self,
+                               const gchar     *log);
+  void (*event)               (Mi2Client       *self,
+                               Mi2EventMessage *message);
+  void (*breakpoint_inserted) (Mi2Client       *client,
+                               Mi2Breakpoint   *breakpoint);
+  void (*breakpoint_removed)  (Mi2Client       *client,
+                               gint             breakpoint_id);
+  void (*stopped)             (Mi2Client       *self,
+                               Mi2StopReason    reason,
+                               Mi2Message      *message);
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+  gpointer _reserved9;
+  gpointer _reserved10;
+  gpointer _reserved11;
+  gpointer _reserved12;
+  gpointer _reserved13;
+  gpointer _reserved14;
+  gpointer _reserved15;
+  gpointer _reserved16;
+};
+
+GType          mi2_stop_reason_get_type                   (void);
+Mi2StopReason  mi2_stop_reason_parse                      (const gchar          *reason);
+Mi2Client     *mi2_client_new                             (GIOStream            *stream);
+void           mi2_client_exec_async                      (Mi2Client            *self,
+                                                           const gchar          *command,
+                                                           GCancellable         *cancellable,
+                                                           GAsyncReadyCallback   callback,
+                                                           gpointer              user_data);
+gboolean       mi2_client_exec_finish                     (Mi2Client            *self,
+                                                           GAsyncResult         *result,
+                                                           Mi2ReplyMessage     **reply,
+                                                           GError              **error);
+void           mi2_client_listen_async                    (Mi2Client            *self,
+                                                           GCancellable         *cancellable,
+                                                           GAsyncReadyCallback   callback,
+                                                           gpointer              user_data);
+gboolean       mi2_client_listen_finish                   (Mi2Client            *self,
+                                                           GAsyncResult         *result,
+                                                           GError              **error);
+void           mi2_client_shutdown_async                  (Mi2Client            *self,
+                                                           GCancellable         *cancellable,
+                                                           GAsyncReadyCallback   callback,
+                                                           gpointer              user_data);
+gboolean       mi2_client_shutdown_finish                 (Mi2Client            *self,
+                                                           GAsyncResult         *result,
+                                                           GError              **error);
+void           mi2_client_step_async                      (Mi2Client            *self,
+                                                           gboolean              reverse,
+                                                           GCancellable         *cancellable,
+                                                           GAsyncReadyCallback   callback,
+                                                           gpointer              user_data);
+gboolean       mi2_client_step_finish                     (Mi2Client            *self,
+                                                           GAsyncResult         *result,
+                                                           GError              **error);
+void           mi2_client_next_async                      (Mi2Client            *self,
+                                                           gboolean              reverse,
+                                                           GCancellable         *cancellable,
+                                                           GAsyncReadyCallback   callback,
+                                                           gpointer              user_data);
+gboolean       mi2_client_next_finish                     (Mi2Client            *self,
+                                                           GAsyncResult         *result,
+                                                           GError              **error);
+void           mi2_client_continue_async                  (Mi2Client            *self,
+                                                           gboolean              reverse,
+                                                           GCancellable         *cancellable,
+                                                           GAsyncReadyCallback   callback,
+                                                           gpointer              user_data);
+gboolean       mi2_client_continue_finish                 (Mi2Client            *self,
+                                                           GAsyncResult         *result,
+                                                           GError              **error);
+void           mi2_client_run_async                       (Mi2Client            *self,
+                                                           GCancellable         *cancellable,
+                                                           GAsyncReadyCallback   callback,
+                                                           gpointer              user_data);
+gboolean       mi2_client_run_finish                      (Mi2Client            *self,
+                                                           GAsyncResult         *result,
+                                                           GError              **error);
+void           mi2_client_insert_breakpoint_async         (Mi2Client            *self,
+                                                           Mi2Breakpoint        *breakpoint,
+                                                           GCancellable         *cancellable,
+                                                           GAsyncReadyCallback   callback,
+                                                           gpointer              user_data);
+gint           mi2_client_insert_breakpoint_finish        (Mi2Client            *self,
+                                                           GAsyncResult         *result,
+                                                           GError              **error);
+void           mi2_client_remove_breakpoint_async         (Mi2Client            *self,
+                                                           gint                  breakpoint_id,
+                                                           GCancellable         *cancellable,
+                                                           GAsyncReadyCallback   callback,
+                                                           gpointer              user_data);
+gboolean       mi2_client_remove_breakpoint_finish        (Mi2Client            *self,
+                                                           GAsyncResult         *result,
+                                                           GError              **error);
+
+
+G_END_DECLS
+
+#endif /* MI2_CLIENT_H */
diff --git a/contrib/mi2/mi2-command-message.c b/contrib/mi2/mi2-command-message.c
new file mode 100644
index 0000000..25b73cd
--- /dev/null
+++ b/contrib/mi2/mi2-command-message.c
@@ -0,0 +1,168 @@
+/* mi2-command-message.c
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "mi2-command-message"
+
+#include <string.h>
+
+#include "mi2-command-message.h"
+#include "mi2-util.h"
+
+struct _Mi2CommandMessage
+{
+  Mi2Message  parent;
+  gchar      *command;
+};
+
+enum {
+  PROP_0,
+  PROP_COMMAND,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (Mi2CommandMessage, mi2_command_message, MI2_TYPE_MESSAGE)
+
+static GParamSpec *properties [N_PROPS];
+
+static GBytes *
+mi2_command_message_serialize (Mi2Message *message)
+{
+  Mi2CommandMessage *self = (Mi2CommandMessage *)message;
+  GString *str;
+
+  g_assert (MI2_IS_COMMAND_MESSAGE (self));
+
+  if (!self->command || !*self->command)
+    return NULL;
+
+  str = g_string_new (NULL);
+
+  if (*self->command == '-')
+    g_string_append (str, self->command);
+  else
+    g_string_append_printf (str, "-%s", self->command);
+
+  /* TODO: Params? */
+
+  g_string_append_c (str, '\n');
+
+  return g_string_free_to_bytes (str);
+}
+
+static void
+mi2_command_message_finalize (GObject *object)
+{
+  Mi2CommandMessage *self = (Mi2CommandMessage *)object;
+
+  g_clear_pointer (&self->command, g_free);
+
+  G_OBJECT_CLASS (mi2_command_message_parent_class)->finalize (object);
+}
+
+static void
+mi2_command_message_get_property (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  Mi2CommandMessage *self = MI2_COMMAND_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_COMMAND:
+      g_value_set_string (value, mi2_command_message_get_command (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_command_message_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  Mi2CommandMessage *self = MI2_COMMAND_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_COMMAND:
+      mi2_command_message_set_command (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_command_message_class_init (Mi2CommandMessageClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  Mi2MessageClass *message_class = MI2_MESSAGE_CLASS (klass);
+
+  object_class->finalize = mi2_command_message_finalize;
+  object_class->get_property = mi2_command_message_get_property;
+  object_class->set_property = mi2_command_message_set_property;
+
+  message_class->serialize = mi2_command_message_serialize;
+
+  properties [PROP_COMMAND] =
+    g_param_spec_string ("command", NULL, NULL, NULL,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+mi2_command_message_init (Mi2CommandMessage *self)
+{
+}
+
+const gchar *
+mi2_command_message_get_command (Mi2CommandMessage *self)
+{
+  g_return_val_if_fail (MI2_IS_COMMAND_MESSAGE (self), NULL);
+
+  return self->command;
+}
+
+void
+mi2_command_message_set_command (Mi2CommandMessage *self,
+                                 const gchar       *command)
+{
+  if (command != self->command)
+    {
+      g_free (self->command);
+      self->command = g_strdup (command);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_COMMAND]);
+    }
+}
+
+Mi2Message *
+mi2_command_message_new_from_string (const gchar *line)
+{
+  Mi2CommandMessage *ret;
+
+  ret = g_object_new (MI2_TYPE_COMMAND_MESSAGE, NULL);
+  ret->command = g_strstrip (g_strdup (&line[1]));
+
+  return MI2_MESSAGE (ret);
+}
diff --git a/contrib/mi2/mi2-command-message.h b/contrib/mi2/mi2-command-message.h
new file mode 100644
index 0000000..ccb8c11
--- /dev/null
+++ b/contrib/mi2/mi2-command-message.h
@@ -0,0 +1,37 @@
+/* mi2-command-message.h
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MI2_COMMAND_MESSAGE_H
+#define MI2_COMMAND_MESSAGE_H
+
+#include "mi2-message.h"
+
+G_BEGIN_DECLS
+
+#define MI2_TYPE_COMMAND_MESSAGE (mi2_command_message_get_type())
+
+G_DECLARE_FINAL_TYPE (Mi2CommandMessage, mi2_command_message, MI2, COMMAND_MESSAGE, Mi2Message)
+
+Mi2Message  *mi2_command_message_new_from_string (const gchar       *line);
+const gchar *mi2_command_message_get_command     (Mi2CommandMessage *self);
+void         mi2_command_message_set_command     (Mi2CommandMessage *self,
+                                                  const gchar       *command);
+
+G_END_DECLS
+
+#endif /* MI2_COMMAND_MESSAGE_H */
diff --git a/contrib/mi2/mi2-console-message.c b/contrib/mi2/mi2-console-message.c
new file mode 100644
index 0000000..71dc3bf
--- /dev/null
+++ b/contrib/mi2/mi2-console-message.c
@@ -0,0 +1,166 @@
+/* mi2-console-message.c
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "mi2-console-message"
+
+#include <string.h>
+
+#include "mi2-console-message.h"
+#include "mi2-util.h"
+
+struct _Mi2ConsoleMessage
+{
+  Mi2Message  parent;
+  gchar      *message;
+};
+
+enum {
+  PROP_0,
+  PROP_MESSAGE,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (Mi2ConsoleMessage, mi2_console_message, MI2_TYPE_MESSAGE)
+
+static GParamSpec *properties [N_PROPS];
+
+static GBytes *
+mi2_console_message_serialize (Mi2Message *message)
+{
+  Mi2ConsoleMessage *self = (Mi2ConsoleMessage *)message;
+  g_autofree gchar *escaped = NULL;
+
+  g_assert (MI2_IS_CONSOLE_MESSAGE (message));
+
+  escaped = g_strescape (self->message ? self->message : "", "");
+
+  if (escaped != NULL)
+    {
+      g_autofree gchar *str = g_strdup_printf ("~\"%s\"\n", escaped);
+
+      if (str != NULL)
+        return g_bytes_new_take (g_steal_pointer (&str), strlen (str));
+    }
+
+  return NULL;
+}
+
+static void
+mi2_console_message_finalize (GObject *object)
+{
+  Mi2ConsoleMessage *self = (Mi2ConsoleMessage *)object;
+
+  g_clear_pointer (&self->message, g_free);
+
+  G_OBJECT_CLASS (mi2_console_message_parent_class)->finalize (object);
+}
+
+static void
+mi2_console_message_get_property (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  Mi2ConsoleMessage *self = MI2_CONSOLE_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_MESSAGE:
+      g_value_set_string (value, mi2_console_message_get_message (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_console_message_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  Mi2ConsoleMessage *self = MI2_CONSOLE_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_MESSAGE:
+      mi2_console_message_set_message (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_console_message_class_init (Mi2ConsoleMessageClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  Mi2MessageClass *message_class = MI2_MESSAGE_CLASS (klass);
+
+  object_class->finalize = mi2_console_message_finalize;
+  object_class->get_property = mi2_console_message_get_property;
+  object_class->set_property = mi2_console_message_set_property;
+
+  message_class->serialize = mi2_console_message_serialize;
+
+  properties [PROP_MESSAGE] =
+    g_param_spec_string ("message", NULL, NULL, NULL,
+                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+mi2_console_message_init (Mi2ConsoleMessage *self)
+{
+}
+
+const gchar *
+mi2_console_message_get_message (Mi2ConsoleMessage *self)
+{
+  g_return_val_if_fail (MI2_IS_CONSOLE_MESSAGE (self), NULL);
+
+  return self->message;
+}
+
+void
+mi2_console_message_set_message (Mi2ConsoleMessage *self,
+                                 const gchar       *message)
+{
+  if (message != self->message)
+    {
+      g_free (self->message);
+      self->message = g_strdup (message);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MESSAGE]);
+    }
+}
+
+Mi2Message *
+mi2_console_message_new_from_string (const gchar *line)
+{
+  Mi2ConsoleMessage *ret;
+
+  ret = g_object_new (MI2_TYPE_CONSOLE_MESSAGE, NULL);
+
+  if (line != NULL && line[0] == '~')
+    ret->message = mi2_util_parse_string (&line[1], NULL);
+
+  return MI2_MESSAGE (ret);
+}
diff --git a/contrib/mi2/mi2-console-message.h b/contrib/mi2/mi2-console-message.h
new file mode 100644
index 0000000..bf702b2
--- /dev/null
+++ b/contrib/mi2/mi2-console-message.h
@@ -0,0 +1,37 @@
+/* mi2-console-message.h
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MI2_CONSOLE_MESSAGE_H
+#define MI2_CONSOLE_MESSAGE_H
+
+#include "mi2-message.h"
+
+G_BEGIN_DECLS
+
+#define MI2_TYPE_CONSOLE_MESSAGE (mi2_console_message_get_type())
+
+G_DECLARE_FINAL_TYPE (Mi2ConsoleMessage, mi2_console_message, MI2, CONSOLE_MESSAGE, Mi2Message)
+
+Mi2Message  *mi2_console_message_new_from_string (const gchar       *line);
+const gchar *mi2_console_message_get_message     (Mi2ConsoleMessage *self);
+void         mi2_console_message_set_message     (Mi2ConsoleMessage *self,
+                                                  const gchar       *message);
+
+G_END_DECLS
+
+#endif /* MI2_CONSOLE_MESSAGE_H */
diff --git a/contrib/mi2/mi2-enums.c.in b/contrib/mi2/mi2-enums.c.in
new file mode 100644
index 0000000..72d5d5c
--- /dev/null
+++ b/contrib/mi2/mi2-enums.c.in
@@ -0,0 +1,39 @@
+/*** BEGIN file-header ***/
+
+#include "config.h"
+
+#include "mi2-enums.h"
+#include "mi2-error.h"
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@filename@" */
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType
+@enum_name@_get_type (void)
+{
+    static GType etype = 0;
+    if (G_UNLIKELY(etype == 0)) {
+        static const G@Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+            { @VALUENAME@, "@VALUENAME@", "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+            { 0, NULL, NULL }
+        };
+        etype = g_@type@_register_static (g_intern_static_string ("@EnumName@"), values);
+    }
+    return etype;
+}
+
+/*** END value-tail ***/
+
+/*** BEGIN file-tail ***/
+
+/*** END file-tail ***/
diff --git a/contrib/mi2/mi2-enums.h.in b/contrib/mi2/mi2-enums.h.in
new file mode 100644
index 0000000..5b7a196
--- /dev/null
+++ b/contrib/mi2/mi2-enums.h.in
@@ -0,0 +1,24 @@
+/*** BEGIN file-header ***/
+#ifndef __MI2_ENUMS_H__
+#define __MI2_ENUMS_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+
+/* enumerations from "@filename@" */
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType @enum_name@_get_type (void);
+#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ())
+/*** END value-header ***/
+
+/*** BEGIN file-tail ***/
+G_END_DECLS
+
+#endif /* __MI2_ENUMS_H__ */
+/*** END file-tail ***/
diff --git a/contrib/mi2/mi2-error.c b/contrib/mi2/mi2-error.c
new file mode 100644
index 0000000..9e81263
--- /dev/null
+++ b/contrib/mi2/mi2-error.c
@@ -0,0 +1,25 @@
+/* mi2-error.c
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mi2-error.h"
+
+GQuark
+mi2_error_quark (void)
+{
+  return g_quark_from_static_string ("mi2-error-domain");
+}
diff --git a/contrib/mi2/mi2-error.h b/contrib/mi2/mi2-error.h
new file mode 100644
index 0000000..0bd46dc
--- /dev/null
+++ b/contrib/mi2/mi2-error.h
@@ -0,0 +1,39 @@
+/* mi2-error.h
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MI2_ERROR_H
+#define MI2_ERROR_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define MI2_ERROR (mi2_error_quark())
+
+typedef enum
+{
+  MI2_ERROR_UNKNOWN_ERROR,
+  MI2_ERROR_EXEC_PENDING,
+  MI2_ERROR_INVALID_DATA,
+} Mi2Error;
+
+GQuark mi2_error_quark (void);
+
+G_END_DECLS
+
+#endif /* MI2_ERROR_H */
diff --git a/contrib/mi2/mi2-event-message.c b/contrib/mi2/mi2-event-message.c
new file mode 100644
index 0000000..079117e
--- /dev/null
+++ b/contrib/mi2/mi2-event-message.c
@@ -0,0 +1,152 @@
+/* mi2-event-mesage.c
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "mi2-event-message"
+
+#include "mi2-event-message.h"
+#include "mi2-util.h"
+
+struct _Mi2EventMessage
+{
+  Mi2Message  parent_instance;
+  gchar      *name;
+};
+
+enum {
+  PROP_0,
+  PROP_NAME,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (Mi2EventMessage, mi2_event_mesage, MI2_TYPE_MESSAGE)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+mi2_event_mesage_finalize (GObject *object)
+{
+  Mi2EventMessage *self = (Mi2EventMessage *)object;
+
+  g_clear_pointer (&self->name, g_free);
+
+  G_OBJECT_CLASS (mi2_event_mesage_parent_class)->finalize (object);
+}
+
+static void
+mi2_event_mesage_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  Mi2EventMessage *self = MI2_EVENT_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_NAME:
+      g_value_set_string (value, mi2_event_message_get_name (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_event_mesage_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  Mi2EventMessage *self = MI2_EVENT_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_NAME:
+      mi2_event_message_set_name (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_event_mesage_class_init (Mi2EventMessageClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = mi2_event_mesage_finalize;
+  object_class->get_property = mi2_event_mesage_get_property;
+  object_class->set_property = mi2_event_mesage_set_property;
+
+  properties [PROP_NAME] =
+    g_param_spec_string ("name",
+                         "Name",
+                         "Name",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+mi2_event_mesage_init (Mi2EventMessage *self)
+{
+}
+
+/**
+ * mi2_event_message_new_from_string:
+ * @line: the string to be parsed
+ *
+ * Returns: (transfer full): An #Mi2Message
+ */
+Mi2Message *
+mi2_event_message_new_from_string (const gchar *line)
+{
+  Mi2EventMessage *ret;
+
+  ret = g_object_new (MI2_TYPE_EVENT_MESSAGE, NULL);
+
+  if (line && *line)
+    {
+      ret->name = mi2_util_parse_word (&line[1], &line);
+      mi2_message_parse_params (MI2_MESSAGE (ret), line);
+    }
+
+  return MI2_MESSAGE (ret);
+}
+
+const gchar *
+mi2_event_message_get_name (Mi2EventMessage *self)
+{
+  g_return_val_if_fail (MI2_IS_EVENT_MESSAGE (self), NULL);
+
+  return self->name;
+}
+
+void
+mi2_event_message_set_name (Mi2EventMessage *self,
+                            const gchar     *name)
+{
+  if (name != self->name)
+    {
+      g_free (self->name);
+      self->name = g_strdup (name);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_NAME]);
+    }
+}
diff --git a/contrib/mi2/mi2-event-message.h b/contrib/mi2/mi2-event-message.h
new file mode 100644
index 0000000..a9b62f5
--- /dev/null
+++ b/contrib/mi2/mi2-event-message.h
@@ -0,0 +1,37 @@
+/* mi2-event-mesage.h
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MI2_EVENT_MESAGE_H
+#define MI2_EVENT_MESAGE_H
+
+#include "mi2-message.h"
+
+G_BEGIN_DECLS
+
+#define MI2_TYPE_EVENT_MESSAGE (mi2_event_mesage_get_type())
+
+G_DECLARE_FINAL_TYPE (Mi2EventMessage, mi2_event_mesage, MI2, EVENT_MESSAGE, Mi2Message)
+
+Mi2Message   *mi2_event_message_new_from_string (const gchar     *line);
+const gchar  *mi2_event_message_get_name        (Mi2EventMessage *self);
+void          mi2_event_message_set_name        (Mi2EventMessage *self,
+                                                 const gchar     *name);
+
+G_END_DECLS
+
+#endif /* MI2_EVENT_MESAGE_H */
diff --git a/contrib/mi2/mi2-glib.h b/contrib/mi2/mi2-glib.h
new file mode 100644
index 0000000..a4a036f
--- /dev/null
+++ b/contrib/mi2/mi2-glib.h
@@ -0,0 +1,36 @@
+/* mi2-glib.h
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MI2_GLIB_H
+#define MI2_GLIB_H
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#include "mi2-client.h"
+#include "mi2-command-message.h"
+#include "mi2-console-message.h"
+#include "mi2-event-message.h"
+#include "mi2-info-message.h"
+#include "mi2-input-stream.h"
+#include "mi2-message.h"
+
+G_END_DECLS
+
+#endif /* MI2_GLIB_H */
diff --git a/contrib/mi2/mi2-info-message.c b/contrib/mi2/mi2-info-message.c
new file mode 100644
index 0000000..c077c20
--- /dev/null
+++ b/contrib/mi2/mi2-info-message.c
@@ -0,0 +1,142 @@
+/* mi2-info-message.c
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "mi2-info-message"
+
+#include <string.h>
+
+#include "mi2-info-message.h"
+#include "mi2-util.h"
+
+struct _Mi2InfoMessage
+{
+  Mi2Message  parent;
+  gchar      *message;
+};
+
+enum {
+  PROP_0,
+  PROP_MESSAGE,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (Mi2InfoMessage, mi2_info_message, MI2_TYPE_MESSAGE)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+mi2_info_message_finalize (GObject *object)
+{
+  Mi2InfoMessage *self = (Mi2InfoMessage *)object;
+
+  g_clear_pointer (&self->message, g_free);
+
+  G_OBJECT_CLASS (mi2_info_message_parent_class)->finalize (object);
+}
+
+static void
+mi2_info_message_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  Mi2InfoMessage *self = MI2_INFO_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_MESSAGE:
+      g_value_set_string (value, mi2_info_message_get_message (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_info_message_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  Mi2InfoMessage *self = MI2_INFO_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_MESSAGE:
+      mi2_info_message_set_message (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_info_message_class_init (Mi2InfoMessageClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = mi2_info_message_finalize;
+  object_class->get_property = mi2_info_message_get_property;
+  object_class->set_property = mi2_info_message_set_property;
+
+  properties [PROP_MESSAGE] =
+    g_param_spec_string ("message", NULL, NULL, NULL,
+                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+mi2_info_message_init (Mi2InfoMessage *self)
+{
+}
+
+const gchar *
+mi2_info_message_get_message (Mi2InfoMessage *self)
+{
+  g_return_val_if_fail (MI2_IS_INFO_MESSAGE (self), NULL);
+
+  return self->message;
+}
+
+void
+mi2_info_message_set_message (Mi2InfoMessage *self,
+                              const gchar    *message)
+{
+  if (message != self->message)
+    {
+      g_free (self->message);
+      self->message = g_strdup (message);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MESSAGE]);
+    }
+}
+
+Mi2Message *
+mi2_info_message_new_from_string (const gchar *line)
+{
+  Mi2InfoMessage *ret;
+
+  ret = g_object_new (MI2_TYPE_INFO_MESSAGE, NULL);
+
+  if (line != NULL && line[0] == '&')
+    ret->message = mi2_util_parse_string (&line[1], NULL);
+
+  return MI2_MESSAGE (ret);
+}
diff --git a/contrib/mi2/mi2-info-message.h b/contrib/mi2/mi2-info-message.h
new file mode 100644
index 0000000..59d2729
--- /dev/null
+++ b/contrib/mi2/mi2-info-message.h
@@ -0,0 +1,37 @@
+/* mi2-info-message.h
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MI2_INFO_MESSAGE_H
+#define MI2_INFO_MESSAGE_H
+
+#include "mi2-message.h"
+
+G_BEGIN_DECLS
+
+#define MI2_TYPE_INFO_MESSAGE (mi2_info_message_get_type())
+
+G_DECLARE_FINAL_TYPE (Mi2InfoMessage, mi2_info_message, MI2, INFO_MESSAGE, Mi2Message)
+
+Mi2Message  *mi2_info_message_new_from_string (const gchar       *line);
+const gchar *mi2_info_message_get_message     (Mi2InfoMessage *self);
+void         mi2_info_message_set_message     (Mi2InfoMessage *self,
+                                               const gchar       *message);
+
+G_END_DECLS
+
+#endif /* MI2_INFO_MESSAGE_H */
diff --git a/contrib/mi2/mi2-input-stream.c b/contrib/mi2/mi2-input-stream.c
new file mode 100644
index 0000000..71f1c5c
--- /dev/null
+++ b/contrib/mi2/mi2-input-stream.c
@@ -0,0 +1,141 @@
+/* mi2-input-stream.c
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "mi2-input-stream"
+
+#include <string.h>
+
+#include "mi2-input-stream.h"
+
+G_DEFINE_TYPE (Mi2InputStream, mi2_input_stream, G_TYPE_DATA_INPUT_STREAM)
+
+static void
+mi2_input_stream_class_init (Mi2InputStreamClass *klass)
+{
+}
+
+static void
+mi2_input_stream_init (Mi2InputStream *self)
+{
+}
+
+Mi2InputStream *
+mi2_input_stream_new (GInputStream *base_stream)
+{
+  return g_object_new (MI2_TYPE_INPUT_STREAM,
+                       "base-stream", base_stream,
+                       NULL);
+}
+
+static void
+mi2_input_stream_read_message_read_line_cb (GObject      *object,
+                                            GAsyncResult *result,
+                                            gpointer      user_data)
+{
+  Mi2InputStream *self = (Mi2InputStream *)object;
+  g_autoptr(Mi2Message) message = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GTask) task = user_data;
+  g_autofree gchar *line = NULL;
+  gsize len = 0;
+
+  g_assert (MI2_IS_INPUT_STREAM (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
+
+  line = g_data_input_stream_read_line_finish_utf8 (G_DATA_INPUT_STREAM (self), result, &len, &error);
+
+  /* Nothing to read, return NULL message */
+  if (line == NULL && error == NULL)
+    {
+      g_task_return_pointer (task, NULL, NULL);
+      return;
+    }
+
+  if (line != NULL && g_str_has_prefix (line, "(gdb)"))
+    {
+      /* Ignore this line, read again */
+      GCancellable *cancellable = g_task_get_cancellable (task);
+      g_data_input_stream_read_line_async (G_DATA_INPUT_STREAM (self),
+                                           G_PRIORITY_LOW,
+                                           cancellable,
+                                           mi2_input_stream_read_message_read_line_cb,
+                                           g_steal_pointer (&task));
+      return;
+    }
+
+  if (line == NULL)
+    {
+      g_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_CLOSED,
+                               "The stream has closed");
+      return;
+    }
+
+  /* Let Mi2Message decode the protocol content */
+  if (NULL == (message = mi2_message_parse (line, len, &error)))
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  g_task_return_pointer (task, g_steal_pointer (&message), g_object_unref);
+}
+
+void
+mi2_input_stream_read_message_async (Mi2InputStream      *self,
+                                     GCancellable        *cancellable,
+                                     GAsyncReadyCallback  callback,
+                                     gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (MI2_IS_INPUT_STREAM (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, mi2_input_stream_read_message_async);
+  g_task_set_return_on_cancel (task, TRUE);
+
+  g_data_input_stream_read_line_async (G_DATA_INPUT_STREAM (self),
+                                       G_PRIORITY_LOW,
+                                       cancellable,
+                                       mi2_input_stream_read_message_read_line_cb,
+                                       g_steal_pointer (&task));
+}
+
+/**
+ * mi2_input_stream_read_message_finish:
+ *
+ * Finish an asynchronous call started by mi2_input_stream_read_message_async().
+ *
+ * Returns: (transfer full): An #Mi2Message if successful. If there is no data
+ *   to read, this function returns %NULL and @error is not set. If an error
+ *   occurred, %NULL is returned and @error is set.
+ */
+Mi2Message *
+mi2_input_stream_read_message_finish (Mi2InputStream  *self,
+                                      GAsyncResult    *result,
+                                      GError         **error)
+{
+  g_return_val_if_fail (MI2_IS_INPUT_STREAM (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
diff --git a/contrib/mi2/mi2-input-stream.h b/contrib/mi2/mi2-input-stream.h
new file mode 100644
index 0000000..fd53843
--- /dev/null
+++ b/contrib/mi2/mi2-input-stream.h
@@ -0,0 +1,57 @@
+/* mi2-input-stream.h
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MI2_INPUT_STREAM_H
+#define MI2_INPUT_STREAM_H
+
+#include <gio/gio.h>
+
+#include "mi2-message.h"
+
+G_BEGIN_DECLS
+
+#define MI2_TYPE_INPUT_STREAM (mi2_input_stream_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (Mi2InputStream, mi2_input_stream, MI2, INPUT_STREAM, GDataInputStream)
+
+struct _Mi2InputStreamClass
+{
+  GDataInputStreamClass parent_class;
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+};
+
+Mi2InputStream *mi2_input_stream_new                 (GInputStream         *base_stream);
+void            mi2_input_stream_read_message_async  (Mi2InputStream       *self,
+                                                      GCancellable         *cancellable,
+                                                      GAsyncReadyCallback   callback,
+                                                      gpointer              user_data);
+Mi2Message     *mi2_input_stream_read_message_finish (Mi2InputStream       *self,
+                                                      GAsyncResult         *result,
+                                                      GError              **error);
+
+G_END_DECLS
+
+#endif /* MI2_INPUT_STREAM_H */
diff --git a/contrib/mi2/mi2-message.c b/contrib/mi2/mi2-message.c
new file mode 100644
index 0000000..108ab8f
--- /dev/null
+++ b/contrib/mi2/mi2-message.c
@@ -0,0 +1,257 @@
+/* mi2-message.c
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "mi2-message"
+
+#include "mi2-message.h"
+
+#include "mi2-command-message.h"
+#include "mi2-console-message.h"
+#include "mi2-error.h"
+#include "mi2-event-message.h"
+#include "mi2-info-message.h"
+#include "mi2-reply-message.h"
+#include "mi2-util.h"
+
+typedef struct
+{
+  GHashTable *params;
+} Mi2MessagePrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (Mi2Message, mi2_message, G_TYPE_OBJECT)
+
+static GHashTable *
+make_hashtable (void)
+{
+  return g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_variant_unref);
+}
+
+static void
+mi2_message_finalize (GObject *object)
+{
+  Mi2Message *self = (Mi2Message *)object;
+  Mi2MessagePrivate *priv = mi2_message_get_instance_private (self);
+
+  g_clear_pointer (&priv->params, g_hash_table_unref);
+
+  G_OBJECT_CLASS (mi2_message_parent_class)->finalize (object);
+}
+
+static void
+mi2_message_class_init (Mi2MessageClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = mi2_message_finalize;
+}
+
+static void
+mi2_message_init (Mi2Message *self)
+{
+}
+
+/**
+ * mi2_message_parse:
+ * @line: the line to parse
+ * @error: a location for a #GError or %NULL
+ *
+ * Parses @line into #Mi2Message.
+ *
+ * Returns: (transfer full): An #Mi2Message if successful; otherwise %NULL
+ *   and @error is set.
+ */
+Mi2Message *
+mi2_message_parse (const gchar  *line,
+                   gsize         len,
+                   GError      **error)
+{
+  const gchar *begin = line;
+  Mi2Message *ret = NULL;
+
+  g_return_val_if_fail (line != NULL, NULL);
+  g_return_val_if_fail (len > 0, NULL);
+
+  switch (line[0])
+    {
+    case '~':
+      ret = mi2_console_message_new_from_string (line);
+      break;
+
+    case '&':
+      ret = mi2_info_message_new_from_string (line);
+      break;
+
+    case '^':
+      ret = mi2_reply_message_new_from_string (line);
+      break;
+
+    case '=':
+    case '*':
+      ret = mi2_event_message_new_from_string (line);
+      break;
+
+    case '-':
+      ret = mi2_command_message_new_from_string (line);
+      break;
+
+    default:
+      break;
+    }
+
+  if (ret == NULL)
+    g_set_error (error,
+                 MI2_ERROR,
+                 MI2_ERROR_INVALID_DATA,
+                 "Failed to parse: %s", begin);
+
+  return ret;
+}
+
+void
+mi2_message_parse_params (Mi2Message  *self,
+                          const gchar *line)
+{
+  g_autoptr(GVariant) params = NULL;
+
+  g_return_if_fail (MI2_IS_MESSAGE (self));
+  g_return_if_fail (line != NULL);
+
+  params = mi2_util_parse_record (line, NULL);
+
+  if (params)
+    {
+      GVariantIter iter;
+      const gchar *key;
+      GVariant *value;
+
+      g_variant_iter_init (&iter, params);
+      while (g_variant_iter_loop (&iter, "{sv}", &key, &value))
+        mi2_message_set_param (self, key, value);
+    }
+}
+
+/**
+ * mi2_message_serialize:
+ * @self: An #Mi2Message
+ *
+ * Serialize the message to be sent to the peer.
+ *
+ * Returns: (transfer full): A #GBytes.
+ */
+GBytes *
+mi2_message_serialize (Mi2Message *self)
+{
+  g_return_val_if_fail (MI2_IS_MESSAGE (self), NULL);
+
+  return MI2_MESSAGE_GET_CLASS (self)->serialize (self);
+}
+
+const gchar *
+mi2_message_get_param_string (Mi2Message  *self,
+                              const gchar *name)
+{
+  Mi2MessagePrivate *priv = mi2_message_get_instance_private (self);
+  GVariant *variant = NULL;
+
+  g_return_val_if_fail (MI2_IS_MESSAGE (self), NULL);
+
+  if (priv->params != NULL)
+    variant = g_hash_table_lookup (priv->params, name);
+
+  return variant == NULL ? NULL : g_variant_get_string (variant, NULL);
+}
+
+void
+mi2_message_set_param_string (Mi2Message  *self,
+                              const gchar *name,
+                              const gchar *value)
+{
+  Mi2MessagePrivate *priv = mi2_message_get_instance_private (self);
+
+  g_return_if_fail (MI2_IS_MESSAGE (self));
+  g_return_if_fail (name != NULL);
+
+  if (priv->params == NULL)
+    priv->params = make_hashtable ();
+
+  if (value == NULL)
+    g_hash_table_remove (priv->params, name);
+  else
+    g_hash_table_insert (priv->params,
+                         g_strdup (name),
+                         g_variant_ref_sink (g_variant_new_string (value)));
+}
+
+GVariant *
+mi2_message_get_param (Mi2Message  *self,
+                       const gchar *param)
+{
+  Mi2MessagePrivate *priv = mi2_message_get_instance_private (self);
+
+  g_return_val_if_fail (MI2_IS_MESSAGE (self), NULL);
+  g_return_val_if_fail (param != NULL, NULL);
+
+  if (priv->params)
+    return g_hash_table_lookup (priv->params, param);
+
+  return NULL;
+}
+
+void
+mi2_message_set_param (Mi2Message  *self,
+                       const gchar *param,
+                       GVariant    *variant)
+{
+  Mi2MessagePrivate *priv = mi2_message_get_instance_private (self);
+
+  g_return_if_fail (MI2_IS_MESSAGE (self));
+  g_return_if_fail (param != NULL);
+
+  if (priv->params == NULL)
+    priv->params = make_hashtable ();
+
+  if (variant == NULL)
+    g_hash_table_remove (priv->params, param);
+  else
+    g_hash_table_insert (priv->params,
+                         g_strdup (param),
+                         g_variant_ref_sink (variant));
+}
+
+/**
+ * mi2_message_get_params:
+ * @self: An #Mi2Message
+ *
+ * Gets the keys for params that are stored in the message, free the
+ * result with g_free() as ownership of the fields is owned by the
+ * #Mi2Message.
+ *
+ * Returns: (transfer container): A %NULL-terminated array of param names.
+ */
+const gchar **
+mi2_message_get_params (Mi2Message *self)
+{
+  Mi2MessagePrivate *priv = mi2_message_get_instance_private (self);
+
+  g_return_val_if_fail (MI2_IS_MESSAGE (self), NULL);
+
+  if (priv->params != NULL)
+    return (const gchar **)g_hash_table_get_keys_as_array (priv->params, NULL);
+
+  return (const gchar **)g_new0 (gchar *, 1);
+}
diff --git a/contrib/mi2/mi2-message.h b/contrib/mi2/mi2-message.h
new file mode 100644
index 0000000..58ffca0
--- /dev/null
+++ b/contrib/mi2/mi2-message.h
@@ -0,0 +1,66 @@
+/* mi2-message.h
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MI2_MESSAGE_H
+#define MI2_MESSAGE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define MI2_TYPE_MESSAGE (mi2_message_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (Mi2Message, mi2_message, MI2, MESSAGE, GObject)
+
+struct _Mi2MessageClass
+{
+  GObjectClass parent_class;
+
+  GBytes *(*serialize) (Mi2Message *self);
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+};
+
+Mi2Message   *mi2_message_parse            (const gchar  *line,
+                                            gsize         len,
+                                            GError      **error);
+GBytes       *mi2_message_serialize        (Mi2Message   *self);
+const gchar **mi2_message_get_params       (Mi2Message   *self);
+void          mi2_message_parse_params     (Mi2Message   *self,
+                                            const gchar  *line);
+GVariant     *mi2_message_get_param        (Mi2Message   *self,
+                                            const gchar  *param);
+void          mi2_message_set_param        (Mi2Message   *self,
+                                            const gchar  *param,
+                                            GVariant     *variant);
+const gchar  *mi2_message_get_param_string (Mi2Message   *self,
+                                            const gchar  *name);
+void          mi2_message_set_param_string (Mi2Message   *self,
+                                            const gchar  *name,
+                                            const gchar  *value);
+
+G_END_DECLS
+
+#endif /* MI2_MESSAGE_H */
diff --git a/contrib/mi2/mi2-output-stream.c b/contrib/mi2/mi2-output-stream.c
new file mode 100644
index 0000000..dfc6fb6
--- /dev/null
+++ b/contrib/mi2/mi2-output-stream.c
@@ -0,0 +1,106 @@
+/* mi2-output-stream.c
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOD_DOMAIN "mi2-output-stream"
+
+#include "mi2-output-stream.h"
+
+G_DEFINE_TYPE (Mi2OutputStream, mi2_output_stream, G_TYPE_DATA_OUTPUT_STREAM)
+
+static void
+mi2_output_stream_class_init (Mi2OutputStreamClass *klass)
+{
+}
+
+static void
+mi2_output_stream_init (Mi2OutputStream *self)
+{
+}
+
+Mi2OutputStream *
+mi2_output_stream_new (GOutputStream *base_stream)
+{
+  return g_object_new (MI2_TYPE_OUTPUT_STREAM,
+                       "base-stream", base_stream,
+                       NULL);
+}
+
+static void
+mi2_output_stream_write_message_cb (GObject      *object,
+                                    GAsyncResult *result,
+                                    gpointer      user_data)
+{
+  Mi2OutputStream *self = (Mi2OutputStream *)object;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GTask) task = user_data;
+
+  g_assert (MI2_IS_OUTPUT_STREAM (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
+
+  if (!g_output_stream_write_bytes_finish (G_OUTPUT_STREAM (self), result, &error))
+    g_task_return_error (task, g_steal_pointer (&error));
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+void
+mi2_output_stream_write_message_async (Mi2OutputStream     *self,
+                                       Mi2Message          *message,
+                                       GCancellable        *cancellable,
+                                       GAsyncReadyCallback  callback,
+                                       gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GBytes) bytes = NULL;
+
+  g_return_if_fail (MI2_IS_OUTPUT_STREAM (self));
+  g_return_if_fail (MI2_IS_MESSAGE (message));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, mi2_output_stream_write_message_async);
+
+  bytes = mi2_message_serialize (message);
+
+  if (bytes == NULL)
+    {
+      g_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_INVALID_DATA,
+                               "message failed to serialize to bytes");
+      return;
+    }
+
+  g_output_stream_write_bytes_async (G_OUTPUT_STREAM (self),
+                                     bytes,
+                                     G_PRIORITY_LOW,
+                                     cancellable,
+                                     mi2_output_stream_write_message_cb,
+                                     g_steal_pointer (&task));
+}
+
+gboolean
+mi2_output_stream_write_message_finish (Mi2OutputStream  *self,
+                                        GAsyncResult     *result,
+                                        GError          **error)
+{
+  g_return_val_if_fail (MI2_IS_OUTPUT_STREAM (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
diff --git a/contrib/mi2/mi2-output-stream.h b/contrib/mi2/mi2-output-stream.h
new file mode 100644
index 0000000..1597bda
--- /dev/null
+++ b/contrib/mi2/mi2-output-stream.h
@@ -0,0 +1,58 @@
+/* mi2-output-stream.h
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MI2_OUTPUT_STREAM_H
+#define MI2_OUTPUT_STREAM_H
+
+#include <gio/gio.h>
+
+#include "mi2-message.h"
+
+G_BEGIN_DECLS
+
+#define MI2_TYPE_OUTPUT_STREAM (mi2_output_stream_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (Mi2OutputStream, mi2_output_stream, MI2, OUTPUT_STREAM, GDataOutputStream)
+
+struct _Mi2OutputStreamClass
+{
+  GDataOutputStreamClass parent_instance;
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+};
+
+Mi2OutputStream *mi2_output_stream_new                  (GOutputStream        *base_stream);
+void             mi2_output_stream_write_message_async  (Mi2OutputStream      *stream,
+                                                         Mi2Message           *message,
+                                                         GCancellable         *cancellable,
+                                                         GAsyncReadyCallback   callback,
+                                                         gpointer              user_data);
+gboolean         mi2_output_stream_write_message_finish (Mi2OutputStream      *self,
+                                                         GAsyncResult         *result,
+                                                         GError              **error);
+
+G_END_DECLS
+
+#endif /* MI2_OUTPUT_STREAM_H */
diff --git a/contrib/mi2/mi2-reply-message.c b/contrib/mi2/mi2-reply-message.c
new file mode 100644
index 0000000..cb5209c
--- /dev/null
+++ b/contrib/mi2/mi2-reply-message.c
@@ -0,0 +1,220 @@
+/* mi2-reply-mesage.c
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "mi2-reply-message"
+
+#include "mi2-error.h"
+#include "mi2-reply-message.h"
+#include "mi2-util.h"
+
+struct _Mi2ReplyMessage
+{
+  Mi2Message  parent_instance;
+  gchar      *name;
+};
+
+enum {
+  PROP_0,
+  PROP_NAME,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (Mi2ReplyMessage, mi2_reply_mesage, MI2_TYPE_MESSAGE)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+mi2_reply_mesage_finalize (GObject *object)
+{
+  Mi2ReplyMessage *self = (Mi2ReplyMessage *)object;
+
+  g_clear_pointer (&self->name, g_free);
+
+  G_OBJECT_CLASS (mi2_reply_mesage_parent_class)->finalize (object);
+}
+
+static void
+mi2_reply_mesage_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  Mi2ReplyMessage *self = MI2_REPLY_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_NAME:
+      g_value_set_string (value, mi2_reply_message_get_name (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_reply_mesage_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  Mi2ReplyMessage *self = MI2_REPLY_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_NAME:
+      mi2_reply_message_set_name (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_reply_mesage_class_init (Mi2ReplyMessageClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = mi2_reply_mesage_finalize;
+  object_class->get_property = mi2_reply_mesage_get_property;
+  object_class->set_property = mi2_reply_mesage_set_property;
+
+  properties [PROP_NAME] =
+    g_param_spec_string ("name",
+                         "Name",
+                         "Name",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+mi2_reply_mesage_init (Mi2ReplyMessage *self)
+{
+}
+
+/**
+ * mi2_reply_message_new_from_string:
+ * @line: the string to be parsed
+ *
+ * Returns: (transfer full): An #Mi2Message
+ */
+Mi2Message *
+mi2_reply_message_new_from_string (const gchar *line)
+{
+  Mi2ReplyMessage *ret;
+
+  ret = g_object_new (MI2_TYPE_REPLY_MESSAGE, NULL);
+
+  if (line && *line)
+    {
+      ret->name = mi2_util_parse_word (&line[1], &line);
+
+      while (line != NULL && *line != '\0')
+        {
+          g_autofree gchar *key = NULL;
+
+          if (*line == ',')
+            line++;
+
+          key = mi2_util_parse_word (line, &line);
+          if (key == NULL)
+            break;
+
+          if (*line == '=')
+            line++;
+
+          if (*line == '"')
+            {
+              g_autofree gchar *value = NULL;
+
+              value = mi2_util_parse_string (line, &line);
+              mi2_message_set_param_string (MI2_MESSAGE (ret), key, value);
+              continue;
+            }
+          else if (*line == '{')
+            {
+              g_autoptr(GVariant) variant = NULL;
+
+              variant = mi2_util_parse_record (line, &line);
+              mi2_message_set_param (MI2_MESSAGE (ret), key, variant);
+              continue;
+            }
+          else if (*line == '[')
+            {
+              g_autoptr(GVariant) variant = NULL;
+
+              variant = mi2_util_parse_list (line, &line);
+              mi2_message_set_param (MI2_MESSAGE (ret), key, variant);
+              continue;
+            }
+
+          g_warning ("Failed to parse: %s\n", line);
+
+          break;
+        }
+    }
+
+  return MI2_MESSAGE (ret);
+}
+
+const gchar *
+mi2_reply_message_get_name (Mi2ReplyMessage *self)
+{
+  g_return_val_if_fail (MI2_IS_REPLY_MESSAGE (self), NULL);
+
+  return self->name;
+}
+
+void
+mi2_reply_message_set_name (Mi2ReplyMessage *self,
+                            const gchar     *name)
+{
+  if (name != self->name)
+    {
+      g_free (self->name);
+      self->name = g_strdup (name);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_NAME]);
+    }
+}
+
+gboolean
+mi2_reply_message_check_error (Mi2ReplyMessage  *self,
+                               GError          **error)
+{
+  g_return_val_if_fail (MI2_IS_REPLY_MESSAGE (self), FALSE);
+
+  if (g_strcmp0 (self->name, "error") == 0)
+    {
+      const gchar *msg = mi2_message_get_param_string (MI2_MESSAGE (self), "msg");
+
+      if (msg == NULL || *msg == '\0')
+        msg = "An unknown error occrred";
+
+      g_set_error_literal (error,
+                           MI2_ERROR,
+                           MI2_ERROR_UNKNOWN_ERROR,
+                           msg);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
diff --git a/contrib/mi2/mi2-reply-message.h b/contrib/mi2/mi2-reply-message.h
new file mode 100644
index 0000000..d5c80e0
--- /dev/null
+++ b/contrib/mi2/mi2-reply-message.h
@@ -0,0 +1,39 @@
+/* mi2-reply-mesage.h
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MI2_REPLY_MESAGE_H
+#define MI2_REPLY_MESAGE_H
+
+#include "mi2-message.h"
+
+G_BEGIN_DECLS
+
+#define MI2_TYPE_REPLY_MESSAGE (mi2_reply_mesage_get_type())
+
+G_DECLARE_FINAL_TYPE (Mi2ReplyMessage, mi2_reply_mesage, MI2, REPLY_MESSAGE, Mi2Message)
+
+Mi2Message   *mi2_reply_message_new_from_string (const gchar      *line);
+const gchar  *mi2_reply_message_get_name        (Mi2ReplyMessage  *self);
+void          mi2_reply_message_set_name        (Mi2ReplyMessage  *self,
+                                                 const gchar      *name);
+gboolean      mi2_reply_message_check_error     (Mi2ReplyMessage  *self,
+                                                 GError          **error);
+
+G_END_DECLS
+
+#endif /* MI2_REPLY_MESAGE_H */
diff --git a/contrib/mi2/mi2-util.c b/contrib/mi2/mi2-util.c
new file mode 100644
index 0000000..8b9822d
--- /dev/null
+++ b/contrib/mi2/mi2-util.c
@@ -0,0 +1,262 @@
+/* mi2-util.c
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mi2-util.h"
+
+gchar *
+mi2_util_parse_string (const gchar  *line,
+                       const gchar **endptr)
+{
+  g_autoptr(GString) str = NULL;
+
+  g_return_val_if_fail (line != NULL, NULL);
+  g_return_val_if_fail (line[0] == '"', NULL);
+
+  str = g_string_new (NULL);
+
+  for (++line; *line; line = g_utf8_next_char (line))
+    {
+      gunichar ch = g_utf8_get_char (line);
+
+      if (ch == '"')
+        break;
+
+      /* Handle escape characters */
+      if (ch == '\\')
+        {
+          line = g_utf8_next_char (line);
+          if (!*line)
+            goto failure;
+          ch = g_utf8_get_char (line);
+
+          switch (ch)
+            {
+            case 'n':
+              g_string_append (str, "\n");
+              break;
+
+            case 't':
+              g_string_append (str, "\t");
+              break;
+
+            default:
+              g_string_append_unichar (str, ch);
+              break;
+            }
+
+          continue;
+        }
+
+      g_string_append_unichar (str, ch);
+    }
+
+  if (*line == '"')
+    line++;
+
+  if (endptr)
+    *endptr = line;
+
+  return g_string_free (g_steal_pointer (&str), FALSE);
+
+failure:
+  g_warning ("Failed to parse string");
+
+  if (endptr)
+    *endptr = NULL;
+
+  return NULL;
+}
+
+gchar *
+mi2_util_parse_word (const gchar  *line,
+                     const gchar **endptr)
+{
+  const gchar *begin = line;
+  gchar *ret;
+
+  g_return_val_if_fail (line != NULL, NULL);
+
+  for (; *line; line = g_utf8_next_char (line))
+    {
+      gunichar ch = g_utf8_get_char (line);
+
+      if (ch == ',' || ch == '=' || g_unichar_isspace (ch))
+        break;
+    }
+
+  ret = g_strndup (begin, line - begin);
+
+  if (endptr)
+    *endptr = line;
+
+  return ret;
+}
+
+GVariant *
+mi2_util_parse_record (const gchar  *line,
+                       const gchar **endptr)
+{
+  GVariantDict dict;
+
+  g_return_val_if_fail (line != NULL, NULL);
+
+  g_variant_dict_init (&dict, NULL);
+
+  /* move past { if we aren't starting from inside {} */
+  if (*line == '{')
+    line++;
+
+  while (*line && *line != '}')
+    {
+      g_autofree gchar *key = NULL;
+
+      if (*line == ',')
+        line++;
+
+      if (!(key = mi2_util_parse_word (line, &line)))
+        goto failure;
+
+      if (*line == '=')
+        line++;
+
+      if (*line == '"')
+        {
+          g_autofree gchar *value = NULL;
+
+          if (!(value = mi2_util_parse_string (line, &line)))
+            goto failure;
+
+          g_variant_dict_insert (&dict, key, "s", value);
+        }
+      else if (*line == '{')
+        {
+          g_autoptr(GVariant) v = NULL;
+
+          if (!(v = mi2_util_parse_record (line, &line)))
+            goto failure;
+
+          g_variant_dict_insert_value (&dict, key, v);
+        }
+      else if (*line == '[')
+        {
+          g_autoptr(GVariant) ar = NULL;
+
+          if (!(ar = mi2_util_parse_list (line, &line)))
+            goto failure;
+
+          g_variant_dict_insert_value (&dict, key, ar);
+        }
+      else
+        goto failure;
+
+      if (*line == ',')
+        line++;
+    }
+
+  if (*line == '}')
+    line++;
+
+  if (endptr)
+    *endptr = line;
+
+  return g_variant_ref_sink (g_variant_dict_end (&dict));
+
+failure:
+  g_warning ("Failed to parse record");
+  g_variant_dict_clear (&dict);
+  if (endptr)
+    *endptr = NULL;
+  return NULL;
+}
+
+GVariant *
+mi2_util_parse_list (const gchar  *line,
+                     const gchar **endptr)
+{
+  GVariantBuilder builder;
+
+  g_return_val_if_fail (line != NULL, NULL);
+  g_return_val_if_fail (*line == '[', NULL);
+
+  /* move past [ */
+  line++;
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
+
+  if (*line != ']')
+    {
+      while (*line != ']')
+        {
+          if (*line == '"')
+            {
+              g_autofree gchar *value = NULL;
+
+              if (!(value = mi2_util_parse_string (line, &line)))
+                goto failure;
+
+              g_variant_builder_open (&builder, G_VARIANT_TYPE ("v"));
+              g_variant_builder_add (&builder, "s", value);
+              g_variant_builder_close (&builder);
+            }
+          else if (*line == '{')
+            {
+              g_autoptr(GVariant) v = NULL;
+
+              if (!(v = mi2_util_parse_record (line, &line)))
+                goto failure;
+
+              g_variant_builder_open (&builder, G_VARIANT_TYPE ("v"));
+              g_variant_builder_add_value (&builder, v);
+              g_variant_builder_close (&builder);
+            }
+          else if (*line == '[')
+            {
+              g_autoptr(GVariant) ar = NULL;
+
+              if (!(ar = mi2_util_parse_list (line, &line)))
+                goto failure;
+
+              g_variant_builder_open (&builder, G_VARIANT_TYPE ("v"));
+              g_variant_builder_add_value (&builder, ar);
+              g_variant_builder_close (&builder);
+            }
+          else
+            goto failure;
+
+
+          if (*line == ',')
+            line++;
+        }
+    }
+
+  g_assert (*line == ']');
+
+  line++;
+
+  if (endptr)
+    *endptr = line;
+
+  return g_variant_ref_sink (g_variant_builder_end (&builder));
+
+failure:
+  g_warning ("Failed to parse list");
+  g_variant_builder_clear (&builder);
+  if (endptr)
+    *endptr = NULL;
+  return NULL;
+}
diff --git a/contrib/mi2/mi2-util.h b/contrib/mi2/mi2-util.h
new file mode 100644
index 0000000..e4bd582
--- /dev/null
+++ b/contrib/mi2/mi2-util.h
@@ -0,0 +1,37 @@
+/* mi2-util.h
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MI2_UTIL_H
+#define MI2_UTIL_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+gchar    *mi2_util_parse_string (const gchar  *line,
+                                 const gchar **endptr);
+gchar    *mi2_util_parse_word   (const gchar  *line,
+                                 const gchar **endptr);
+GVariant *mi2_util_parse_record (const gchar  *line,
+                                 const gchar **endptr);
+GVariant *mi2_util_parse_list   (const gchar  *line,
+                                 const gchar **endptr);
+
+G_END_DECLS
+
+#endif /* MI2_UTIL_H */
diff --git a/contrib/mi2/test-client.c b/contrib/mi2/test-client.c
new file mode 100644
index 0000000..2c60dfb
--- /dev/null
+++ b/contrib/mi2/test-client.c
@@ -0,0 +1,305 @@
+/* test-client.c
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <fcntl.h>
+#include <gio/gunixinputstream.h>
+#include <gio/gunixoutputstream.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "mi2-client.h"
+#include "mi2-error.h"
+
+static GMainLoop *main_loop;
+static gint g_breakpoint_id;
+
+static gchar *
+open_pty (gint *out_master_fd,
+          gint *out_slave_fd)
+{
+  gint master_fd;
+  gint slave_fd;
+#ifdef HAVE_PTSNAME_R
+  char name[PATH_MAX + 1];
+#else
+  char *name;
+#endif
+
+  master_fd = posix_openpt (O_NOCTTY | O_RDWR);
+  grantpt (master_fd);
+  unlockpt (master_fd);
+
+#ifdef HAVE_PTSNAME_R
+  ptsname_r (master_fd, name, sizeof name - 1);
+  name[sizeof name - 1] = '\0';
+#else
+  name = ptsname (master_fd);
+#endif
+
+  slave_fd = open (name, O_RDWR | O_CLOEXEC);
+
+  *out_slave_fd = slave_fd;
+  *out_master_fd = master_fd;
+
+  g_assert_cmpstr (name, ==, ttyname (slave_fd));
+
+  return g_strdup (name);
+}
+
+static GIOStream *
+create_io_stream_to_gdb (void)
+{
+  g_autoptr(GSubprocess) subprocess = NULL;
+  g_autoptr(GIOStream) io_stream = NULL;
+  g_autoptr(GInputStream) input = NULL;
+  g_autoptr(GOutputStream) output = NULL;
+  g_autoptr(GError) error = NULL;
+
+  subprocess = g_subprocess_new (G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE,
+                                 &error,
+                                 "gdb", "--interpreter", "mi2", "ls",
+                                 NULL);
+  g_assert_no_error (error);
+  g_assert (subprocess);
+
+  input = g_subprocess_get_stdout_pipe (subprocess);
+  output = g_subprocess_get_stdin_pipe (subprocess);
+  io_stream = g_simple_io_stream_new (input, output);
+
+  g_subprocess_wait_async (subprocess, NULL, NULL, NULL);
+
+  return g_steal_pointer (&io_stream);
+}
+
+static void
+log_handler (Mi2Client   *client,
+             const gchar *log)
+{
+  g_print ("%s", log);
+}
+
+static void
+thread_group_added (Mi2Client       *client,
+                    Mi2EventMessage *message,
+                    gpointer         user_data)
+{
+}
+
+static void
+event (Mi2Client       *client,
+       Mi2EventMessage *message,
+       gpointer         user_data)
+{
+  g_print ("EVENT: %s\n", mi2_event_message_get_name (message));
+}
+
+static void
+breakpoint_cb (GObject      *object,
+               GAsyncResult *result,
+               gpointer      user_data)
+{
+  Mi2Client *client = (Mi2Client *)object;
+  g_autoptr(GError) error = NULL;
+  gboolean r;
+
+  g_assert (MI2_IS_CLIENT (client));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  r = mi2_client_insert_breakpoint_finish (client, result, &error);
+  g_assert_no_error (error);
+  g_assert_cmpint (r, ==, TRUE);
+}
+
+static void
+remove_breakpoint_cb (GObject      *object,
+                      GAsyncResult *result,
+                      gpointer      user_data)
+{
+  Mi2Client *client = (Mi2Client *)object;
+  g_autoptr(GError) error = NULL;
+  gboolean r;
+
+  g_assert (MI2_IS_CLIENT (client));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  r = mi2_client_remove_breakpoint_finish (client, result, &error);
+  g_assert_no_error (error);
+  g_assert_cmpint (r, ==, TRUE);
+}
+
+static void
+stack_info_frame_cb (GObject      *object,
+                     GAsyncResult *result,
+                     gpointer      user_data)
+{
+  Mi2Client *client = (Mi2Client *)object;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(Mi2Breakpoint) breakpoint = NULL;
+  gboolean r;
+
+  g_assert (MI2_IS_CLIENT (client));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  r = mi2_client_exec_finish (client, result, NULL, &error);
+  g_assert_error (error, MI2_ERROR, MI2_ERROR_UNKNOWN_ERROR);
+  g_assert_cmpstr (error->message, ==, "No registers.");
+  g_assert_cmpint (r, ==, FALSE);
+
+  breakpoint = mi2_breakpoint_new ();
+  mi2_breakpoint_set_function (breakpoint, "main");
+
+  mi2_client_insert_breakpoint_async (client,
+                                      breakpoint,
+                                      NULL,
+                                      breakpoint_cb,
+                                      NULL);
+}
+
+static void
+on_stopped (Mi2Client     *client,
+            Mi2StopReason  reason,
+            Mi2Message    *message,
+            gpointer       user_data)
+{
+  g_assert (MI2_IS_CLIENT (client));
+  g_assert (MI2_IS_MESSAGE (message));
+
+  g_print ("stopped %d %s\n", reason, mi2_message_get_param_string (message, "reason"));
+
+  if (reason == MI2_STOP_BREAKPOINT_HIT)
+    mi2_client_continue_async (client, FALSE, NULL, NULL, NULL);
+  else
+    mi2_client_remove_breakpoint_async (client,
+                                        g_breakpoint_id,
+                                        NULL,
+                                        remove_breakpoint_cb,
+                                        NULL);
+}
+
+static void
+run_cb (GObject      *object,
+        GAsyncResult *result,
+        gpointer      user_data)
+{
+  Mi2Client *client = (Mi2Client *)object;
+  g_autoptr(GError) error = NULL;
+  gboolean r;
+
+  r = mi2_client_run_finish (client, result, &error);
+  g_assert_no_error (error);
+  g_assert_cmpint (r, ==, TRUE);
+}
+
+static void
+on_breakpoint_inserted (Mi2Client     *client,
+                        Mi2Breakpoint *breakpoint,
+                        gpointer       user_data)
+{
+  g_breakpoint_id = mi2_breakpoint_get_id (breakpoint);
+  g_print ("breakpoint added: %d\n", g_breakpoint_id);
+
+  mi2_client_run_async (client, NULL, run_cb, NULL);
+}
+
+static void
+on_breakpoint_removed (Mi2Client *client,
+                       gint       breakpoint_id,
+                       gpointer   user_data)
+{
+  g_print ("breakpoint removed: %d\n", breakpoint_id);
+
+  g_print ("Shutting down client\n");
+  mi2_client_shutdown_async (client, NULL, NULL, NULL);
+}
+
+static void
+tty_done (GObject      *object,
+          GAsyncResult *result,
+          gpointer      user_data)
+{
+  Mi2Client *client = (Mi2Client *)object;
+  g_autoptr(GError) error = NULL;
+  gboolean r;
+
+  g_assert (MI2_IS_CLIENT (client));
+
+  r = mi2_client_exec_finish (client, result, NULL, &error);
+  g_assert_no_error (error);
+  g_assert (r);
+
+  mi2_client_exec_async (client,
+                         /* converted to -stack-info-frame */
+                         "stack-info-frame",
+                         NULL,
+                         stack_info_frame_cb,
+                         NULL);
+}
+
+static void
+listen_done_cb (GObject      *object,
+                GAsyncResult *result,
+                gpointer      user_data)
+{
+  g_autoptr(GError) error = NULL;
+  gboolean r;
+
+  g_print ("Listen operation completed\n");
+
+  r = mi2_client_listen_finish (MI2_CLIENT (object), result, &error);
+  g_assert_no_error (error);
+  g_assert_cmpint (r, ==, TRUE);
+
+  g_main_loop_quit (main_loop);
+}
+
+gint
+main (gint argc,
+      gchar *argv[])
+{
+  g_autoptr(Mi2Client) client = NULL;
+  g_autoptr(GIOStream) io_stream = NULL;
+  g_autofree gchar *path = NULL;
+  g_autofree gchar *cmd = NULL;
+  gint master_fd;
+  gint slave_fd;
+
+  main_loop = g_main_loop_new (NULL, FALSE);
+  io_stream = create_io_stream_to_gdb ();
+  client = mi2_client_new (io_stream);
+
+  path = open_pty (&master_fd, &slave_fd);
+
+  g_signal_connect (client, "log", G_CALLBACK (log_handler), NULL);
+  g_signal_connect (client, "event::thread-group-added", G_CALLBACK (thread_group_added), NULL);
+  g_signal_connect (client, "event", G_CALLBACK (event), NULL);
+  g_signal_connect (client, "stopped", G_CALLBACK (on_stopped), NULL);
+  g_signal_connect (client, "breakpoint-inserted", G_CALLBACK (on_breakpoint_inserted), NULL);
+  g_signal_connect (client, "breakpoint-removed", G_CALLBACK (on_breakpoint_removed), NULL);
+
+  mi2_client_listen_async (client, NULL, listen_done_cb, NULL);
+
+  cmd = g_strdup_printf ("-gdb-set inferior-tty %s", path);
+  mi2_client_exec_async (client, cmd, NULL, tty_done, NULL);
+
+  g_main_loop_run (main_loop);
+  g_main_loop_unref (main_loop);
+
+  return 0;
+}
diff --git a/contrib/mi2/test-stream.c b/contrib/mi2/test-stream.c
new file mode 100644
index 0000000..34f31ca
--- /dev/null
+++ b/contrib/mi2/test-stream.c
@@ -0,0 +1,132 @@
+/* test-stream.c
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mi2-console-message.h"
+#include "mi2-input-stream.h"
+#include "mi2-output-stream.h"
+
+static GMainLoop *main_loop;
+
+static void
+read_message_cb (GObject      *object,
+                 GAsyncResult *result,
+                 gpointer      user_data)
+{
+  Mi2InputStream *stream = (Mi2InputStream *)object;
+  g_autoptr(Mi2Message) message = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (MI2_IS_INPUT_STREAM (stream));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  message = mi2_input_stream_read_message_finish (stream, result, &error);
+  g_assert_no_error (error);
+
+  if (message != NULL)
+    mi2_input_stream_read_message_async (stream, NULL, read_message_cb, NULL);
+  else
+    g_main_loop_quit (main_loop);
+}
+
+static void
+test_basic_read (void)
+{
+  g_autoptr(Mi2InputStream) stream = NULL;
+  g_autoptr(GInputStream) base_stream = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GFile) file = NULL;
+
+  main_loop = g_main_loop_new (NULL, FALSE);
+
+  file = g_file_new_for_path (TEST_DATA_DIR"/test-stream-1.txt");
+
+  base_stream = G_INPUT_STREAM (g_file_read (file, NULL, NULL));
+  g_assert_no_error (error);
+  g_assert (base_stream != NULL);
+
+  stream = mi2_input_stream_new (base_stream);
+  g_assert_no_error (error);
+  g_assert (stream != NULL);
+
+  mi2_input_stream_read_message_async (stream, NULL, read_message_cb, NULL);
+
+  g_main_loop_run (main_loop);
+  g_clear_pointer (&main_loop, g_main_loop_unref);
+}
+
+static void
+write_message_cb (GObject      *object,
+                  GAsyncResult *result,
+                  gpointer      user_data)
+{
+  Mi2OutputStream *stream = (Mi2OutputStream *)object;
+  g_autoptr(GError) error = NULL;
+  gboolean r;
+
+  g_assert (MI2_IS_OUTPUT_STREAM (stream));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  r = mi2_output_stream_write_message_finish (stream, result, &error);
+  g_assert_no_error (error);
+  g_assert (r);
+
+  g_main_loop_quit (main_loop);
+}
+
+static void
+test_basic_write (void)
+{
+  g_autoptr(Mi2OutputStream) stream = NULL;
+  g_autoptr(GOutputStream) base_stream = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(Mi2Message) message = NULL;
+  const gchar *str;
+
+  main_loop = g_main_loop_new (NULL, FALSE);
+
+  base_stream = g_memory_output_stream_new_resizable ();
+  g_assert_no_error (error);
+  g_assert (base_stream != NULL);
+
+  stream = mi2_output_stream_new (base_stream);
+  g_assert_no_error (error);
+  g_assert (stream != NULL);
+
+  message = g_object_new (MI2_TYPE_CONSOLE_MESSAGE,
+                          "message", "this is a test message",
+                          NULL);
+
+  mi2_output_stream_write_message_async (stream, message, NULL, write_message_cb, NULL);
+
+  g_main_loop_run (main_loop);
+
+  str = g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (base_stream));
+  g_assert_cmpstr (str, ==, "~\"this is a test message\"\n");
+
+  g_clear_pointer (&main_loop, g_main_loop_unref);
+}
+
+gint
+main (gint   argc,
+      gchar *argv[])
+{
+  g_test_init (&argc, &argv, NULL);
+  g_test_add_func ("/Mi2/InputStream/read_message_async", test_basic_read);
+  g_test_add_func ("/Mi2/InputStream/write_message_async", test_basic_write);
+  return g_test_run ();
+}
diff --git a/meson.build b/meson.build
index 596af82..0e5c46b 100644
--- a/meson.build
+++ b/meson.build
@@ -262,6 +262,7 @@ subdir('data/gsettings')
 subdir('data/icons')
 subdir('data/style-schemes')
 subdir('contrib/libeditorconfig')
+subdir('contrib/mi2')
 subdir('contrib/xml')
 subdir('contrib/gstyle') # Depends on libxml
 subdir('libide')


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]