From 262c098794914079125490d088c7eb2ceb515e41 Mon Sep 17 00:00:00 2001
From: Luca Weiss <luca.weiss@fairphone.com>
Date: Fri, 23 Feb 2024 10:12:35 +0100
Subject: [PATCH 3/5] uim: Add Send APDU command

Allow an APDU to be sent to the SIM card, using the logical channel
opened with Open Logical Channel.

QMI format is based on ChromiumOS hermes definition:
https://chromium.googlesource.com/chromiumos/platform2/+/eab2f7edb247fc11b3f0ec4b886aa7a5b12f24f6/hermes/uim_cmd.qmi#38
---
 data/qmi-service-uim.json                     |  37 ++++++
 .../libqmi-glib/libqmi-glib-docs.xml          |   1 +
 src/qmicli/qmicli-uim.c                       | 109 ++++++++++++++++++
 3 files changed, 147 insertions(+)

diff --git a/data/qmi-service-uim.json b/data/qmi-service-uim.json
index 2a0d21e6..9a562ead 100644
--- a/data/qmi-service-uim.json
+++ b/data/qmi-service-uim.json
@@ -920,6 +920,43 @@
                                                                                                          "format"        : "guint8" } ] } } ] },
                      "prerequisites"      : [ { "common-ref" : "Success" } ] } ] },
 
