summaryrefslogtreecommitdiffstats
path: root/src/host/layer23/src/common/sap_proto.c
blob: c3d202f664e1875ca0777d497689e668f194a697 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
/*
 * 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.
 *
 * 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/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 withing 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, &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;
}