Change in osmo-remsim[master]: client: major restructuring around new main_fsm

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

laforge gerrit-no-reply at lists.osmocom.org
Wed Mar 4 15:42:36 UTC 2020


laforge has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-remsim/+/17361 )


Change subject: client: major restructuring around new main_fsm
......................................................................

client: major restructuring around new main_fsm

The remsim_client code already used FSMs for the connections
to both remsim-server and remsim-bankd.  However the 'main' part of the
program was not yet implemented as a FSM, making it somewhat difficult
to perform the right actions in every possible situation.

This commit re-structures the code around a central main_fsm, which
gets notified from the per-connection FSMs and which handles the common
processing.  It also handles the execution of external script commands,
and hence further unifies the code base between the different backends
(simtrace2, ifd_handler, shell)

Closes: #4414

Change-Id: I44a430bc5674dea00ed72a0b28729ac8bcb4e022
---
M src/client/Makefile.am
M src/client/client.h
A src/client/main_fsm.c
M src/client/remsim_client.c
M src/client/remsim_client_main.c
D src/client/simtrace2-remsim_client.c
M src/client/user_ifdhandler.c
M src/client/user_shell.c
A src/client/user_simtrace2.c
M src/rspro_client_fsm.c
10 files changed, 1,119 insertions(+), 1,392 deletions(-)



  git pull ssh://gerrit.osmocom.org:29418/osmo-remsim refs/changes/61/17361/1

diff --git a/src/client/Makefile.am b/src/client/Makefile.am
index f37126a..43b030f 100644
--- a/src/client/Makefile.am
+++ b/src/client/Makefile.am
@@ -7,7 +7,7 @@
 bin_PROGRAMS = osmo-remsim-client-st2 osmo-remsim-client-shell
 
 osmo_remsim_client_shell_SOURCES = user_shell.c remsim_client_main.c \
-				   remsim_client.c ../rspro_client_fsm.c ../debug.c
+				   remsim_client.c main_fsm.c ../rspro_client_fsm.c ../debug.c
 osmo_remsim_client_shell_CFLAGS = $(AM_CFLAGS)
 osmo_remsim_client_shell_LDADD = $(OSMOCORE_LIBS) $(OSMOGSM_LIBS) $(OSMOABIS_LIBS) \
 		      $(top_builddir)/src/libosmo-rspro.la
@@ -19,15 +19,16 @@
 bundlelinuxdir=$(bundledir)/Linux
 bundlelinux_LTLIBRARIES = libifd_remsim_client.la
 libifd_remsim_client_la_SOURCES = user_ifdhandler.c \
-				   remsim_client.c ../rspro_client_fsm.c ../debug.c
+				   remsim_client.c main_fsm.c ../rspro_client_fsm.c ../debug.c
 libifd_remsim_client_la_CFLAGS = $(AM_CFLAGS)
 libifd_remsim_client_la_CPPFLAGS = $(PCSC_CFLAGS)
 libifd_remsim_client_la_LDFLAGS = -no-undefined
 libifd_remsim_client_la_LIBADD = $(OSMOCORE_LIBS) $(OSMOGSM_LIBS) $(OSMOABIS_LIBS) \
 		      $(top_builddir)/src/libosmo-rspro.la
 
-osmo_remsim_client_st2_SOURCES = simtrace2-remsim_client.c \
-				 ../rspro_client_fsm.c ../debug.c
+osmo_remsim_client_st2_SOURCES = user_simtrace2.c remsim_client_main.c \
+				 remsim_client.c main_fsm.c ../rspro_client_fsm.c ../debug.c
+osmo_remsim_client_st2_CPPFLAGS = -DUSB_SUPPORT
 osmo_remsim_client_st2_CFLAGS = $(AM_CFLAGS)
 osmo_remsim_client_st2_LDADD = $(OSMOCORE_LIBS) $(OSMOGSM_LIBS) $(OSMOABIS_LIBS) \
 			       $(OSMOUSB_LIBS) $(OSMOSIMTRACE2_LIBS) \
diff --git a/src/client/client.h b/src/client/client.h
index 0761255..b828882 100644
--- a/src/client/client.h
+++ b/src/client/client.h
@@ -10,9 +10,49 @@
 #include "slotmap.h"
 #include "debug.h"
 
+/***********************************************************************
+ * frontend interface
+ ***********************************************************************/
+
+struct bankd_client;
+
+struct frontend_phys_status {
+	struct {
+		/* all members can be 0 (inactive), 1 (active) or -1 (not supported/known) */
+		int reset_active;
+		int vcc_present;
+		int clk_active;
+		int card_present;
+	} flags;
+	uint16_t voltage_mv;
+	uint8_t fi;
+	uint8_t di;
+	uint8_t wi;
+	uint8_t waiting_time;
+};
+
+struct frontend_pts {
+	const uint8_t *buf;
+	size_t len;
+};
+
+struct frontend_tpdu {
+	const uint8_t *buf;
+	size_t len;
+};
+
+/* API from generic core to frontend (modem/cardem) */
+int frontend_request_card_insert(struct bankd_client *bc);
+int frontend_request_sim_remote(struct bankd_client *bc);
+int frontend_request_modem_reset(struct bankd_client *bc);
+int frontend_handle_card2modem(struct bankd_client *bc, const uint8_t *data, size_t len);
+int frontend_handle_set_atr(struct bankd_client *bc, const uint8_t *data, size_t len);
+int frontend_handle_slot_status(struct bankd_client *bc, const SlotPhysStatus_t *sts);
+int frontend_append_script_env(struct bankd_client *bc, char **env, size_t max_env);
+
 /* main.c */
 
-struct cardem_inst;
+struct osmo_st2_cardem_inst;
 
 #define ATR_SIZE_MAX            55
 struct client_config {
@@ -48,6 +88,8 @@
 	struct rspro_server_conn srv_conn;
 	/* connection to the remsim-bankd (data) */
 	struct rspro_server_conn bankd_conn;
+	/* CLIENT_MAIN fsm */
+	struct osmo_fsm_inst *main_fi;
 
 	/* remote component ID */
 	struct app_comp_id peer_comp_id;
@@ -55,17 +97,43 @@
 	struct bank_slot bankd_slot;
 
 	struct client_config *cfg;
-	struct cardem_inst *cardem;
+	struct osmo_st2_cardem_inst *cardem;
+	struct frontend_phys_status last_status;
 	void *data;
 };
 
 #define srvc2bankd_client(srvc)		container_of(srvc, struct bankd_client, srv_conn)
 #define bankdc2bankd_client(bdc)	container_of(bdc, struct bankd_client, bankd_conn)
 
-struct bankd_client *remsim_client_create(void *ctx, const char *name, const char *software);
+struct client_config *client_config_init(void *ctx);
+struct bankd_client *remsim_client_create(void *ctx, const char *name, const char *software,
+					  struct client_config *cfg);
 void remsim_client_set_clslot(struct bankd_client *bc, int client_id, int slot_nr);
 
-
-extern int client_user_bankd_handle_rx(struct rspro_server_conn *bankdc, const RsproPDU_t *pdu);
-
 extern int client_user_main(struct bankd_client *g_client);
