688 lines
19 KiB
C
688 lines
19 KiB
C
/*
|
|
* SAP (SIM Access Profile) FSM definition
|
|
* based on Bluetooth SAP specification
|
|
*
|
|
* (C) 2018-2019 by Vadim Yanitskiy <axilirator@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.
|
|
*
|
|
*/
|
|
|
|
#include <errno.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_disconnect(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, 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, ¶m_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 = osmo_load16be(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)
|
|
{
|
|
struct sap_message *sap_msg = (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, ¶m_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 ((enum sap_conn_status_type) status) {
|
|
case SAP_CONN_STATUS_OK_CALL:
|
|
ms->sap_entity.card_status = SAP_CARD_STATUS_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 msgb *msg;
|
|
|
|
LOGP(DSAP, LOGL_DEBUG, "Initiating 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((struct osmocom_ms *) fi->priv, msg);
|
|
}
|
|
|
|
static void sap_fsm_release_handler(struct osmo_fsm_inst *fi,
|
|
uint32_t event, void *data)
|
|
{
|
|
LOGP(DSAP, LOGL_DEBUG, "Connection 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)
|
|
{
|
|
struct osmocom_ms *ms = (struct osmocom_ms *) fi->priv;
|
|
|
|
switch ((enum sap_fsm_state) prev_state) {
|
|
case SAP_STATE_CONNECTING:
|
|
case SAP_STATE_WAIT_FOR_CARD:
|
|
/* According to 4.1, if a subscription module is inserted
|
|
* in the Server and powered on (i.e. STATUS_IND message
|
|
* indicates "Card reset" state), the Client shall request
|
|
* the ATR of the subscription module. */
|
|
if (ms->sap_entity.card_status == SAP_CARD_STATUS_RESET)
|
|
sap_send_atr_req(ms);
|
|
break;
|
|
default:
|
|
/* Do nothing, suppress compiler warning */
|
|
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 ((enum sap_msg_type) 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_response_handler(struct osmo_fsm_inst *fi,
|
|
uint32_t event, void *data)
|
|
{
|
|
struct sap_message *sap_msg = (struct sap_message *) data;
|
|
struct osmocom_ms *ms = (struct osmocom_ms *) fi->priv;
|
|
struct sap_param *param = NULL;
|
|
uint16_t param_len = 0;
|
|
int param_id, rc;
|
|
|
|
switch ((enum sap_msg_type) event) {
|
|
/* Both POWER_SIM_OFF_REQ and RESET_SIM_REQ can be sent in nearly
|
|
* any state, in order to allow the Client to reactivate
|
|
* a not accessible subscription module card. */
|
|
case SAP_POWER_SIM_OFF_REQ:
|
|
case SAP_RESET_SIM_REQ:
|
|
OSMO_ASSERT(data != NULL);
|
|
goto request;
|
|
|
|
/* Messages without parameters */
|
|
case SAP_SET_TRANSPORT_PROTOCOL_RESP:
|
|
case SAP_POWER_SIM_OFF_RESP:
|
|
case SAP_POWER_SIM_ON_RESP:
|
|
case SAP_RESET_SIM_RESP:
|
|
param_id = -1;
|
|
break;
|
|
|
|
case SAP_TRANSFER_CARD_READER_STATUS_RESP:
|
|
param_id = SAP_CARD_READER_STATUS;
|
|
break;
|
|
case SAP_TRANSFER_APDU_RESP:
|
|
param_id = SAP_RESPONSE_APDU;
|
|
break;
|
|
case SAP_TRANSFER_ATR_RESP:
|
|
param_id = SAP_ATR;
|
|
break;
|
|
|
|
default:
|
|
/* Shall not happen */
|
|
OSMO_ASSERT(0);
|
|
}
|
|
|
|
/* We're done with 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));
|
|
goto response;
|
|
}
|
|
|
|
if (param_id < 0)
|
|
goto response;
|
|
|
|
param = sap_get_param(sap_msg, param_id, ¶m_len);
|
|
if (!param) {
|
|
LOGP(DSAP, LOGL_ERROR, "Message '%s' missing "
|
|
"mandatory parameter '%s'\n",
|
|
get_value_string(sap_msg_names, sap_msg->msg_id),
|
|
get_value_string(sap_param_names, param_id));
|
|
rc = -EINVAL;
|
|
goto response;
|
|
}
|
|
|
|
response:
|
|
/* Poke optional response handler */
|
|
if (ms->sap_entity.sap_rsp_cb != NULL) {
|
|
if (param != NULL) {
|
|
ms->sap_entity.sap_rsp_cb(ms, rc,
|
|
sap_msg->msg_id, param_len, param->value);
|
|
} else {
|
|
ms->sap_entity.sap_rsp_cb(ms, rc,
|
|
sap_msg->msg_id, 0, NULL);
|
|
}
|
|
}
|
|
|
|
return;
|
|
|
|
request:
|
|
rc = sap_send_msgb(ms, (struct msgb *) data);
|
|
if (rc)
|
|
return;
|
|
|
|
osmo_fsm_inst_state_chg(fi, event == SAP_RESET_SIM_REQ ?
|
|
SAP_STATE_PROC_RESET_REQ : SAP_STATE_PROC_POWEROFF_REQ,
|
|
SAP_FSM_PROC_REQ_TIMEOUT, SAP_FSM_PROC_REQ_T);
|
|
}
|
|
|
|
/* 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_disconnect,
|
|
},
|
|
[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_response_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_response_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_response_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_response_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_response_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_response_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_response_handler,
|
|
},
|
|
};
|
|
|
|
static void sap_fsm_tear_down(struct osmo_fsm_inst *fi,
|
|
enum osmo_fsm_term_cause cause)
|
|
{
|
|
struct osmocom_ms *ms = (struct osmocom_ms *) fi->priv;
|
|
|
|
/* Flush buffers, close socket */
|
|
_sap_close_sock(ms);
|
|
|
|
/* Reset SAP state */
|
|
ms->sap_entity.card_status = SAP_CARD_STATUS_NOT_ACC;
|
|
ms->sap_entity.max_msg_size = GSM_SAP_LENGTH;
|
|
ms->sap_entity.fi = NULL;
|
|
}
|
|
|
|
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, ¶m_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 ((enum sap_card_status_type) status) {
|
|
/* SIM card is ready */
|
|
case SAP_CARD_STATUS_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_RECOVERED:
|
|
if (fi->state != SAP_STATE_IDLE)
|
|
osmo_fsm_inst_state_chg(fi, SAP_STATE_IDLE, 0, 0);
|
|
break;
|
|
|
|
/* SIM card inserted, we need to power it on */
|
|
case SAP_CARD_STATUS_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_NOT_ACC:
|
|
case SAP_CARD_STATUS_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)
|
|
{
|
|
struct sap_message *sap_msg = (struct sap_message *) data;
|
|
|
|
switch ((enum sap_msg_type) 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 (!SAP_STATE_IS_ACTIVE(fi->state))
|
|
goto not_peritted;
|
|
|
|
sap_msg = NULL;
|
|
/* fall-through */
|
|
|
|
/* Disconnect initiated by the Client */
|
|
case SAP_DISCONNECT_REQ:
|
|
/* DISCONNECT_REQ has no parameters, so the caller
|
|
* shall not allocate the message manually. */
|
|
OSMO_ASSERT(sap_msg == NULL);
|
|
|
|
/* If we have no active connection, tear-down immediately */
|
|
if (!SAP_STATE_IS_ACTIVE(fi->state)) {
|
|
osmo_fsm_inst_state_chg(fi,
|
|
SAP_STATE_NOT_CONNECTED, 0, 0);
|
|
break;
|
|
}
|
|
|
|
/* Trigger Client-initiated 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 (!SAP_STATE_IS_ACTIVE(fi->state))
|
|
goto not_peritted;
|
|
|
|
sap_fsm_handle_card_status_ind(fi, sap_msg);
|
|
break;
|
|
|
|
case SAP_ERROR_RESP:
|
|
LOGP(DSAP, LOGL_NOTICE, "RX Error Response from Server\n");
|
|
|
|
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 ((enum sap_fsm_state) 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, "Timeout for 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,
|
|
.cleanup = &sap_fsm_tear_down,
|
|
.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),
|
|
};
|
|
|
|
/*! Allocate a new SAP state machine for a given ms.
|
|
* \param[in] ms MS instance associated with SAP FSM
|
|
* \returns 0 in case of success, negative in case of error
|
|
*/
|
|
int sap_fsm_alloc(struct osmocom_ms *ms)
|
|
{
|
|
struct osmosap_entity *sap;
|
|
|
|
sap = &ms->sap_entity;
|
|
OSMO_ASSERT(sap->fi == NULL);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
static __attribute__((constructor)) void on_dso_load(void)
|
|
{
|
|
OSMO_ASSERT(osmo_fsm_register(&sap_fsm_def) == 0);
|
|
}
|