Change in osmocom-bb[master]: layer23/sap_interface.c: reimplement SAP interface

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/.

Vadim Yanitskiy gerrit-no-reply at lists.osmocom.org
Mon Dec 24 22:09:23 UTC 2018


Vadim Yanitskiy has uploaded this change for review. ( https://gerrit.osmocom.org/12441


Change subject: layer23/sap_interface.c: reimplement SAP interface
......................................................................

layer23/sap_interface.c: reimplement SAP interface

Change-Id: I77bb108615bb2c94c441568f195b04e0a5421643
---
M src/host/layer23/include/osmocom/bb/common/Makefile.am
M src/host/layer23/include/osmocom/bb/common/osmocom_data.h
A src/host/layer23/include/osmocom/bb/common/sap_fsm.h
M src/host/layer23/include/osmocom/bb/common/sap_interface.h
A src/host/layer23/include/osmocom/bb/common/sap_proto.h
M src/host/layer23/include/osmocom/bb/mobile/subscriber.h
M src/host/layer23/src/common/Makefile.am
A src/host/layer23/src/common/sap_fsm.c
M src/host/layer23/src/common/sap_interface.c
A src/host/layer23/src/common/sap_proto.c
M src/host/layer23/src/common/sim.c
M src/host/layer23/src/mobile/app_mobile.c
M src/host/layer23/src/mobile/subscriber.c
13 files changed, 1,342 insertions(+), 562 deletions(-)



  git pull ssh://gerrit.osmocom.org:29418/osmocom-bb refs/changes/41/12441/1

diff --git a/src/host/layer23/include/osmocom/bb/common/Makefile.am b/src/host/layer23/include/osmocom/bb/common/Makefile.am
index cd3437e..d66be98 100644
--- a/src/host/layer23/include/osmocom/bb/common/Makefile.am
+++ b/src/host/layer23/include/osmocom/bb/common/Makefile.am
@@ -1,2 +1,3 @@
 noinst_HEADERS = l1ctl.h l1l2_interface.h l23_app.h logging.h \
-		 networks.h gps.h sysinfo.h osmocom_data.h utils.h
+		 networks.h gps.h sysinfo.h osmocom_data.h utils.h \
+		 sap_proto.h sap_fsm.h sap_interface.h
diff --git a/src/host/layer23/include/osmocom/bb/common/osmocom_data.h b/src/host/layer23/include/osmocom/bb/common/osmocom_data.h
index 0399f5f..9bbacd8 100644
--- a/src/host/layer23/include/osmocom/bb/common/osmocom_data.h
+++ b/src/host/layer23/include/osmocom/bb/common/osmocom_data.h
@@ -23,8 +23,17 @@
 #include <osmocom/bb/common/l1ctl.h>
 
 struct osmosap_entity {
-	uint8_t sap_state;
+	struct osmo_fsm_inst *fi;
 	uint16_t max_msg_size;
+	uint8_t card_status;
+
+	/* Optional (connection state) event call-backs */
+	sap_idle_cb_t idle_cb;
+	sap_rel_cb_t rel_cb;
+
+	/* Optional (ATR, APDU) event call-backs */
+	sap_apdu_cb_t apdu_cb;
+	sap_atr_cb_t atr_cb;
 };
 
 struct osmol1_entity {
diff --git a/src/host/layer23/include/osmocom/bb/common/sap_fsm.h b/src/host/layer23/include/osmocom/bb/common/sap_fsm.h
new file mode 100644
index 0000000..797e023
--- /dev/null
+++ b/src/host/layer23/include/osmocom/bb/common/sap_fsm.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <osmocom/bb/common/osmocom_data.h>
+
+/* How long should we wait for connection establishment */
+#define SAP_FSM_CONN_EST_TIMEOUT 5
+#define SAP_FSM_CONN_EST_T 0
+
+/* How long should we wait for connection release */
+#define SAP_FSM_CONN_REL_TIMEOUT 3
+#define SAP_FSM_CONN_REL_T 1
+
+/* How long should we wait for request to complete */
+#define SAP_FSM_PROC_REQ_TIMEOUT 5
+#define SAP_FSM_PROC_REQ_T 2
+
+enum sap_fsm_state {
+	SAP_STATE_NOT_CONNECTED,
+	SAP_STATE_CONNECTING,
+	SAP_STATE_DISCONNECTING, /* Auxiliary state (not from specs) */
+	SAP_STATE_WAIT_FOR_CARD, /* Auxiliary state (not from specs) */
+	SAP_STATE_IDLE,
+	SAP_STATE_PROC_ATR_REQ,
+	SAP_STATE_PROC_APDU_REQ,
+	SAP_STATE_PROC_RESET_REQ,
+	SAP_STATE_PROC_STATUS_REQ,
+	SAP_STATE_PROC_SET_TP_REQ,
+	SAP_STATE_PROC_POWERON_REQ,
+	SAP_STATE_PROC_POWEROFF_REQ,
+};
+
+int sap_fsm_alloc(struct osmocom_ms *ms);
diff --git a/src/host/layer23/include/osmocom/bb/common/sap_interface.h b/src/host/layer23/include/osmocom/bb/common/sap_interface.h
index 78e3846..bde6822 100644
--- a/src/host/layer23/include/osmocom/bb/common/sap_interface.h
+++ b/src/host/layer23/include/osmocom/bb/common/sap_interface.h
@@ -1,98 +1,24 @@
 #pragma once
 
+#include <stdint.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/bb/common/osmocom_data.h>
+
+typedef int (*sap_idle_cb_t)(struct osmocom_ms *ms);
+typedef int (*sap_rel_cb_t)(struct osmocom_ms *ms);
+
+typedef int (*sap_apdu_cb_t)(struct osmocom_ms *ms, struct msgb *msg);
+typedef int (*sap_atr_cb_t)(struct osmocom_ms *ms,
+	const uint8_t *atr, uint16_t atr_len);
+
+void sap_init(struct osmocom_ms *ms);
 int sap_open(struct osmocom_ms *ms);
 int sap_close(struct osmocom_ms *ms);
-int osmosap_send_apdu(struct osmocom_ms *ms, uint8_t *data, uint16_t length);
-int osmosap_init(struct osmocom_ms *ms);
+int _sap_close_sock(struct osmocom_ms *ms);
 
-enum osmosap_state {
-	SAP_SOCKET_ERROR,
-	SAP_NOT_CONNECTED,
-	SAP_IDLE,
-	SAP_CONNECTION_UNDER_NEGOTIATION,
-	SAP_PROCESSING_ATR_REQUEST,
-	SAP_PROCESSING_APDU_REQUEST
-};
+int sap_send_poweron_req(struct osmocom_ms *ms);
+int sap_send_poweroff_req(struct osmocom_ms *ms);
 
-/* Table 5.1: Message Overview */
-enum osmosap_msg_type {
-	SAP_CONNECT_REQ = 0x00,
-	SAP_CONNECT_RESP = 0x01,
-	SAP_DISCONNECT_REQ = 0x02,
-	SAP_DISCONNECT_RESP = 0x03,
-	SAP_DISCONNECT_IND = 0x04,
-	SAP_TRANSFER_APDU_REQ = 0x05,
-	SAP_TRANSFER_APDU_RESP = 0x06,
-	SAP_TRANSFER_ATR_REQ = 0x07,
-	SAP_TRANSFER_ATR_RESP = 0x08,
-	SAP_POWER_SIM_OFF_REQ = 0x09,
-	SAP_POWER_SIM_OFF_RESP = 0x0A,
-	SAP_POWER_SIM_ON_REQ = 0x0B,
-	SAP_POWER_SIM_ON_RESP = 0x0C,
-	SAP_RESET_SIM_REQ = 0x0D,
-	SAP_RESET_SIM_RESP = 0x0E,
-	SAP_TRANSFER_CARD_READER_STATUS_REQ = 0x0F,
-	SAP_TRANSFER_CARD_READER_STATUS_RESP = 0x10,
-	SAP_STATUS_IND = 0x11,
-	SAP_ERROR_RESP = 0x12,
-	SAP_SET_TRANSPORT_PROTOCOL_REQ = 0x13,
-	SAP_SET_TRANSPORT_PROTOCOL_RESP = 0x14
-};
-
-/* Table 5.15: List of Parameter IDs */
-enum osmosap_param_type {
-	SAP_MAX_MSG_SIZE = 0x00,
-	SAP_CONNECTION_STATUS = 0x01,
-	SAP_RESULT_CODE = 0x02,
-	SAP_DISCONNECTION_TYPE = 0x03,
-	SAP_COMMAND_APDU = 0x04,
-	SAP_COMMAND_APDU_7816 = 0x10,
-	SAP_RESPONSE_APDU = 0x05,
-	SAP_ATR = 0x06,
-	SAP_CARD_READER_STATUS = 0x07,
-	SAP_STATUS_CHANGE = 0x08,
-	SAP_TRANSPORT_PROTOCOL = 0x09
-};
-
-/* Table 5.18: Possible values for ResultCode */
-enum sap_result_type {
-	SAP_RESULT_OK_REQ_PROC_CORR = 0x00,
-	SAP_RESULT_ERROR_NO_REASON = 0x01,
-	SAP_RESULT_ERROR_CARD_NOT_ACC = 0x02,
-	SAP_RESULT_ERROR_CARD_POWERED_OFF = 0x03,
-	SAP_RESULT_ERROR_CARD_REMOVED = 0x04,
-	SAP_RESULT_ERROR_CARD_POWERED_ON = 0x05,
-	SAP_RESULT_ERROR_DATA_UNAVAIL = 0x06,
-	SAP_RESULT_ERROR_NOT_SUPPORTED = 0x07,
-};
-
-/* Table 5.19: Possible values for StatusChange */
-enum sap_status_chg_type {
-	SAP_STATUS_CHG_UNKNOWN_ERROR = 0x00,
-	SAP_STATUS_CHG_CARD_RESET = 0x01,
-	SAP_STATUS_CHG_CARD_NOT_ACC = 0x02,
-	SAP_STATUS_CHG_CARD_REMOVED = 0x03,
-	SAP_STATUS_CHG_CARD_INSERTED = 0x04,
-	SAP_STATUS_CHG_CARD_RECOVERED = 0x05,
-};
-
-/* Table 5.16: Possible values for ConnectionStatus */
-enum sap_conn_status_type {
-	SAP_CONN_STATUS_OK_READY = 0x00,
-	SAP_CONN_STATUS_ERROR_CONN = 0x01,
-	SAP_CONN_STATUS_ERROR_MAX_MSG_SIZE = 0x02,
-	SAP_CONN_STATUS_ERROR_SMALL_MSG_SIZE = 0x03,
-	SAP_CONN_STATUS_OK_CALL = 0x04,
-};
-
-struct sap_param {
-	uint8_t id;
-	uint16_t len;
-	uint8_t *value;
-};
-
-struct sap_msg {
-	uint8_t id;
-	uint8_t num_params;
-	struct sap_param *params;
-};
+int sap_send_atr_req(struct osmocom_ms *ms);
+int sap_send_apdu(struct osmocom_ms *ms, uint8_t *apdu, uint16_t apdu_len);
diff --git a/src/host/layer23/include/osmocom/bb/common/sap_proto.h b/src/host/layer23/include/osmocom/bb/common/sap_proto.h
new file mode 100644
index 0000000..1c3b2d5
--- /dev/null
+++ b/src/host/layer23/include/osmocom/bb/common/sap_proto.h
@@ -0,0 +1,121 @@
+#pragma once
+
+#include <stdint.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+
+/* Table 5.1: Message Overview
+ * NOTE: messages are used as events for SAP FSM */
+enum sap_msg_type {
+	SAP_CONNECT_REQ = 0x00,
+	SAP_CONNECT_RESP = 0x01,
+	SAP_DISCONNECT_REQ = 0x02,
+	SAP_DISCONNECT_RESP = 0x03,
+	SAP_DISCONNECT_IND = 0x04,
+	SAP_TRANSFER_APDU_REQ = 0x05,
+	SAP_TRANSFER_APDU_RESP = 0x06,
+	SAP_TRANSFER_ATR_REQ = 0x07,
+	SAP_TRANSFER_ATR_RESP = 0x08,
+	SAP_POWER_SIM_OFF_REQ = 0x09,
+	SAP_POWER_SIM_OFF_RESP = 0x0A,
+	SAP_POWER_SIM_ON_REQ = 0x0B,
+	SAP_POWER_SIM_ON_RESP = 0x0C,
+	SAP_RESET_SIM_REQ = 0x0D,
+	SAP_RESET_SIM_RESP = 0x0E,
+	SAP_TRANSFER_CARD_READER_STATUS_REQ = 0x0F,
+	SAP_TRANSFER_CARD_READER_STATUS_RESP = 0x10,
+	SAP_STATUS_IND = 0x11,
+	SAP_ERROR_RESP = 0x12,
+	SAP_SET_TRANSPORT_PROTOCOL_REQ = 0x13,
+	SAP_SET_TRANSPORT_PROTOCOL_RESP = 0x14
+};
+
+/* Table 5.15: List of Parameter IDs */
+enum sap_param_type {
+	SAP_MAX_MSG_SIZE = 0x00,
+	SAP_CONNECTION_STATUS = 0x01,
+	SAP_RESULT_CODE = 0x02,
+	SAP_DISCONNECTION_TYPE = 0x03,
+	SAP_COMMAND_APDU = 0x04,
+	SAP_COMMAND_APDU_7816 = 0x10,
+	SAP_RESPONSE_APDU = 0x05,
+	SAP_ATR = 0x06,
+	SAP_CARD_READER_STATUS = 0x07,
+	SAP_STATUS_CHANGE = 0x08,
+	SAP_TRANSPORT_PROTOCOL = 0x09
+};
+
+/* Table 5.18: Possible values for ResultCode */
+enum sap_result_type {
+	SAP_RESULT_OK_REQ_PROC_CORR = 0x00,
+	SAP_RESULT_ERROR_NO_REASON = 0x01,
+	SAP_RESULT_ERROR_CARD_NOT_ACC = 0x02,
+	SAP_RESULT_ERROR_CARD_POWERED_OFF = 0x03,
+	SAP_RESULT_ERROR_CARD_REMOVED = 0x04,
+	SAP_RESULT_ERROR_CARD_POWERED_ON = 0x05,
+	SAP_RESULT_ERROR_DATA_UNAVAIL = 0x06,
+	SAP_RESULT_ERROR_NOT_SUPPORTED = 0x07,
+};
+
+/* Table 5.19: Possible values for StatusChange */
+enum sap_card_status_type {
+	SAP_CARD_STATUS_UNKNOWN_ERROR = 0x00,
+	SAP_CARD_STATUS_CARD_RESET = 0x01,
+	SAP_CARD_STATUS_CARD_NOT_ACC = 0x02,
+	SAP_CARD_STATUS_CARD_REMOVED = 0x03,
+	SAP_CARD_STATUS_CARD_INSERTED = 0x04,
+	SAP_CARD_STATUS_CARD_RECOVERED = 0x05,
+};
+
+/* Table 5.16: Possible values for ConnectionStatus */
+enum sap_conn_status_type {
+	SAP_CONN_STATUS_OK_READY = 0x00,
+	SAP_CONN_STATUS_ERROR_CONN = 0x01,
+	SAP_CONN_STATUS_ERROR_MAX_MSG_SIZE = 0x02,
+	SAP_CONN_STATUS_ERROR_SMALL_MSG_SIZE = 0x03,
+	SAP_CONN_STATUS_OK_CALL = 0x04,
+};
+
+extern const struct value_string sap_param_names[];
+extern const struct value_string sap_msg_names[];
+extern const struct value_string sap_result_names[];
+extern const struct value_string sap_card_status_names[];
+extern const struct value_string sap_conn_status_names[];
+
+/* Figure 5.2: Payload Coding */
+struct sap_param {
+	/* Parameter ID, see \ref enum sap_param_type */
+	uint8_t param_id;
+	/* Reserved for further use (shall be set to 0x00) */
+	uint8_t reserved[1];
+	/* Parameter length */
+	uint16_t length;
+	/* Parameter value (and optional padding) */
+	uint8_t value[0];
+} __attribute__((packed));
+
+/* Figure 5.1 Message Format */
+struct sap_message {
+	/* Message ID, see \ref enum sap_msg_type */
+	uint8_t msg_id;
+	/* Number of parameters */
+	uint8_t num_params;
+	/* Reserved for further use (shall be set to 0x00) */
+	uint8_t reserved[2];
+	/* Payload, see \ref struct sap_param */
+	uint8_t payload[0];
+} __attribute__((packed));
+
+#define GSM_SAP_LENGTH 300
+#define GSM_SAP_HEADROOM 32
+
+struct msgb *sap_msgb_alloc(uint8_t msg_id);
+const struct sap_message *sap_msg_parse(const uint8_t *buf, size_t buf_len);
+int sap_check_result_code(const struct sap_message *sap_msg);
+
+void sap_msgb_add_param(struct msgb *msg,
+	enum sap_param_type param_type,
+	uint16_t param_len, uint8_t *param_value);
+struct sap_param *sap_get_param(const struct sap_message *sap_msg,
+	enum sap_param_type param_type, uint16_t *param_len);
diff --git a/src/host/layer23/include/osmocom/bb/mobile/subscriber.h b/src/host/layer23/include/osmocom/bb/mobile/subscriber.h
index c747af9..37b6db7 100644
--- a/src/host/layer23/include/osmocom/bb/mobile/subscriber.h
+++ b/src/host/layer23/include/osmocom/bb/mobile/subscriber.h
@@ -93,6 +93,7 @@
 int gsm_subscr_sapcard(struct osmocom_ms *ms);
 int gsm_subscr_remove_sapcard(struct osmocom_ms *ms);
 int gsm_subscr_simcard(struct osmocom_ms *ms);
+int subscr_sim_request(struct osmocom_ms *ms);
 void gsm_subscr_sim_pin(struct osmocom_ms *ms, char *pin1, char *pin2,
 	int8_t mode);
 int gsm_subscr_write_loci(struct osmocom_ms *ms);
diff --git a/src/host/layer23/src/common/Makefile.am b/src/host/layer23/src/common/Makefile.am
index b76094c..a8d9f7e 100644
--- a/src/host/layer23/src/common/Makefile.am
+++ b/src/host/layer23/src/common/Makefile.am
@@ -2,5 +2,5 @@
 AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBGPS_CFLAGS)
 
 noinst_LIBRARIES = liblayer23.a
-liblayer23_a_SOURCES = l1ctl.c l1l2_interface.c sap_interface.c \
+liblayer23_a_SOURCES = l1ctl.c l1l2_interface.c sap_fsm.c sap_proto.c sap_interface.c \
 	logging.c networks.c sim.c sysinfo.c gps.c l1ctl_lapdm_glue.c utils.c
diff --git a/src/host/layer23/src/common/sap_fsm.c b/src/host/layer23/src/common/sap_fsm.c
new file mode 100644
index 0000000..94a46a7
--- /dev/null
+++ b/src/host/layer23/src/common/sap_fsm.c
@@ -0,0 +1,679 @@
+/*
+ * SAP (SIM Access Profile) FSM definition
+ * based on Bluetooth SAP specification
+ *
+ * (C) 2018 by Vadim Yanitskiy <axilirator at gmail.com>
+ *
+ * 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 <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/socket.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/fsm.h>
+
+#include <osmocom/bb/common/osmocom_data.h>
+#include <osmocom/bb/common/logging.h>
+
+#include <osmocom/bb/common/sap_interface.h>
+#include <osmocom/bb/common/sap_proto.h>
+#include <osmocom/bb/common/sap_fsm.h>
+
+/*! Send encoded SAP message to the Server
+ * \param[in] ms MS instance with active SAP connection
+ * \param[in] msg encoded SAP message buffer
+ * \returns 0 in case of success, negative in case of error
+ */
+static int sap_send_msgb(struct osmocom_ms *ms, struct msgb *msg)
+{
+	int rc;
+
+	rc = osmo_wqueue_enqueue(&ms->sap_wq, msg);
+	if (rc) {
+		LOGP(DSAP, LOGL_ERROR, "Failed to enqueue SAP message\n");
+		msgb_free(msg);
+		return rc;
+	}
+
+	return 0;
+}
+
+static void sap_fsm_tear_down(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	struct osmocom_ms *ms = (struct osmocom_ms *) fi->priv;
+
+	/* Flush buffers, close socket */
+	_sap_close_sock(ms);
+
+	/* Call release call-back (if present) */
+	if (ms->sap_entity.rel_cb)
+		ms->sap_entity.rel_cb(ms);
+
+	/* Trigger self-destruction :/ */
+	osmo_fsm_inst_free(ms->sap_entity.fi);
+	ms->sap_entity.fi = NULL;
+}
+
+static void sap_fsm_connect(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	struct osmocom_ms *ms = (struct osmocom_ms *) fi->priv;
+	struct msgb *msg;
+	uint16_t size;
+
+	/* Section 5.1.1, CONNECT_REQ */
+	msg = sap_msgb_alloc(SAP_CONNECT_REQ);
+	if (!msg)
+		return;
+
+	/* Section 4.1.1, start MaxMsgSize negotiation */
+	size = htons(ms->sap_entity.max_msg_size);
+	sap_msgb_add_param(msg, SAP_MAX_MSG_SIZE,
+		sizeof(size), (uint8_t *) &size);
+
+	sap_send_msgb(ms, msg);
+}
+
+static void sap_negotiate_msg_size(struct osmosap_entity *sap,
+	const struct sap_message *sap_msg)
+{
+	uint16_t size, param_len;
+	const char *cause = NULL;
+	struct sap_param *param;
+
+	param = sap_get_param(sap_msg, SAP_MAX_MSG_SIZE, &param_len);
+	if (!param) {
+		cause = "missing expected MaxMsgSize parameter";
+		goto error;
+	}
+	if (param_len != sizeof(size)) {
+		cause = "MaxMsgSize parameter has wrong length";
+		goto error;
+	}
+
+	/* Parse MaxMsgSize suggested by server */
+	size = ntohs((uint16_t *) param->value);
+	if (size > SAP_MAX_MSG_SIZE) {
+		cause = "suggested MaxMsgSize is too big for us";
+		goto error;
+	}
+
+	/* Attempt to initiate connection again */
+	sap->max_msg_size = size;
+	sap_fsm_connect(sap->fi, sap->fi->state);
+	return;
+
+error:
+	LOGP(DSAP, LOGL_ERROR, "MaxMsgSize negotiation failed: %s\n", cause);
+	osmo_fsm_inst_state_chg(sap->fi, SAP_STATE_NOT_CONNECTED, 0, 0);
+}
+
+static void sap_fsm_conn_handler(struct osmo_fsm_inst *fi,
+	uint32_t event, void *data)
+{
+	const struct sap_message *sap_msg = (const struct sap_message *) data;
+	struct osmocom_ms *ms = (struct osmocom_ms *) fi->priv;
+	struct sap_param *param;
+	uint16_t param_len;
+	uint8_t status;
+
+	/* Section 5.1.2, CONNECT_RESP */
+	param = sap_get_param(sap_msg, SAP_CONNECTION_STATUS, &param_len);
+	if (!param || param_len != sizeof(status)) {
+		LOGP(DSAP, LOGL_ERROR, "Missing mandatory connection status\n");
+		osmo_fsm_inst_state_chg(fi, SAP_STATE_NOT_CONNECTED, 0, 0);
+		return;
+	}
+
+	/* Parse connection status */
+	status = param->value[0];
+
+	LOGP(DSAP, LOGL_INFO, "SAP connection status (0x%02x): %s\n",
+		status, get_value_string(sap_conn_status_names, status));
+
+	switch (status) {
+	case SAP_CONN_STATUS_OK_CALL:
+		ms->sap_entity.card_status = SAP_CARD_STATUS_CARD_NOT_ACC;
+		/* fall-through */
+	case SAP_CONN_STATUS_OK_READY:
+		osmo_fsm_inst_state_chg(fi, SAP_STATE_WAIT_FOR_CARD, 0, 0);
+		break;
+
+	case SAP_CONN_STATUS_ERROR_SMALL_MSG_SIZE:
+	case SAP_CONN_STATUS_ERROR_CONN:
+		osmo_fsm_inst_state_chg(fi, SAP_STATE_NOT_CONNECTED, 0, 0);
+		break;
+
+	/* Section 4.1.1, MaxMsgSize negotiation */
+	case SAP_CONN_STATUS_ERROR_MAX_MSG_SIZE:
+		sap_negotiate_msg_size(&ms->sap_entity, sap_msg);
+		break;
+	}
+}
+
+static void sap_fsm_conn_release(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	struct osmocom_ms *ms = (struct osmocom_ms *) fi->priv;
+	struct msgb *msg;
+
+	LOGP(DSAP, LOGL_DEBUG, "Client initiated connection release\n");
+
+	/* We don't care about possible allocating / sending errors */
+	msg = sap_msgb_alloc(SAP_DISCONNECT_REQ);
+	if (msg != NULL)
+		sap_send_msgb(ms, msg);
+}
+
+static void sap_fsm_release_handler(struct osmo_fsm_inst *fi,
+	uint32_t event, void *data)
+{
+	LOGP(DSAP, LOGL_DEBUG, "Client initiated release complete\n");
+	osmo_fsm_inst_state_chg(fi, SAP_STATE_NOT_CONNECTED, 0, 0);
+}
+
+static void sap_fsm_idle_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	switch (prev_state) {
+	case SAP_STATE_CONNECTING:
+	case SAP_STATE_WAIT_FOR_CARD:
+		/* Sent ATR request (required by the specs) */
+		sap_send_atr_req((struct osmocom_ms *) fi->priv);
+		break;
+	default:
+		/* Do nothing */
+		break;
+	}
+}
+
+static void sap_fsm_idle_handler(struct osmo_fsm_inst *fi,
+	uint32_t event, void *data)
+{
+	struct osmocom_ms *ms = (struct osmocom_ms *) fi->priv;
+	struct msgb *msg = (struct msgb *) data;
+	enum sap_fsm_state state;
+	int rc;
+
+	/* Map event to the corresponding state */
+	switch (event) {
+	case SAP_TRANSFER_ATR_REQ:
+		state = SAP_STATE_PROC_ATR_REQ;
+		break;
+	case SAP_TRANSFER_APDU_REQ:
+		state = SAP_STATE_PROC_APDU_REQ;
+		break;
+	case SAP_RESET_SIM_REQ:
+		state = SAP_STATE_PROC_RESET_REQ;
+		break;
+	case SAP_TRANSFER_CARD_READER_STATUS_REQ:
+		state = SAP_STATE_PROC_STATUS_REQ;
+		break;
+	case SAP_SET_TRANSPORT_PROTOCOL_REQ:
+		state = SAP_STATE_PROC_SET_TP_REQ;
+		break;
+	case SAP_POWER_SIM_ON_REQ:
+		state = SAP_STATE_PROC_POWERON_REQ;
+		break;
+	case SAP_POWER_SIM_OFF_REQ:
+		state = SAP_STATE_PROC_POWEROFF_REQ;
+		break;
+	default:
+		/* Shall not happen */
+		OSMO_ASSERT(0);
+	}
+
+	rc = sap_send_msgb(ms, msg);
+	if (rc)
+		return;
+
+	osmo_fsm_inst_state_chg(fi, state,
+		SAP_FSM_PROC_REQ_TIMEOUT, SAP_FSM_PROC_REQ_T);
+}
+
+static void sap_fsm_proc_atr_handler(struct osmo_fsm_inst *fi,
+	uint32_t event, void *data)
+{
+	const struct sap_message *sap_msg = (const struct sap_message *) data;
+	struct osmocom_ms *ms = (struct osmocom_ms *) fi->priv;
+	struct sap_param *param;
+	uint16_t param_len;
+	int rc;
+
+	if (event != SAP_TRANSFER_ATR_RESP) {
+		LOGP(DSAP, LOGL_ERROR, "Event unhandled (yet)\n");
+		return;
+	}
+
+	/* We're done with ATR request now */
+	osmo_fsm_inst_state_chg(fi, SAP_STATE_IDLE, 0, 0);
+
+	/* Check the ResultCode */
+	rc = sap_check_result_code(sap_msg);
+	if (rc != SAP_RESULT_OK_REQ_PROC_CORR) {
+		LOGP(DSAP, LOGL_NOTICE, "Bad ResultCode: '%s'\n",
+			get_value_string(sap_result_names, rc));
+		return;
+	}
+
+	param = sap_get_param(sap_msg, SAP_ATR, &param_len);
+	if (!param) {
+		LOGP(DSAP, LOGL_ERROR, "Missing mandatory '%s' parameter\n",
+			get_value_string(sap_param_names, SAP_ATR));
+		return;
+	}
+
+	LOGP(DSAP, LOGL_DEBUG, "RX ATR response: %s\n",
+		osmo_hexdump(param->value, param_len));
+
+	/* Forward to ATR handler (if present) */
+	if (ms->sap_entity.atr_cb)
+		ms->sap_entity.atr_cb(ms, param->value, param_len);
+
+	/* We are ready to process APDU requests now */
+	if (ms->sap_entity.idle_cb)
+		ms->sap_entity.idle_cb(ms);
+}
+
+static void sap_fsm_proc_apdu_handler(struct osmo_fsm_inst *fi,
+	uint32_t event, void *data)
+{
+	const struct sap_message *sap_msg = (const struct sap_message *) data;
+	struct osmocom_ms *ms = (struct osmocom_ms *) fi->priv;
+	struct sap_param *param;
+	uint16_t param_len;
+	struct msgb *msg;
+	uint8_t *apdu;
+	int rc;
+
+	if (event != SAP_TRANSFER_APDU_RESP) {
+		LOGP(DSAP, LOGL_ERROR, "Event unhandled (yet)\n");
+		return;
+	}
+
+	/* We're done with APDU request now */
+	osmo_fsm_inst_state_chg(fi, SAP_STATE_IDLE, 0, 0);
+
+	/* Check the ResultCode */
+	rc = sap_check_result_code(sap_msg);
+	if (rc != SAP_RESULT_OK_REQ_PROC_CORR) {
+		LOGP(DSAP, LOGL_NOTICE, "Bad ResultCode: '%s'\n",
+			get_value_string(sap_result_names, rc));
+		return;
+	}
+
+	param = sap_get_param(sap_msg, SAP_RESPONSE_APDU, &param_len);
+	if (!param) {
+		LOGP(DSAP, LOGL_ERROR, "Missing mandatory '%s' parameter\n",
+			get_value_string(sap_param_names, SAP_RESPONSE_APDU));
+		return;
+	}
+
+	/* Forward to APDU handler (if present) */
+	if (!ms->sap_entity.apdu_cb) {
+		LOGP(DSAP, LOGL_NOTICE, "No handler set, response APDU ignored\n");
+		return;
+	}
+
+	/* FIXME: why do we use this length?!? */
+	msg = msgb_alloc(GSM_SAP_LENGTH, "sim_apdu");
+	if (!msg) {
+		LOGP(DSAP, LOGL_ERROR, "Failed to allocate memory\n");
+		return;
+	}
+
+	apdu = msgb_put(msg, param_len);
+	memcpy(apdu, param->value, param_len);
+
+	/* Forward to APDU handler (if present) */
+	LOGP(DSAP, LOGL_DEBUG, "Forwarding APDU to the handler\n");
+	ms->sap_entity.apdu_cb(ms, msg);
+}
+
+/* Generates mask for a single state or event */
+#define S(x) (1 << x)
+
+/* Figure 4.13: Simplified State Machine */
+static const struct osmo_fsm_state sap_fsm_states[] = {
+	[SAP_STATE_NOT_CONNECTED] = {
+		.name = "NOT_CONNECTED",
+		.out_state_mask = S(SAP_STATE_CONNECTING),
+		.onenter = &sap_fsm_tear_down,
+	},
+	[SAP_STATE_CONNECTING] = {
+		.name = "CONNECTING",
+		.out_state_mask = S(SAP_STATE_NOT_CONNECTED)
+				| S(SAP_STATE_WAIT_FOR_CARD),
+		.in_event_mask  = S(SAP_CONNECT_RESP),
+		.onenter = &sap_fsm_connect,
+		.action = &sap_fsm_conn_handler,
+	},
+	/* NOTE: this is a custom state (i.e. not defined by the specs).
+	 * We need it in order to do release procedure correctly. */
+	[SAP_STATE_DISCONNECTING] = {
+		.name = "DISCONNECTING",
+		.out_state_mask = S(SAP_STATE_NOT_CONNECTED),
+		.in_event_mask  = S(SAP_DISCONNECT_RESP),
+		.onenter = &sap_fsm_conn_release,
+		.action = &sap_fsm_release_handler,
+	},
+	/* NOTE: this is a custom state (i.e. not defined by the specs).
+	 * We need it in order to wait until SIM card becomes available.
+	 * SAP_STATUS_IND event is handled by sap_fsm_allstate_action(). */
+	[SAP_STATE_WAIT_FOR_CARD] = {
+		.name = "WAIT_FOR_CARD",
+		.out_state_mask = S(SAP_STATE_NOT_CONNECTED)
+				| S(SAP_STATE_DISCONNECTING)
+				| S(SAP_STATE_IDLE),
+	},
+	[SAP_STATE_IDLE] = {
+		.name = "IDLE",
+		.out_state_mask = S(SAP_STATE_NOT_CONNECTED)
+				| S(SAP_STATE_DISCONNECTING)
+				| S(SAP_STATE_WAIT_FOR_CARD)
+				| S(SAP_STATE_PROC_APDU_REQ)
+				| S(SAP_STATE_PROC_ATR_REQ)
+				| S(SAP_STATE_PROC_RESET_REQ)
+				| S(SAP_STATE_PROC_STATUS_REQ)
+				| S(SAP_STATE_PROC_SET_TP_REQ)
+				| S(SAP_STATE_PROC_POWERON_REQ)
+				| S(SAP_STATE_PROC_POWEROFF_REQ),
+		.in_event_mask  = S(SAP_TRANSFER_ATR_REQ)
+				| S(SAP_TRANSFER_APDU_REQ)
+				| S(SAP_RESET_SIM_REQ)
+				| S(SAP_TRANSFER_CARD_READER_STATUS_REQ)
+				| S(SAP_SET_TRANSPORT_PROTOCOL_REQ)
+				| S(SAP_POWER_SIM_ON_REQ)
+				| S(SAP_POWER_SIM_OFF_REQ),
+		.onenter = &sap_fsm_idle_enter,
+		.action = &sap_fsm_idle_handler,
+	},
+	[SAP_STATE_PROC_ATR_REQ] = {
+		.name = "PROC_ATR_REQ",
+		.out_state_mask = S(SAP_STATE_NOT_CONNECTED)
+				| S(SAP_STATE_DISCONNECTING)
+				| S(SAP_STATE_WAIT_FOR_CARD)
+				| S(SAP_STATE_IDLE)
+				| S(SAP_STATE_PROC_RESET_REQ)
+				| S(SAP_STATE_PROC_POWEROFF_REQ),
+		.in_event_mask  = S(SAP_TRANSFER_ATR_RESP)
+				| S(SAP_RESET_SIM_REQ)
+				| S(SAP_POWER_SIM_OFF_REQ),
+		.action = &sap_fsm_proc_atr_handler,
+	},
+	[SAP_STATE_PROC_APDU_REQ] = {
+		.name = "PROC_APDU_REQ",
+		.out_state_mask = S(SAP_STATE_NOT_CONNECTED)
+				| S(SAP_STATE_DISCONNECTING)
+				| S(SAP_STATE_WAIT_FOR_CARD)
+				| S(SAP_STATE_IDLE)
+				| S(SAP_STATE_PROC_RESET_REQ)
+				| S(SAP_STATE_PROC_POWEROFF_REQ),
+		.in_event_mask  = S(SAP_TRANSFER_APDU_RESP)
+				| S(SAP_RESET_SIM_REQ)
+				| S(SAP_POWER_SIM_OFF_REQ),
+		.action = &sap_fsm_proc_apdu_handler,
+	},
+	[SAP_STATE_PROC_RESET_REQ] = {
+		.name = "PROC_RESET_REQ",
+		.out_state_mask = S(SAP_STATE_NOT_CONNECTED)
+				| S(SAP_STATE_DISCONNECTING)
+				| S(SAP_STATE_WAIT_FOR_CARD)
+				| S(SAP_STATE_IDLE)
+				| S(SAP_STATE_PROC_POWEROFF_REQ),
+		.in_event_mask  = S(SAP_RESET_SIM_RESP)
+				| S(SAP_POWER_SIM_OFF_REQ),
+		// .action = sap_fsm_proc_handler,
+	},
+	[SAP_STATE_PROC_STATUS_REQ] = {
+		.name = "PROC_STATUS_REQ",
+		.out_state_mask = S(SAP_STATE_NOT_CONNECTED)
+				| S(SAP_STATE_DISCONNECTING)
+				| S(SAP_STATE_WAIT_FOR_CARD)
+				| S(SAP_STATE_IDLE)
+				| S(SAP_STATE_PROC_RESET_REQ)
+				| S(SAP_STATE_PROC_POWEROFF_REQ),
+		.in_event_mask  = S(SAP_TRANSFER_CARD_READER_STATUS_RESP)
+				| S(SAP_RESET_SIM_REQ)
+				| S(SAP_POWER_SIM_OFF_REQ),
+		// .action = sap_fsm_proc_handler,
+	},
+	[SAP_STATE_PROC_SET_TP_REQ] = {
+		.name = "PROC_SET_TP_REQ",
+		.out_state_mask = S(SAP_STATE_NOT_CONNECTED)
+				| S(SAP_STATE_DISCONNECTING)
+				| S(SAP_STATE_WAIT_FOR_CARD)
+				| S(SAP_STATE_IDLE),
+		.in_event_mask  = S(SAP_SET_TRANSPORT_PROTOCOL_RESP),
+		// .action = sap_fsm_proc_handler,
+	},
+	[SAP_STATE_PROC_POWERON_REQ] = {
+		.name = "PROC_POWERON_REQ",
+		.out_state_mask = S(SAP_STATE_NOT_CONNECTED)
+				| S(SAP_STATE_DISCONNECTING)
+				| S(SAP_STATE_WAIT_FOR_CARD)
+				| S(SAP_STATE_IDLE)
+				| S(SAP_STATE_PROC_POWEROFF_REQ),
+		.in_event_mask  = S(SAP_POWER_SIM_ON_RESP)
+				| S(SAP_POWER_SIM_OFF_REQ),
+		// .action = sap_fsm_proc_handler,
+	},
+	[SAP_STATE_PROC_POWEROFF_REQ] = {
+		.name = "PROC_POWEROFF_REQ",
+		.out_state_mask = S(SAP_STATE_NOT_CONNECTED)
+				| S(SAP_STATE_DISCONNECTING)
+				| S(SAP_STATE_WAIT_FOR_CARD)
+				| S(SAP_STATE_IDLE),
+		.in_event_mask  = S(SAP_POWER_SIM_OFF_RESP),
+		// .action = sap_fsm_proc_handler,
+	},
+};
+
+static void sap_fsm_handle_card_status_ind(struct osmo_fsm_inst *fi,
+	const struct sap_message *sap_msg)
+{
+	struct osmocom_ms *ms = (struct osmocom_ms *) fi->priv;
+	struct sap_param *param;
+	uint16_t param_len;
+	uint8_t status;
+
+	param = sap_get_param(sap_msg, SAP_STATUS_CHANGE, &param_len);
+	if (!param || param_len != sizeof(status)) {
+		LOGP(DSAP, LOGL_ERROR, "Missing mandatory '%s' parameter\n",
+			get_value_string(sap_param_names, SAP_STATUS_CHANGE));
+		return;
+	}
+
+	status = param->value[0];
+
+	if (ms->sap_entity.card_status != status) {
+		LOGP(DSAP, LOGL_NOTICE, "(SIM) card status change '%s' -> '%s'\n",
+			get_value_string(sap_card_status_names, ms->sap_entity.card_status),
+			get_value_string(sap_card_status_names, status));
+		ms->sap_entity.card_status = status;
+	}
+
+	switch (status) {
+	/* SIM card is ready */
+	case SAP_CARD_STATUS_CARD_RESET:
+		if (fi->state != SAP_STATE_IDLE)
+			osmo_fsm_inst_state_chg(fi, SAP_STATE_IDLE, 0, 0);
+		break;
+
+	/* SIM card has recovered after unaccessful state */
+	case SAP_CARD_STATUS_CARD_RECOVERED:
+		if (fi->state != SAP_STATE_IDLE)
+			osmo_fsm_inst_state_chg(fi, SAP_STATE_IDLE, 0, 0);
+		break;
+
+	/* SIM care inserted, we need to power it on */
+	case SAP_CARD_STATUS_CARD_INSERTED:
+		if (fi->state != SAP_STATE_IDLE)
+			osmo_fsm_inst_state_chg(fi, SAP_STATE_IDLE, 0, 0);
+		sap_send_poweron_req(ms);
+		break;
+
+	case SAP_CARD_STATUS_UNKNOWN_ERROR:
+	case SAP_CARD_STATUS_CARD_NOT_ACC:
+	case SAP_CARD_STATUS_CARD_REMOVED:
+	default: /* Unknown card status */
+		if (fi->state != SAP_STATE_WAIT_FOR_CARD)
+			osmo_fsm_inst_state_chg(fi, SAP_STATE_WAIT_FOR_CARD, 0, 0);
+		break;
+	}
+}
+
+static void sap_fsm_allstate_action(struct osmo_fsm_inst *fi,
+	uint32_t event, void *data)
+{
+	switch (event) {
+	/* Disconnect indication initiated by the Server.
+	 * FIXME: at the moment, immediate release is always assumed,
+	 * but ideally we should check type of release (using *data) */
+	case SAP_DISCONNECT_IND:
+		/* This message may arrive in any of the sub-states of
+		 * the "Connected" state (i.e. connection shall exist) */
+		if (fi->state < SAP_STATE_WAIT_FOR_CARD)
+			goto not_peritted;
+		/* fall-through */
+
+	/* Disconnect initiated by the Client */
+	case SAP_DISCONNECT_REQ:
+		/* If we have no active connection, tear-down immediately */
+		if (fi->state < SAP_STATE_WAIT_FOR_CARD) {
+			osmo_fsm_inst_state_chg(fi,
+				SAP_STATE_NOT_CONNECTED, 0, 0);
+			return;
+		}
+
+		/* Trigger Client-initiater connection release */
+		osmo_fsm_inst_state_chg(fi, SAP_STATE_DISCONNECTING,
+			SAP_FSM_CONN_REL_TIMEOUT, SAP_FSM_CONN_REL_T);
+		break;
+
+	/* SIM status indication (inserted or ejected) */
+	case SAP_STATUS_IND:
+		/* This message may arrive in any of the sub-states of
+		 * the "Connected" state (i.e. connection shall exist) */
+		if (fi->state < SAP_STATE_WAIT_FOR_CARD)
+			goto not_peritted;
+
+		sap_fsm_handle_card_status_ind(fi,
+			(const struct sap_message *) data);
+		break;
+
+	case SAP_ERROR_RESP:
+		if (fi->state == SAP_STATE_CONNECTING) {
+			/* Connection establishment error */
+			osmo_fsm_inst_state_chg(fi,
+				SAP_STATE_NOT_CONNECTED, 0, 0);
+		} else if (fi->state > SAP_STATE_IDLE) {
+			/* Error replaces any Request message */
+			osmo_fsm_inst_state_chg(fi,
+				SAP_STATE_IDLE, 0, 0);
+		} else {
+			/* Should not happen in general */
+			goto not_peritted;
+		}
+		break;
+
+	default:
+		/* Shall not happen */
+		OSMO_ASSERT(0);
+	}
+
+	return;
+
+not_peritted:
+	LOGPFSML(fi, LOGL_NOTICE, "Event '%s' is not "
+		"permitted in state '%s', please fix!\n",
+		osmo_fsm_event_name(fi->fsm, event),
+		osmo_fsm_state_name(fi->fsm, fi->state));
+}
+
+static int sap_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+	switch (fi->state) {
+	/* Connection establishment / release timeout */
+	case SAP_STATE_DISCONNECTING:
+	case SAP_STATE_CONNECTING:
+		LOGP(DSAP, LOGL_NOTICE, "Connection timeout\n");
+		osmo_fsm_inst_state_chg(fi, SAP_STATE_NOT_CONNECTED, 0, 0);
+		break;
+
+	/* Request processing timeout */
+	case SAP_STATE_PROC_ATR_REQ:
+	case SAP_STATE_PROC_APDU_REQ:
+	case SAP_STATE_PROC_RESET_REQ:
+	case SAP_STATE_PROC_STATUS_REQ:
+	case SAP_STATE_PROC_SET_TP_REQ:
+	case SAP_STATE_PROC_POWERON_REQ:
+	case SAP_STATE_PROC_POWEROFF_REQ:
+		LOGP(DSAP, LOGL_NOTICE, "Timeout waiting for '%s' to complete, "
+			"going back to IDLE\n", osmo_fsm_inst_state_name(fi));
+		osmo_fsm_inst_state_chg(fi, SAP_STATE_IDLE, 0, 0);
+		break;
+
+	default:
+		LOGP(DSAP, LOGL_ERROR, "Unhandled state '%s'\n",
+			osmo_fsm_inst_state_name(fi));
+	}
+
+	/* Do not tear-down FSM */
+	return 0;
+}
+
+static struct osmo_fsm sap_fsm_def = {
+	.name = "sap_fsm",
+	.log_subsys = DSAP,
+	.states = sap_fsm_states,
+	.num_states = ARRAY_SIZE(sap_fsm_states),
+	.event_names = sap_msg_names,
+	.timer_cb = &sap_fsm_timer_cb,
+	.allstate_action = &sap_fsm_allstate_action,
+	.allstate_event_mask = 0
+		| S(SAP_DISCONNECT_REQ)
+		| S(SAP_DISCONNECT_IND)
+		| S(SAP_STATUS_IND)
+		| S(SAP_ERROR_RESP),
+};
+
+int sap_fsm_alloc(struct osmocom_ms *ms)
+{
+	struct osmosap_entity *sap;
+
+	sap = &ms->sap_entity;
+	OSMO_ASSERT(sap->fi == NULL);
+
+	/* Register our FSM (if required) */
+	if (!osmo_fsm_find_by_name(sap_fsm_def.name))
+		OSMO_ASSERT(osmo_fsm_register(&sap_fsm_def) == 0);
+
+	/* Allocate an instance using ms as talloc context */
+	sap->fi = osmo_fsm_inst_alloc(&sap_fsm_def, ms,
+		ms, LOGL_DEBUG, ms->name);
+	if (!sap->fi) {
+		LOGP(DSAP, LOGL_ERROR, "Failed to allocate SAP FSM\n");
+		return -ENOMEM;
+	}
+
+	return 0;
+}
diff --git a/src/host/layer23/src/common/sap_interface.c b/src/host/layer23/src/common/sap_interface.c
index c31d07b..4ed457f 100644
--- a/src/host/layer23/src/common/sap_interface.c
+++ b/src/host/layer23/src/common/sap_interface.c
@@ -4,6 +4,7 @@
  * (C) 2010,2018 by Harald Welte <laforge at gnumonks.org>
  * (C) 2010 by Andreas Eversberg <jolly at eversberg.eu>
  * (C) 2011 by Nico Golde <nico at ngolde.de>
