Merge remote-tracking branch 'cros/upstream' into 'cros/master'
diff --git a/.gitignore b/.gitignore
index 8cb5c2a..1c814dc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,12 @@
 *.lo
 *.la
 *.bz2
+*.swp
+tags
+.*DS_Store
+.deps
+.libs
+.__autoconf_trace_data
 
 ChangeLog
 INSTALL
@@ -65,6 +71,7 @@
 src/libmbim-glib/test/test-fragment
 src/libmbim-glib/test/test-message-parser
 src/libmbim-glib/test/test-message-builder
+src/libmbim-glib/test/test-proxy-helpers
 src/libmbim-glib/test/*.log
 src/libmbim-glib/test/*.trs
 
@@ -78,4 +85,6 @@
 src/mbimcli/.libs
 src/mbimcli/mbimcli
 
-utils/mbim-network
\ No newline at end of file
+utils/mbim-network
+
+src/mbim-proxy/mbim-proxy
diff --git a/NEWS b/NEWS
index ce7936e..6b19cc9 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,30 @@
 
+Overview of changes in libmbim 1.10
+----------------------------------------
+
+ * API break: Flag values in 'MbimRegistrationFlag' were updated to match the
+   ones in the MBIM documentation.
+
+ * Implemented a new 'mbim-proxy', which allows sharing a single MBIM control
+   port among different processes. The usage of the proxy is optional, and can
+   be requested by specifying the MBIM_DEVICE_OPEN_FLAGS_PROXY flag in the new
+   mbim_device_open_full() method. The 'mbimcli' command line tool was also
+   extended with a new '--device-open-proxy,-p' option, to allow requesting the
+   use of the proxy process.
+
+ * New 'removed' signal added to the MbimDevice, to notify when the underlying
+   connection to the device is lost (e.g. lost connection to the mbim-proxy, or
+   lost access to the MBIM control port).
+
+ * Added support for registering and using custom services.
+
+ * Added additional GMM cause codes to MbimNwError.
+
+ * Transactions are now matched not only by ID but also by type.
+
+ * Several other minor improvements and fixes.
+
+
 Overview of changes in libmbim 1.8
 ----------------------------------------
 
diff --git a/configure.ac b/configure.ac
index 56370aa..27f82c9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3,7 +3,7 @@
 
 dnl The libmbim version number
 m4_define([mbim_major_version], [1])
-m4_define([mbim_minor_version], [9])
+m4_define([mbim_minor_version], [11])
 m4_define([mbim_micro_version], [0])
 m4_define([mbim_version],
           [mbim_major_version.mbim_minor_version.mbim_micro_version])
@@ -16,9 +16,9 @@
 dnl            with old code), increment a.
 dnl        If the interface has changed in an incompatible way (that is,
 dnl            functions have changed or been removed), then zero a.
-m4_define([mbim_glib_lt_current],  [3])
+m4_define([mbim_glib_lt_current],  [4])
 m4_define([mbim_glib_lt_revision], [0])
-m4_define([mbim_glib_lt_age],      [3])
+m4_define([mbim_glib_lt_age],      [0])
 
 
 AC_INIT([libmbim], [mbim_version], [libmbim-devel@lists.freedesktop.org])
@@ -69,6 +69,7 @@
                   glib-2.0 >= 2.32
                   gobject-2.0
                   gio-2.0
+                  gio-unix-2.0
                   gudev-1.0 >= 147)
 AC_SUBST(LIBMBIM_GLIB_CFLAGS)
 AC_SUBST(LIBMBIM_GLIB_LIBS)
@@ -81,6 +82,14 @@
 AC_SUBST(MBIMCLI_CFLAGS)
 AC_SUBST(MBIMCLI_LIBS)
 
+dnl General dependencies for mbim-proxy
+PKG_CHECK_MODULES(MBIMPROXY,
+                  glib-2.0 >= 2.32
+                  gobject-2.0
+                  gio-2.0)
+AC_SUBST(MBIMPROXY_CFLAGS)
+AC_SUBST(MBIMPROXY_LIBS)
+
 GLIB_MKENUMS=`$PKG_CONFIG --variable=glib_mkenums glib-2.0`
 AC_SUBST(GLIB_MKENUMS)
 
@@ -104,6 +113,7 @@
                  src/libmbim-glib/generated/Makefile
                  src/libmbim-glib/test/Makefile
                  src/mbimcli/Makefile
+                 src/mbim-proxy/Makefile
                  utils/Makefile
                  docs/Makefile
                  docs/reference/Makefile
diff --git a/data/Makefile.am b/data/Makefile.am
index f6820cc..111e028 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -9,4 +9,5 @@
 	mbim-service-stk.json \
 	mbim-service-dss.json \
 	mbim-service-ms-firmware-id.json \
-	mbim-service-ms-host-shutdown.json
+	mbim-service-ms-host-shutdown.json \
+	mbim-service-proxy-control.json
diff --git a/data/mbim-service-proxy-control.json b/data/mbim-service-proxy-control.json
new file mode 100644
index 0000000..2379e79
--- /dev/null
+++ b/data/mbim-service-proxy-control.json
@@ -0,0 +1,17 @@
+
+[
+  // *********************************************************************************
+  { "type" : "Service",
+    "name" : "Proxy Control" },
+
+  // *********************************************************************************
+  { "name"     : "Configuration",
+    "service"  : "Proxy Control",
+    "type"     : "Command",
+    "set"      : [ { "name"   : "DevicePath",
+                     "format" : "string" },
+                   { "name"   : "Timeout",
+                     "format" : "guint32" } ],
+    "response" : [] }
+
+]
diff --git a/docs/reference/libmbim-glib/libmbim-glib-common.sections b/docs/reference/libmbim-glib/libmbim-glib-common.sections
index a1d1103..5c3af88 100644
--- a/docs/reference/libmbim-glib/libmbim-glib-common.sections
+++ b/docs/reference/libmbim-glib/libmbim-glib-common.sections
@@ -22,6 +22,7 @@
 MBIM_UUID_DSS
 MBIM_UUID_MS_FIRMWARE_ID
 MBIM_UUID_MS_HOST_SHUTDOWN
+MBIM_UUID_PROXY_CONTROL
 <SUBSECTION Methods>
 mbim_service_get_string
 mbim_service_lookup_name
@@ -53,6 +54,7 @@
 MbimCidDss
 MbimCidMsFirmwareId
 MbimCidMsHostShutdown
+MbimCidProxyControl
 <SUBSECTION Methods>
 mbim_cid_can_set
 mbim_cid_can_query
@@ -67,6 +69,7 @@
 mbim_cid_dss_get_string
 mbim_cid_ms_firmware_id_get_string
 mbim_cid_ms_host_shutdown_get_string
+mbim_cid_proxy_control_get_string
 <SUBSECTION Private>
 mbim_cid_basic_connect_build_string_from_mask
 mbim_cid_sms_build_string_from_mask
@@ -77,6 +80,7 @@
 mbim_cid_dss_build_string_from_mask
 mbim_cid_ms_firmware_id_build_string_from_mask
 mbim_cid_ms_host_shutdown_build_string_from_mask
+mbim_cid_proxy_control_build_string_from_mask
 <SUBSECTION Standard>
 MBIM_TYPE_CID_AUTH
 MBIM_TYPE_CID_BASIC_CONNECT
@@ -87,6 +91,7 @@
 MBIM_TYPE_CID_USSD
 MBIM_TYPE_CID_MS_FIRMWARE_ID
 MBIM_TYPE_CID_MS_HOST_SHUTDOWN
+MBIM_TYPE_CID_PROXY_CONTROL
 mbim_cid_auth_get_type
 mbim_cid_basic_connect_get_type
 mbim_cid_dss_get_type
@@ -96,6 +101,7 @@
 mbim_cid_ussd_get_type
 mbim_cid_ms_firmware_id_get_type
 mbim_cid_ms_host_shutdown_get_type
+mbim_cid_proxy_control_get_type
 </SECTION>
 
 <SECTION>
@@ -154,6 +160,10 @@
 mbim_message_indicate_status_get_cid
 mbim_message_indicate_status_get_raw_information_buffer
 <SUBSECTION Private>
+mbim_message_open_done_new
+mbim_message_close_done_new
+mbim_message_proxy_control_configuration_response_parse
+mbim_message_proxy_control_configuration_set_new
 mbim_message_type_build_string_from_mask
 mbim_message_command_type_build_string_from_mask
 <SUBSECTION Standard>
@@ -164,6 +174,12 @@
 <SECTION>
 <FILE>mbim-device</FILE>
 <TITLE>MbimDevice</TITLE>
+MBIM_DEVICE_FILE
+MBIM_DEVICE_IN_SESSION
+MBIM_DEVICE_TRANSACTION_ID
+MBIM_DEVICE_SIGNAL_REMOVED
+MBIM_DEVICE_SIGNAL_INDICATE_STATUS
+MBIM_DEVICE_SIGNAL_ERROR
 MbimDevice
 mbim_device_new
 mbim_device_new_finish
@@ -174,6 +190,9 @@
 mbim_device_is_open
 mbim_device_open
 mbim_device_open_finish
+MbimDeviceOpenFlags
+mbim_device_open_full
+mbim_device_open_full_finish
 mbim_device_close
 mbim_device_close_finish
 mbim_device_close_force
@@ -182,11 +201,6 @@
 mbim_device_command_finish
 <SUBSECTION Private>
 MbimDeviceClass
-MBIM_DEVICE_FILE
-MBIM_DEVICE_IN_SESSION
-MBIM_DEVICE_TRANSACTION_ID
-MBIM_DEVICE_SIGNAL_INDICATE_STATUS
-MBIM_DEVICE_SIGNAL_ERROR
 <SUBSECTION Standard>
 MBIM_DEVICE
 MBIM_DEVICE_CLASS
@@ -199,6 +213,28 @@
 </SECTION>
 
 <SECTION>
+<FILE>mbim-proxy</FILE>
+<TITLE>MbimProxy</TITLE>
+MBIM_PROXY_SOCKET_PATH
+MBIM_PROXY_N_CLIENTS
+MBIM_PROXY_N_DEVICES
+MbimProxy
+mbim_proxy_new
+mbim_proxy_get_n_clients
+mbim_proxy_get_n_devices
+<SUBSECTION Standard>
+MbimProxyClass
+MBIM_PROXY
+MBIM_PROXY_CLASS
+MBIM_PROXY_GET_CLASS
+MBIM_IS_PROXY
+MBIM_IS_PROXY_CLASS
+MBIM_TYPE_PROXY
+MbimProxyPrivate
+mbim_proxy_get_type
+</SECTION>
+
+<SECTION>
 <FILE>mbim-enums</FILE>
 MbimDeviceType
 MbimCellularClass
diff --git a/docs/reference/libmbim-glib/libmbim-glib-docs.xml b/docs/reference/libmbim-glib/libmbim-glib-docs.xml
index b709ef3..1765137 100644
--- a/docs/reference/libmbim-glib/libmbim-glib-docs.xml
+++ b/docs/reference/libmbim-glib/libmbim-glib-docs.xml
@@ -44,13 +44,14 @@
     <xi:include href="xml/mbim-cid.xml"/>
     <xi:include href="xml/mbim-message.xml"/>
     <xi:include href="xml/mbim-device.xml"/>
+    <xi:include href="xml/mbim-proxy.xml"/>
     <xi:include href="xml/mbim-enums.xml"/>
     <xi:include href="xml/mbim-errors.xml"/>
     <xi:include href="xml/mbim-utils.xml"/>
   </chapter>
 
   <chapter>
-    <title>Services</title>
+    <title>Generic Services</title>
     <xi:include href="xml/mbim-basic-connect.xml"/>
     <xi:include href="xml/mbim-sms.xml"/>
     <xi:include href="xml/mbim-ussd.xml"/>
@@ -58,6 +59,10 @@
     <xi:include href="xml/mbim-phonebook.xml"/>
     <xi:include href="xml/mbim-stk.xml"/>
     <xi:include href="xml/mbim-dss.xml"/>
+  </chapter>
+
+  <chapter>
+    <title>Other Services</title>
     <xi:include href="xml/mbim-ms-firmware-id.xml"/>
     <xi:include href="xml/mbim-ms-host-shutdown.xml"/>
   </chapter>
diff --git a/src/Makefile.am b/src/Makefile.am
index 7de85b4..6732c9e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,2 +1,2 @@
 
-SUBDIRS = libmbim-glib mbimcli
+SUBDIRS = libmbim-glib mbimcli mbim-proxy
diff --git a/src/libmbim-glib/Makefile.am b/src/libmbim-glib/Makefile.am
index 0988a28..4f8bbf3 100644
--- a/src/libmbim-glib/Makefile.am
+++ b/src/libmbim-glib/Makefile.am
@@ -12,6 +12,7 @@
 	-I$(top_builddir)/src/libmbim-glib \
 	-I$(top_builddir)/src/libmbim-glib/generated \
 	-DLIBMBIM_GLIB_COMPILATION \
+	-DLIBEXEC_PATH=\""$(libexecdir)"\" \
 	-DG_LOG_DOMAIN=\"Mbim\"
 libmbim_glib_core_la_SOURCES = \
 	mbim-version.h \
@@ -22,7 +23,9 @@
 	mbim-cid.h mbim-cid.c \
 	mbim-message-private.h mbim-message.h mbim-message.c \
 	mbim-device.h mbim-device.c \
-	mbim-compat.h mbim-compat.c
+	mbim-compat.h mbim-compat.c \
+	mbim-proxy.h mbim-proxy.c \
+	mbim-proxy-helpers.h mbim-proxy-helpers.c
 
 # Final installable library
 lib_LTLIBRARIES = libmbim-glib.la
@@ -50,7 +53,8 @@
 	mbim-cid.h \
 	mbim-message.h \
 	mbim-device.h \
-	mbim-compat.h
+	mbim-compat.h \
+	mbim-proxy.h
 
 EXTRA_DIST = \
 	mbim-version.h.in
diff --git a/src/libmbim-glib/generated/Makefile.am b/src/libmbim-glib/generated/Makefile.am
index 43a79aa..ce81a71 100644
--- a/src/libmbim-glib/generated/Makefile.am
+++ b/src/libmbim-glib/generated/Makefile.am
@@ -11,7 +11,8 @@
 	mbim-stk.h \
 	mbim-dss.h \
 	mbim-ms-firmware-id.h \
-	mbim-ms-host-shutdown.h
+	mbim-ms-host-shutdown.h \
+	mbim-proxy-control.h
 
 GENERATED_C = \
 	mbim-error-types.c \
@@ -25,7 +26,8 @@
 	mbim-stk.c \
 	mbim-dss.c \
 	mbim-ms-firmware-id.c \
-	mbim-ms-host-shutdown.c
+	mbim-ms-host-shutdown.c \
+	mbim-proxy-control.c
 
 GENERATED_SECTIONS = \
 	mbim-basic-connect.sections \
@@ -36,7 +38,8 @@
 	mbim-stk.sections \
 	mbim-dss.sections \
 	mbim-ms-firmware-id.sections \
-	mbim-ms-host-shutdown.sections
+	mbim-ms-host-shutdown.sections \
+	mbim-proxy-control.sections
 
 # Error types
 mbim-error-types.h: $(top_srcdir)/src/libmbim-glib/mbim-errors.h $(top_srcdir)/build-aux/templates/mbim-error-types-template.h
@@ -158,6 +161,15 @@
 			--input $(top_srcdir)/data/mbim-service-ms-host-shutdown.json \
 			--output mbim-ms-host-shutdown
 
+# Proxy Control service
+mbim-proxy-control.h mbim-proxy-control.c mbim-proxy-control.sections: $(top_srcdir)/data/mbim-service-proxy-control.json $(top_srcdir)/build-aux/mbim-codegen/*.py $(top_srcdir)/build-aux/mbim-codegen/mbim-codegen
+	$(AM_V_GEN)  \
+		rm -f mbim-proxy-control.h && \
+		rm -f mbim-proxy-control.c && \
+		$(top_srcdir)/build-aux/mbim-codegen/mbim-codegen \
+			--input $(top_srcdir)/data/mbim-service-proxy-control.json \
+			--output mbim-proxy-control
+
 BUILT_SOURCES = $(GENERATED_H) $(GENERATED_C)
 
 nodist_libmbim_glib_generated_la_SOURCES = \
diff --git a/src/libmbim-glib/libmbim-glib.h b/src/libmbim-glib/libmbim-glib.h
index 0cfe560..b41122b 100644
--- a/src/libmbim-glib/libmbim-glib.h
+++ b/src/libmbim-glib/libmbim-glib.h
@@ -34,6 +34,7 @@
 #include "mbim-message.h"
 #include "mbim-device.h"
 #include "mbim-enums.h"
+#include "mbim-proxy.h"
 
 /* generated */
 #include "mbim-enum-types.h"
diff --git a/src/libmbim-glib/mbim-cid.c b/src/libmbim-glib/mbim-cid.c
index 5ce53e2..50dc4f1 100644
--- a/src/libmbim-glib/mbim-cid.c
+++ b/src/libmbim-glib/mbim-cid.c
@@ -126,6 +126,12 @@
     { TRUE,  FALSE, FALSE }, /* MBIM_CID_MS_HOST_SHUTDOWN_NOTIFY */
 };
 
+/* Note: index of the array is CID-1 */
+#define MBIM_CID_PROXY_CONTROL_LAST MBIM_CID_PROXY_CONTROL_CONFIGURATION
+static const CidConfig cid_proxy_control_config [MBIM_CID_PROXY_CONTROL_LAST] = {
+    { TRUE,  FALSE, FALSE }, /* MBIM_CID_PROXY_CONTROL_CONFIGURATION */
+};
+
 /**
  * mbim_cid_can_set:
  * @service: a #MbimService.
@@ -143,7 +149,7 @@
     g_return_val_if_fail (cid > 0, FALSE);
     /* Known service required */
     g_return_val_if_fail (service > MBIM_SERVICE_INVALID, FALSE);
