From 2ac09dbc9e433290a26c6ace653d28d0aff223d0 Mon Sep 17 00:00:00 2001
From: Luca Weiss <luca.weiss@fairphone.com>
Date: Fri, 23 Feb 2024 08:46:55 +0100
Subject: [PATCH 2/5] uim: Add Open Logical Channel command

Allows the client to open a logical channel to the SIM card which can
then be used to e.g. send APDU requests to the SIM card.

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

diff --git a/data/qmi-service-uim.json b/data/qmi-service-uim.json
index 35edd8e1..2a0d21e6 100644
--- a/data/qmi-service-uim.json
+++ b/data/qmi-service-uim.json
@@ -920,6 +920,49 @@
                                                                                                          "format"        : "guint8" } ] } } ] },
                      "prerequisites"      : [ { "common-ref" : "Success" } ] } ] },
 
+  // *********************************************************************************
+  {  "name"    : "Open Logical Channel",
+     "type"    : "Message",
+     "service" : "UIM",
+     "id"      : "0x0042",
+     "since"   : "1.36",
+     "input"   : [ { "name"   : "Slot",
+                     "id"     : "0x01",
+                     "type"   : "TLV",
+                     "since"  : "1.36",
+                     "format" : "guint8" },
+                   { "name"               : "AID",
+                     "id"                 : "0x10",
+                     "type"               : "TLV",
+                     "since"              : "1.36",
+                     "format"             : "array",
+                     "size-prefix-format" : "guint8",
+                     "array-element"      : { "format" : "guint8" } } ],
+     "output"  : [ { "common-ref" : "Operation Result" },
+                   { "name"          : "Channel ID",
+                     "id"            : "0x10",
+                     "type"          : "TLV",
+                     "since"         : "1.36",
+                     "format"        : "guint8",
+                     "prerequisites" : [ { "common-ref" : "Success" } ] },
+                   { "name"          : "Card result",
+                     "id"            : "0x11",
+                     "type"          : "TLV",
+                     "since"         : "1.36",
+                     "format"        : "sequence",
+                     "contents"      : [ { "name"   : "SW1",
+                                           "format" : "guint8" },
+                                         { "name"   : "SW2",
+                                           "format" : "guint8" } ] },
+                   { "name"               : "Select Response",
+                     "id"                 : "0x12",
+                     "type"               : "TLV",
+                     "since"              : "1.36",
+                     "format"             : "array",
+                     "size-prefix-format" : "guint8",
+                     "array-element"      : { "format" : "guint8" },
+                     "prerequisites"      : [ { "common-ref" : "Success" } ] } ] },
+
   // *********************************************************************************
   {  "name"    : "Refresh Register All",
      "type"    : "Message",
diff --git a/docs/reference/libqmi-glib/libqmi-glib-docs.xml b/docs/reference/libqmi-glib/libqmi-glib-docs.xml
index 1374c365..f6cd58b2 100644
--- a/docs/reference/libqmi-glib/libqmi-glib-docs.xml
+++ b/docs/reference/libqmi-glib/libqmi-glib-docs.xml
@@ -371,6 +371,7 @@
       <xi:include href="xml/qmi-message-uim-refresh-register-all.xml"/>
       <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"/>
     </section>
   </chapter>
 
diff --git a/src/qmicli/qmicli-uim.c b/src/qmicli/qmicli-uim.c
index c3d0abad..402d74fe 100644
--- a/src/qmicli/qmicli-uim.c
+++ b/src/qmicli/qmicli-uim.c
@@ -65,6 +65,7 @@ static gchar *change_provisioning_session_str;
 static gchar *switch_slot_str;
 static gchar *depersonalization_str;
 static gchar *remote_unlock_str;
+static gchar *open_logical_channel_str;
 static gchar **monitor_refresh_file_array;
 static gboolean get_card_status_flag;
 static gboolean get_supported_messages_flag;
@@ -204,6 +205,12 @@ static GOptionEntry entries[] = {
       "Updates the SimLock configuration data",
       "[XX:XX:...]"
     },
+#endif
+#if defined HAVE_QMI_MESSAGE_UIM_OPEN_LOGICAL_CHANNEL
+    { "uim-open-logical-channel", 0, 0, G_OPTION_ARG_STRING, &open_logical_channel_str,
+      "Open logical channel",
+      "[(slot number),(aid)]"
+    },
 #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'",
@@ -250,6 +257,7 @@ qmicli_uim_options_enabled (void)
                  !!monitor_refresh_file_array +
                  !!depersonalization_str +
                  !!remote_unlock_str +
+                 !!open_logical_channel_str +
                  get_card_status_flag +
                  get_supported_messages_flag +
                  get_slot_status_flag +
@@ -2658,6 +2666,76 @@ remote_unlock_ready (QmiClientUim *client,
 
 #endif /* HAVE_QMI_MESSAGE_UIM_REMOTE_UNLOCK */
 
+#if defined HAVE_QMI_MESSAGE_UIM_OPEN_LOGICAL_CHANNEL
+
+static QmiMessageUimOpenLogicalChannelInput *
+open_logical_channel_input_create (const gchar *str)
+{
+    QmiMessageUimOpenLogicalChannelInput *input;
+    g_auto(GStrv)                         split = NULL;
+    guint                                 slot;
+    g_autoptr(GArray)                     aid_data = NULL;
+
+    /* Prepare inputs.
+     * Format of the string is:
+     *    "[(slot number),(aid)]"
+     */
+    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;
+    }
+
+    /* AID is optional */
+    if (split[1]) {
+        if (!qmicli_read_raw_data_from_string (split[1], &aid_data)) {
+            g_printerr ("error: invalid AID data\n");
+            return NULL;
+        }
+    }
+
+    input = qmi_message_uim_open_logical_channel_input_new ();
+    qmi_message_uim_open_logical_channel_input_set_slot (input, slot, NULL);
+    if (aid_data)
+        qmi_message_uim_open_logical_channel_input_set_aid (input, aid_data, NULL);
+
+    return input;
+}
+
+static void
+open_logical_channel_ready (QmiClientUim *client,
+                            GAsyncResult *res)
+{
+    g_autoptr(QmiMessageUimOpenLogicalChannelOutput) output = NULL;
+    g_autoptr(GError)                                error = NULL;
+    guint8                                           channel_id;
+
+    output = qmi_client_uim_open_logical_channel_finish (client, res, &error);
+    if (!output) {
+        g_printerr ("error: operation failed: %s\n", error->message);
+        operation_shutdown (FALSE);
+        return;
+    }
+
+    if (!qmi_message_uim_open_logical_channel_output_get_result (output, &error)) {
+        g_printerr ("error: open logical channel operation failed: %s\n", error->message);
+        operation_shutdown (FALSE);
+        return;
+    }
+
+    if (!qmi_message_uim_open_logical_channel_output_get_channel_id (output, &channel_id, &error)) {
+        g_printerr ("error: get channel id operation failed: %s\n", error->message);
+        operation_shutdown (FALSE);
+        return;
+    }
+
+    g_print ("Open Logical Channel operation successfully completed: %d\n", channel_id);
+    operation_shutdown (TRUE);
+}
+
+#endif /* HAVE_QMI_MESSAGE_UIM_OPEN_LOGICAL_CHANNEL */
+
 void
 qmicli_uim_run (QmiDevice *device,
                 QmiClientUim *client,
@@ -3057,6 +3135,28 @@ qmicli_uim_run (QmiDevice *device,
     }
 #endif
 
+#if defined HAVE_QMI_MESSAGE_UIM_OPEN_LOGICAL_CHANNEL
+    /* Request to open logical channel? */
+    if (open_logical_channel_str) {
+        g_autoptr(QmiMessageUimOpenLogicalChannelInput) input = NULL;
+
+        g_debug ("Asynchronously opening logical channel...");
+        input = open_logical_channel_input_create (open_logical_channel_str);
+        if (!input) {
+            operation_shutdown (FALSE);
+            return;
+        }
+
+        qmi_client_uim_open_logical_channel (ctx->client,
+                                             input,
+                                             10,
+                                             ctx->cancellable,
+                                             (GAsyncReadyCallback)open_logical_channel_ready,
+                                             NULL);
+        return;
+    }
+#endif
+
     /* Just client allocate/release? */
     if (noop_flag) {
         g_idle_add (noop_cb, NULL);
-- 
2.45.1

