[layer23] Adding SIM client
authorAndreas.Eversberg <jolly@eversberg.eu>
Sat, 28 Aug 2010 09:36:07 +0000 (09:36 +0000)
committerAndreas.Eversberg <jolly@eversberg.eu>
Sat, 28 Aug 2010 09:36:07 +0000 (09:36 +0000)
The SIM client is not the SIM reader. It is used to process higher layer
requests. One request may be: "read the IMSI file" or "unlock SIM card, here
is the key". It then selects the right file of SIM card and processes the
request by exchanging APDUs with the SIM reader.

NOTE: Because the reader inside layer 1 is not yet finished, the SIM client
will not work and cannot be tested yet.

src/host/layer23/include/osmocom/bb/common/logging.h
src/host/layer23/include/osmocom/bb/common/osmocom_data.h
src/host/layer23/include/osmocom/bb/mobile/sim.h [new file with mode: 0644]
src/host/layer23/src/common/logging.c
src/host/layer23/src/mobile/Makefile.am
src/host/layer23/src/mobile/sim.c [new file with mode: 0644]

index 1a11cf9..922ef11 100644 (file)
@@ -18,6 +18,7 @@ enum {
        DLAPDM,
        DL1C,
        DSUM,
+       DSIM,
 };
 
 extern const struct log_info log_info;
index 2fa59f7..5de2c79 100644 (file)
@@ -17,6 +17,7 @@ struct osmocom_ms;
 #include <osmocom/bb/mobile/gsm322.h>
 #include <osmocom/bb/mobile/gsm48_mm.h>
 #include <osmocom/bb/mobile/gsm48_cc.h>