-    g_return_val_if_fail (service <= MBIM_SERVICE_MS_HOST_SHUTDOWN, FALSE);
+    g_return_val_if_fail (service <= MBIM_SERVICE_PROXY_CONTROL, FALSE);
 
     switch (service) {
     case MBIM_SERVICE_BASIC_CONNECT:
@@ -164,6 +170,8 @@
         return cid_ms_firmware_id_config[cid - 1].set;
     case MBIM_SERVICE_MS_HOST_SHUTDOWN:
         return cid_ms_host_shutdown_config[cid - 1].set;
+    case MBIM_SERVICE_PROXY_CONTROL:
+        return cid_proxy_control_config[cid - 1].set;
     default:
         g_assert_not_reached ();
         return FALSE;
@@ -187,7 +195,7 @@
     g_return_val_if_fail (cid > 0, FALSE);
     /* Known service required */
     g_return_val_if_fail (service > MBIM_SERVICE_INVALID, FALSE);
-    g_return_val_if_fail (service <= MBIM_SERVICE_MS_HOST_SHUTDOWN, FALSE);
+    g_return_val_if_fail (service <= MBIM_SERVICE_PROXY_CONTROL, FALSE);
 
     switch (service) {
     case MBIM_SERVICE_BASIC_CONNECT:
@@ -208,6 +216,8 @@
         return cid_ms_firmware_id_config[cid - 1].query;
     case MBIM_SERVICE_MS_HOST_SHUTDOWN:
         return cid_ms_host_shutdown_config[cid - 1].query;
+    case MBIM_SERVICE_PROXY_CONTROL:
+        return cid_proxy_control_config[cid - 1].query;
     default:
         g_assert_not_reached ();
         return FALSE;
@@ -231,7 +241,7 @@
     g_return_val_if_fail (cid > 0, FALSE);
     /* Known service required */
     g_return_val_if_fail (service > MBIM_SERVICE_INVALID, FALSE);
-    g_return_val_if_fail (service <= MBIM_SERVICE_MS_HOST_SHUTDOWN, FALSE);
+    g_return_val_if_fail (service <= MBIM_SERVICE_PROXY_CONTROL, FALSE);
 
     switch (service) {
     case MBIM_SERVICE_BASIC_CONNECT:
@@ -252,7 +262,8 @@
         return cid_ms_firmware_id_config[cid - 1].notify;
     case MBIM_SERVICE_MS_HOST_SHUTDOWN:
         return cid_ms_host_shutdown_config[cid - 1].notify;
-
+    case MBIM_SERVICE_PROXY_CONTROL:
+        return cid_proxy_control_config[cid - 1].notify;
     default:
         g_assert_not_reached ();
         return FALSE;
@@ -277,7 +288,7 @@
     g_return_val_if_fail (cid > 0, NULL);
     /* Known service required */
     g_return_val_if_fail (service > MBIM_SERVICE_INVALID, NULL);
-    g_return_val_if_fail (service <= MBIM_SERVICE_MS_HOST_SHUTDOWN, NULL);
+    g_return_val_if_fail (service <= MBIM_SERVICE_PROXY_CONTROL, NULL);
 
     switch (service) {
     case MBIM_SERVICE_BASIC_CONNECT:
@@ -298,6 +309,8 @@
         return mbim_cid_ms_firmware_id_get_string (cid);
     case MBIM_SERVICE_MS_HOST_SHUTDOWN:
         return mbim_cid_ms_host_shutdown_get_string (cid);
+    case MBIM_SERVICE_PROXY_CONTROL:
+        return mbim_cid_proxy_control_get_string (cid);
     default:
         g_assert_not_reached ();
         return FALSE;
diff --git a/src/libmbim-glib/mbim-cid.h b/src/libmbim-glib/mbim-cid.h
index cd50fc5..3315340 100644
--- a/src/libmbim-glib/mbim-cid.h
+++ b/src/libmbim-glib/mbim-cid.h
@@ -207,6 +207,18 @@
     MBIM_CID_MS_HOST_SHUTDOWN_NOTIFY  = 1
 } MbimCidMsHostShutdown;
 
+/**
+ * MbimCidProxyControl:
+ * @MBIM_CID_PROXY_CONTROL_UNKNOWN: Unknown command.
+ * @MBIM_CID_PROXY_CONTROL_CONFIGURATION: Configuration.
+ *
+ * MBIM commands in the %MBIM_SERVICE_PROXY_CONTROL service.
+ */
+typedef enum {
+    MBIM_CID_PROXY_CONTROL_UNKNOWN       = 0,
+    MBIM_CID_PROXY_CONTROL_CONFIGURATION = 1
+} MbimCidProxyControl;
+
 /* Command helpers */
 
 gboolean     mbim_cid_can_set       (MbimService service,
diff --git a/src/libmbim-glib/mbim-device.c b/src/libmbim-glib/mbim-device.c
index 3ce9c09..086ae4e 100644
--- a/src/libmbim-glib/mbim-device.c
+++ b/src/libmbim-glib/mbim-device.c
@@ -18,6 +18,7 @@
  * Boston, MA 02110-1301 USA.
  *
  * Copyright (C) 2013-2014 Aleksander Morgado <aleksander@aleksander.es>
+ * Copyright (C) 2014 Smith Micro Software, Inc.
  *
  * Implementation based on the 'QmiDevice' GObject from libqmi-glib.
  */
@@ -28,6 +29,7 @@
 #include <termios.h>
 #include <unistd.h>
 #include <gio/gio.h>
+#include <gio/gunixsocketaddress.h>
 #include <gudev/gudev.h>
 #include <sys/ioctl.h>
 #define IOCTL_WDM_MAX_COMMAND _IOR('H', 0xA0, guint16)
@@ -37,6 +39,8 @@
 #include "mbim-message.h"
 #include "mbim-message-private.h"
 #include "mbim-error-types.h"
+#include "mbim-proxy.h"
+#include "mbim-proxy-control.h"
 
 /**
  * SECTION:mbim-device
@@ -67,6 +71,7 @@
 enum {
     SIGNAL_INDICATE_STATUS,
     SIGNAL_ERROR,
+    SIGNAL_REMOVED,
     SIGNAL_LAST
 };
 
@@ -78,6 +83,12 @@
     TRANSACTION_TYPE_LAST  = 2
 } TransactionType;
 
+typedef enum {
+    OPEN_STATUS_CLOSED  = 0,
+    OPEN_STATUS_OPENING = 1,
+    OPEN_STATUS_OPEN    = 2
+} OpenStatus;
+
 struct _MbimDevicePrivate {
     /* File */
     GFile *file;
@@ -88,6 +99,11 @@
     GIOChannel *iochannel;
     guint watch_id;
     GByteArray *response;
+    OpenStatus open_status;
+
+    /* Support for mbim-proxy */
+    GSocketClient *socket_client;
+    GSocketConnection *socket_connection;
 
     /* HT to keep track of ongoing host/function transactions
      *  Host transactions:  created by us
@@ -105,6 +121,7 @@
     guint16 max_control_transfer;
 };
 
+#define MAX_SPAWN_RETRIES             10
 #define MAX_CONTROL_TRANSFER          4096
 #define MAX_TIME_BETWEEN_FRAGMENTS_MS 1250
 
@@ -215,6 +232,10 @@
                                      ctx->type,
                                      MBIM_MESSAGE_TYPE_INVALID,
                                      ctx->transaction_id);
+    if (!tr)
+        /* transaction already completed */
+        return FALSE;
+
     tr->timeout_id = 0;
 
     /* If no fragment was received, complete transaction with a timeout error */
@@ -389,7 +410,7 @@
 {
     g_return_val_if_fail (MBIM_IS_DEVICE (self), FALSE);
 
-    return !!self->priv->iochannel;
+    return (self->priv->open_status == OPEN_STATUS_OPEN);
 }
 
 /*****************************************************************************/
@@ -569,7 +590,7 @@
     case MBIM_MESSAGE_TYPE_COMMAND:
     case MBIM_MESSAGE_TYPE_HOST_ERROR:
         /* Shouldn't expect host-generated messages as replies */
-        g_warning ("[%s] Host-generated message received: ignoring",
+        g_message ("[%s] Host-generated message received: ignoring",
                    self->priv->path_display);
         return;
     }
@@ -620,6 +641,7 @@
             g_byte_array_remove_range (self->priv->response, 0, self->priv->response->len);
 
         mbim_device_close_force (self, NULL);
+        g_signal_emit (self, signals[SIGNAL_REMOVED], 0 );
         return FALSE;
     }
 
@@ -795,94 +817,256 @@
     return max;
 }
 
+typedef struct {
+    MbimDevice *self;
+    GSimpleAsyncResult *result;
+    guint spawn_retries;
+} CreateIoChannelContext;
+
+static void
+create_iochannel_context_complete_and_free (CreateIoChannelContext *ctx)
+{
+    g_simple_async_result_complete_in_idle (ctx->result);
+    g_object_unref (ctx->result);
+    g_object_unref (ctx->self);
+    g_slice_free (CreateIoChannelContext, ctx);
+}
+
 static gboolean
-create_iochannel (MbimDevice *self,
-                  GError **error)
+create_iochannel_finish (MbimDevice *self,
+                         GAsyncResult *res,
+                         GError **error)
+{
+    return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void
+setup_iochannel (CreateIoChannelContext *ctx)
 {
     GError *inner_error = NULL;
+
+    /* We don't want UTF-8 encoding, we're playing with raw binary data */
+    g_io_channel_set_encoding (ctx->self->priv->iochannel, NULL, NULL);
+
+    /* We don't want to get the channel buffered */
+    g_io_channel_set_buffered (ctx->self->priv->iochannel, FALSE);
+
+    /* Let the GIOChannel own the FD */
+    g_io_channel_set_close_on_unref (ctx->self->priv->iochannel, TRUE);
+
+    /* We don't want to get blocked while writing stuff */
+    if (!g_io_channel_set_flags (ctx->self->priv->iochannel,
+                                 G_IO_FLAG_NONBLOCK,
+                                 &inner_error)) {
+        g_simple_async_result_take_error (ctx->result, inner_error);
+        g_io_channel_shutdown (ctx->self->priv->iochannel, FALSE, NULL);
+        g_io_channel_unref (ctx->self->priv->iochannel);
+        ctx->self->priv->iochannel = NULL;
+        g_clear_object (&ctx->self->priv->socket_connection);
+        g_clear_object (&ctx->self->priv->socket_client);
+        create_iochannel_context_complete_and_free (ctx);
+        return;
+    }
+
+    ctx->self->priv->watch_id = g_io_add_watch (ctx->self->priv->iochannel,
+                                                G_IO_IN | G_IO_ERR | G_IO_HUP,
+                                                (GIOFunc)data_available,
+                                                ctx->self);
+
+    g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+    create_iochannel_context_complete_and_free (ctx);
+}
+
+static void
+create_iochannel_with_fd (CreateIoChannelContext *ctx)
+{
     gint fd;
     guint16 max;
 
-    if (self->priv->iochannel) {
-        g_set_error_literal (error,
-                             MBIM_CORE_ERROR,
-                             MBIM_CORE_ERROR_WRONG_STATE,
-                             "Already open");
-        return FALSE;
-    }
-
-    g_assert (self->priv->file);
-    g_assert (self->priv->path);
-
     errno = 0;
-    fd = open (self->priv->path, O_RDWR | O_EXCL | O_NONBLOCK | O_NOCTTY);
+    fd = open (ctx->self->priv->path, O_RDWR | O_EXCL | O_NONBLOCK | O_NOCTTY);
     if (fd < 0) {
-        g_set_error (error,
-                     MBIM_CORE_ERROR,
-                     MBIM_CORE_ERROR_FAILED,
-                     "Cannot open device file '%s': %s",
-                     self->priv->path_display,
-                     strerror (errno));
-        return FALSE;
+        g_simple_async_result_set_error (
+            ctx->result,
+            MBIM_CORE_ERROR,
+            MBIM_CORE_ERROR_FAILED,
+            "Cannot open device file '%s': %s",
+            ctx->self->priv->path_display,
+            strerror (errno));
+        create_iochannel_context_complete_and_free (ctx);
+        return;
     }
 
     /* Query message size */
     if (ioctl (fd, IOCTL_WDM_MAX_COMMAND, &max) < 0) {
         g_debug ("[%s] Couldn't query maximum message size: "
                  "IOCTL_WDM_MAX_COMMAND failed: %s",
-                 self->priv->path_display,
+                 ctx->self->priv->path_display,
                  strerror (errno));
         /* Fallback, try to read the descriptor file */
-        max = read_max_control_transfer (self);
+        max = read_max_control_transfer (ctx->self);
     } else {
         g_debug ("[%s] Queried max control message size: %" G_GUINT16_FORMAT,
-                 self->priv->path_display,
+                 ctx->self->priv->path_display,
                  max);
     }
-    self->priv->max_control_transfer = max;
+    ctx->self->priv->max_control_transfer = max;
 
     /* Create new GIOChannel */
-    self->priv->iochannel = g_io_channel_unix_new (fd);
+    ctx->self->priv->iochannel = g_io_channel_unix_new (fd);
 
-    /* We don't want UTF-8 encoding, we're playing with raw binary data */
-    g_io_channel_set_encoding (self->priv->iochannel, NULL, NULL);
+    setup_iochannel (ctx);
+}
 
-    /* We don't want to get the channel buffered */
-    g_io_channel_set_buffered (self->priv->iochannel, FALSE);
+static void create_iochannel_with_socket (CreateIoChannelContext *ctx);
 
-    /* Let the GIOChannel own the FD */
-    g_io_channel_set_close_on_unref (self->priv->iochannel, TRUE);
+static gboolean
+wait_for_proxy_cb (CreateIoChannelContext *ctx)
+{
+    create_iochannel_with_socket (ctx);
+    return FALSE;
+}
 
-    /* We don't want to get blocked while writing stuff */
-    if (!g_io_channel_set_flags (self->priv->iochannel,
-                                 G_IO_FLAG_NONBLOCK,
-                                 &inner_error)) {
-        g_prefix_error (&inner_error, "Cannot set non-blocking channel: ");
-        g_propagate_error (error, inner_error);
-        g_io_channel_shutdown (self->priv->iochannel, FALSE, NULL);
-        g_io_channel_unref (self->priv->iochannel);
-        self->priv->iochannel = NULL;
-        return FALSE;
+static void
+create_iochannel_with_socket (CreateIoChannelContext *ctx)
+{
+    GSocketAddress *socket_address;
+    GError *error = NULL;
+
+    /* Create socket client */
+    if (ctx->self->priv->socket_client)
+        g_object_unref (ctx->self->priv->socket_client);
+    ctx->self->priv->socket_client = g_socket_client_new ();
+    g_socket_client_set_family (ctx->self->priv->socket_client, G_SOCKET_FAMILY_UNIX);
+    g_socket_client_set_socket_type (ctx->self->priv->socket_client, G_SOCKET_TYPE_STREAM);
+    g_socket_client_set_protocol (ctx->self->priv->socket_client, G_SOCKET_PROTOCOL_DEFAULT);
+
+    /* Setup socket address */
+    socket_address = (g_unix_socket_address_new_with_type (
+                          MBIM_PROXY_SOCKET_PATH,
+                          -1,
+                          G_UNIX_SOCKET_ADDRESS_ABSTRACT));
+
+    /* Connect to address */
+    if (ctx->self->priv->socket_connection)
+        g_object_unref (ctx->self->priv->socket_connection);
+    ctx->self->priv->socket_connection = (g_socket_client_connect (
+                                              ctx->self->priv->socket_client,
+                                              G_SOCKET_CONNECTABLE (socket_address),
+                                              NULL,
+                                              &error));
+    g_object_unref (socket_address);
+
+    if (!ctx->self->priv->socket_connection) {
+        gchar **argc;
+
+        g_debug ("cannot connect to proxy: %s", error->message);
+        g_clear_error (&error);
+        g_clear_object (&ctx->self->priv->socket_client);
+
+        /* Don't retry forever */
+        ctx->spawn_retries++;
+        if (ctx->spawn_retries > MAX_SPAWN_RETRIES) {
+            g_simple_async_result_set_error (ctx->result,
+                                             MBIM_CORE_ERROR,
+                                             MBIM_CORE_ERROR_FAILED,
+                                             "Couldn't spawn the mbim-proxy");
+            create_iochannel_context_complete_and_free (ctx);
+            return;
+        }
+
+        g_debug ("spawning new mbim-proxy (try %u)...", ctx->spawn_retries);
+
+        argc = g_new0 (gchar *, 2);
+        argc[0] = g_strdup (LIBEXEC_PATH "/mbim-proxy");
+        if (!g_spawn_async (NULL, /* working directory */
+                            argc,
+                            NULL, /* envp */
+                            G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
+                            NULL, /* child_setup */
+                            NULL, /* child_setup_user_data */
+                            NULL,
+                            &error)) {
+            g_debug ("error spawning mbim-proxy: %s", error->message);
+            g_clear_error (&error);
+        }
+        g_strfreev (argc);
+
+        /* Wait some ms and retry */
+        g_timeout_add (100, (GSourceFunc)wait_for_proxy_cb, ctx);
+        return;
     }
 
-    self->priv->watch_id = g_io_add_watch (self->priv->iochannel,
-                                           G_IO_IN | G_IO_ERR | G_IO_HUP,
-                                           (GIOFunc)data_available,
-                                           self);
+    ctx->self->priv->iochannel = g_io_channel_unix_new (
+                                     g_socket_get_fd (
+                                         g_socket_connection_get_socket (ctx->self->priv->socket_connection)));
 
-    return !!self->priv->iochannel;
+    /* try to read the descriptor file */
+    ctx->self->priv->max_control_transfer = read_max_control_transfer (ctx->self);
+
+    setup_iochannel (ctx);
 }
 
+static void
+create_iochannel (MbimDevice           *self,
+                  gboolean              proxy,
+                  GAsyncReadyCallback   callback,
+                  gpointer              user_data)
+{
+    CreateIoChannelContext *ctx;
+
+    ctx = g_slice_new (CreateIoChannelContext);
+    ctx->self = g_object_ref (self);
+    ctx->result = g_simple_async_result_new (G_OBJECT (self),
+                                             callback,
+                                             user_data,
+                                             create_iochannel);
+    ctx->spawn_retries = 0;
+
+    if (self->priv->iochannel) {
+        g_simple_async_result_set_error (ctx->result,
+                                         MBIM_CORE_ERROR,
+                                         MBIM_CORE_ERROR_WRONG_STATE,
+                                         "Already open");
+        create_iochannel_context_complete_and_free (ctx);
+        return;
+    }
+
+    g_assert (self->priv->file);
+    g_assert (self->priv->path);
+
+    if (proxy)
+        create_iochannel_with_socket (ctx);
+    else
+        create_iochannel_with_fd (ctx);
+}
+
+typedef enum {
+    DEVICE_OPEN_CONTEXT_STEP_FIRST = 0,
+    DEVICE_OPEN_CONTEXT_STEP_CREATE_IOCHANNEL,
+    DEVICE_OPEN_CONTEXT_STEP_FLAGS_PROXY,
+    DEVICE_OPEN_CONTEXT_STEP_OPEN_MESSAGE,
+    DEVICE_OPEN_CONTEXT_STEP_LAST
+} DeviceOpenContextStep;
+
 typedef struct {
     MbimDevice *self;
     GSimpleAsyncResult *result;
     GCancellable *cancellable;
+    DeviceOpenContextStep step;
+    MbimDeviceOpenFlags flags;
     guint timeout;
 } DeviceOpenContext;
 
 static void
-device_open_context_complete_and_free (DeviceOpenContext *ctx)
+device_open_context_complete_and_free (DeviceOpenContext *ctx,
+                                       GError            *error)
 {
+    if (error)
+        g_simple_async_result_take_error (ctx->result, error);
+    else
+        g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+
     g_simple_async_result_complete_in_idle (ctx->result);
     g_object_unref (ctx->result);
     if (ctx->cancellable)
@@ -891,6 +1075,26 @@
     g_slice_free (DeviceOpenContext, ctx);
 }
 
+static void device_open_context_step (DeviceOpenContext *ctx);
+
+/**
+ * mbim_device_open_full_finish:
+ * @self: a #MbimDevice.
+ * @res: a #GAsyncResult.
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an asynchronous open operation started with mbim_device_open_full().
+ *
+ * Returns: %TRUE if successful, %FALSE if @error is set.
+ */
+gboolean
+mbim_device_open_full_finish (MbimDevice    *self,
+                              GAsyncResult  *res,
+                              GError       **error)
+{
+    return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
 /**
  * mbim_device_open_finish:
  * @self: a #MbimDevice.
@@ -903,10 +1107,10 @@
  */
 gboolean
 mbim_device_open_finish (MbimDevice   *self,
-                        GAsyncResult  *res,
-                        GError       **error)
+                         GAsyncResult  *res,
+                         GError       **error)
 {
-    return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+    return mbim_device_open_full_finish (self, res, error);
 }
 
 static void open_message (DeviceOpenContext *ctx);
@@ -934,15 +1138,25 @@
             /* No more seconds left in the timeout... return error */
         }
 
-        g_simple_async_result_take_error (ctx->result, error);
-    } else if (!mbim_message_open_done_get_result (response, &error))
-        g_simple_async_result_take_error (ctx->result, error);
-    else
-        g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+        g_debug ("open operation timed out: closed");
+        self->priv->open_status = OPEN_STATUS_CLOSED;
+        device_open_context_complete_and_free (ctx, error);
+        return;
+    }
 
-    if (response)
+    if (!mbim_message_open_done_get_result (response, &error)) {
+        g_debug ("getting open done result failed: closed");
+        self->priv->open_status = OPEN_STATUS_CLOSED;
+        device_open_context_complete_and_free (ctx, error);
         mbim_message_unref (response);
-    device_open_context_complete_and_free (ctx);
+        return;
+    }
+
+    mbim_message_unref (response);
+
+    /* go on */
+    ctx->step++;
+    device_open_context_step (ctx);
 }
 
 static void
