summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksander Morgado <aleksander@lanedo.com>2013-08-06 13:01:54 +0200
committerAleksander Morgado <aleksander@lanedo.com>2013-09-05 15:36:35 +0200
commit852783f222a0db3666ef4ab24879ec20ff87c128 (patch)
treecf03f1cc6291974b235980506e7e73c448ca37d0
parent4f10b4ed239cf25c2fa3205baaab08889cdefd0f (diff)
downloadlibqmi-852783f222a0db3666ef4ab24879ec20ff87c128.tar.gz
qmi-proxy: initial implementation
-rw-r--r--.gitignore4
-rw-r--r--configure.ac1
-rw-r--r--data/qmi-service-ctl.json16
-rw-r--r--docs/reference/libqmi-glib/libqmi-glib-common.sections19
-rw-r--r--docs/reference/libqmi-glib/libqmi-glib-docs.xml1
-rw-r--r--src/Makefile.am2
-rw-r--r--src/libqmi-glib/Makefile.am6
-rw-r--r--src/libqmi-glib/libqmi-glib.h1
-rw-r--r--src/libqmi-glib/qmi-message.c14
-rw-r--r--src/libqmi-glib/qmi-message.h1
-rw-r--r--src/libqmi-glib/qmi-proxy.c521
-rw-r--r--src/libqmi-glib/qmi-proxy.h55
-rw-r--r--src/qmi-proxy/Makefile.am16
-rw-r--r--src/qmi-proxy/qmi-proxy.c171
14 files changed, 824 insertions, 4 deletions
diff --git a/.gitignore b/.gitignore
index c03c04f5..2b2dfcdd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -62,6 +62,10 @@ src/qmicli/test/Makefile
src/qmicli/test/Makefile.in
src/qmicli/test/test-helpers
+src/qmi-proxy/.deps
+src/qmi-proxy/.libs
+src/qmi-proxy/qmi-proxy
+
build-aux/qmi-codegen/*.pyc
docs/reference/libqmi-glib/version.xml
diff --git a/configure.ac b/configure.ac
index 91257c99..343126dc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -97,6 +97,7 @@ AC_CONFIG_FILES([Makefile
src/libqmi-glib/test/Makefile
src/qmicli/Makefile
src/qmicli/test/Makefile
+ src/qmi-proxy/Makefile
utils/Makefile
docs/Makefile
docs/reference/Makefile
diff --git a/data/qmi-service-ctl.json b/data/qmi-service-ctl.json
index 1ce2ddc6..1e293f7c 100644
--- a/data/qmi-service-ctl.json
+++ b/data/qmi-service-ctl.json
@@ -144,5 +144,19 @@
{ "name" : "Sync",
"type" : "Indication",
"service" : "CTL",
- "id" : "0x0027" }
+ "id" : "0x0027" },
+
+ // *********************************************************************************
+ // Internal
+ { "name" : "Internal Proxy Open",
+ "type" : "Message",
+ "service" : "CTL",
+ "id" : "0xFF00",
+ "input" : [ { "name" : "Device Path",
+ "id" : "0x01",
+ "mandatory" : "yes",
+ "type" : "TLV",
+ "format" : "string" } ],
+ "output" : [ { "common-ref" : "Operation Result" } ] }
+
]
diff --git a/docs/reference/libqmi-glib/libqmi-glib-common.sections b/docs/reference/libqmi-glib/libqmi-glib-common.sections
index 4c7bb795..7048e945 100644
--- a/docs/reference/libqmi-glib/libqmi-glib-common.sections
+++ b/docs/reference/libqmi-glib/libqmi-glib-common.sections
@@ -79,6 +79,24 @@ qmi_device_get_type
</SECTION>
<SECTION>
+<FILE>qmi-proxy</FILE>
+<TITLE>QmiProxy</TITLE>
+QMI_PROXY_SOCKET_PATH
+QmiProxy
+qmi_proxy_new
+<SUBSECTION Standard>
+QmiProxyClass
+QMI_PROXY
+QMI_PROXY_CLASS
+QMI_PROXY_GET_CLASS
+QMI_IS_PROXY
+QMI_IS_PROXY_CLASS
+QMI_TYPE_PROXY
+QmiProxyPrivate
+qmi_proxy_get_type
+</SECTION>
+
+<SECTION>
<FILE>qmi-enums</FILE>
QmiService
<SUBSECTION Methods>
@@ -731,6 +749,7 @@ qmi_message_new_from_raw
qmi_message_response_new
qmi_message_ref
qmi_message_unref
+qmi_message_is_request
qmi_message_is_response
qmi_message_is_indication
qmi_message_get_service
diff --git a/docs/reference/libqmi-glib/libqmi-glib-docs.xml b/docs/reference/libqmi-glib/libqmi-glib-docs.xml
index 706312ec..1795b596 100644
--- a/docs/reference/libqmi-glib/libqmi-glib-docs.xml
+++ b/docs/reference/libqmi-glib/libqmi-glib-docs.xml
@@ -43,6 +43,7 @@
<xi:include href="xml/qmi-message.xml"/>
<xi:include href="xml/qmi-device.xml"/>
<xi:include href="xml/qmi-client.xml"/>
+ <xi:include href="xml/qmi-proxy.xml"/>
<xi:include href="xml/qmi-enums.xml"/>
<xi:include href="xml/qmi-errors.xml"/>
<xi:include href="xml/qmi-utils.xml"/>
diff --git a/src/Makefile.am b/src/Makefile.am
index 6d08b9c1..9f52adf7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,2 +1,2 @@
-SUBDIRS = libqmi-glib qmicli
+SUBDIRS = libqmi-glib qmicli qmi-proxy
diff --git a/src/libqmi-glib/Makefile.am b/src/libqmi-glib/Makefile.am
index b97c5731..76762805 100644
--- a/src/libqmi-glib/Makefile.am
+++ b/src/libqmi-glib/Makefile.am
@@ -30,7 +30,8 @@ libqmi_glib_la_SOURCES = \
qmi-utils.h qmi-utils.c \
qmi-message.h qmi-message.c \
qmi-device.h qmi-device.c \
- qmi-client.h qmi-client.c
+ qmi-client.h qmi-client.c \
+ qmi-proxy.h qmi-proxy.c
libqmi_glib_la_LIBADD = \
${top_builddir}/src/libqmi-glib/generated/libqmi-glib-generated.la \
@@ -57,7 +58,8 @@ include_HEADERS = \
qmi-utils.h \
qmi-message.h \
qmi-device.h \
- qmi-client.h
+ qmi-client.h \
+ qmi-proxy.h
EXTRA_DIST = \
qmi-version.h.in
diff --git a/src/libqmi-glib/libqmi-glib.h b/src/libqmi-glib/libqmi-glib.h
index 2143e860..c4f8ba28 100644
--- a/src/libqmi-glib/libqmi-glib.h
+++ b/src/libqmi-glib/libqmi-glib.h
@@ -31,6 +31,7 @@
#include "qmi-version.h"
#include "qmi-device.h"
#include "qmi-client.h"
+#include "qmi-proxy.h"
#include "qmi-message.h"
#include "qmi-enums.h"
#include "qmi-utils.h"
diff --git a/src/libqmi-glib/qmi-message.c b/src/libqmi-glib/qmi-message.c
index 55165623..c016f5e4 100644
--- a/src/libqmi-glib/qmi-message.c
+++ b/src/libqmi-glib/qmi-message.c
@@ -143,6 +143,20 @@ get_qmi_flags (QmiMessage *self)
}
/**
+ * qmi_message_is_request:
+ * @self: a #QmiMessage.
+ *
+ * Checks whether the given #QmiMessage is a request.
+ *
+ * Returns: %TRUE if @self is a request message, %FALSE otherwise.
+ */
+gboolean
+qmi_message_is_request (QmiMessage *self)
+{
+ return (!qmi_message_is_response (self) && !qmi_message_is_indication (self));
+}
+
+/**
* qmi_message_is_response:
* @self: a #QmiMessage.
*
diff --git a/src/libqmi-glib/qmi-message.h b/src/libqmi-glib/qmi-message.h
index ccc0167c..958df035 100644
--- a/src/libqmi-glib/qmi-message.h
+++ b/src/libqmi-glib/qmi-message.h
@@ -67,6 +67,7 @@ void qmi_message_unref (QmiMessage *self);
/*****************************************************************************/
/* QMI Message content getters */
+gboolean qmi_message_is_request (QmiMessage *self);
gboolean qmi_message_is_response (QmiMessage *self);
gboolean qmi_message_is_indication (QmiMessage *self);
QmiService qmi_message_get_service (QmiMessage *self);
diff --git a/src/libqmi-glib/qmi-proxy.c b/src/libqmi-glib/qmi-proxy.c
new file mode 100644
index 00000000..bb692c04
--- /dev/null
+++ b/src/libqmi-glib/qmi-proxy.c
@@ -0,0 +1,521 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/*
+ * libqmi-glib -- GLib/GIO based library to control QMI 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) 2013 Aleksander Morgado <aleksander@lanedo.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 "qmi-error-types.h"
+#include "qmi-device.h"
+#include "qmi-ctl.h"
+#include "qmi-utils.h"
+#include "qmi-proxy.h"
+
+#define BUFFER_SIZE 512
+#define QMI_MESSAGE_CTL_INTERNAL_PROXY_OPEN 0xFF00
+#define QMI_MESSAGE_CTL_INTERNAL_PROXY_OPEN_INPUT_TLV_DEVICE_PATH 0x01
+
+G_DEFINE_TYPE (QmiProxy, qmi_proxy, G_TYPE_OBJECT)
+
+struct _QmiProxyPrivate {
+ /* Unix socket service */
+ GSocketService *socket_service;
+
+ /* Clients */
+ GList *clients;
+
+ /* Devices */
+ GList *devices;
+};
+
+/*****************************************************************************/
+
+typedef struct {
+ QmiProxy *proxy; /* not full ref */
+ GSocketConnection *connection;
+ GSource *connection_readable_source;
+ GByteArray *buffer;
+ QmiDevice *device;
+ QmiMessage *internal_proxy_open_request;
+} Client;
+
+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);
+
+ 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->internal_proxy_open_request)
+ g_byte_array_unref (client->internal_proxy_open_request);
+
+ g_object_unref (client->connection);
+ g_slice_free (Client, client);
+}
+
+static void
+connection_close (Client *client)
+{
+ QmiProxy *self = client->proxy;
+
+ client_free (client);
+ self->priv->clients = g_list_remove (self->priv->clients, client);
+}
+
+static QmiDevice *
+find_device_for_path (QmiProxy *self,
+ const gchar *path)
+{
+ GList *l;
+
+ for (l = self->priv->devices; l; l = g_list_next (l)) {
+ QmiDevice *device;
+
+ device = (QmiDevice *)l->data;
+
+ /* Return if found */
+ if (g_str_equal (qmi_device_get_path (device), path))
+ return device;
+ }
+
+ return NULL;
+}
+
+static gboolean
+send_message (Client *client,
+ QmiMessage *message,
+ GError **error)
+{
+ 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)
+{
+ QmiMessage *response;
+ GError *error = NULL;
+
+ g_debug ("connection to QMI device '%s' established", qmi_device_get_path (client->device));
+
+ g_assert (client->internal_proxy_open_request != NULL);
+ response = qmi_message_response_new (client->internal_proxy_open_request, QMI_PROTOCOL_ERROR_NONE);
+
+ if (!send_message (client, response, &error)) {
+ qmi_message_unref (response);
+ connection_close (client);
+ return;
+ }
+
+ qmi_message_unref (response);
+ qmi_message_unref (client->internal_proxy_open_request);
+ client->internal_proxy_open_request = NULL;
+}
+
+static void
+device_open_ready (QmiDevice *device,
+ GAsyncResult *res,
+ Client *client)
+{
+ QmiProxy *self = client->proxy;
+ QmiDevice *existing;
+ GError *error = NULL;
+
+ if (!qmi_device_open_finish (device, res, &error)) {
+ g_debug ("couldn't open QMI device: %s", error->message);
+ g_debug ("client connection closed");
+ connection_close (client);
+ g_error_free (error);
+ return;
+ }
+
+ /* Store device in the proxy independently */
+ existing = find_device_for_path (self, qmi_device_get_path (client->device));
+ if (existing) {
+ /* Race condition, we created two QmiDevices 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));
+ }
+
+ complete_internal_proxy_open (client);
+}
+
+static void
+device_new_ready (GObject *source,
+ GAsyncResult *res,
+ Client *client)
+{
+ GError *error = NULL;
+
+ client->device = qmi_device_new_finish (res, &error);
+ if (!client->device) {
+ g_debug ("couldn't open QMI device: %s", error->message);
+ g_debug ("client connection closed");
+ connection_close (client);
+ g_error_free (error);
+ return;
+ }
+
+ qmi_device_open (client->device,
+ QMI_DEVICE_OPEN_FLAGS_NONE,
+ 10,
+ NULL,
+ (GAsyncReadyCallback)device_open_ready,
+ client);
+}
+
+static gboolean
+process_internal_proxy_open (Client *client,
+ QmiMessage *message)
+{
+ QmiProxy *self = client->proxy;
+ const guint8 *buffer;
+ guint16 buffer_len;
+ gchar *device_file_path;
+
+ buffer = qmi_message_get_raw_tlv (message,
+ QMI_MESSAGE_CTL_INTERNAL_PROXY_OPEN_INPUT_TLV_DEVICE_PATH,
+ &buffer_len);
+ if (!buffer) {
+ g_debug ("ignoring message from client: invalid proxy open request");
+ return FALSE;
+ }
+
+ qmi_utils_read_string_from_buffer (&buffer, &buffer_len, 0, 0, &device_file_path);
+ g_debug ("valid request to open connection to QMI device file: %s", device_file_path);
+
+
+ /* Keep it */
+ client->internal_proxy_open_request = qmi_message_ref (message);
+
+ client->device = find_device_for_path (self, device_file_path);
+
+ /* Need to create a device ourselves */
+ if (!client->device) {
+ GFile *file;
+
+ file = g_file_new_for_path (device_file_path);
+ qmi_device_new (file,
+ NULL,
+ (GAsyncReadyCallback)device_new_ready,
+ client);
+ g_object_unref (file);
+ g_free (device_file_path);
+ return TRUE;
+ }
+
+ g_free (device_file_path);
+
+ /* Keep a reference to the device in the client */
+ g_object_ref (client->device);
+
+ complete_internal_proxy_open (client);
+ return FALSE;
+}
+
+static void
+device_command_ready (QmiDevice *device,
+ GAsyncResult *res,
+ Client *client)
+{
+ QmiMessage *response;
+ GError *error = NULL;
+
+ response = qmi_device_command_finish (device, res, &error);
+ if (!response) {
+ g_warning ("sending request to device failed: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ if (!send_message (client, response, &error)) {
+ qmi_message_unref (response);
+ connection_close (client);
+ return;
+ }
+
+ qmi_message_unref (response);
+}
+
+static gboolean
+process_message (Client *client,
+ QmiMessage *message)
+{
+ /* Accept only request messages from the client */
+ if (!qmi_message_is_request (message)) {
+ g_debug ("invalid message from client: not a request message");
+ return FALSE;
+ }
+
+ if (qmi_message_get_service (message) == QMI_SERVICE_CTL) {
+ if (qmi_message_get_message_id (message) == QMI_MESSAGE_CTL_INTERNAL_PROXY_OPEN)
+ return process_internal_proxy_open (client, message);
+
+ /* CTL, fixup transaction id and keep on */
+ }
+
+ /* Non-CTL service, just forward to qmi device */
+
+ qmi_device_command (client->device,
+ message,
+ 10, /* for now... */
+ NULL,
+ (GAsyncReadyCallback)device_command_ready,
+ client);
+ return TRUE;
+}
+
+static void
+parse_request (Client *client)
+{
+ do {
+ GError *error = NULL;
+ QmiMessage *message;
+
+ /* Every message received must start with the QMUX marker.
+ * If it doesn't, we broke framing :-/
+ * If we broke framing, an error should be reported and the device
+ * should get closed */
+ if (client->buffer->len > 0 &&
+ client->buffer->data[0] != QMI_MESSAGE_QMUX_MARKER) {
+ /* TODO: Report fatal error */
+ g_warning ("QMI framing error detected");
+ return;
+ }
+
+ message = qmi_message_new_from_raw (client->buffer, &error);
+ if (!message) {
+ if (!error)
+ /* More data we need */
+ return;
+
+ /* Warn about the issue */
+ g_warning ("Invalid QMI message received: '%s'",
+ error->message);
+ g_error_free (error);
+ } else {
+ /* Play with the received message */
+ process_message (client, message);
+ qmi_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 read;
+
+ if (condition & G_IO_HUP || condition & G_IO_ERR) {
+ g_debug ("client connection closed");
+ connection_close (client);
+ return FALSE;
+ }
+
+ if (!(condition & G_IO_IN || condition & G_IO_PRI))
+ return TRUE;
+
+ read = g_input_stream_read (g_io_stream_get_input_stream (G_IO_STREAM (client->connection)),
+ buffer,
+ BUFFER_SIZE,
+ NULL,
+ &error);
+ if (read < 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 (read == 0)
+ return TRUE;
+
+ /* else, read > 0 */
+ if (!G_UNLIKELY (client->buffer))
+ client->buffer = g_byte_array_sized_new (read);
+ g_byte_array_append (client->buffer, buffer, read);
+
+ /* Try to parse input messages */
+ parse_request (client);
+
+ return TRUE;
+}
+
+static void
+incoming_cb (GSocketService *service,
+ GSocketConnection *connection,
+ GObject *unused,
+ QmiProxy *self)
+{
+ Client *client;
+
+ g_debug ("client connection open...");
+
+ /* 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);
+
+ /* Keep the client info around */
+ self->priv->clients = g_list_append (self->priv->clients, client);
+}
+
+static gboolean
+setup_socket_service (QmiProxy *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 (
+ QMI_PROXY_SOCKET_PATH,
+ -1,
+ G_UNIX_SOCKET_ADDRESS_ABSTRACT));
+ if (!g_socket_bind (socket, socket_address, TRUE, error))
+ 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: ", QMI_PROXY_SOCKET_PATH);
+ g_object_unref (socket);
+ return FALSE;
+ }
+
+ g_debug ("starting UNIX socket service at '%s'...", QMI_PROXY_SOCKET_PATH);
+ g_socket_service_start (self->priv->socket_service);
+ g_object_unref (socket);
+ return TRUE;
+}
+
+/*****************************************************************************/
+
+QmiProxy *
+qmi_proxy_new (GError **error)
+{
+ QmiProxy *self;
+
+ self = g_object_new (QMI_TYPE_PROXY, NULL);
+ if (!setup_socket_service (self, error))
+ g_clear_object (&self);
+ return self;
+}
+
+static void
+qmi_proxy_init (QmiProxy *self)
+{
+ /* Setup private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ QMI_TYPE_PROXY,
+ QmiProxyPrivate);
+}
+
+static void
+dispose (GObject *object)
+{
+ QmiProxyPrivate *priv = QMI_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 (QMI_PROXY_SOCKET_PATH);
+ g_debug ("UNIX socket service at '%s' stopped", QMI_PROXY_SOCKET_PATH);
+ }
+
+ G_OBJECT_CLASS (qmi_proxy_parent_class)->dispose (object);
+}
+
+static void
+qmi_proxy_class_init (QmiProxyClass *proxy_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (proxy_class);
+
+ g_type_class_add_private (object_class, sizeof (QmiProxyPrivate));
+
+ /* Virtual methods */
+ object_class->dispose = dispose;
+}
diff --git a/src/libqmi-glib/qmi-proxy.h b/src/libqmi-glib/qmi-proxy.h
new file mode 100644
index 00000000..9845dbef
--- /dev/null
+++ b/src/libqmi-glib/qmi-proxy.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * libqmi-glib -- GLib/GIO based library to control QMI 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) 2013 Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#ifndef QMI_PROXY_H
+#define QMI_PROXY_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#define QMI_TYPE_PROXY (qmi_proxy_get_type ())
+#define QMI_PROXY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), QMI_TYPE_PROXY, QmiProxy))
+#define QMI_PROXY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), QMI_TYPE_PROXY, QmiProxyClass))
+#define QMI_IS_PROXY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), QMI_TYPE_PROXY))
+#define QMI_IS_PROXY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), QMI_TYPE_PROXY))
+#define QMI_PROXY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), QMI_TYPE_PROXY, QmiProxyClass))
+
+typedef struct _QmiProxy QmiProxy;
+typedef struct _QmiProxyClass QmiProxyClass;
+typedef struct _QmiProxyPrivate QmiProxyPrivate;
+
+#define QMI_PROXY_SOCKET_PATH "qmi-proxy"
+
+struct _QmiProxy {
+ GObject parent;
+ QmiProxyPrivate *priv;
+};
+
+struct _QmiProxyClass {
+ GObjectClass parent;
+};
+
+GType qmi_proxy_get_type (void);
+
+QmiProxy *qmi_proxy_new (GError **error);
+
+#endif /* QMI_PROXY_H */
diff --git a/src/qmi-proxy/Makefile.am b/src/qmi-proxy/Makefile.am
new file mode 100644
index 00000000..114973e1
--- /dev/null
+++ b/src/qmi-proxy/Makefile.am
@@ -0,0 +1,16 @@
+
+sbin_PROGRAMS = qmi-proxy
+
+qmi_proxy_CPPFLAGS = \
+ $(GLIB_CFLAGS) \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/src/libqmi-glib \
+ -I$(top_srcdir)/src/libqmi-glib/generated \
+ -I$(top_builddir)/src/libqmi-glib \
+ -I$(top_builddir)/src/libqmi-glib/generated
+
+qmi_proxy_SOURCES = qmi-proxy.c
+
+qmi_proxy_LDADD = \
+ $(GLIB_LIBS) \
+ $(top_builddir)/src/libqmi-glib/libqmi-glib.la
diff --git a/src/qmi-proxy/qmi-proxy.c b/src/qmi-proxy/qmi-proxy.c
new file mode 100644
index 00000000..e879e725
--- /dev/null
+++ b/src/qmi-proxy/qmi-proxy.c
@@ -0,0 +1,171 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * qmid -- A proxy to communicate with QMI 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) 2013 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#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 <libqmi-glib.h>
+
+#define PROGRAM_NAME "qmid"
+#define PROGRAM_VERSION PACKAGE_VERSION
+
+/* Globals */
+static GMainLoop *loop;
+static QmiProxy *proxy;
+
+/* Main options */
+static gboolean version_flag;
+
+static GOptionEntry main_entries[] = {
+ { "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;
+ }
+
+ 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 (2013) Aleksander Morgado\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);
+}
+
+/*****************************************************************************/
+
+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 QMI 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 ("Qmi", G_LOG_LEVEL_MASK, log_handler, NULL);
+
+ /* 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 = qmi_proxy_new (&error);
+ if (!proxy) {
+ g_printerr ("error: %s\n", error->message);
+ exit (EXIT_FAILURE);
+ }
+
+ /* 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);
+
+ return EXIT_SUCCESS;
+}