+
+
+/***********************************************************************
+ * main FSM
+ ***********************************************************************/
+
+enum main_fsm_event {
+	MF_E_SRVC_CONNECTED,	/* connection to server established (TCP + RSPRO level) */
+	MF_E_SRVC_LOST,		/* connection to server was lost */
+	MF_E_SRVC_CONFIG_BANK,	/* server instructs us to connect to bankd/slot */
+	MF_E_SRVC_RESET_REQ,	/* RsproPDUchoice_PR_ResetStateReq */
+
+	MF_E_BANKD_CONNECTED,	/* connection to bankd established (TCP + RSPRO level) */
+	MF_E_BANKD_LOST,	/* connection to bankd was lost */
+	MF_E_BANKD_TPDU,	/* RsproPDUchoice_PR_tpduCardToModem */
+	MF_E_BANKD_ATR,		/* RsproPDUchoice_PR_setAtrReq */
+	MF_E_BANKD_SLOT_STATUS,	/* bankSlotStatusInd */
+
+	MF_E_MDM_STATUS_IND,	/* status from modem/cardem */
+	MF_E_MDM_PTS_IND,	/* PTS indication from modem/cardem */
+	MF_E_MDM_TPDU,		/* TPDU from modem/cardem */
+};
+struct osmo_fsm_inst *main_fsm_alloc(void *ctx, struct bankd_client *bc);
+
+
+
diff --git a/src/client/main_fsm.c b/src/client/main_fsm.c
new file mode 100644
index 0000000..b94af26
--- /dev/null
+++ b/src/client/main_fsm.c
@@ -0,0 +1,377 @@
+/* (C) 2020 by Harald Welte <laforge at gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * 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 <string.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/exec.h>
+
+#include "rspro_util.h"
+#include "client.h"
+#include "debug.h"
+
+#define S(x)	(1 << (x))
+
+/***********************************************************************/
+
+/* build the (additional) environment for executing a script */
+static char **build_script_env(struct bankd_client *bc, const char *cause)
+{
+	char **env = talloc_zero_size(bc, 256*sizeof(char *));
+	int rc, i = 0;
+
+	if (!env)
+		return NULL;
+
+	env[i++] = talloc_asprintf(env, "REMSIM_CLIENT_VERSION=%s", VERSION);
+
+	env[i++] = talloc_asprintf(env, "REMSIM_SERVER_ADDR=%s:%u",
+				   bc->srv_conn.server_host, bc->srv_conn.server_port);
+	env[i++] = talloc_asprintf(env, "REMSIM_SERVER_STATE=%s",
+				   osmo_fsm_inst_state_name(bc->srv_conn.fi));
+
+	env[i++] = talloc_asprintf(env, "REMSIM_BANKD_ADDR=%s:%u",
+				   bc->bankd_conn.server_host, bc->bankd_conn.server_port);
+	env[i++] = talloc_asprintf(env, "REMSIM_BANKD_STATE=%s",
+				   osmo_fsm_inst_state_name(bc->bankd_conn.fi));
+
+
+	if (bc->srv_conn.clslot) {
+		env[i++] = talloc_asprintf(env, "REMSIM_CLIENT_SLOT=%lu:%lu",
+					   bc->srv_conn.clslot->clientId,
+					   bc->srv_conn.clslot->slotNr);
+	}
+	env[i++] = talloc_asprintf(env, "REMSIM_BANKD_SLOT=%u:%u",
+				   bc->bankd_slot.bank_id, bc->bankd_slot.slot_nr);
+
+	env[i++] = talloc_asprintf(env, "REMSIM_SIM_VCC=%u", bc->last_status.flags.vcc_present);
+	env[i++] = talloc_asprintf(env, "REMSIM_SIM_RST=%u", bc->last_status.flags.reset_active);
+	/* TODO: SIM card state CLK */
+
+	env[i++] = talloc_asprintf(env, "REMSIM_CAUSE=%s", cause);
+
+	/* ask frontend to append any frontend-speccific additional environment vars */
+	rc = frontend_append_script_env(bc, env+i, 256-i);
+	if (rc > 0)
+		i += rc;
+
+	/* terminate last entry */
+	env[i++] = NULL;
+	return env;
+}
+
+static int call_script(struct bankd_client *bc, const char *cause)
+{
+	char **env, *cmd;
+	int rc;
+
+	if (!bc->cfg->event_script)
+		return 0;
+
+	env = build_script_env(bc, cause);
+	if (!env)
+		return -ENOMEM;
+
+	cmd = talloc_asprintf(env, "%s %s", bc->cfg->event_script, cause);
+	if (!cmd) {
+		talloc_free(env);
+		return -ENOMEM;
+	}
+
+	rc = osmo_system_nowait(cmd, osmo_environment_whitelist, env);
+	talloc_free(env);
+
+	return rc;
+}
+
+
+/***********************************************************************/
+
+
+enum main_fsm_state {
+	MF_ST_INIT,
+	MF_ST_UNCONFIGURED,	/* waiting for configuration from server */
+	MF_ST_WAIT_BANKD,	/* configured; waiting for bankd conn */
+	MF_ST_OPERATIONAL,	/* fully operational (configured + bankd conn live */
+};
+
+static const struct value_string main_fsm_event_names[] = {
+	OSMO_VALUE_STRING(MF_E_SRVC_CONNECTED),
+	OSMO_VALUE_STRING(MF_E_SRVC_LOST),
+	OSMO_VALUE_STRING(MF_E_SRVC_CONFIG_BANK),
+	OSMO_VALUE_STRING(MF_E_SRVC_RESET_REQ),
+	OSMO_VALUE_STRING(MF_E_BANKD_CONNECTED),
+	OSMO_VALUE_STRING(MF_E_BANKD_LOST),
+	OSMO_VALUE_STRING(MF_E_BANKD_TPDU),
+	OSMO_VALUE_STRING(MF_E_BANKD_ATR),
+	OSMO_VALUE_STRING(MF_E_BANKD_SLOT_STATUS),
+	OSMO_VALUE_STRING(MF_E_MDM_STATUS_IND),
+	OSMO_VALUE_STRING(MF_E_MDM_PTS_IND),
+	OSMO_VALUE_STRING(MF_E_MDM_TPDU),
+	{ 0, NULL }
+};
+
+static void main_st_operational(struct osmo_fsm_inst *fi, uint32_t event, void *data);
+
+static void main_st_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct bankd_client *bc = (struct bankd_client *) fi->priv;
+
+	switch (event) {
+	case MF_E_SRVC_CONNECTED:
+		osmo_fsm_inst_state_chg(fi, MF_ST_UNCONFIGURED, 0, 0);
+		call_script(bc, "event-server-connect");
+		break;
+	default:
+		OSMO_ASSERT(0);
+	}
+}
+
+static void main_st_unconfigured_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	struct bankd_client *bc = (struct bankd_client *) fi->priv;
+	/* we might be called from a 'higher' state such as operational; clean up */
+	osmo_fsm_inst_dispatch(bc->bankd_conn.fi, SRVC_E_DISCONNECT, NULL);
+}
+
+static void main_st_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	switch (event) {
+	case MF_E_SRVC_CONFIG_BANK:
+		/* same treatment as below */
+		main_st_operational(fi, event, data);
+		break;
+	default:
+		OSMO_ASSERT(0);
+	}
+}
+
+static void main_st_wait_bankd(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct bankd_client *bc = (struct bankd_client *) fi->priv;
+
+	switch (event) {
+	case MF_E_SRVC_CONFIG_BANK:
+		/* same treatment as below */
+		main_st_operational(fi, event, data);
+		break;
+	case MF_E_BANKD_CONNECTED:
+		osmo_fsm_inst_state_chg(fi, MF_ST_OPERATIONAL, 0, 0);
+		call_script(bc, "event-bankd-connect");
+		break;
+	default:
+		OSMO_ASSERT(0);
+	}
+}
+
+static void main_st_operational_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	struct bankd_client *bc = (struct bankd_client *) fi->priv;
+
+	/* Simulate card-insert to modem */
+	frontend_request_card_insert(bc);
+	call_script(bc, "request-card-insert");
+
+	/* Select remote (forwarded) SIM */
+	frontend_request_sim_remote(bc);
+	call_script(bc, "request-sim-remote");
+
+	/* Set the ATR */
+	frontend_handle_set_atr(bc, bc->cfg->atr.data, bc->cfg->atr.len);
+
+	/* Reset the modem */
+	frontend_request_modem_reset(bc);
+	call_script(bc, "request-modem-reset");
+}
+
+static void main_st_operational(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct bankd_client *bc = (struct bankd_client *) fi->priv;
+	struct frontend_phys_status *pstatus = NULL;
+	struct frontend_pts *pts = NULL;
+	struct frontend_tpdu *tpdu = NULL;
+	RsproPDU_t *pdu_rx = NULL;
+	RsproPDU_t *resp;
+	BankSlot_t bslot;
+
+	switch (event) {
+	case MF_E_BANKD_LOST:
+		osmo_fsm_inst_state_chg(fi, MF_ST_WAIT_BANKD, 0, 0);
+		break;
+	case MF_E_SRVC_CONFIG_BANK:
+		pdu_rx = data;
+		OSMO_ASSERT(pdu_rx);
+		OSMO_ASSERT(pdu_rx->msg.present == RsproPDUchoice_PR_configClientBankReq);
+		/* store/set the bankd ip/port as instructed by the server */
+		osmo_talloc_replace_string(bc, &bc->bankd_conn.server_host,
+					   rspro_IpAddr2str(&pdu_rx->msg.choice.configClientBankReq.bankd.ip));
+		bc->bankd_conn.server_port = pdu_rx->msg.choice.configClientBankReq.bankd.port;
+		rspro2bank_slot(&bc->bankd_slot, &pdu_rx->msg.choice.configClientBankReq.bankSlot);
+		/* bankd port 0 is a magic value to indicate "no bankd" */
+		if (bc->bankd_conn.server_port == 0)
+			osmo_fsm_inst_state_chg(fi, MF_ST_UNCONFIGURED, 0, 0);
+		else {
+			osmo_fsm_inst_state_chg(fi, MF_ST_WAIT_BANKD, 0, 0);
+			/* TODO: do we need to disconnect before? */
+			osmo_fsm_inst_dispatch(bc->bankd_conn.fi, SRVC_E_ESTABLISH, NULL);
+		}
+		/* send response to server */
+		resp = rspro_gen_ConfigClientBankRes(ResultCode_ok);
+		server_conn_send_rspro(&bc->srv_conn, resp);
+		call_script(bc, "event-config-bankd");
+		break;
+	case MF_E_BANKD_TPDU:
+		pdu_rx = data;
+		OSMO_ASSERT(pdu_rx);
+		OSMO_ASSERT(pdu_rx->msg.present == RsproPDUchoice_PR_tpduCardToModem);
+		/* forward to modem/cardem (via API) */
+		frontend_handle_card2modem(bc, pdu_rx->msg.choice.tpduCardToModem.data.buf,
+					   pdu_rx->msg.choice.tpduCardToModem.data.size);
+		/* response happens indirectly via tpduModemToCard */
+		break;
+	case MF_E_BANKD_ATR:
+		pdu_rx = data;
+		OSMO_ASSERT(pdu_rx);
+		OSMO_ASSERT(pdu_rx->msg.present == RsproPDUchoice_PR_setAtrReq);
+		/* forward to modem/cardem (via API) */
+		frontend_handle_set_atr(bc, pdu_rx->msg.choice.setAtrReq.atr.buf,
+					pdu_rx->msg.choice.setAtrReq.atr.size);
+		/* send response to bankd */
+		resp = rspro_gen_SetAtrRes(ResultCode_ok);
+		server_conn_send_rspro(&bc->bankd_conn, resp);
+		break;
+	case MF_E_BANKD_SLOT_STATUS:
+		pdu_rx = data;
+		OSMO_ASSERT(pdu_rx);
+		OSMO_ASSERT(pdu_rx->msg.present == RsproPDUchoice_PR_bankSlotStatusInd);
+		/* forward to modem/cardem (via API) */
+		frontend_handle_slot_status(bc, &pdu_rx->msg.choice.bankSlotStatusInd.slotPhysStatus);
+		break;
+	case MF_E_MDM_STATUS_IND:
+		pstatus = data;
+		OSMO_ASSERT(pstatus);
+		/* forward to bankd */
+		bank_slot2rspro(&bslot, &bc->bankd_slot);
+		resp = rspro_gen_ClientSlotStatusInd(bc->srv_conn.clslot, &bslot,
+						     pstatus->flags.reset_active,
+						     pstatus->flags.vcc_present,
+						     pstatus->flags.clk_active,
+						     pstatus->flags.card_present);
+		server_conn_send_rspro(&bc->bankd_conn, resp);
+		if (!memcmp(&bc->last_status.flags, &pstatus->flags, sizeof(pstatus->flags)))
+			call_script(bc, "event-modem-status");
+		bc->last_status = *pstatus;
+		break;
+	case MF_E_MDM_PTS_IND:
+		pts = data;
+		OSMO_ASSERT(pts);
+		/* forward to bankd? */
+		break;
+	case MF_E_MDM_TPDU:
+		tpdu = data;
+		OSMO_ASSERT(tpdu);
+		/* forward to bankd */
+		bank_slot2rspro(&bslot, &bc->bankd_slot);
+		resp = rspro_gen_TpduModem2Card(bc->srv_conn.clslot, &bslot, tpdu->buf, tpdu->len);
+		server_conn_send_rspro(&bc->bankd_conn, resp);
+		break;
+	default:
+		OSMO_ASSERT(0);
+	}
+}
+
+static void main_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	switch (event) {
+	case MF_E_SRVC_LOST:
+		/* should we do anything? The SRVC fsm will take care of reconnect, and we
+		 * can continue to talk to the bankd without any trouble... */
+		break;
+	case MF_E_SRVC_RESET_REQ:
+		osmo_fsm_inst_state_chg(fi, MF_ST_UNCONFIGURED, 0, 0);
+		break;
+	default:
+		OSMO_ASSERT(0);
+	}
+}
+
+
+static const struct osmo_fsm_state main_fsm_states[] = {
+	[MF_ST_INIT] = {
+		.name = "INIT",
+		.in_event_mask = S(MF_E_SRVC_CONNECTED),
+		.out_state_mask = S(MF_ST_UNCONFIGURED),
+		.action = main_st_init,
+	},
+	[MF_ST_UNCONFIGURED] = {
+		.name = "UNCONFIGURED",
+		.in_event_mask = S(MF_E_SRVC_CONFIG_BANK),
+		.out_state_mask = S(MF_ST_INIT) | S(MF_ST_WAIT_BANKD),
+		.action = main_st_unconfigured,
+		.onenter = main_st_unconfigured_onenter,
+	},
+	[MF_ST_WAIT_BANKD] = {
+		.name = "WAIT_BANKD",
+		.in_event_mask = S(MF_E_SRVC_CONFIG_BANK) | S(MF_E_BANKD_CONNECTED),
+		.out_state_mask = S(MF_ST_INIT) | S(MF_ST_UNCONFIGURED) | S(MF_ST_OPERATIONAL),
+		.action = main_st_wait_bankd,
+	},
+	[MF_ST_OPERATIONAL] = {
+		.name = "OPERATIONAL",
+		.in_event_mask = S(MF_E_SRVC_CONFIG_BANK) |
+				 S(MF_E_BANKD_LOST) |
+				 S(MF_E_BANKD_TPDU) |
+				 S(MF_E_BANKD_ATR) |
+				 S(MF_E_BANKD_SLOT_STATUS) |
+				 S(MF_E_MDM_STATUS_IND) |
+				 S(MF_E_MDM_PTS_IND) |
+				 S(MF_E_MDM_TPDU),
+		.out_state_mask = S(MF_ST_INIT) | S(MF_ST_UNCONFIGURED) | S(MF_ST_WAIT_BANKD),
+		.action = main_st_operational,
+		.onenter = main_st_operational_onenter,
+	},
+};
+
+static struct osmo_fsm client_main_fsm = {
+	.name = "CLIENT_MAIN",
+	.states = main_fsm_states,
+	.num_states = ARRAY_SIZE(main_fsm_states),
+	.allstate_event_mask = S(MF_E_SRVC_LOST) | S(MF_E_SRVC_RESET_REQ),
+	.allstate_action = main_allstate_action,
+	.log_subsys = DMAIN,
+	.event_names = main_fsm_event_names,
+};
+
+struct osmo_fsm_inst *main_fsm_alloc(void *ctx, struct bankd_client *bc)
+{
+	return osmo_fsm_inst_alloc(&client_main_fsm, ctx, bc, LOGL_DEBUG, "main");
+}
+
+static __attribute((constructor)) void on_dso_load_main_fsm(void)
+{
+	OSMO_ASSERT(osmo_fsm_register(&client_main_fsm) == 0);
+}
diff --git a/src/client/remsim_client.c b/src/client/remsim_client.c
index dd78c8e..182894f 100644
--- a/src/client/remsim_client.c
+++ b/src/client/remsim_client.c
@@ -20,6 +20,10 @@
  *
  */
 
+/* This file contains code shared among all remsim client applications,
+ * including the ifd-handler, which is not an executable program with a main()
+ * function or command line parsing, but a shared library */
+
 #include <errno.h>
 #include <string.h>
 
@@ -33,8 +37,38 @@
 #include "client.h"
 #include "debug.h"
 
+struct client_config *client_config_init(void *ctx)
+{
+	struct client_config *cfg = talloc_zero(ctx, struct client_config);
+	if (!cfg)
+		return NULL;
+
+	cfg->server_host = talloc_strdup(cfg, "127.0.0.1");
+	cfg->server_port = 9998;
+	cfg->client_id = -1;
+	cfg->client_slot = -1;
+	cfg->gsmtap_host = talloc_strdup(cfg, "127.0.0.1");
+	cfg->keep_running = false;
+
+	cfg->usb.vendor_id = -1;
+	cfg->usb.product_id = -1;
+	cfg->usb.config_id = -1;
+	cfg->usb.if_num = -1;
+	cfg->usb.altsetting = 0;
+	cfg->usb.addr = -1;
+	cfg->usb.path = NULL;
+
+	cfg->atr.data[0] = 0x3B;
+	cfg->atr.data[1] = 0x00; // the shortest simplest ATR possible
+	cfg->atr.len = 2;
+
+	return cfg;
+};
+
 static int bankd_handle_rx(struct rspro_server_conn *bankdc, const RsproPDU_t *pdu)
 {
+	struct bankd_client *bc = bankdc2bankd_client(bankdc);
+
 	switch (pdu->msg.present) {
 	case RsproPDUchoice_PR_connectClientRes:
 		/* Store 'identity' of bankd to in peer_comp_id */
@@ -42,8 +76,11 @@
 		osmo_fsm_inst_dispatch(bankdc->fi, SRVC_E_CLIENT_CONN_RES, (void *) pdu);
 		break;
 	case RsproPDUchoice_PR_tpduCardToModem:
+		return osmo_fsm_inst_dispatch(bc->main_fi, MF_E_BANKD_TPDU, (void *) pdu);
 	case RsproPDUchoice_PR_setAtrReq:
-		return client_user_bankd_handle_rx(bankdc, pdu);
+		return osmo_fsm_inst_dispatch(bc->main_fi, MF_E_BANKD_ATR, (void *) pdu);
+	case RsproPDUchoice_PR_bankSlotStatusInd:
+		return osmo_fsm_inst_dispatch(bc->main_fi, MF_E_BANKD_SLOT_STATUS, (void *) pdu);
 	default:
 		LOGPFSML(bankdc->fi, LOGL_ERROR, "Unknown/Unsupported RSPRO PDU %s\n",
 			 rspro_msgt_name(pdu));
@@ -78,19 +115,7 @@
 		server_conn_send_rspro(srvc, resp);
 		break;
 	case RsproPDUchoice_PR_configClientBankReq:
-		/* store/set the bankd ip/port as instructed by the server */
-		osmo_talloc_replace_string(bc, &bc->bankd_conn.server_host,
-					   rspro_IpAddr2str(&pdu->msg.choice.configClientBankReq.bankd.ip));
-		rspro2bank_slot(&bc->bankd_slot, &pdu->msg.choice.configClientBankReq.bankSlot);
-		bc->bankd_conn.server_port = pdu->msg.choice.configClientBankReq.bankd.port;
-		/* bankd port 0 is a magic value to indicate "no bankd" */
-		if (bc->bankd_conn.server_port == 0)
-			osmo_fsm_inst_dispatch(bc->bankd_conn.fi, SRVC_E_DISCONNECT, NULL);
-		else
-			osmo_fsm_inst_dispatch(bc->bankd_conn.fi, SRVC_E_ESTABLISH, NULL);
-		/* send response to server */
-		resp = rspro_gen_ConfigClientBankRes(ResultCode_ok);
-		server_conn_send_rspro(srvc, resp);
+		osmo_fsm_inst_dispatch(bc->main_fi, MF_E_SRVC_CONFIG_BANK, (void *) pdu);
 		break;
 	default:
 		LOGPFSML(srvc->fi, LOGL_ERROR, "Unknown/Unsupported RSPRO PDU type: %s\n",
@@ -101,7 +126,8 @@
 	return 0;
 }
 
-struct bankd_client *remsim_client_create(void *ctx, const char *name, const char *software)
+struct bankd_client *remsim_client_create(void *ctx, const char *name, const char *software,
+					  struct client_config *cfg)
 {
 	struct bankd_client *bc = talloc_zero(ctx, struct bankd_client);
 	struct rspro_server_conn *srvc, *bankdc;
@@ -110,10 +136,20 @@
 	if (!bc)
 		return NULL;
 
+	bc->cfg = cfg;
+
+	bc->main_fi = main_fsm_alloc(bc, bc);
+	if (!bc->main_fi) {
+		fprintf(stderr, "Unable to create main client FSM: %s\n", strerror(errno));
+		exit(1);
+	}
+
+	remsim_client_set_clslot(bc, cfg->client_id, cfg->client_slot);
+
 	/* create and [attempt to] establish connection to remsim-server */
 	srvc = &bc->srv_conn;
-	srvc->server_host = "localhost";
-	srvc->server_port = 9998;
+	srvc->server_host = cfg->server_host;
+	srvc->server_port = cfg->server_port;
 	srvc->handle_rx = srvc_handle_rx;
 	srvc->own_comp_id.type = ComponentType_remsimClient;
 	OSMO_STRLCPY_ARRAY(srvc->own_comp_id.name, name);
@@ -125,6 +161,9 @@
 		fprintf(stderr, "Unable to create Server conn FSM: %s\n", strerror(errno));
 		exit(1);
 	}
+	osmo_fsm_inst_change_parent(srvc->fi, bc->main_fi, MF_E_SRVC_LOST);
+	srvc->parent_conn_evt = MF_E_SRVC_CONNECTED;
+	srvc->parent_disc_evt = MF_E_SRVC_LOST;
 
 	bankdc = &bc->bankd_conn;
 	/* server_host / server_port are configured from remsim-server */
@@ -136,6 +175,9 @@
 		exit(1);
 	}
 	osmo_fsm_inst_update_id(bankdc->fi, "bankd");
+	osmo_fsm_inst_change_parent(bankdc->fi, bc->main_fi, MF_E_BANKD_LOST);
+	bankdc->parent_conn_evt = MF_E_BANKD_CONNECTED;
+	bankdc->parent_disc_evt = MF_E_BANKD_LOST;
 
 	return bc;
 }
