[PATCH] libmbim: Add mbim-proxy support

Greg Suarez gpsuarez2512 at gmail.com
Wed Apr 16 09:48:25 PDT 2014


This patch implements the mbim-proxy support to allow mulitple clients
to have concurrent access to the MBIM device through libmbim.

The implementation is heavily based on qmi-proxy.

The function mbim_device_open_full() is provided for clients to specify that
the device should be opened through the mbim-proxy via the flag
MBIM_DEVICE_OPEN_FLAGS_PROXY.

The function mbim_device_open() will behave as it has before and open the
device directly.

Proxy Control:
A new service UUID is created to allow for clients to control the proxy.
This service is currently only used to pass on the device path (/dev/cdc-wdm*)
the client is requesting to open. In the future CIDs will be added to allow
for setting the MaxControlMessage size and setting timeout for specific
messages.

Unknown UUIDs:
Currently the mbim-proxy will allow messages with unknown UUIDs to pass through.
The proxy will track unknown UUIDs sent by the client and device notifications
matching the tracked UUID will be sent to the client.  Device notifications
of known UUIDs will be sent to all connected clients.

Signed-off-by: Greg Suarez <gsuarez at smithmicro.com>
---
 .gitignore                              |  10 +-
 configure.ac                            |  10 +
 data/Makefile.am                        |   3 +-
 data/mbim-service-proxy-control.json    |  15 +
 src/Makefile.am                         |   2 +-
 src/libmbim-glib/Makefile.am            |   7 +-
 src/libmbim-glib/generated/Makefile.am  |  18 +-
 src/libmbim-glib/libmbim-glib.h         |   1 +
 src/libmbim-glib/mbim-cid.c             |  22 +-
 src/libmbim-glib/mbim-cid.h             |  12 +
 src/libmbim-glib/mbim-device.c          | 447 ++++++++++++---
 src/libmbim-glib/mbim-device.h          |  20 +
 src/libmbim-glib/mbim-message-private.h |   4 +
 src/libmbim-glib/mbim-message.c         |  56 +-
 src/libmbim-glib/mbim-message.h         |  16 +-
 src/libmbim-glib/mbim-proxy.c           | 938 ++++++++++++++++++++++++++++++++
 src/libmbim-glib/mbim-proxy.h           |  59 ++
 src/libmbim-glib/mbim-uuid.c            |  16 +-
 src/libmbim-glib/mbim-uuid.h            |  11 +
 src/mbim-proxy/Makefile.am              |  16 +
 src/mbim-proxy/mbim-proxy.c             | 223 ++++++++
 src/mbimcli/mbimcli.c                   |  11 +-
 22 files changed, 1804 insertions(+), 113 deletions(-)
 create mode 100644 data/mbim-service-proxy-control.json
 create mode 100644 src/libmbim-glib/mbim-proxy.c
 create mode 100644 src/libmbim-glib/mbim-proxy.h
 create mode 100644 src/mbim-proxy/Makefile.am
 create mode 100644 src/mbim-proxy/mbim-proxy.c

diff --git a/.gitignore b/.gitignore
index 8cb5c2a..c0aac51 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,11 @@
 *.lo
 *.la
 *.bz2
+*.swp
+tags
+.*DS_Store
+.deps
+.libs
 
 ChangeLog
 INSTALL
@@ -78,4 +83,7 @@ src/mbimcli/.deps
 src/mbimcli/.libs
 src/mbimcli/mbimcli
 