@@ -962,6 +1176,181 @@
     mbim_message_unref (request);
 }
 
+static void
+proxy_cfg_message_ready (MbimDevice        *self,
+                         GAsyncResult      *res,
+                         DeviceOpenContext *ctx)
+{
+    MbimMessage *response;
+    GError *error = NULL;
+
+    response = mbim_device_command_finish (self, res, &error);
+    if (!response) {
+        /* Hard error if proxy cfg command fails */
+        g_debug ("proxy configuration failed: closed");
+        self->priv->open_status = OPEN_STATUS_CLOSED;
+        device_open_context_complete_and_free (ctx, error);
+        return;
+    }
+
+    mbim_message_unref (response);
+
+    ctx->step++;
+    device_open_context_step (ctx);
+}
+
+static void
+proxy_cfg_message (DeviceOpenContext *ctx)
+{
+    GError *error = NULL;
+    MbimMessage *request;
+
+    request = mbim_message_proxy_control_configuration_set_new (ctx->self->priv->path, ctx->timeout, &error);
+
+    /* This message is no longer a direct reply; as the proxy will also try to open the device
+     * directly. If it cannot open the device, it will return an error. */
+    mbim_device_command (ctx->self,
+                         request,
+                         ctx->timeout,
+                         ctx->cancellable,
+                         (GAsyncReadyCallback)proxy_cfg_message_ready,
+                         ctx);
+    mbim_message_unref (request);
+}
+
+static void
+create_iochannel_ready (MbimDevice *self,
+                        GAsyncResult *res,
+                        DeviceOpenContext *ctx)
+{
+    GError *error = NULL;
+
+    if (!create_iochannel_finish (self, res, &error)) {
+        g_debug ("creating iochannel failed: closed");
+        self->priv->open_status = OPEN_STATUS_CLOSED;
+        device_open_context_complete_and_free (ctx, error);
+        return;
+    }
+
+    /* Go on */
+    ctx->step++;
+    device_open_context_step (ctx);
+}
+
+static void
+device_open_context_step (DeviceOpenContext *ctx)
+{
+    switch (ctx->step) {
+    case DEVICE_OPEN_CONTEXT_STEP_FIRST:
+        if (ctx->self->priv->open_status == OPEN_STATUS_OPEN) {
+            GError *error;
+
+            error = g_error_new (MBIM_CORE_ERROR,
+                                 MBIM_CORE_ERROR_WRONG_STATE,
+                                 "Already open");
+            device_open_context_complete_and_free (ctx, error);
+            return;
+        }
+
+        if (ctx->self->priv->open_status == OPEN_STATUS_OPENING) {
+            GError *error;
+
+            error = g_error_new (MBIM_CORE_ERROR,
+                                 MBIM_CORE_ERROR_WRONG_STATE,
+                                 "Already opening");
+            device_open_context_complete_and_free (ctx, error);
+            return;
+        }
+
+        g_debug ("opening device...");
+        g_assert (ctx->self->priv->open_status == OPEN_STATUS_CLOSED);
+        ctx->self->priv->open_status = OPEN_STATUS_OPENING;
+
+        ctx->step++;
+        /* Fall down */
+
+    case DEVICE_OPEN_CONTEXT_STEP_CREATE_IOCHANNEL:
+        create_iochannel (ctx->self,
+                          !!(ctx->flags & MBIM_DEVICE_OPEN_FLAGS_PROXY),
+                          (GAsyncReadyCallback)create_iochannel_ready,
+                          ctx);
+        return;
+
+    case DEVICE_OPEN_CONTEXT_STEP_FLAGS_PROXY:
+        if (ctx->flags & MBIM_DEVICE_OPEN_FLAGS_PROXY) {
+            proxy_cfg_message (ctx);
+            return;
+        }
+        ctx->step++;
+        /* Fall down */
+
+    case DEVICE_OPEN_CONTEXT_STEP_OPEN_MESSAGE:
+        /* If the device is already in-session, avoid the open message */
+        if (!ctx->self->priv->in_session) {
+            open_message (ctx);
+            return;
+        }
+        ctx->step++;
+        /* Fall down */
+
+    case DEVICE_OPEN_CONTEXT_STEP_LAST:
+        /* Nothing else to process, complete without error */
+        ctx->self->priv->open_status = OPEN_STATUS_OPEN;
+        device_open_context_complete_and_free (ctx, NULL);
+        return;
+
+    default:
+        break;
+    }
+
+    g_assert_not_reached ();
+}
+
+/**
+ * mbim_device_open_full:
+ * @self: a #MbimDevice.
+ * @flags: a set of #MbimDeviceOpenFlags.
+ * @timeout: maximum time, in seconds, to wait for the device to be opened.
+ * @cancellable: optional #GCancellable object, #NULL to ignore.
+ * @callback: a #GAsyncReadyCallback to call when the operation is finished.
+ * @user_data: the data to pass to callback function.
+ *
+ * Asynchronously opens a #MbimDevice for I/O.
+ *
+ * This method is an extension of the generic mbim_device_open(), which allows
+ * launching the #MbimDevice with proxy support.
+ *
+ * When the operation is finished @callback will be called. You can then call
+ * mbim_device_open_full_finish() to get the result of the operation.
+ */
+void
+mbim_device_open_full (MbimDevice          *self,
+                       MbimDeviceOpenFlags  flags,
+                       guint                timeout,
+                       GCancellable        *cancellable,
+                       GAsyncReadyCallback  callback,
+                       gpointer             user_data)
+{
+    DeviceOpenContext *ctx;
+
+    g_return_if_fail (MBIM_IS_DEVICE (self));
+    g_return_if_fail (timeout > 0);
+
+    ctx = g_slice_new (DeviceOpenContext);
+    ctx->self = g_object_ref (self);
+    ctx->result = g_simple_async_result_new (G_OBJECT (self),
+                                             callback,
+                                             user_data,
+                                             mbim_device_open_full);
+    ctx->step = DEVICE_OPEN_CONTEXT_STEP_FIRST;
+    ctx->flags = flags;
+    ctx->timeout = timeout;
+    ctx->cancellable = (cancellable ? g_object_ref (cancellable) : NULL);
+
+    /* Start processing */
+    device_open_context_step (ctx);
+}
+
 /**
  * mbim_device_open:
  * @self: a #MbimDevice.
@@ -982,37 +1371,12 @@
                   GAsyncReadyCallback  callback,
                   gpointer             user_data)
 {
-    DeviceOpenContext *ctx;
-    GError *error = NULL;
-
-    g_return_if_fail (MBIM_IS_DEVICE (self));
-    g_return_if_fail (timeout > 0);
-
-    ctx = g_slice_new (DeviceOpenContext);
-    ctx->self = g_object_ref (self);
-    ctx->result = g_simple_async_result_new (G_OBJECT (self),
-                                             callback,
-                                             user_data,
-                                             mbim_device_open);
-    ctx->timeout = timeout;
-    ctx->cancellable = (cancellable ? g_object_ref (cancellable) : NULL);
-
-    if (!create_iochannel (self, &error)) {
-        g_prefix_error (&error,
-                        "Cannot open MBIM device: ");
-        g_simple_async_result_take_error (ctx->result, error);
-        device_open_context_complete_and_free (ctx);
-        return;
-    }
-
-    /* If the device is already in-session, avoid the open message */
-    if (self->priv->in_session) {
-        g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
-        device_open_context_complete_and_free (ctx);
-        return;
-    }
-
-    open_message (ctx);
+    mbim_device_open_full (self,
+                           MBIM_DEVICE_OPEN_FLAGS_NONE,
+                           timeout,
+                           cancellable,
+                           callback,
+                           user_data);
 }
 
 /*****************************************************************************/
@@ -1024,11 +1388,21 @@
 {
     GError *inner_error = NULL;
 
-    g_io_channel_shutdown (self->priv->iochannel, TRUE, &inner_error);
+    self->priv->open_status = OPEN_STATUS_CLOSED;
+
+    /* Already closed? */
+    if (!self->priv->iochannel && !self->priv->socket_connection && !self->priv->socket_client)
+        return TRUE;
+
+    if (self->priv->iochannel) {
+        g_io_channel_shutdown (self->priv->iochannel, TRUE, &inner_error);
+        g_io_channel_unref (self->priv->iochannel);
+        self->priv->iochannel = NULL;
+    }
 
     /* Failures when closing still make the device to get closed */
-    g_io_channel_unref (self->priv->iochannel);
-    self->priv->iochannel = NULL;
+    g_clear_object (&self->priv->socket_connection);
+    g_clear_object (&self->priv->socket_client);
 
     if (self->priv->watch_id) {
         g_source_remove (self->priv->watch_id);
@@ -1063,10 +1437,6 @@
 {
     g_return_val_if_fail (MBIM_IS_DEVICE (self), FALSE);
 
-    /* Already closed? */
-    if (!self->priv->iochannel)
-        return TRUE;
-
     return destroy_iochannel (self, error);
 }
 
@@ -1740,6 +2110,7 @@
 
     /* Initialize transaction ID */
     self->priv->transaction_id = 0x01;
+    self->priv->open_status = OPEN_STATUS_CLOSED;
 }
 
 static void
@@ -1749,6 +2120,8 @@
 
     g_clear_object (&self->priv->file);
 
+    destroy_iochannel (self, NULL);
+
     G_OBJECT_CLASS (mbim_device_parent_class)->dispose (object);
 }
 
@@ -1764,17 +2137,12 @@
         if (self->priv->transactions[i]) {
             g_assert (g_hash_table_size (self->priv->transactions[i]) == 0);
             g_hash_table_unref (self->priv->transactions[i]);
+            self->priv->transactions[i] = NULL;
         }
     }
 
     g_free (self->priv->path);
     g_free (self->priv->path_display);
-    if (self->priv->watch_id)
-        g_source_remove (self->priv->watch_id);
-    if (self->priv->response)
-        g_byte_array_unref (self->priv->response);
-    if (self->priv->iochannel)
-        g_io_channel_unref (self->priv->iochannel);
 
     G_OBJECT_CLASS (mbim_device_parent_class)->finalize (object);
 }
@@ -1861,4 +2229,22 @@
                       G_TYPE_NONE,
                       1,
                       G_TYPE_ERROR);
+
+  /**
+   * MbimDevice::device-removed:
+   * @self: the #MbimDevice
+   * @message: None
+   *
+   * The ::device-removed signal is emitted when an unexpected port hang-up is received.
+   */
+    signals[SIGNAL_REMOVED] =
+        g_signal_new (MBIM_DEVICE_SIGNAL_REMOVED,
+                      G_OBJECT_CLASS_TYPE (G_OBJECT_CLASS (klass)),
+                      G_SIGNAL_RUN_LAST,
+                      0,
+                      NULL,
+                      NULL,
+                      NULL,
+                      G_TYPE_NONE,
+                      0);
 }
diff --git a/src/libmbim-glib/mbim-device.h b/src/libmbim-glib/mbim-device.h
index 67b1b1a..c1a2241 100644
--- a/src/libmbim-glib/mbim-device.h
+++ b/src/libmbim-glib/mbim-device.h
@@ -51,6 +51,7 @@
 
 #define MBIM_DEVICE_SIGNAL_INDICATE_STATUS "device-indicate-status"
 #define MBIM_DEVICE_SIGNAL_ERROR           "device-error"
+#define MBIM_DEVICE_SIGNAL_REMOVED         "device-removed"
 
 /**
  * MbimDevice:
@@ -84,6 +85,28 @@
 const gchar *mbim_device_get_path_display (MbimDevice *self);
 gboolean     mbim_device_is_open          (MbimDevice *self);
 
+/**
+ * MbimDeviceOpenFlags:
+ * @MBIM_DEVICE_OPEN_FLAGS_NONE: None.
+ * @MBIM_DEVICE_OPEN_FLAGS_PROXY: Try to open the port through the 'mbim-proxy'.
+ *
+ * Flags to specify which actions to be performed when the device is open.
+ */
+typedef enum {
+    MBIM_DEVICE_OPEN_FLAGS_NONE  = 0,
+    MBIM_DEVICE_OPEN_FLAGS_PROXY = 1 << 0
+} MbimDeviceOpenFlags;
+
+void     mbim_device_open_full        (MbimDevice           *self,
+                                       MbimDeviceOpenFlags   flags,
+                                       guint                 timeout,
+                                       GCancellable         *cancellable,
+                                       GAsyncReadyCallback   callback,
+                                       gpointer              user_data);
+gboolean mbim_device_open_full_finish (MbimDevice           *self,
+                                       GAsyncResult         *res,
+                                       GError              **error);
+
 void     mbim_device_open        (MbimDevice           *self,
                                   guint                 timeout,
                                   GCancellable         *cancellable,
diff --git a/src/libmbim-glib/mbim-message-private.h b/src/libmbim-glib/mbim-message-private.h
index 5d7bffe..b5606e0 100644
--- a/src/libmbim-glib/mbim-message-private.h
+++ b/src/libmbim-glib/mbim-message-private.h
@@ -129,6 +129,10 @@
 } __attribute__((packed));
 
 /*****************************************************************************/
+/* Message creation */
+GByteArray *_mbim_message_allocate (MbimMessageType message_type, guint32 transaction_id, guint32 additional_size);
+
+/*****************************************************************************/
 /* Fragment interface */
 
 gboolean      _mbim_message_is_fragment          (const MbimMessage  *self);
diff --git a/src/libmbim-glib/mbim-message.c b/src/libmbim-glib/mbim-message.c
index 45d0a44..48f6018 100644
--- a/src/libmbim-glib/mbim-message.c
+++ b/src/libmbim-glib/mbim-message.c
@@ -62,7 +62,7 @@
 
 /*****************************************************************************/
 
-static GByteArray *
+GByteArray *
 _mbim_message_allocate (MbimMessageType message_type,
                         guint32         transaction_id,
                         guint32         additional_size)
@@ -1007,12 +1007,7 @@
 mbim_message_set_transaction_id (MbimMessage *self,
                                  guint32      transaction_id)
 {
-    /* Only allow setting transaction ID in host-generated messages */
     g_return_if_fail (self != NULL);
-    g_return_if_fail (MBIM_MESSAGE_GET_MESSAGE_TYPE (self) == MBIM_MESSAGE_TYPE_COMMAND ||
-                      MBIM_MESSAGE_GET_MESSAGE_TYPE (self) == MBIM_MESSAGE_TYPE_OPEN ||
-                      MBIM_MESSAGE_GET_MESSAGE_TYPE (self) == MBIM_MESSAGE_TYPE_CLOSE ||
-                      MBIM_MESSAGE_GET_MESSAGE_TYPE (self) == MBIM_MESSAGE_TYPE_HOST_ERROR);
 
     ((struct header *)(self->data))->transaction_id = GUINT32_TO_LE (transaction_id);
 }
@@ -1518,6 +1513,32 @@
 /* 'Open Done' message interface */
 
 /**
+ * mbim_message_open_done_new:
+ * @transaction_id: transaction ID.
+ * @error_status_code: a #MbimStatusError.
+ *
+ * Create a new #MbimMessage of type %MBIM_MESSAGE_TYPE_OPEN_DONE with the specified
+ * parameters.
+ *
+ * Returns: (transfer full): a newly created #MbimMessage, which should be freed with mbim_message_unref().
+ */
+MbimMessage *
+mbim_message_open_done_new (guint32         transaction_id,
+                            MbimStatusError error_status_code)
+{
+    GByteArray *self;
+
+    self = _mbim_message_allocate (MBIM_MESSAGE_TYPE_OPEN_DONE,
+                                   transaction_id,
+                                   sizeof (struct open_done_message));
+
+    /* Open header */
+    ((struct full_message *)(self->data))->message.open_done.status_code = GUINT32_TO_LE (error_status_code);
+
+    return (MbimMessage *)self;
+}
+
+/**
  * mbim_message_open_done_get_status_code:
  * @self: a #MbimMessage.
  *
@@ -1588,6 +1609,32 @@
 /* 'Close Done' message interface */
 
 /**
+ * mbim_message_close_done_new:
+ * @transaction_id: transaction ID.
+ * @error_status_code: a #MbimStatusError.
+ *
+ * Create a new #MbimMessage of type %MBIM_MESSAGE_TYPE_CLOSE_DONE with the specified
+ * parameters.
+ *
+ * Returns: (transfer full): a newly created #MbimMessage, which should be freed with mbim_message_unref().
+ */
+MbimMessage *
+mbim_message_close_done_new (guint32         transaction_id,
+                             MbimStatusError error_status_code)
+{
+    GByteArray *self;
+
+    self = _mbim_message_allocate (MBIM_MESSAGE_TYPE_CLOSE_DONE,
+                                   transaction_id,
+                                   sizeof (struct close_done_message));
+
+    /* Open header */
+    ((struct full_message *)(self->data))->message.close_done.status_code = GUINT32_TO_LE (error_status_code);
+
+    return (MbimMessage *)self;
+}
+
+/**
  * mbim_message_close_done_get_status_code:
  * @self: a #MbimMessage.
  *
@@ -1738,7 +1785,7 @@
 
     /* Known service required */
     g_return_val_if_fail (service > MBIM_SERVICE_INVALID, FALSE);