diff --git a/src/client/remsim_client_main.c b/src/client/remsim_client_main.c
index 95eb089..6587e6b 100644
--- a/src/client/remsim_client_main.c
+++ b/src/client/remsim_client_main.c
@@ -23,27 +23,56 @@
 {
 	printf(
 		"  -h --help                  Print this help message\n"
+		"  -v --version               Print program version\n"
 		"  -i --server-ip A.B.C.D     remsim-server IP address\n"
 		"  -p --server-port 13245     remsim-server TCP port\n"
-		"  -i --client-id <0-65535>   RSPRO ClientId of this client\n"
+		"  -c --client-id <0-65535>   RSPRO ClientId of this client\n"
 		"  -n --client-slot <0-65535> RSPRO SlotNr of this client\n"
+		"  -e --event-script <path>   event script to be called by client\n"
+#ifdef USB_SUPPORT
+		"  -V --usb-vendor VENDOR_ID\n"
+		"  -P --usb-product PRODUCT_ID\n"
+		"  -C --usb-config CONFIG_ID\n"
+		"  -I --usb-interface INTERFACE_ID\n"
+		"  -S --usb-altsetting ALTSETTING_ID\n"
+		"  -A --usb-address ADDRESS\n"
+		"  -H --usb-path PATH\n"
+#endif
 	      );
 }
 
