mirror of
https://github.com/estkme-group/lpac
synced 2024-09-19 07:20:41 +02:00
328 lines
10 KiB
C
328 lines
10 KiB
C
// vim: expandtab sw=4 ts=4:
|
|
#include "gbinder_hidl.h"
|
|
|
|
#include <euicc/euicc.h>
|
|
#include <euicc/hexutil.h>
|
|
#include <euicc/interface.h>
|
|
#include <gbinder.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#define DEBUG (getenv("GBINDER_APDU_DEBUG") != NULL && strcmp("true", getenv("GBINDER_APDU_DEBUG")) == 0)
|
|
|
|
#define HIDL_SERVICE_DEVICE "/dev/hwbinder"
|
|
#define HIDL_SERVICE_IFACE "android.hardware.radio@1.0::IRadio"
|
|
#define HIDL_SERVICE_IFACE_CALLBACK "android.hardware.radio@1.0::IRadioResponse"
|
|
|
|
// ref: IRadio
|
|
#define HIDL_SERVICE_SET_RESPONSE_FUNCTIONS GBINDER_FIRST_CALL_TRANSACTION
|
|
#define HIDL_SERVICE_ICC_OPEN_LOGICAL_CHANNEL (GBINDER_FIRST_CALL_TRANSACTION + 105)
|
|
#define HIDL_SERVICE_ICC_CLOSE_LOGICAL_CHANNEL (GBINDER_FIRST_CALL_TRANSACTION + 106)
|
|
#define HIDL_SERVICE_ICC_TRANSMIT_APDU_LOGICAL_CHANNEL (GBINDER_FIRST_CALL_TRANSACTION + 107)
|
|
|
|
// ref: IRadioResponse
|
|
#define HIDL_SERVICE_ICC_OPEN_LOGICAL_CHANNEL_CALLBACK (GBINDER_FIRST_CALL_TRANSACTION + 104)
|
|
#define HIDL_SERVICE_ICC_CLOSE_LOGICAL_CHANNEL_CALLBACK (GBINDER_FIRST_CALL_TRANSACTION + 105)
|
|
#define HIDL_SERVICE_ICC_TRANSMIT_APDU_LOGICAL_CHANNEL_CALLBACK (GBINDER_FIRST_CALL_TRANSACTION + 106)
|
|
|
|
static int lastChannelId = -1;
|
|
|
|
struct radio_response_info {
|
|
int32_t type;
|
|
int32_t serial;
|
|
int32_t error;
|
|
};
|
|
|
|
struct icc_io_result {
|
|
int32_t sw1;
|
|
int32_t sw2;
|
|
GBinderHidlString simResponse;
|
|
};
|
|
|
|
struct sim_apdu {
|
|
int32_t sessionId;
|
|
int32_t cla;
|
|
int32_t instruction;
|
|
int32_t p1;
|
|
int32_t p2;
|
|
int32_t p3;
|
|
GBinderHidlString data;
|
|
};
|
|
|
|
static const GBinderWriterField sim_apdu_f[] = {
|
|
GBINDER_WRITER_FIELD_HIDL_STRING(struct sim_apdu, data),
|
|
GBINDER_WRITER_FIELD_END()
|
|
};
|
|
|
|
static const GBinderWriterType sim_apdu_t = {
|
|
GBINDER_WRITER_STRUCT_NAME_AND_SIZE(struct sim_apdu), sim_apdu_f
|
|
};
|
|
|
|
static GBinderServiceManager *sm;
|
|
// IRadioResponse
|
|
static GBinderLocalObject *response_callback;
|
|
// IRadio
|
|
static GBinderRemoteObject *remote;
|
|
static GBinderClient *client;
|
|
|
|
static GMainLoop *binder_loop;
|
|
|
|
static int lastIntResp = -1;
|
|
static int lastRadioErr = 0;
|
|
static struct icc_io_result lastIccIoResult = {0};
|
|
|
|
static GBinderLocalReply *radio_response_transact(
|
|
GBinderLocalObject *obj,
|
|
GBinderRemoteRequest *req,
|
|
guint code, guint flags, int *status, void *user_data)
|
|
{
|
|
GBinderReader reader;
|
|
|
|
gbinder_remote_request_init_reader(req, &reader);
|
|
const struct radio_response_info *resp =
|
|
gbinder_reader_read_hidl_struct(&reader, struct radio_response_info);
|
|
lastRadioErr = resp->error;
|
|
|
|
if (lastRadioErr != 0)
|
|
goto out;
|
|
|
|
switch (code) {
|
|
case HIDL_SERVICE_ICC_OPEN_LOGICAL_CHANNEL_CALLBACK:
|
|
gbinder_reader_read_int32(&reader, &lastIntResp);
|
|
break;
|
|
case HIDL_SERVICE_ICC_TRANSMIT_APDU_LOGICAL_CHANNEL_CALLBACK:
|
|
const struct icc_io_result *icc_io_res = gbinder_reader_read_hidl_struct(&reader, struct icc_io_result);
|
|
// We cannot rely on the *req pointer being valid after we return
|
|
lastIccIoResult.sw1 = icc_io_res->sw1;
|
|
lastIccIoResult.sw2 = icc_io_res->sw2;
|
|
lastIccIoResult.simResponse.data.str = strndup(icc_io_res->simResponse.data.str, icc_io_res->simResponse.len);
|
|
lastIccIoResult.simResponse.len = icc_io_res->simResponse.len;
|
|
lastIccIoResult.simResponse.owns_buffer = TRUE;
|
|
break;
|
|
}
|
|
|
|
out:
|
|
g_main_loop_quit(binder_loop);
|
|
return NULL;
|
|
}
|
|
|
|
static void cleanup_channel(int id)
|
|
{
|
|
GBinderLocalRequest *req = gbinder_client_new_request(client);
|
|
GBinderWriter writer;
|
|
gbinder_local_request_init_writer(req, &writer);
|
|
gbinder_writer_append_int32(&writer, 1000);
|
|
gbinder_writer_append_int32(&writer, id);
|
|
gbinder_client_transact_sync_oneway(client, HIDL_SERVICE_ICC_CLOSE_LOGICAL_CHANNEL, req);
|
|
gbinder_local_request_unref(req);
|
|
g_main_loop_run(binder_loop);
|
|
}
|
|
|
|
static void cleanup(void)
|
|
{
|
|
if (lastChannelId != -1) {
|
|
fprintf(stderr, "Cleaning up leaked APDU channel %d\n", lastChannelId);
|
|
cleanup_channel(lastChannelId);
|
|
lastChannelId = -1;
|
|
}
|
|
}
|
|
|
|
static void sighandler(int sig)
|
|
{
|
|
// This would trigger atexit() hooks
|
|
exit(0);
|
|
}
|
|
|
|
static int try_open_slot(int slotId, const uint8_t *aid, uint32_t aid_len)
|
|
{
|
|
// First, try to connect to the HIDL service for this slot
|
|
char fqname[255];
|
|
snprintf(fqname, 255, "%s/slot%d", HIDL_SERVICE_IFACE, slotId);
|
|
fprintf(stderr, "Attempting to connect to %s\n", fqname);
|
|
|
|
int status = 0;
|
|
sm = gbinder_servicemanager_new(HIDL_SERVICE_DEVICE);
|
|
remote = gbinder_remote_object_ref(
|
|
gbinder_servicemanager_get_service_sync(sm, fqname, &status));
|
|
client = gbinder_client_new(remote, HIDL_SERVICE_IFACE);
|
|
|
|
if (!client) {
|
|
fprintf(stderr, "Failed to connect to IRadio\n");
|
|
gbinder_client_unref(client);
|
|
gbinder_remote_object_unref(remote);
|
|
gbinder_servicemanager_unref(sm);
|
|
return -1;
|
|
}
|
|
|
|
response_callback = gbinder_servicemanager_new_local_object(
|
|
sm, HIDL_SERVICE_IFACE_CALLBACK, radio_response_transact, NULL);
|
|
|
|
GBinderLocalRequest *req = gbinder_client_new_request(client);
|
|
GBinderWriter writer;
|
|
gbinder_local_request_init_writer(req, &writer);
|
|
gbinder_writer_append_local_object(&writer, response_callback);
|
|
gbinder_writer_append_local_object(&writer, NULL);
|
|
gbinder_client_transact_sync_reply(client, HIDL_SERVICE_SET_RESPONSE_FUNCTIONS, req, &status);
|
|
gbinder_local_request_unref(req);
|
|
|
|
if (status < 0) {
|
|
fprintf(stderr, "Failed to call IRadio::setResponseFunctions");
|
|
return -1;
|
|
}
|
|
|
|
// Now, try to open the AID
|
|
uint8_t aid_hex[255];
|
|
euicc_hexutil_bin2hex(aid_hex, 255, aid, aid_len);
|
|
|
|
req = gbinder_client_new_request(client);
|
|
gbinder_local_request_init_writer(req, &writer);
|
|
gbinder_writer_append_int32(&writer, 1000);
|
|
gbinder_writer_append_hidl_string_copy(&writer, aid_hex);
|
|
gbinder_writer_append_int32(&writer, 0);
|
|
status = gbinder_client_transact_sync_oneway(client, HIDL_SERVICE_ICC_OPEN_LOGICAL_CHANNEL, req);
|
|
gbinder_local_request_unref(req);
|
|
|
|
if (status < 0) {
|
|
fprintf(stderr, "Failed to call IRadio::iccOpenLogicalChannel: %d\n", status);
|
|
return status;
|
|
}
|
|
|
|
g_main_loop_run(binder_loop);
|
|
|
|
if (lastRadioErr != 0) {
|
|
fprintf(stderr, "Failed to open APDU logical channel: %d\n", lastRadioErr);
|
|
return -lastRadioErr;
|
|
}
|
|
fprintf(stderr, "opened logical channel id: %d\n", lastIntResp);
|
|
|
|
return lastIntResp;
|
|
}
|
|
|
|
static int apdu_interface_connect(struct euicc_ctx *ctx)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void apdu_interface_disconnect(struct euicc_ctx *ctx)
|
|
{
|
|
cleanup();
|
|
}
|
|
|
|
static int apdu_interface_logic_channel_open(struct euicc_ctx *ctx, const uint8_t *aid, uint8_t aid_len)
|
|
{
|
|
// We only start to use gbinder connection here, because only now can we detect whether
|
|
// a given slot is a valid eSIM slot. This way we can automatically fall back in the case
|
|
// where a device has only one eSIM -- we don't want to force the user to choose in this case.
|
|
int res = try_open_slot(1, aid, aid_len);
|
|
if (res < 0)
|
|
res = try_open_slot(2, aid, aid_len);
|
|
if (res >= 0)
|
|
lastChannelId = res;
|
|
return res;
|
|
}
|
|
|
|
static void apdu_interface_logic_channel_close(struct euicc_ctx *ctx, uint8_t channel)
|
|
{
|
|
cleanup_channel(channel);
|
|
if (lastChannelId == channel)
|
|
lastChannelId = -1;
|
|
|
|
// Only do this cleanup here, because on exit these objects will be destroyed anyway
|
|
gbinder_client_unref(client);
|
|
gbinder_remote_object_unref(remote);
|
|
gbinder_servicemanager_unref(sm);
|
|
}
|
|
|
|
static int apdu_interface_transmit(struct euicc_ctx *ctx, uint8_t **rx, uint32_t *rx_len, const uint8_t *tx, uint32_t tx_len)
|
|
{
|
|
GBinderLocalRequest *req = gbinder_client_new_request(client);
|
|
GBinderWriter writer;
|
|
gbinder_local_request_init_writer(req, &writer);
|
|
gbinder_writer_append_int32(&writer, 1000);
|
|
|
|
uint8_t tx_hex[4096] = {0};
|
|
euicc_hexutil_bin2hex(tx_hex, 4096, &tx[5], tx_len - 5);
|
|
|
|
if (DEBUG)
|
|
fprintf(stderr, "APDU req: %s\n", tx_hex);
|
|
|
|
struct sim_apdu apdu = {
|
|
.sessionId = lastChannelId,
|
|
.cla = tx[0],
|
|
.instruction = tx[1],
|
|
.p1 = tx[2],
|
|
.p2 = tx[3],
|
|
.p3 = tx[4],
|
|
.data = {
|
|
.data = {
|
|
.str = (const char *) tx_hex
|
|
},
|
|
.len = strlen(tx_hex) + 1,
|
|
.owns_buffer = FALSE,
|
|
},
|
|
};
|
|
gbinder_writer_append_struct(&writer, &apdu, &sim_apdu_t, NULL);
|
|
int status = gbinder_client_transact_sync_oneway(client, HIDL_SERVICE_ICC_TRANSMIT_APDU_LOGICAL_CHANNEL, req);
|
|
gbinder_local_request_unref(req);
|
|
|
|
if (status < 0) {
|
|
fprintf(stderr, "Failed to call IRadio::iccTransmitApduLogicalChannel: %d\n", status);
|
|
return status;
|
|
}
|
|
|
|
g_main_loop_run(binder_loop);
|
|
|
|
if (lastRadioErr != 0) {
|
|
return -lastRadioErr;
|
|
}
|
|
|
|
if (DEBUG)
|
|
fprintf(stderr, "APDU resp: %d%d %d %s\n", lastIccIoResult.sw1, lastIccIoResult.sw2, lastIccIoResult.simResponse.len, lastIccIoResult.simResponse.data.str);
|
|
|
|
*rx_len = lastIccIoResult.simResponse.len / 2 + 2;
|
|
*rx = calloc(*rx_len, sizeof(uint8_t));
|
|
euicc_hexutil_hex2bin_r(*rx, *rx_len, lastIccIoResult.simResponse.data.str, lastIccIoResult.simResponse.len);
|
|
(*rx)[*rx_len - 2] = lastIccIoResult.sw1;
|
|
(*rx)[*rx_len - 1] = lastIccIoResult.sw2;
|
|
|
|
// see radio_response_transact -- this is our buffer.
|
|
free((void *) lastIccIoResult.simResponse.data.str);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int libapduinterface_init(struct euicc_apdu_interface *ifstruct)
|
|
{
|
|
ifstruct->connect = apdu_interface_connect;
|
|
ifstruct->disconnect = apdu_interface_disconnect;
|
|
ifstruct->logic_channel_open = apdu_interface_logic_channel_open;
|
|
ifstruct->logic_channel_close = apdu_interface_logic_channel_close;
|
|
ifstruct->transmit = apdu_interface_transmit;
|
|
|
|
// Install cleanup routine
|
|
atexit(cleanup);
|
|
signal(SIGINT, sighandler);
|
|
|
|
// The glib loop is detached from any client object, so create it here.
|
|
binder_loop = g_main_loop_new(NULL, FALSE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int libapduinterface_main(int argc, char **argv)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void libapduinterface_fini(void)
|
|
{
|
|
}
|
|
|
|
const struct euicc_driver driver_apdu_gbinder_hidl = {
|
|
.type = DRIVER_APDU,
|
|
.name = "gbinder_hidl",
|
|
.init = libapduinterface_init,
|
|
.main = libapduinterface_main,
|
|
.fini = libapduinterface_fini,
|
|
};
|