-    g_return_val_if_fail (service <= MBIM_SERVICE_MS_HOST_SHUTDOWN || mbim_service_id_is_custom (service), FALSE);
+    g_return_val_if_fail (service <= MBIM_SERVICE_PROXY_CONTROL, FALSE);
     service_id = mbim_uuid_from_service (service);
 
     self = _mbim_message_allocate (MBIM_MESSAGE_TYPE_COMMAND,
diff --git a/src/libmbim-glib/mbim-message.h b/src/libmbim-glib/mbim-message.h
index 1c6892d..f07a807 100644
--- a/src/libmbim-glib/mbim-message.h
+++ b/src/libmbim-glib/mbim-message.h
@@ -131,9 +131,11 @@
 /*****************************************************************************/
 /* 'Open Done' message interface */
 
-MbimStatusError mbim_message_open_done_get_status_code (const MbimMessage  *self);
-gboolean        mbim_message_open_done_get_result      (const MbimMessage  *self,
-                                                        GError            **error);
+MbimMessage     *mbim_message_open_done_new (guint32         transaction_id,
+                                             MbimStatusError error_status_code);
+MbimStatusError  mbim_message_open_done_get_status_code (const MbimMessage  *self);
+gboolean         mbim_message_open_done_get_result      (const MbimMessage  *self,
+                                                         GError            **error);
 
 /*****************************************************************************/
 /* 'Close' message interface */
@@ -143,9 +145,11 @@
 /*****************************************************************************/
 /* 'Close Done' message interface */
 
-MbimStatusError mbim_message_close_done_get_status_code (const MbimMessage  *self);
-gboolean        mbim_message_close_done_get_result      (const MbimMessage  *self,
-                                                         GError            **error);
+MbimMessage     *mbim_message_close_done_new (guint32         transaction_id,
+                                              MbimStatusError error_status_code);
+MbimStatusError  mbim_message_close_done_get_status_code (const MbimMessage  *self);
+gboolean         mbim_message_close_done_get_result      (const MbimMessage  *self,
+                                                          GError            **error);
 
 /*****************************************************************************/
 /* 'Error' message interface */
diff --git a/src/libmbim-glib/mbim-proxy-helpers.c b/src/libmbim-glib/mbim-proxy-helpers.c
new file mode 100644
index 0000000..d0cd0d2
--- /dev/null
+++ b/src/libmbim-glib/mbim-proxy-helpers.c
@@ -0,0 +1,283 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/*
+ * libmbim-glib -- GLib/GIO based library to control MBIM devices
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2014 Aleksander Morgado <aleksander@aleksander.es>
+ * Copyright (C) 2014 Smith Micro Software, Inc.
+ */
+
+#include <string.h>
+#include "mbim-proxy-helpers.h"
+#include "mbim-message-private.h"
+#include "mbim-message.h"
+#include "mbim-cid.h"
+#include "mbim-uuid.h"
+
+/*****************************************************************************/
+
+static gboolean
+cmp_event_entry_contents (const MbimEventEntry *in,
+                          const MbimEventEntry *out)
+{
+    guint i, o;
+
+    g_assert (mbim_uuid_cmp (&(in->device_service_id), &(out->device_service_id)));
+
+    /* First, compare number of cids in the array */
+    if (in->cids_count != out->cids_count)
+        return FALSE;
+
+    if (in->cids_count == 0)
+        g_assert (in->cids == NULL);
+    if (out->cids_count == 0)
+        g_assert (out->cids == NULL);
+
+    for (i = 0; i < in->cids_count; i++) {
+        for (o = 0; o < out->cids_count; o++) {
+            if (in->cids[i] == out->cids[o])
+                break;
+        }
+        if (o == out->cids_count)
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+gboolean
+_mbim_proxy_helper_service_subscribe_list_cmp (const MbimEventEntry * const *a,
+                                               gsize                         a_size,
+                                               const MbimEventEntry * const *b,
+                                               gsize                         b_size)
+{
+    gsize i, o;
+
+    /* First, compare number of entries a the array */
+    if (a_size != b_size)
+        return FALSE;
+
+    /* Now compare each service one by one */
+    for (i = 0; i < a_size; i++) {
+        /* Look for this same service a the other array */
+        for (o = 0; o < b_size; o++) {
+            /* When service found, compare contents */
+            if (mbim_uuid_cmp (&(a[i]->device_service_id), &(b[o]->device_service_id))) {
+                if (!cmp_event_entry_contents (a[i], b[o]))
+                    return FALSE;
+                break;
+            }
+        }
+        /* Service not found! */
+        if (!b[o])
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+void
+_mbim_proxy_helper_service_subscribe_list_debug (const MbimEventEntry * const *list,
+                                                 gsize                         list_size)
+{
+    gsize i;
+
+    for (i = 0; i < list_size; i++) {
+        const MbimEventEntry *entry = list[i];
+        MbimService service;
+        gchar *str;
+
+        service = mbim_uuid_to_service (&entry->device_service_id);
+        str = mbim_uuid_get_printable (&entry->device_service_id);
+        g_debug ("[service %u] %s (%s)",
+                 (guint)i, str, mbim_service_lookup_name (service));
+        g_free (str);
+
+        if (entry->cids_count == 0)
+            g_debug ("[service %u] all CIDs enabled", (guint)i);
+        else {
+            guint j;
+
+            g_debug ("[service %u] %u CIDs enabled", (guint)i, entry->cids_count);;
+            for (j = 0; j < entry->cids_count; j++) {
+                const gchar *cid_str;
+
+                cid_str = mbim_cid_get_printable (service, entry->cids[j]);
+                g_debug ("[service %u] [cid %u] %u (%s)",
+                         (guint)i, j, entry->cids[j], cid_str ? cid_str : "unknown");
+            }
+        }
+    }
+}
+
+/*****************************************************************************/
+
+MbimEventEntry **
+_mbim_proxy_helper_service_subscribe_standard_list_new (gsize *out_size)
+{
+    gsize i;
+    MbimService service;
+    MbimEventEntry **out;
+
+    g_assert (out_size != NULL);
+
+    out = g_new0 (MbimEventEntry *, 1 + (MBIM_SERVICE_DSS - MBIM_SERVICE_BASIC_CONNECT + 1));
+
+    for (service = MBIM_SERVICE_BASIC_CONNECT, i = 0;
+         service <= MBIM_SERVICE_DSS;
+         service++, i++) {
+         out[i] = g_new0 (MbimEventEntry, 1);
+         memcpy (&out[i]->device_service_id, mbim_uuid_from_service (service), sizeof (MbimUuid));
+    }
+
+    *out_size = i;
+    return out;
+}
+
+/*****************************************************************************/
+
+MbimEventEntry **
+_mbim_proxy_helper_service_subscribe_request_parse (MbimMessage *message,
+                                                    gsize       *out_size)
+{
+    MbimEventEntry **array = NULL;
+    guint32 i;
+    guint32 element_count;
+    guint32 offset = 0;
+    guint32 array_offset;
+    MbimEventEntry *event;
+
+    g_assert (message != NULL);
+    g_assert (out_size != NULL);
+
+    element_count = _mbim_message_read_guint32 (message, offset);
+    if (element_count) {
+        array = g_new (MbimEventEntry *, element_count + 1);
+
+        offset += 4;
+        for (i = 0; i < element_count; i++) {
+            array_offset = _mbim_message_read_guint32 (message, offset);
+
+            event = g_new (MbimEventEntry, 1);
+
+            memcpy (&(event->device_service_id), _mbim_message_read_uuid (message, array_offset), 16);
+            array_offset += 16;
+
+            event->cids_count = _mbim_message_read_guint32 (message, array_offset);
+            array_offset += 4;
+
+            if (event->cids_count)
+                event->cids = _mbim_message_read_guint32_array (message, event->cids_count, array_offset);
+            else
+                event->cids = NULL;
+
+            array[i] = event;
+            offset += 8;
+        }
+
+        array[element_count] = NULL;
+    }
+
+    *out_size = element_count;
+    return array;
+}
+
+/*****************************************************************************/
+
+MbimEventEntry **
+_mbim_proxy_helper_service_subscribe_list_merge (MbimEventEntry **in,
+                                                 gsize            in_size,
+                                                 MbimEventEntry **merge,
+                                                 gsize            merge_size,
+                                                 gsize           *out_size)
+{
+    gsize m;
+
+    g_assert (out_size != NULL);
+
+    *out_size = in_size;
+
+    if (!merge || !merge_size)
+        return in;
+
+    for (m = 0; m < merge_size; m++) {
+        MbimEventEntry *entry = NULL;
+
+        /* look for matching uuid */
+        if (in && in_size) {
+            gsize i;
+
+            for (i = 0; i < in_size; i++) {
+                if (mbim_uuid_cmp (&merge[m]->device_service_id, &in[i]->device_service_id)) {
+                    entry = in[i];
+                    break;
+                }
+            }
+        }
+
+        /* matching uuid not found in merge array, add it */
+        if (!entry) {
+            gsize o;
+
+            /* Index of the new element to add... */
+            o = *out_size;
+
+            /* Increase number of events in the output array */
+            (*out_size)++;
+            in = g_realloc (in, sizeof (MbimEventEntry *) * (*out_size + 1));
+            in[o] = g_memdup (merge[m], sizeof (MbimEventEntry));
+            if (merge[m]->cids_count)
+                in[o]->cids = g_memdup (merge[m]->cids, sizeof (guint32) * merge[m]->cids_count);
+            else
+                in[o]->cids = NULL;
+            in[*out_size] = NULL;
+        } else {
+            gsize cm, co;
+
+            /* matching uuid found, add cids */
+            if (!entry->cids_count)
+                /* all cids already enabled for uuid */
+                continue;
+
+            /* If we're adding all enabled cids, directly apply that */
+            if (merge[m]->cids_count == 0) {
+                g_free (entry->cids);
+                entry->cids = NULL;
+                entry->cids_count = 0;
+            }
+
+            for (cm = 0; cm < merge[m]->cids_count; cm++) {
+                for (co = 0; co < entry->cids_count; co++) {
+                    if (merge[m]->cids[cm] == entry->cids[co]) {
+                        break;
+                    }
+                }
+
+                if (co == entry->cids_count) {
+                    /* cid not found in merge array, add it */
+                    entry->cids = g_realloc (entry->cids, sizeof (guint32) * (++entry->cids_count));
+                    entry->cids[co] = merge[m]->cids[cm];
+                }
+            }
+        }
+    }
+
+    return in;
+}
diff --git a/src/libmbim-glib/mbim-proxy-helpers.h b/src/libmbim-glib/mbim-proxy-helpers.h
new file mode 100644
index 0000000..6b43cde
--- /dev/null
+++ b/src/libmbim-glib/mbim-proxy-helpers.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/*
+ * libmbim-glib -- GLib/GIO based library to control MBIM devices
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2014 Aleksander Morgado <aleksander@aleksander.es>
+ * Copyright (C) 2014 Smith Micro Software, Inc.
+ *
+ * This is a private non-installed header
+ */
+
+#ifndef _LIBMBIM_GLIB_MBIM_PROXY_HELPERS_H_
+#define _LIBMBIM_GLIB_MBIM_PROXY_HELPERS_H_
+
+#if !defined (LIBMBIM_GLIB_COMPILATION)
+#error "This is a private header!!"
+#endif
+
+#include <glib.h>
+
+#include "mbim-basic-connect.h"
+
+G_BEGIN_DECLS
+
+gboolean         _mbim_proxy_helper_service_subscribe_list_cmp          (const MbimEventEntry * const *a,
+                                                                         gsize                         a_size,
+                                                                         const MbimEventEntry * const *b,
+                                                                         gsize                         b_size);
+void             _mbim_proxy_helper_service_subscribe_list_debug        (const MbimEventEntry * const *list,
+                                                                         gsize                         list_size);
+MbimEventEntry **_mbim_proxy_helper_service_subscribe_standard_list_new (gsize           *out_size);
+MbimEventEntry **_mbim_proxy_helper_service_subscribe_request_parse     (MbimMessage     *message,
+                                                                         gsize           *out_size);
+MbimEventEntry **_mbim_proxy_helper_service_subscribe_list_merge        (MbimEventEntry **original,
+                                                                         gsize            original_size,
+                                                                         MbimEventEntry **merge,
+                                                                         gsize            merge_size,
+                                                                         gsize           *out_size);
+
+G_END_DECLS
+
+#endif /* _LIBMBIM_GLIB_MBIM_PROXY_HELPERS_H_ */
diff --git a/src/libmbim-glib/mbim-proxy.c b/src/libmbim-glib/mbim-proxy.c
new file mode 100644
index 0000000..7677cc6
--- /dev/null
+++ b/src/libmbim-glib/mbim-proxy.c
@@ -0,0 +1,1331 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/*
+ * libmbim-glib -- GLib/GIO based library to control MBIM devices
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2014 Aleksander Morgado <aleksander@lanedo.com>
+ * Copyright (C) 2014 Smith Micro Software, Inc.
+ */
+
+#include <string.h>
+#include <ctype.h>
+#include <sys/file.h>
+#include <errno.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <gio/gunixsocketaddress.h>
+
+#include "mbim-device.h"
+#include "mbim-utils.h"
+#include "mbim-proxy.h"
+#include "mbim-message-private.h"
+#include "mbim-cid.h"
+#include "mbim-enum-types.h"
+#include "mbim-error-types.h"
+#include "mbim-basic-connect.h"
+#include "mbim-proxy-helpers.h"
+
+#define BUFFER_SIZE 512
+
+G_DEFINE_TYPE (MbimProxy, mbim_proxy, G_TYPE_OBJECT)
+
+enum {
+    PROP_0,
+    PROP_N_CLIENTS,
+    PROP_N_DEVICES,
+    PROP_LAST
+};
+
+static GParamSpec *properties[PROP_LAST];
+
+struct _MbimProxyPrivate {
+    /* Unix socket service */
+    GSocketService *socket_service;
+
+    /* Clients */
+    GList *clients;
+
+    /* Devices */
+    GList *devices;
+    GList *opening_devices;
+
+    /* Global events array */
+    MbimEventEntry **mbim_event_entry_array;
+    gsize mbim_event_entry_array_size;
+};
+
+static void        track_device         (MbimProxy *self, MbimDevice *device);
+static void        untrack_device       (MbimProxy *self, MbimDevice *device);
+static MbimDevice *peek_device_for_path (MbimProxy *self, const gchar *path);
+
+/*****************************************************************************/
+
+/**
+ * mbim_proxy_get_n_clients:
+ * @self: a #MbimProxy.
+ *
+ * Get the number of clients currently connected to the proxy.
+ *
+ * Returns: a #guint.
+ */
+guint
+mbim_proxy_get_n_clients (MbimProxy *self)
+{
+    g_return_val_if_fail (MBIM_IS_PROXY (self), 0);
+
+    return g_list_length (self->priv->clients);
+}
+
+/**
+ * mbim_proxy_get_n_devices:
+ * @self: a #MbimProxy.
+ *
+ * Get the number of devices currently connected to the proxy.
+ *
+ * Returns: a #guint.
+ */
+guint
+mbim_proxy_get_n_devices (MbimProxy *self)
+{
+    g_return_val_if_fail (MBIM_IS_PROXY (self), 0);
+
+    return g_list_length (self->priv->devices);
+}
+
+/*****************************************************************************/
+/* Client info */
+
+typedef struct {
+    volatile gint ref_count;
+
+    MbimProxy *self; /* not full ref */
+    GSocketConnection *connection;
+    GSource *connection_readable_source;
+    GByteArray *buffer;
+
+    /* Only one proxy config allowed at a time */
+    gboolean config_ongoing;
+
+    MbimDevice *device;
+    guint indication_id;
+    gboolean service_subscriber_list_enabled;
+    MbimEventEntry **mbim_event_entry_array;
+    gsize mbim_event_entry_array_size;
+} Client;
+
+static gboolean connection_readable_cb (GSocket *socket, GIOCondition condition, Client *client);
+static void     track_client           (MbimProxy *self, Client *client);
+static void     untrack_client         (MbimProxy *self, Client *client);
+
+static void
+client_disconnect (Client *client)
+{
+    if (client->connection_readable_source) {
+        g_source_destroy (client->connection_readable_source);
+        g_source_unref (client->connection_readable_source);
+        client->connection_readable_source = 0;
+    }
+
+    if (client->connection) {
+        g_debug ("Client (%d) connection closed...", g_socket_get_fd (g_socket_connection_get_socket (client->connection)));
+        g_output_stream_close (g_io_stream_get_output_stream (G_IO_STREAM (client->connection)), NULL, NULL);
+        g_object_unref (client->connection);
+        client->connection = NULL;
+    }
+}
+
+static void client_indication_cb (MbimDevice *device,
+                                  MbimMessage *message,
+                                  Client *client);
+
+static void
+client_set_device (Client *client,
+                   MbimDevice *device)
+{
+    if (client->device) {
+        if (g_signal_handler_is_connected (client->device, client->indication_id))
+            g_signal_handler_disconnect (client->device, client->indication_id);
+        g_object_unref (client->device);
+    }
+
+    if (device) {
+        client->device = g_object_ref (device);
+        client->indication_id = g_signal_connect (client->device,
+                                                  MBIM_DEVICE_SIGNAL_INDICATE_STATUS,
+                                                  G_CALLBACK (client_indication_cb),
+                                                  client);
+    } else {
+        client->device = NULL;
+        client->indication_id = 0;
+    }
+}
+
+static void
+client_unref (Client *client)
+{
+    if (g_atomic_int_dec_and_test (&client->ref_count)) {
+        /* Ensure disconnected */
+        client_disconnect (client);
+        /* Reset device */
+        client_set_device (client, NULL);
+
+        if (client->buffer)
+            g_byte_array_unref (client->buffer);
+
+        if (client->mbim_event_entry_array)
+            mbim_event_entry_array_free (client->mbim_event_entry_array);
+
+        g_slice_free (Client, client);
+    }
+}
+
+static Client *
+client_ref (Client *client)
+{
+    g_atomic_int_inc (&client->ref_count);
+    return client;
+}
+
+static gboolean
+client_send_message (Client *client,
+                     MbimMessage *message,
+                     GError **error)
+{
+    if (!client->connection) {
+        g_set_error (error,
+                     MBIM_CORE_ERROR,
+                     MBIM_CORE_ERROR_WRONG_STATE,
+                     "Cannot send message: not connected");
+        return FALSE;
+    }
+
+    g_debug ("Client (%d) TX: %u bytes", g_socket_get_fd (g_socket_connection_get_socket (client->connection)), message->len);
+    if (!g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (client->connection)),
+                                    message->data,
+                                    message->len,
+                                    NULL, /* bytes_written */
+                                    NULL, /* cancellable */
+                                    error)) {
+        g_prefix_error (error, "Cannot send message to client: ");
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+/*****************************************************************************/
+/* Track/untrack clients */
+
+static void
+track_client (MbimProxy *self,
+              Client *client)
+{
+    self->priv->clients = g_list_append (self->priv->clients, client_ref (client));
+    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_N_CLIENTS]);
+}
+
+static void
+untrack_client (MbimProxy *self,
+                Client *client)
+{
+    /* Disconnect the client explicitly when untracking */
+    client_disconnect (client);
+
+    if (g_list_find (self->priv->clients, client)) {
+        self->priv->clients = g_list_remove (self->priv->clients, client);
+        client_unref (client);
+        g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_N_CLIENTS]);
+    }
+}
+
+/*****************************************************************************/
+/* Client indications */
+
+static void
+client_indication_cb (MbimDevice *device,
+                      MbimMessage *message,
+                      Client *client)
+{
+    guint i;
+    GError *error = NULL;
+    gboolean forward_indication = FALSE;
+    MbimEventEntry *event = NULL;
+
+    if (client->service_subscriber_list_enabled) {
+        /* if client sent the device service subscribe list with element count 0 then
+         * ignore all indications */
+        if (client->mbim_event_entry_array) {
+            for (i = 0; i < client->mbim_event_entry_array_size; i++) {
+                if (mbim_uuid_cmp (mbim_message_indicate_status_get_service_id (message),
+                                   &client->mbim_event_entry_array[i]->device_service_id)) {
+                    event = client->mbim_event_entry_array[i];
+                    break;
+                }
+            }
+
+            if (event) {
+                /* found matching service, search for cid */
+                if (event->cids_count) {
+                    for (i = 0; i < event->cids_count; i++) {
+                        if (mbim_message_indicate_status_get_cid (message) == event->cids[i]) {
+                            forward_indication = TRUE;
+                            break;
+                        }
+                    }
+                } else
+                    /* cids_count of 0 enables all indications for the service */
+                    forward_indication = TRUE;
+            }
+        }
+    } else if (mbim_message_indicate_status_get_service (message) != MBIM_SERVICE_INVALID &&
+               !mbim_service_id_is_custom (mbim_message_indicate_status_get_service (message)))
+        /* only forward standard service indications if service subscriber list is not enabled */
+        forward_indication = TRUE;
+
+    if (forward_indication) {
+        if (!client_send_message (client, message, &error)) {
+            g_warning ("couldn't forward indication to client");
+            g_error_free (error);
+        }
+    }
+}
+
+/*****************************************************************************/
+/* Request info */
+
+typedef struct {
+    MbimProxy *self;
+    Client *client;
+    MbimMessage *message;
+    MbimMessage *response;
+    guint32 original_transaction_id;
+    /* Only used in proxy config */
+    guint32 timeout_secs;
+} Request;
+
+static void
+request_complete_and_free (Request *request)
+{
+    if (request->response) {
+        GError *error = NULL;
+
+        /* Try to send response to client; if it fails, always assume we have
+         * to close the connection */
+        if (!client_send_message (request->client, request->response, &error)) {
+            g_debug ("couldn't send response back to client: %s", error->message);
+            g_error_free (error);
+            /* Disconnect and untrack client */
+            untrack_client (request->self, request->client);
+        }
+
+        mbim_message_unref (request->response);
+    }
+
+    if (request->message)
+        mbim_message_unref (request->message);
+    client_unref (request->client);
+    g_object_unref (request->self);
+    g_slice_free (Request, request);
+}
+
+static Request *
+request_new (MbimProxy   *self,
+             Client      *client,
+             MbimMessage *message)
+{
+    Request *request;
+
+    request = g_slice_new0 (Request);
+    request->self = g_object_ref (self);
+    request->client = client_ref (client);
+    request->message = mbim_message_ref (message);
+    request->original_transaction_id = mbim_message_get_transaction_id (message);
+
+    return request;
+}
+
+/*****************************************************************************/
+/* Internal proxy device opening operation */
+
+static gboolean
+internal_device_open_finish (MbimProxy     *self,
+                             GAsyncResult  *res,
+                             GError       **error)
+{
+    return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+typedef struct {
+    MbimDevice *device;
+    GList *pending;
+} OpeningDevice;
+
+static void
+opening_device_complete_and_free (OpeningDevice *info,
+                                  const GError  *error)
+{
+    GList *l;
+
+    /* Complete all pending open actions */
+    for (l = info->pending; l; l = g_list_next (l)) {
+        GSimpleAsyncResult *simple = (GSimpleAsyncResult *)(l->data);
+
+        if (error)
+            g_simple_async_result_set_from_error (simple, error);
+        else
+            g_simple_async_result_set_op_res_gboolean (simple, TRUE);
+        g_simple_async_result_complete_in_idle (simple);
+        g_object_unref (simple);
+    }
+
+    g_list_free (info->pending);
+    g_object_unref (info->device);
+    g_slice_free (OpeningDevice, info);
+}
+
+static OpeningDevice *
+peek_opening_device_info (MbimProxy  *self,
+                          MbimDevice *device)
+{
+    GList *l;
+
+    /* If already being opened, queue it up */
+    for (l = self->priv->opening_devices; l; l = g_list_next (l)) {
+        OpeningDevice *info;
+
+        info = (OpeningDevice *)(l->data);
+        if (g_str_equal (mbim_device_get_path (device), mbim_device_get_path (info->device)))
+            return info;
+    }
+
+    return NULL;
+}
+
+static void
+cancel_opening_device (MbimProxy  *self,
+                       MbimDevice *device)
+{
+    OpeningDevice *info;
+    GError *error;
+
+    info = peek_opening_device_info (self, device);
+    if (!info)
+        return;
+
+    error = g_error_new (MBIM_CORE_ERROR, MBIM_CORE_ERROR_ABORTED, "Device is gone");
+    opening_device_complete_and_free (info, error);
+    g_error_free (error);
+}
+
+static void
+device_open_ready (MbimDevice   *device,
+                   GAsyncResult *res,
+                   MbimProxy    *self)
+{
+    GError *error = NULL;
+    OpeningDevice *info;
+
+    mbim_device_open_finish (device, res, &error);
+
+    info = peek_opening_device_info (self, device);
+    g_assert (info != NULL);
+
+    /* Remove opening device info */
+    self->priv->opening_devices = g_list_remove (self->priv->opening_devices, info);
+
+    /* Complete all pending open actions */
+    opening_device_complete_and_free (info, error);
+
+    if (error) {
+        /* Fully untrack the device as it wasn't correctly open */
+        untrack_device (self, device);
+        g_error_free (error);
+    }
+}
+
+static void
+internal_device_open (MbimProxy           *self,
+                      MbimDevice          *device,
+                      guint32              timeout_secs,
+                      GAsyncReadyCallback  callback,
+                      gpointer             user_data)
+{
+    OpeningDevice *info;
+    GSimpleAsyncResult *simple;
+
+    simple = g_simple_async_result_new (G_OBJECT (self),
+                                        callback,
+                                        user_data,
+                                        internal_device_open);
+
+    /* If the device is already open, we are done */
+    if (mbim_device_is_open (device)) {
+        g_simple_async_result_set_op_res_gboolean (simple, TRUE);
+        g_simple_async_result_complete_in_idle (simple);
+        g_object_unref (simple);
+        return;
+    }
+
+    /* If already being opened, queue it up */
+    info = peek_opening_device_info (self, device);
+    if (info) {
+        info->pending = g_list_append (info->pending, simple);
+        return;
+    }
+
+    /* First time opening, go on */
+    info = g_slice_new0 (OpeningDevice);
+    info->device = g_object_ref (device);
+    info->pending = g_list_append (info->pending, simple);
+    self->priv->opening_devices = g_list_prepend (self->priv->opening_devices, info);
+
+    /* Note: for now, only the first timeout request is taken into account */
+
+    /* Need to open the device; and we must make sure the proxy only does this once, even
+     * when multiple clients request it */
+    mbim_device_open (device,
+                      timeout_secs,
+                      NULL,
+                      (GAsyncReadyCallback)device_open_ready,
+                      g_object_ref (self));
+}
+
+/*****************************************************************************/
+/* Proxy open */
+
+static gboolean
+process_internal_proxy_open (MbimProxy   *self,
+                             Client      *client,
+                             MbimMessage *message)
+{
+    Request *request;
+    MbimStatusError status = MBIM_STATUS_ERROR_FAILURE;
+
+    /* create request holder */
+    request = request_new (self, client, message);
+
+    if (!client->device)
+        g_warning ("cannot process Open: device not set");
+    else if (!mbim_device_is_open (client->device))
+        g_warning ("cannot process Open: device not opened by proxy");
+    else {
+        g_debug ("connection to MBIM device '%s' established", mbim_device_get_path (client->device));
+        status = MBIM_STATUS_ERROR_NONE;
+    }
+
+    request->response = mbim_message_open_done_new (mbim_message_get_transaction_id (request->message), status);
+    request_complete_and_free (request);
+    return TRUE;
+}
+
+/*****************************************************************************/
+/* Proxy close */
+
+static gboolean
+process_internal_proxy_close (MbimProxy   *self,
+                              Client      *client,
+                              MbimMessage *message)
+{
+    Request *request;
+
+    request = request_new (self, client, message);
+    request->response = mbim_message_close_done_new (mbim_message_get_transaction_id (message), MBIM_STATUS_ERROR_NONE);
+    request_complete_and_free (request);
+    return TRUE;
+}
+
+/*****************************************************************************/
+/* Proxy config */
+
+static MbimMessage *
+build_proxy_control_command_done (MbimMessage     *message,
+                                  MbimStatusError  status)
+{
+    MbimMessage *response;
+    struct command_done_message *command_done;
+
+    response = (MbimMessage *) _mbim_message_allocate (MBIM_MESSAGE_TYPE_COMMAND_DONE,
+                                                       mbim_message_get_transaction_id (message),
+                                                       sizeof (struct command_done_message));
+    command_done = &(((struct full_message *)(response->data))->message.command_done);
+    command_done->fragment_header.total   = GUINT32_TO_LE (1);
+    command_done->fragment_header.current = 0;
+    memcpy (command_done->service_id, MBIM_UUID_PROXY_CONTROL, sizeof (MbimUuid));
+    command_done->command_id  = GUINT32_TO_LE (mbim_message_command_get_cid (message));
+    command_done->status_code = GUINT32_TO_LE (status);
+    command_done->buffer_length = 0;
+
+    return response;
+}
+
+static void
+proxy_config_internal_device_open_ready (MbimProxy    *self,
+                                         GAsyncResult *res,
+                                         Request      *request)
+{
+    GError *error = NULL;
+
+    if (!internal_device_open_finish (self, res, &error)) {
+        g_warning ("error opening device: %s", error->message);
+        g_error_free (error);
+        /* Untrack client and complete without response */
+        untrack_client (request->self, request->client);
+        request_complete_and_free (request);
+        return;
+    }
+
+    if (request->client->config_ongoing == TRUE)
+        request->client->config_ongoing = FALSE;
+    request->response = build_proxy_control_command_done (request->message, MBIM_STATUS_ERROR_NONE);
+    request_complete_and_free (request);
+}
+
+static void
+device_new_ready (GObject      *source,
+                  GAsyncResult *res,
+                  Request      *request)
+{
+    GError *error = NULL;
+    MbimDevice *existing;
+    MbimDevice *device;
+
+    device = mbim_device_new_finish (res, &error);
+    if (!device) {
+        g_warning ("couldn't create MBIM device: %s", error->message);
+        g_error_free (error);
+        /* Untrack client and complete without response */
+        untrack_client (request->self, request->client);
+        request_complete_and_free (request);
+        return;
+    }
+
+    /* Store device in the proxy independently */
+    existing = peek_device_for_path (request->self, mbim_device_get_path (device));
+    if (existing) {
+        /* Race condition, we created two MbimDevices for the same port, just skip ours, no big deal */
+        client_set_device (request->client, existing);
+    } else {
+        /* Keep the newly added device in the proxy */
+        track_device (request->self, device);
+        /* Also keep track of the device in the client */
+        client_set_device (request->client, device);
+    }
+    g_object_unref (device);
+
+    internal_device_open (request->self,
+                          request->client->device,
+                          request->timeout_secs,
+                          (GAsyncReadyCallback)proxy_config_internal_device_open_ready,
+                          request);
+}
+
+static gboolean
+process_internal_proxy_config (MbimProxy   *self,
+                               Client      *client,
+                               MbimMessage *message)
+{
+    Request *request;
+    MbimDevice *device;
+    gchar *path;
+    GFile *file;
+
+    /* create request holder */
+    request = request_new (self, client, message);
+
+    /* Error out if there is already a proxy config ongoing */
+    if (client->config_ongoing) {
+        request->response = build_proxy_control_command_done (message, MBIM_STATUS_ERROR_BUSY);
+        request_complete_and_free (request);
+        return TRUE;
+    }
+
+    /* Only allow SET command */
+    if (mbim_message_command_get_command_type (message) != MBIM_MESSAGE_COMMAND_TYPE_SET) {
+        request->response = build_proxy_control_command_done (message, MBIM_STATUS_ERROR_INVALID_PARAMETERS);
+        request_complete_and_free (request);
+        return TRUE;
+    }
+
+    /* Retrieve path from request */
+    path = _mbim_message_read_string (message, 0, 0);
+    if (!path) {
+        request->response = build_proxy_control_command_done (message, MBIM_STATUS_ERROR_INVALID_PARAMETERS);
+        request_complete_and_free (request);
+        return TRUE;
+    }
+
+    /* Only allow subsequent requests with the same path */
+    if (client->device) {
+        if (g_str_equal (path, mbim_device_get_path (client->device)))
+            request->response = build_proxy_control_command_done (message, MBIM_STATUS_ERROR_NONE);
+        else
+            request->response = build_proxy_control_command_done (message, MBIM_STATUS_ERROR_FAILURE);
+        request_complete_and_free (request);
+        g_free (path);
+        return TRUE;
+    }
+
+    /* Read requested timeout value */
+    request->timeout_secs = _mbim_message_read_guint32 (message, 8);
+
+    /* Check if some other client already handled the same device */
+    device = peek_device_for_path (self, path);
+    if (device) {
+        /* Keep reference and continue */
+        client_set_device (client, device);
+
+        internal_device_open (self,
+                              device,
+                              request->timeout_secs,
+                              (GAsyncReadyCallback)proxy_config_internal_device_open_ready,
+                              request);
+        g_free (path);
+        return TRUE;
+    }
+
+    /* Flag as ongoing */
+    client->config_ongoing = TRUE;
+
+    /* Create new MBIM device */
+    file = g_file_new_for_path (path);
+    mbim_device_new (file,
+                     NULL,
+                     (GAsyncReadyCallback)device_new_ready,
+                     request);
+    g_object_unref (file);
+    g_free (path);
+    return TRUE;
+}
+
+/*****************************************************************************/
+/* Subscriber list */
+
+static void
+track_service_subscribe_list (Client      *client,
+                              MbimMessage *message)
+{
+    client->service_subscriber_list_enabled = TRUE;
+
+    if (client->mbim_event_entry_array)
+        mbim_event_entry_array_free (client->mbim_event_entry_array);
+
+    client->mbim_event_entry_array = _mbim_proxy_helper_service_subscribe_request_parse (message, &client->mbim_event_entry_array_size);
+
+    if (mbim_utils_get_traces_enabled ()) {
+        g_debug ("Client (%d) service subscribe list built", g_socket_get_fd (g_socket_connection_get_socket (client->connection)));
+        _mbim_proxy_helper_service_subscribe_list_debug ((const MbimEventEntry * const *)client->mbim_event_entry_array,
+                                                         client->mbim_event_entry_array_size);
+    }
+}
+
+static MbimEventEntry **
+merge_client_service_subscribe_lists (MbimProxy *self,
+                                      gsize     *out_size)
+{
+    GList *l;
+    MbimEventEntry **updated;
+    gsize updated_size = 0;
+
+    g_assert (out_size != NULL);
+
+    /* Add previous global list */
+    updated = _mbim_proxy_helper_service_subscribe_list_merge (NULL, 0,
+                                                               self->priv->mbim_event_entry_array, self->priv->mbim_event_entry_array_size,
+                                                               &updated_size);
+
+    for (l = self->priv->clients; l; l = g_list_next (l)) {
+        Client *client;
+
+        client = l->data;
+        if (!client->mbim_event_entry_array)
+            continue;
+
+        /* Add per-client list */
+        updated = _mbim_proxy_helper_service_subscribe_list_merge (updated, updated_size,
+                                                                   client->mbim_event_entry_array, client->mbim_event_entry_array_size,
+                                                                   &updated_size);
+    }
+
+    if (mbim_utils_get_traces_enabled ()) {
+        g_debug ("Merged service subscribe list built");
+        _mbim_proxy_helper_service_subscribe_list_debug ((const MbimEventEntry * const *)updated, updated_size);
+    }
+
+    *out_size = updated_size;
+    return updated;
+}
+
+static void
+device_service_subscribe_list_set_complete (Request         *request,
+                                            MbimStatusError  status)
+{
+    struct command_done_message *command_done;
+    guint32 raw_len;
+    const guint8 *raw_data;
+
+    /* The raw message data to send back as response to client */
+    raw_data = mbim_message_command_get_raw_information_buffer (request->message, &raw_len);
+
+    request->response = (MbimMessage *)_mbim_message_allocate (MBIM_MESSAGE_TYPE_COMMAND_DONE,
+                                                               mbim_message_get_transaction_id (request->message),
+                                                               sizeof (struct command_done_message) +
+                                                               raw_len);
+    command_done = &(((struct full_message *)(request->response->data))->message.command_done);
+    command_done->fragment_header.total = GUINT32_TO_LE (1);
+    command_done->fragment_header.current = 0;
+    memcpy (command_done->service_id, MBIM_UUID_BASIC_CONNECT, sizeof (MbimUuid));
+    command_done->command_id = GUINT32_TO_LE (MBIM_CID_BASIC_CONNECT_DEVICE_SERVICE_SUBSCRIBE_LIST);
+    command_done->status_code = GUINT32_TO_LE (status);
+    command_done->buffer_length = GUINT32_TO_LE (raw_len);
+    memcpy (&command_done->buffer[0], raw_data, raw_len);
+
+    request_complete_and_free (request);
+}
+
+static void
+device_service_subscribe_list_set_ready (MbimDevice   *device,
+                                         GAsyncResult *res,
+                                         Request      *request)
+{
+    MbimMessage *tmp_response;
+    MbimStatusError error_status_code;
+    GError *error = NULL;
+
+    tmp_response = mbim_device_command_finish (device, res, &error);
+    if (!tmp_response) {
+        g_debug ("sending request to device failed: %s", error->message);
+        g_error_free (error);
+        /* Don't disconnect client, just let the request timeout in its side */
+        request_complete_and_free (request);
+        return;
+    }
+
+    error_status_code = GUINT32_FROM_LE (((struct full_message *)(tmp_response->data))->message.command_done.status_code);
+    mbim_message_unref (tmp_response);
+
+    device_service_subscribe_list_set_complete (request, error_status_code);
+}
+
+static gboolean
+process_device_service_subscribe_list (MbimProxy   *self,
+                                       Client      *client,
+                                       MbimMessage *message)
+{
+    MbimEventEntry **updated;
+    gsize updated_size = 0;
+    Request *request;
+
+    /* create request holder */
+    request = request_new (self, client, message);
+
+    /* trace the service subscribe list for the client */
+    track_service_subscribe_list (client, message);
+
+    /* merge all service subscribe list for all clients to set on device */
+    updated = merge_client_service_subscribe_lists (self, &updated_size);
+
+    /* If lists are equal, ignore re-setting them up */
+    if (_mbim_proxy_helper_service_subscribe_list_cmp (
+            (const MbimEventEntry *const *)updated, updated_size,
+            (const MbimEventEntry *const *)self->priv->mbim_event_entry_array, self->priv->mbim_event_entry_array_size)) {
+        /* Complete directly without error */
+        mbim_event_entry_array_free (updated);
+        device_service_subscribe_list_set_complete (request, MBIM_STATUS_ERROR_NONE);
+        return TRUE;
+    }
+
+    /* Lists are different, updated stored one */
+    mbim_event_entry_array_free (self->priv->mbim_event_entry_array);
+    self->priv->mbim_event_entry_array = updated;
+    self->priv->mbim_event_entry_array_size = updated_size;
+
+    message = mbim_message_device_service_subscribe_list_set_new (self->priv->mbim_event_entry_array_size,
+                                                                  (const MbimEventEntry *const *)self->priv->mbim_event_entry_array,
+                                                                  NULL);
+    mbim_message_set_transaction_id (message, mbim_device_get_next_transaction_id (client->device));
+
+    mbim_device_command (client->device,
+                         message,
+                         300,
+                         NULL,
+                         (GAsyncReadyCallback)device_service_subscribe_list_set_ready,
+                         request);
+
+    mbim_message_unref (message);
+    return TRUE;
+}
+
+/*****************************************************************************/
+/* Standard command */
+
+static void
+device_command_ready (MbimDevice *device,
+                      GAsyncResult *res,
+                      Request *request)
+{
+    GError *error = NULL;
+
+    request->response = mbim_device_command_finish (device, res, &error);
+    if (!request->response) {
+        g_debug ("sending request to device failed: %s", error->message);
+        g_error_free (error);
+        /* Don't disconnect client, just let the request timeout in its side */
+        request_complete_and_free (request);
+        return;
+    }
+
+    /* replace reponse transaction id with the requested transaction id */
+    mbim_message_set_transaction_id (request->response, request->original_transaction_id);
+
+    request_complete_and_free (request);
+}
+
+static gboolean
+process_command (MbimProxy   *self,
+                 Client      *client,
+                 MbimMessage *message)
+{
+    Request *request;
+
+    /* create request holder */
+    request = request_new (self, client, message);
+
+    /* replace command transaction id with internal proxy transaction id to avoid collision */
+    mbim_message_set_transaction_id (message, mbim_device_get_next_transaction_id (client->device));
+
+    /* The timeout needs to be big enough for any kind of transaction to
+     * complete, otherwise the remote clients will lose the reply if they
+     * configured a timeout bigger than this internal one. We should likely
+     * make this value configurable per-client, instead of a hardcoded value.
+     */
+    mbim_device_command (client->device,
+                         message,
+                         300,
+                         NULL,
+                         (GAsyncReadyCallback)device_command_ready,
+                         request);
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+process_message (MbimProxy   *self,
+                 Client      *client,
+                 MbimMessage *message)
+{
+    /* Filter by message type */
+    switch (mbim_message_get_message_type (message)) {
+    case MBIM_MESSAGE_TYPE_OPEN:
+        return process_internal_proxy_open (self, client, message);
+    case MBIM_MESSAGE_TYPE_CLOSE:
+        return process_internal_proxy_close (self, client, message);
+    case MBIM_MESSAGE_TYPE_COMMAND:
+        /* Proxy control message? */
+        if (mbim_message_command_get_service (message) == MBIM_SERVICE_PROXY_CONTROL &&
+            mbim_message_command_get_cid (message) == MBIM_CID_PROXY_CONTROL_CONFIGURATION)
+            return process_internal_proxy_config (self, client, message);
+        /* device service subscribe list message? */
+        if (mbim_message_command_get_service (message) == MBIM_SERVICE_BASIC_CONNECT &&
+            mbim_message_command_get_cid (message) == MBIM_CID_BASIC_CONNECT_DEVICE_SERVICE_SUBSCRIBE_LIST)
+            return process_device_service_subscribe_list (self, client, message);
+        /* Otherwise, standard command to forward */
+        return process_command (self, client, message);
+    default:
+        g_debug ("invalid message from client: not a command message");
+        return FALSE;
+    }
+
+    g_assert_not_reached ();
+}
+
+static void
+parse_request (MbimProxy *self,
+               Client    *client)
+{
+    do {
+        MbimMessage *message;
+        guint32 len = 0;
+
+        if (client->buffer->len >= sizeof (struct header) &&
+            (len = GUINT32_FROM_LE (((struct header *)client->buffer->data)->length)) > client->buffer->len) {
+            /* have not received complete message */
+            return;
+        }
+
+        if (!len)
+            return;
+
+        message = mbim_message_new (client->buffer->data, len);
+        if (!message)
+            return;
+        g_byte_array_remove_range (client->buffer, 0, len);
+
+        /* Play with the received message */
+        process_message (self, client, message);
+        mbim_message_unref (message);
+    } while (client->buffer->len > 0);
+}
+
+static gboolean
+connection_readable_cb (GSocket *socket,
+                        GIOCondition condition,
+                        Client *client)
+{
+    MbimProxy *self;
+    guint8 buffer[BUFFER_SIZE];
+    GError *error = NULL;
+    gssize r;
+
+    /* Recover proxy pointer soon */
+    self = client->self;
+
+    if (condition & G_IO_HUP || condition & G_IO_ERR) {
+        untrack_client (self, client);
+        return FALSE;
+    }
+
+    if (!(condition & G_IO_IN || condition & G_IO_PRI))
+        return TRUE;
+
+    r = g_input_stream_read (g_io_stream_get_input_stream (G_IO_STREAM (client->connection)),
+                             buffer,
+                             BUFFER_SIZE,
+                             NULL,
+                             &error);
+    if (r < 0) {
+        g_warning ("Error reading from istream: %s", error ? error->message : "unknown");
+        if (error)
+            g_error_free (error);
+        /* Close the device */
+        untrack_client (self, client);
+        return FALSE;
+    }
+
+    if (r == 0)
+        return TRUE;
+
+    /* else, r > 0 */
+    if (!G_UNLIKELY (client->buffer))
+        client->buffer = g_byte_array_sized_new (r);
+    g_byte_array_append (client->buffer, buffer, r);
+
+    /* Try to parse input messages */
+    parse_request (self, client);
+
+    return TRUE;
+}
+
+static void
+incoming_cb (GSocketService *service,
+             GSocketConnection *connection,
+             GObject *unused,
+             MbimProxy *self)
+{
+    Client *client;
+    GCredentials *credentials;
+    GError *error = NULL;
+    uid_t uid;
+
+    g_debug ("Client (%d) connection open...", g_socket_get_fd (g_socket_connection_get_socket (connection)));
+
+    credentials = g_socket_get_credentials (g_socket_connection_get_socket (connection), &error);
+    if (!credentials) {
+        g_warning ("Client not allowed: Error getting socket credentials: %s", error->message);
+        g_error_free (error);
+        return;
+    }
+
+    uid = g_credentials_get_unix_user (credentials, &error);
+    g_object_unref (credentials);
+    if (error) {
+        g_warning ("Client not allowed: Error getting unix user id: %s", error->message);
+        g_error_free (error);
+        return;
+    }
+
+    if (uid != 0) {
+        g_warning ("Client not allowed: Not enough privileges");
+        return;
+    }
+
+    /* Create client */
+    client = g_slice_new0 (Client);
+    client->self = self;
+    client->ref_count = 1;
+    client->connection = g_object_ref (connection);
+    client->connection_readable_source = g_socket_create_source (g_socket_connection_get_socket (client->connection),
+                                                                 G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP,
+                                                                 NULL);
+    g_source_set_callback (client->connection_readable_source,
+                           (GSourceFunc)connection_readable_cb,
+                           client,
+                           NULL);
+    g_source_attach (client->connection_readable_source, NULL);
+
+    /* Keep the client info around */
+    track_client (self, client);
+
+    client_unref (client);
+}
+
+static gboolean
+setup_socket_service (MbimProxy *self,
+                      GError **error)
+{
+    GSocketAddress *socket_address;
+    GSocket *socket;
+
+    socket = g_socket_new (G_SOCKET_FAMILY_UNIX,
+                           G_SOCKET_TYPE_STREAM,
+                           G_SOCKET_PROTOCOL_DEFAULT,
+                           error);
+    if (!socket)
+        return FALSE;
+
+    /* Bind to address */
+    socket_address = (g_unix_socket_address_new_with_type (
+                          MBIM_PROXY_SOCKET_PATH,
+                          -1,
+                          G_UNIX_SOCKET_ADDRESS_ABSTRACT));
+    if (!g_socket_bind (socket, socket_address, TRUE, error)) {
+        g_object_unref (socket_address);
+        g_object_unref (socket);
+        return FALSE;
+    }
+    g_object_unref (socket_address);
+
+    g_debug ("creating UNIX socket service...");
+
+    /* Listen */
+    if (!g_socket_listen (socket, error)) {
+        g_object_unref (socket);
+        return FALSE;
+    }
+
+    /* Create socket service */
+    self->priv->socket_service = g_socket_service_new ();
+    g_signal_connect (self->priv->socket_service, "incoming", G_CALLBACK (incoming_cb), self);
+    if (!g_socket_listener_add_socket (G_SOCKET_LISTENER (self->priv->socket_service),
+                                       socket,
+                                       NULL, /* don't pass an object, will take a reference */
+                                       error)) {
+        g_prefix_error (error, "Error adding socket at '%s' to socket service: ", MBIM_PROXY_SOCKET_PATH);
+        g_object_unref (socket);
+        return FALSE;
+    }
+
+    g_debug ("starting UNIX socket service at '%s'...", MBIM_PROXY_SOCKET_PATH);
+    g_socket_service_start (self->priv->socket_service);
+    g_object_unref (socket);
+    return TRUE;
+}
+
+/*****************************************************************************/
+/* Device tracking */
+
+static MbimDevice *
+peek_device_for_path (MbimProxy   *self,
+                      const gchar *path)
+{
+    GList *l;
+
+    for (l = self->priv->devices; l; l = g_list_next (l)) {
+        /* Return if found */
+        if (g_str_equal (mbim_device_get_path ((MbimDevice *)l->data), path))
+            return (MbimDevice *)l->data;
+    }
+
+    return NULL;
+}
+
+static void
+proxy_device_removed_cb (MbimDevice *device,
+                         MbimProxy *self)
+{
+    untrack_device (self, device);
+}
+
+static void
+untrack_device (MbimProxy  *self,
+                MbimDevice *device)
+{
+    GList *l;
+    GList *to_remove = NULL;
+
+    if (!g_list_find (self->priv->devices, device))
+        return;
+
+    /* Disconnect right away */
+    g_signal_handlers_disconnect_by_func (device, proxy_device_removed_cb, self);
+
+    /* If pending openings ongoing, complete them with error */
+    cancel_opening_device (self, device);
+
+    /* Lookup all clients with this device */
+    for (l = self->priv->clients; l; l = g_list_next (l)) {
+        if (g_str_equal (mbim_device_get_path (((Client *)(l->data))->device), mbim_device_get_path (device)))
+            to_remove = g_list_append (to_remove, l->data);
+    }
+
+    /* Remove all these clients */
+    for (l = to_remove; l; l = g_list_next (l))
+        untrack_client (self, (Client *)(l->data));
+    g_list_free (to_remove);
+
+    /* And finally, remove the device */
+    self->priv->devices = g_list_remove (self->priv->devices, device);
+    g_object_unref (device);
+    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_N_DEVICES]);
+}
+
+static void
+track_device (MbimProxy *self,
+              MbimDevice *device)
+{
+    self->priv->devices = g_list_append (self->priv->devices, g_object_ref (device));
+    g_signal_connect (device,
+                      MBIM_DEVICE_SIGNAL_REMOVED,
+                      G_CALLBACK (proxy_device_removed_cb),
+                      self);
+    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_N_DEVICES]);
+}
+
+/*****************************************************************************/
+
+MbimProxy *
+mbim_proxy_new (GError **error)
+{
+    MbimProxy *self;
+
+    /* Only root can run the mbim-proxy */
+    if (getuid () != 0) {
+        g_set_error (error,
+                     MBIM_CORE_ERROR,
+                     MBIM_CORE_ERROR_FAILED,
+                     "Not enough privileges");
+        return NULL;
+    }
+
+    self = g_object_new (MBIM_TYPE_PROXY, NULL);
+    if (!setup_socket_service (self, error))
+        g_clear_object (&self);
+    return self;
+}
+
+static void
+mbim_proxy_init (MbimProxy *self)
+{
+    /* Setup private data */
+    self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+                                              MBIM_TYPE_PROXY,
+                                              MbimProxyPrivate);
+
+    /* By default, we assume we have all default services enabled */
+    self->priv->mbim_event_entry_array = _mbim_proxy_helper_service_subscribe_standard_list_new (&self->priv->mbim_event_entry_array_size);
+}
+
+static void
+get_property (GObject *object,
+              guint prop_id,
+              GValue *value,
+              GParamSpec *pspec)
+{
+    MbimProxy *self = MBIM_PROXY (object);
+
+    switch (prop_id) {
+    case PROP_N_CLIENTS:
+        g_value_set_uint (value, g_list_length (self->priv->clients));
+        break;
+    case PROP_N_DEVICES:
+        g_value_set_uint (value, g_list_length (self->priv->devices));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+dispose (GObject *object)
+{
+    MbimProxyPrivate *priv = MBIM_PROXY (object)->priv;
+
+    /* This list should always be empty when disposing */
+    g_assert (priv->opening_devices == NULL);
+
+    if (priv->clients) {
+        g_list_free_full (priv->clients, (GDestroyNotify) client_unref);
+        priv->clients = NULL;
+    }
+
+    if (priv->devices) {
+        g_list_free_full (priv->devices, (GDestroyNotify) g_object_unref);
+        priv->devices = NULL;
+    }
+
+    if (priv->socket_service) {
+        if (g_socket_service_is_active (priv->socket_service))
+            g_socket_service_stop (priv->socket_service);
+        g_clear_object (&priv->socket_service);
+        g_unlink (MBIM_PROXY_SOCKET_PATH);
+        g_debug ("UNIX socket service at '%s' stopped", MBIM_PROXY_SOCKET_PATH);
+    }
+
+    if (priv->mbim_event_entry_array) {
+        mbim_event_entry_array_free (priv->mbim_event_entry_array);
+        priv->mbim_event_entry_array = NULL;
+        priv->mbim_event_entry_array_size = 0;
+    }
+
+    G_OBJECT_CLASS (mbim_proxy_parent_class)->dispose (object);
+}
+
+static void
+mbim_proxy_class_init (MbimProxyClass *proxy_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (proxy_class);
+
+    g_type_class_add_private (object_class, sizeof (MbimProxyPrivate));
+
+    /* Virtual methods */
+    object_class->get_property = get_property;
+    object_class->dispose = dispose;
+
+    /* Properties */
+    properties[PROP_N_CLIENTS] =
+        g_param_spec_uint (MBIM_PROXY_N_CLIENTS,
+                           "Number of clients",
+                           "Number of clients currently connected to the proxy",
+                           0,
+                           G_MAXUINT,
+                           0,
+                           G_PARAM_READABLE);
+    g_object_class_install_property (object_class, PROP_N_CLIENTS, properties[PROP_N_CLIENTS]);
+
+    properties[PROP_N_DEVICES] =
+        g_param_spec_uint (MBIM_PROXY_N_DEVICES,
+                           "Number of devices",
+                           "Number of devices currently managed by the proxy",
+                           0,
+                           G_MAXUINT,
+                           0,
+                           G_PARAM_READABLE);
+    g_object_class_install_property (object_class, PROP_N_DEVICES, properties[PROP_N_DEVICES]);
+}
diff --git a/src/libmbim-glib/mbim-proxy.h b/src/libmbim-glib/mbim-proxy.h
new file mode 100644
index 0000000..f874cc4
--- /dev/null
+++ b/src/libmbim-glib/mbim-proxy.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * libmbim-glib -- GLib/GIO based library to control MBIM devices
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2014 Aleksander Morgado <aleksander@lanedo.com>
+ * Copyright (C) 2014 Smith Micro Software, Inc.
+ */
+
+#ifndef MBIM_PROXY_H
+#define MBIM_PROXY_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#define MBIM_TYPE_PROXY            (mbim_proxy_get_type ())
+#define MBIM_PROXY(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MBIM_TYPE_PROXY, MbimProxy))
+#define MBIM_PROXY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), MBIM_TYPE_PROXY, MbimProxyClass))
+#define MBIM_IS_PROXY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MBIM_TYPE_PROXY))
+#define MBIM_IS_PROXY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), MBIM_TYPE_PROXY))
+#define MBIM_PROXY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), MBIM_TYPE_PROXY, MbimProxyClass))
+
+typedef struct _MbimProxy MbimProxy;
+typedef struct _MbimProxyClass MbimProxyClass;
+typedef struct _MbimProxyPrivate MbimProxyPrivate;
+
+#define MBIM_PROXY_SOCKET_PATH "mbim-proxy"
+
+#define MBIM_PROXY_N_CLIENTS   "mbim-proxy-n-clients"
+#define MBIM_PROXY_N_DEVICES   "mbim-proxy-n-devices"
+
+struct _MbimProxy {
+    GObject parent;
+    MbimProxyPrivate *priv;
+};
+
+struct _MbimProxyClass {
+    GObjectClass parent;
+};
+
+GType mbim_proxy_get_type (void);
+
+MbimProxy *mbim_proxy_new           (GError **error);
+guint      mbim_proxy_get_n_clients (MbimProxy *self);
+guint      mbim_proxy_get_n_devices (MbimProxy *self);
+
+#endif /* MBIM_PROXY_H */
diff --git a/src/libmbim-glib/mbim-uuid.c b/src/libmbim-glib/mbim-uuid.c
index c06c208..81f6f7a 100644
--- a/src/libmbim-glib/mbim-uuid.c
+++ b/src/libmbim-glib/mbim-uuid.c
@@ -20,6 +20,7 @@
  *
  * Copyright (C) 2013 - 2014 Aleksander Morgado <aleksander@aleksander.es>
  * Copyright (C) 2014 NVDIA Corporation
+ * Copyright (C) 2014 Smith Micro Software, Inc.
  */
 
 #include <config.h>
@@ -37,6 +38,8 @@
  * This section defines the data type for unique identifiers.
  */
 
+#define MBIM_SERVICE_LAST MBIM_SERVICE_PROXY_CONTROL
+
 /*****************************************************************************/
 
 /**
@@ -218,6 +221,14 @@
     .e = { 0x27, 0xd7, 0xfb, 0x80, 0x95, 0x9c }
 };
 
+static const MbimUuid uuid_proxy_control = {
+    .a = { 0x83, 0x8c, 0xf7, 0xfb },
+    .b = { 0x8d, 0x0d },
+    .c = { 0x4d, 0x7f },
+    .d = { 0x87, 0x1e },
+    .e = { 0xd7, 0x1d , 0xbe, 0xfb, 0xb3, 0x9b }
+};
+
 static GList *mbim_custom_service_list = NULL;
 
 typedef struct {
@@ -302,7 +313,7 @@
 {
     GList *l;
 
-    if (id <= MBIM_SERVICE_MS_HOST_SHUTDOWN)
+    if (id <= MBIM_SERVICE_LAST)
         return FALSE;
 
     for (l = mbim_custom_service_list; l != NULL; l = l->next) {
@@ -329,7 +340,7 @@
 {
     GList *l;
 
-    if (service <= MBIM_SERVICE_MS_HOST_SHUTDOWN)
+    if (service <= MBIM_SERVICE_LAST)
         return mbim_service_get_string (service);
 
     for (l = mbim_custom_service_list; l != NULL; l = l->next) {
@@ -353,7 +364,7 @@
     GList *l;
 
     g_return_val_if_fail (service >= MBIM_SERVICE_INVALID &&
-                          (service <= MBIM_SERVICE_MS_HOST_SHUTDOWN ||
+                          (service <= MBIM_SERVICE_PROXY_CONTROL ||
                            mbim_service_id_is_custom (service)),
                           &uuid_invalid);
 
@@ -378,6 +389,8 @@
         return &uuid_ms_firmware_id;
     case MBIM_SERVICE_MS_HOST_SHUTDOWN:
         return &uuid_ms_host_shutdown;
+    case MBIM_SERVICE_PROXY_CONTROL:
+        return &uuid_proxy_control;
     default:
         for (l = mbim_custom_service_list; l != NULL; l = l->next) {
             if (service == ((MbimCustomService *)l->data)->service_id)
@@ -427,6 +440,9 @@
     if (mbim_uuid_cmp (uuid, &uuid_ms_host_shutdown))
         return MBIM_SERVICE_MS_HOST_SHUTDOWN;
 
+    if (mbim_uuid_cmp (uuid, &uuid_proxy_control))
+        return MBIM_SERVICE_PROXY_CONTROL;
+
     for (l = mbim_custom_service_list; l != NULL; l = l->next) {
         if (mbim_uuid_cmp (&((MbimCustomService *)l->data)->uuid, uuid))
             return ((MbimCustomService *)l->data)->service_id;
diff --git a/src/libmbim-glib/mbim-uuid.h b/src/libmbim-glib/mbim-uuid.h
index c3a6ac2..91def98 100644
--- a/src/libmbim-glib/mbim-uuid.h
+++ b/src/libmbim-glib/mbim-uuid.h
@@ -70,6 +70,7 @@
  * @MBIM_SERVICE_DSS: Device Service Stream service.
  * @MBIM_SERVICE_MS_FIRMWARE_ID: Microsoft Firmware ID service.
  * @MBIM_SERVICE_MS_HOST_SHUTDOWN: Microsoft Host Shutdown service.
+ * @MBIM_SERVICE_PROXY_CONTROL: Proxy Control service.
  *
  * Enumeration of the generic MBIM services.
  */
@@ -84,6 +85,8 @@
     MBIM_SERVICE_DSS              = 7,
     MBIM_SERVICE_MS_FIRMWARE_ID   = 8,
     MBIM_SERVICE_MS_HOST_SHUTDOWN = 9,
+    MBIM_SERVICE_PROXY_CONTROL    = 10,
+    /* Note: update MBIM_SERVICE_LAST when a new value is added */
 } MbimService;
 
 /**
@@ -176,6 +179,15 @@
  */
 #define MBIM_UUID_MS_HOST_SHUTDOWN mbim_uuid_from_service (MBIM_SERVICE_MS_HOST_SHUTDOWN)
 
+/**
+ * MBIM_UUID_PROXY_CONTROL:
+ *
+ * Get the UUID of the %MBIM_SERVICE_PROXY_CONTROL service.
+ *
+ * Returns: (transfer none): a #MbimUuid.
+ */
+#define MBIM_UUID_PROXY_CONTROL mbim_uuid_from_service (MBIM_SERVICE_PROXY_CONTROL)
+
 const gchar *mbim_service_lookup_name (guint service);
 
 guint mbim_register_custom_service (const MbimUuid *uuid,
diff --git a/src/libmbim-glib/test/Makefile.am b/src/libmbim-glib/test/Makefile.am
index 708dc3b..cddfb26 100644
--- a/src/libmbim-glib/test/Makefile.am
+++ b/src/libmbim-glib/test/Makefile.am
@@ -6,7 +6,8 @@
 	test-message \
 	test-fragment \
 	test-message-parser \
-	test-message-builder
+	test-message-builder \
+	test-proxy-helpers
 
 TEST_PROGS += $(noinst_PROGRAMS)
 
@@ -90,3 +91,19 @@
 	$(top_builddir)/src/libmbim-glib/libmbim-glib-core.la \
 	$(top_builddir)/src/libmbim-glib/generated/libmbim-glib-generated.la \
 	$(LIBMBIM_GLIB_LIBS)
+
+test_proxy_helpers_SOURCES = \
+	test-proxy-helpers.c \
+	$(top_srcdir)/src/libmbim-glib/mbim-proxy-helpers.h \
+	$(top_srcdir)/src/libmbim-glib/mbim-proxy-helpers.c
+test_proxy_helpers_CPPFLAGS = \
+	$(LIBMBIM_GLIB_CFLAGS) \
+	-I$(top_srcdir) \
+	-I$(top_srcdir)/src/libmbim-glib \
+	-I$(top_builddir)/src/libmbim-glib \
+	-I$(top_builddir)/src/libmbim-glib/generated \
+	-DLIBMBIM_GLIB_COMPILATION
+test_proxy_helpers_LDADD = \
+	$(top_builddir)/src/libmbim-glib/libmbim-glib-core.la \
+	$(top_builddir)/src/libmbim-glib/generated/libmbim-glib-generated.la \
+	$(LIBMBIM_GLIB_LIBS)
diff --git a/src/libmbim-glib/test/test-proxy-helpers.c b/src/libmbim-glib/test/test-proxy-helpers.c
new file mode 100644
index 0000000..84c773e
--- /dev/null
+++ b/src/libmbim-glib/test/test-proxy-helpers.c
@@ -0,0 +1,535 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * 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 2 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:
+ *
+ * Copyright (C) 2014 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+#include <string.h>
+
+#include "mbim-cid.h"
+#include "mbim-uuid.h"
+#include "mbim-basic-connect.h"
+#include "mbim-proxy-helpers.h"
+
+/*****************************************************************************/
+
+static void
+test_parse_single_service_0_cids (void)
+{
+    MbimMessage *message;
+    MbimEventEntry **in;
+    MbimEventEntry **out;
+    GError *error = NULL;
+    gsize out_size = 0;
+
+    in = g_new0 (MbimEventEntry *, 2);
+    in[0] = g_new0 (MbimEventEntry, 1);
+    memcpy (&in[0]->device_service_id, MBIM_UUID_BASIC_CONNECT, sizeof (MbimUuid));
+    in[0]->cids_count = 0;
+    in[0]->cids = NULL;
+
+    message = mbim_message_device_service_subscribe_list_set_new (1, (const MbimEventEntry *const *)in, &error);
+    g_assert_no_error (error);
+    g_assert (message != NULL);
+
+    out = _mbim_proxy_helper_service_subscribe_request_parse (message, &out_size);
+    g_assert (out != NULL);
+    g_assert (_mbim_proxy_helper_service_subscribe_list_cmp ((const MbimEventEntry * const *)in, 1,
+                                                             (const MbimEventEntry * const *)out, out_size));
+    g_assert_cmpuint (out_size, ==, 1);
+
+    mbim_message_unref (message);
+    mbim_event_entry_array_free (in);
+    mbim_event_entry_array_free (out);
+}
+
+static void
+test_parse_single_service_1_cids (void)
+{
+    MbimMessage *message;
+    MbimEventEntry **in;
+    MbimEventEntry **out;
+    GError *error = NULL;
+    gsize out_size = 0;
+
+    in = g_new0 (MbimEventEntry *, 2);
+    in[0] = g_new0 (MbimEventEntry, 1);
+    memcpy (&in[0]->device_service_id, MBIM_UUID_BASIC_CONNECT, sizeof (MbimUuid));
+    in[0]->cids_count = 1;
+    in[0]->cids = g_new0 (guint32, in[0]->cids_count);
+    in[0]->cids[0] = MBIM_CID_BASIC_CONNECT_SUBSCRIBER_READY_STATUS;
+
+    message = mbim_message_device_service_subscribe_list_set_new (1, (const MbimEventEntry *const *)in, &error);
+    g_assert_no_error (error);
+    g_assert (message != NULL);
+
+    out = _mbim_proxy_helper_service_subscribe_request_parse (message, &out_size);
+    g_assert (out != NULL);
+    g_assert (_mbim_proxy_helper_service_subscribe_list_cmp ((const MbimEventEntry * const *)in, 1,
+                                                             (const MbimEventEntry * const *)out, out_size));
+    g_assert_cmpuint (out_size, ==, 1);
+
+    mbim_message_unref (message);
+    mbim_event_entry_array_free (in);
+    mbim_event_entry_array_free (out);
+}
+
+static void
+test_parse_single_service_5_cids (void)
+{
+    MbimMessage *message;
+    MbimEventEntry **in;
+    MbimEventEntry **out;
+    GError *error = NULL;
+    gsize out_size = 0;
+
+    in = g_new0 (MbimEventEntry *, 2);
+    in[0] = g_new0 (MbimEventEntry, 1);
+    memcpy (&in[0]->device_service_id, MBIM_UUID_BASIC_CONNECT, sizeof (MbimUuid));
+    in[0]->cids_count = 5;
+    in[0]->cids = g_new0 (guint32, in[0]->cids_count);
+    in[0]->cids[0] = MBIM_CID_BASIC_CONNECT_SUBSCRIBER_READY_STATUS;
+    in[0]->cids[1] = MBIM_CID_BASIC_CONNECT_RADIO_STATE;
+    in[0]->cids[2] = MBIM_CID_BASIC_CONNECT_SIGNAL_STATE;
+    in[0]->cids[3] = MBIM_CID_BASIC_CONNECT_IP_CONFIGURATION;
+    in[0]->cids[4] = MBIM_CID_BASIC_CONNECT_NETWORK_IDLE_HINT;
+
+    message = mbim_message_device_service_subscribe_list_set_new (1, (const MbimEventEntry *const *)in, &error);
+    g_assert_no_error (error);
+    g_assert (message != NULL);
+
+    out = _mbim_proxy_helper_service_subscribe_request_parse (message, &out_size);
+    g_assert (out != NULL);
+    g_assert (_mbim_proxy_helper_service_subscribe_list_cmp ((const MbimEventEntry * const *)in, 1,
+                                                             (const MbimEventEntry * const *)out, out_size));
+    g_assert_cmpuint (out_size, ==, 1);
+
+    mbim_message_unref (message);
+    mbim_event_entry_array_free (in);
+    mbim_event_entry_array_free (out);
+}
+
+/*****************************************************************************/
+
+static MbimEventEntry *
+find_service_in_list (MbimEventEntry **list,
+                      gsize            list_size,
+                      MbimService      service)
+{
+    gsize i;
+
+    for (i = 0; i < list_size; i++) {
+        if (mbim_uuid_cmp (&(list[i]->device_service_id), mbim_uuid_from_service (service)))
+            return list[i];
+    }
+
+    return NULL;
+}
+
+static void
+check_standard_list (MbimEventEntry **list,
+                     gsize            list_size)
+{
+    MbimEventEntry *tmp;
+    MbimService s;
+    gsize i;
+
+    for (i = 0; list[i]; i++);
+    g_assert_cmpuint (i, ==, list_size);
+    g_assert_cmpuint (i, ==, (MBIM_SERVICE_DSS - MBIM_SERVICE_BASIC_CONNECT + 1));
+
+    for (s = MBIM_SERVICE_BASIC_CONNECT; s <= MBIM_SERVICE_DSS; s++) {
+        tmp = find_service_in_list (list, list_size, s);
+        g_assert (tmp != NULL);
+        g_assert_cmpuint (tmp->cids_count, ==, 0);
+        g_assert (tmp->cids == NULL);
+    }
+}
+
+static void
+test_standard_list (void)
+{
+    MbimEventEntry **out;
+    gsize out_size = 0;
+
+    out = _mbim_proxy_helper_service_subscribe_standard_list_new (&out_size);
+    check_standard_list (out, out_size);
+    mbim_event_entry_array_free (out);
+}
+
+/*****************************************************************************/
+
+static void
+test_merge_standard_list_full_none (void)
+{
+    MbimEventEntry **list;
+    gsize list_size = 0;
+    gsize out_size = 0;
+
+    /* list with all standard services */
+    list = _mbim_proxy_helper_service_subscribe_standard_list_new (&list_size);
+
+    /* merge */
+    list = _mbim_proxy_helper_service_subscribe_list_merge (list, list_size, NULL, 0, &out_size);
+
+    check_standard_list (list, out_size);
+
+    mbim_event_entry_array_free (list);
+}
+
+static void
+test_merge_standard_list_full_subset (void)
+{
+    MbimEventEntry **list;
+    gsize list_size = 0;
+    MbimEventEntry **addition;
+    gsize addition_size;
+    gsize out_size = 0;
+
+    /* list with all standard services */
+    list = _mbim_proxy_helper_service_subscribe_standard_list_new (&list_size);
+
+    /* setup a new list with a subset of standard services */
+    addition_size = 2;
+    addition = g_new0 (MbimEventEntry *, addition_size + 1);
+    addition[0] = g_new0 (MbimEventEntry, 1);
+    memcpy (&addition[0]->device_service_id, MBIM_UUID_BASIC_CONNECT, sizeof (MbimUuid));
+    addition[0]->cids_count = 5;
+    addition[0]->cids = g_new0 (guint32, addition[0]->cids_count);
+    addition[0]->cids[0] = MBIM_CID_BASIC_CONNECT_SUBSCRIBER_READY_STATUS;
+    addition[0]->cids[1] = MBIM_CID_BASIC_CONNECT_RADIO_STATE;
+    addition[0]->cids[2] = MBIM_CID_BASIC_CONNECT_SIGNAL_STATE;
+    addition[0]->cids[3] = MBIM_CID_BASIC_CONNECT_IP_CONFIGURATION;
+    addition[0]->cids[4] = MBIM_CID_BASIC_CONNECT_NETWORK_IDLE_HINT;
+    addition[1] = g_new0 (MbimEventEntry, 1);
+    memcpy (&addition[1]->device_service_id, MBIM_UUID_SMS, sizeof (MbimUuid));
+    addition[1]->cids_count = 2;
+    addition[1]->cids = g_new0 (guint32, addition[1]->cids_count);
+    addition[1]->cids[0] = MBIM_CID_SMS_READ;
+    addition[1]->cids[1] = MBIM_CID_SMS_SEND;
+
+    /* merge */
+    list = _mbim_proxy_helper_service_subscribe_list_merge (list, list_size, addition, addition_size, &out_size);
+
+    /* Now, as we added a subset of the elements of the standard list to the
+     * full standard list, we should still get as output the full standard list
+     */
+    check_standard_list (list, out_size);
+
+    mbim_event_entry_array_free (list);
+    mbim_event_entry_array_free (addition);
+}
+
+static void
+test_merge_standard_list_full_full (void)
+{
+    MbimEventEntry **list;
+    gsize list_size = 0;
+    MbimEventEntry **addition;
+    gsize addition_size = 0;
+    gsize out_size = 0;
+
+    /* list with all standard services */
+    list = _mbim_proxy_helper_service_subscribe_standard_list_new (&list_size);
+    /* again, list with all standard services */
+    addition = _mbim_proxy_helper_service_subscribe_standard_list_new (&addition_size);
+
+    /* merge */
+    list = _mbim_proxy_helper_service_subscribe_list_merge (list, list_size, addition, addition_size, &out_size);
+
+    /* Now, as we added a subset of the elements of the standard list to the
+     * full standard list, we should still get as output the full standard list
+     */
+    check_standard_list (list, out_size);
+
+    mbim_event_entry_array_free (list);
+    mbim_event_entry_array_free (addition);
+}
+
+static void
+test_merge_standard_list_subset_full (void)
+{
+    MbimEventEntry **list;
+    gsize list_size;
+    MbimEventEntry **addition;
+    gsize addition_size = 0;
+    gsize out_size = 0;
+
+    /* setup a new list with a subset of standard services */
+    list_size = 2;
+    list = g_new0 (MbimEventEntry *, list_size + 1);
+    list[0] = g_new0 (MbimEventEntry, 1);
+    memcpy (&list[0]->device_service_id, MBIM_UUID_BASIC_CONNECT, sizeof (MbimUuid));
+    list[0]->cids_count = 5;
+    list[0]->cids = g_new0 (guint32, list[0]->cids_count);
+    list[0]->cids[0] = MBIM_CID_BASIC_CONNECT_SUBSCRIBER_READY_STATUS;
+    list[0]->cids[1] = MBIM_CID_BASIC_CONNECT_RADIO_STATE;
+    list[0]->cids[2] = MBIM_CID_BASIC_CONNECT_SIGNAL_STATE;
+    list[0]->cids[3] = MBIM_CID_BASIC_CONNECT_IP_CONFIGURATION;
+    list[0]->cids[4] = MBIM_CID_BASIC_CONNECT_NETWORK_IDLE_HINT;
+    list[1] = g_new0 (MbimEventEntry, 1);
+    memcpy (&list[1]->device_service_id, MBIM_UUID_SMS, sizeof (MbimUuid));
+    list[1]->cids_count = 2;
+    list[1]->cids = g_new0 (guint32, list[1]->cids_count);
+    list[1]->cids[0] = MBIM_CID_SMS_READ;
+    list[1]->cids[1] = MBIM_CID_SMS_SEND;
+
+    /* list with all standard services */
+    addition = _mbim_proxy_helper_service_subscribe_standard_list_new (&addition_size);
+
+    /* merge */
+    list = _mbim_proxy_helper_service_subscribe_list_merge (list, list_size, addition, addition_size, &out_size);
+
+    /* Now, as we added the full standard list to a subset, we should still get
+     * as output the full standard list */
+    check_standard_list (list, out_size);
+
+    mbim_event_entry_array_free (list);
+    mbim_event_entry_array_free (addition);
+}
+
+static void
+test_merge_standard_list_none_full (void)
+{
+    MbimEventEntry **list, **merged_list;
+    gsize addition_size = 0;
+    gsize out_size = 0;
+
+    /* list with all standard services */
+    list = _mbim_proxy_helper_service_subscribe_standard_list_new (&addition_size);
+
+    /* merge */
+    merged_list = _mbim_proxy_helper_service_subscribe_list_merge (NULL, 0, list, addition_size, &out_size);
+
+    check_standard_list (merged_list, out_size);
+
+    mbim_event_entry_array_free (list);
+    mbim_event_entry_array_free (merged_list);
+}
+
+static void
+test_merge_list_same_service (void)
+{
+    MbimEventEntry **list;
+    gsize list_size;
+    MbimEventEntry **addition;
+    gsize addition_size;
+    MbimEventEntry **expected;
+    gsize expected_size;
+    gsize out_size = 0;
+
+    /* setup a new list with a subset of standard services */
+    list_size = 1;
+    list = g_new0 (MbimEventEntry *, list_size + 1);
+    list[0] = g_new0 (MbimEventEntry, 1);
+    memcpy (&list[0]->device_service_id, MBIM_UUID_BASIC_CONNECT, sizeof (MbimUuid));
+    list[0]->cids_count = 2;
+    list[0]->cids = g_new0 (guint32, list[0]->cids_count);
+    list[0]->cids[0] = MBIM_CID_BASIC_CONNECT_IP_CONFIGURATION;
+    list[0]->cids[1] = MBIM_CID_BASIC_CONNECT_NETWORK_IDLE_HINT;
+
+    /* setup a new list with a subset of standard services */
+    addition_size = 1;
+    addition = g_new0 (MbimEventEntry *, addition_size + 1);
+    addition[0] = g_new0 (MbimEventEntry, 1);
+    memcpy (&addition[0]->device_service_id, MBIM_UUID_BASIC_CONNECT, sizeof (MbimUuid));
+    addition[0]->cids_count = 3;
+    addition[0]->cids = g_new0 (guint32, addition[0]->cids_count);
+    addition[0]->cids[0] = MBIM_CID_BASIC_CONNECT_SUBSCRIBER_READY_STATUS;
+    addition[0]->cids[1] = MBIM_CID_BASIC_CONNECT_RADIO_STATE;
+    addition[0]->cids[2] = MBIM_CID_BASIC_CONNECT_SIGNAL_STATE;
+
+    /* merge */
+    list = _mbim_proxy_helper_service_subscribe_list_merge (list, list_size, addition, addition_size, &out_size);
+
+    /* setup the expected list */
+    expected_size = 1;
+    expected = g_new0 (MbimEventEntry *, expected_size + 1);
+    expected[0] = g_new0 (MbimEventEntry, 1);
+    memcpy (&expected[0]->device_service_id, MBIM_UUID_BASIC_CONNECT, sizeof (MbimUuid));
+    expected[0]->cids_count = 5;
+    expected[0]->cids = g_new0 (guint32, expected[0]->cids_count);
+    expected[0]->cids[0] = MBIM_CID_BASIC_CONNECT_SUBSCRIBER_READY_STATUS;
+    expected[0]->cids[1] = MBIM_CID_BASIC_CONNECT_RADIO_STATE;
+    expected[0]->cids[2] = MBIM_CID_BASIC_CONNECT_SIGNAL_STATE;
+    expected[0]->cids[3] = MBIM_CID_BASIC_CONNECT_IP_CONFIGURATION;
+    expected[0]->cids[4] = MBIM_CID_BASIC_CONNECT_NETWORK_IDLE_HINT;
+
+    /* Compare */
+    g_assert (_mbim_proxy_helper_service_subscribe_list_cmp ((const MbimEventEntry * const *)list, out_size,
+                                                             (const MbimEventEntry * const *)expected, expected_size));
+
+    mbim_event_entry_array_free (list);
+    mbim_event_entry_array_free (addition);
+    mbim_event_entry_array_free (expected);
+}
+
+static void
+test_merge_list_different_services (void)
+{
+    MbimEventEntry **list;
+    gsize list_size;
+    MbimEventEntry **addition;
+    gsize addition_size;
+    MbimEventEntry **expected;
+    gsize expected_size;
+    gsize out_size = 0;
+
+    /* setup a new list with a subset of standard services */
+    list_size = 1;
+    list = g_new0 (MbimEventEntry *, list_size + 1);
+    list[0] = g_new0 (MbimEventEntry, 1);
+    memcpy (&list[0]->device_service_id, MBIM_UUID_BASIC_CONNECT, sizeof (MbimUuid));
+    list[0]->cids_count = 5;
+    list[0]->cids = g_new0 (guint32, list[0]->cids_count);
+    list[0]->cids[0] = MBIM_CID_BASIC_CONNECT_IP_CONFIGURATION;
+    list[0]->cids[1] = MBIM_CID_BASIC_CONNECT_NETWORK_IDLE_HINT;
+    list[0]->cids[2] = MBIM_CID_BASIC_CONNECT_SUBSCRIBER_READY_STATUS;
+    list[0]->cids[3] = MBIM_CID_BASIC_CONNECT_RADIO_STATE;
+    list[0]->cids[4] = MBIM_CID_BASIC_CONNECT_SIGNAL_STATE;
+
+    /* setup a new list with a subset of standard services */
+    addition_size = 1;
+    addition = g_new0 (MbimEventEntry *, addition_size + 1);
+    addition[0] = g_new0 (MbimEventEntry, 1);
+    memcpy (&addition[0]->device_service_id, MBIM_UUID_SMS, sizeof (MbimUuid));
+    addition[0]->cids_count = 2;
+    addition[0]->cids = g_new0 (guint32, addition[0]->cids_count);
+    addition[0]->cids[0] = MBIM_CID_SMS_READ;
+    addition[0]->cids[1] = MBIM_CID_SMS_SEND;
+
+    /* merge */
+    list = _mbim_proxy_helper_service_subscribe_list_merge (list, list_size, addition, addition_size, &out_size);
+
+    /* setup the expected list */
+    expected_size = 2;
+    expected = g_new0 (MbimEventEntry *, expected_size + 1);
+    expected[0] = g_new0 (MbimEventEntry, 1);
+    memcpy (&expected[0]->device_service_id, MBIM_UUID_BASIC_CONNECT, sizeof (MbimUuid));
+    expected[0]->cids_count = 5;
+    expected[0]->cids = g_new0 (guint32, expected[0]->cids_count);
+    expected[0]->cids[0] = MBIM_CID_BASIC_CONNECT_SUBSCRIBER_READY_STATUS;
+    expected[0]->cids[1] = MBIM_CID_BASIC_CONNECT_RADIO_STATE;
+    expected[0]->cids[2] = MBIM_CID_BASIC_CONNECT_SIGNAL_STATE;
+    expected[0]->cids[3] = MBIM_CID_BASIC_CONNECT_IP_CONFIGURATION;
+    expected[0]->cids[4] = MBIM_CID_BASIC_CONNECT_NETWORK_IDLE_HINT;
+    expected[1] = g_new0 (MbimEventEntry, 1);
+    memcpy (&expected[1]->device_service_id, MBIM_UUID_SMS, sizeof (MbimUuid));
+    expected[1]->cids_count = 2;
+    expected[1]->cids = g_new0 (guint32, expected[1]->cids_count);
+    expected[1]->cids[0] = MBIM_CID_SMS_READ;
+    expected[1]->cids[1] = MBIM_CID_SMS_SEND;
+
+    /* Compare */
+    g_assert (_mbim_proxy_helper_service_subscribe_list_cmp ((const MbimEventEntry * const *)list, out_size,
+                                                             (const MbimEventEntry * const *)expected, expected_size));
+
+    mbim_event_entry_array_free (list);
+    mbim_event_entry_array_free (addition);
+    mbim_event_entry_array_free (expected);
+}
+
+static void
+test_merge_list_merged_services (void)
+{
+    MbimEventEntry **list;
+    gsize list_size;
+    MbimEventEntry **addition;
+    gsize addition_size;
+    MbimEventEntry **expected;
+    gsize expected_size;
+    gsize out_size = 0;
+
+    /* setup a new list with a subset of standard services */
+    list_size = 2;
+    list = g_new0 (MbimEventEntry *, list_size + 1);
+    list[0] = g_new0 (MbimEventEntry, 1);
+    memcpy (&list[0]->device_service_id, MBIM_UUID_BASIC_CONNECT, sizeof (MbimUuid));
+    list[0]->cids_count = 3;
+    list[0]->cids = g_new0 (guint32, list[0]->cids_count);
+    list[0]->cids[0] = MBIM_CID_BASIC_CONNECT_SUBSCRIBER_READY_STATUS;
+    list[0]->cids[1] = MBIM_CID_BASIC_CONNECT_RADIO_STATE;
+    list[0]->cids[2] = MBIM_CID_BASIC_CONNECT_SIGNAL_STATE;
+    list[1] = g_new0 (MbimEventEntry, 1);
+    memcpy (&list[1]->device_service_id, MBIM_UUID_SMS, sizeof (MbimUuid));
+    list[1]->cids_count = 1;
+    list[1]->cids = g_new0 (guint32, list[1]->cids_count);
+    list[1]->cids[0] = MBIM_CID_SMS_READ;
+
+    /* setup a new list with a subset of standard services */
+    addition_size = 2;
+    addition = g_new0 (MbimEventEntry *, addition_size + 1);
+    addition[0] = g_new0 (MbimEventEntry, 1);
+    memcpy (&addition[0]->device_service_id, MBIM_UUID_BASIC_CONNECT, sizeof (MbimUuid));
+    addition[0]->cids_count = 2;
+    addition[0]->cids = g_new0 (guint32, addition[0]->cids_count);
+    addition[0]->cids[0] = MBIM_CID_BASIC_CONNECT_IP_CONFIGURATION;
+    addition[0]->cids[1] = MBIM_CID_BASIC_CONNECT_NETWORK_IDLE_HINT;
+    addition[1] = g_new0 (MbimEventEntry, 1);
+    memcpy (&addition[1]->device_service_id, MBIM_UUID_SMS, sizeof (MbimUuid));
+    addition[1]->cids_count = 1;
+    addition[1]->cids = g_new0 (guint32, addition[1]->cids_count);
+    addition[1]->cids[0] = MBIM_CID_SMS_SEND;
+
+    /* merge */
+    list = _mbim_proxy_helper_service_subscribe_list_merge (list, list_size, addition, addition_size, &out_size);
+
+    /* setup the expected list */
+    expected_size = 2;
+    expected = g_new0 (MbimEventEntry *, expected_size + 1);
+    expected[0] = g_new0 (MbimEventEntry, 1);
+    memcpy (&expected[0]->device_service_id, MBIM_UUID_BASIC_CONNECT, sizeof (MbimUuid));
+    expected[0]->cids_count = 5;
+    expected[0]->cids = g_new0 (guint32, expected[0]->cids_count);
+    expected[0]->cids[0] = MBIM_CID_BASIC_CONNECT_SUBSCRIBER_READY_STATUS;
+    expected[0]->cids[1] = MBIM_CID_BASIC_CONNECT_RADIO_STATE;
+    expected[0]->cids[2] = MBIM_CID_BASIC_CONNECT_SIGNAL_STATE;
+    expected[0]->cids[3] = MBIM_CID_BASIC_CONNECT_IP_CONFIGURATION;
+    expected[0]->cids[4] = MBIM_CID_BASIC_CONNECT_NETWORK_IDLE_HINT;
+    expected[1] = g_new0 (MbimEventEntry, 1);
+    memcpy (&expected[1]->device_service_id, MBIM_UUID_SMS, sizeof (MbimUuid));
+    expected[1]->cids_count = 2;
+    expected[1]->cids = g_new0 (guint32, expected[1]->cids_count);
+    expected[1]->cids[0] = MBIM_CID_SMS_READ;
+    expected[1]->cids[1] = MBIM_CID_SMS_SEND;
+
+    /* Compare */
+    g_assert (_mbim_proxy_helper_service_subscribe_list_cmp ((const MbimEventEntry * const *)list, out_size,
+                                                             (const MbimEventEntry * const *)expected, expected_size));
+
+    mbim_event_entry_array_free (list);
+    mbim_event_entry_array_free (addition);
+    mbim_event_entry_array_free (expected);
+}
+
+/*****************************************************************************/
+
+int main (int argc, char **argv)
+{
+    g_test_init (&argc, &argv, NULL);
+
+    g_test_add_func ("/libmbim-glib/proxy/standard-list",              test_standard_list);
+    g_test_add_func ("/libmbim-glib/proxy/parse/single-service/0",     test_parse_single_service_0_cids);
+    g_test_add_func ("/libmbim-glib/proxy/parse/single-service/1",     test_parse_single_service_1_cids);
+    g_test_add_func ("/libmbim-glib/proxy/parse/single-service/5",     test_parse_single_service_5_cids);
+    g_test_add_func ("/libmbim-glib/proxy/merge/standard/full_none",   test_merge_standard_list_full_none);
+    g_test_add_func ("/libmbim-glib/proxy/merge/standard/full_subset", test_merge_standard_list_full_subset);
+    g_test_add_func ("/libmbim-glib/proxy/merge/standard/full_full",   test_merge_standard_list_full_full);
+    g_test_add_func ("/libmbim-glib/proxy/merge/standard/subset_full", test_merge_standard_list_subset_full);
+    g_test_add_func ("/libmbim-glib/proxy/merge/standard/none_full",   test_merge_standard_list_none_full);
+    g_test_add_func ("/libmbim-glib/proxy/merge/same-service",         test_merge_list_same_service);
+    g_test_add_func ("/libmbim-glib/proxy/merge/different-services",   test_merge_list_different_services);
+    g_test_add_func ("/libmbim-glib/proxy/merge/merged-services",      test_merge_list_merged_services);
+
+    return g_test_run ();
+}
diff --git a/src/mbim-proxy/Makefile.am b/src/mbim-proxy/Makefile.am
new file mode 100644
index 0000000..83a8ce4
--- /dev/null
+++ b/src/mbim-proxy/Makefile.am
@@ -0,0 +1,16 @@
+
+libexec_PROGRAMS = mbim-proxy
+
+mbim_proxy_CPPFLAGS = \
+	$(MBIMPROXY_CFLAGS) \
+	-I$(top_srcdir) \
+	-I$(top_srcdir)/src/libmbim-glib \
+	-I$(top_srcdir)/src/libmbim-glib/generated \
+	-I$(top_builddir)/src/libmbim-glib \
+	-I$(top_builddir)/src/libmbim-glib/generated
+
+mbim_proxy_SOURCES = mbim-proxy.c
+
+mbim_proxy_LDADD = \
+	$(MBIMPROXY_LIBS) \
+	$(top_builddir)/src/libmbim-glib/libmbim-glib.la
diff --git a/src/mbim-proxy/mbim-proxy.c b/src/mbim-proxy/mbim-proxy.c
new file mode 100644
index 0000000..f29303f
--- /dev/null
+++ b/src/mbim-proxy/mbim-proxy.c
@@ -0,0 +1,252 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * mbim-proxy -- A proxy to communicate with MBIM ports
+ *
+ * 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 2 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/>.
+ *
+ * Copyright (C) 2014 Aleksander Morgado <aleksander@gnu.org>
+ * Copyright (C) 2014 Smith Micro Software, Inc.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <locale.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <gio/gio.h>
+#include <glib-unix.h>
+
+#include <libmbim-glib.h>
+
+#define PROGRAM_NAME    "mbim-proxy"
+#define PROGRAM_VERSION PACKAGE_VERSION
+
+#define EMPTY_PROXY_LIFETIME_SECS 30
+
+/* Globals */
+static GMainLoop *loop;
+static MbimProxy *proxy;
+static guint timeout_id;
+static guint client_connected_once = FALSE;
+
+/* Main options */
+static gboolean verbose_flag;
+static gboolean version_flag;
+
+static GOptionEntry main_entries[] = {
+    { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose_flag,
+      "Run action with verbose logs, including the debug ones",
+      NULL
+    },
+    { "version", 'V', 0, G_OPTION_ARG_NONE, &version_flag,
+      "Print version",
+      NULL
+    },
+    { NULL }
+};
+
+static gboolean
+quit_cb (gpointer user_data)
+{
+    if (loop) {
+        g_warning ("Caught signal, stopping the loop...");
+        g_idle_add ((GSourceFunc) g_main_loop_quit, loop);
+    }
+
+    return FALSE;
+}
+
+static void
+log_handler (const gchar *log_domain,
+             GLogLevelFlags log_level,
+             const gchar *message,
+             gpointer user_data)
+{
+    const gchar *log_level_str;
+    time_t now;
+    gchar time_str[64];
+    struct tm *local_time;
+    gboolean err;
+
+    now = time ((time_t *) NULL);
+    local_time = localtime (&now);
+    strftime (time_str, 64, "%d %b %Y, %H:%M:%S", local_time);
+    err = FALSE;
+
+    switch (log_level) {
+    case G_LOG_LEVEL_WARNING:
+        log_level_str = "-Warning **";
+        err = TRUE;
+        break;
+
+    case G_LOG_LEVEL_CRITICAL:
+    case G_LOG_FLAG_FATAL:
+    case G_LOG_LEVEL_ERROR:
+        log_level_str = "-Error **";
+        err = TRUE;
+        break;
+
+    case G_LOG_LEVEL_DEBUG:
+        log_level_str = "[Debug]";
+        break;
+
+    default:
+        log_level_str = "";
+        break;
+    }
+
+    if (!verbose_flag && !err)
+        return;
+
+    g_fprintf (err ? stderr : stdout,
+               "[%s] %s %s\n",
+               time_str,
+               log_level_str,
+               message);
+}
+
+static void
+print_version_and_exit (void)
+{
+    g_print ("\n"
+             PROGRAM_NAME " " PROGRAM_VERSION "\n"
+             "Copyright (C) 2013 Aleksander Morgado\n"
+             "Copyright (C) 2014 Greg Suarez\n"
+             "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl-2.0.html>\n"
+             "This is free software: you are free to change and redistribute it.\n"
+             "There is NO WARRANTY, to the extent permitted by law.\n"
+             "\n");
+    exit (EXIT_SUCCESS);
+}
+
+/*****************************************************************************/
+
+static gboolean
+stop_loop_cb (void)
+{
+    timeout_id = 0;
+    if (loop)
+        g_main_loop_quit (loop);
+    return FALSE;
+}
+
+static void
+proxy_n_clients_changed (MbimProxy *_proxy)
+{
+    /* once a client has connected only exit if there are no devices */
+    if (client_connected_once)
+        return;
+
+    if (mbim_proxy_get_n_clients (proxy) == 0) {
+        g_assert (timeout_id == 0);
+        timeout_id = g_timeout_add_seconds (EMPTY_PROXY_LIFETIME_SECS,
+                                            (GSourceFunc)stop_loop_cb,
+                                            NULL);
+        return;
+    }
+
+    /* At least one client, remove timeout if any */
+    if (timeout_id) {
+        g_source_remove (timeout_id);
+        timeout_id = 0;
+    }
+
+    client_connected_once = TRUE;
+}
+
+static void
+proxy_n_devices_changed (MbimProxy *_proxy)
+{
+    if (mbim_proxy_get_n_devices (proxy) == 0) {
+        g_assert (timeout_id == 0);
+        timeout_id = g_timeout_add_seconds (EMPTY_PROXY_LIFETIME_SECS,
+                                            (GSourceFunc)stop_loop_cb,
+                                            NULL);
+        return;
+    }
+
+    /* At least one device, remove timeout if any */
+    if (timeout_id) {
+        g_source_remove (timeout_id);
+        timeout_id = 0;
+    }
+}
+
+/*****************************************************************************/
+
+int main (int argc, char **argv)
+{
+    GError *error = NULL;
+    GOptionContext *context;
+
+    setlocale (LC_ALL, "");
+
+    g_type_init ();
+
+    /* Setup option context, process it and destroy it */
+    context = g_option_context_new ("- Proxy for MBIM devices");
+    g_option_context_add_main_entries (context, main_entries, NULL);
+    if (!g_option_context_parse (context, &argc, &argv, &error)) {
+        g_printerr ("error: %s\n",
+                    error->message);
+        exit (EXIT_FAILURE);
+    }
+    g_option_context_free (context);
+
+    if (version_flag)
+        print_version_and_exit ();
+
+    g_log_set_handler (NULL,  G_LOG_LEVEL_MASK, log_handler, NULL);
+    g_log_set_handler ("Mbim", G_LOG_LEVEL_MASK, log_handler, NULL);
+    if (verbose_flag)
+        mbim_utils_set_traces_enabled (TRUE);
+
+    /* Setup signals */
+    g_unix_signal_add (SIGINT,  quit_cb, NULL);
+    g_unix_signal_add (SIGHUP,  quit_cb, NULL);
+    g_unix_signal_add (SIGTERM, quit_cb, NULL);
+
+    /* Setup proxy */
+    proxy = mbim_proxy_new (&error);
+    if (!proxy) {
+        g_printerr ("error: %s\n", error->message);
+        exit (EXIT_FAILURE);
+    }
+
+    proxy_n_clients_changed (proxy);
+    g_signal_connect (proxy,
+                      "notify::" MBIM_PROXY_N_CLIENTS,
+                      G_CALLBACK (proxy_n_clients_changed),
+                      NULL);
+    g_signal_connect (proxy,
+                      "notify::" MBIM_PROXY_N_DEVICES,
+                      G_CALLBACK (proxy_n_devices_changed),
+                      NULL);
+
+    /* Loop */
+    loop = g_main_loop_new (NULL, FALSE);
+    g_main_loop_run (loop);
+    g_main_loop_unref (loop);
+
+    /* Cleanup; releases socket and such */
+    g_object_unref (proxy);
+
+    g_debug ("exiting 'mbim-proxy'...");
+
+    return EXIT_SUCCESS;
+}
diff --git a/src/mbimcli/mbimcli.c b/src/mbimcli/mbimcli.c
index b299a37..be5c853 100644
--- a/src/mbimcli/mbimcli.c
+++ b/src/mbimcli/mbimcli.c
@@ -47,6 +47,7 @@
 
 /* Main options */
 static gchar *device_str;
+static gboolean device_open_proxy_flag;
 static gchar *no_open_str;
 static gboolean no_close_flag;
 static gboolean noop_flag;
@@ -59,6 +60,10 @@
       "Specify device path",
       "[PATH]"
     },
+    { "device-open-proxy", 'p', 0, G_OPTION_ARG_NONE, &device_open_proxy_flag,
+      "Request to use the 'mbim-proxy' proxy",
+      NULL
+    },
     { "no-open", 0, 0, G_OPTION_ARG_STRING, &no_open_str,
       "Do not explicitly open the MBIM device before running the command",
       "[Transaction ID]"
@@ -277,6 +282,7 @@
                   GAsyncResult *res)
 {
     GError *error = NULL;
+    MbimDeviceOpenFlags open_flags = MBIM_DEVICE_OPEN_FLAGS_NONE;
 
     device = mbim_device_new_finish (res, &error);
     if (!device) {
@@ -301,12 +307,17 @@
                       NULL);
     }
 
+    /* Setup device open flags */
+    if (device_open_proxy_flag)
+        open_flags |= MBIM_DEVICE_OPEN_FLAGS_PROXY;
+
     /* Open the device */
-    mbim_device_open (device,
-                      30,
-                      cancellable,
-                      (GAsyncReadyCallback) device_open_ready,
-                      NULL);
+    mbim_device_open_full (device,
+                           open_flags,
+                           30,
+                           cancellable,
+                           (GAsyncReadyCallback) device_open_ready,
+                           NULL);
 }
 
 /*****************************************************************************/