Merge "Redesign requests, add callbacks, add power callback"
diff --git a/src/Makefile b/src/Makefile
index 56c3aac..7919a50 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -17,11 +17,13 @@
 	qmidms.c \
 	qmimsg.c \
 	util.c
+
 UNITTESTS = \
 	qmidev_unittest
 TESTS = \
 	connect_disconnect_test \
 	get_cid_stress_test \
+	power_test \
 	qmiinfo
 
 PC_CFLAGS := $(shell pkg-config --cflags glib-2.0)
@@ -52,6 +54,9 @@
 get_cid_stress_test: get_cid_stress_test.o | libqmi.so
 	$(LINK.c)
 
+power_test: power_test.o | libqmi.so
+	$(LINK.c)
+
 qmiinfo: qmiinfo.o | libqmi.so
 	$(LINK.c)
 
@@ -61,6 +66,7 @@
 	install -d $(DESTDIR)$(SBINDIR)
 	install -m755 connect_disconnect_test $(DESTDIR)$(SBINDIR)
 	install -m755 get_cid_stress_test $(DESTDIR)$(SBINDIR)
+	install -m755 power_test $(DESTDIR)$(SBINDIR)
 	install -m755 qmiinfo $(DESTDIR)$(SBINDIR)
 
 clean:
diff --git a/src/qmi.h b/src/qmi.h
index cfca765..fcf0555 100644
--- a/src/qmi.h
+++ b/src/qmi.h
@@ -16,6 +16,17 @@
 struct qmimsg;
 
 enum {
+  QMI_FLAG_CTL_RESPONSE   = 0x01,
+  QMI_FLAG_CTL_INDICATION = 0x02
+};
+
+enum {
+  QMI_FLAG_SVC_COMPOUND   = 0x01,
+  QMI_FLAG_SVC_RESPONSE   = 0x02,
+  QMI_FLAG_SVC_INDICATION = 0x04
+};
+
+enum {
   QMI_CID_NONE = 0x00,
   QMI_CID_BROADCAST = 0xFF
 };
@@ -53,6 +64,39 @@
   QMI_SVC_OMA = 0xE2
 };
 
+enum {
+  QMICTL_MSG_SET_INSTANCE_ID = 0x0020,
+  QMICTL_MSG_GET_VERSION_INFO,
+  QMICTL_MSG_GET_CLIENT_ID,
+  QMICTL_MSG_RELEASE_CLIENT_ID,
+  QMICTL_MSG_REVOKE_CLIENT_ID,
+  QMICTL_MSG_INVALID_CLIENT_ID,
+  QMICTL_MSG_SET_DATA_FORMAT,
+  QMICTL_MSG_SYNC,
+  QMICTL_MSG_EVENT,
+  QMICTL_MSG_SET_POWER_SAVE_CONFIG,
+  QMICTL_MSG_SET_POWER_SAVE_MODE,
+  QMICTL_MSG_GET_POWER_SAVE_MODE
+};
+
+enum {
+  QMIDMS_MSG_EVENT              = 0x0001,
+  QMIDMS_MSG_GET_IDS            = 0x0025,
+  QMIDMS_MSG_GET_OPERATING_MODE = 0x002D,
+  QMIDMS_MSG_SET_OPERATING_MODE = 0x002E
+};
+
+enum {
+  QMIDMS_EVENT_POWER_STATE = 0x10,
+  QMIDMS_EVENT_BATTERY_LEVEL,
+  QMIDMS_EVENT_PIN_STATUS,
+  QMIDMS_EVENT_ACTIVATION_STATE,
+  QMIDMS_EVENT_OPERATING_MODE,
+  QMIDMS_EVENT_UIM_STATE,
+  QMIDMS_EVENT_WIRELESS_DISABLE_STATE
+};
+
+
 /**
  * Gets the result from a TLV message.
  *
diff --git a/src/qmictl.c b/src/qmictl.c
index d442d3e..1e7717c 100644
--- a/src/qmictl.c
+++ b/src/qmictl.c
@@ -26,23 +26,7 @@
 #include "qmimsg.h"
 #include "util.h"
 
-/* CTL is always client zero. */
-#define QMI_CTL_CLIENT 0x00
-
-enum {
-  QMICTL_MSG_SET_INSTANCE_ID = 0x0020,
-  QMICTL_MSG_GET_VERSION_INFO,
-  QMICTL_MSG_GET_CLIENT_ID,
-  QMICTL_MSG_RELEASE_CLIENT_ID,
-  QMICTL_MSG_REVOKE_CLIENT_ID,
-  QMICTL_MSG_INVALID_CLIENT_ID,
-  QMICTL_MSG_SET_DATA_FORMAT,
-  QMICTL_MSG_SYNC,
-  QMICTL_MSG_EVENT,
-  QMICTL_MSG_SET_POWER_SAVE_CONFIG,
-  QMICTL_MSG_SET_POWER_SAVE_MODE,
-  QMICTL_MSG_GET_POWER_SAVE_MODE
-};
+struct service qmictl_service = { QMI_SVC_CTL, QMICTL_MSG_EVENT, NULL, NULL };
 
 struct qmi_cid {
   uint8_t service;
@@ -57,31 +41,31 @@
   return qmimsg_tlv_get(message, QMI_TLV_VALUE, sizeof(*id_out), id_out);
 }
 
+/* QMI CTL call: Get Client ID */
+
 struct get_cid_context {
   qmictl_get_cid_response_fn callback;
   void *context;
   uint8_t service;
 };
 
-static void got_cid(struct qmidev *qmidev,
-                   void *data,
-                   int status,
-                   struct qmimsg *message)
+static int get_cid_request(void *data,
+                           struct qmimsg *message)
 {
-  assert(qmidev);
-  assert(data);
-  assert(message);
+  uint8_t *service = data;
+  return qmimsg_tlv_add(message, QMI_TLV_VALUE, sizeof(*service), service);
+}
 
+static void get_cid_response(struct qmidev *qmidev,
+                             void *data,
+                             int result,
+                             struct qmimsg *message)
+{
   struct get_cid_context *context = data;
-  struct qmi_cid id;
-  int result;
+  struct qmi_cid id = { 0, 0 };
 
-  id.client = 0;
-
-  if (status) {
-    result = status;
+  if (result)
     goto out;
-  }
 
   result = ctl_get_id(message, &id);
   if (result)
@@ -92,8 +76,6 @@
     goto out;
   }
 
-  result = 0;
-
 out:
   context->callback(qmidev, context->context, result, id.client);
   g_slice_free(struct get_cid_context, context);
@@ -103,46 +85,42 @@
                    qmictl_get_cid_response_fn caller_callback,
                    void *caller_context)
 {
+  struct get_cid_context *context;
   int result;
 
-  struct get_cid_context *context = g_slice_new(struct get_cid_context);
+  context = g_slice_new(struct get_cid_context);
   context->callback = caller_callback;
   context->context  = caller_context;
   context->service  = service;
 
-  struct qmimsg *message;
-  result = qmidev_make_request(qmidev,
-                               QMI_SVC_CTL,
-                               QMICTL_MSG_GET_CLIENT_ID,
-                               &message);
+  result = qmidev_request(qmidev, &qmictl_service,
+                          QMICTL_MSG_GET_CLIENT_ID,
+                          &get_cid_request, &service,
+                          &get_cid_response, context);
   if (result)
-    goto fail;
-
-  qmimsg_tlv_add(message, QMI_TLV_VALUE, sizeof(service), &service);
-
-  result = qmidev_send_request(qmidev, message,
-                               QMICTL_MSG_GET_CLIENT_ID,
-                               &got_cid, context);
-  if (result)
-    goto fail;
-
-  return 0;
-
-fail:
-  g_slice_free(struct get_cid_context, context);
+    g_slice_free(struct get_cid_context, context);
   return result;
 }
 
+/* QMI CTL call: Release Client ID */
+
 struct release_cid_context {
   qmidev_response_fn callback;
   void *context;
   struct qmi_cid id;
 };
 