+#include <osmocom/bb/mobile/sim.h>
 
 /* A layer2 entity */
 struct osmol2_entity {
@@ -42,15 +43,11 @@ struct osmocom_ms {
        uint16_t test_arfcn;
 
        struct gsm_support support;
-
        struct gsm_settings settings;
-
        struct gsm_subscriber subscr;
-
+       struct gsm_sim sim;
        struct osmol2_entity l2_entity;
-
        struct rx_meas_stat meas;
-
        struct gsm48_rrlayer rrlayer;
        struct gsm322_plmn plmn;
        struct gsm322_cellsel cellsel;
diff --git a/src/host/layer23/include/osmocom/bb/mobile/sim.h b/src/host/layer23/include/osmocom/bb/mobile/sim.h
new file mode 100644 (file)
index 0000000..938c9ac
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * (C) 2010 by Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+/* 9.2 commands */
+#define GSM1111_CLASS_GSM              0xa0
+#define        GSM1111_INST_SELECT             0xa4
+#define        GSM1111_INST_STATUS             0xf2
+#define        GSM1111_INST_READ_BINARY        0xb0
+#define        GSM1111_INST_UPDATE_BINARY      0xd6
+#define        GSM1111_INST_READ_RECORD        0xb2
+#define        GSM1111_INST_UPDATE_RECORD      0xdc
+#define        GSM1111_INST_SEEK               0xa2
+#define        GSM1111_INST_INCREASE           0x32
+#define        GSM1111_INST_VERIFY_CHV         0x20
+#define        GSM1111_INST_CHANGE_CHV         0x24
+#define        GSM1111_INST_DISABLE_CHV        0x26
+#define        GSM1111_INST_ENABLE_CHV         0x28
+#define        GSM1111_INST_UNBLOCK_CHV        0x2c
+#define        GSM1111_INST_INVALIDATE         0x04
+#define        GSM1111_INST_REHABLILITATE      0x44
+#define        GSM1111_INST_RUN_GSM_ALGO       0x88
+#define        GSM1111_INST_SLEEP              0xfa
+#define        GSM1111_INST_GET_RESPONSE       0xc0
+#define        GSM1111_INST_TERMINAL_PROFILE   0x10
+#define        GSM1111_INST_ENVELOPE           0xc2
+#define        GSM1111_INST_FETCH              0x12
+#define        GSM1111_INST_TERMINAL_RESPONSE  0x14
+
+/* 9.3 access conditions */
+#define GSM1111_ACC_ALWAYS             0x0
+#define GSM1111_ACC_CHV1               0x1
+#define GSM1111_ACC_CHV2               0x2
+#define GSM1111_ACC_RFU                        0x3
+#define GSM1111_ACC_NEW                        0xf
+/* others are ADM */
+
+/* 9.3 type of file */
+#define GSM1111_TOF_RFU                        0x00
+#define GSM1111_TOF_MF                 0x01
+#define GSM1111_TOF_DF                 0x02
+#define GSM1111_TOF_EF                 0x04
+
+/* 9.3 struct of file */
+#define GSM1111_SOF_TRANSPARENT                0x00
+#define GSM1111_SOF_LINEAR             0x01
+#define GSM1111_SOF_CYCLIC             0x03
+
+/* 9.4 status */
+#define GSM1111_STAT_NORMAL            0x90
+#define GSM1111_STAT_PROACTIVE         0x91
+#define GSM1111_STAT_DL_ERROR          0x9e
+#define GSM1111_STAT_RESPONSE          0x9f
+#define GSM1111_STAT_APP_TK_BUSY       0x93
+#define GSM1111_STAT_MEM_PROBLEM       0x92
+#define GSM1111_STAT_REFERENCING       0x94
+#define GSM1111_STAT_SECURITY          0x98
+#define GSM1111_STAT_INCORR_P3         0x67
+#define GSM1111_STAT_INCORR_P1_P2      0x6b
+#define GSM1111_STAT_UKN_INST          0x6d
+#define GSM1111_STAT_WRONG_CLASS       0x6e
+#define GSM1111_STAT_TECH_PROBLEM      0x6f
+
+/* 9.4.4 Referencing management SW2 */
+#define GSM1111_REF_NO_EF              0x00
+#define GSM1111_REF_OUT_OF_RANGE       0x02
+#define GSM1111_REF_FILE_NOT_FOUND     0x04
+#define GSM1111_REF_FILE_INCONSI       0x08
+
+/* 9.4.5 Security management SW2 */
+#define GSM1111_SEC_NO_CHV             0x02
+#define GSM1111_SEC_NO_ACCESS          0x04
+#define GSM1111_SEC_CONTRA_CHV         0x08
+#define GSM1111_SEC_CONTRA_INVAL       0x10
+#define GSM1111_SEC_BLOCKED            0x40
+#define GSM1111_SEC_MAX_VALUE          0x50
+
+/* messages from application to sim client */
+enum {
+       /* requests */
+       SIM_JOB_READ_BINARY,
+       SIM_JOB_UPDATE_BINARY,
+       SIM_JOB_READ_RECORD,
+       SIM_JOB_UPDATE_RECORD,
+       SIM_JOB_SEEK_RECORD,
+       SIM_JOB_INCREASE,
+       SIM_JOB_INVALIDATE,
+       SIM_JOB_REHABILITATE,
+       SIM_JOB_RUN_GSM_ALGO,
+       SIM_JOB_PIN1_UNLOCK,
+       SIM_JOB_PIN1_CHANGE,
+       SIM_JOB_PIN1_DISABLE,
+       SIM_JOB_PIN1_ENABLE,
+       SIM_JOB_PIN1_UNBLOCK,
+       SIM_JOB_PIN2_UNLOCK,
+       SIM_JOB_PIN2_CHANGE,
+       SIM_JOB_PIN2_UNBLOCK,
+
+       /* results */
+       SIM_JOB_OK,
+       SIM_JOB_ERROR,
+};
+
+/* messages from sim client to application */
+#define SIM_JOB_OK             0
+#define SIM_JOB_ERROR          1
+
+/* error causes */
+#define SIM_CAUSE_NO_SIM       0       /* no SIM present, if detectable */
+#define SIM_CAUSE_SIM_ERROR    1       /* any error while reading SIM */
+#define SIM_CAUSE_REQUEST_ERROR        2       /* error in request */
+#define SIM_CAUSE_PIN1_REQUIRED        3       /* CHV1 is required for access */
+#define SIM_CAUSE_PIN2_REQUIRED        4       /* CHV2 is required for access */
+#define SIM_CAUSE_PIN1_BLOCKED 5       /* CHV1 was entered too many times */
+#define SIM_CAUSE_PIN2_BLOCKED 6       /* CHV2 was entered too many times */
+#define SIM_CAUSE_PUC_BLOCKED  7       /* unblock entered too many times */
+
+/* job states */
+enum {
+       SIM_JST_IDLE = 0,
+       SIM_JST_SELECT_MFDF,            /* SELECT sent */
+       SIM_JST_SELECT_MFDF_RESP,       /* GET RESPONSE sent */
+       SIM_JST_SELECT_EF,              /* SELECT sent */
+       SIM_JST_SELECT_EF_RESP,         /* GET RESPONSE sent */
+       SIM_JST_WAIT_FILE,              /* file command sent */
+       SIM_JST_RUN_GSM_ALGO,           /* wait for algorithm to process */
+       SIM_JST_RUN_GSM_ALGO_RESP,      /* wait for response */
+       SIM_JST_PIN1_UNLOCK,
+       SIM_JST_PIN1_CHANGE,
+       SIM_JST_PIN1_DISABLE,
+       SIM_JST_PIN1_ENABLE,
+       SIM_JST_PIN1_UNBLOCK,
+       SIM_JST_PIN2_UNLOCK,
+       SIM_JST_PIN2_CHANGE,
+       SIM_JST_PIN2_UNBLOCK,
+};
+
+#define MAX_SIM_PATH_LENGTH    6 + 1 /* one for the termination */
+
+struct gsm_sim_handler {
+       struct llist_head       entry;
+
+       uint32_t                handle;
+       void                    (*cb)(struct osmocom_ms *ms, struct msgb *msg);
+};
+
+struct gsm_sim {
+       struct llist_head       handlers; /* gsm_sim_handler */
+       struct llist_head       jobs; /* messages */
+       uint16_t path[MAX_SIM_PATH_LENGTH];
+       uint16_t file;
+
+       struct msgb             *job_msg;
+       uint32_t                job_handle;
+       int                     job_state;
+
+       uint8_t                 reset;
+       uint8_t                 pin1[8], pin2[8];
+       uint8_t                 pin1_len, pin2_len;
+};
+
+struct sim_hdr {
+       int handle;
+       uint8_t job_type;
+       uint16_t path[MAX_SIM_PATH_LENGTH];
+       uint16_t file;
+       uint8_t rec_no, rec_mode; /* in case of record */
+       uint8_t seek_type_mode; /* in case of seek command */
+};
+
+#define SIM_ALLOC_SIZE         128
+#define SIM_ALLOC_HEADROOM     64
+
+struct msgb *gsm_sim_msgb_alloc(uint32_t handle, uint8_t job_type);
+uint32_t sim_open(struct osmocom_ms *ms,
+       void (*cb)(struct osmocom_ms *ms, struct msgb *msg));
+void sim_close(struct osmocom_ms *ms, uint32_t handle);
+void sim_job(struct osmocom_ms *ms, struct msgb *msg);
+
+/* Section 9.2.1 (response to selecting DF or MF) */
+struct gsm1111_response_mfdf {
+       uint16_t rfu1;
+       uint16_t free_mem;
+       uint16_t file_id;
+       uint8_t tof;
+       uint8_t rfu2[5];
+       uint8_t length;
+       uint8_t gsm_data[0];
+} __attribute__ ((packed));
+
+struct gsm1111_response_mfdf_gsm {
+       uint8_t file_char;
+       uint8_t num_df;
+       uint8_t num_ef;
+       uint8_t num_codes;
+       uint8_t rfu1;
+       uint8_t chv1_remain:4,
+                rfu2:3,
+                chv1_init;
+       uint8_t unblk1_remain:4,
+                rfu3:3,
+                unblk1_init;
+       uint8_t chv2_remain:4,
+                rfu4:3,
+                chv2_init;
+       uint8_t unblk2_remain:4,
+                rfu5:3,
+                unblk2_init;
+       uint8_t more_data[0];
+} __attribute__ ((packed));
+
+/* Section 9.2.1 (response to selecting EF) */
+struct gsm1111_response_ef {
+       uint16_t rfu1;
+       uint16_t file_size;
+       uint16_t file_id;
+       uint8_t tof;
+       uint8_t inc_allowed;
+       uint8_t acc_update:4,
+                acc_read:4;
+       uint8_t rfu2:4,
+                acc_inc:4;
+       uint8_t acc_inval:4,
+                acc_reha:4;
+       uint8_t not_inval:1,
+                rfu3:1,
+                ru_inval:1,
+                rfu4:5;
+       uint8_t length;
+       uint8_t structure;
+} __attribute__ ((packed));
+
+/* Section 10.3.17 */
+struct gsm1111_ef_loci {
+       uint32_t tmsi;
+       struct gsm48_loc_area_id lai;
+       uint8_t tmsi_time;
+       uint8_t lupd_status;
+} __attribute__ ((packed));
+
+/* Section 10.5.1 */
+struct gsm1111_ef_adn {
+       uint8_t len_bcd;
+       uint8_t ton_npi;
+       uint8_t number[10];
+       uint8_t capa_conf;
+       uint8_t ext_id;
+} __attribute__ ((packed));
+
+int gsm_sim_init(struct osmocom_ms *ms);
+int gsm_sim_exit(struct osmocom_ms *ms);
+int gsm_sim_job_dequeue(struct osmocom_ms *ms);
+
+
index 281f46f..17e2249 100644 (file)
@@ -102,6 +102,12 @@ static const struct log_info_cat default_categories[] = {
                .color = "\033[1;37m",
                .enabled = 1, .loglevel = LOGL_DEBUG,
        },
+       [DSIM] = {
+               .name = "DSIM",
+               .description = "SIM client",
+               .color = "\033[0;35m",
+               .enabled = 1, .loglevel = LOGL_DEBUG,
+       },
 };
 
 const struct log_info log_info = {
index 055b3c2..b5d910a 100644 (file)
@@ -4,7 +4,7 @@ LDADD = ../common/liblayer23.a $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS)
 
 noinst_LIBRARIES = libmobile.a
 libmobile_a_SOURCES = gsm322.c gsm48_cc.c gsm48_mm.c gsm48_rr.c        \
-       mnccms.c settings.c subscriber.c support.c gps.c        \
+       mnccms.c settings.c subscriber.c support.c gps.c sim.c  \
        sysinfo.c transaction.c vty_interface.c
 
 bin_PROGRAMS = mobile
diff --git a/src/host/layer23/src/mobile/sim.c b/src/host/layer23/src/mobile/sim.c
new file mode 100644 (file)
index 0000000..f10e65e
--- /dev/null
@@ -0,0 +1,1151 @@
+/*
+ * (C) 2010 by Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+#include <arpa/inet.h>
+#include <osmocore/talloc.h>
+#include <osmocore/utils.h>
+
+#include <osmocom/bb/common/logging.h>
+#include <osmocom/bb/common/osmocom_data.h>
+
+extern void *l23_ctx;
+static int sim_process_job(struct osmocom_ms *ms);
+
+/*
+ * support
+ */
+
+uint32_t new_handle = 1;
+
+static struct gsm1111_df_name {
+       uint16_t file;
+       const char *name;
+} gsm1111_df_name[] = {
+       { 0x3f00, "MF" },
+       { 0x7f20, "DFgsm" },
+       { 0x7f10, "DFtelecom" },
+       { 0x7f22, "DFis-41" },
+       { 0x7f23, "DFfp-cts" },
+       { 0x5f50, "DFgraphics" },
+       { 0x5f30, "DFiridium" },
+       { 0x5f31, "DFglobst" },
+       { 0x5f32, "DFico" },
+       { 0x5f33, "DFaces" },
+       { 0x5f40, "DFeia/tia-553" },
+       { 0x5f60, "DFcts" },
+       { 0x5f70, "DFsolsa" },
+       { 0x5f3c, "DFmexe" },
+       { 0, NULL }
+};
+
+static const char *get_df_name(uint16_t fid)
+{
+       int i;
+       static char text[7];
+
+       for (i = 0; gsm1111_df_name[i].file; i++)
+               if (gsm1111_df_name[i].file == fid)
+                       break;
+       if (gsm1111_df_name[i].file)
+               return gsm1111_df_name[i].name;
+
+       sprintf(text, "0x%04x", fid);
+       return text;
+}
+
+static struct gsm_sim_handler *sim_get_handler(struct gsm_sim *sim,
+       uint32_t handle)
+{
+       struct gsm_sim_handler *handler;
+
+       llist_for_each_entry(handler, &sim->handlers, entry)
+               if (handler->handle == handle)
+                       return handler;
+
+       return NULL;
+}
+
+/*
+ * messages
+ */
+
+static const struct value_string sim_job_names[] = {
+       { SIM_JOB_READ_BINARY,          "SIM_JOB_READ_BINARY" },
+       { SIM_JOB_UPDATE_BINARY,        "SIM_JOB_UPDATE_BINARY" },
+       { SIM_JOB_READ_RECORD,          "SIM_JOB_READ_RECORD" },
+       { SIM_JOB_UPDATE_RECORD,        "SIM_JOB_UPDATE_RECORD" },
+       { SIM_JOB_SEEK_RECORD,          "SIM_JOB_SEEK_RECORD" },
+       { SIM_JOB_INCREASE,             "SIM_JOB_INCREASE" },
+       { SIM_JOB_INVALIDATE,           "SIM_JOB_INVALIDATE" },
+       { SIM_JOB_REHABILITATE,         "SIM_JOB_REHABILITATE" },
+       { SIM_JOB_RUN_GSM_ALGO,         "SIM_JOB_RUN_GSM_ALGO" },
+       { SIM_JOB_PIN1_UNLOCK,          "SIM_JOB_PIN1_UNLOCK" },
+       { SIM_JOB_PIN1_CHANGE,          "SIM_JOB_PIN1_CHANGE" },
+       { SIM_JOB_PIN1_DISABLE,         "SIM_JOB_PIN1_DISABLE" },
+       { SIM_JOB_PIN1_ENABLE,          "SIM_JOB_PIN1_ENABLE" },
+       { SIM_JOB_PIN1_UNBLOCK,         "SIM_JOB_PIN1_UNBLOCK" },
+       { SIM_JOB_PIN2_UNLOCK,          "SIM_JOB_PIN2_UNLOCK" },
+       { SIM_JOB_PIN2_CHANGE,          "SIM_JOB_PIN2_CHANGE" },
+       { SIM_JOB_PIN2_UNBLOCK,         "SIM_JOB_PIN2_UNBLOCK" },
+       { SIM_JOB_OK,                   "SIM_JOB_OK" },
+       { SIM_JOB_ERROR,                "SIM_JOB_ERROR" },
+       { 0,                            NULL }
+};
+
+static const char *get_job_name(int value)
+{
+       return get_value_string(sim_job_names, value);
+}
+
+/* allocate sim client message (upper layer) */
+struct msgb *gsm_sim_msgb_alloc(uint32_t handle, uint8_t job_type)
+{
+       struct msgb *msg;
+       struct sim_hdr *nsh;
+
+       msg = msgb_alloc_headroom(SIM_ALLOC_SIZE+SIM_ALLOC_HEADROOM,
+               SIM_ALLOC_HEADROOM, "SIM");
+       if (!msg)
+               return NULL;
+
+       nsh = (struct sim_hdr *) msgb_put(msg, sizeof(*nsh));
+       nsh->handle = handle;
+       nsh->job_type = job_type;
+
+       return msg;
+}
+
+/* reply to job, after it is done. reuse the msgb in the job */
+void gsm_sim_reply(struct osmocom_ms *ms, uint8_t result_type, uint8_t *result,
+       uint16_t result_len)
+{
+       struct gsm_sim *sim = &ms->sim;
+       struct msgb *msg = sim->job_msg;
+       struct sim_hdr *sh;
+       uint8_t *payload;
+       uint16_t payload_len;
+       struct gsm_sim_handler *handler;
+
+       LOGP(DSIM, LOGL_INFO, "sending result to callback function\n");
+
+       /* if no handler, or no callback, just free the job */
+       sh = (struct sim_hdr *)msg->data;
+       handler = sim_get_handler(sim, sh->handle);
+       if (!handler && !handler->cb) {
+               LOGP(DSIM, LOGL_INFO, "no callback or no handler, "
+                       "dropping result\n");
+               msgb_free(sim->job_msg);
+               sim->job_msg = NULL;
+               sim->job_state = SIM_JST_IDLE;
+               return;
+       }
+
+       payload = msg->data + sizeof(*sh);
+       payload_len = msg->len - sizeof(*sh);
+
+       /* remove data */
+       msg->tail -= payload_len;
+       msg->len -= payload_len;
+
+       /* add reply data */
+       sh->job_type = result_type;
+       if (result_len)
+               memcpy(msgb_put(msg, result_len), result, result_len);
+
+       /* callback */
+       sim->job_state = SIM_JST_IDLE;
+       sim->job_msg = NULL;
+       handler->cb(ms, msg);
+}
+
+/* send APDU to card reader */
+static int sim_apdu_send(struct osmocom_ms *ms, uint8_t *data, uint16_t length)
+{
+       // FIXME: send apdu to layer 1
+       LOGP(DSIM, LOGL_INFO, "sending APDU (class 0x%02x, ins 0x%02x)\n",
+               data[0], data[1]);
+       printf("process stops here, because no APDU is exchanged with layer 1\n");
+       return 0;
+}
+
+/* dequeue messages (RSL-SAP) */
+int gsm_sim_job_dequeue(struct osmocom_ms *ms)
+{
+       struct gsm_sim *sim = &ms->sim;
+       struct sim_hdr *sh;
+       struct msgb *msg;
+       struct gsm_sim_handler *handler;
+
+       /* already have a job */
+       if (sim->job_msg)
+               return 0;
+
+       /* get next job */
+       while ((msg = msgb_dequeue(&sim->jobs))) {
+               /* resolve handler */
+               sh = (struct sim_hdr *) msg->data;
+               LOGP(DSIM, LOGL_INFO, "got new job: %s (handle=%08x)\n",
+                       get_job_name(sh->job_type), sh->handle);
+               handler = sim_get_handler(sim, sh->handle);
+               if (!handler) {
+                       LOGP(DSIM, LOGL_INFO, "no handler, ignoring job\n");
+                       /* does not exist anymore */
+                       msgb_free(msg);
+                       continue;
+               }
+
+               /* init job */
+               sim->job_state = SIM_JST_IDLE;
+               sim->job_msg = msg;
+               sim->job_handle = sh->handle;
+
+               /* process current job, message is freed there */
+               sim_process_job(ms);
+               return 1; /* work done */
+       }
+       
+       return 0;
+}
+
+
+/*
+ * SIM commands
+ */
+
+/* 9.2.1 */
+static int gsm1111_tx_select(struct osmocom_ms *ms, uint16_t fid)
+{
+       uint8_t buffer[5 + 2];
+
+       LOGP(DSIM, LOGL_INFO, "SELECT (file=0x%04x)\n", fid);
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_SELECT;
+       buffer[2] = 0x00;
+       buffer[3] = 0x00;
+       buffer[4] = 2;
+       buffer[5] = fid >> 8;
+       buffer[6] = fid;
+
+       return sim_apdu_send(ms, buffer, 5 + 2);
+}
+
+#if 0
+/* 9.2.2 */
+static int gsm1111_tx_status(struct osmocom_ms *ms)
+{
+       uint8_t buffer[5];
+
+       LOGP(DSIM, LOGL_INFO, "STATUS\n");
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_STATUS;
+       buffer[2] = 0x00;
+       buffer[3] = 0x00;
+       buffer[4] = 0;
+
+       return sim_apdu_send(ms, buffer, 5);
+}
+#endif
+
+/* 9.2.3 */
+static int gsm1111_tx_read_binary(struct osmocom_ms *ms, uint16_t offset,
+       uint8_t length)
+{
+       uint8_t buffer[5];
+
+       LOGP(DSIM, LOGL_INFO, "READ BINARY (offset=%d len=%d)\n", offset,
+               length);
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_READ_BINARY;
+       buffer[2] = offset >> 8;
+       buffer[3] = offset;
+       buffer[4] = length;
+
+       return sim_apdu_send(ms, buffer, 5);
+}
+
+/* 9.2.4 */
+static int gsm1111_tx_update_binary(struct osmocom_ms *ms, uint16_t offset,
+       uint8_t *data, uint8_t length)
+{
+       uint8_t buffer[5 + length];
+
+       LOGP(DSIM, LOGL_INFO, "UPDATE BINARY (offset=%d len=%d)\n", offset,
+               length);
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_UPDATE_BINARY;
+       buffer[2] = offset >> 8;
+       buffer[3] = offset;
+       buffer[4] = length;
+       memcpy(buffer + 5, data, length);
+
+       return sim_apdu_send(ms, buffer, 5 + length);
+}
+
+/* 9.2.5 */
+static int gsm1111_tx_read_record(struct osmocom_ms *ms, uint8_t rec_no,
+       uint8_t mode, uint8_t length)
+{
+       uint8_t buffer[5];
+
+       LOGP(DSIM, LOGL_INFO, "READ RECORD (rec_no=%d mode=%d len=%d)\n",
+               rec_no, mode, length);
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_READ_RECORD;
+       buffer[2] = rec_no;
+       buffer[3] = mode;
+       buffer[4] = length;
+
+       return sim_apdu_send(ms, buffer, 5);
+}
+
+/* 9.2.6 */
+static int gsm1111_tx_update_record(struct osmocom_ms *ms, uint8_t rec_no,
+       uint8_t mode, uint8_t *data, uint8_t length)
+{
+       uint8_t buffer[5 + length];
+
+       LOGP(DSIM, LOGL_INFO, "UPDATE RECORD (rec_no=%d mode=%d len=%d)\n",
+               rec_no, mode, length);
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_UPDATE_RECORD;
+       buffer[2] = rec_no;
+       buffer[3] = mode;
+       buffer[4] = length;
+       memcpy(buffer + 5, data, length);
+
+       return sim_apdu_send(ms, buffer, 5 + length);
+}
+
+/* 9.2.7 */
+static int gsm1111_tx_seek(struct osmocom_ms *ms, uint8_t type_mode,
+       uint8_t *pattern, uint8_t length)
+{
+       uint8_t buffer[5 + length];
+       uint8_t type = type_mode >> 4;
+       uint8_t mode = type_mode & 0x0f;
+
+       LOGP(DSIM, LOGL_INFO, "SEEK (type=%d mode=%d len=%d)\n", type, mode,
+               length);
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_SEEK;
+       buffer[2] = 0x00;
+       buffer[3] = type_mode;
+       buffer[4] = length;
+       memcpy(buffer + 5, pattern, length);
+
+       return sim_apdu_send(ms, buffer, 5 + length);
+}
+
+/* 9.2.8 */
+static int gsm1111_tx_increase(struct osmocom_ms *ms, uint32_t value)
+{
+       uint8_t buffer[5 + 3];
+
+       LOGP(DSIM, LOGL_INFO, "INCREASE (value=%d)\n", value);
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_INCREASE;
+       buffer[2] = 0x00;
+       buffer[3] = 0x00;
+       buffer[4] = 3;
+       buffer[5] = value >> 16;
+       buffer[6] = value >> 8;
+       buffer[7] = value;
+
+       return sim_apdu_send(ms, buffer, 5 + 3);
+}
+
+/* 9.2.9 */
+static int gsm1111_tx_verify_chv(struct osmocom_ms *ms, uint8_t chv_no,
+       uint8_t *chv, uint8_t length)
+{
+       uint8_t buffer[5 + 8];
+       int i;
+
+       LOGP(DSIM, LOGL_INFO, "VERIFY CHV (CHV%d)\n", chv_no);
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_VERIFY_CHV;
+       buffer[2] = 0x00;
+       buffer[3] = chv_no;
+       buffer[4] = 8;
+       for (i = 0; i < 8; i++) {
+               if (i < length)
+                       buffer[5 + i] = chv[i];
+               else
+                       buffer[5 + i] = 0xff;
+       }
+
+       return sim_apdu_send(ms, buffer, 5 + 8);
+}
+
+/* 9.2.10 */
+static int gsm1111_tx_change_chv(struct osmocom_ms *ms, uint8_t chv_no,
+       uint8_t *chv_old, uint8_t length_old, uint8_t *chv_new,
+       uint8_t length_new)
+{
+       uint8_t buffer[5 + 16];
+       int i;
+
+       LOGP(DSIM, LOGL_INFO, "CHANGE CHV (CHV%d)\n", chv_no);
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_CHANGE_CHV;
+       buffer[2] = 0x00;
+       buffer[3] = chv_no;
+       buffer[4] = 8;
+       for (i = 0; i < 8; i++) {
+               if (i < length_old)
+                       buffer[5 + i] = chv_old[i];
+               else
+                       buffer[5 + i] = 0xff;
+               if (i < length_new)
+                       buffer[13 + i] = chv_new[i];
+               else
+                       buffer[13 + i] = 0xff;
+       }
+
+       return sim_apdu_send(ms, buffer, 5 + 16);
+}
+
+/* 9.2.11 */
+static int gsm1111_tx_disable_chv(struct osmocom_ms *ms, uint8_t *chv,
+       uint8_t length)
+{
+       uint8_t buffer[5 + 8];
+       int i;
+
+       LOGP(DSIM, LOGL_INFO, "DISABLE CHV (CHV1)\n");
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_DISABLE_CHV;
+       buffer[2] = 0x00;
+       buffer[3] = 0x01;
+       buffer[4] = 8;
+       for (i = 0; i < 8; i++) {
+               if (i < length)
+                       buffer[5 + i] = chv[i];
+               else
+                       buffer[5 + i] = 0xff;
+       }
+
+       return sim_apdu_send(ms, buffer, 5 + 8);
+}
+
+/* 9.2.12 */
+static int gsm1111_tx_enable_chv(struct osmocom_ms *ms, uint8_t *chv,
+       uint8_t length)
+{
+       uint8_t buffer[5 + 8];
+       int i;
+
+       LOGP(DSIM, LOGL_INFO, "ENABLE CHV (CHV1)\n");
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_ENABLE_CHV;
+       buffer[2] = 0x00;
+       buffer[3] = 0x01;
+       buffer[4] = 8;
+       for (i = 0; i < 8; i++) {
+               if (i < length)
+                       buffer[5 + i] = chv[i];
+               else
+                       buffer[5 + i] = 0xff;
+       }
+
+       return sim_apdu_send(ms, buffer, 5 + 8);
+}
+
+/* 9.2.13 */
+static int gsm1111_tx_unblock_chv(struct osmocom_ms *ms, uint8_t chv_no,
+       uint8_t *chv_unblk, uint8_t length_unblk, uint8_t *chv_new,
+       uint8_t length_new)
+{
+       uint8_t buffer[5 + 16];
+       int i;
+
+       LOGP(DSIM, LOGL_INFO, "UNBLOCK CHV (CHV%d)\n", (chv_no == 2) ? 2 : 1);
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_UNBLOCK_CHV;
+       buffer[2] = 0x00;
+       buffer[3] = (chv_no == 1) ? 0 : chv_no;
+       buffer[4] = 8;
+       for (i = 0; i < 8; i++) {
+               if (i < length_unblk)
+                       buffer[5 + i] = chv_unblk[i];
+               else
+                       buffer[5 + i] = 0xff;
+               if (i < length_new)
+                       buffer[13 + i] = chv_new[i];
+               else
+                       buffer[13 + i] = 0xff;
+       }
+
+       return sim_apdu_send(ms, buffer, 5 + 16);
+}
+
+/* 9.2.14 */
+static int gsm1111_tx_invalidate(struct osmocom_ms *ms)
+{
+       uint8_t buffer[5];
+
+       LOGP(DSIM, LOGL_INFO, "INVALIDATE\n");
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_INVALIDATE;
+       buffer[2] = 0x00;
+       buffer[3] = 0x00;
+       buffer[4] = 0;
+
+       return sim_apdu_send(ms, buffer, 5);
+}
+
+/* 9.2.15 */
+static int gsm1111_tx_rehabilitate(struct osmocom_ms *ms)
+{
+       uint8_t buffer[5];
+
+       LOGP(DSIM, LOGL_INFO, "REHABILITATE\n");
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_REHABLILITATE;
+       buffer[2] = 0x00;
+       buffer[3] = 0x00;
+       buffer[4] = 0;
+
+       return sim_apdu_send(ms, buffer, 5);
+}
+
+/* 9.2.16 */
+static int gsm1111_tx_run_gsm_algo(struct osmocom_ms *ms, uint8_t *rand)
+{
+       uint8_t buffer[5 + 16];
+
+       LOGP(DSIM, LOGL_INFO, "RUN GSM ALGORITHM\n");
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_RUN_GSM_ALGO;
+       buffer[2] = 0x00;
+       buffer[3] = 0x00;
+       buffer[4] = 16;
+       memcpy(buffer + 5, rand, 16);
+
+       return sim_apdu_send(ms, buffer, 5 + 16);
+}
+
+#if 0
+/* 9.2.17 */
+static int gsm1111_tx_sleep(struct osmocom_ms *ms)
+{
+       uint8_t buffer[5];
+
+       LOGP(DSIM, LOGL_INFO, "\n");
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_SLEEP;
+       buffer[2] = 0x00;
+       buffer[3] = 0x00;
+       buffer[4] = 0;
+
+       return sim_apdu_send(ms, buffer, 5);
+}
+#endif
+
+/* 9.2.18 */
+static int gsm1111_tx_get_response(struct osmocom_ms *ms, uint8_t length)
+{
+       uint8_t buffer[5];
+
+       LOGP(DSIM, LOGL_INFO, "GET RESPONSE (len=%d)\n", length);
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_GET_RESPONSE;
+       buffer[2] = 0x00;
+       buffer[3] = 0x00;
+       buffer[4] = length;
+
+       return sim_apdu_send(ms, buffer, 5);
+}
+
+#if 0
+/* 9.2.19 */
+static int gsm1111_tx_terminal_profile(struct osmocom_ms *ms, uint8_t *data,
+       uint8_t length)
+{
+       uint8_t buffer[5 + length];
+
+       LOGP(DSIM, LOGL_INFO, "TERMINAL PROFILE (len=%d)\n", length);
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_TERMINAL_PROFILE;
+       buffer[2] = 0x00;
+       buffer[3] = 0x00;
+       buffer[4] = length;
+       memcpy(buffer + 5, data, length);
+
+       return sim_apdu_send(ms, buffer, 5 + length);
+}
+
+/* 9.2.20 */
+static int gsm1111_tx_envelope(struct osmocom_ms *ms, uint8_t *data,
+       uint8_t length)
+{
+       uint8_t buffer[5 + length];
+
+       LOGP(DSIM, LOGL_INFO, "ENVELOPE (len=%d)\n", length);
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_ENVELOPE;
+       buffer[2] = 0x00;
+       buffer[3] = 0x00;
+       buffer[4] = length;
+       memcpy(buffer + 5, data, length);
+
+       return sim_apdu_send(ms, buffer, 5 + length);
+}
+
+/* 9.2.21 */
+static int gsm1111_tx_fetch(struct osmocom_ms *ms, uint8_t length)
+{
+       uint8_t buffer[5];
+
+       LOGP(DSIM, LOGL_INFO, "FETCH (len=%d)\n", length);
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_FETCH;
+       buffer[2] = 0x00;
+       buffer[3] = 0x00;
+       buffer[4] = length;
+
+       return sim_apdu_send(ms, buffer, 5);
+}
+
+/* 9.2.22 */
+static int gsm1111_tx_terminal_response(struct osmocom_ms *ms, uint8_t *data,
+       uint8_t length)
+{
+       uint8_t buffer[5 + length];
+
+       LOGP(DSIM, LOGL_INFO, "TERMINAL RESPONSE (len=%d)\n", length);
+       buffer[0] = GSM1111_CLASS_GSM;
+       buffer[1] = GSM1111_INST_TERMINAL_RESPONSE;
+       buffer[2] = 0x00;
+       buffer[3] = 0x00;
+       buffer[4] = length;
+       memcpy(buffer + 5, data, length);
+
+       return sim_apdu_send(ms, buffer, 5 + length);
+}
+#endif
+
+/*
+ * SIM state machine
+ */
+
+/* process job */
+static int sim_process_job(struct osmocom_ms *ms)
+{
+       struct gsm_sim *sim = &ms->sim;
+       uint8_t *payload;
+       uint16_t payload_len;
+       struct sim_hdr *sh;
+       uint8_t cause;
+       int i;
+
+       /* no current */
+       if (!sim->job_msg)
+               return 0;
+
+       sh = (struct sim_hdr *)sim->job_msg->data;
+       payload = sim->job_msg->data + sizeof(*sh);
+       payload_len = sim->job_msg->len - sizeof(*sh);
+
+       /* do reset before sim reading */
+       if (!sim->reset) {
+               sim->reset = 1;
+               // FIXME: send reset command to L1
+       }
+
+       /* check MF / DF */
+       i = 0;
+       while (sh->path[i] && sim->path[i]) {
+               if (sh->path[i] != sh->path[i])
+                       break;
+               i++;
+       }
+       /* if path in message is shorter or if paths are different */
+       if (sim->path[i]) {
+               LOGP(DSIM, LOGL_INFO, "wrong DF, go MF\n");
+               sim->job_state = SIM_JST_SELECT_MFDF;
+               /* go MF */
+               sim->path[0] = 0;
+               return gsm1111_tx_select(ms, 0x3f00);
+       }
+       /* if path in message is longer */
+       if (sh->path[i]) {
+               LOGP(DSIM, LOGL_INFO, "requested path is longer, go child %s\n",
+                       get_df_name(sh->path[i]));
+               sim->job_state = SIM_JST_SELECT_MFDF;
+               /* select child */
+               sim->path[i] = sh->path[i];
+               sim->path[i + 1] = 0;
+               return gsm1111_tx_select(ms, sh->path[i]);
+       }
+       /* if paths are equal, continue */
+
+       /* set state and trigger SIM process */
+       switch (sh->job_type) {
+       case SIM_JOB_READ_BINARY:
+       case SIM_JOB_UPDATE_BINARY:
+       case SIM_JOB_READ_RECORD:
+       case SIM_JOB_UPDATE_RECORD:
+       case SIM_JOB_SEEK_RECORD:
+       case SIM_JOB_INCREASE:
+       case SIM_JOB_INVALIDATE:
+       case SIM_JOB_REHABILITATE:
+               sim->job_state = SIM_JST_SELECT_EF;
+               return gsm1111_tx_select(ms, sh->file);
+       case SIM_JOB_RUN_GSM_ALGO:
+               if (payload_len != 16) {
+                       LOGP(DSIM, LOGL_ERROR, "random not 16 bytes\n");
+                       break;
+               }
+               sim->job_state = SIM_JST_RUN_GSM_ALGO;
+               return gsm1111_tx_run_gsm_algo(ms, payload);
+       case SIM_JOB_PIN1_UNLOCK:
+               if (payload_len < 4 || payload_len > 8) {
+                       LOGP(DSIM, LOGL_ERROR, "key not in range 4..8\n");
+                       break;
+               }
+               sim->job_state = SIM_JST_PIN1_UNLOCK;
+               memcpy(sim->pin1, payload, payload_len);
+               sim->pin1_len = payload_len;
+               return gsm1111_tx_verify_chv(ms, 0x01, payload, payload_len);
+       case SIM_JOB_PIN2_UNLOCK:
+               if (payload_len < 4 || payload_len > 8) {
+                       LOGP(DSIM, LOGL_ERROR, "key not in range 4..8\n");
+                       break;
+               }
+               sim->job_state = SIM_JST_PIN2_UNLOCK;
+               memcpy(sim->pin2, payload, payload_len);
+               sim->pin2_len = payload_len;
+               return gsm1111_tx_verify_chv(ms, 0x02, payload, payload_len);
+       case SIM_JOB_PIN1_CHANGE:
+               if (!sim->pin1_len) {
+                       LOGP(DSIM, LOGL_ERROR, "no pin set\n");
+                       break;
+               }
+               if (payload_len < 4 || payload_len > 8 || !sim->pin1_len) {
+                       LOGP(DSIM, LOGL_ERROR, "key not in range 4..8\n");
+                       break;
+               }
+               sim->job_state = SIM_JST_PIN1_CHANGE;
+               return gsm1111_tx_change_chv(ms, 0x01, sim->pin1, sim->pin1_len,
+                       payload, payload_len);
+       case SIM_JOB_PIN2_CHANGE:
+               if (!sim->pin2_len) {
+                       LOGP(DSIM, LOGL_ERROR, "no pin set\n");
+                       break;
+               }
+               if (payload_len < 4 || payload_len > 8) {
+                       LOGP(DSIM, LOGL_ERROR, "key not in range 4..8\n");
+                       break;
+               }
+               sim->job_state = SIM_JST_PIN2_CHANGE;
+               return gsm1111_tx_change_chv(ms, 0x02, sim->pin1, sim->pin1_len,
+                       payload, payload_len);
+       case SIM_JOB_PIN1_DISABLE:
+               if (!sim->pin1_len) {
+                       LOGP(DSIM, LOGL_ERROR, "no pin set\n");
+                       break;
+               }
+               sim->job_state = SIM_JST_PIN1_DISABLE;
+               return gsm1111_tx_disable_chv(ms, sim->pin1, sim->pin1_len);
+       case SIM_JOB_PIN1_ENABLE:
+               if (!sim->pin1_len) {
+                       LOGP(DSIM, LOGL_ERROR, "no pin set\n");
+                       break;
+               }
+               sim->job_state = SIM_JST_PIN1_ENABLE;
+               return gsm1111_tx_enable_chv(ms, sim->pin1, sim->pin1_len);
+       case SIM_JOB_PIN1_UNBLOCK:
+               if (payload_len < 12 || payload_len > 16) {
+                       LOGP(DSIM, LOGL_ERROR, "key not in range 4..8\n");
+                       break;
+               }
+               sim->job_state = SIM_JST_PIN1_UNLOCK;
+               memcpy(sim->pin1, payload + 8, payload_len - 8);
+               sim->pin1_len = payload_len;
+               /* NOTE: CHV1 is coded 0x00 here */
+               return gsm1111_tx_unblock_chv(ms, 0x00, payload, 8, payload + 8,
+                       payload_len - 8);
+       case SIM_JOB_PIN2_UNBLOCK:
+               if (payload_len < 12 || payload_len > 16) {
+                       LOGP(DSIM, LOGL_ERROR, "key not in range 4..8\n");
+                       break;
+               }
+               sim->job_state = SIM_JST_PIN2_UNLOCK;
+               memcpy(sim->pin2, payload + 8, payload_len - 8);
+               sim->pin2_len = payload_len;
+               return gsm1111_tx_unblock_chv(ms, 0x02, payload, 8, payload + 8,
+                       payload_len - 8);
+       }
+
+       LOGP(DSIM, LOGL_ERROR, "unknown job %x, please fix\n", sh->job_type);
+       cause = SIM_CAUSE_REQUEST_ERROR;
+       gsm_sim_reply(ms, SIM_JOB_ERROR, &cause, 1);
+
+       return 0;
+}
+
+/* receive SIM response */
+static int sim_apdu_receive(struct osmocom_ms *ms, struct msgb *msg)
+{
+       struct gsm_sim *sim = &ms->sim;
+       uint8_t *payload;
+       uint16_t payload_len;
+       uint8_t *data = msg->data;
+       int length = msg->len, ef_len;
+       uint8_t sw1, sw2;
+       uint8_t cause;
+       struct sim_hdr *sh;
+       struct gsm1111_response_ef *ef;
+       struct gsm1111_response_mfdf *mfdf;
+       struct gsm1111_response_mfdf_gsm *mfdf_gsm;
+       int i;
+
+       /* ignore, if current job already gone */
+       if (!sim->job_msg) {
+               LOGP(DSIM, LOGL_ERROR, "received APDU but no job, "
+                       "please fix!\n");
+               msgb_free(msg);
+               return 0;
+       }
+
+       sh = (struct sim_hdr *)sim->job_msg->data;
+       payload = sim->job_msg->data + sizeof(*sh);
+       payload_len = sim->job_msg->len - sizeof(*sh);
+
+       /* process status */
+       if (length < 2) {
+               msgb_free(msg);
+               return 0;
+       }
+       sw1 = data[length - 2];
+       sw2 = data[length - 1];
+       length -= 2;
+       LOGP(DSIM, LOGL_INFO, "received APDU (len=%d sw1=0x%02x sw2=0x%02x)\n",
+               length, sw1, sw2);
+
+       switch (sw1) {
+       case GSM1111_STAT_MEM_PROBLEM:
+               if (sw2 >= 0x40) {
+                       LOGP(DSIM, LOGL_NOTICE, "memory of SIM failed\n");
+                       sim_error:
+                       cause = SIM_CAUSE_SIM_ERROR;
+                       gsm_sim_reply(ms, SIM_JOB_ERROR, &cause, 1);
+                       msgb_free(msg);
+                       return 0;
+               }
+               LOGP(DSIM, LOGL_NOTICE, "memory of SIM is bad (write took %d "
+                       "times to succeed)\n", sw2);
+               /* fall through */
+       case GSM1111_STAT_NORMAL:
+       case GSM1111_STAT_PROACTIVE:
+       case GSM1111_STAT_DL_ERROR:
+       case GSM1111_STAT_RESPONSE:
+               LOGP(DSIM, LOGL_INFO, "command successfull\n");
+               switch (sh->job_type) {
+               case SIM_JOB_PIN1_CHANGE:
+                       memcpy(sim->pin1, payload, payload_len);
+                       sim->pin1_len = payload_len;
+                       break;
+               case SIM_JOB_PIN2_CHANGE:
+                       memcpy(sim->pin2, payload, payload_len);
+                       sim->pin2_len = payload_len;
+                       break;
+               }
+               break;
+       default:
+               LOGP(DSIM, LOGL_INFO, "command failed\n");
+               switch (sh->job_type) {
+               case SIM_JOB_PIN1_UNLOCK:
+               case SIM_JOB_PIN1_UNBLOCK:
+                       sim->pin1_len = 0;
+                       break;
+               case SIM_JOB_PIN2_UNLOCK:
+               case SIM_JOB_PIN2_UNBLOCK:
+                       sim->pin2_len = 0;
+                       break;
+               }
+               request_error:
+               cause = SIM_CAUSE_REQUEST_ERROR;
+               gsm_sim_reply(ms, SIM_JOB_ERROR, &cause, 1);
+               msgb_free(msg);
+               return 0;
+       }
+
+
+       switch (sim->job_state) {
+       /* step 1: after selecting MF / DF, request the response */
+       case SIM_JST_SELECT_MFDF:
+               /* not enough data */
+               if (sw2 < 22) {
+                       LOGP(DSIM, LOGL_NOTICE, "expecting minimum 22 bytes\n");
+                       goto sim_error;
+               }
+               /* request response */
+               sim->job_state = SIM_JST_SELECT_MFDF_RESP;
+               gsm1111_tx_get_response(ms, sw2);
+               msgb_free(msg);
+               return 0;
+       /* step 2: after getting response of selecting MF / DF, continue
+        * to "process_job".
+        */
+       case SIM_JST_SELECT_MFDF_RESP:
+               if (length < 22) {
+                       LOGP(DSIM, LOGL_NOTICE, "expecting minimum 22 bytes\n");
+                       goto sim_error;
+               }
+               mfdf = (struct gsm1111_response_mfdf *)data;
+               mfdf_gsm = (struct gsm1111_response_mfdf_gsm *)(data + 13);
+               /* if MF was selected, but MF is not indicated */
+               if (ntohs(mfdf->file_id) != 0x3f00 && sim->path[0] == 0) {
+                       goto sim_error;
+               }
+               /* if MF was selected, but type is not indicated */
+               if (mfdf->tof != GSM1111_TOF_MF && sim->path[0]) {
+                       goto sim_error;
+               }
+               /* if DF was selected, but this DF is not indicated */
+               i = 0;
+               while (sim->path[i + 1])
+                       i++;
+               if (ntohs(mfdf->file_id) != sim->path[i]) {
+                       goto sim_error;
+               }
+               /* if DF was selected, but type is not indicated */
+               if (mfdf->tof != GSM1111_TOF_DF) {
+                       goto sim_error;
+               }
+               /* now continue */
+               msgb_free(msg);
+               return sim_process_job(ms);
+       /* step 1: after selecting EF, request response of SELECT */
+       case SIM_JST_SELECT_EF:
+               /* not enough data */
+               if (sw2 < 14) {
+                       LOGP(DSIM, LOGL_NOTICE, "expecting minimum 14 bytes\n");
+                       goto sim_error;
+               }
+               /* request response */
+               sim->job_state = SIM_JST_SELECT_EF_RESP;
+               gsm1111_tx_get_response(ms, sw2);
+               msgb_free(msg);
+               return 0;
+       /* step 2: after getting response of selecting EF, do file command */
+       case SIM_JST_SELECT_EF_RESP:
+               if (length < 14) {
+                       LOGP(DSIM, LOGL_NOTICE, "expecting minimum 14 bytes\n");
+                       goto sim_error;
+               }
+               ef = (struct gsm1111_response_ef *)data;
+               /* if EF was selected, but type is not indicated */
+               if (ntohs(ef->file_id) != sim->file) {
+                       goto sim_error;
+               }
+               /* get length of file */
+               ef_len = ntohs(ef->file_size);
+               /* do file command */
+               sim->job_state = SIM_JST_WAIT_FILE;
+               switch (sh->job_type) {
+               case SIM_JOB_READ_BINARY:
+                       // FIXME: do chunks when greater or equal 256 bytes */
+                       gsm1111_tx_read_binary(ms, 0, ef_len);
+                       break;
+               case SIM_JOB_UPDATE_BINARY:
+                       // FIXME: do chunks when greater or equal 256 bytes */
+                       if (ef_len < length) {
+                               LOGP(DSIM, LOGL_NOTICE, "selected file is "
+                                       "smaller (%d) than data to update "
+                                       "(%d)\n", ef_len, length);
+                               goto request_error;
+                       }
+                       gsm1111_tx_update_binary(ms, 0, data, length);
+                       break;
+               case SIM_JOB_READ_RECORD:
+                       gsm1111_tx_read_record(ms, sh->rec_no, sh->rec_mode,
+                               ef_len);
+                       break;
+               case SIM_JOB_UPDATE_RECORD:
+                       if (ef_len != length) {
+                               LOGP(DSIM, LOGL_NOTICE, "selected file length "
+                                       "(%d) does not equal record to update "
+                                       "(%d)\n", ef_len, length);
+                               goto request_error;
+                       }
+                       gsm1111_tx_update_record(ms, sh->rec_no, sh->rec_mode,
+                               data, length);
+                       break;
+               case SIM_JOB_SEEK_RECORD:
+                       gsm1111_tx_seek(ms, sh->seek_type_mode, data, length);
+                       break;
+               case SIM_JOB_INCREASE:
+                       if (length != 4) {
+                               LOGP(DSIM, LOGL_ERROR, "expecting uint32_t as "
+                                       "value lenght, but got %d bytes\n",
+                                       length);
+                               goto request_error;
+                       }
+                       gsm1111_tx_increase(ms, *((uint32_t *)data));
+                       break;
+               case SIM_JOB_INVALIDATE:
+                       gsm1111_tx_invalidate(ms);
+                       break;
+               case SIM_JOB_REHABILITATE:
+                       gsm1111_tx_rehabilitate(ms);
+                       break;
+               }
+               msgb_free(msg);
+               return 0;
+       /* step 3: after processing file command, job is done */
+       case SIM_JST_WAIT_FILE:
+               /* reply job with data */
+               gsm_sim_reply(ms, SIM_JOB_OK, data, length);
+               msgb_free(msg);
+               return 0;
+       /* step 1: after running GSM algorithm, request response */
+       case SIM_JST_RUN_GSM_ALGO:
+               /* not enough data */
+               if (sw2 < 12) {
+                       LOGP(DSIM, LOGL_NOTICE, "expecting minimum 12 bytes\n");
+                       goto sim_error;
+               }
+               /* request response */
+               sim->job_state = SIM_JST_RUN_GSM_ALGO_RESP;
+               gsm1111_tx_get_response(ms, sw2);
+               msgb_free(msg);
+               return 0;
+       /* step 2: after processing GSM command, job is done */
+       case SIM_JST_RUN_GSM_ALGO_RESP:
+               /* reply job with data */
+               gsm_sim_reply(ms, SIM_JOB_OK, data, length);
+               msgb_free(msg);
+               return 0;
+       case SIM_JST_PIN1_UNLOCK:
+       case SIM_JST_PIN1_CHANGE:
+       case SIM_JST_PIN1_DISABLE:
+       case SIM_JST_PIN1_ENABLE:
+       case SIM_JST_PIN1_UNBLOCK:
+       case SIM_JST_PIN2_UNLOCK:
+       case SIM_JST_PIN2_CHANGE:
+       case SIM_JST_PIN2_UNBLOCK:
+               /* reply job with data */
+               gsm_sim_reply(ms, SIM_JOB_OK, data, length);
+               msgb_free(msg);
+               return 0;
+       }
+
+       LOGP(DSIM, LOGL_ERROR, "unknown state %u, please fix!\n",
+               sim->job_state);
+       goto request_error;
+}
+
+/*
+ * API
+ */
+
+/* open access to sim */
+uint32_t sim_open(struct osmocom_ms *ms,
+       void (*cb)(struct osmocom_ms *ms, struct msgb *msg))
+{
+       struct gsm_sim *sim = &ms->sim;
+       struct gsm_sim_handler *handler;
+
+       /* create handler and attach */
+       handler = talloc_zero(l23_ctx, struct gsm_sim_handler);
+       if (!handler)
+               return 0;
+       handler->handle = new_handle++;
+       handler->cb = cb;
+       llist_add_tail(&handler->entry, &sim->handlers);
+
+       return handler->handle;
+}
+
+/* close access to sim */
+void sim_close(struct osmocom_ms *ms, uint32_t handle)
+{
+       struct gsm_sim *sim = &ms->sim;
+       struct gsm_sim_handler *handler;
+
+       handler = sim_get_handler(sim, handle);
+       if (!handle)
+               return;
+
+       /* kill ourself */
+       llist_del(&handler->entry);
+       talloc_free(handler);
+}
+
+/* send job */
+void sim_job(struct osmocom_ms *ms, struct msgb *msg)
+{
+       struct gsm_sim *sim = &ms->sim;
+
+       msgb_enqueue(&sim->jobs, msg);
+}
+
+/*
+ * init
+ */
+
+int gsm_sim_init(struct osmocom_ms *ms)
+{
+       struct gsm_sim *sim = &ms->sim;
+
+       /* current path is root (MF), no file selected */
+       sim->path[0] = 0;
+       sim->file = 0;
+
+       INIT_LLIST_HEAD(&sim->handlers);
+       INIT_LLIST_HEAD(&sim->jobs);
+
+       LOGP(DSIM, LOGL_INFO, "init SIM client\n");
+
+       return 0;
+}
+
+int gsm_sim_exit(struct osmocom_ms *ms)
+{
+       struct gsm_sim *sim = &ms->sim;
+       struct gsm_sim_handler *handler, *handler2;
+       struct msgb *msg;
+
+       LOGP(DSIM, LOGL_INFO, "exit SIM client\n");
+
+       /* remove pending job msg */
+       if (sim->job_msg) {
+               msgb_free(sim->job_msg);
+               sim->job_msg = NULL;
+       }
+       /* flush handlers */
+       llist_for_each_entry_safe(handler, handler2, &sim->handlers, entry)
+               sim_close(ms, handler->handle);
+       /* flush jobs */
+       while ((msg = msgb_dequeue(&sim->jobs)))
+               msgb_free(msg);
+
+       return 0;
+}
+
+
+
+