319 lines
9.9 KiB
C
319 lines
9.9 KiB
C
/*
|
|
* SAP (SIM Access Profile) protocol definition
|
|
* based on Bluetooth SAP specification
|
|
*
|
|
* (C) 2011 by Nico Golde <nico@ngolde.de>
|
|
* (C) 2018 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 <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <errno.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <osmocom/core/logging.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.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" },
|
|
{ 0, NULL }
|
|
};
|
|
|
|
/* 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" },
|
|
{ 0, NULL }
|
|
};
|
|
|
|
/* 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 "},
|
|
{ 0, NULL }
|
|
};
|
|
|
|
/* 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_RESET, "Card reset" },
|
|
{ SAP_CARD_STATUS_NOT_ACC, "Card not accessible" },
|
|
{ SAP_CARD_STATUS_REMOVED, "Card removed" },
|
|
{ SAP_CARD_STATUS_INSERTED, "Card inserted" },
|
|
{ SAP_CARD_STATUS_RECOVERED, "Card recovered" },
|
|
{ 0, NULL }
|
|
};
|
|
|
|
/* 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" },
|
|
{ 0, NULL }
|
|
};
|
|
|
|
/*! Allocate a new message buffer with SAP message header.
|
|
* \param[in] msg_id SAP message identifier
|
|
* \returns message buffer in case of success, NULL otherwise
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/*! Add a new parameter to a given SAP message buffer.
|
|
* Padding is added automatically, SAP message header
|
|
* (number of parameters) is also updated automatically.
|
|
* \param[in] msg SAP message buffer
|
|
* \param[in] param_type parameter type (see sap_param_type enum)
|
|
* \param[in] param_len parameter length
|
|
* \param[in] param_value pointer to parameter value
|
|
*/
|
|
void sap_msgb_add_param(struct msgb *msg,
|
|
enum sap_param_type param_type,
|
|
uint16_t param_len, const 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);
|
|
}
|
|
}
|
|
|
|
/*! Attempt to find a given parameter in a given SAP message.
|
|
* \param[in] sap_msg pointer to SAP message header
|
|
* \param[in] param_type parameter type (see sap_param_type enum)
|
|
* \param[out] param_len parameter length (if found)
|
|
* \returns pointer to a given parameter within the message, NULL otherwise
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/*! Parse SAP message from a given buffer into a new message buffer.
|
|
* \param[in] buf pointer to a buffer with to be parsed message
|
|
* \param[in] buf_len length of the buffer
|
|
* \param[in] max_msg_size max (negotiated) message size
|
|
* \returns new message buffer with parsed message, NULL otherwise
|
|
*/
|
|
struct msgb *sap_msg_parse(const uint8_t *buf, size_t buf_len, int max_msg_size)
|
|
{
|
|
const struct sap_message *sap_msg;
|
|
const uint8_t *ptr;
|
|
struct msgb *msg;
|
|
size_t len;
|
|
int i;
|
|
|
|
/* Message header is mandatory */
|
|
if (buf_len < sizeof(*sap_msg)) {
|
|
LOGP(DSAP, LOGL_ERROR, "Missing SAP message header\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* MaxMsgSize limitation (optional) */
|
|
if (max_msg_size > 0 && buf_len > max_msg_size) {
|
|
LOGP(DSAP, LOGL_ERROR, "Buffer (len=%zu) is bigger than "
|
|
"given MaxMsgSize=%d\n", buf_len, max_msg_size);
|
|
return NULL;
|
|
}
|
|
|
|
sap_msg = (const struct sap_message *) buf;
|
|
len = 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 (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 > len)
|
|
goto malformed;
|
|
|
|
len -= offset;
|
|
ptr += offset;
|
|
}
|
|
|
|
/* Allocate a new message buffer */
|
|
msg = msgb_alloc(GSM_SAP_LENGTH, "sap_msg");
|
|
if (!msg) {
|
|
LOGP(DSAP, LOGL_ERROR, "Failed to allocate SAP message\n");
|
|
return NULL;
|
|
}
|
|
|
|
msg->data = msgb_put(msg, buf_len);
|
|
memcpy(msg->data, buf, buf_len);
|
|
|
|
return msg;
|
|
|
|
malformed:
|
|
LOGP(DSAP, LOGL_ERROR, "Malformed SAP message "
|
|
"(parameter %i/%u)\n", i + 1, sap_msg->num_params);
|
|
return NULL;
|
|
}
|
|
|
|
/*! Parse ResultCode from a given SAP message.
|
|
* \param[in] sap_msg pointer to SAP message header
|
|
* \returns parsed ResultCode (if found), negative otherwise
|
|
*/
|
|
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, ¶m_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;
|
|
}
|