-static void handle_options(struct bankd_client *bc, int argc, char **argv)
+static void handle_options(struct client_config *cfg, int argc, char **argv)
 {
+	int rc;
+
 	while (1) {
 		int option_index = 0, c;
 		static const struct option long_options[] = {
 			{ "help", 0, 0, 'h' },
+			{ "version", 0, 0, 'v' },
 			{ "server-ip", 1, 0, 'i' },
 			{ "server-port", 1, 0, 'p' },
 			{ "client-id", 1, 0, 'c' },
 			{ "client-slot", 1, 0, 'n' },
+			{ "atr", 1, 0, 'a' },
+			{ "event-script", 1, 0, 'e' },
+#ifdef USB_SUPPORT
+			{ "usb-vendor", 1, 0, 'V' },
+			{ "usb-product", 1, 0, 'P' },
+			{ "usb-config", 1, 0, 'C' },
+			{ "usb-interface", 1, 0, 'I' },
+			{ "usb-altsetting", 1, 0, 'S' },
+			{ "usb-address", 1, 0, 'A' },
+			{ "usb-path", 1, 0, 'H' },
+#endif
 			{ 0, 0, 0, 0 }
 		};
 
-		c = getopt_long(argc, argv, "hi:p:c:n:",
+		c = getopt_long(argc, argv, "hvi:p:c:n:e:"
+#ifdef USB_SUPPORT
+						"V:P:C:I:S:A:H:"
+#endif
+				,
 				long_options, &option_index);
 		if (c == -1)
 			break;
@@ -53,18 +82,55 @@
 			printf_help();
 			exit(0);
 			break;
+		case 'v':
+			printf("osmo-remsim-client version %s\n", VERSION);
+			exit(0);
+			break;
 		case 'i':
-			bc->srv_conn.server_host = optarg;
+			osmo_talloc_replace_string(cfg, &cfg->server_host, optarg);
 			break;
 		case 'p':
-			bc->srv_conn.server_port = atoi(optarg);
+			cfg->server_port = atoi(optarg);
 			break;
 		case 'c':
-			remsim_client_set_clslot(bc, atoi(optarg), -1);
+			cfg->client_id = atoi(optarg);
 			break;
 		case 'n':
-			remsim_client_set_clslot(bc, -1, atoi(optarg));
+			cfg->client_slot = atoi(optarg);
 			break;
+		case 'a':
+			rc = osmo_hexparse(optarg, cfg->atr.data, ARRAY_SIZE(cfg->atr.data));
+			if (rc < 2 || rc > ARRAY_SIZE(cfg->atr.data)) {
+				fprintf(stderr, "ATR malformed\n");
+				exit(2);
+			}
+			break;
+		case 'e':
+			osmo_talloc_replace_string(cfg, &cfg->event_script, optarg);
+			break;
+#ifdef USB_SUPPORT
+		case 'V':
+			cfg->usb.vendor_id = strtol(optarg, NULL, 16);
+			break;
+		case 'P':
+			cfg->usb.product_id = strtol(optarg, NULL, 16);
+			break;
+		case 'C':
+			cfg->usb.config_id = atoi(optarg);
+			break;
+		case 'I':
+			cfg->usb.if_num = atoi(optarg);
+			break;
+		case 'S':
+			cfg->usb.altsetting = atoi(optarg);
+			break;
+		case 'A':
+			cfg->usb.addr = atoi(optarg);
+			break;
+		case 'H':
+			cfg->usb.path = optarg;
+			break;
+#endif
 		default:
 			break;
 		}
@@ -74,6 +140,7 @@
 int main(int argc, char **argv)
 {
 	struct bankd_client *g_client;
+	struct client_config *cfg;
 	char hostname[256];
 
 	gethostname(hostname, sizeof(hostname));
@@ -84,9 +151,11 @@
 
 	osmo_init_logging2(g_tall_ctx, &log_info);
 
-	g_client = remsim_client_create(g_tall_ctx, hostname, "remsim-client");
+	cfg = client_config_init(g_tall_ctx);
+	OSMO_ASSERT(cfg);
+	handle_options(cfg, argc, argv);
 
-	handle_options(g_client, argc, argv);
+	g_client = remsim_client_create(g_tall_ctx, hostname, "remsim-client",cfg);
 
 	osmo_fsm_inst_dispatch(g_client->srv_conn.fi, SRVC_E_ESTABLISH, NULL);
 
diff --git a/src/client/simtrace2-remsim_client.c b/src/client/simtrace2-remsim_client.c
deleted file mode 100644
index e495f10..0000000
--- a/src/client/simtrace2-remsim_client.c
+++ /dev/null
@@ -1,1284 +0,0 @@
-/* (C) 2018-2020 by Harald Welte <laforge at gnumonks.org>
- * (C) 2018 by sysmocom - s.f.m.c. GmbH, Author: Kevin Redon
- *
- * All Rights Reserved
- *
- * SPDX-License-Identifier: GPL-2.0+
- *
- * 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 <errno.h>
-#include <string.h>
-
-#include <talloc.h>
-
-#include <osmocom/core/msgb.h>
-#include <osmocom/core/fsm.h>
-#include <osmocom/core/utils.h>
-#include <osmocom/core/logging.h>
-#include <osmocom/core/application.h>
-#include <osmocom/core/exec.h>
-
-#include <osmocom/abis/ipa.h>
-#include <osmocom/gsm/protocol/ipaccess.h>
-
-#include "rspro_util.h"
-#include "client.h"
-#include "debug.h"
-
-#include <unistd.h>
-#include <stdio.h>
-#include <linux/limits.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <getopt.h>
-
-#include <libusb.h>
-
-#include <osmocom/usb/libusb.h>
-#include <osmocom/simtrace2/simtrace_prot.h>
-#include <osmocom/simtrace2/simtrace_usb.h>
-#include <osmocom/simtrace2/apdu_dispatch.h>
-
-#include <osmocom/core/gsmtap.h>
-#include <osmocom/core/gsmtap_util.h>
-#include <osmocom/core/utils.h>
-#include <osmocom/core/socket.h>
-#include <osmocom/core/msgb.h>
-#include <osmocom/sim/class_tables.h>
-#include <osmocom/sim/sim.h>
-
-/* transport to a SIMtrace device */
-struct st_transport {
-	/* USB */
-	struct libusb_device_handle *usb_devh;
-	struct {
-		uint8_t in;
-		uint8_t out;
-		uint8_t irq_in;
-	} usb_ep;
-};
-
-/* a SIMtrace slot; communicates over a transport */
-struct st_slot {
-	/* transport through which the slot can be reached */
-	struct st_transport *transp;
-	/* number of the slot within the transport */
-	uint8_t slot_nr;
-};
-
-/* One istance of card emulation */
-struct cardem_inst {
-	/* slot on which this card emulation instance runs */
-	struct st_slot *slot;
-	struct cardemu_usb_msg_status last_status;
-	char *usb_path;
-};
-
-/* global GSMTAP instance */
-static struct gsmtap_inst *g_gti;
-
-struct bankd_client *g_client;
-static void *g_tall_ctx;
-void __thread *talloc_asn1_ctx;
-int asn_debug;
-
-/* should we leave main loop processing? */
-bool g_leave_main = false;
-
-__attribute__((unused)) static int gsmtap_send_sim(const uint8_t *apdu, unsigned int len)
-{
-	struct gsmtap_hdr *gh;
-	unsigned int gross_len = len + sizeof(*gh);
-	uint8_t *buf = malloc(gross_len);
-	int rc;
-
-	if (!buf)
-		return -ENOMEM;
-
-	memset(buf, 0, sizeof(*gh));
-	gh = (struct gsmtap_hdr *) buf;
-	gh->version = GSMTAP_VERSION;
-	gh->hdr_len = sizeof(*gh)/4;
-	gh->type = GSMTAP_TYPE_SIM;
-
-	memcpy(buf + sizeof(*gh), apdu, len);
-
-	rc = write(gsmtap_inst_fd(g_gti), buf, gross_len);
-	if (rc < 0) {
-		perror("write gsmtap");
-		free(buf);
-		return rc;
-	}
-
-	free(buf);
-	return 0;
-}
-
-/* build the (additional) environment for executing a script */
-static char **build_script_env(struct bankd_client *clnt, const char *cause)
-{
-	struct cardem_inst *ci = clnt->cardem;
-	char **env = talloc_zero_size(clnt, 256*sizeof(char *));
-	int i = 0;
-
-	if (!env)
-		return NULL;
-
-	env[i++] = talloc_asprintf(env, "REMSIM_CLIENT_VERSION=%s", VERSION);
-
-	env[i++] = talloc_asprintf(env, "REMSIM_SERVER_ADDR=%s:%u",
-				   clnt->srv_conn.server_host, clnt->srv_conn.server_port);
-	env[i++] = talloc_asprintf(env, "REMSIM_SERVER_STATE=%s",
-				   osmo_fsm_inst_state_name(clnt->srv_conn.fi));
-
-	env[i++] = talloc_asprintf(env, "REMSIM_BANKD_ADDR=%s:%u",
-				   clnt->bankd_conn.server_host, clnt->bankd_conn.server_port);
-	env[i++] = talloc_asprintf(env, "REMSIM_BANKD_STATE=%s",
-				   osmo_fsm_inst_state_name(clnt->bankd_conn.fi));
-
-
-	if (clnt->srv_conn.clslot) {
-		env[i++] = talloc_asprintf(env, "REMSIM_CLIENT_SLOT=%lu:%lu",
-					   g_client->srv_conn.clslot->clientId,
-					   g_client->srv_conn.clslot->slotNr);
-	}
-	env[i++] = talloc_asprintf(env, "REMSIM_BANKD_SLOT=%u:%u",
-				   clnt->bankd_slot.bank_id, clnt->bankd_slot.slot_nr);
-
-	env[i++] = talloc_asprintf(env, "REMSIM_USB_PATH=%s", ci->usb_path);
-	env[i++] = talloc_asprintf(env, "REMSIM_USB_INTERFACE=%u", clnt->cfg->usb.if_num);
-
-	/* TODO: SIM card state VCC/CLK/RST */
-	env[i++] = talloc_asprintf(env, "REMSIM_SIM_VCC=%u",
-				   !!(ci->last_status.flags & CEMU_STATUS_F_VCC_PRESENT));
-	env[i++] = talloc_asprintf(env, "REMSIM_SIM_RST=%u",
-				   !!(ci->last_status.flags & CEMU_STATUS_F_RESET_ACTIVE));
-
-	env[i++] = talloc_asprintf(env, "REMSIM_CAUSE=%s", cause);
-
-	/* terminate last entry */
-	env[i++] = NULL;
-	return env;
-}
-
-static int call_script(struct bankd_client *clnt, const char *cause)
-{
-	char **env, *cmd;
-	int rc;
-
-	if (!clnt->cfg->event_script)
-		return 0;
-
-	env = build_script_env(clnt, cause);
-	if (!env)
-		return -ENOMEM;
-
-	cmd = talloc_asprintf(env, "%s %s", clnt->cfg->event_script, cause);
-	if (!cmd) {
-		talloc_free(env);
-		return -ENOMEM;
-	}
-
-	rc = osmo_system_nowait(cmd, osmo_environment_whitelist, env);
-	talloc_free(env);
-
-	return rc;
-}
-
-/***********************************************************************
- * SIMTRACE core protocol
- ***********************************************************************/
-
-/*! \brief allocate a message buffer for simtrace use */
-static struct msgb *st_msgb_alloc(void)
-{
-	return msgb_alloc_headroom(1024+32, 32, "SIMtrace");
-}
-
-#if 0
-static void apdu_out_cb(uint8_t *buf, unsigned int len, void *user_data)
-{
-	printf("APDU: %s\n", osmo_hexdump(buf, len));
-	gsmtap_send_sim(buf, len);
-}
-#endif
-
-static void usb_out_xfer_cb(struct libusb_transfer *xfer)
-{
-	struct msgb *msg = xfer->user_data;
-
-	switch (xfer->status) {
-	case LIBUSB_TRANSFER_COMPLETED:
-		break;
-	case LIBUSB_TRANSFER_NO_DEVICE:
-		fprintf(stderr, "USB device disappeared\n");
-		g_leave_main = true;
-		break;
-	default:
-		fprintf(stderr, "USB OUT transfer failed, status=%u\n", xfer->status);
-		g_leave_main = true;
-		break;
-	}
-
-	msgb_free(msg);
-	libusb_free_transfer(xfer);
-}
-
-/*! \brief Transmit a given command to the SIMtrace2 device */
-int st_transp_tx_msg(struct st_transport *transp, struct msgb *msg)
-{
-	struct libusb_transfer *xfer;
-	int rc;
-
-	printf("SIMtrace <- %s\n", msgb_hexdump(msg));
-
-	xfer = libusb_alloc_transfer(0);
-	OSMO_ASSERT(xfer);
-	xfer->dev_handle = transp->usb_devh;
-	xfer->flags = 0;
-	xfer->type = LIBUSB_TRANSFER_TYPE_BULK;
-	xfer->endpoint = transp->usb_ep.out;
-	xfer->timeout = 1000;
-	xfer->user_data = msg;
-	xfer->length = msgb_length(msg);
-	xfer->buffer = msgb_data(msg);
-	xfer->callback = usb_out_xfer_cb;
-
-	/* submit the OUT transfer */
-	rc = libusb_submit_transfer(xfer);
-	OSMO_ASSERT(rc == 0);
-
-	return rc;
-}
-
-static struct simtrace_msg_hdr *st_push_hdr(struct msgb *msg, uint8_t msg_class, uint8_t msg_type,
-					    uint8_t slot_nr)
-{
-	struct simtrace_msg_hdr *sh;
-
-	sh = (struct simtrace_msg_hdr *) msgb_push(msg, sizeof(*sh));
-	memset(sh, 0, sizeof(*sh));
-	sh->msg_class = msg_class;
-	sh->msg_type = msg_type;
-	sh->slot_nr = slot_nr;
-	sh->msg_len = msgb_length(msg);
-
-	return sh;
-}
-
-/* transmit a given message to a specified slot. Expects all headers
- * present before calling the function */
-int st_slot_tx_msg(struct st_slot *slot, struct msgb *msg,
-		   uint8_t msg_class, uint8_t msg_type)
-{
-	st_push_hdr(msg, msg_class, msg_type, slot->slot_nr);
-
-	return st_transp_tx_msg(slot->transp, msg);
-}
-
-/***********************************************************************
- * Card Emulation protocol
- ***********************************************************************/
-
-
-/*! \brief Request the SIMtrace2 to generate a card-insert signal */
-static int cardem_request_card_insert(struct cardem_inst *ci, bool inserted)
-{
-	struct msgb *msg = st_msgb_alloc();
-	struct cardemu_usb_msg_cardinsert *cins;
-
-	cins = (struct cardemu_usb_msg_cardinsert *) msgb_put(msg, sizeof(*cins));
-	memset(cins, 0, sizeof(*cins));
-	if (inserted)
-		cins->card_insert = 1;
-
-	return st_slot_tx_msg(ci->slot, msg, SIMTRACE_MSGC_CARDEM, SIMTRACE_MSGT_DT_CEMU_CARDINSERT);
-}
-
-/*! \brief Request the SIMtrace2 to transmit a Procedure Byte, then Rx */
-static int cardem_request_pb_and_rx(struct cardem_inst *ci, uint8_t pb, uint8_t le)
-{
-	struct msgb *msg = st_msgb_alloc();
-	struct cardemu_usb_msg_tx_data *txd;
-	txd = (struct cardemu_usb_msg_tx_data *) msgb_put(msg, sizeof(*txd));
-
-	printf("SIMtrace <= %s(%02x, %d)\n", __func__, pb, le);
-
-	memset(txd, 0, sizeof(*txd));
-	txd->data_len = 1;
-	txd->flags = CEMU_DATA_F_PB_AND_RX;
-	/* one data byte */
-	msgb_put_u8(msg, pb);
-
-	return st_slot_tx_msg(ci->slot, msg, SIMTRACE_MSGC_CARDEM, SIMTRACE_MSGT_DT_CEMU_TX_DATA);
-}
-
-/*! \brief Request the SIMtrace2 to transmit a Procedure Byte, then Tx */
-static int cardem_request_pb_and_tx(struct cardem_inst *ci, uint8_t pb,
-				    const uint8_t *data, uint16_t data_len_in)
-{
-	struct msgb *msg = st_msgb_alloc();
-	struct cardemu_usb_msg_tx_data *txd;
-	uint8_t *cur;
-
-	txd = (struct cardemu_usb_msg_tx_data *) msgb_put(msg, sizeof(*txd));
-
-	printf("SIMtrace <= %s(%02x, %s, %d)\n", __func__, pb,
-		osmo_hexdump(data, data_len_in), data_len_in);
-
-	memset(txd, 0, sizeof(*txd));
-	txd->data_len = 1 + data_len_in;
-	txd->flags = CEMU_DATA_F_PB_AND_TX;
-	/* procedure byte */
-	msgb_put_u8(msg, pb);
-	/* data */
-	cur = msgb_put(msg, data_len_in);
-	memcpy(cur, data, data_len_in);
-
-	return st_slot_tx_msg(ci->slot, msg, SIMTRACE_MSGC_CARDEM, SIMTRACE_MSGT_DT_CEMU_TX_DATA);
-}
-
-/*! \brief Request the SIMtrace2 to send a Status Word */
-static int cardem_request_sw_tx(struct cardem_inst *ci, const uint8_t *sw)
-{
-	struct msgb *msg = st_msgb_alloc();
-	struct cardemu_usb_msg_tx_data *txd;
-	uint8_t *cur;
-
-	txd = (struct cardemu_usb_msg_tx_data *) msgb_put(msg, sizeof(*txd));
-
-	printf("SIMtrace <= %s(%02x %02x)\n", __func__, sw[0], sw[1]);
-
-	memset(txd, 0, sizeof(*txd));
-	txd->data_len = 2;
-	txd->flags = CEMU_DATA_F_PB_AND_TX | CEMU_DATA_F_FINAL;
-	cur = msgb_put(msg, 2);
-	cur[0] = sw[0];
-	cur[1] = sw[1];
-
-	return st_slot_tx_msg(ci->slot, msg, SIMTRACE_MSGC_CARDEM, SIMTRACE_MSGT_DT_CEMU_TX_DATA);
-}
-
-/*! \brief Request the SIMtrace2 to send a Status Word */
-static int cardem_request_config(struct cardem_inst *ci, uint32_t features)
-{
-	struct msgb *msg = st_msgb_alloc();
-	struct cardemu_usb_msg_config *cfg;
-
-	cfg = (struct cardemu_usb_msg_config *) msgb_put(msg, sizeof(*cfg));
-
-	printf("SIMtrace <= %s(%08x)\n", __func__, features);
-
-	memset(cfg, 0, sizeof(*cfg));
-	cfg->features = features;
-
-	return st_slot_tx_msg(ci->slot, msg, SIMTRACE_MSGC_CARDEM, SIMTRACE_MSGT_BD_CEMU_CONFIG);
-}
-
-// FIXME check if the ATR actually includes a checksum
-__attribute__((unused)) static void atr_update_csum(uint8_t *atr, unsigned int atr_len)
-{
-	uint8_t csum = 0;
-	int i;
-
-	for (i = 1; i < atr_len - 1; i++)
-		csum = csum ^ atr[i];
-
-	atr[atr_len-1] = csum;
-}
-
-static int cardem_request_set_atr(struct cardem_inst *ci, const uint8_t *atr, unsigned int atr_len)
-{
-	struct msgb *msg = st_msgb_alloc();
-	struct cardemu_usb_msg_set_atr *satr;
-	uint8_t *cur;
-
-	satr = (struct cardemu_usb_msg_set_atr *) msgb_put(msg, sizeof(*satr));
-
-	printf("SIMtrace <= %s(%s)\n", __func__, osmo_hexdump(atr, atr_len));
-
-	memset(satr, 0, sizeof(*satr));
-	satr->atr_len = atr_len;
-	cur = msgb_put(msg, atr_len);
-	memcpy(cur, atr, atr_len);
-
-	return st_slot_tx_msg(ci->slot, msg, SIMTRACE_MSGC_CARDEM, SIMTRACE_MSGT_DT_CEMU_SET_ATR);
-}
-
-/***********************************************************************
- * Modem Control protocol
- ***********************************************************************/
-
-static int _modem_reset(struct st_slot *slot, uint8_t asserted, uint16_t pulse_ms)
-{
-	struct msgb *msg = st_msgb_alloc();
-	struct st_modem_reset *sr ;
-
-	sr = (struct st_modem_reset *) msgb_put(msg, sizeof(*sr));
-	sr->asserted = asserted;
-	sr->pulse_duration_msec = pulse_ms;
-
-	return st_slot_tx_msg(slot, msg, SIMTRACE_MSGC_MODEM, SIMTRACE_MSGT_DT_MODEM_RESET);
-}
-
-/*! \brief pulse the RESET line of the modem for \a duration_ms milli-seconds*/
-int st_modem_reset_pulse(struct st_slot *slot, uint16_t duration_ms)
-{
-	return _modem_reset(slot, 2, duration_ms);
-}
-
-/*! \brief assert the RESET line of the modem */
-int st_modem_reset_active(struct st_slot *slot)
-{
-	return _modem_reset(slot, 1, 0);
-}
-
-/*! \brief de-assert the RESET line of the modem */
-int st_modem_reset_inactive(struct st_slot *slot)
-{
-	return _modem_reset(slot, 0, 0);
-}
-
-static int _modem_sim_select(struct st_slot *slot, uint8_t remote_sim)
-{
-	struct msgb *msg = st_msgb_alloc();
-	struct st_modem_sim_select *ss;
-
-	ss = (struct st_modem_sim_select *) msgb_put(msg, sizeof(*ss));
-	ss->remote_sim = remote_sim;
-
-	return st_slot_tx_msg(slot, msg, SIMTRACE_MSGC_MODEM, SIMTRACE_MSGT_DT_MODEM_SIM_SELECT);
-}
-
-/*! \brief select local (physical) SIM for given slot */
-int st_modem_sim_select_local(struct st_slot *slot)
-{
-	return _modem_sim_select(slot, 0);
-}
-
-/*! \brief select remote (emulated/forwarded) SIM for given slot */
-int st_modem_sim_select_remote(struct st_slot *slot)
-{
-	return _modem_sim_select(slot, 1);
-}
-
-/*! \brief Request slot to send us status information about the modem */
-int st_modem_get_status(struct st_slot *slot)
-{
-	struct msgb *msg = st_msgb_alloc();
-
-	return st_slot_tx_msg(slot, msg, SIMTRACE_MSGC_MODEM, SIMTRACE_MSGT_BD_MODEM_STATUS);
-}
-
-
-/***********************************************************************
- * Incoming Messages
- ***********************************************************************/
-
-/*! \brief Process a STATUS message from the SIMtrace2 */
-static int process_do_status(struct cardem_inst *ci, uint8_t *buf, int len)
-{
-	struct cardemu_usb_msg_status *status;
-	status = (struct cardemu_usb_msg_status *) buf;
-
-	printf("SIMtrace => STATUS: flags=0x%x, fi=%u, di=%u, wi=%u wtime=%u\n",
-		status->flags, status->fi, status->di, status->wi,
-		status->waiting_time);
-
-	return 0;
-}
-
-/*! \brief Process a PTS indication message from the SIMtrace2 */
-static int process_do_pts(struct cardem_inst *ci, uint8_t *buf, int len)
-{
-	struct cardemu_usb_msg_pts_info *pts;
-	pts = (struct cardemu_usb_msg_pts_info *) buf;
-
-	printf("SIMtrace => PTS req: %s\n", osmo_hexdump(pts->req, sizeof(pts->req)));
-
-	return 0;
-}
-
-/*! \brief Process a ERROR indication message from the SIMtrace2 */
-__attribute__((unused)) static int process_do_error(struct cardem_inst *ci, uint8_t *buf, int len)
-{
-	struct cardemu_usb_msg_error *err;
-	err = (struct cardemu_usb_msg_error *) buf;
-
-	printf("SIMtrace => ERROR: %u/%u/%u: %s\n",
-		err->severity, err->subsystem, err->code,
-		err->msg_len ? (char *)err->msg : "");
-
-	return 0;
-}
-
-static struct osmo_apdu_context ac; // this will hold the complete APDU (across calls)
-
-/*! \brief Process a RX-DATA indication message from the SIMtrace2 */
-static int process_do_rx_da(struct cardem_inst *ci, uint8_t *buf, int len)
-{
-	struct cardemu_usb_msg_rx_data *data = (struct cardemu_usb_msg_rx_data *) buf; // cast the data from the USB message
-	int rc;
-
-	printf("SIMtrace => DATA: flags=%x, %s: ", data->flags,
-		osmo_hexdump(data->data, data->data_len));
-
-	rc = osmo_apdu_segment_in(&ac, data->data, data->data_len,
-				  data->flags & CEMU_DATA_F_TPDU_HDR); // parse the APDU data in the USB message
-
-	if (rc & APDU_ACT_TX_CAPDU_TO_CARD) { // there is no pending data coming from the modem
-		uint8_t apdu_command[sizeof(ac.hdr) + ac.lc.tot]; // to store the APDU command to send
-		memcpy(apdu_command, &ac.hdr, sizeof(ac.hdr)); // copy APDU command header
-		if (ac.lc.tot) {
-			memcpy(apdu_command + sizeof(ac.hdr), ac.dc, ac.lc.tot); // copy APDU command data 
-		}
-		// send APDU to card
-		BankSlot_t bslot;
-		bank_slot2rspro(&bslot, &g_client->bankd_slot);
-		RsproPDU_t *pdu = rspro_gen_TpduModem2Card(g_client->srv_conn.clslot, &bslot, apdu_command, sizeof(ac.hdr) + ac.lc.tot); // create RSPRO packet
-		server_conn_send_rspro(&g_client->bankd_conn, pdu);
-		// the response will come separately
-	} else if (ac.lc.tot > ac.lc.cur) { // there is pending data from the modem
-		cardem_request_pb_and_rx(ci, ac.hdr.ins, ac.lc.tot - ac.lc.cur); // send procedure byte to get remaining data
-	}
-	return 0;
-}
-
-#if 0
-	case SIMTRACE_CMD_DO_ERROR
-		rc = process_do_error(ci, buf, len);
-		break;
-#endif
-
-/*! \brief Process an incoming message from the SIMtrace2 */
-static int process_usb_msg(struct cardem_inst *ci, uint8_t *buf, int len)
-{
-	struct simtrace_msg_hdr *sh = (struct simtrace_msg_hdr *)buf;
-	int rc;
-
-	printf("SIMtrace -> %s\n", osmo_hexdump(buf, len));
-
-	buf += sizeof(*sh);
-
-	switch (sh->msg_type) {
-	case SIMTRACE_MSGT_BD_CEMU_STATUS:
-		rc = process_do_status(ci, buf, len);
-		break;
-	case SIMTRACE_MSGT_DO_CEMU_PTS:
-		rc = process_do_pts(ci, buf, len);
-		break;
-	case SIMTRACE_MSGT_DO_CEMU_RX_DATA:
-		rc = process_do_rx_da(ci, buf, len);
-		break;
-	case SIMTRACE_MSGT_BD_CEMU_CONFIG:
-		/* firmware confirms configuration change; ignore */
-		break;
-	default:
-		printf("unknown simtrace msg type 0x%02x\n", sh->msg_type);
-		rc = -1;
-		break;
-	}
-
-	return rc;
-}
-
-
-/*! \brief Process a STATUS message on IRQ endpoint from the SIMtrace2 */
-static int process_irq_status(struct cardem_inst *ci, const uint8_t *buf, int len)
-{
-	const struct cardemu_usb_msg_status *status = (struct cardemu_usb_msg_status *) buf;
-
-	printf("SIMtrace IRQ STATUS: flags=0x%x, fi=%u, di=%u, wi=%u wtime=%u\n",
-		status->flags, status->fi, status->di, status->wi,
-		status->waiting_time);
-
-	BankSlot_t bslot;
-	bank_slot2rspro(&bslot, &g_client->bankd_slot);
-	RsproPDU_t *pdu = rspro_gen_ClientSlotStatusInd(g_client->srv_conn.clslot, &bslot,
-							status->flags & CEMU_STATUS_F_RESET_ACTIVE,
-							status->flags & CEMU_STATUS_F_VCC_PRESENT,
-							status->flags & CEMU_STATUS_F_CLK_ACTIVE,
-							-1 /* FIXME: make this dependent on board */);
-	server_conn_send_rspro(&g_client->bankd_conn, pdu);
-
-	if (ci->last_status.flags != status->flags) {
-		ci->last_status = *status;
-		call_script(g_client, "event-modem-status");
-	} else
-		ci->last_status = *status;
-
-	return 0;
-}
-
-static int process_usb_msg_irq(struct cardem_inst *ci, const uint8_t *buf, unsigned int len)
-{
-	struct simtrace_msg_hdr *sh = (struct simtrace_msg_hdr *)buf;
-	int rc;
-
-	printf("SIMtrace IRQ %s\n", osmo_hexdump(buf, len));
-
-	buf += sizeof(*sh);
-
-	switch (sh->msg_type) {
-	case SIMTRACE_MSGT_BD_CEMU_STATUS:
-		rc = process_irq_status(ci, buf, len);
-		break;
-	default:
-		printf("unknown simtrace msg type 0x%02x\n", sh->msg_type);
-		rc = -1;
-		break;
-	}
-
-	return rc;
-}
-
-static void usb_in_xfer_cb(struct libusb_transfer *xfer)
-{
-	struct cardem_inst *ci = xfer->user_data;
-	int rc;
-
-	switch (xfer->status) {
-	case LIBUSB_TRANSFER_COMPLETED:
-		/* hand the message up the stack */
-		process_usb_msg(ci, xfer->buffer, xfer->actual_length);
-		break;
-	case LIBUSB_TRANSFER_NO_DEVICE:
-		fprintf(stderr, "USB device disappeared\n");
-		g_leave_main = true;
-		break;
-	default:
-		fprintf(stderr, "USB IN transfer failed, status=%u\n", xfer->status);
-		g_leave_main = true;
-		break;
-	}
-
-	/* re-submit the IN transfer */
-	rc = libusb_submit_transfer(xfer);
-	OSMO_ASSERT(rc == 0);
-}
-
-
-static void allocate_and_submit_in(struct cardem_inst *ci)
-{
-	struct st_transport *transp = ci->slot->transp;
-	struct libusb_transfer *xfer;
-	int rc;
-
-	xfer = libusb_alloc_transfer(0);
-	OSMO_ASSERT(xfer);
-	xfer->dev_handle = transp->usb_devh;
-	xfer->flags = 0;
-	xfer->type = LIBUSB_TRANSFER_TYPE_BULK;
-	xfer->endpoint = transp->usb_ep.in;
-	xfer->timeout = 0;
-	xfer->user_data = ci;
-	xfer->length = 16*256;
-
-	xfer->buffer = libusb_dev_mem_alloc(xfer->dev_handle, xfer->length);
-	OSMO_ASSERT(xfer->buffer);
-	xfer->callback = usb_in_xfer_cb;
-
-	/* submit the IN transfer */
-	rc = libusb_submit_transfer(xfer);
-	OSMO_ASSERT(rc == 0);
-}
-
-
-static void usb_irq_xfer_cb(struct libusb_transfer *xfer)
-{
-	struct cardem_inst *ci = xfer->user_data;
-	int rc;
-
-	switch (xfer->status) {
-	case LIBUSB_TRANSFER_COMPLETED:
-		process_usb_msg_irq(ci, xfer->buffer, xfer->actual_length);
-		break;
-	case LIBUSB_TRANSFER_NO_DEVICE:
-		fprintf(stderr, "USB device disappeared\n");
-		g_leave_main = true;
-		break;
-	default:
-		fprintf(stderr, "USB IRQ transfer failed, status=%u\n", xfer->status);
-		g_leave_main = true;
-		break;
-	}
-
-	/* re-submit the IN transfer */
-	rc = libusb_submit_transfer(xfer);
-	OSMO_ASSERT(rc == 0);
-}
-
-
-static void allocate_and_submit_irq(struct cardem_inst *ci)
-{
-	struct st_transport *transp = ci->slot->transp;
-	struct libusb_transfer *xfer;
-	int rc;
-
-	xfer = libusb_alloc_transfer(0);
-	OSMO_ASSERT(xfer);
-	xfer->dev_handle = transp->usb_devh;
-	xfer->flags = 0;
-	xfer->type = LIBUSB_TRANSFER_TYPE_INTERRUPT;
-	xfer->endpoint = transp->usb_ep.irq_in;
-	xfer->timeout = 0;
-	xfer->user_data = ci;
-	xfer->length = 64;
-
-	xfer->buffer = libusb_dev_mem_alloc(xfer->dev_handle, xfer->length);
-	OSMO_ASSERT(xfer->buffer);
-	xfer->callback = usb_irq_xfer_cb;
-
-	/* submit the IN transfer */
-	rc = libusb_submit_transfer(xfer);
-	OSMO_ASSERT(rc == 0);
-}
-
-
-static struct st_transport _transp;
-
-static struct st_slot _slot = {
-	.transp = &_transp,
-	.slot_nr = 0,
-};
-
-static struct cardem_inst *g_ci;
-
-static void signal_handler(int signal)
-{
-	switch (signal) {
-	case SIGINT:
-		cardem_request_card_insert(g_ci, false);
-		exit(0);
-		break;
-	default:
-		break;
-	}
-}
-
-/** remsim_client **/
-
-static int bankd_handle_tpduCardToModem(struct bankd_client *bc, const RsproPDU_t *pdu)
-{
-	OSMO_ASSERT(pdu);
-	OSMO_ASSERT(RsproPDUchoice_PR_tpduCardToModem == pdu->msg.present);
-
-	const struct TpduCardToModem *card2modem = &pdu->msg.choice.tpduCardToModem;
-	if (card2modem->data.size < 2) { // at least the two SW bytes are needed
-		return -1;
-	}
-
-	// save SW to our current APDU context
-	ac.sw[0] = card2modem->data.buf[card2modem->data.size - 2];
-	ac.sw[1] = card2modem->data.buf[card2modem->data.size - 1];
-	printf("SIMtrace <= SW=0x%02x%02x, len_rx=%d\n", ac.sw[0], ac.sw[1], card2modem->data.size - 2);
-	if (card2modem->data.size > 2) { // send PB and data to modem
-		cardem_request_pb_and_tx(bc->cardem, ac.hdr.ins, card2modem->data.buf, card2modem->data.size - 2);
-	}
-	cardem_request_sw_tx(bc->cardem, ac.sw); // send SW to modem
-
-	return 0;
-}
-
-static int bankd_handle_setAtrReq(struct bankd_client *bc, const RsproPDU_t *pdu)
-{
-	RsproPDU_t *resp;
-	int rc;
-
-	OSMO_ASSERT(pdu);
-	OSMO_ASSERT(RsproPDUchoice_PR_setAtrReq == pdu->msg.present);
-
-	/* FIXME: is this permitted at any time by the SIMtrace2 cardemfirmware? */
-	rc = cardem_request_set_atr(bc->cardem, pdu->msg.choice.setAtrReq.atr.buf,
-				    pdu->msg.choice.setAtrReq.atr.size);
-	if (rc == 0)
-		resp = rspro_gen_SetAtrRes(ResultCode_ok);
-	else
-		resp = rspro_gen_SetAtrRes(ResultCode_cardTransmissionError);
-	if (!resp)
-		return -ENOMEM;
-	server_conn_send_rspro(&g_client->bankd_conn, resp);
-
-	return 0;
-}
-
-/* handle incoming message from bankd */
-static int bankd_handle_rx(struct rspro_server_conn *bankdc, const RsproPDU_t *pdu)
-{
-	switch (pdu->msg.present) {
-	case RsproPDUchoice_PR_connectClientRes:
-		/* Store 'identity' of bankd to in peer_comp_id */
-		rspro_comp_id_retrieve(&bankdc->peer_comp_id, &pdu->msg.choice.connectClientRes.identity);
-		osmo_fsm_inst_dispatch(bankdc->fi, SRVC_E_CLIENT_CONN_RES, (void *) pdu);
-		call_script(g_client, "event-bankd-connect");
-		break;
-	case RsproPDUchoice_PR_tpduCardToModem: // APDU response from card received
-		bankd_handle_tpduCardToModem(g_client, pdu);
-		break;
-	case RsproPDUchoice_PR_setAtrReq:
-		bankd_handle_setAtrReq(g_client, pdu);
-		break;
-	default:
-		LOGPFSML(bankdc->fi, LOGL_ERROR, "Unknown/Unsuppoerted RSPRO PDU %s\n",
-			 rspro_msgt_name(pdu));
-		return -1;
-	}
-
-	return 0;
-}
-
-/* handle incoming messages from server */
-static int srvc_handle_rx(struct rspro_server_conn *srvc, const RsproPDU_t *pdu)
-{
-	RsproPDU_t  *resp;
-
-	switch (pdu->msg.present) {
-	case RsproPDUchoice_PR_connectClientRes:
-		/* Store 'identity' of server in srvc->peer_comp_id */
-		rspro_comp_id_retrieve(&srvc->peer_comp_id, &pdu->msg.choice.connectClientRes.identity);
-		osmo_fsm_inst_dispatch(srvc->fi, SRVC_E_CLIENT_CONN_RES, (void *) pdu);
-		call_script(g_client, "event-server-connect");
-		break;
-	case RsproPDUchoice_PR_configClientIdReq:
-		/* store/set the clientID as instructed by the server */
-		if (!g_client->srv_conn.clslot)
-			g_client->srv_conn.clslot = talloc_zero(g_client, ClientSlot_t);
-		*g_client->srv_conn.clslot = pdu->msg.choice.configClientIdReq.clientSlot;
-		if (!g_client->bankd_conn.clslot)
-			g_client->bankd_conn.clslot = talloc_zero(g_client, ClientSlot_t);
-		*g_client->bankd_conn.clslot = *g_client->srv_conn.clslot;
-		/* send response to server */
-		resp = rspro_gen_ConfigClientIdRes(ResultCode_ok);
-		server_conn_send_rspro(srvc, resp);
-		break;
-	case RsproPDUchoice_PR_configClientBankReq:
-		/* store/set the bankd ip/port as instructed by the server */
-		osmo_talloc_replace_string(g_client, &g_client->bankd_conn.server_host,
-					   rspro_IpAddr2str(&pdu->msg.choice.configClientBankReq.bankd.ip));
-		rspro2bank_slot(&g_client->bankd_slot, &pdu->msg.choice.configClientBankReq.bankSlot);
-		g_client->bankd_conn.server_port = pdu->msg.choice.configClientBankReq.bankd.port;
-		/* bankd port 0 is a magic value to indicate "no bankd" */
-		if (g_client->bankd_conn.server_port == 0)
-			osmo_fsm_inst_dispatch(g_client->bankd_conn.fi, SRVC_E_DISCONNECT, NULL);
-		else
-			osmo_fsm_inst_dispatch(g_client->bankd_conn.fi, SRVC_E_ESTABLISH, NULL);
-		/* send response to server */
-		resp = rspro_gen_ConfigClientBankRes(ResultCode_ok);
-		server_conn_send_rspro(srvc, resp);
-		call_script(g_client, "event-config-bankd");
-		break;
-	default:
-		LOGPFSML(srvc->fi, LOGL_ERROR, "Unknown/Unsupported RSPRO PDU type: %s\n",
-			 rspro_msgt_name(pdu));
-		return -1;
-	}
-
-	return 0;
-}
-
-static void handle_sig_usr1(int signal)
-{
-	OSMO_ASSERT(signal == SIGUSR1);
-	talloc_report_full(g_tall_ctx, stderr);
-	printf("===== NULL\n");
-	talloc_report_full(NULL, stderr);
-}
-
-static void print_welcome(void)
-{
-	printf("simtrace2-remsim-client - Remote SIM card client for SIMtrace\n"
-	       "(C) 2010-2020, Harald Welte <laforge at gnumonks.org>\n"
-	       "(C) 2018, sysmocom -s.f.m.c. GmbH, Author: Kevin Redon <kredon at sysmocom.de>\n\n");
-}
-
-static void print_help(void)
-{
-	printf( "\t-s\t--server-host HOST\n"
-		"\t-p\t--server-port PORT\n"
-		"\t-c\t--client-id <0-65535>\n"
-		"\t-n\t--client-slot <0-65535>\n"
-		"\t-h\t--help\n"
-		"\t-v\t--version\n"
-		"\t-i\t--gsmtap-ip\tA.B.C.D\n"
-		"\t-k\t--keep-running\n"
-		"\t-V\t--usb-vendor\tVENDOR_ID\n"
-		"\t-P\t--usb-product\tPRODUCT_ID\n"
-		"\t-C\t--usb-config\tCONFIG_ID\n"
-		"\t-I\t--usb-interface\tINTERFACE_ID\n"
-		"\t-S\t--usb-altsetting ALTSETTING_ID\n"
-		"\t-A\t--usb-address\tADDRESS\n"
-		"\t-H\t--usb-path\tPATH\n"
-		"\t-a\t--atr\tATR\n"
-		"\t-e\t--event-script\tPATH\n"
-		"\n"
-		);
-}
-
-static struct client_config *client_config_init(void *ctx)
-{
-	struct client_config *cfg = talloc_zero(ctx, struct client_config);
-	if (!cfg)
-		return NULL;
-
-	cfg->server_host = talloc_strdup(cfg, "127.0.0.1");
-	cfg->server_port = 9998;
-	cfg->client_id = -1;
-	cfg->client_slot = -1;
-	cfg->gsmtap_host = talloc_strdup(cfg, "127.0.0.1");
-	cfg->keep_running = false;
-
-	cfg->usb.vendor_id = -1;
-	cfg->usb.product_id = -1;
-	cfg->usb.config_id = -1;
-	cfg->usb.if_num = -1;
-	cfg->usb.altsetting = 0;
-	cfg->usb.addr = -1;
-	cfg->usb.path = NULL;
-
-	cfg->atr.data[0] = 0x3B;
-	cfg->atr.data[1] = 0x00; // the shortest simplest ATR possible
-	cfg->atr.len = 2;
-
-	return cfg;
-};
-
-static void handle_options(struct client_config *cfg, int argc, char **argv)
-{
-	const struct option opts[] = {
-		{ "server-host", 1, 0, 's' },
-		{ "server-port", 1, 0, 'p' },
-		{ "client-id", 1, 0, 'c' },
-		{ "client-slot", 1, 0, 'n' },
-		{ "help", 0, 0, 'h' },
-		{ "version", 0, 0, 'v' },
-		{ "gsmtap-ip", 1, 0, 'i' },
-		{ "keep-running", 0, 0, 'k' },
-		{ "usb-vendor", 1, 0, 'V' },
-		{ "usb-product", 1, 0, 'P' },
-		{ "usb-config", 1, 0, 'C' },
-		{ "usb-interface", 1, 0, 'I' },
-		{ "usb-altsetting", 1, 0, 'S' },
-		{ "usb-address", 1, 0, 'A' },
-		{ "usb-path", 1, 0, 'H' },
-		{ "atr", 1, 0, 'a' },
-		{ "event-script", 1, 0, 'e' },
-		{ NULL, 0, 0, 0 }
-	};
-	int c, rc;
-
-	while (1) {
-		int option_index = 0;
-
-		c = getopt_long(argc, argv, "s:p:c:n:hvi:kV:P:C:I:S:A:H:a:e:", opts, &option_index);
-		if (c == -1)
-			break;
-		switch (c) {
-		case 's':
-			osmo_talloc_replace_string(cfg, &cfg->server_host, optarg);
-			break;
-		case 'p':
-			cfg->server_port = atoi(optarg);
-			break;
-		case 'c':
-			cfg->client_id = atoi(optarg);
-			break;
-		case 'n':
-			cfg->client_slot = atoi(optarg);
-			break;
-		case 'h':
-			print_help();
-			exit(0);
-			break;
-		case 'v':
-			printf("osmo-remsim-client version %s\n", VERSION);
-			exit(0);
-			break;
-		case 'i':
-			osmo_talloc_replace_string(cfg, &cfg->gsmtap_host, optarg);
-			break;
-		case 'k':
-			cfg->keep_running = 1;
-			break;
-		case 'V':
-			cfg->usb.vendor_id = strtol(optarg, NULL, 16);
-			break;
-		case 'P':
-			cfg->usb.product_id = strtol(optarg, NULL, 16);
-			break;
-		case 'C':
-			cfg->usb.config_id = atoi(optarg);
-			break;
-		case 'I':
-			cfg->usb.if_num = atoi(optarg);
-			break;
-		case 'S':
-			cfg->usb.altsetting = atoi(optarg);
-			break;
-		case 'A':
-			cfg->usb.addr = atoi(optarg);
-			break;
-		case 'H':
-			cfg->usb.path = optarg;
-			break;
-		case 'a':
-			rc = osmo_hexparse(optarg, cfg->atr.data, ARRAY_SIZE(cfg->atr.data));
-			if (rc < 2 || rc > ARRAY_SIZE(cfg->atr.data)) {
-				fprintf(stderr, "ATR malformed\n");
-				exit(2);
-			}
-			cfg->atr.len = rc;
-			break;
-		case 'e':
-			osmo_talloc_replace_string(cfg, &cfg->event_script, optarg);
-			break;
-		}
-	}
-
-	if (argc > optind) {
-		fprintf(stderr, "Unsupported positional arguments on command line\n");
-		exit(2);
-	}
-}
-
-
-static void main_body(struct cardem_inst *ci, struct client_config *cfg)
-{
-	struct st_transport *transp = ci->slot->transp;
-	struct usb_interface_match _ifm, *ifm = &_ifm;
-	int rc, i;
-
-	ifm->vendor = cfg->usb.vendor_id;
-	ifm->product = cfg->usb.product_id;
-	ifm->configuration = cfg->usb.config_id;
-	ifm->interface = cfg->usb.if_num;
-	ifm->altsetting = cfg->usb.altsetting;
-	ifm->addr = cfg->usb.addr;
-	if (cfg->usb.path)
-		osmo_strlcpy(ifm->path, cfg->usb.path, sizeof(ifm->path));
-	transp->usb_devh = osmo_libusb_open_claim_interface(NULL, NULL, ifm);
-	if (!transp->usb_devh) {
-		fprintf(stderr, "can't open USB device\n");
-		return;
-	}
-
-	/* (re)determine the USB path of the opened device */
-	talloc_free(ci->usb_path);
-	ci->usb_path = osmo_libusb_dev_get_path_c(ci, libusb_get_device(transp->usb_devh));
-
-	rc = libusb_claim_interface(transp->usb_devh, cfg->usb.if_num);
-	if (rc < 0) {
-		fprintf(stderr, "can't claim interface %d; rc=%d\n", cfg->usb.if_num, rc);
-		goto close_exit;
-	}
-
-	rc = osmo_libusb_get_ep_addrs(transp->usb_devh, cfg->usb.if_num, &transp->usb_ep.out,
-					&transp->usb_ep.in, &transp->usb_ep.irq_in);
-	if (rc < 0) {
-		fprintf(stderr, "can't obtain EP addrs; rc=%d\n", rc);
-		goto close_exit;
-	}
-
-	// switch modem SIM port to emulated SIM on OWHW
-	if (USB_VENDOR_OPENMOKO == ifm->vendor && USB_PRODUCT_OWHW_SAM3 == ifm->product) { // we are on the OWHW
-		int modem = -1;
-		switch (ifm->interface) { // the USB interface indicates for which modem we want to emulate the SIM
-		case 0:
-			modem = 1;
-			break;
-		case 1:
-			modem = 2;
-			break;
-		default:
-			fprintf(stderr, "unknown GPIO for SIMtrace interface %d\n", ifm->interface);
-			goto close_exit;
-		}
-		//
-		char gpio_path[PATH_MAX];
-		snprintf(gpio_path, sizeof(gpio_path), "/dev/gpio/connect_st_usim%d/value", modem);
-		int connec_st_usim = open(gpio_path, O_WRONLY);
-		if (-1 == connec_st_usim) {
-			fprintf(stderr, "can't open GPIO %s to switch modem %d to emulated USIM\n", gpio_path, modem);
-			goto close_exit;
-		}
-		if (1 != write(connec_st_usim, "1", 1)) {
-			fprintf(stderr, "can't write GPIO %s to switch modem %d to emulated USIM\n", gpio_path, modem);
-			goto close_exit;
-		}
-		printf("switched modem %d to emulated USIM\n", modem);
-
-		snprintf(gpio_path, sizeof(gpio_path), "/dev/gpio/mdm%d_rst/value", modem);
-		int mdm_rst = open(gpio_path, O_WRONLY);
-		if (-1 == mdm_rst) {
-			fprintf(stderr, "can't open GPIO %s to reset modem %d\n", gpio_path, modem);
-			goto close_exit;
-		}
-		if (1 != write(mdm_rst, "1", 1)) {
-			fprintf(stderr, "can't write GPIO %s to reset modem %d\n", gpio_path, modem);
-			goto close_exit;
-		}
-		sleep(1); // wait a bit to ensure reset is effective
-		if (1 != write(mdm_rst, "0", 1)) {
-			fprintf(stderr, "can't write GPIO %s to reset modem %d\n", gpio_path, modem);
-			goto close_exit;
-		}
-		printf("modem %d reset\n", modem);
-	}
-
-	/* request firmware to generate STATUS on IRQ endpoint */
-	cardem_request_config(ci, CEMU_FEAT_F_STATUS_IRQ);
-
-	/* simulate card-insert to modem (owhw, not qmod) */
-	cardem_request_card_insert(ci, true);
-	call_script(g_client, "request-card-insert");
-
-	/* select remote (forwarded) SIM */
-	st_modem_sim_select_remote(ci->slot);
-	call_script(g_client, "request-sim-remote");
-
-	/* set the ATR */
-	//atr_update_csum(real_atr, sizeof(real_atr));
-	cardem_request_set_atr(ci, cfg->atr.data, cfg->atr.len);
-
-	/* select remote (forwarded) SIM */
-	st_modem_reset_pulse(ci->slot, 300);
-	call_script(g_client, "request-modem-reset");
-
-	printf("Entering main loop\n");
-
-	allocate_and_submit_irq(ci);
-	/* submit multiple IN URB in order to work around OS#4409 */
-	for (i = 0; i < 4; i++)
-		allocate_and_submit_in(ci);
-
-	while (!g_leave_main) {
-		osmo_select_main(false);
-	}
-	g_leave_main = false;
-
-	libusb_release_interface(transp->usb_devh, 0);
-
-close_exit:
-	if (transp->usb_devh)
-		libusb_close(transp-> usb_devh);
-}
-
-int main(int argc, char **argv)
-{
-	struct rspro_server_conn *srvc, *bankdc;
-	struct client_config *cfg;
-	int rc;
-	int ret = 1;
-
-	print_welcome();
-
-	talloc_enable_null_tracking();
-	g_tall_ctx = talloc_named_const(NULL, 0, "global");
-	talloc_asn1_ctx = talloc_named_const(g_tall_ctx, 0, "asn1");
-	msgb_talloc_ctx_init(g_tall_ctx, 0);
-	osmo_init_logging2(g_tall_ctx, &log_info);
-
-	g_ci = talloc_zero(g_tall_ctx, struct cardem_inst);
-	g_ci->slot = &_slot;
-
-	cfg = client_config_init(g_ci);
-	handle_options(cfg, argc, argv);
-
-	if (cfg->usb.vendor_id < 0 || cfg->usb.product_id < 0) {
-		fprintf(stderr, "You have to specify the vendor and product ID\n");
-		goto do_exit;
-	}
-
-	signal(SIGUSR1, handle_sig_usr1);
-
-	rc = osmo_libusb_init(NULL);
-	if (rc < 0) {
-		fprintf(stderr, "libusb initialization failed\n");
-		goto do_exit;
-	}
-
-	g_gti = gsmtap_source_init(cfg->gsmtap_host, GSMTAP_UDP_PORT, 0);
-	if (!g_gti) {
-		perror("unable to open GSMTAP");
-		goto close_exit;
-	}
-	gsmtap_source_add_sink(g_gti);
-
-	signal(SIGINT, &signal_handler);
-
-	// initialize remote SIM client
-
-	g_client = talloc_zero(g_tall_ctx, struct bankd_client);
-	g_client->cfg = cfg;
-	g_client->cardem = g_ci;
-
-	if (cfg->client_id != -1) {
-		g_client->srv_conn.clslot = talloc_zero(g_client, ClientSlot_t);
-		g_client->srv_conn.clslot->clientId = cfg->client_id;
-		/* default to client slot 0 */
-		if (cfg->client_slot == -1)
-			g_client->srv_conn.clslot->slotNr = 0;
-		else
-			g_client->srv_conn.clslot->slotNr = cfg->client_slot;
-		g_client->bankd_conn.clslot = talloc_zero(g_client, ClientSlot_t);
-		*g_client->bankd_conn.clslot = *g_client->srv_conn.clslot;
-	}
-
-	/* create and [attempt to] establish connection to remsim-server */
-	srvc = &g_client->srv_conn;
-	srvc->server_host = cfg->server_host;
-	srvc->server_port = cfg->server_port;
-	srvc->handle_rx = srvc_handle_rx;
-	srvc->own_comp_id.type = ComponentType_remsimClient;
-	OSMO_STRLCPY_ARRAY(srvc->own_comp_id.name, "simtrace2-remsim-client");
-	OSMO_STRLCPY_ARRAY(srvc->own_comp_id.software, "remsim-client");
-	OSMO_STRLCPY_ARRAY(srvc->own_comp_id.sw_version, PACKAGE_VERSION);
-	rc = server_conn_fsm_alloc(g_client, srvc);
-	if (rc < 0) {
-		fprintf(stderr, "Unable to create Server conn FSM: %s\n", strerror(errno));
-		exit(1);
-	}
-	osmo_fsm_inst_dispatch(srvc->fi, SRVC_E_ESTABLISH, NULL);
-
-	/* create and not yet establish connection to remsim-bankd */
-	srvc = &g_client->srv_conn;
-	bankdc = &g_client->bankd_conn;
-	/* server_host / server_port are configured from remsim-server */
-	bankdc->handle_rx = bankd_handle_rx;
-	memcpy(&bankdc->own_comp_id, &srvc->own_comp_id, sizeof(bankdc->own_comp_id));
-	rc = server_conn_fsm_alloc(g_client, bankdc);
-	if (rc < 0) {
-		fprintf(stderr, "Unable to create bankd conn FSM: %s\n", strerror(errno));
-		exit(1);
-	}
-	osmo_fsm_inst_update_id(bankdc->fi, "bankd");
-
-	asn_debug = 0;
-
-	// connect to SIMtrace2 cardem
-	do {
-		main_body(g_ci, cfg);
-		sleep(1);
-	} while (cfg->keep_running);
-
-close_exit:
-	osmo_libusb_exit(NULL);
-do_exit:
-	return ret;
-}
diff --git a/src/client/user_ifdhandler.c b/src/client/user_ifdhandler.c
index 677b781..188d3a6 100644
--- a/src/client/user_ifdhandler.c
+++ b/src/client/user_ifdhandler.c
@@ -148,70 +148,66 @@
 }
 
 /***********************************************************************
- * Incoming RSPRO messages from bank-daemon (SIM card)
+ * frontend to remsim-client main FSM code
  ***********************************************************************/
 
-static int bankd_handle_tpduCardToModem(struct bankd_client *bc, const RsproPDU_t *pdu)
+int frontend_request_card_insert(struct bankd_client *bc)
 {
-	const struct TpduCardToModem *card2modem;
+	return 0;
+}
+
+int frontend_request_sim_remote(struct bankd_client *bc)
+{
+	return 0;
+}
+
+int frontend_request_modem_reset(struct bankd_client *bc)
+{
+	return 0;
+}
+
+int frontend_handle_card2modem(struct bankd_client *bc, const uint8_t *data, size_t len)
+{
 	struct client_thread *ct = bc->data;
 	struct msgb *msg;
 
-	OSMO_ASSERT(pdu);
-	OSMO_ASSERT(RsproPDUchoice_PR_tpduCardToModem == pdu->msg.present);
+	OSMO_ASSERT(data);
 
-	card2modem = &pdu->msg.choice.tpduCardToModem;
-	DEBUGP(DMAIN, "R-APDU: %s\n", osmo_hexdump(card2modem->data.buf, card2modem->data.size));
+	DEBUGP(DMAIN, "R-APDU: %s\n", osmo_hexdump(data, len));
 	/* enqueue towards IFD thread */
-	msg = itmsg_alloc(ITMSG_TYPE_R_APDU_IND, 0, card2modem->data.buf, card2modem->data.size);
+	msg = itmsg_alloc(ITMSG_TYPE_R_APDU_IND, 0, data, len);
 	OSMO_ASSERT(msg);
 	enqueue_to_ifd(ct, msg);
 
 	return 0;
 }
 
-static int bankd_handle_setAtrReq(struct bankd_client *bc, const RsproPDU_t *pdu)
+int frontend_handle_set_atr(struct bankd_client *bc, const uint8_t *data, size_t len)
 {
 	struct client_thread *ct = bc->data;
-	RsproPDU_t *resp;
 	unsigned int atr_len;
 
-	OSMO_ASSERT(pdu);
-	OSMO_ASSERT(RsproPDUchoice_PR_setAtrReq == pdu->msg.present);
+	OSMO_ASSERT(data);
 
-	DEBUGP(DMAIN, "SET_ATR: %s\n", osmo_hexdump(pdu->msg.choice.setAtrReq.atr.buf,
-						     pdu->msg.choice.setAtrReq.atr.size));
+	DEBUGP(DMAIN, "SET_ATR: %s\n", osmo_hexdump(data, len));
 
 	/* store ATR in local data structure until somebody needs it */
-	atr_len = pdu->msg.choice.setAtrReq.atr.size;
+	atr_len = len;
 	if (atr_len > sizeof(ct->atr))
 		atr_len = sizeof(ct->atr);
-	memcpy(ct->atr, pdu->msg.choice.setAtrReq.atr.buf, atr_len);
+	memcpy(ct->atr, data, atr_len);
 	ct->atr_len = atr_len;
 
-	resp = rspro_gen_SetAtrRes(ResultCode_ok);
-	if (!resp)
-		return -ENOMEM;
-	server_conn_send_rspro(&bc->bankd_conn, resp);
-
 	return 0;
 }
 
-
-int client_user_bankd_handle_rx(struct rspro_server_conn *bankdc, const RsproPDU_t *pdu)
+int frontend_handle_slot_status(struct bankd_client *bc, const SlotPhysStatus_t *sts)
 {
-	struct bankd_client *bc = bankdc2bankd_client(bankdc);
+	return 0;
+}
 
-	switch (pdu->msg.present) {
-	case RsproPDUchoice_PR_tpduCardToModem:
-		bankd_handle_tpduCardToModem(bc, pdu);
-		break;
-	case RsproPDUchoice_PR_setAtrReq:
-		bankd_handle_setAtrReq(bc, pdu);
-		break;
-	default:
-		OSMO_ASSERT(0);
-	}
+int frontend_append_script_env(struct bankd_client *bc, char **env, size_t max_env)
+{
 	return 0;
 }
 
@@ -363,6 +359,7 @@
 static void *client_pthread_main(void *arg)
 {
 	struct client_thread_cfg *cfg = arg;
+	struct client_config *ccfg;
 	struct client_thread *ct;
 	int rc;
 
@@ -373,17 +370,20 @@
 	ct = talloc_zero(OTC_GLOBAL, struct client_thread);
 	OSMO_ASSERT(ct);
 
+	ccfg = client_config_init(ct);
+	OSMO_ASSERT(ccfg);
+	osmo_talloc_replace_string(ccfg, &ccfg->server_host, cfg->server_host);
+	if (cfg->server_port >= 0)
+		ccfg->server_port = cfg->server_port;
+	ccfg->client_id = cfg->client_id;
+	ccfg->client_slot = cfg->client_slot;
+
 	if (!talloc_asn1_ctx)
 	       talloc_asn1_ctx= talloc_named_const(ct, 0, "asn1");
 
-	ct->bc = remsim_client_create(ct, cfg->name, "remsim_ifdhandler");
+	ct->bc = remsim_client_create(ct, cfg->name, "remsim_ifdhandler", ccfg);
 	OSMO_ASSERT(ct->bc);
 	ct->bc->data = ct;
-	remsim_client_set_clslot(ct->bc, cfg->client_id, cfg->client_slot);
-	if (cfg->server_host)
-		ct->bc->srv_conn.server_host = (char *) cfg->server_host;
-	if (cfg->server_port >= 0)
-		ct->bc->srv_conn.server_port = cfg->server_port;
 
 	INIT_LLIST_HEAD(&ct->it_msgq);
 	osmo_fd_setup(&ct->it_ofd, cfg->it_sock_fd, OSMO_FD_READ, &it_sock_fd_cb, ct, 0);
diff --git a/src/client/user_shell.c b/src/client/user_shell.c
index abe1542..40bcddd 100644
--- a/src/client/user_shell.c
+++ b/src/client/user_shell.c
@@ -12,58 +12,54 @@
 
 
 /***********************************************************************
- * Incoming RSPRO messages from bank-daemon (SIM card)
+ * stdin frontend code to remsim-client
  ***********************************************************************/
 
-static int bankd_handle_tpduCardToModem(struct bankd_client *bc, const RsproPDU_t *pdu)
+int frontend_request_card_insert(struct bankd_client *bc)
 {
-	OSMO_ASSERT(pdu);
-	OSMO_ASSERT(RsproPDUchoice_PR_tpduCardToModem == pdu->msg.present);
+	return 0;
+}
 
-	const struct TpduCardToModem *card2modem = &pdu->msg.choice.tpduCardToModem;
+int frontend_request_sim_remote(struct bankd_client *bc)
+{
+	return 0;
+}
 
-	printf("R-APDU: %s\n", osmo_hexdump(card2modem->data.buf, card2modem->data.size));
+int frontend_request_modem_reset(struct bankd_client *bc)
+{
+	return 0;
+}
+
+int frontend_handle_card2modem(struct bankd_client *bc, const uint8_t *data, size_t len)
+{
+	OSMO_ASSERT(data);
+	printf("R-APDU: %s\n", osmo_hexdump(data, len));
 	fflush(stdout);
 
 	return 0;
 }
 
-static int bankd_handle_setAtrReq(struct bankd_client *bc, const RsproPDU_t *pdu)
+int frontend_handle_set_atr(struct bankd_client *bc, const uint8_t *data, size_t len)
 {
-	RsproPDU_t *resp;
+	OSMO_ASSERT(data);
 
-	OSMO_ASSERT(pdu);
-	OSMO_ASSERT(RsproPDUchoice_PR_setAtrReq == pdu->msg.present);
-
-	printf("SET_ATR: %s\n", osmo_hexdump(pdu->msg.choice.setAtrReq.atr.buf,
-					     pdu->msg.choice.setAtrReq.atr.size));
+	printf("SET_ATR: %s\n", osmo_hexdump(data, len));
 	fflush(stdout);
 
-	resp = rspro_gen_SetAtrRes(ResultCode_ok);
-	if (!resp)
-		return -ENOMEM;
-	server_conn_send_rspro(&bc->bankd_conn, resp);
-
 	return 0;
 }
 
-
-int client_user_bankd_handle_rx(struct rspro_server_conn *bankdc, const RsproPDU_t *pdu)
+int frontend_handle_slot_status(struct bankd_client *bc, const SlotPhysStatus_t *sts)
 {
-	struct bankd_client *client = bankdc2bankd_client(bankdc);
-	switch (pdu->msg.present) {
-	case RsproPDUchoice_PR_tpduCardToModem:
-		bankd_handle_tpduCardToModem(client, pdu);
-		break;
-	case RsproPDUchoice_PR_setAtrReq:
-		bankd_handle_setAtrReq(client, pdu);
-		break;
-	default:
-		OSMO_ASSERT(0);
-	}
 	return 0;
 }
 
+int frontend_append_script_env(struct bankd_client *bc, char **env, size_t max_env)
+{
+	return 0;
+}
+
+
 /***********************************************************************
  * Incoming command from the user application (stdin shell in our case)
  ***********************************************************************/
diff --git a/src/client/user_simtrace2.c b/src/client/user_simtrace2.c
new file mode 100644
index 0000000..24a8d09
--- /dev/null
+++ b/src/client/user_simtrace2.c
@@ -0,0 +1,458 @@
+/* (C) 2018-2020 by Harald Welte <laforge at gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * 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 <stdio.h>
+#include <errno.h>
+
+#include <libusb.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/usb/libusb.h>
+
+#include <osmocom/simtrace2/apdu_dispatch.h>
+#include <osmocom/simtrace2/simtrace2_api.h>
+#include <osmocom/simtrace2/simtrace_prot.h>
+
+#include "client.h"
+#include "debug.h"
+
+
+/***********************************************************************
+ * Incoming Messages from cardem firmware
+ ***********************************************************************/
+
+/*! \brief Process a STATUS message from the SIMtrace2 */
+static int process_do_status(struct osmo_st2_cardem_inst *ci, uint8_t *buf, int len)
+{
+	struct cardemu_usb_msg_status *status;
+	status = (struct cardemu_usb_msg_status *) buf;
+
+	printf("SIMtrace => STATUS: flags=0x%x, fi=%u, di=%u, wi=%u wtime=%u\n",
+		status->flags, status->fi, status->di, status->wi,
+		status->waiting_time);
+
+	return 0;
+}
+
+/*! \brief Process a PTS indication message from the SIMtrace2 */
+static int process_do_pts(struct osmo_st2_cardem_inst *ci, uint8_t *buf, int len)
+{
+	struct cardemu_usb_msg_pts_info *pts = (struct cardemu_usb_msg_pts_info *) buf;
+	struct bankd_client *bc = ci->priv;
+	struct frontend_pts fpts = {
+		.buf = pts->req,
+		.len = sizeof(pts->req),
+	};
+
+	printf("SIMtrace => PTS req: %s\n", osmo_hexdump(pts->req, sizeof(pts->req)));
+
+	osmo_fsm_inst_dispatch(bc->main_fi, MF_E_MDM_PTS_IND, &fpts);
+
+	return 0;
+}
+
+/*! \brief Process a ERROR indication message from the SIMtrace2 */
+__attribute__((unused)) static int process_do_error(struct osmo_st2_cardem_inst *ci, uint8_t *buf, int len)
+{
+	struct cardemu_usb_msg_error *err;
+	err = (struct cardemu_usb_msg_error *) buf;
+
+	printf("SIMtrace => ERROR: %u/%u/%u: %s\n",
+		err->severity, err->subsystem, err->code,
+		err->msg_len ? (char *)err->msg : "");
+
+	return 0;
+}
+
+static struct osmo_apdu_context ac; // this will hold the complete APDU (across calls)
+
+/*! \brief Process a RX-DATA indication message from the SIMtrace2 */
+static int process_do_rx_da(struct osmo_st2_cardem_inst *ci, uint8_t *buf, int len)
+{
+	struct cardemu_usb_msg_rx_data *data = (struct cardemu_usb_msg_rx_data *) buf;
+	struct bankd_client *bc = ci->priv;
+	struct frontend_tpdu ftpdu;
+	int rc;
+
+	printf("SIMtrace => DATA: flags=%x, %s: ", data->flags,
+		osmo_hexdump(data->data, data->data_len));
+
+ 	/* parse the APDU data in the USB message */
+	rc = osmo_apdu_segment_in(&ac, data->data, data->data_len,
+				  data->flags & CEMU_DATA_F_TPDU_HDR);
+
+	if (rc & APDU_ACT_TX_CAPDU_TO_CARD) {
+		/* there is no pending data coming from the modem */
+		uint8_t apdu_command[sizeof(ac.hdr) + ac.lc.tot];
+		memcpy(apdu_command, &ac.hdr, sizeof(ac.hdr));
+		if (ac.lc.tot)
+			memcpy(apdu_command + sizeof(ac.hdr), ac.dc, ac.lc.tot);
+		/* send APDU to card */
+		ftpdu.buf = apdu_command;
+		ftpdu.len = sizeof(ac.hdr) + ac.lc.tot;
+		osmo_fsm_inst_dispatch(bc->main_fi, MF_E_BANKD_TPDU, &ftpdu);
+	} else if (ac.lc.tot > ac.lc.cur) {
+		/* there is pending data from the modem: send procedure byte to get remaining data */
+		osmo_st2_cardem_request_pb_and_rx(ci, ac.hdr.ins, ac.lc.tot - ac.lc.cur);
+	}
+	return 0;
+}
+
+#if 0
+	case SIMTRACE_CMD_DO_ERROR
+		rc = process_do_error(ci, buf, len);
+		break;
+#endif
+
+/*! \brief Process an incoming message from the SIMtrace2 */
+static int process_usb_msg(struct osmo_st2_cardem_inst *ci, uint8_t *buf, int len)
+{
+	struct simtrace_msg_hdr *sh = (struct simtrace_msg_hdr *)buf;
+	int rc;
+
+	printf("SIMtrace -> %s\n", osmo_hexdump(buf, len));
+
+	buf += sizeof(*sh);
+
+	switch (sh->msg_type) {
+	case SIMTRACE_MSGT_BD_CEMU_STATUS:
+		rc = process_do_status(ci, buf, len);
+		break;
+	case SIMTRACE_MSGT_DO_CEMU_PTS:
+		rc = process_do_pts(ci, buf, len);
+		break;
+	case SIMTRACE_MSGT_DO_CEMU_RX_DATA:
+		rc = process_do_rx_da(ci, buf, len);
+		break;
+	case SIMTRACE_MSGT_BD_CEMU_CONFIG:
+		/* firmware confirms configuration change; ignore */
+		break;
+	default:
+		printf("unknown simtrace msg type 0x%02x\n", sh->msg_type);
+		rc = -1;
+		break;
+	}
+
+	return rc;
+}
+
+
+/*! \brief Process a STATUS message on IRQ endpoint from the SIMtrace2 */
+static int process_irq_status(struct osmo_st2_cardem_inst *ci, const uint8_t *buf, int len)
+{
+	const struct cardemu_usb_msg_status *status = (struct cardemu_usb_msg_status *) buf;
+	struct bankd_client *bc = ci->priv;
+	struct frontend_phys_status pstatus = {
+		.flags = {
+			.reset_active = status->flags & CEMU_STATUS_F_RESET_ACTIVE,
+			.vcc_present = status->flags & CEMU_STATUS_F_VCC_PRESENT,
+			.clk_active = status->flags & CEMU_STATUS_F_CLK_ACTIVE,
+			.card_present = -1 /* FIXME: make this dependent on board */,
+		},
+		.voltage_mv = status->voltage_mv,
+		.fi = status->fi,
+		.di = status->di,
+		.wi = status->wi,
+		.waiting_time = status->waiting_time,
+	};
+
+	printf("SIMtrace IRQ STATUS: flags=0x%x, fi=%u, di=%u, wi=%u wtime=%u\n",
+		status->flags, status->fi, status->di, status->wi,
+		status->waiting_time);
+
+	osmo_fsm_inst_dispatch(bc->main_fi, MF_E_MDM_STATUS_IND, &pstatus);
+	return 0;
+}
+
+static int process_usb_msg_irq(struct osmo_st2_cardem_inst *ci, const uint8_t *buf, unsigned int len)
+{
+	struct simtrace_msg_hdr *sh = (struct simtrace_msg_hdr *)buf;
+	int rc;
+
+	printf("SIMtrace IRQ %s\n", osmo_hexdump(buf, len));
+
+	buf += sizeof(*sh);
+
+	switch (sh->msg_type) {
+	case SIMTRACE_MSGT_BD_CEMU_STATUS:
+		rc = process_irq_status(ci, buf, len);
+		break;
+	default:
+		printf("unknown simtrace msg type 0x%02x\n", sh->msg_type);
+		rc = -1;
+		break;
+	}
+
+	return rc;
+}
+
+static void usb_in_xfer_cb(struct libusb_transfer *xfer)
+{
+	struct osmo_st2_cardem_inst *ci = xfer->user_data;
+	int rc;
+
+	switch (xfer->status) {
+	case LIBUSB_TRANSFER_COMPLETED:
+		/* hand the message up the stack */
+		process_usb_msg(ci, xfer->buffer, xfer->actual_length);
+		break;
+	case LIBUSB_TRANSFER_NO_DEVICE:
+		fprintf(stderr, "USB device disappeared\n");
+		exit(1);
+		break;
+	default:
+		fprintf(stderr, "USB IN transfer failed, status=%u\n", xfer->status);
+		exit(1);
+		break;
+	}
+
+	/* re-submit the IN transfer */
+	rc = libusb_submit_transfer(xfer);
+	OSMO_ASSERT(rc == 0);
+}
+
+
+static void allocate_and_submit_in(struct osmo_st2_cardem_inst *ci)
+{
+	struct osmo_st2_transport *transp = ci->slot->transp;
+	struct libusb_transfer *xfer;
+	int rc;
+
+	xfer = libusb_alloc_transfer(0);
+	OSMO_ASSERT(xfer);
+	xfer->dev_handle = transp->usb_devh;
+	xfer->flags = 0;
+	xfer->type = LIBUSB_TRANSFER_TYPE_BULK;
+	xfer->endpoint = transp->usb_ep.in;
+	xfer->timeout = 0;
+	xfer->user_data = ci;
+	xfer->length = 16*256;
+
+	xfer->buffer = libusb_dev_mem_alloc(xfer->dev_handle, xfer->length);
+	OSMO_ASSERT(xfer->buffer);
+	xfer->callback = usb_in_xfer_cb;
+
+	/* submit the IN transfer */
+	rc = libusb_submit_transfer(xfer);
+	OSMO_ASSERT(rc == 0);
+}
+
+
+static void usb_irq_xfer_cb(struct libusb_transfer *xfer)
+{
+	struct osmo_st2_cardem_inst *ci = xfer->user_data;
+	int rc;
+
+	switch (xfer->status) {
+	case LIBUSB_TRANSFER_COMPLETED:
+		process_usb_msg_irq(ci, xfer->buffer, xfer->actual_length);
+		break;
+	case LIBUSB_TRANSFER_NO_DEVICE:
+		fprintf(stderr, "USB device disappeared\n");
+		exit(1);
+		break;
+	default:
+		fprintf(stderr, "USB IRQ transfer failed, status=%u\n", xfer->status);
+		exit(1);
+		break;
+	}
+
+	/* re-submit the IN transfer */
+	rc = libusb_submit_transfer(xfer);
+	OSMO_ASSERT(rc == 0);
+}
+
+
+static void allocate_and_submit_irq(struct osmo_st2_cardem_inst *ci)
+{
+	struct osmo_st2_transport *transp = ci->slot->transp;
+	struct libusb_transfer *xfer;
+	int rc;
+
+	xfer = libusb_alloc_transfer(0);
+	OSMO_ASSERT(xfer);
+	xfer->dev_handle = transp->usb_devh;
+	xfer->flags = 0;
+	xfer->type = LIBUSB_TRANSFER_TYPE_INTERRUPT;
+	xfer->endpoint = transp->usb_ep.irq_in;
+	xfer->timeout = 0;
+	xfer->user_data = ci;
+	xfer->length = 64;
+
+	xfer->buffer = libusb_dev_mem_alloc(xfer->dev_handle, xfer->length);
+	OSMO_ASSERT(xfer->buffer);
+	xfer->callback = usb_irq_xfer_cb;
+
+	/* submit the IN transfer */
+	rc = libusb_submit_transfer(xfer);
+	OSMO_ASSERT(rc == 0);
+}
+
+
+
+
+/***********************************************************************
+ * simtrace2 frontend code to remsim-client
+ ***********************************************************************/
+
+int frontend_request_card_insert(struct bankd_client *bc)
+{
+	struct osmo_st2_cardem_inst *ci = bc->cardem;
+	return osmo_st2_cardem_request_card_insert(ci, true);
+}
+
+int frontend_request_sim_remote(struct bankd_client *bc)
+{
+	struct osmo_st2_cardem_inst *ci = bc->cardem;
+	return osmo_st2_modem_sim_select_remote(ci->slot);
+}
+
+int frontend_request_modem_reset(struct bankd_client *bc)
+{
+	struct osmo_st2_cardem_inst *ci = bc->cardem;
+	return osmo_st2_modem_reset_pulse(ci->slot, 300);
+}
+
+int frontend_handle_card2modem(struct bankd_client *bc, const uint8_t *data, size_t len)
+{
+	struct osmo_st2_cardem_inst *ci = bc->cardem;
+	// save SW to our current APDU context
+	ac.sw[0] = data[len-2];
+	ac.sw[1] = data[len=1];
+
+	printf("SIMtrace <= SW=0x%02x%02x, len_rx=%zu\n", ac.sw[0], ac.sw[1], len-2);
+	if (len > 2) { // send PB and data to modem
+		osmo_st2_cardem_request_pb_and_tx(ci, ac.hdr.ins, data, len-2);
+	}
+	osmo_st2_cardem_request_sw_tx(ci, ac.sw); // send SW to modem
+	return 0;
+}
+
+int frontend_handle_set_atr(struct bankd_client *bc, const uint8_t *data, size_t len)
+{
+	struct osmo_st2_cardem_inst *ci = bc->cardem;
+	return osmo_st2_cardem_request_set_atr(ci, data, len);
+}
+
+int frontend_handle_slot_status(struct bankd_client *bc, const SlotPhysStatus_t *sts)
+{
+	/* we currently don't propagate bankd status to cardem */
+	return 0;
+}
+
+int frontend_append_script_env(struct bankd_client *bc, char **env, size_t max_env)
+{
+	struct osmo_st2_cardem_inst *ci = bc->cardem;
+	int i = 0;
+
+	if (max_env < 4)
+		return -ENOSPC;
+
+	env[i++] = talloc_asprintf(env, "REMSIM_USB_PATH=%s", ci->usb_path);
+	/* TODO: Configuration; Altsetting */
+	env[i++] = talloc_asprintf(env, "REMSIM_USB_INTERFACE=%u", bc->cfg->usb.if_num);
+
+	return i;
+}
+
+/* FIXME: This must be cleaned up */
+static struct osmo_st2_transport _transp;
+static struct osmo_st2_slot _slot = {
+	.transp = &_transp,
+	.slot_nr = 0,
+};
+
+int client_user_main(struct bankd_client *bc)
+{
+	struct usb_interface_match _ifm, *ifm = &_ifm;
+	struct osmo_st2_transport *transp;
+	struct osmo_st2_cardem_inst *ci;
+	struct client_config *cfg = bc->cfg;
+	int rc, i;
+
+	rc = osmo_libusb_init(NULL);
+	if (rc < 0) {
+		fprintf(stderr, "libusb initialization failed\n");
+		return rc;
+	}
+
+	ci = talloc_zero(bc, struct osmo_st2_cardem_inst);
+	OSMO_ASSERT(ci);
+	ci->slot = &_slot;
+	transp = ci->slot->transp;
+	ci->priv = bc;
+	bc->cardem = ci;
+
+	ifm->vendor = cfg->usb.vendor_id;
+	ifm->product = cfg->usb.product_id;
+	ifm->configuration = cfg->usb.config_id;
+	ifm->interface = cfg->usb.if_num;
+	ifm->altsetting = cfg->usb.altsetting;
+	ifm->addr = cfg->usb.addr;
+	if (cfg->usb.path)
+		osmo_strlcpy(ifm->path, cfg->usb.path, sizeof(ifm->path));
+	transp->usb_devh = osmo_libusb_open_claim_interface(NULL, NULL, ifm);
+	if (!transp->usb_devh) {
+		fprintf(stderr, "can't open USB device\n");
+		return -1;
+	}
+
+	/* (re)determine the USB path of the opened device */
+	talloc_free(ci->usb_path);
+	ci->usb_path = osmo_libusb_dev_get_path_c(ci, libusb_get_device(transp->usb_devh));
+
+	rc = libusb_claim_interface(transp->usb_devh, cfg->usb.if_num);
+	if (rc < 0) {
+		fprintf(stderr, "can't claim interface %d; rc=%d\n", cfg->usb.if_num, rc);
+		goto close_exit;
+	}
+
+	rc = osmo_libusb_get_ep_addrs(transp->usb_devh, cfg->usb.if_num, &transp->usb_ep.out,
+					&transp->usb_ep.in, &transp->usb_ep.irq_in);
+	if (rc < 0) {
+		fprintf(stderr, "can't obtain EP addrs; rc=%d\n", rc);
+		goto close_exit;
+	}
+
+	allocate_and_submit_irq(ci);
+	/* submit multiple IN URB in order to work around OS#4409 */
+	for (i = 0; i < 4; i++)
+		allocate_and_submit_in(ci);
+
+	/* request firmware to generate STATUS on IRQ endpoint */
+	osmo_st2_cardem_request_config(ci, CEMU_FEAT_F_STATUS_IRQ);
+
+	while (1) {
+		osmo_select_main(0);
+	}
+
+	return 0;
+
+close_exit:
+	if (transp->usb_devh)
+		libusb_close(transp->usb_devh);
+	osmo_libusb_exit(NULL);
+
+	return -1;
+}
diff --git a/src/rspro_client_fsm.c b/src/rspro_client_fsm.c
index e55892c..36a3016 100644
--- a/src/rspro_client_fsm.c
+++ b/src/rspro_client_fsm.c
@@ -397,7 +397,7 @@
 	[SRVC_ST_INIT] = {
 		.name = "INIT",
 		.in_event_mask = 0, /* S(SRVC_E_ESTABLISH) via allstate */
-		.out_state_mask = S(SRVC_ST_REESTABLISH),
+		.out_state_mask = S(SRVC_ST_INIT) | S(SRVC_ST_REESTABLISH),
 		.action = srvc_st_init,
 	},
 	[SRVC_ST_ESTABLISHED] = {

-- 
To view, visit https://gerrit.osmocom.org/c/osmo-remsim/+/17361
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: osmo-remsim
Gerrit-Branch: master
Gerrit-Change-Id: I44a430bc5674dea00ed72a0b28729ac8bcb4e022
Gerrit-Change-Number: 17361
Gerrit-PatchSet: 1
Gerrit-Owner: laforge <laforge at osmocom.org>
Gerrit-CC: Jenkins Builder
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20200304/b5200f7e/attachment.htm>


More information about the gerrit-log mailing list