-utils/mbim-network
\ No newline at end of file
+utils/mbim-network
+
+src/mbim-proxy/mbim-proxy
+.__autoconf_trace_data
diff --git a/configure.ac b/configure.ac
index 56370aa..754b4bc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -69,6 +69,7 @@ PKG_CHECK_MODULES(LIBMBIM_GLIB,
                   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 @@ PKG_CHECK_MODULES(MBIMCLI,
 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 @@ AC_CONFIG_FILES([Makefile
                  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..21c02a9 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -9,4 +9,5 @@ EXTRA_DIST = \
 	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.json
diff --git a/data/mbim-service-proxy-control.json b/data/mbim-service-proxy-control.json
new file mode 100644
index 0000000..391ffcd
--- /dev/null
+++ b/data/mbim-service-proxy-control.json
@@ -0,0 +1,15 @@
+
+[
+  // *********************************************************************************
+  { "type" : "Service",
+    "name" : "Proxy Control" },
+
+  // *********************************************************************************
+  { "name"     : "Configuration",
+    "service"  : "Proxy Control",
+    "type"     : "Command",
+    "set"      : [ { "name"   : "DevicePath",
+                     "format" : "string" } ],
+    "response" : [] }
+
+]
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..62f3405 100644
--- a/src/libmbim-glib/Makefile.am
+++ b/src/libmbim-glib/Makefile.am
@@ -12,6 +12,7 @@ libmbim_glib_core_la_CPPFLAGS = \
 	-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,8 @@ libmbim_glib_core_la_SOURCES = \
 	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
 
 # Final installable library
 lib_LTLIBRARIES = libmbim-glib.la
@@ -50,7 +52,8 @@ include_HEADERS = \
 	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 @@ GENERATED_H = \
 	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 @@ GENERATED_C = \
 	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 @@ GENERATED_SECTIONS = \
 	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 @@ mbim-ms-host-shutdown.h mbim-ms-host-shutdown.c mbim-ms-host-shutdown.sections:
 			--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 25e14fd..b16c46b 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 944cd98..1456ad5 100644
--- a/src/libmbim-glib/mbim-cid.c
+++ b/src/libmbim-glib/mbim-cid.c
@@ -126,6 +126,11 @@ static const CidConfig cid_ms_host_shutdown_config [MBIM_CID_MS_HOST_SHUTDOWN_LA
     { TRUE,  FALSE, FALSE }, /* MBIM_CID_MS_HOST_SHUTDOWN_NOTIFY */
 };
 
+#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 +148,7 @@ mbim_cid_can_set (MbimService service,
     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 +169,8 @@ mbim_cid_can_set (MbimService service,
         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 +194,7 @@ mbim_cid_can_query (MbimService service,
     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 +215,8 @@ mbim_cid_can_query (MbimService service,
         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 +240,7 @@ mbim_cid_can_notify (MbimService service,
     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 +261,8 @@ mbim_cid_can_notify (MbimService service,
         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 +287,7 @@ mbim_cid_get_printable (MbimService service,
     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 +308,8 @@ mbim_cid_get_printable (MbimService service,
         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 8a579ad..2a12f98 100644
--- a/src/libmbim-glib/mbim-cid.h
+++ b/src/libmbim-glib/mbim-cid.h
@@ -207,6 +207,18 @@ typedef enum {
     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 6f965e9..7d8810c 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 Aleksander Morgado <aleksander at lanedo.com>
+ * Copyright (C) 2014 Greg Suarez <gsuarez at smithmicro.com>
  *
  * 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
@@ -89,6 +93,10 @@ struct _MbimDevicePrivate {
     guint watch_id;
     GByteArray *response;
 
+    /* Support for mbim-proxy */
+    GSocketClient *socket_client;
+    GSocketConnection *socket_connection;
+
     /* HT to keep track of ongoing host/function transactions
      *  Host transactions:  created by us
      *  Modem transactions: modem-created indications with multiple fragments
@@ -208,6 +216,10 @@ transaction_timed_out (TransactionWaitContext *ctx)
     GError *error = NULL;
 
     tr = device_release_transaction (ctx->self, ctx->type, 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 */
@@ -774,88 +786,242 @@ out:
     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;
-    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;
+    /* 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;
     }
 
-    g_assert (self->priv->file);
-    g_assert (self->priv->path);
+    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;
 
     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 */
+    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 */
+    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);
+    g_object_ref (ctx->self->priv->socket_connection);
+    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;
 
@@ -870,6 +1036,8 @@ device_open_context_complete_and_free (DeviceOpenContext *ctx)
     g_slice_free (DeviceOpenContext, ctx);
 }
 
+static void device_open_context_step (DeviceOpenContext *ctx);
+
 /**
  * mbim_device_open_finish:
  * @self: a #MbimDevice.
@@ -921,7 +1089,9 @@ open_message_ready (MbimDevice        *self,
 
     if (response)
         mbim_message_unref (response);
-    device_open_context_complete_and_free (ctx);
+
+    ctx->step++;
+    device_open_context_step (ctx);
 }
 
 static void
@@ -941,6 +1111,131 @@ open_message (DeviceOpenContext *ctx)
     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)
+        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, &error);
+
+    mbim_device_command (ctx->self,
+                         request,
+                         1,
+                         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_simple_async_result_take_error (ctx->result, error);
+        device_open_context_complete_and_free (ctx);
+        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:
+        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) {
+            g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+            device_open_context_complete_and_free (ctx);
+            return;
+        }
+
+        open_message (ctx);
+        return;
+
+     case DEVICE_OPEN_CONTEXT_STEP_LAST:
+        /* Nothing else to process, send the open message */
+        device_open_context_complete_and_free (ctx);
+        return;
+
+    default:
+        break;
+    }
+
+    g_assert_not_reached ();
+}
+
+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.
@@ -961,38 +1256,14 @@ mbim_device_open (MbimDevice          *self,
                   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);
 }
+                       
 
 /*****************************************************************************/
 /* Close channel */
@@ -1003,11 +1274,19 @@ destroy_iochannel (MbimDevice  *self,
 {
     GError *inner_error = NULL;
 
-    g_io_channel_shutdown (self->priv->iochannel, TRUE, &inner_error);
+    /* 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);
@@ -1041,11 +1320,6 @@ mbim_device_close_force (MbimDevice *self,
                          GError **error)
 {
     g_return_val_if_fail (MBIM_IS_DEVICE (self), FALSE);
-
-    /* Already closed? */
-    if (!self->priv->iochannel)
-        return TRUE;
-
     return destroy_iochannel (self, error);
 }
 
@@ -1739,6 +2013,7 @@ finalize (GObject *object)
         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;
         }
     }
 
diff --git a/src/libmbim-glib/mbim-device.h b/src/libmbim-glib/mbim-device.h
index 963571f..b098a4b 100644
--- a/src/libmbim-glib/mbim-device.h
+++ b/src/libmbim-glib/mbim-device.h
@@ -52,6 +52,8 @@ typedef struct _MbimDevicePrivate MbimDevicePrivate;
 #define MBIM_DEVICE_SIGNAL_INDICATE_STATUS "device-indicate-status"
 #define MBIM_DEVICE_SIGNAL_ERROR           "device-error"
 
+#define MAX_SPAWN_RETRIES 10
+
 /**
  * MbimDevice:
  *
@@ -84,6 +86,24 @@ const gchar *mbim_device_get_path         (MbimDevice *self);
 const gchar *mbim_device_get_path_display (MbimDevice *self);
 gboolean     mbim_device_is_open          (MbimDevice *self);
 
+/**
+ * MbimDeviceOpenFlags:
+ * @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);
+
 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 2bc64a0..87a55c3 100644
--- a/src/libmbim-glib/mbim-message-private.h
+++ b/src/libmbim-glib/mbim-message-private.h
@@ -129,6 +129,10 @@ struct full_message {
 } __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 7078129..b027b04 100644
--- a/src/libmbim-glib/mbim-message.c
+++ b/src/libmbim-glib/mbim-message.c
@@ -62,7 +62,7 @@ mbim_message_get_type (void)
 
 /*****************************************************************************/
 
-static GByteArray *
+GByteArray *
 _mbim_message_allocate (MbimMessageType message_type,
                         guint32         transaction_id,
                         guint32         additional_size)
@@ -1518,6 +1518,32 @@ mbim_message_open_get_max_control_transfer (const MbimMessage *self)
 /* '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 +1614,32 @@ mbim_message_close_new (guint32 transaction_id)
 /* '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 +1790,7 @@ mbim_message_command_new (guint32                transaction_id,
 
     /* 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 d5ac8e5..1dbe002 100644
--- a/src/libmbim-glib/mbim-message.h
+++ b/src/libmbim-glib/mbim-message.h
@@ -131,9 +131,11 @@ guint32      mbim_message_open_get_max_control_transfer (const MbimMessage *self
 /*****************************************************************************/
 /* '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 @@ MbimMessage *mbim_message_close_new (guint32 transaction_id);
 /*****************************************************************************/
 /* '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.c b/src/libmbim-glib/mbim-proxy.c
new file mode 100644
index 0000000..04125c1
--- /dev/null
+++ b/src/libmbim-glib/mbim-proxy.c
@@ -0,0 +1,938 @@
+/* -*- 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 at lanedo.com>
+ * Copyright (C) 2014 Greg Suarez <gsuarez at smithmicro.com>
+ */
+
+#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"
+
+#define BUFFER_SIZE 512
+
+G_DEFINE_TYPE (MbimProxy, mbim_proxy, G_TYPE_OBJECT)
+
+enum {
+    PROP_0,
+    PROP_N_CLIENTS,
+    PROP_LAST
+};
+
+static GParamSpec *properties[PROP_LAST];
+
+struct _MbimProxyPrivate {
+    /* Unix socket service */
+    GSocketService *socket_service;
+
+    /* Clients */
+    GList *clients;
+
+    /* Devices */
+    GList *devices;
+};
+
+/*****************************************************************************/
+
+/**
+ * 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);
+}
+
+/*****************************************************************************/
+
+typedef struct {
+    MbimUuid uuid;
+} MbimClientInfo;
+
+typedef struct {
+    MbimProxy *proxy; /* not full ref */
+    GSocketConnection *connection;
+    GSource *connection_readable_source;
+    GByteArray *buffer;
+    MbimDevice *device;
+    MbimMessage *internal_proxy_open_request;
+    GArray *mbim_client_info_array;
+    guint indication_id;
+    gchar *device_file_path;
+    gboolean opening_device;
+} Client;
+
+typedef struct {
+    MbimProxy *proxy;
+    Client *client;
+} DeviceOpenContext;
+
+static gboolean connection_readable_cb (GSocket *socket, GIOCondition condition, Client *client);
+
+static void
+client_free (Client *client)
+{
+    g_source_destroy (client->connection_readable_source);
+    g_source_unref (client->connection_readable_source);
+
+    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);
+    }
+
+    g_output_stream_close (g_io_stream_get_output_stream (G_IO_STREAM (client->connection)), NULL, NULL);
+
+    if (client->buffer)
+        g_byte_array_unref (client->buffer);
+
+    if (client->device_file_path)
+        g_free (client->device_file_path);
+
+    if (client->internal_proxy_open_request)
+        mbim_message_unref (client->internal_proxy_open_request);
+
+    g_array_unref (client->mbim_client_info_array);
+
+    g_object_unref (client->connection);
+    g_slice_free (Client, client);
+}
+
+static guint
+get_n_clients_with_device (MbimProxy *self,
+                           MbimDevice *device)
+{
+    GList *l;
+    guint n = 0;
+
+    for (l = self->priv->clients; l; l = g_list_next (l)) {
+        Client *client = l->data;
+
+        if (device == client->device ||
+            g_str_equal (mbim_device_get_path (device), mbim_device_get_path (client->device)))
+            n++;
+    }
+
+    return n;
+}
+
+static Client *
+get_client (MbimProxy *self,
+            Client    *client)
+{
+    GList  *l;
+
+    l = g_list_find (self->priv->clients, client);
+    if (l)
+        return l->data;
+    return NULL;
+}
+
+static void
+device_close_ready (MbimDevice   *device,
+                    GAsyncResult *result)
+{
+    GError *error = NULL;
+
+    if (!mbim_device_close_finish (device, result, &error)) {
+        g_debug ("error: couldn't close device: %s", error->message);
+        g_error_free (error);
+    } else
+        g_debug ("Device closed");
+
+    g_object_unref (device);
+}
+
+static void
+device_close (MbimProxy *self,
+              MbimDevice *device)
+{
+    if (!device)
+        return;
+
+    /* If no more clients using the device, close and cleanup */
+    if (get_n_clients_with_device (self, device) == 0) {
+        GList *l;
+
+        for (l = self->priv->devices; l; l = g_list_next (l)) {
+            MbimDevice *device_in_list = MBIM_DEVICE (l->data);
+
+            if (device == device_in_list ||
+                g_str_equal (mbim_device_get_path (device), mbim_device_get_path (device_in_list))) {
+                g_debug ("closing device '%s': no longer used", mbim_device_get_path_display (device));
+                mbim_device_close (device_in_list, 
+                                   15,
+                                   NULL,
+                                   (GAsyncReadyCallback) device_close_ready,
+                                   NULL);
+                self->priv->devices = g_list_remove (self->priv->devices, device_in_list);
+                break;
+            }
+        }
+    }
+}
+
+static void
+connection_close (Client *client)
+{
+    MbimProxy *self;
+    MbimDevice *device;
+
+    g_debug ("Client (%d) connection closed...", g_socket_get_fd (g_socket_connection_get_socket (client->connection)));
+
+    self = client->proxy;
+    device = client->device ? g_object_ref (client->device) : NULL;
+    client_free (client);
+    self->priv->clients = g_list_remove (self->priv->clients, client);
+    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_N_CLIENTS]);
+
+    device_close (self, device);
+    g_object_unref (device);
+}
+
+static MbimDevice *
+find_device_for_path (MbimProxy *self,
+                      const gchar *path)
+{
+    GList *l;
+
+    for (l = self->priv->devices; l; l = g_list_next (l)) {
+        MbimDevice *device;
+
+        device = (MbimDevice *)l->data;
+
+        /* Return if found */
+        if (g_str_equal (mbim_device_get_path (device), path))
+            return device;
+    }
+
+    return NULL;
+}
+
+static gboolean
+send_message (Client *client,
+              MbimMessage *message,
+              GError **error)
+{
+    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;
+}
+
+static void
+complete_internal_proxy_open (Client *client)
+{
+    MbimMessage *response;
+    GError *error = NULL;
+
+    g_debug ("connection to MBIM device '%s' established", mbim_device_get_path (client->device));
+
+    g_assert (client->internal_proxy_open_request != NULL);
+    response = mbim_message_open_done_new (mbim_message_get_transaction_id (client->internal_proxy_open_request), 
+                                           MBIM_STATUS_ERROR_NONE);
+
+    if (!send_message (client, response, &error)) {
+        mbim_message_unref (response);
+        connection_close (client);
+        return;
+    }
+
+    mbim_message_unref (response);
+    mbim_message_unref (client->internal_proxy_open_request);
+    client->internal_proxy_open_request = NULL;
+}
+
+static void
+indication_cb (MbimDevice *device,
+               MbimMessage *message,
+               Client *client)
+{
+    guint i;
+    GError *error = NULL;
+    gboolean forward_indication = FALSE;
+
+    if (mbim_message_indicate_status_get_service (message) == MBIM_SERVICE_INVALID) {
+        for (i = 0; i < client->mbim_client_info_array->len; i++) {
+            MbimClientInfo *info;
+
+            info = &g_array_index (client->mbim_client_info_array, MbimClientInfo, i);
+            /* If service UUID match, forward to the remote client */
+            if (mbim_uuid_cmp(mbim_message_indicate_status_get_service_id (message), &info->uuid)) {
+                forward_indication = TRUE;
+                break;
+            }
+        }
+    } else
+        forward_indication = TRUE;
+
+    if (forward_indication) {
+        if (!send_message (client, message, &error)) {
+            g_warning ("couldn't forward indication to client");
+            g_error_free (error);
+        }
+    }
+}
+
+static void
+device_open_ready (MbimDevice *device,
+                   GAsyncResult *res,
+                   DeviceOpenContext *ctx)
+{
+    MbimProxy *self = ctx->proxy;
+    MbimDevice *existing;
+    Client *client;
+    GError *error = NULL;
+
+    client = get_client (self, ctx->client);
+    if (!client) {
+        /* client must've been disconnected */
+        mbim_device_open_finish (device, res, NULL);
+        device_close (self, device);
+        g_slice_free (DeviceOpenContext, ctx);
+        return;
+    }
+
+    if (!mbim_device_open_finish (device, res, &error)) {
+        g_debug ("couldn't open MBIM device: %s", error->message);
+        connection_close (client);
+        g_error_free (error);
+        g_slice_free (DeviceOpenContext, ctx);
+        return;
+    }
+
+    /* Store device in the proxy independently */
+    existing = find_device_for_path (self, mbim_device_get_path (client->device));
+    if (existing) {
+        /* Race condition, we created two MbimDevices for the same port, just skip ours, no big deal */
+        g_object_unref (client->device);
+        client->device = g_object_ref (existing);
+    } else {
+        /* Keep the newly added device in the proxy */
+        self->priv->devices = g_list_append (self->priv->devices, g_object_ref (client->device));
+    }
+
+    /* Register for device indications */
+    client->indication_id = g_signal_connect (client->device,
+                                              MBIM_DEVICE_SIGNAL_INDICATE_STATUS,
+                                              G_CALLBACK (indication_cb),
+                                              client);
+
+    g_slice_free (DeviceOpenContext, ctx);
+    complete_internal_proxy_open (client);
+}
+
+static void
+device_new_ready (GObject *source,
+                  GAsyncResult *res,
+                  DeviceOpenContext *ctx)
+{
+    GError *error = NULL;
+    Client *client;
+    MbimDevice *device;
+
+    device = mbim_device_new_finish (res, &error);
+    client = get_client (ctx->proxy, ctx->client);
+    if (!client) {
+        /* client must've been disconnected */
+        if (!device)
+            g_error_free (error);
+        device_close (ctx->proxy, device);
+        g_slice_free (DeviceOpenContext, ctx);
+        return;
+    }
+
+    client->device = device;
+    if (!client->device) {
+        g_debug ("couldn't open MBIM device: %s", error->message);
+        connection_close (client);
+        g_error_free (error);
+        g_slice_free (DeviceOpenContext, ctx);
+        return;
+    }
+
+    mbim_device_open (client->device,
+                      30,
+                      NULL,
+                      (GAsyncReadyCallback)device_open_ready,
+                      ctx);
+}
+
+static gboolean
+process_internal_proxy_open (Client *client,
+                             MbimMessage *message)
+{
+    MbimProxy *self = client->proxy;
+    DeviceOpenContext  *ctx;
+
+
+    /* Keep it */
+    client->internal_proxy_open_request = mbim_message_ref (message);
+
+    if (client->device_file_path) {
+        client->device = find_device_for_path (self, client->device_file_path);
+    } else if (!client->device_file_path && g_list_length (self->priv->devices)) {
+        /* no device file path set, just use first device found */
+        client->device = (MbimDevice *)(g_list_first (self->priv->devices))->data;
+    } else {
+        g_debug ("missing device file path");
+        complete_internal_proxy_open (client);
+        return FALSE;
+    }
+
+    /* Need to create a device ourselves */
+    if (!client->device) {
+        GFile *file;
+
+        ctx = g_slice_new0 (DeviceOpenContext);
+        ctx->proxy = self;
+        ctx->client = client;
+
+        file = g_file_new_for_path (client->device_file_path);
+        mbim_device_new (file,
+                         NULL,
+                         (GAsyncReadyCallback)device_new_ready,
+                         ctx);
+        g_object_unref (file);
+        return TRUE;
+    } else {
+        /* register client for notifications */
+        client->indication_id = g_signal_connect (client->device,
+                                                  MBIM_DEVICE_SIGNAL_INDICATE_STATUS,
+                                                  G_CALLBACK (indication_cb),
+                                                  client);
+    }
+
+    /* Keep a reference to the device in the client */
+    g_object_ref (client->device);
+
+    complete_internal_proxy_open (client);
+    return FALSE;
+}
+
+static gboolean
+process_internal_proxy_close (Client *client,
+                              MbimMessage *message)
+{
+    MbimProxy *self = client->proxy;
+    MbimDevice *device;
+    MbimMessage *response;
+    GError *error = NULL;
+
+    device = client->device ? g_object_ref (client->device) : NULL;
+    device_close (self, device);
+    g_object_unref (device);
+
+    response = mbim_message_close_done_new (mbim_message_get_transaction_id (message), MBIM_STATUS_ERROR_NONE);
+    if (!send_message (client, response, &error)) {
+        mbim_message_unref (response);
+        connection_close (client);
+        return FALSE;
+    }
+
+    mbim_message_unref (response);
+    return TRUE;
+}
+
+static gboolean
+process_internal_proxy_config (Client *client,
+                               MbimMessage *message)
+{
+    MbimMessage *response;
+    MbimStatusError error_status_code;
+    struct command_done_message *command_done;
+    GError *error = NULL;
+
+
+    if (mbim_message_command_get_command_type(message) == MBIM_MESSAGE_COMMAND_TYPE_SET) {
+        if (client->device_file_path)
+            g_free (client->device_file_path);
+
+        client->device_file_path = _mbim_message_read_string (message, 0, 0);
+        error_status_code = MBIM_STATUS_ERROR_NONE;
+    } else
+        error_status_code = MBIM_STATUS_ERROR_INVALID_PARAMETERS;
+        
+    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 (error_status_code);
+
+    if (!send_message (client, response, &error)) {
+        mbim_message_unref (response);
+        connection_close (client);
+        return FALSE;
+    }
+
+    mbim_message_unref (response);
+    return TRUE;
+}
+
+static void
+track_uuid (Client *client,
+            gboolean track,
+            MbimMessage *message)
+{
+    gchar *uuid_display;
+    MbimClientInfo info;
+    guint i;
+    gboolean exists;
+
+    memcpy (&info.uuid, mbim_message_command_done_get_service_id(message), sizeof(info.uuid));
+    uuid_display = mbim_uuid_get_printable (&info.uuid);
+
+    /* Check if it already exists */
+    for (i = 0; i < client->mbim_client_info_array->len; i++) {
+        MbimClientInfo *existing;
+
+        existing = &g_array_index (client->mbim_client_info_array, MbimClientInfo, i);
+        if (mbim_uuid_cmp(&info.uuid, &existing->uuid))
+            break;
+    }
+    exists = (i < client->mbim_client_info_array->len);
+
+    if (track && !exists) {
+        g_debug ("MBIM client tracked [%s,%s]",
+                 mbim_device_get_path_display (client->device),
+        uuid_display);
+        g_array_append_val (client->mbim_client_info_array, info);
+    } else if (!track && exists) {
+        g_debug ("MBIM client untracked [%s,%s]",
+                 mbim_device_get_path_display (client->device),
+                 uuid_display);
+        g_array_remove_index (client->mbim_client_info_array, i);
+    }
+
+    g_free (uuid_display);
+}
+
+typedef struct {
+    Client *client;
+    guint32 transaction_id;
+} Request;
+
+static void
+device_command_ready (MbimDevice *device,
+                      GAsyncResult *res,
+                      Request *request)
+{
+    MbimMessage *response;
+    GError *error = NULL;
+
+    response = mbim_device_command_finish (device, res, &error);
+    if (!response) {
+        g_warning ("sending request to device failed: %s", error->message);
+	if (!mbim_device_is_open(device)) {
+	    g_debug ("device is closed");
+	    connection_close (request->client);
+	}
+
+        g_error_free (error);
+        g_slice_free (Request, request);
+        return;
+    }
+
+    /* track custom uuids */
+    if (mbim_message_get_message_type (response) == MBIM_MESSAGE_TYPE_COMMAND_DONE) {
+        if (mbim_message_command_done_get_service (response) == MBIM_SERVICE_INVALID)
+            track_uuid (request->client, TRUE, response);
+    }
+
+    /* replace reponse transaction id with the requested transaction id */
+    ((struct header *)(response->data))->transaction_id = GUINT32_TO_LE (request->transaction_id);
+
+    if (!send_message (request->client, response, &error))
+        connection_close (request->client);
+
+    mbim_message_unref (response);
+    g_slice_free (Request, request);
+}
+
+static gboolean
+process_message (Client *client,
+                 MbimMessage *message)
+{
+    guint timeout = 10;
+    Request *request;
+
+    /* Accept only command messages from the client */
+    if (mbim_message_get_message_type (message) != MBIM_MESSAGE_TYPE_COMMAND &&
+        mbim_message_get_message_type (message) != MBIM_MESSAGE_TYPE_OPEN    &&
+        mbim_message_get_message_type (message) != MBIM_MESSAGE_TYPE_CLOSE) {
+        g_debug ("invalid message from client: not a command message");
+        return FALSE;
+    }
+
+    if (mbim_message_get_message_type (message) == MBIM_MESSAGE_TYPE_OPEN) {
+        return process_internal_proxy_open (client, message);
+    }
+    else if (mbim_message_get_message_type (message) == MBIM_MESSAGE_TYPE_CLOSE) {
+        return process_internal_proxy_close (client, message);
+    }
+    else 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 (client, message);
+    }
+
+    request = g_slice_new0 (Request);
+    request->client = client;
+    request->transaction_id = mbim_message_get_transaction_id (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));
+
+    if (mbim_message_command_get_service (message) == MBIM_SERVICE_BASIC_CONNECT) {
+        if (mbim_message_command_get_cid (message) == MBIM_CID_BASIC_CONNECT_VISIBLE_PROVIDERS) {
+            /* increase timeout for network scan */
+            timeout = 120;
+        } else if (mbim_message_command_get_cid (message) == MBIM_CID_BASIC_CONNECT_REGISTER_STATE &&
+                   mbim_message_command_get_command_type (message) == MBIM_MESSAGE_COMMAND_TYPE_SET) {
+            /* increase timeout for network registration */
+            timeout = 60;
+        } else if (mbim_message_command_get_cid (message) == MBIM_CID_BASIC_CONNECT_RADIO_STATE &&
+                   mbim_message_command_get_command_type (message) == MBIM_MESSAGE_COMMAND_TYPE_SET) {
+            /* increase timeout for setting radio state */
+            timeout = 30;
+        } else if (mbim_message_command_get_cid (message) == MBIM_CID_BASIC_CONNECT_PACKET_SERVICE &&
+                   mbim_message_command_get_command_type (message) == MBIM_MESSAGE_COMMAND_TYPE_SET) {
+            /* increase timeout for setting packet service */
+            timeout = 30;
+        } else if (mbim_message_command_get_cid (message) == MBIM_CID_BASIC_CONNECT_CONNECT &&
+                   mbim_message_command_get_command_type (message) == MBIM_MESSAGE_COMMAND_TYPE_SET) {
+            /* increase timeout for connecting */
+            timeout = 60;
+        } else if (mbim_message_command_get_cid (message) == MBIM_CID_BASIC_CONNECT_IP_CONFIGURATION &&
+                   mbim_message_command_get_command_type (message) == MBIM_MESSAGE_COMMAND_TYPE_QUERY) {
+            /* increase timeout for querying ip configuration */
+            timeout = 60;
+        }
+    }
+
+    mbim_device_command (client->device,
+                         message,
+                         timeout,
+                         NULL,
+                         (GAsyncReadyCallback)device_command_ready,
+                         request);
+    return TRUE;
+}
+
+static void
+parse_request (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;
+        } else {
+            g_byte_array_remove_range (client->buffer, 0, len);
+
+            /* Play with the received message */
+            process_message (client, message);
+            mbim_message_unref (message);
+        }
+    } while (client->buffer->len > 0);
+}
+
+static gboolean
+connection_readable_cb (GSocket *socket,
+                        GIOCondition condition,
+                        Client *client)
+{
+    guint8 buffer[BUFFER_SIZE];
+    GError *error = NULL;
+    gssize r;
+
+    if (condition & G_IO_HUP || condition & G_IO_ERR) {
+        connection_close (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 */
+        connection_close (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 (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->proxy = self;
+    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);
+    client->mbim_client_info_array = g_array_sized_new (FALSE, FALSE, sizeof (MbimClientInfo), 8);
+
+    /* Keep the client info around */
+    self->priv->clients = g_list_append (self->priv->clients, client);
+    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_N_CLIENTS]);
+}
+
+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);
+        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;
+}
+
+/*****************************************************************************/
+
+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);
+}
+
+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;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+dispose (GObject *object)
+{
+    MbimProxyPrivate *priv = MBIM_PROXY (object)->priv;
+
+    if (priv->clients) {
+        g_list_free_full (priv->clients, (GDestroyNotify) client_free);
+        priv->clients = 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);
+    }
+
+    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]);
+}
diff --git a/src/libmbim-glib/mbim-proxy.h b/src/libmbim-glib/mbim-proxy.h
new file mode 100644
index 0000000..88b4698
--- /dev/null
+++ b/src/libmbim-glib/mbim-proxy.h
@@ -0,0 +1,59 @@
+/* -*- 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 at lanedo.com>
+ * Copyright (C) 2014 Greg Suarez <gsuarez at smithmicro.com>
+ */
+
+#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"
+
+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);
+
+#endif /* MBIM_PROXY_H */
diff --git a/src/libmbim-glib/mbim-uuid.c b/src/libmbim-glib/mbim-uuid.c
index 8b53ed8..c26febf 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 at gnu.org>
  * Copyright (C) 2014 NVDIA Corporation
