mirror of
https://github.com/estkme-group/lpac
synced 2025-07-20 10:36:37 +02:00
When waiting for the sim to become available after potentially switching, we don't actually do much waiting. We're basically only delayed by the speed of the communication here. If the switch doesn't happen quickly enough, this may result in us deciding a sim didn't become available, but subsequent calls will produce usable results. So introduce a small 50 ms wait time, so we limit the total waiting time to about 1 second. This I think should be enough for the sim to actually become available. resolve #241 Signed-off-by: Frans Klaver <frans.klaver@vislink.com>
372 lines
11 KiB
C
372 lines
11 KiB
C
// SPDX-License-Identifier: MIT
|
|
/*
|
|
* Copyright (c) 2024, Frans Klaver <frans.klaver@vislink.com>
|
|
*/
|
|
#include "mbim.h"
|
|
|
|
#include <libmbim-glib.h>
|
|
#include <stdio.h>
|
|
#include <euicc/interface.h>
|
|
#include <euicc/euicc.h>
|
|
#include "mbim_helpers.h"
|
|
|
|
struct mbim_data {
|
|
const char *device_path;
|
|
int last_channel_id;
|
|
gboolean use_proxy;
|
|
guint32 uim_slot;
|
|
GMainContext *context;
|
|
MbimDevice *device;
|
|
};
|
|
|
|
static gboolean is_sim_available(struct mbim_data *mbim_priv)
|
|
{
|
|
MbimMessage *request = mbim_message_subscriber_ready_status_query_new(NULL);
|
|
g_autoptr(MbimMessage) response = mbim_device_command_sync(
|
|
mbim_priv->device, mbim_priv->context, request, NULL
|
|
);
|
|
if (!response)
|
|
return FALSE;
|
|
|
|
MbimSubscriberReadyState ready_state;
|
|
if (!mbim_message_subscriber_ready_status_response_parse(
|
|
response, &ready_state, NULL, NULL, NULL, NULL, NULL, NULL
|
|
)) {
|
|
return FALSE;
|
|
}
|
|
|
|
switch (ready_state) {
|
|
case MBIM_SUBSCRIBER_READY_STATE_NO_ESIM_PROFILE:
|
|
case MBIM_SUBSCRIBER_READY_STATE_INITIALIZED:
|
|
return TRUE;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static int select_sim_slot(struct mbim_data *mbim_priv)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
MbimMessage *current_slot_request =
|
|
mbim_message_ms_basic_connect_extensions_device_slot_mappings_query_new(NULL);
|
|
|
|
g_autoptr(MbimMessage) current_slot_response = mbim_device_command_sync(
|
|
mbim_priv->device, mbim_priv->context, current_slot_request, &error
|
|
);
|
|
if (!current_slot_response) {
|
|
fprintf(stderr, "error: device didn't respond: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
guint32 current_slot_count;
|
|
g_autoptr(MbimSlotArray) current_slots = NULL;
|
|
if (!mbim_message_ms_basic_connect_extensions_device_slot_mappings_response_parse(
|
|
current_slot_response, ¤t_slot_count, ¤t_slots, &error
|
|
)) {
|
|
fprintf(stderr, "error: sim select response could not be parsed: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
if (current_slot_count && current_slots[0]->slot == mbim_priv->uim_slot) {
|
|
return 0;
|
|
}
|
|
|
|
g_autoptr(GPtrArray) new_slot_array = g_ptr_array_new_with_free_func(g_free);
|
|
MbimSlot *new_slot = g_new(MbimSlot, 1);
|
|
new_slot->slot = mbim_priv->uim_slot;
|
|
g_ptr_array_add(new_slot_array, new_slot);
|
|
|
|
MbimMessage *update_slot_request = mbim_message_ms_basic_connect_extensions_device_slot_mappings_set_new(
|
|
new_slot_array->len, (const MbimSlot **)new_slot_array->pdata, &error
|
|
);
|
|
if (!update_slot_request) {
|
|
fprintf(stderr, "error: unable to select sim slot: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
g_autoptr(MbimMessage) update_slot_response = mbim_device_command_sync(
|
|
mbim_priv->device, mbim_priv->context, update_slot_request, &error
|
|
);
|
|
if (!update_slot_response) {
|
|
fprintf(stderr, "error: device didn't respond: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
guint32 slot_count;
|
|
g_autoptr(MbimSlotArray) updated_slots = NULL;
|
|
if (!mbim_message_ms_basic_connect_extensions_device_slot_mappings_response_parse(
|
|
update_slot_response, &slot_count, &updated_slots, &error
|
|
)) {
|
|
fprintf(stderr, "error: sim select response could not be parsed: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
int retries = 20;
|
|
while (retries--) {
|
|
if (is_sim_available(mbim_priv)) {
|
|
return 0;
|
|
}
|
|
struct timespec ts = {
|
|
.tv_sec = 0,
|
|
.tv_nsec = 50000000
|
|
};
|
|
nanosleep(&ts, NULL);
|
|
}
|
|
|
|
fprintf(stderr, "sim did not become available\n");
|
|
return -1;
|
|
}
|
|
|
|
static int apdu_interface_connect(struct euicc_ctx *ctx)
|
|
{
|
|
struct mbim_data *mbim_priv = ctx->apdu.interface->userdata;
|
|
g_autoptr(GError) error = NULL;
|
|
GFile *file;
|
|
|
|
file = g_file_new_for_path(mbim_priv->device_path);
|
|
|
|
mbim_priv->context = g_main_context_new();
|
|
|
|
mbim_priv->device = mbim_device_new_from_path(file, mbim_priv->context, &error);
|
|
if (!mbim_priv->device) {
|
|
fprintf(stderr, "error: create mbim device from path failed: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
MbimDeviceOpenFlags open_flags = MBIM_DEVICE_OPEN_FLAGS_NONE;
|
|
if (mbim_priv->use_proxy)
|
|
open_flags |= MBIM_DEVICE_OPEN_FLAGS_PROXY;
|
|
|
|
mbim_device_open_sync(mbim_priv->device, open_flags, mbim_priv->context, &error);
|
|
if (error) {
|
|
fprintf(stderr, "error: open mbim device failed: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
return select_sim_slot(mbim_priv);
|
|
}
|
|
|
|
/*
|
|
* Allocate storage in rx and copy the contents of response_data there. Also
|
|
* tack the status at the end, as the MBIM protocol separates the status from
|
|
* the rest of the response.
|
|
*/
|
|
static int copy_data_with_status(
|
|
uint8_t **rx, uint32_t *rx_len,
|
|
const guint8 *response_data, guint32 response_size,
|
|
guint32 status)
|
|
{
|
|
*rx_len = response_size + 2;
|
|
*rx = malloc(*rx_len);
|
|
if (!*rx)
|
|
return -1;
|
|
|
|
memcpy(*rx, response_data, response_size);
|
|
(*rx)[*rx_len - 2] = status & 0xff;
|
|
(*rx)[*rx_len - 1] = (status >> 8) & 0xff;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mbim_apdu_interface_transmit(
|
|
struct euicc_ctx *ctx,
|
|
uint8_t **rx, uint32_t *rx_len,
|
|
const uint8_t *tx, uint32_t tx_len)
|
|
{
|
|
struct mbim_data *mbim_priv = ctx->apdu.interface->userdata;
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
MbimMessage *request = mbim_message_ms_uicc_low_level_access_apdu_set_new(
|
|
mbim_priv->last_channel_id,
|
|
MBIM_UICC_SECURE_MESSAGING_NONE,
|
|
MBIM_UICC_CLASS_BYTE_TYPE_INTER_INDUSTRY,
|
|
tx_len,
|
|
tx,
|
|
&error
|
|
);
|
|
if (!request) {
|
|
fprintf(stderr, "error: creating apdu message failed: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
g_autoptr(MbimMessage) response = mbim_device_command_sync(
|
|
mbim_priv->device, mbim_priv->context, request, &error
|
|
);
|
|
if (!response) {
|
|
fprintf(stderr, "error: no apdu response received: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
guint32 status = 0;
|
|
guint32 response_size = 0;
|
|
const guint8 *response_data = NULL;
|
|
|
|
if (!mbim_message_ms_uicc_low_level_access_apdu_response_parse(
|
|
response, &status, &response_size, &response_data, &error
|
|
)) {
|
|
fprintf(stderr, "error: unable to parse apdu response: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
return copy_data_with_status(rx, rx_len, response_data, response_size, status);
|
|
}
|
|
|
|
static int mbim_apdu_interface_logic_channel_open(
|
|
struct euicc_ctx *ctx,
|
|
const uint8_t *aid,
|
|
uint8_t aid_len)
|
|
{
|
|
struct mbim_data *mbim_priv = ctx->apdu.interface->userdata;
|
|
g_autoptr(GError) error = NULL;
|
|
guint8 channel_id;
|
|
|
|
MbimMessage *request = mbim_message_ms_uicc_low_level_access_open_channel_set_new(
|
|
aid_len, aid, 0, 1, &error
|
|
);
|
|
if (!request) {
|
|
fprintf(stderr, "error: creating channel message failed: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
g_autoptr(MbimMessage) response = mbim_device_command_sync(
|
|
mbim_priv->device, mbim_priv->context, request, &error
|
|
);
|
|
if (!response) {
|
|
fprintf(stderr, "error: no channel response received: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
guint32 status = 0;
|
|
guint32 channel = -1;
|
|
guint32 response_size = 0;
|
|
const guint8 *response_data = NULL;
|
|
|
|
if (!mbim_message_ms_uicc_low_level_access_open_channel_response_parse(
|
|
response, &status, &channel, &response_size, &response_data, &error
|
|
)) {
|
|
fprintf(stderr, "error: unable to parse channel response: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
mbim_priv->last_channel_id = channel;
|
|
return channel;
|
|
}
|
|
|
|
static void mbim_apdu_interface_logic_channel_close(struct euicc_ctx *ctx, uint8_t channel)
|
|
{
|
|
struct mbim_data *mbim_priv = ctx->apdu.interface->userdata;
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
MbimMessage *request = mbim_message_ms_uicc_low_level_access_close_channel_set_new(
|
|
channel, 1, &error
|
|
);
|
|
if (!request) {
|
|
fprintf(stderr, "error: creating channel message failed: %s\n", error->message);
|
|
return;
|
|
}
|
|
|
|
g_autoptr(MbimMessage) response = mbim_device_command_sync(
|
|
mbim_priv->device, mbim_priv->context, request, &error
|
|
);
|
|
if (!response) {
|
|
fprintf(stderr, "error: no channel response received: %s\n", error->message);
|
|
return;
|
|
}
|
|
|
|
guint32 status = 0;
|
|
|
|
if (!mbim_message_ms_uicc_low_level_access_close_channel_response_parse(
|
|
response, &status, &error
|
|
)) {
|
|
fprintf(stderr, "error: unable to parse channel response: %s\n", error->message);
|
|
return;
|
|
}
|
|
|
|
if (channel == mbim_priv->last_channel_id)
|
|
mbim_priv->last_channel_id = -1;
|
|
}
|
|
|
|
static void mbim_apdu_interface_disconnect(struct euicc_ctx *ctx)
|
|
{
|
|
struct mbim_data *mbim_priv = ctx->apdu.interface->userdata;
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
if (mbim_priv->last_channel_id > 0)
|
|
{
|
|
fprintf(stderr, "Cleaning up leaked APDU channel %d\n", mbim_priv->last_channel_id);
|
|
mbim_apdu_interface_logic_channel_close(NULL, mbim_priv->last_channel_id);
|
|
mbim_priv->last_channel_id = -1;
|
|
}
|
|
|
|
mbim_device_close_sync(mbim_priv->device, mbim_priv->context, &error);
|
|
|
|
g_main_context_unref(mbim_priv->context);
|
|
mbim_priv->context = NULL;
|
|
}
|
|
|
|
static int libapduinterface_init(struct euicc_apdu_interface *ifstruct)
|
|
{
|
|
struct mbim_data *mbim_priv;
|
|
|
|
guint32 uim_slot = 0;
|
|
/*
|
|
* We're using the same UIM_SLOT environment variable as the QMI backends.
|
|
* QMI uses 1-based indexing for the sim slots. MBIM uses 0-based indexing,
|
|
* so account for that.
|
|
*/
|
|
const char *env_uim_slot = getenv("UIM_SLOT");
|
|
if (env_uim_slot) {
|
|
uim_slot = atoi(env_uim_slot);
|
|
if (uim_slot == 0) {
|
|
fprintf(stderr, "error: Invalid UIM_SLOT: '%s'\n", env_uim_slot);
|
|
return -1;
|
|
}
|
|
uim_slot--;
|
|
}
|
|
|
|
mbim_priv = malloc(sizeof(struct mbim_data));
|
|
if (!mbim_priv) {
|
|
fprintf(stderr, "Failed allocating memory\n");
|
|
return -1;
|
|
}
|
|
|
|
mbim_priv->uim_slot = uim_slot;
|
|
|
|
const char *use_proxy = getenv("MBIM_USE_PROXY");
|
|
if (use_proxy && strcmp(use_proxy, "0") != 0) {
|
|
mbim_priv->use_proxy = TRUE;
|
|
}
|
|
|
|
if (!(mbim_priv->device_path = getenv("MBIM_DEVICE"))) {
|
|
mbim_priv->device_path = "/dev/cdc-wdm0";
|
|
}
|
|
|
|
memset(ifstruct, 0, sizeof(struct euicc_apdu_interface));
|
|
ifstruct->connect = apdu_interface_connect;
|
|
ifstruct->disconnect = mbim_apdu_interface_disconnect;
|
|
ifstruct->logic_channel_open = mbim_apdu_interface_logic_channel_open;
|
|
ifstruct->logic_channel_close = mbim_apdu_interface_logic_channel_close;
|
|
ifstruct->transmit = mbim_apdu_interface_transmit;
|
|
ifstruct->userdata = mbim_priv;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int libapduinterface_main(int argc, char **argv)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void libapduinterface_fini(struct euicc_apdu_interface *ifstruct)
|
|
{
|
|
g_free(ifstruct->userdata);
|
|
}
|
|
|
|
const struct euicc_driver driver_apdu_mbim = {
|
|
.type = DRIVER_APDU,
|
|
.name = "mbim",
|
|
.init = (int (*)(void *))libapduinterface_init,
|
|
.main = libapduinterface_main,
|
|
.fini = (void (*)(void *))libapduinterface_fini,
|
|
};
|