+  // *********************************************************************************
+  {  "name"    : "Send APDU",
+     "type"    : "Message",
+     "service" : "UIM",
+     "id"      : "0x003B",
+     "since"   : "1.36",
+     "input"   : [ { "name"          : "Slot",
+                     "id"            : "0x01",
+                     "type"          : "TLV",
+                     "since"         : "1.36",
+                     "format"        : "guint8" },
+                   { "name"               : "APDU",
+                     "id"                 : "0x02",
+                     "type"               : "TLV",
+                     "since"              : "1.36",
+                     "format"             : "array",
+                     "size-prefix-format" : "guint16",
+                     "array-element"      : { "format" : "guint8" } },
+                   { "name"          : "Channel ID",
+                     "id"            : "0x10",
+                     "type"          : "TLV",
+                     "since"         : "1.36",
+                     "format"        : "guint8" },
+                   { "name"          : "Procedure bytes",
+                     "id"            : "0x11",
+                     "type"          : "TLV",
+                     "since"         : "1.36",
+                     "format"        : "guint8" } ],
+     "output"  : [ { "common-ref" : "Operation Result" },
+                   { "name"               : "APDU Response",
+                     "id"                 : "0x10",
+                     "type"               : "TLV",
+                     "since"              : "1.36",
+                     "format"             : "array",
+                     "size-prefix-format" : "guint16",
+                     "array-element"      : { "format" : "guint8" } } ] },
+
   // *********************************************************************************
   {  "name"    : "Open Logical Channel",
      "type"    : "Message",
diff --git a/docs/reference/libqmi-glib/libqmi-glib-docs.xml b/docs/reference/libqmi-glib/libqmi-glib-docs.xml
index f6cd58b2..72c76fa5 100644
--- a/docs/reference/libqmi-glib/libqmi-glib-docs.xml
+++ b/docs/reference/libqmi-glib/libqmi-glib-docs.xml
@@ -372,6 +372,7 @@
       <xi:include href="xml/qmi-message-uim-refresh-complete.xml"/>
       <xi:include href="xml/qmi-message-uim-remote-unlock.xml"/>
       <xi:include href="xml/qmi-message-uim-open-logical-channel.xml"/>
+      <xi:include href="xml/qmi-message-uim-send-apdu.xml"/>
     </section>
   </chapter>
 
diff --git a/src/qmicli/qmicli-uim.c b/src/qmicli/qmicli-uim.c
index 402d74fe..cd91ec99 100644
--- a/src/qmicli/qmicli-uim.c
+++ b/src/qmicli/qmicli-uim.c
@@ -30,6 +30,7 @@
 #include <gio/gio.h>
 
 #include <libqmi-glib.h>
+#include <qmi-common.h>
 
 #include "qmicli.h"
 #include "qmicli-helpers.h"
@@ -66,6 +67,7 @@ static gchar *switch_slot_str;
 static gchar *depersonalization_str;
 static gchar *remote_unlock_str;
 static gchar *open_logical_channel_str;
+static gchar *send_apdu_str;
 static gchar **monitor_refresh_file_array;
 static gboolean get_card_status_flag;
 static gboolean get_supported_messages_flag;
@@ -211,6 +213,12 @@ static GOptionEntry entries[] = {
       "Open logical channel",
       "[(slot number),(aid)]"
     },
+#endif
+#if defined HAVE_QMI_MESSAGE_UIM_SEND_APDU
+    { "uim-send-apdu", 0, 0, G_OPTION_ARG_STRING, &send_apdu_str,
+      "Send APDU",
+      "[(slot number),(channel ID),(apdu)]"
+    },
 #endif
     { "uim-noop", 0, 0, G_OPTION_ARG_NONE, &noop_flag,
       "Just allocate or release a UIM client. Use with `--client-no-release-cid' and/or `--client-cid'",
@@ -258,6 +266,7 @@ qmicli_uim_options_enabled (void)
                  !!depersonalization_str +
                  !!remote_unlock_str +
                  !!open_logical_channel_str +
+                 !!send_apdu_str +
                  get_card_status_flag +
                  get_supported_messages_flag +
                  get_slot_status_flag +
@@ -2736,6 +2745,84 @@ open_logical_channel_ready (QmiClientUim *client,
 
 #endif /* HAVE_QMI_MESSAGE_UIM_OPEN_LOGICAL_CHANNEL */
 
+#if defined HAVE_QMI_MESSAGE_UIM_SEND_APDU
+
+static QmiMessageUimSendApduInput *
+send_apdu_input_create (const gchar *str)
+{
+    QmiMessageUimSendApduInput *input;
+    g_auto(GStrv)               split = NULL;
+    guint                       slot;
+    guint                       channel_id;
+    g_autoptr(GArray)           apdu_data = NULL;
+
+    /* Prepare inputs.
+     * Format of the string is:
+     *    "[(slot number),(channel ID),(apdu)]"
+     */
+    split = g_strsplit (str, ",", -1);
+
+    if (!split[0] || !qmicli_read_uint_from_string (split[0], &slot) || (slot > G_MAXUINT8)) {
+        g_printerr ("error: invalid slot number\n");
+        return NULL;
+    }
+
+    if (!split[1] || !qmicli_read_uint_from_string (split[1], &channel_id) || (channel_id > G_MAXUINT8)) {
+        g_printerr ("error: invalid channel ID\n");
+        return NULL;
+    }
+
+    if (!split[2] || !qmicli_read_raw_data_from_string (split[2], &apdu_data)) {
+        g_printerr ("error: invalid APDU data\n");
+        return NULL;
+    }
+
+    input = qmi_message_uim_send_apdu_input_new ();
+    qmi_message_uim_send_apdu_input_set_slot (input, slot, NULL);
+    qmi_message_uim_send_apdu_input_set_channel_id (input, channel_id, NULL);
+    qmi_message_uim_send_apdu_input_set_apdu (input, apdu_data, NULL);
+
+    return input;
+}
+
+static void
+send_apdu_ready (QmiClientUim *client,
+                 GAsyncResult *res)
+{
+    g_autoptr(QmiMessageUimSendApduOutput) output = NULL;
+    g_autoptr(GError)                      error = NULL;
+    GArray                                *apdu_res = NULL;
+    gchar                                 *apdu_res_hex;
+
+    output = qmi_client_uim_send_apdu_finish (client, res, &error);
+    if (!output) {
+        g_printerr ("error: operation failed: %s\n", error->message);
+        operation_shutdown (FALSE);
+        return;
+    }
+
+    if (!qmi_message_uim_send_apdu_output_get_result (output, &error)) {
+        g_printerr ("error: send apdu operation failed: %s\n", error->message);
+        operation_shutdown (FALSE);
+        return;
+    }
+
+    if (!qmi_message_uim_send_apdu_output_get_apdu_response (output, &apdu_res, &error)) {
+        g_printerr ("error: get apdu response operation failed: %s\n", error->message);
+        operation_shutdown (FALSE);
+        return;
+    }
+
+    g_print ("Send APDU operation successfully completed:");
+    apdu_res_hex = qmi_common_str_hex (apdu_res->data, apdu_res->len, ':');
+    g_print (" %s\n", apdu_res_hex);
+    g_free (apdu_res_hex);
+
+    operation_shutdown (TRUE);
+}
+
+#endif /* HAVE_QMI_MESSAGE_UIM_SEND_APDU */
+
 void
 qmicli_uim_run (QmiDevice *device,
                 QmiClientUim *client,
@@ -3157,6 +3244,28 @@ qmicli_uim_run (QmiDevice *device,
     }
 #endif
 
+#if defined HAVE_QMI_MESSAGE_UIM_SEND_APDU
+    /* Request to send APDU? */
+    if (send_apdu_str) {
+        g_autoptr(QmiMessageUimSendApduInput) input = NULL;
+
+        g_debug ("Asynchronously sending APDU...");
+        input = send_apdu_input_create (send_apdu_str);
+        if (!input) {
+            operation_shutdown (FALSE);
+            return;
+        }
+
+        qmi_client_uim_send_apdu (ctx->client,
+                                  input,
+                                  10,
+                                  ctx->cancellable,
+                                  (GAsyncReadyCallback)send_apdu_ready,
+                                  NULL);
+        return;
+    }
+#endif
+
     /* Just client allocate/release? */
     if (noop_flag) {
         g_idle_add (noop_cb, NULL);
-- 
2.45.1