+ * (C) 2018 by Vadim Yanitskiy <axilirator at gmail.com>
  *
  * All Rights Reserved
  *
@@ -23,536 +24,272 @@
  *
  */
 
-#include <osmocom/bb/common/osmocom_data.h>
-#include <osmocom/bb/common/logging.h>
-#include <osmocom/bb/common/sap_interface.h>
-
-#include <osmocom/core/utils.h>
-#include <osmocom/core/talloc.h>
-#include <osmocom/core/socket.h>
-
-#include <sys/socket.h>
-#include <sys/un.h>
-
-#include <arpa/inet.h>
-
-#define _GNU_SOURCE
 #include <unistd.h>
 #include <errno.h>
-#include <string.h>
-#include <stdlib.h>
 
-#define GSM_SAP_LENGTH 300
-#define GSM_SAP_HEADROOM 32
+#include <sys/socket.h>
 
-static void sap_connect(struct osmocom_ms *ms);
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/fsm.h>
 
-/* Table 5.15: List of Parameter IDs */
-static const struct value_string sap_param_names[] = {
-	{ SAP_MAX_MSG_SIZE,		"MaxMsgSize" },
-	{ SAP_CONNECTION_STATUS,	"ConnectionStatus" },
-	{ SAP_RESULT_CODE,		"ResultCode" },
-	{ SAP_DISCONNECTION_TYPE,	"DisconnectionType" },
-	{ SAP_COMMAND_APDU,		"CommandAPDU" },
-	{ SAP_COMMAND_APDU_7816,	"CommandAPDU7816" },
-	{ SAP_RESPONSE_APDU,		"ResponseAPDU" },
-	{ SAP_ATR,			"ATR" },
-	{ SAP_CARD_READER_STATUS,	"CardReaderStatus" },
-	{ SAP_STATUS_CHANGE,		"StatusChange" },
-	{ SAP_TRANSPORT_PROTOCOL,	"TransportProtocol" },
-};
+#include <osmocom/bb/common/osmocom_data.h>
+#include <osmocom/bb/common/logging.h>
 