-static void released_cid(struct qmidev *qmidev,
-                         void *data,
-                         int status,
-                         struct qmimsg *message)
+static int release_cid_request(void *data,
+                               struct qmimsg *message)
+{
+  struct qmi_cid *id = data;
+  return qmimsg_tlv_add(message, QMI_TLV_VALUE, sizeof(*id), id);
+}
+
+static void release_cid_response(struct qmidev *qmidev,
+                                 void *data,
+                                 int result,
+                                 struct qmimsg *message)
 {
   assert(qmidev);
   assert(data);
@@ -150,24 +128,17 @@
 
   struct release_cid_context *context = data;
   struct qmi_cid id;
-  int result;
 
-  if (status) {
-    result = status;
+  if (result)
     goto out;
-  }
 
   result = ctl_get_id(message, &id);
   if (result)
     goto out;
 
   if (id.service != context->id.service ||
-      id.client != context->id.client) {
+      id.client != context->id.client)
     result = QMI_ERR_MESSAGE_INVALID;
-    goto out;
-  }
-
-  result = 0;
 
 out:
   context->callback(qmidev, context->context, result);
@@ -178,34 +149,20 @@
                        qmidev_response_fn caller_callback,
                        void *caller_context)
 {
+  struct release_cid_context *context;
   int result;
 
-  struct release_cid_context *context =
-      g_slice_new(struct release_cid_context);
+  context = g_slice_new(struct release_cid_context);
   context->callback   = caller_callback;
   context->context    = caller_context;
   context->id.service = service;
   context->id.client  = client;
 
-  struct qmimsg *message;
-  result = qmidev_make_request(qmidev,
-                               QMI_SVC_CTL,
-                               QMICTL_MSG_RELEASE_CLIENT_ID,
-                               &message);
+  result = qmidev_request(qmidev, &qmictl_service,
+                          QMICTL_MSG_RELEASE_CLIENT_ID,
+                          &release_cid_request, &context->id,
+                          &release_cid_response, context);
   if (result)
-    goto fail;
-
-  qmimsg_tlv_add(message, QMI_TLV_VALUE, sizeof(context->id), &context->id);
-
-  result = qmidev_send_request(qmidev, message,
-                               QMICTL_MSG_RELEASE_CLIENT_ID,
-                               &released_cid, context);
-  if (result)
-    goto fail;
-
-  return 0;
-
-fail:
-  g_slice_free(struct release_cid_context, context);
+    g_slice_free(struct release_cid_context, context);
   return result;
 }
diff --git a/src/qmictl.h b/src/qmictl.h
index 093bbb0..ff948b5 100644
--- a/src/qmictl.h
+++ b/src/qmictl.h
@@ -17,6 +17,10 @@
 
 #include <libqmi.h>
 
+#include "qmidev.h"
+
+struct service qmictl_service;
+
 /**
  * Callback type for qmictl_get_cid.
  *
diff --git a/src/qmidev.c b/src/qmidev.c
index a97ef01..23ca917 100644
--- a/src/qmidev.c
+++ b/src/qmidev.c
@@ -10,6 +10,7 @@
 #include <errno.h>
 #include <glib.h>
 #include <stdint.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
@@ -24,6 +25,8 @@
 #include "poller.h"
 #include "qmi.h"
 #include "qmictl.h"
+#include "qmidev.h"
+#include "qmidms.h"
 #include "qmimsg.h"
 #include "util.h"
 
@@ -36,21 +39,45 @@
 
   struct dev *dev;
   struct polled *dev_polled;
-  int timerfd;
-  struct polled *timer_polled;
 
   GList *callbacks;
   GList *listeners;
-  GList *services;
+  GList *clients;
 
   int connected;
-  uint8_t control_tid;
 };
 
-struct qmisvc {
-  uint8_t sid;
-  uint8_t cid;
-  uint16_t next_tid;
+enum {
+  MATCH_SERVICE     = 0x01,
+  MATCH_CLIENT      = 0x02,
+  MATCH_QMI_FLAGS   = 0x04,
+  MATCH_TRANSACTION = 0x08,
+  MATCH_MESSAGE     = 0x10
+};
+
+/**
+ * Callback type for qmidev_listen.
+ *
+ * Will be called when a message matching the listen criteria is received.
+ *
+ * @qmidev: the QMI device on which a message was received
+ * @context: the context passed to qmidev_listen
+ * @message: the message received
+ *
+ * The callback should return zero to continue listening for such messages,
+ * or non-zero to remove the listener.
+ */
+typedef int (*qmidev_listen_callback_fn)(struct qmidev *qmidev,
+                                         void *context,
+                                         struct qmimsg *message);
+
+struct listen_criteria {
+  int flags;
+  uint8_t service;
+  uint8_t client;
+  uint8_t qmi_flags;
+  uint16_t transaction;
+  uint16_t message;
 };
 
 struct listener {
@@ -64,6 +91,11 @@
   void *context;
 };
 
+static int service_is_control(struct service *service)
+{
+  return !service->service_id;
+}
+
 static int call_listeners(struct qmidev *qmidev, struct qmimsg *message);
 static void call_callbacks(struct qmidev *qmidev);
 
@@ -79,9 +111,9 @@
 
   qmidev->callbacks = NULL;
   qmidev->listeners = NULL;
+  qmidev->clients = NULL;
 
   qmidev->connected = 0;
-  qmidev->control_tid = 1;
 
   return qmidev;
 }
@@ -138,23 +170,620 @@
   return poller_poll(qmidev->poller);
 }
 
-static struct qmisvc *qmisvc_alloc(uint8_t sid)
+struct listener *qmidev_listen(struct qmidev *qmidev,
+                               struct listen_criteria *criteria,
+                               qmidev_listen_callback_fn callback,
+                               void *context)
 {
-  struct qmisvc *svc = g_slice_new(struct qmisvc);
-  svc->sid = sid;
-  svc->cid = QMI_CID_NONE;
-  svc->next_tid = 1;
-  return svc;
+  assert(qmidev);
+  assert(criteria);
+  assert(callback);
+
+  struct listener *listener = g_slice_new(struct listener);
+
+  listener->criteria = *criteria;
+  listener->callback = callback;
+  listener->context = context;
+
+  qmidev->listeners = g_list_prepend(qmidev->listeners, listener);
+
+  return listener;
 }
 
-static void qmisvc_free(struct qmisvc *svc)
+void qmidev_cancel_listen(struct qmidev *qmidev, struct listener *listener)
 {
-  g_slice_free(struct qmisvc, svc);
+  assert(qmidev);
+  assert(listener);
+
+  qmidev->listeners = g_list_remove(qmidev->listeners, listener);
+  g_slice_free(struct listener, listener);
 }
 