+ * Copyright (C) 2014 Greg Suarez <gsuarez at smithmicro.com>
  */
 
 #include <config.h>
@@ -218,6 +219,14 @@ static const MbimUuid uuid_ms_host_shutdown = {
     .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 {
@@ -353,7 +362,7 @@ mbim_uuid_from_service (MbimService service)
     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 +387,8 @@ mbim_uuid_from_service (MbimService service)
         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 +438,9 @@ mbim_uuid_to_service (const MbimUuid *uuid)
     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 6ed74db..48370d5 100644
--- a/src/libmbim-glib/mbim-uuid.h
+++ b/src/libmbim-glib/mbim-uuid.h
@@ -70,6 +70,7 @@ gboolean  mbim_uuid_from_printable (const gchar *str,
  * @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,7 @@ typedef enum {
     MBIM_SERVICE_DSS              = 7,
     MBIM_SERVICE_MS_FIRMWARE_ID   = 8,
     MBIM_SERVICE_MS_HOST_SHUTDOWN = 9,
+    MBIM_SERVICE_PROXY_CONTROL    = 10,
 } MbimService;
 
 /**
@@ -176,6 +178,15 @@ typedef enum {
  */
 #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/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..cd8b344
--- /dev/null
+++ b/src/mbim-proxy/mbim-proxy.c
@@ -0,0 +1,223 @@
+/* -*- 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 at gnu.org>
+ * Copyright (C) 2014 Greg Suarez <gsuarez at smithmicro.com>
+ */
+
+#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;
+
+/* 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)
+{
+    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;
+    }
+}
+
+/*****************************************************************************/
+
+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);
+
+    /* 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 9b3ef6d..01229e5 100644
--- a/src/mbimcli/mbimcli.c
+++ b/src/mbimcli/mbimcli.c
@@ -302,11 +302,12 @@ device_new_ready (GObject      *unused,
     }
 
     /* Open the device */
-    mbim_device_open (device,
-                      30,
-                      cancellable,
-                      (GAsyncReadyCallback) device_open_ready,
-                      NULL);
+    mbim_device_open_full (device,
+                           MBIM_DEVICE_OPEN_FLAGS_PROXY,
+                           30,
+                           cancellable,
+                           (GAsyncReadyCallback) device_open_ready,
+                           NULL);
 }
 
 /*****************************************************************************/
-- 
1.9.0



More information about the libmbim-devel mailing list