-/* Table 5.1: Message Overview */
-static const struct value_string sap_msg_names[] = {
-	{ SAP_CONNECT_REQ,			"CONNECT_REQ" },
-	{ SAP_CONNECT_RESP,			"CONNECT_RESP" },
-	{ SAP_DISCONNECT_REQ,			"DISCONNECT_REQ" },
-	{ SAP_DISCONNECT_RESP,			"DISCONNECT_RESP" },
-	{ SAP_DISCONNECT_IND,			"DISCONNECT_IND" },
-	{ SAP_TRANSFER_APDU_REQ,		"TRANSFER_APDU_REQ" },
-	{ SAP_TRANSFER_APDU_RESP,		"TRANSFER_APDU_RESP" },
-	{ SAP_TRANSFER_ATR_REQ,			"TRANSFER_ATR_REQ" },
-	{ SAP_TRANSFER_ATR_RESP,		"TRANSFER_ATR_RESP" },
-	{ SAP_POWER_SIM_OFF_REQ,		"POWER_SIM_OFF_REQ" },
-	{ SAP_POWER_SIM_OFF_RESP,		"POWER_SIM_OFF_RESP" },
-	{ SAP_POWER_SIM_ON_REQ,			"POWER_SIM_ON_REQ" },
-	{ SAP_POWER_SIM_ON_RESP,		"POWER_SIM_ON_RESP" },
-	{ SAP_RESET_SIM_REQ,			"RESET_SIM_REQ" },
-	{ SAP_RESET_SIM_RESP,			"RESET_SIM_RESP" },
-	{ SAP_TRANSFER_CARD_READER_STATUS_REQ,	"TRANSFER_CARD_READER_STATUS_REQ" },
-	{ SAP_TRANSFER_CARD_READER_STATUS_RESP,	"TRANSFER_CARD_READER_STATUS_RESP" },
-	{ SAP_STATUS_IND,			"STATUS_IND" },
-	{ SAP_ERROR_RESP,			"ERROR_RESP" },
-	{ SAP_SET_TRANSPORT_PROTOCOL_REQ,	"SET_TRANSPORT_PROTOCOL_REQ" },
-	{ SAP_SET_TRANSPORT_PROTOCOL_RESP,	"SET_TRANSPORT_PROTOCOL_RESP" },
-};
+#include <osmocom/bb/common/sap_interface.h>
+#include <osmocom/bb/common/sap_proto.h>
+#include <osmocom/bb/common/sap_fsm.h>
 