+struct call_listener_context {
+  struct qmidev *qmidev;
+  struct qmimsg *message;
+  uint8_t msg_service;
+  uint8_t msg_client;
+  uint8_t msg_qmi_flags;
+  uint16_t msg_transaction;
+  uint16_t msg_message;
+  int calls;
+};
+
+static void call_listener(void *data, void *user_data)
+{
+  struct call_listener_context *context = user_data;
+  struct listener *listener = data;
+  struct listen_criteria *crit = &listener->criteria;
+  int flags = crit->flags;
+
+  if ((flags & MATCH_SERVICE) &&
+      (crit->service != context->msg_service))
+    return;
+
+  if ((flags & MATCH_CLIENT) &&
+      (crit->client != context->msg_client))
+    return;
+
+  if ((flags & MATCH_QMI_FLAGS) &&
+      (crit->qmi_flags != context->msg_qmi_flags))
+    return;
+
+  if ((flags & MATCH_TRANSACTION) &&
+      (crit->transaction != context->msg_transaction))
+    return;
+
+  if ((flags & MATCH_MESSAGE) &&
+      (crit->message != context->msg_message))
+    return;
+
+  int result = listener->callback(context->qmidev,
+                                  listener->context,
+                                  context->message);
+  if (result) {
+    /* The glib documentation does not note this, but it is in fact safe to
+       call g_list_remove while called back from g_list_foreach. */
+    qmidev_cancel_listen(context->qmidev, listener);
+  }
+
+  context->calls++;
+}
+
+static int call_listeners(struct qmidev *qmidev, struct qmimsg *message)
+{
+  struct call_listener_context context;
+  context.qmidev = qmidev;
+  context.message = message;
+  qmimsg_get_header(message,
+                    &context.msg_service,
+                    &context.msg_client,
+                    &context.msg_qmi_flags,
+                    &context.msg_transaction,
+                    &context.msg_message);
+  context.calls = 0;
+  g_list_foreach(qmidev->listeners, &call_listener, &context);
+  return context.calls == 0;
+}
+
+
+
+struct client {
+  struct service *service;
+
+  uint8_t client_id;
+  uint16_t next_transaction_id;
+  struct listener *event_listener;
+  GList *events;
+
+  void *priv;
+};
+
+static struct client *make_ctl_client(struct qmidev *qmidev)
+{
+  struct client *client = g_slice_new(struct client);
+
+  client->service = &qmictl_service;
+  client->client_id = 0x00;
+  client->next_transaction_id = 0x0001;
+  client->events = NULL;
+  client->priv = NULL;
+
+  if (client->service->create_fn)
+    client->service->create_fn(qmidev, client);
+
+  return client;
+}
+
+struct transaction {
+  struct client *client;
+  uint16_t transaction_id;
+
+  struct listener *response_listener;
+  response_fn callback;
+  void *context;
+};
+
+struct call_event_listeners_context {
+  struct qmidev *qmidev;
+  struct client *client;
+  uint8_t type;
+  uint16_t length;
+  void *value;
+  int calls;
+};
+
+static void call_event_listener(void *data, void *user_data)
+{
+  struct call_event_listeners_context *context = user_data;
+  struct event *event = data;
+
+  if (event->type != context->type)
+    return;
+
+  event->callback(context->qmidev,
+                  event->user_callback,
+                  event->user_context,
+                  context->length,
+                  context->value);
+
+  context->calls++;
+}
+
+static void call_event_listeners(void *data,
+                                 uint8_t type, uint16_t length, void *value)
+{
+  struct call_event_listeners_context *context = data;
+
+  context->type = type;
+  context->length = length;
+  context->value = value;
+  context->calls = 0;
+
+  g_list_foreach(context->client->events, &call_event_listener, context);
+}
+
+static int event_message_received(struct qmidev *qmidev,
+                                   void *data,
+                                   struct qmimsg *message)
+{
+  struct call_event_listeners_context context;
+
+  context.qmidev = qmidev;
+  context.client = data;
+  qmimsg_tlv_foreach(message, &call_event_listeners, &context);
+
+  return 0;
+}
+
+struct get_client_context {
+  struct client *client;
+  qmidev_client_fn callback;
+  void *context;
+};
+
+static void got_cid(struct qmidev *qmidev,
+                    void *data,
+                    int status,
+                    uint8_t cid)
+{
+  struct get_client_context *context = data;
+  struct client *client = context->client;
+  struct listen_criteria event_criteria;
+
+  if (status) {
+    g_slice_free(struct client, client);
+    client = NULL;
+    goto out;
+  }
+
+  client->client_id = cid;
+  if (client->service->create_fn)
+    client->service->create_fn(qmidev, client);
+
+  event_criteria.flags = MATCH_SERVICE | MATCH_CLIENT | MATCH_QMI_FLAGS |
+                         MATCH_MESSAGE;
+  event_criteria.service = client->service->service_id;
+  event_criteria.client = client->client_id;
+  if (service_is_control(client->service))
+    event_criteria.qmi_flags = QMI_FLAG_CTL_INDICATION;
+  else
+    event_criteria.qmi_flags = QMI_FLAG_SVC_INDICATION;
+  event_criteria.message = client->service->event_msg_id;
+  client->event_listener = qmidev_listen(qmidev,
+                                         &event_criteria,
+                                         &event_message_received,
+                                         client);
+
+out:
+  context->callback(qmidev, context->context, status, client);
+  g_slice_free(struct get_client_context, context);
+}
+
+int qmidev_get_client(struct qmidev *qmidev,
+                      struct service *service,
+                      qmidev_client_fn caller_callback,
+                      void *caller_context)
+{
+  struct client *client;
+  struct get_client_context *context;
+  int result;
+
+  client = g_slice_new(struct client);
+  client->service = service;
+  client->next_transaction_id = 0x0001;
+  client->events = NULL;
+  client->priv = NULL;
+
+  context = g_slice_new(struct get_client_context);
+  context->client = client;
+  context->callback = caller_callback;
+  context->context = caller_context;
+
+  result = qmictl_get_cid(qmidev,
+                          client->service->service_id,
+                          &got_cid,
+                          context);
+  if (result) {
+    g_slice_free(struct get_client_context, context);
+    g_slice_free(struct client, client);
+  }
+
+  return result;
+}
+
+struct release_client_context {
+  struct client *client;
+  qmidev_response_fn callback;
+  void *context;
+};
+
+static void released_cid(struct qmidev *qmidev,
+                         void *data,
+                         int status)
+{
+  struct release_client_context *context = data;
+  struct client *client = context->client;
+
+  if (status)
+    goto out;
+
+  qmidev_cancel_listen(qmidev, client->event_listener);
+
+  /* TODO: Free things that reference client (transactions). */
+
+  if (context->client->service->destroy_fn)
+      context->client->service->destroy_fn(qmidev, context->client);
+  g_slice_free(struct client, context->client);
+
+out:
+  context->callback(qmidev, context->context, status);
+  g_slice_free(struct release_client_context, context);
+}
+
+int qmidev_release_client(struct qmidev *qmidev,
+                          struct client *client,
+                          qmidev_response_fn caller_callback,
+                          void *caller_context)
+{
+  struct release_client_context *context;
+  int result;
+
+  context = g_slice_new(struct release_client_context);
+  context->client = client;
+  context->callback = caller_callback;
+  context->context = caller_context;
+
+  result = qmictl_release_cid(qmidev,
+                              client->service->service_id,
+                              client->client_id,
+                              &released_cid,
+                              context);
+  if (result)
+    g_slice_free(struct release_client_context, context);
+
+  return result;
+}
+
+static gint client_compare_service(gconstpointer a, gconstpointer b)
+{
+  const struct client *client_a = a;
+  const struct client *client_b = b;
+
+  return client_a->service - client_b->service;
+}
+
+static struct client *find_client(struct qmidev *qmidev,
+                                  struct service *service)
+{
+  struct client wanted;
+  GList *found_list;
+
+  wanted.service = service;
+  found_list = g_list_find_custom(qmidev->clients,
+                                  &wanted,
+                                  &client_compare_service);
+
+  if (found_list)
+    return (struct client *) found_list->data;
+  else
+    return NULL;
+}
+
+struct client *qmidev_default_client(struct qmidev *qmidev,
+                                     struct service *service)
+{
+  return find_client(qmidev, service);
+}
+
+static struct transaction *make_transaction(struct client *client,
+                                            response_fn callback,
+                                            void *context)
+{
+  struct transaction *transaction;
+
+  transaction = g_slice_new(struct transaction);
+  transaction->client = client;
+  transaction->transaction_id = client->next_transaction_id;
+  transaction->callback = callback;
+  transaction->context = context;
+
+  return transaction;
+}
+
+static void increment_transaction_id(struct client *client)
+{
+  static const uint16_t min_transaction_id = 0x0001;
+  uint16_t max_transaction_id;
+
+  if (client->service->service_id == QMI_SVC_CTL)
+    max_transaction_id = 0x00FF;
+  else
+    max_transaction_id = 0xFFFF;
+
+  if (client->next_transaction_id < max_transaction_id)
+    client->next_transaction_id++;
+  else
+    client->next_transaction_id = min_transaction_id;
+}
+
+static int response_received(struct qmidev *qmidev,
+                             void *data,
+                             struct qmimsg *message)
+{
+  struct transaction *transaction = data;
+
+  transaction->callback(qmidev,
+                        transaction->context,
+                        qmi_result(message),
+                        message);
+  g_slice_free(struct transaction, transaction);
+
+  /* remove listener */
+  return 1;
+}
+
+int qmidev_client_request(struct qmidev *qmidev,
+                          struct client *client,
+                          uint16_t msg_id,
+                          request_fn make_request_fn,
+                          void *request_context,
+                          response_fn handle_response_fn,
+                          void *response_context)
+{
+  struct transaction *transaction;
+  struct qmimsg *request_message;
+  struct listen_criteria response_criteria;
+  int result;
+
+  transaction = make_transaction(client, handle_response_fn, response_context);
+
+  qmimsg_new(0, client->service->service_id, client->client_id,
+             0, transaction->transaction_id, msg_id,
+             &request_message);
+  if (make_request_fn) {
+    result = make_request_fn(request_context, request_message);
+    if (result)
+      goto fail;
+  }
+
+  response_criteria.flags = MATCH_SERVICE | MATCH_CLIENT | MATCH_TRANSACTION |
+                            MATCH_QMI_FLAGS | MATCH_MESSAGE;
+  response_criteria.service = client->service->service_id;
+  response_criteria.client = client->client_id;
+  if (service_is_control(client->service))
+    response_criteria.qmi_flags = QMI_FLAG_CTL_RESPONSE;
+  else
+    response_criteria.qmi_flags = QMI_FLAG_SVC_RESPONSE;
+  response_criteria.transaction = client->next_transaction_id;
+  response_criteria.message = msg_id;
+
+  transaction->response_listener =
+          qmidev_listen(qmidev,
+                        &response_criteria,
+                        &response_received,
+                        transaction);
+
+  /* TODO: Timeout. */
+
+  result = qmidev_send(qmidev, request_message);
+
+  increment_transaction_id(client);
+
+  return 0;
+
+fail:
+  g_slice_free(struct transaction, transaction);
+  qmimsg_free(request_message);
+  return result;
+}
+
+int qmidev_request(struct qmidev *qmidev,
+                   struct service *service,
+                   uint16_t msg_id,
+                   request_fn make_request_fn,
+                   void *request_context,
+                   response_fn handle_response_fn,
+                   void *response_context)
+{
+  struct client *client;
+
+  client = find_client(qmidev, service);
+  if (!client)
+    return QMI_ERR_SERVICE_UNAVAILABLE;
+
+  return qmidev_client_request(qmidev, client, msg_id,
+                               make_request_fn, request_context,
+                               handle_response_fn, response_context);
+}
+
+struct void_context {
+  qmidev_response_fn callback;
+  void *context;
+};
+
+static void void_response_received(struct qmidev *qmidev,
+                                   void *data,
+                                   int result,
+                                   struct qmimsg *message)
+{
+  struct void_context *context = data;
+
+  /* nothing useful in message. */
+  message = message;
+
+  context->callback(qmidev, context->context, result);
+  g_slice_free(struct void_context, context);
+}
+
+int qmidev_void_request(struct qmidev *qmidev,
+                        struct service *service,
+                        uint16_t msg_id,
+                        request_fn make_request_fn,
+                        void *request_context,
+                        qmidev_response_fn handle_response_fn,
+                        void *response_context)
+{
+  struct void_context *context;
+  int result;
+
+  context = g_slice_new(struct void_context);
+  context->callback = handle_response_fn;
+  context->context = response_context;
+
+  result = qmidev_request(qmidev, service, msg_id,
+                          make_request_fn, request_context,
+                          &void_response_received, context);
+  if (result)
+    g_slice_free(struct void_context, context);
+  return result;
+}
+
+void *client_priv(struct client *client)
+{
+  assert(client);
+  return client->priv;
+}
+
+void client_set_priv(struct client *client, void *priv)
+{
+  assert(client);
+  client->priv = priv;
+}
+
+void event_init(struct event *event, uint8_t type, event_fn callback)
+{
+  event->type = type;
+  event->listener = NULL;
+  event->callback = callback;
+  event->user_callback = NULL;
+  event->user_context = NULL;
+}
+
+struct set_callback_context {
+  uint8_t event_type;
+  uint8_t report_event;
+  qmidev_response_fn callback;
+  void *context;
+};
+
+static void trivial_set_callback_done(struct qmidev *qmidev,
+                                      void *data)
+{
+  struct set_callback_context *context = data;
+  context->callback(qmidev, context->context, 0);
+  g_slice_free(struct set_callback_context, context);
+}
+
+int set_event_report_request(void *data,
+                             struct qmimsg *message)
+{
+  struct set_callback_context *context = data;
+  return qmimsg_tlv_add(message,
+                        context->event_type,
+                        sizeof(context->report_event),
+                        &context->report_event);
+}
+
+void set_event_report_response(struct qmidev *qmidev,
+                               void *data,
+                               int result,
+                               struct qmimsg *message)
+{
+  struct set_callback_context *context = data;
+
+  /* nothing useful in message */
+  message = message;
+
+  context->callback(qmidev, context->context, result);
+  g_slice_free(struct set_callback_context, context);
+}
+
+int qmidev_set_callback(struct qmidev *qmidev,
+                        struct client *client,
+                        struct event *event,
+                        void *user_callback,
+                        void *user_context,
+                        qmidev_response_fn caller_callback,
+                        void *caller_context)
+{
+  struct set_callback_context *context;
+  int active_before, active_after;
+  int result;
+
+  active_before = !!event->user_callback;
+  active_after = !!user_callback;
+
+  context = g_slice_new(struct set_callback_context);
+  context->event_type = event->type;
+  context->report_event = active_after ? 0x01 : 0x00;
+  context->callback = caller_callback;
+  context->context = caller_context;
+
+  event->user_callback = user_callback;
+  event->user_context = user_context;
+
+  if (active_before == active_after) {
+    qmidev_call_later(qmidev, &trivial_set_callback_done, context);
+    return 0;
+  }
+
+  if (active_after)
+    client->events = g_list_prepend(client->events, event);
+  else
+    client->events = g_list_remove(client->events, event);
+
+  result = qmidev_client_request(qmidev, client,
+                                 client->service->event_msg_id,
+                                 &set_event_report_request, context,
+                                 &set_event_report_response, context);
+  if (result)
+    g_slice_free(struct set_callback_context, context);
+  return result;
+}
+
+
 struct connect_context {
   GQueue *pending_services;
-  struct qmisvc *current_service;
 
   qmidev_response_fn caller_callback;
   void *caller_context;
@@ -163,19 +792,15 @@
 static void connect_next_service(struct qmidev *qmidev,
                                  struct connect_context *context);
 
-static void got_cid(struct qmidev *qmidev,
-                    void *data,
-                    int status,
-                    uint8_t cid)
+static void got_client(struct qmidev *qmidev,
+                       void *data,
+                       int status,
+                       struct client *client)
 {
   struct connect_context *context = data;
 
   if (!status) {
-    context->current_service->cid = cid;
-    qmidev->services = g_list_prepend(qmidev->services,
-                                      context->current_service);
-  } else {
-    qmisvc_free(context->current_service);
+    qmidev->clients = g_list_prepend(qmidev->clients, client);
   }
 
   connect_next_service(qmidev, context);
@@ -184,17 +809,21 @@
 static void connect_next_service(struct qmidev *qmidev,
                                  struct connect_context *context)
 {
-  if (g_queue_is_empty(context->pending_services)) {
-    context->caller_callback(qmidev, context->caller_context, 0);
-    g_queue_free(context->pending_services);
-    g_slice_free(struct connect_context, context);
-    return;
-  }
+  struct service *next_service;
+  int result;
 
-  context->current_service = g_queue_pop_head(context->pending_services);
-  /* TODO: Check return */
-  qmictl_get_cid(qmidev, context->current_service->sid,
-                 &got_cid, context);
+  assert(qmidev);
+  assert(context);
+
+  if (!g_queue_is_empty(context->pending_services)) {
+    next_service = g_queue_pop_head(context->pending_services);
+    result = qmidev_get_client(qmidev, next_service, &got_client, context);
+    if (result)
+      context->caller_callback(qmidev, context->caller_context, result);
+  } else {
+    context->caller_callback(qmidev, context->caller_context, 0);
+    g_slice_free(struct connect_context, context);
+  }
 }
 
 int qmidev_connect(struct qmidev *qmidev,
@@ -214,24 +843,14 @@
 
   qmidev->connected = 1;
 
+  qmidev->clients = g_list_prepend(qmidev->clients, make_ctl_client(qmidev));
+
   struct connect_context *context = g_slice_new(struct connect_context);
   context->caller_callback = caller_callback;
   context->caller_context = caller_context;
-  context->current_service = NULL;
   context->pending_services = g_queue_new();
 
-  qmidev->services = g_list_append(qmidev->services, qmisvc_alloc(QMI_SVC_CTL));
-  g_queue_push_tail(context->pending_services, qmisvc_alloc(QMI_SVC_WDS));
-  g_queue_push_tail(context->pending_services, qmisvc_alloc(QMI_SVC_DMS));
-  g_queue_push_tail(context->pending_services, qmisvc_alloc(QMI_SVC_NAS));
-  g_queue_push_tail(context->pending_services, qmisvc_alloc(QMI_SVC_QOS));
-  g_queue_push_tail(context->pending_services, qmisvc_alloc(QMI_SVC_WMS));
-  g_queue_push_tail(context->pending_services, qmisvc_alloc(QMI_SVC_PDS));
-  g_queue_push_tail(context->pending_services, qmisvc_alloc(QMI_SVC_AUTH));
-  g_queue_push_tail(context->pending_services, qmisvc_alloc(QMI_SVC_VOICE));
-  g_queue_push_tail(context->pending_services, qmisvc_alloc(QMI_SVC_CAT));
-  g_queue_push_tail(context->pending_services, qmisvc_alloc(QMI_SVC_RMS));
-  g_queue_push_tail(context->pending_services, qmisvc_alloc(QMI_SVC_OMA));
+  g_queue_push_tail(context->pending_services, &qmidms_service);
 
   connect_next_service(qmidev, context);
 
@@ -239,13 +858,11 @@
 }
 
 struct disconnect_context {
-  struct qmisvc *current_service;
-
   qmidev_response_fn caller_callback;
   void *caller_context;
 };
 
-static void services_disconnected(struct qmidev *qmidev,
+static void disconnected_services(struct qmidev *qmidev,
                                   struct disconnect_context *context)
 {
   int result = dev_close(qmidev->dev);
@@ -257,39 +874,41 @@
 static void disconnect_next_service(struct qmidev *qmidev,
                                     struct disconnect_context *context);
 
-static void released_cid(struct qmidev *qmidev,
-                         void *data,
-                         int status)
+static void released_client(struct qmidev *qmidev,
+                            void *data,
+                            int status)
 {
-  struct disconnect_context *context = data;
-
   if (status) {
     /* TODO: What do we do if release fails? */
   }
 
-  qmisvc_free(context->current_service);
-  disconnect_next_service(qmidev, context);
+  disconnect_next_service(qmidev, data);
 }
 
 static void disconnect_next_service(struct qmidev *qmidev,
                                     struct disconnect_context *context)
 {
-  if (!qmidev->services) {
-    services_disconnected(qmidev, context);
+  struct client *next_client;
+  int result;
+
+  if (!qmidev->clients) {
+    disconnected_services(qmidev, context);
     return;
   }
 
-  context->current_service = qmidev->services->data;
-  qmidev->services = g_list_remove(qmidev->services, context->current_service);
-  /* TODO: Check return */
-  if (context->current_service->sid != QMI_SVC_CTL) {
-    qmictl_release_cid(qmidev, context->current_service->sid,
-                       context->current_service->cid,
-                       &released_cid, context);
-  } else {
-    qmisvc_free(context->current_service);
+  next_client = qmidev->clients->data;
+  qmidev->clients = g_list_remove(qmidev->clients, next_client);
+  if (!next_client->client_id) {
+    /* This is the CTL client.  Free it by hand and try the next one. */
+    g_slice_free(struct client, next_client);
     disconnect_next_service(qmidev, context);
+    return;
   }
+
+  result = qmidev_release_client(qmidev, next_client,
+                                 &released_client, context);
+  if (result)
+    context->caller_callback(qmidev, context->caller_context, result);
 }
 
 int qmidev_disconnect(struct qmidev *qmidev,
@@ -305,180 +924,12 @@
   struct disconnect_context *context = g_slice_new(struct disconnect_context);
   context->caller_callback = caller_callback;
   context->caller_context = caller_context;
-  context->current_service = NULL;
 
   disconnect_next_service(qmidev, context);
 
   return 0;
 }
 
-gint service_compare_sid(gconstpointer a, gconstpointer b)
-{
-  const struct qmisvc *svc_a = a;
-  const struct qmisvc *svc_b = b;
-
-  return svc_a->sid - svc_b->sid;
-}
-
-struct qmisvc *find_service(struct qmidev *qmidev, uint8_t service_id)
-{
-  struct qmisvc wanted;
-  wanted.sid = service_id;
-
-  GList *found_list = g_list_find_custom(qmidev->services, &wanted,
-                                         &service_compare_sid);
-  if (found_list)
-    return (struct qmisvc *) found_list->data;
-  else
-    return NULL;
-}
-
-uint16_t get_free_tid(struct qmisvc *service)
-{
-  uint16_t next_tid = service->next_tid;
-
-  /* CTL uses 8-bit transaction IDs; other services use 16-bit. */
-  uint16_t max_tid = (service->sid == QMI_SVC_CTL) ? 0x00FF : 0xFFFF;
-  if (service->next_tid < max_tid)
-    service->next_tid++;
-  else
-    service->next_tid = 0x0001;
-
-  return next_tid;
-}
-
-int qmidev_make_request(struct qmidev *qmidev,
-                        uint8_t service_id,
-                        uint16_t req_msg_id,
-                        struct qmimsg **message_out)
-{
-  struct qmisvc *service;
-  uint16_t transaction_id;
-  struct qmimsg *message;
-  int result;
-
-  assert(qmidev);
-  assert(message_out);
-
-  service = find_service(qmidev, service_id);
-  if (!service)
-    return QMI_ERR_SERVICE_UNAVAILABLE;
-
-  transaction_id = get_free_tid(service);
-
-  result = qmimsg_new(0, service_id, service->cid,
-                      0, transaction_id, req_msg_id,
-                      &message);
-  if (result)
-    return result;
-
-  *message_out = message;
-
-  return 0;
-}
-
-struct request_context {
-  uint16_t resp_msg_id;
-
-  qmidev_request_fn caller_callback;
-  void *caller_context;
-};
-
-int got_reply(struct qmidev *qmidev, void *data, struct qmimsg *message)
-{
-  struct request_context *context = data;
-  int result;
-
-  /* TODO: Check response message ID. */
-
-  result = qmi_result(message);
-  if (result)
-    goto out;
-
-out:
-  context->caller_callback(qmidev, context->caller_context, result, message);
-  g_slice_free(struct request_context, context);
-  return 1;
-}
-
-int qmidev_send_request(struct qmidev *qmidev,
-                        struct qmimsg *req_msg,
-                        uint16_t resp_msg_id,
-                        qmidev_request_fn caller_callback,
-                        void *caller_context)
-{
-  assert(qmidev);
-  assert(req_msg);
-  assert(caller_callback);
-
-  if (!qmidev->connected)
-    return QMI_ERR_NOT_CONNECTED;
-
-  struct request_context *context = g_slice_new(struct request_context);
-
-  context->caller_callback = caller_callback;
-  context->caller_context = caller_context;
-  context->resp_msg_id = resp_msg_id;
-
-  struct listen_criteria criteria;
-  qmimsg_get_header(req_msg,
-                    &criteria.service,
-                    &criteria.client,
-                    &criteria.transaction);
-  struct listener *listener = qmidev_listen(qmidev, &criteria,
-                                            &got_reply, context);
-
-  /* TODO: Do something with listener?  Currently we don't need it for
-     anything, but once we add timeouts we'll need to be able to cancel
-     it. */
-
-  int result = qmidev_send(qmidev, req_msg);
-  if (result) {
-    qmidev_cancel_listen(qmidev, listener);
-    g_slice_free(struct request_context, context);
-    qmimsg_free(req_msg);
-    return result;
-  }
-
-  return 0;
-}
-
-struct void_context {
-  qmidev_response_fn caller_callback;
-  void *caller_context;
-};
-
-static void void_callback(struct qmidev *qmidev,
-                          void *data,
-                          int result,
-                          struct qmimsg *message)
-{
-  struct void_context *context = data;
-
-  assert(qmidev);
-  assert(data);
-
-  /* unused */
-  message = message;
-
-  context->caller_callback(qmidev, context->caller_context, result);
-  g_slice_free(struct void_context, context);
-}
-
-int qmidev_send_void_request(struct qmidev *qmidev,
-                             struct qmimsg *req_msg,
-                             uint16_t resp_msg_id,
-                             qmidev_response_fn caller_callback,
-                             void *caller_context)
-{
-  struct void_context *context = g_slice_new(struct void_context);
-  context->caller_callback = caller_callback;
-  context->caller_context = caller_context;
-
-  return qmidev_send_request(qmidev, req_msg, resp_msg_id,
-                             &void_callback, context);
-}
-
 static int dev_read_fn(void *ctx, void *buf, size_t len)
 {
   assert(ctx);
@@ -496,11 +947,12 @@
 
   struct qmimsg *msg;
   int result = qmimsg_read(&dev_read_fn, qmidev, &msg);
-  /* TODO: Do something. */
   if (result)
     return;
-  if (call_listeners(qmidev, msg) && qmidev_log_unhandled_messages)
+  if (call_listeners(qmidev, msg) && qmidev_log_unhandled_messages) {
+    fprintf(stderr, "Unhandled message received:\n");
     qmimsg_print(msg);
+  }
   qmimsg_free(msg);
 }
 
@@ -600,79 +1052,30 @@
 
 
 
+
+/**
+ * Starts listening for QMI messages matching certain criteria.
+ *
+ * @qmidev: the QMI device
+ * @criteria: the listen criteria (see struct listen_criteria)
+ * @callback: the callback to call when a matching message is received
+ * @context: the context with which to call the callback
+ *
+ * Returns a pointer to a struct listener that can be passed to
+ * qmidev_cancel_listen to stop listening.
+ */
+
 struct listener *qmidev_listen(struct qmidev *qmidev,
                                struct listen_criteria *criteria,
                                qmidev_listen_callback_fn callback,
-                               void *context)
-{
-  assert(qmidev);
-  assert(criteria);
-  assert(callback);
+                               void *context);
 
-  struct listener *listener = g_slice_new(struct listener);
+/**
+ * Stops listening for QMI messages matching certain criteria.
+ *
+ * @qmidev: the QMI device
+ * @listener: the listener returned by qmidev_listen
+ */
+void qmidev_cancel_listen(struct qmidev *qmidev, struct listener *listener);
 
-  listener->criteria = *criteria;
-  listener->callback = callback;
-  listener->context = context;
 
-  qmidev->listeners = g_list_prepend(qmidev->listeners, listener);
-
-  return listener;
-}
-
-void qmidev_cancel_listen(struct qmidev *qmidev, struct listener *listener)
-{
-  assert(qmidev);
-  assert(listener);
-
-  qmidev->listeners = g_list_remove(qmidev->listeners, listener);
-}
-
-struct call_listener_context {
-  struct qmidev *qmidev;
-  struct qmimsg *message;
-  uint8_t service;
-  uint8_t client;
-  uint16_t transaction;
-  int calls;
-};
-
-static void call_listener(void *data, void *user_data)
-{
-  struct call_listener_context *context = user_data;
-  struct listener *listener = data;
-
-  if (listener->criteria.service != context->service)
-    return;
-
-  if ((listener->criteria.client != QMI_CID_BROADCAST) &&
-      (listener->criteria.client != context->client))
-    return;
-
-  if ((listener->criteria.transaction != 0) &&
-      (listener->criteria.transaction != context->transaction))
-    return;
-
-  int result = listener->callback(context->qmidev,
-                                  listener->context,
-                                  context->message);
-  if (result) {
-    /* The glib documentation does not note this, but it is in fact safe to
-       call g_list_remove while called back from g_list_foreach. */
-    qmidev_cancel_listen(context->qmidev, listener);
-  }
-
-  context->calls++;
-}
-
-static int call_listeners(struct qmidev *qmidev, struct qmimsg *message)
-{
-  struct call_listener_context context;
-  context.qmidev = qmidev;
-  context.message = message;
-  qmimsg_get_header(message,
-                    &context.service, &context.client, &context.transaction);
-  context.calls = 0;
-  g_list_foreach(qmidev->listeners, &call_listener, &context);
-  return context.calls == 0;
-}
diff --git a/src/qmidev.h b/src/qmidev.h
index 16becde..74e3656 100644
--- a/src/qmidev.h
+++ b/src/qmidev.h
@@ -22,14 +22,186 @@
 struct qmimsg;
 struct callback;
 
-struct listener;
+struct client;
 
-struct listen_criteria {
-  uint8_t service;
-  uint8_t client;
-  uint16_t transaction;
+typedef void (*client_create_fn)(struct qmidev *qmidev,
+                                 struct client *client);
+
+typedef void (*client_destroy_fn)(struct qmidev *qmidev,
+                                  struct client *client);
+
+typedef int (*request_fn)(void *context,
+                          struct qmimsg *message);
+
+typedef void (*response_fn)(struct qmidev *qmidev,
+                            void *context,
+                            int status,
+                            struct qmimsg *message);
+
+typedef void (*event_fn)(struct qmidev *qmidev,
+                         void *user_callback,
+                         void *user_context,
+                         uint16_t length,
+                         void *value);
+
+struct event {
+  uint8_t type;
+  struct listener *listener;
+  event_fn callback;
+  void *user_callback;
+  void *user_context;
 };
 
+struct service {
+  uint8_t service_id;
+  uint16_t event_msg_id;
+
+  client_create_fn create_fn;
+  client_destroy_fn destroy_fn;
+};
+
+#define INIT_SERVICE(id, event_msg_id) { (id), (event_msg_id), NULL, NULL }
+
+void *client_priv(struct client *client);
+
+void client_set_priv(struct client *client, void *priv);
+
+/**
+ * Fills in a struct event.
+ *
+ * @event: the struct event to fill in
+ * @type: the type of the TLV element representing the event
+ * @callback: the callback used to call the user's callback when the event
+ *            happens
+ */
+void event_init(struct event *event, uint8_t type, event_fn callback);
+
+/** Callback type for qmidev_get_client */
+typedef void (*qmidev_client_fn)(struct qmidev *qmidev,
+                                 void *context,
+                                 int status,
+                                 struct client *client);
+
+/**
+ * Allocates a client for a particular service on the card.
+ */
+int qmidev_get_client(struct qmidev *qmidev,
+                      struct service *service,
+                      qmidev_client_fn callback,
+                      void *context);
+
+
+/**
+ * Releases an allocated client.
+ */
+int qmidev_release_client(struct qmidev *qmidev,
+                          struct client *client,
+                          qmidev_response_fn callback,
+                          void *context);
+
+/**
+ * Returns the default client for a given service.
+ */
+struct client *qmidev_default_client(struct qmidev *qmidev,
+                                     struct service *service);
+
+/*
+ * General request structure:
+ *
+ * Most requests will have four declarations:
+ *
+ * 1. a context struct to be passed to the response handler.  This stores the
+ *    callback and context passed to the main request function, so the
+ *    response handler can call the callback after extracting any returned
+ *    data from the response message.
+ *
+ * 2. (optional) a make_request function.  If the request requires any TLVs to
+ *    be included in the request message, this function takes a QMI message
+ *    and a context pointer and adds the data to the message.  (This is a
+ *    separate function because it makes things neater for requests that don't
+ *    have any parameter -- a NULL make_request function simply isn't called.)
+ *
+ * 3. a handle_response function.  This is called when a response to the
+ *    request is received, and is responsible for getting any returned data
+ *    from the response message, calling the original callback, and freeing
+ *    the context.
+ *
+ * 4. the main request function.  This allocates a context and calls
+ *    qmidev_request with pointers to the request and response functions and
+ *    associated contexts.
+ */
+
+/**
+ * Makes a request on a particular client (as opposed to the default one) of a
+ * service.
+ *
+ * @qmidev: the QMI device
+ * @client: the client on which to make the request
+ * @msg_id: the message ID of the request and response messages
+ * @make_request_fn: a function to be called to fill in the request message,
+ *                   if it requires a payload (otherwise NULL).  Will be
+ *                   called before qmidev_client_request returns.
+ * @request_context: the context for make_request_fn
+ * @handle_response_fn: the function to call when the response is received
+ * @response_context: the context for handle_response_fn
+ */
+int qmidev_client_request(struct qmidev *qmidev,
+                          struct client *client,
+                          uint16_t msg_id,
+                          request_fn make_request_fn,
+                          void *request_context,
+                          response_fn handle_response_fn,
+                          void *response_context);
+
+/**
+ * Makes a request on the default client of a service.
+ *
+ * Arguments are otherwise the same as qmidev_client_request.
+ */
+int qmidev_request(struct qmidev *qmidev,
+                   struct service *service,
+                   uint16_t msg_id,
+                   request_fn make_request_fn,
+                   void *request_context,
+                   response_fn handle_response_fn,
+                   void *response_context);
+
+/**
+ * Makes a void request on the default client of a service.
+ *
+ * Immediately calls the response function without doing anything with the
+ * response message besides getting the result code.
+ *
+ * Arguments are otherwise the same as qmidev_request.
+ */
+int qmidev_void_request(struct qmidev *qmidev,
+                        struct service *service,
+                        uint16_t msg_id,
+                        request_fn make_request_fn,
+                        void *request_context,
+                        qmidev_response_fn handle_response_fn,
+                        void *response_context);
+
+/**
+ * Sets or clears a callback.
+ *
+ * @qmidev: the QMI device
+ * @client: the client on which to set/clear the callback
+ * @event: the event structure representing the event whose callback we are
+ *         setting or clearing
+ * @new_callback: the new callback to set, or NULL if clearing
+ * @new_context: the context for new_callback
+ * @caller_callback: the callback to call once new_callback is set
+ * @caller_context: the context for caller_callback
+ */
+int qmidev_set_callback(struct qmidev *qmidev,
+                        struct client *client,
+                        struct event *event,
+                        void *new_callback,
+                        void *new_context,
+                        qmidev_response_fn caller_callback,
+                        void *caller_context);
+
 /* TODO: These should be replaced by qmidev_poll. */
 
 /**
@@ -62,89 +234,11 @@
                                   struct qmimsg *resp_msg);
 
 /**
- * Creates a new QMI message for a request.
- *
- * The service and message ID fields of the message will be filled from the
- * respective parameters.  The client ID is set to the client ID allocated
- * by the qmidev for this service, and the transaction ID is assigned
- * sequentially within that client.
- *
- * @service: The service to which the request will be sent
- * @req_msg_id: The message ID of the request
- */
-int qmidev_make_request(struct qmidev *qmidev,
-                        uint8_t service_id,
-                        uint16_t req_msg_id,
-                        struct qmimsg **message_out);
-
-/**
- * Sends a request message created with qmidev_make_request, and listens for
- * a reply.
- *
- * @req_msg: The message returned from qmidev_make_request
- * @resp_msg_id: The expected message ID of the response
- * @callback: The callback to call when a response is received
- * @context: The context with which to call the callback
- */
-int qmidev_send_request(struct qmidev *qmidev,
-                        struct qmimsg *req_msg,
-                        uint16_t resp_msg_id,
-                        qmidev_request_fn callback,
-                        void *context);
-
-int qmidev_send_void_request(struct qmidev *qmidev,
-                             struct qmimsg *req_msg,
-                             uint16_t resp_msg_id,
-                             qmidev_response_fn caller_callback,
-                             void *caller_context);
-
-/**
  * Sends a QMI message.
  * Returns zero on success, errno on failure.
  */
 int qmidev_send(struct qmidev *qmidev, struct qmimsg *msg);
 
-/**
- * Callback type for qmidev_listen.
- *
- * Will be called when a message matching the listen criteria is received.
- *
- * @qmidev: the QMI device on which a message was received
- * @context: the context passed to qmidev_listen
- * @message: the message received
- *
- * The callback should return zero to continue listening for such messages,
- * or non-zero to remove the listener.
- */
-typedef int (*qmidev_listen_callback_fn)(struct qmidev *qmidev,
-                                         void *context,
-                                         struct qmimsg *message);
-
-/**
- * Starts listening for QMI messages matching certain criteria.
- *
- * @qmidev: the QMI device
- * @criteria: the listen criteria (see struct listen_criteria)
- * @callback: the callback to call when a matching message is received
- * @context: the context with which to call the callback
- *
- * Returns a pointer to a struct listener that can be passed to
- * qmidev_cancel_listen to stop listening.
- */
-
-struct listener *qmidev_listen(struct qmidev *qmidev,
-                               struct listen_criteria *criteria,
-                               qmidev_listen_callback_fn callback,
-                               void *context);
-
-/**
- * Stops listening for QMI messages matching certain criteria.
- *
- * @qmidev: the QMI device
- * @listener: the listener returned by qmidev_listen
- */
-void qmidev_cancel_listen(struct qmidev *qmidev, struct listener *listener);
-
 typedef void (*callback_fn)(struct qmidev *qmidev, void *context);
 
 /**
diff --git a/src/qmidms.c b/src/qmidms.c
index af4510a..6c68162 100644
--- a/src/qmidms.c
+++ b/src/qmidms.c
@@ -25,50 +25,83 @@
 #include "qmidev.h"
 #include "qmimsg.h"
 
-enum {
-  QMIDMS_MSG_GET_IDS = 0x0025,
-  QMIDMS_MSG_GET_OPERATING_MODE = 0x002D,
-  QMIDMS_MSG_SET_OPERATING_MODE = 0x002E
+struct qmidms_priv {
+  struct event operating_mode;
 };
 
-enum {
+static void operating_mode_changed(struct qmidev *qmidev,
+                                   void *user_callback, void *user_context,
+                                   uint16_t length, void *value)
+{
+  qmidev_power_callback_fn callback = user_callback;
+  qmidev_power_state state;
+  int status;
+
+  if (length == 1) {
+    state = *(uint8_t *)value;
+    status = 0;
+  } else {
+    state = 0;
+    status = QMI_ERR_MESSAGE_INVALID;
+  }
+
+  callback(qmidev, user_context, status, state);
+}
+
+static void client_create(struct qmidev *qmidev, struct client *client)
+{
+  qmidev = qmidev;
+  struct qmidms_priv *priv = g_slice_new(struct qmidms_priv);
+  event_init(&priv->operating_mode,
+             QMIDMS_EVENT_OPERATING_MODE,
+             &operating_mode_changed);
+  client_set_priv(client, priv);
+}
+
+static void client_destroy(struct qmidev *qmidev, struct client *client)
+{
+  qmidev = qmidev;
+  struct qmidms_priv *priv = client_priv(client);
+  g_slice_free(struct qmidms_priv, priv);
+}
+
+struct service qmidms_service = {
+  .service_id   = QMI_SVC_DMS,
+  .event_msg_id = QMIDMS_MSG_EVENT,
+  .create_fn    = &client_create,
+  .destroy_fn   = &client_destroy
+};enum {
   QMIDMS_TLV_GET_IDS_ESN = 0x10,
   QMIDMS_TLV_GET_IDS_IMEI,
   QMIDMS_TLV_GET_IDS_MEID
 };
 
 struct get_ids_context {
-  qmidev_get_ids_response_fn caller_callback;
-  void *caller_context;
+  qmidev_get_ids_response_fn callback;
+  void *context;
 };
 
-static void got_ids(struct qmidev *qmidev,
-                    void *data,
-                    int status,
-                    struct qmimsg *message)
+static void get_ids_response(struct qmidev *qmidev,
+                             void *data,
+                             int result,
+                             struct qmimsg *message)
 {
+  struct get_ids_context *context = data;
+  char *esn = NULL, *imei = NULL, *meid = NULL;
+
   assert(qmidev);
   assert(data);
 
-  struct get_ids_context *context = data;
-  int result;
-
-  char *esn = NULL, *imei = NULL, *meid = NULL;
-
-  if (status) {
-    result = status;
+  if (result)
     goto out;
-  }
 
   qmi_tlv_strdup(message, QMIDMS_TLV_GET_IDS_ESN,  NULL, &esn);
   qmi_tlv_strdup(message, QMIDMS_TLV_GET_IDS_IMEI, NULL, &imei);
   qmi_tlv_strdup(message, QMIDMS_TLV_GET_IDS_MEID, NULL, &meid);
 
-  result = 0;
-
 out:
-  context->caller_callback(qmidev, context->caller_context, result,
-                           esn, imei, meid);
+  context->callback(qmidev, context->context, result,
+                    esn, imei, meid);
   g_free(esn);
   g_free(imei);
   g_free(meid);
@@ -79,28 +112,19 @@
                    qmidev_get_ids_response_fn caller_callback,
                    void *caller_context)
 {
+  struct get_ids_context *context;
   int result;
 
-  struct get_ids_context *context = g_slice_new(struct get_ids_context);
-  context->caller_callback = caller_callback;
-  context->caller_context  = caller_context;
+  context = g_slice_new(struct get_ids_context);
+  context->callback = caller_callback;
+  context->context  = caller_context;
 
-  struct qmimsg *message;
-  result = qmidev_make_request(qmidev, QMI_SVC_DMS, QMIDMS_MSG_GET_IDS,
-                               &message);
+  result = qmidev_request(qmidev, &qmidms_service,
+                          QMIDMS_MSG_GET_IDS,
+                          NULL, NULL,
+                          &get_ids_response, context);
   if (result)
-    goto fail;
-
-  result = qmidev_send_request(qmidev, message,
-                               QMIDMS_MSG_GET_IDS,
-                               &got_ids, context);
-  if (result)
-    goto fail;
-
-  return 0;
-
-fail:
-  g_slice_free(struct get_ids_context, context);
+    g_slice_free(struct get_ids_context, context);
   return result;
 }
 
@@ -109,30 +133,24 @@
   void *caller_context;
 };
 
-static void got_power(struct qmidev *qmidev,
-                      void *data,
-                      int status,
-                      struct qmimsg *message)
+static void get_power_response(struct qmidev *qmidev,
+                               void *data,
+                               int result,
+                               struct qmimsg *message)
 {
+  struct get_power_context *context = data;
   uint8_t mode;
 
   assert(qmidev);
   assert(data);
 
-  struct get_power_context *context = data;
-  int result;
-
-  if (status) {
-    result = status;
+  if (result)
     goto out;
-  }
 
   result = qmimsg_tlv_get(message, QMI_TLV_VALUE, sizeof(mode), &mode);
   if (result)
     goto out;
 
-  result = 0;
-
 out:
   context->caller_callback(qmidev, context->caller_context, result, mode);
   g_slice_free(struct get_power_context, context);
@@ -142,53 +160,53 @@
                      qmidev_power_state_response_fn caller_callback,
                      void *caller_context)
 {
+  struct get_power_context *context;
   int result;
 
-  struct get_power_context *context = g_slice_new(struct get_power_context);
+  context = g_slice_new(struct get_power_context);
   context->caller_callback = caller_callback;
   context->caller_context  = caller_context;
 
-  struct qmimsg *message;
-  result = qmidev_make_request(qmidev,
-                               QMI_SVC_DMS,
-                               QMIDMS_MSG_GET_OPERATING_MODE,
-                               &message);
+  result = qmidev_request(qmidev, &qmidms_service,
+                          QMIDMS_MSG_GET_OPERATING_MODE,
+                          NULL, NULL,
+                          &get_power_response, context);
   if (result)
-    goto fail;
-
-  result = qmidev_send_request(qmidev, message,
-                               QMIDMS_MSG_GET_OPERATING_MODE,
-                               &got_power, context);
-  if (result)
-    goto fail;
-
-  return 0;
-
-fail:
-  g_slice_free(struct get_power_context, context);
+    g_slice_free(struct get_power_context, context);
   return result;
 }
 
+static int set_power_request(void *data,
+                             struct qmimsg *message)
+{
+  uint8_t *mode = data;
+  return qmimsg_tlv_add(message, QMI_TLV_VALUE, sizeof(*mode), mode);
+}
+
 int qmidev_set_power(struct qmidev *qmidev,
                      qmidev_power_state state,
-                     qmidev_response_fn caller_callback,
-                     void *caller_context)
+                     qmidev_response_fn callback,
+                     void *context)
 {
   uint8_t mode = state;
-  int result;
 
-  struct qmimsg *message;
-  result = qmidev_make_request(qmidev,
-                               QMI_SVC_DMS,
-                               QMIDMS_MSG_SET_OPERATING_MODE,
-                               &message);
-  if (result)
-    return result;
-  result = qmimsg_tlv_add(message, QMI_TLV_VALUE, sizeof(mode), &mode);
-  if (result)
-    return QMI_ERR_INTERNAL;
-  return qmidev_send_void_request(qmidev, message,
-                                  QMIDMS_MSG_SET_OPERATING_MODE,
-                                  caller_callback,
-                                  caller_context);
+  return qmidev_void_request(qmidev, &qmidms_service,
+                             QMIDMS_MSG_SET_OPERATING_MODE,
+                             &set_power_request, &mode,
+                             callback, context);
+}
+
+
+
+int qmidev_set_power_callback(struct qmidev *qmidev,
+                              qmidev_power_callback_fn callback,
+                              void *context,
+                              qmidev_response_fn set_callback,
+                              void *set_context)
+{
+  struct client *client = qmidev_default_client(qmidev, &qmidms_service);
+  struct qmidms_priv *priv = client_priv(client);
+  return qmidev_set_callback(qmidev, client, &priv->operating_mode,
+                             callback, context,
+                             set_callback, set_context);
 }
diff --git a/src/qmidms.h b/src/qmidms.h
new file mode 100644
index 0000000..a1f76de
--- /dev/null
+++ b/src/qmidms.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/*
+ * qmidms: QMI calls on DMS (Device Management Service).
+ *
+ * (See qmictl.c for sources.)
+ */
+
+#ifndef LIBQMI_QMIDMS_H
+#define LIBQMI_QMIDMS_H
+
+#include <stdint.h>
+
+#include "qmidev.h"
+
+struct service qmidms_service;
+
+#endif /* LIBQMI_QMIDMS_H */
diff --git a/src/qmimsg.c b/src/qmimsg.c
index e668705..57a8ce4 100644
--- a/src/qmimsg.c
+++ b/src/qmimsg.c
@@ -331,17 +331,25 @@
   return 0;
 }
 
-void qmimsg_get_header(struct qmimsg *m,
-                       uint8_t *s_out, uint8_t *c_out, uint16_t *t_out)
+void qmimsg_get_header(struct qmimsg *message,
+                       uint8_t *service_out,
+                       uint8_t *client_out,
+                       uint8_t *qmi_flags_out,
+                       uint16_t *transaction_out,
+                       uint16_t *message_out)
 {
-  assert(m);
+  assert(message);
 
-  if (s_out)
-    *s_out = qmux_service(m);
-  if (c_out)
-    *c_out = qmux_client(m);
-  if (t_out)
-    *t_out = qmi_transaction(m);
+  if (service_out)
+    *service_out = qmux_service(message);
+  if (client_out)
+    *client_out = qmux_client(message);
+  if (qmi_flags_out)
+    *qmi_flags_out = qmi_flags(message);
+  if (transaction_out)
+    *transaction_out = qmi_transaction(message);
+  if (message_out)
+    *message_out = qmi_message(message);
 }
 
 void qmimsg_print(struct qmimsg *m)
@@ -420,6 +428,18 @@
   return qmimsg_tlv_get_internal(message, type, length, value, 0);
 }
 
+void qmimsg_tlv_foreach(struct qmimsg *message,
+                        qmimsg_tlv_foreach_fn func, void *context)
+{
+  assert(message);
+  assert(func);
+
+  struct tlv *tlv;
+  for (tlv = qmi_tlv_first(message); tlv; tlv = qmi_tlv_next(message, tlv)) {
+    func(context, tlv->type, tlv->length, tlv->value);
+  }
+}
+
 
 int qmimsg_tlv_add(struct qmimsg *m,
                    uint8_t type, uint16_t length, const void *value)
diff --git a/src/qmimsg.h b/src/qmimsg.h
index a1d850f..105cf2d 100644
--- a/src/qmimsg.h
+++ b/src/qmimsg.h
@@ -61,8 +61,11 @@
  * Retrieves a tasteful subset of the header fields in a QMI message.
  */
 void qmimsg_get_header(struct qmimsg *message,
-                       uint8_t *service_out, uint8_t *client_out,
-                       uint16_t *transaction_out);
+                       uint8_t *service_out,
+                       uint8_t *client_out,
+                       uint8_t *qmi_flags_out,
+                       uint16_t *transaction_out,
+                       uint16_t *message_out);
 
 /**
  * Finds a TLV element with the given type in the payload of the given QMI
@@ -89,6 +92,14 @@
 int qmimsg_tlv_get_varlen(struct qmimsg *message,
                           uint8_t type, uint16_t *length, void *value);
 
+typedef void (*qmimsg_tlv_foreach_fn)(void *context,
+                                      uint8_t type,
+                                      uint16_t length,
+                                      void *value);
+
+void qmimsg_tlv_foreach(struct qmimsg *message,
+                        qmimsg_tlv_foreach_fn func, void *context);
+
 /**
  * Appends a TLV element with the given type, length, and value to the payload
  * of the given QMI message.