-/* Table 5.18: Possible values for ResultCode */
-static const struct value_string sap_result_names[] = {
-	{ SAP_RESULT_OK_REQ_PROC_CORR,		"OK, request processed correctly" },
-	{ SAP_RESULT_ERROR_NO_REASON,		"Error, no reason defined" },
-	{ SAP_RESULT_ERROR_CARD_NOT_ACC,	"Error, card not accessible" },
-	{ SAP_RESULT_ERROR_CARD_POWERED_OFF,	"Error, card (already) powered off" },
-	{ SAP_RESULT_ERROR_CARD_REMOVED,	"Error, card removed" },
-	{ SAP_RESULT_ERROR_CARD_POWERED_ON,	"Error, card already powered on" },
-	{ SAP_RESULT_ERROR_DATA_UNAVAIL,	"Error, data not available" },
-	{ SAP_RESULT_ERROR_NOT_SUPPORTED,	"Error, not supported "}
-};
-
-/* Table 5.19: Possible values for StatusChange */
-static const struct value_string sap_status_change_names[] = {
-	{ SAP_STATUS_CHG_UNKNOWN_ERROR,		"Unknown Error" },
-	{ SAP_STATUS_CHG_CARD_RESET,		"Card reset" },
-	{ SAP_STATUS_CHG_CARD_NOT_ACC,		"Card not accessible" },
-	{ SAP_STATUS_CHG_CARD_REMOVED,		"Card removed" },
-	{ SAP_STATUS_CHG_CARD_INSERTED,		"Card inserted" },
-	{ SAP_STATUS_CHG_CARD_RECOVERED,	"Card recovered" },
-};
-
-/* Table 5.16: Possible values for ConnectionStatus */
-static const struct value_string sap_status_names[] = {
-	{ SAP_CONN_STATUS_OK_READY,		"OK, Server can fulfill requirements" },
-	{ SAP_CONN_STATUS_ERROR_CONN,		"Error, Server unable to establish connection" },
-	{ SAP_CONN_STATUS_ERROR_MAX_MSG_SIZE,	"Error, Server does not support maximum message size" },
-	{ SAP_CONN_STATUS_ERROR_SMALL_MSG_SIZE,	"Error, maximum message size by Client is too small" },
-	{ SAP_CONN_STATUS_OK_CALL,		"OK, ongoing call" },
-};
-
-static struct msgb *sap_create_msg(uint8_t id, uint8_t num_params, struct sap_param *params)
+/*! Send ATR request to the Server.
+ * \param[in] ms MS instance with active SAP connection
+ * \returns 0 in case of success, negative in case of error
+ */
+int sap_send_atr_req(struct osmocom_ms *ms)
 {
 	struct msgb *msg;
-	uint8_t *msgp;
-	uint8_t i, plen, padding = 0;
+	int rc;
 
-	msg = msgb_alloc(GSM_SAP_LENGTH, "osmosap");
-	if (!msg) {
-		LOGP(DSAP, LOGL_ERROR, "Failed to allocate msg.\n");
-		return NULL;
-	}
-
-	/* BTSAP 5.1 */
-	msgb_put_u8(msg, id);
-	msgb_put_u8(msg, num_params);
-	msgb_put_u16(msg, 0);
-
-	for(i=0; i<num_params; i++){
-		plen = params[i].len;
-		msgb_put_u8(msg, params[i].id);
-		msgb_put_u8(msg, 0);
-		msgb_put_u16(msg, plen);
-		if(plen % 4){
-			padding = 4 - (plen % 4);
-		}
-		msgp = msgb_put(msg, plen + padding);
-		memcpy(msgp, params[i].value, plen);
-
-		if(padding){
-			memset(msgp + plen, 0, padding);
-		}
-	}
-
-	return msg;
-}
-
-static int osmosap_send(struct osmocom_ms *ms, struct msgb *msg)
-{
-	if(ms->sap_entity.sap_state == SAP_NOT_CONNECTED)
-		sap_connect(ms);
-
-	if (ms->sap_wq.bfd.fd <= 0)
-		return -EINVAL;
-
-	if (osmo_wqueue_enqueue(&ms->sap_wq, msg) != 0) {
-		LOGP(DSAP, LOGL_ERROR, "Failed to enqueue msg.\n");
-		msgb_free(msg);
-		return -1;
-	}
-
-	return 0;
-}
-
-static int sap_parse_result(struct sap_param *param)
-{
-	if(param->id != SAP_RESULT_CODE){
-		LOGP(DSAP, LOGL_INFO, "> Parameter id: %u no valid result type\n", param->id);
-		return -1;
-	} else {
-		LOGP(DSAP, LOGL_INFO, "> RESULT CODE: %s\n",
-				get_value_string(sap_result_names, param->value[0]));
-	}
-
-	if(param->value[0] > ARRAY_SIZE(sap_result_names)){
-		return -1;
-	}
-
-	return 0;
-}
-
-static uint8_t *sap_get_param(uint8_t *data, struct sap_param *param)
-{
-	uint8_t *dptr = data;
-	uint8_t padlen;
-
-	param->id = *dptr++;
-	/* skip reserved byte */
-	dptr++;
-	param->len = *dptr << 8;
-	dptr++;
-	param->len |= *dptr++;
-	param->value = talloc_zero_size(NULL, param->len);
-	memcpy(param->value, dptr, param->len);
-
-	/* skip parameter and padding and return pointer to next parameter */
-	dptr += param->len;
-	if(param->len % 4){
-		padlen = (4 - param->len % 4);
-	} else {
-		padlen = 0;
-	}
-	dptr += padlen;
-
-	return dptr;
-}
-
-static void sap_msg_free(struct sap_msg *msg)
-{
-	uint8_t i;
-	for(i=0; i<msg->num_params; i++){
-		talloc_free(msg->params[i].value);
-		talloc_free(msg->params);
-	}
-	talloc_free(msg);
-}
-
-static struct sap_msg *sap_parse_msg(uint8_t *data)
-{
-	struct sap_msg *msg = talloc_zero(NULL, struct sap_msg);
-	uint8_t *ptr = data;
-	uint8_t i;
-
-	if(!msg){
-		return NULL;
-	}
-
-	msg->id = *ptr++;
-	LOGP(DSAP, LOGL_INFO, "> %s \n", get_value_string(sap_msg_names, msg->id));
-
-	msg->num_params = *ptr++;
-	/* skip two reserved null bytes, BTSAP 5.1 */
-	ptr += 2;
-
-	msg->params = talloc_zero_size(NULL, sizeof(struct sap_param) * msg->num_params);
-
-	for(i=0; i<msg->num_params; i++){
-		ptr = sap_get_param(ptr, &msg->params[i]);
-		LOGP(DSAP, LOGL_INFO, "> %s %s\n",
-				get_value_string(sap_param_names, msg->params[i].id),
-				osmo_hexdump(msg->params[i].value, msg->params[i].len));
-	}
-
-	return msg;
-}
-
-static void sap_apdu_resp(struct osmocom_ms *ms, uint8_t *data, uint16_t len)
-{
-	struct msgb *msg;
-	uint8_t *apdu;
-	msg = msgb_alloc(GSM_SAP_LENGTH, "osmosap");
-	if(!msg){
-		LOGP(DSAP, LOGL_ERROR, "Failed to allocate memory.\n");
-		return;
-	}
-
-	apdu = msgb_put(msg, len);
-	memcpy(apdu, data, len);
-
-	LOGP(DSAP, LOGL_DEBUG, "Forwarding APDU to SIM handler.\n");
-	sim_apdu_resp(ms, msg);
-}
-
-static int sap_adapt_msg_size(struct osmocom_ms *ms, struct sap_param *param)
-{
-	uint16_t size;
-	size = (param->value[0] << 8) | param->value[1];
-	if(size != ms->sap_entity.max_msg_size && size > 0){
-		LOGP(DSAP, LOGL_NOTICE, "Server can not handle max_msg_size, adapting.\n");
-		ms->sap_entity.max_msg_size = size;
-		return -1;
-	}
-	return 0;
-}
-
-static void sap_atr(struct osmocom_ms *ms)
-{
-	struct msgb *msg;
-	if(ms->sap_entity.sap_state != SAP_IDLE){
-		LOGP(DSAP, LOGL_ERROR, "Attempting to send ATR request while not being idle.\n");
-		return;
-	}
-
-	msg = sap_create_msg(SAP_TRANSFER_ATR_REQ, 0, NULL);
-	if(!msg)
-		return;
-
-	osmosap_send(ms, msg);
-	ms->sap_entity.sap_state = SAP_PROCESSING_ATR_REQUEST;
-}
-
-static void sap_parse_resp(struct osmocom_ms *ms, uint8_t *data, uint16_t len)
-{
-	struct sap_msg *msg;
-
-	msg = sap_parse_msg(data);
-	if (!msg) {
-		LOGP(DSAP, LOGL_ERROR, "Failed to parse SAP message\n");
-		return;
-	}
-
-	switch(msg->id){
-	case SAP_CONNECT_RESP:
-		LOGP(DSAP, LOGL_INFO, "Status: %s\n", get_value_string(sap_status_names, msg->params[0].value[0]));
-		if(msg->params[0].value[0] == 0){
-			ms->sap_entity.sap_state = SAP_IDLE;
-		}
-		if(msg->num_params == 2 && msg->params[1].len == 2){
-			if(sap_adapt_msg_size(ms, &msg->params[1]) < 0) {
-				ms->sap_entity.sap_state = SAP_NOT_CONNECTED;
-			} else {
-				sap_atr(ms);
-			}
-		}
-		break;
-	case SAP_DISCONNECT_RESP:
-		ms->sap_entity.sap_state = SAP_NOT_CONNECTED;
-		break;
-	case SAP_STATUS_IND:
-		LOGP(DSAP, LOGL_INFO, "New card state: %s\n", get_value_string(sap_status_change_names,
-					msg->params[0].value[0]));
-		if(msg->params[0].value[0] != 1){
-			/* TODO: handle case in which the card is not ready yet */
-		}
-		break;
-	case SAP_TRANSFER_ATR_RESP:
-		if(ms->sap_entity.sap_state != SAP_PROCESSING_ATR_REQUEST){
-			LOGP(DSAP, LOGL_ERROR, "got ATR resp in state: %u\n", ms->sap_entity.sap_state);
-			return;
-		}
-		if(msg->num_params >= 2){
-			LOGP(DSAP, LOGL_INFO, "ATR: %s\n", osmo_hexdump(msg->params[1].value, msg->params[1].len));
-		}
-		ms->sap_entity.sap_state = SAP_IDLE;
-		break;
-	case SAP_TRANSFER_APDU_RESP:
-		if(ms->sap_entity.sap_state != SAP_PROCESSING_APDU_REQUEST){
-			LOGP(DSAP, LOGL_ERROR, "got APDU resp in state: %u\n", ms->sap_entity.sap_state);
-			return;
-		}
-		if(msg->num_params != 2){
-			LOGP(DSAP, LOGL_ERROR, "wrong number of parameters %u in APDU response\n", msg->num_params);
-			return;
-		}
-		ms->sap_entity.sap_state = SAP_IDLE;
-		if(sap_parse_result(&msg->params[0]) == 0){
-			/* back apdu resp to layer23 */
-			sap_apdu_resp(ms, msg->params[1].value, msg->params[1].len);
-			LOGP(DSAP, LOGL_INFO, "sap_apdu_resp called, sending data back to layer23\n");
-		}
-		break;
-	case SAP_ERROR_RESP:
-		if(ms->sap_entity.sap_state == SAP_CONNECTION_UNDER_NEGOTIATION){
-			ms->sap_entity.sap_state = SAP_NOT_CONNECTED;
-		} else {
-			ms->sap_entity.sap_state = SAP_IDLE;
-		}
-		break;
-	default:
-		LOGP(DSAP, LOGL_ERROR, "got unknown or not implemented SAP msgid: %u\n", msg->id);
-		break;
-	}
-}
-
-static int sap_read(struct osmo_fd *fd)
-{
-	struct msgb *msg = NULL;
-	struct osmocom_ms *ms = (struct osmocom_ms *) fd->data;
-	uint8_t *sap_buffer;
-	ssize_t rc;
-
-	sap_buffer = talloc_zero_size(NULL, ms->sap_entity.max_msg_size);
-	if(!sap_buffer){
-		LOGP(DSAP, LOGL_ERROR, "Failed to allocate memory\n");
+	msg = sap_msgb_alloc(SAP_TRANSFER_ATR_REQ);
+	if (!msg)
 		return -ENOMEM;
-	}
 
-	rc = read(fd->fd, sap_buffer, ms->sap_entity.max_msg_size - 1);
-	if (rc < 0) {
-		LOGP(DSAP, LOGL_ERROR,"SAP socket failed\n");
+	rc = osmo_fsm_inst_dispatch(ms->sap_entity.fi,
+		SAP_TRANSFER_ATR_REQ, msg);
+	if (rc) {
 		msgb_free(msg);
-		sap_close(ms);
 		return rc;
 	}
-	if(rc == 0) {
-		LOGP(DSAP, LOGL_NOTICE, "SAP socket closed by server\n");
-		msgb_free(msg);
-		sap_close(ms);
-		return -ECONNREFUSED;
-	}
-
-	sap_buffer[rc] = 0;
-	LOGP(DSAP, LOGL_INFO, "Received %zd bytes: %s\n", rc, osmo_hexdump(sap_buffer, rc));
-
-	sap_parse_resp(ms, sap_buffer, rc);
-
-	talloc_free(sap_buffer);
 
 	return 0;
 }
 
-static int sap_write(struct osmo_fd *fd, struct msgb *msg)
+/*! Send APDU request to the Server.
+ * \param[in] ms MS instance with active SAP connection
+ * \param[in] apdu APDU to be send
+ * \param[in] apdu_len length of APDU
+ * \returns 0 in case of success, negative in case of error
+ */
+int sap_send_apdu(struct osmocom_ms *ms, uint8_t *apdu, uint16_t apdu_len)
+{
+	struct msgb *msg;
+	int rc;
+
+	msg = sap_msgb_alloc(SAP_TRANSFER_APDU_REQ);
+	if (!msg)
+		return -ENOMEM;
+
+	sap_msgb_add_param(msg, SAP_COMMAND_APDU, apdu_len, apdu);
+
+	rc = osmo_fsm_inst_dispatch(ms->sap_entity.fi,
+		SAP_TRANSFER_APDU_REQ, msg);
+	if (rc) {
+		msgb_free(msg);
+		return rc;
+	}
+
+	return 0;
+}
+
+/*! Send (SIM) power on request to the Server.
+ * \param[in] ms MS instance with active SAP connection
+ * \returns 0 in case of success, negative in case of error
+ */
+int sap_send_poweron_req(struct osmocom_ms *ms)
+{
+	struct msgb *msg;
+	int rc;
+
+	msg = sap_msgb_alloc(SAP_POWER_SIM_ON_REQ);
+	if (!msg)
+		return -ENOMEM;
+
+	rc = osmo_fsm_inst_dispatch(ms->sap_entity.fi,
+		SAP_POWER_SIM_ON_REQ, msg);
+	if (rc) {
+		msgb_free(msg);
+		return rc;
+	}
+
+	return 0;
+}
+
+/*! Send (SIM) power off request to the Server.
+ * \param[in] ms MS instance with active SAP connection
+ * \returns 0 in case of success, negative in case of error
+ */
+int sap_send_poweroff_req(struct osmocom_ms *ms)
+{
+	struct msgb *msg;
+	int rc;
+
+	msg = sap_msgb_alloc(SAP_POWER_SIM_OFF_REQ);
+	if (!msg)
+		return -ENOMEM;
+
+	rc = osmo_fsm_inst_dispatch(ms->sap_entity.fi,
+		SAP_POWER_SIM_OFF_REQ, msg);
+	if (rc) {
+		msgb_free(msg);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int sap_read_cb(struct osmo_fd *fd)
+{
+	struct osmocom_ms *ms = (struct osmocom_ms *) fd->data;
+	struct osmosap_entity *sap = &ms->sap_entity;
+	const struct sap_message *sap_msg;
+	uint8_t buf[GSM_SAP_LENGTH];
+	ssize_t rc;
+
+	/* Prevent buffer overflow */
+	OSMO_ASSERT(sap->max_msg_size <= GSM_SAP_LENGTH);
+
+	rc = read(fd->fd, buf, sap->max_msg_size);
+	if (rc < 0) {
+		LOGP(DSAP, LOGL_ERROR, "SAP socket failed\n");
+		rc = -EIO;
+		goto error;
+	}
+	if (rc == 0) {
+		LOGP(DSAP, LOGL_NOTICE, "SAP socket closed by server\n");
+		rc = -ECONNREFUSED;
+		goto error;
+	}
+
+	LOGP(DSAP, LOGL_DEBUG, "RX SAP message (len=%zd): %s\n",
+		rc, osmo_hexdump(buf, rc));
+
+	/* Parse SAP message */
+	sap_msg = sap_msg_parse(buf, rc);
+	if (!sap_msg) {
+		LOGP(DSAP, LOGL_ERROR, "Failed to parse SAP message\n");
+		return -EINVAL;
+	}
+
+	/* Pass received message to our FSM using message
+	 * type as the FSM event name */
+	osmo_fsm_inst_dispatch(sap->fi, sap_msg->msg_id, sap_msg);
+
+	return 0;
+
+error:
+	/* Immediately tear-down FSM */
+	osmo_fsm_inst_state_chg(sap->fi, SAP_STATE_NOT_CONNECTED, 0, 0);
+	return rc;
+}
+
+static int sap_write_cb(struct osmo_fd *fd, struct msgb *msg)
 {
 	ssize_t rc;
 
 	if (fd->fd <= 0)
 		return -EINVAL;
 
-	LOGP(DSAP, LOGL_INFO, "< %s\n", osmo_hexdump(msg->data, msg->len));
 	rc = write(fd->fd, msg->data, msg->len);
 	if (rc != msg->len) {
-		LOGP(DSAP, LOGL_ERROR, "Failed to write data: rc: %zd\n", rc);
+		LOGP(DSAP, LOGL_ERROR, "Failed to write data\n");
 		return rc;
 	}
 
+	LOGP(DSAP, LOGL_DEBUG, "TX SAP message (len=%u): %s\n",
+		msg->len, osmo_hexdump(msg->data, msg->len));
+
 	return 0;
 }
 
-static void sap_connect(struct osmocom_ms *ms)
-{
-	uint8_t buffer[3];
-	struct msgb *msg;
-	uint16_t size = ms->sap_entity.max_msg_size;
-	struct sap_param params[1];
-
-	params[0].id = SAP_MAX_MSG_SIZE;
-	params[0].len = 2;
-
-	if(ms->sap_entity.sap_state != SAP_NOT_CONNECTED) {
-		LOGP(DSAP, LOGL_ERROR, "Attempting to connect while there is an active connection.\n");
-		return;
-	}
-
-	buffer[0] = (size >> 8) & 0xFF;
-	buffer[1] = size & 0xFF;
-	buffer[2] = 0;
-	params[0].value = buffer;
-
-	msg = sap_create_msg(SAP_CONNECT_REQ, 1, params);
-	if(!msg)
-		return;
-
-	osmosap_send(ms, msg);
-
-	ms->sap_entity.sap_state = SAP_CONNECTION_UNDER_NEGOTIATION;
-}
-
-static void sap_disconnect(struct osmocom_ms *ms)
-{
-	struct msgb *msg;
-	if(ms->sap_entity.sap_state != SAP_NOT_CONNECTED && ms->sap_entity.sap_state != SAP_CONNECTION_UNDER_NEGOTIATION){
-		LOGP(DSAP, LOGL_ERROR, "Attempting to disconnect while no active connection.\n");
-		return;
-	}
-
-	msg = sap_create_msg(SAP_DISCONNECT_REQ, 0, NULL);
-	if(!msg)
-		return;
-
-	osmosap_send(ms, msg);
-
-	ms->sap_entity.sap_state = SAP_NOT_CONNECTED;
-}
-
-static int sap_apdu(struct osmocom_ms *ms, uint8_t *data, uint16_t len)
-{
-	struct msgb *msg;
-	struct sap_param params[1];
-	int rc;
-
-	params[0].id = SAP_COMMAND_APDU;
-	params[0].len = len;
-	params[0].value = data;
-
-	if(ms->sap_entity.sap_state != SAP_IDLE){
-		LOGP(DSAP, LOGL_ERROR, "Attempting to send APDU request while not being idle.\n");
-		return -EIO;
-	}
-
-	msg = sap_create_msg(SAP_TRANSFER_APDU_REQ, 1, params);
-	if(!msg)
-		return -ENOMEM;
-
-	rc = osmosap_send(ms, msg);
-	if (rc)
-		return rc;
-
-	ms->sap_entity.sap_state = SAP_PROCESSING_APDU_REQUEST;
-
-	return 0;
-}
-
+/*! Establishes SAP connection to the Server,
+ *  allocates SAP FSM, and triggers connection procedure.
+ * \param[in] ms MS instance with configured SAP socket path
+ * \returns 0 in case of success, negative in case of error
+ */
 int sap_open(struct osmocom_ms *ms)
 {
 	int rc;
 
+	LOGP(DSAP, LOGL_INFO, "Establishing SAP connection "
+		"(using socket '%s')\n", ms->settings.sap_socket_path);
+
 	rc = osmo_sock_unix_init_ofd(&ms->sap_wq.bfd, SOCK_STREAM, 0,
 		ms->settings.sap_socket_path, OSMO_SOCK_F_CONNECT);
 	if (rc < 0) {
 		LOGP(DSAP, LOGL_ERROR, "Failed to create unix domain socket %s: %s\n",
 		     ms->settings.sap_socket_path, strerror(-rc));
-		ms->sap_entity.sap_state = SAP_SOCKET_ERROR;
 		return rc;
 	}
 
 	osmo_wqueue_init(&ms->sap_wq, 100);
 	ms->sap_wq.bfd.data = ms;
-	ms->sap_wq.read_cb = sap_read;
-	ms->sap_wq.write_cb = sap_write;
+	ms->sap_wq.read_cb = &sap_read_cb;
+	ms->sap_wq.write_cb = &sap_write_cb;
 
-	sap_connect(ms);
+	/* Allocate a SAP FSM for a given ms */
+	rc = sap_fsm_alloc(ms);
+	if (rc) {
+		_sap_close_sock(ms);
+		return rc;
+	}
 
-	return 0;
+	/* Initiate SAP connection with Server */
+	LOGP(DSAP, LOGL_DEBUG, "Connecting to the Server...\n");
+	return osmo_fsm_inst_state_chg(ms->sap_entity.fi, SAP_STATE_CONNECTING,
+		SAP_FSM_CONN_EST_TIMEOUT, SAP_FSM_CONN_EST_T);
 }
 
+/*! Closes SAP connection with the Server.
+ * \param[in] ms MS instance with active SAP connection
+ * \returns 0 in case of success, negative in case of error
+ */
 int sap_close(struct osmocom_ms *ms)
 {
+	if (ms->sap_entity.fi == NULL) {
+		LOGP(DSAP, LOGL_NOTICE, "No active SAP connection (no FSM)\n");
+		return -EINVAL;
+	}
+
+	LOGP(DSAP, LOGL_INFO, "Closing SAP connection\n");
+	return osmo_fsm_inst_dispatch(ms->sap_entity.fi,
+		SAP_DISCONNECT_REQ, NULL);
+}
+
+/*! Low-level function for closing SAP (socket) connection.
+ * \param[in] ms MS instance with active SAP connection
+ * \returns 0 in case of success, negative in case of error
+ */
+int _sap_close_sock(struct osmocom_ms *ms)
+{
 	if (ms->sap_wq.bfd.fd <= 0)
 		return -EINVAL;
 
-	sap_disconnect(ms);
 	close(ms->sap_wq.bfd.fd);
 	ms->sap_wq.bfd.fd = -1;
+
 	osmo_fd_unregister(&ms->sap_wq.bfd);
 	osmo_wqueue_clear(&ms->sap_wq);
 
 	return 0;
 }
 
-/* same signature as in L1CTL, so it can be called from sim.c */
-int osmosap_send_apdu(struct osmocom_ms *ms, uint8_t *data, uint16_t length)
-{
-	//LOGP(DSAP, LOGL_ERROR, "Received the following APDU from sim.c: %s\n" ,
-	//     osmo_hexdump(data, length));
-	return sap_apdu(ms, data, length);
-}
-
-/* init */
-int osmosap_init(struct osmocom_ms *ms)
+/*! Initiates SAP client state for a given MS. */
+void sap_init(struct osmocom_ms *ms)
 {
 	struct osmosap_entity *sap = &ms->sap_entity;
 
 	LOGP(DSAP, LOGL_INFO, "init SAP client\n");
-	sap->sap_state = SAP_NOT_CONNECTED;
+
+	/* Default MaxMsgSize (to be negotiated) */
 	sap->max_msg_size = GSM_SAP_LENGTH;
+	/* SIM card status is not known yet */
+	sap->card_status = SAP_CARD_STATUS_CARD_NOT_ACC;
 
-	return 0;
+	/* Optional event call-backs */
+	sap->idle_cb = NULL;
+	sap->rel_cb = NULL;
+	sap->apdu_cb = NULL;
+	sap->atr_cb = NULL;
 }
-
diff --git a/src/host/layer23/src/common/sap_proto.c b/src/host/layer23/src/common/sap_proto.c
new file mode 100644
index 0000000..b170eb6
--- /dev/null
+++ b/src/host/layer23/src/common/sap_proto.c
@@ -0,0 +1,271 @@
+/*
+ * SAP (SIM Access Profile) protocol definition
+ * based on Bluetooth SAP specification
+ *
+ * (C) 2011 by Nico Golde <nico at ngolde.de>
+ * (C) 2018 by Vadim Yanitskiy <axilirator at gmail.com>
+ *
+ * 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 <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+
+#include <osmocom/bb/common/sap_proto.h>
+#include <osmocom/bb/common/logging.h>
+
+/* Table 5.15: List of Parameter IDs */
+const struct value_string sap_param_names[] = {
+	{ SAP_MAX_MSG_SIZE,		"MaxMsgSize" },
+	{ SAP_CONNECTION_STATUS,	"ConnectionStatus" },
+	{ SAP_RESULT_CODE,		"ResultCode" },
+	{ SAP_DISCONNECTION_TYPE,	"DisconnectionType" },
+	{ SAP_COMMAND_APDU,		"CommandAPDU" },
+	{ SAP_COMMAND_APDU_7816,	"CommandAPDU7816" },
+	{ SAP_RESPONSE_APDU,		"ResponseAPDU" },
+	{ SAP_ATR,			"ATR" },
+	{ SAP_CARD_READER_STATUS,	"CardReaderStatus" },
+	{ SAP_STATUS_CHANGE,		"StatusChange" },
+	{ SAP_TRANSPORT_PROTOCOL,	"TransportProtocol" },
+};
+
+/* Table 5.1: Message Overview */
+const struct value_string sap_msg_names[] = {
+	{ SAP_CONNECT_REQ,			"CONNECT_REQ" },
+	{ SAP_CONNECT_RESP,			"CONNECT_RESP" },
+	{ SAP_DISCONNECT_REQ,			"DISCONNECT_REQ" },
+	{ SAP_DISCONNECT_RESP,			"DISCONNECT_RESP" },
+	{ SAP_DISCONNECT_IND,			"DISCONNECT_IND" },
+	{ SAP_TRANSFER_APDU_REQ,		"TRANSFER_APDU_REQ" },
+	{ SAP_TRANSFER_APDU_RESP,		"TRANSFER_APDU_RESP" },
+	{ SAP_TRANSFER_ATR_REQ,			"TRANSFER_ATR_REQ" },
+	{ SAP_TRANSFER_ATR_RESP,		"TRANSFER_ATR_RESP" },
+	{ SAP_POWER_SIM_OFF_REQ,		"POWER_SIM_OFF_REQ" },
+	{ SAP_POWER_SIM_OFF_RESP,		"POWER_SIM_OFF_RESP" },
+	{ SAP_POWER_SIM_ON_REQ,			"POWER_SIM_ON_REQ" },
+	{ SAP_POWER_SIM_ON_RESP,		"POWER_SIM_ON_RESP" },
+	{ SAP_RESET_SIM_REQ,			"RESET_SIM_REQ" },
+	{ SAP_RESET_SIM_RESP,			"RESET_SIM_RESP" },
+	{ SAP_TRANSFER_CARD_READER_STATUS_REQ,	"TRANSFER_CARD_READER_STATUS_REQ" },
+	{ SAP_TRANSFER_CARD_READER_STATUS_RESP,	"TRANSFER_CARD_READER_STATUS_RESP" },
+	{ SAP_STATUS_IND,			"STATUS_IND" },
+	{ SAP_ERROR_RESP,			"ERROR_RESP" },
+	{ SAP_SET_TRANSPORT_PROTOCOL_REQ,	"SET_TRANSPORT_PROTOCOL_REQ" },
+	{ SAP_SET_TRANSPORT_PROTOCOL_RESP,	"SET_TRANSPORT_PROTOCOL_RESP" },
+};
+
+/* Table 5.18: Possible values for ResultCode */
+const struct value_string sap_result_names[] = {
+	{ SAP_RESULT_OK_REQ_PROC_CORR,		"OK, request processed correctly" },
+	{ SAP_RESULT_ERROR_NO_REASON,		"Error, no reason defined" },
+	{ SAP_RESULT_ERROR_CARD_NOT_ACC,	"Error, card not accessible" },
+	{ SAP_RESULT_ERROR_CARD_POWERED_OFF,	"Error, card (already) powered off" },
+	{ SAP_RESULT_ERROR_CARD_REMOVED,	"Error, card removed" },
+	{ SAP_RESULT_ERROR_CARD_POWERED_ON,	"Error, card already powered on" },
+	{ SAP_RESULT_ERROR_DATA_UNAVAIL,	"Error, data not available" },
+	{ SAP_RESULT_ERROR_NOT_SUPPORTED,	"Error, not supported "}
+};
+
+/* Table 5.19: Possible values for StatusChange */
+const struct value_string sap_card_status_names[] = {
+	{ SAP_CARD_STATUS_UNKNOWN_ERROR,	"Unknown Error" },
+	{ SAP_CARD_STATUS_CARD_RESET,		"Card reset" },
+	{ SAP_CARD_STATUS_CARD_NOT_ACC,		"Card not accessible" },
+	{ SAP_CARD_STATUS_CARD_REMOVED,		"Card removed" },
+	{ SAP_CARD_STATUS_CARD_INSERTED,	"Card inserted" },
+	{ SAP_CARD_STATUS_CARD_RECOVERED,	"Card recovered" },
+};
+
+/* Table 5.16: Possible values for ConnectionStatus */
+const struct value_string sap_conn_status_names[] = {
+	{ SAP_CONN_STATUS_OK_READY,		"OK, Server can fulfill requirements" },
+	{ SAP_CONN_STATUS_ERROR_CONN,		"Error, Server unable to establish connection" },
+	{ SAP_CONN_STATUS_ERROR_MAX_MSG_SIZE,	"Error, Server does not support maximum message size" },
+	{ SAP_CONN_STATUS_ERROR_SMALL_MSG_SIZE,	"Error, maximum message size by Client is too small" },
+	{ SAP_CONN_STATUS_OK_CALL,		"OK, ongoing call" },
+};
+
+struct msgb *sap_msgb_alloc(uint8_t msg_id)
+{
+	struct sap_message *sap_msg;
+	struct msgb *msg;
+
+	msg = msgb_alloc(GSM_SAP_LENGTH, "sap_msg");
+	if (!msg) {
+		LOGP(DSAP, LOGL_ERROR, "Failed to allocate SAP message\n");
+		return NULL;
+	}
+
+	sap_msg = (struct sap_message *) msgb_put(msg, sizeof(*sap_msg));
+	sap_msg->msg_id = msg_id;
+
+	return msg;
+}
+
+void sap_msgb_add_param(struct msgb *msg,
+	enum sap_param_type param_type,
+	uint16_t param_len, uint8_t *param_value)
+{
+	struct sap_message *sap_msg;
+	struct sap_param *param;
+	uint8_t padding;
+	uint8_t *buf;
+
+	/* Update number of parameters */
+	sap_msg = (struct sap_message *) msg->data;
+	sap_msg->num_params++;
+
+	/* Allocate a new parameter */
+	param = (struct sap_param *) msgb_put(msg, sizeof(*param));
+	param->param_id = param_type;
+	param->reserved[0] = 0x00;
+
+	/* Encode parameter value and length */
+	param->length = htons(param_len);
+	buf = msgb_put(msg, param_len);
+	memcpy(buf, param_value, param_len);
+
+	/* Optional padding */
+	padding = 4 - (param_len % 4);
+	if (padding) {
+		buf = msgb_put(msg, padding);
+		memset(buf, 0x00, padding);
+	}
+}
+
+struct sap_param *sap_get_param(const struct sap_message *sap_msg,
+	enum sap_param_type param_type, uint16_t *param_len)
+{
+	const uint8_t *ptr = sap_msg->payload;
+	struct sap_param *param = NULL;
+	uint16_t plen;
+	int i;
+
+	/* We assume that message is parsed already,
+	 * so we don't check for buffer overflows */
+	for (i = 0; i < sap_msg->num_params; i++) {
+		/* Parse one parameter */
+		param = (struct sap_param *) ptr;
+		plen = ntohs(param->length);
+
+		/* Match against a given ID */
+		if (param->param_id == param_type) {
+			if (param_len != NULL)
+				*param_len = plen;
+			return param;
+		}
+
+		/* Shift pointer to the next parameter */
+		ptr += sizeof(*param) + plen;
+		/* Optional padding */
+		ptr += 4 - (plen % 4);
+	}
+
+	return NULL;
+}
+
+const struct sap_message *sap_msg_parse(const uint8_t *buf, size_t buf_len)
+{
+	const struct sap_message *sap_msg;
+	const uint8_t *ptr;
+	int i;
+
+	/* Message header is mandatory */
+	if (buf_len < sizeof(*sap_msg)) {
+		LOGP(DSAP, LOGL_ERROR, "Missing SAP message header\n");
+		return NULL;
+	}
+
+	sap_msg = (const struct sap_message *) buf;
+	buf_len -= sizeof(*sap_msg);
+	ptr = sap_msg->payload;
+
+	LOGP(DSAP, LOGL_DEBUG, "SAP message '%s' has %u parameter(s)\n",
+		get_value_string(sap_msg_names, sap_msg->msg_id),
+		sap_msg->num_params);
+
+	for (i = 0; i < sap_msg->num_params; i++) {
+		struct sap_param *param;
+		uint16_t param_len;
+		uint16_t offset;
+
+		/* Prevent buffer overflow */
+		if (buf_len < sizeof(*param))
+			goto malformed;
+
+		/* Parse one parameter */
+		param = (struct sap_param *) ptr;
+		param_len = ntohs(param->length);
+
+		LOGP(DSAP, LOGL_DEBUG, "SAP parameter '%s' (len=%u): %s\n",
+			get_value_string(sap_param_names, param->param_id),
+			param_len, osmo_hexdump(param->value, param_len));
+
+		/* Calculate relative offset */
+		offset  = sizeof(*param) + param_len;
+		offset += 4 - (param_len % 4); /* Optional padding */
+
+		/* Prevent buffer overflow */
+		if (offset > buf_len)
+			goto malformed;
+
+		buf_len -= offset;
+		ptr += offset;
+	}
+
+	return sap_msg;
+
+malformed:
+	LOGP(DSAP, LOGL_ERROR, "Malformed SAP message "
+		"(parameter %i/%u)\n", i + 1, sap_msg->num_params);
+	return NULL;
+}
+
+int sap_check_result_code(const struct sap_message *sap_msg)
+{
+	struct sap_param *param;
+	uint16_t param_len;
+	uint8_t res_code;
+
+	param = sap_get_param(sap_msg, SAP_RESULT_CODE, &param_len);
+	if (!param || param_len != sizeof(res_code)) {
+		LOGP(DSAP, LOGL_ERROR, "Missing mandatory '%s' parameter\n",
+			get_value_string(sap_param_names, SAP_RESULT_CODE));
+		return -EINVAL;
+	}
+
+	res_code = param->value[0];
+	if (res_code > ARRAY_SIZE(sap_result_names)) {
+		LOGP(DSAP, LOGL_ERROR, "Unknown SAP ResultCode=0x%02x\n", res_code);
+		return -EINVAL;
+	}
+
+	LOGP(DSAP, LOGL_DEBUG, "SAP ResultCode is '%s'\n",
+		get_value_string(sap_result_names, res_code));
+
+	return res_code;
+}
diff --git a/src/host/layer23/src/common/sim.c b/src/host/layer23/src/common/sim.c
index 9d14cd3..a63ae2a 100644
--- a/src/host/layer23/src/common/sim.c
+++ b/src/host/layer23/src/common/sim.c
@@ -205,7 +205,7 @@
 	 * it makes more sense to do it here then in L1CTL */
 	if (ms->subscr.sim_type == GSM_SIM_TYPE_SAP) {
 		LOGP(DSIM, LOGL_INFO, "Using SAP backend\n");
-		rc = osmosap_send_apdu(ms, data, length);
+		rc = sap_send_apdu(ms, data, length);
 	} else {
 		LOGP(DSIM, LOGL_INFO, "Using built-in SIM reader\n");
 		rc = l1ctl_tx_sim_req(ms, data, length);
diff --git a/src/host/layer23/src/mobile/app_mobile.c b/src/host/layer23/src/mobile/app_mobile.c
index 17c0c76..1f473a7 100644
--- a/src/host/layer23/src/mobile/app_mobile.c
+++ b/src/host/layer23/src/mobile/app_mobile.c
@@ -200,7 +200,11 @@
 	lapdm_channel_set_l1(&ms->lapdm_channel, l1ctl_ph_prim_cb, ms);
 
 	/* init SAP client before SIM card starts up */
-	osmosap_init(ms);
+	sap_init(ms);
+
+	/* TODO: remove SIM card on sap_entity.rel_cb() */
+	ms->sap_entity.idle_cb = &subscr_sim_request;
+	ms->sap_entity.apdu_cb = &sim_apdu_resp;
 
 	gsm_sim_init(ms);
 	gsm48_cc_init(ms);
diff --git a/src/host/layer23/src/mobile/subscriber.c b/src/host/layer23/src/mobile/subscriber.c
index b2be554..e8c0ae3 100644
--- a/src/host/layer23/src/mobile/subscriber.c
+++ b/src/host/layer23/src/mobile/subscriber.c
@@ -544,7 +544,7 @@
 };
 
 /* request file from SIM */
-static int subscr_sim_request(struct osmocom_ms *ms)
+int subscr_sim_request(struct osmocom_ms *ms)
 {
 	struct gsm_subscriber *subscr = &ms->subscr;
 	struct subscr_sim_file *sf = &subscr_sim_files[subscr->sim_file_index];
@@ -1275,6 +1275,7 @@
 	gsm_subscr_exit(ms);
 	gsm_subscr_init(ms);
 
+	subscr->ustate = GSM_SIM_U2_NOT_UPDATED;
 	subscr->sim_type = GSM_SIM_TYPE_SAP;
 	sprintf(subscr->sim_name, "sap");
 	subscr->sim_valid = 1;
@@ -1297,8 +1298,6 @@
 
 		return rc;
 	}
-
-	return 0;
 }
 
 /* Deattach sapcard */

-- 
To view, visit https://gerrit.osmocom.org/12441
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: osmocom-bb
Gerrit-Branch: master
Gerrit-MessageType: newchange
Gerrit-Change-Id: I77bb108615bb2c94c441568f195b04e0a5421643
Gerrit-Change-Number: 12441
Gerrit-PatchSet: 1
Gerrit-Owner: Vadim Yanitskiy <axilirator at gmail.com>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20181224/2381f59f/attachment.htm>


More information about the gerrit-log mailing list