/*
 * Copyright (C) 2011-2015 Frank Morgner
 *
 * This file is part of OpenSC.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "sm-iso-internal.h"
#include "libopensc/asn1.h"
#include "libopensc/log.h"
#include "sm/sm-iso.h"
#include <stdlib.h>
#include <string.h>

#ifdef ENABLE_SM

static const struct sc_asn1_entry c_sm_capdu[] = {
	{ "Cryptogram",
		SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x05, SC_ASN1_OPTIONAL, NULL, NULL },
	{ "Padding-content indicator followed by cryptogram",
		SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x07, SC_ASN1_OPTIONAL, NULL, NULL },
	{ "Protected Le",
		SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x17, SC_ASN1_OPTIONAL, NULL, NULL },
	{ "Cryptographic Checksum",
		SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x0E, SC_ASN1_OPTIONAL, NULL, NULL },
	{ NULL , 0 , 0 , 0 , NULL , NULL }
};

static const struct sc_asn1_entry c_sm_rapdu[] = {
	{ "Cryptogram",
		SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x05, SC_ASN1_OPTIONAL, NULL, NULL },
	{ "Padding-content indicator followed by cryptogram" ,
		SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x07, SC_ASN1_OPTIONAL, NULL, NULL },
	{ "Processing Status",
		SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x19, 0               , NULL, NULL },
	{ "Cryptographic Checksum",
		SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x0E, SC_ASN1_OPTIONAL, NULL, NULL },
	{ NULL, 0, 0, 0, NULL, NULL }
};

static int
add_iso_pad(const u8 *data, size_t datalen, int block_size, u8 **padded)
{
	u8 *p;
	size_t p_len;

	if (!padded)
		return SC_ERROR_INVALID_ARGUMENTS;

	/* calculate length of padded message */
	p_len = (datalen / block_size) * block_size + block_size;

	p = realloc(*padded, p_len);
	if (!p)
		return SC_ERROR_OUT_OF_MEMORY;

	if (*padded != data)
		/* Flawfinder: ignore */
		memcpy(p, data, datalen);

	*padded = p;

	/* now add iso padding */
	memset(p + datalen, 0x80, 1);
	memset(p + datalen + 1, 0, p_len - datalen - 1);

	return p_len;
}

static int
add_padding(const struct iso_sm_ctx *ctx, const u8 *data, size_t datalen,
		u8 **padded)
{
	u8 *p;

	switch (ctx->padding_indicator) {
		case SM_NO_PADDING:
			if (*padded != data) {
				if (datalen != 0) {
					p = realloc(*padded, datalen);
					if (!p)
						return SC_ERROR_OUT_OF_MEMORY;
					*padded = p;
					memcpy(*padded, data, datalen);
				} else {
					*padded = NULL;
				}
			}
			return datalen;
		case SM_ISO_PADDING:
			return add_iso_pad(data, datalen, ctx->block_length, padded);
		default:
			return SC_ERROR_INVALID_ARGUMENTS;
	}
}

static int
rm_padding(u8 padding_indicator, const u8 *data, size_t datalen)
{
	size_t len;

	if (!datalen || !data)
		return SC_ERROR_INVALID_ARGUMENTS;

	switch (padding_indicator) {
		case SM_NO_PADDING:
			len = datalen;
			break;

		case SM_ISO_PADDING:
			len = datalen;

			while (len) {
				len--;
				if (data[len])
					break;
			}

			if (data[len] != 0x80)
				return SC_ERROR_INVALID_DATA;

			break;

		default:
			return SC_ERROR_NOT_SUPPORTED;
	}

	return len;
}

static int format_le(size_t le, struct sc_asn1_entry *le_entry,
		u8 **lebuf, size_t *le_len)
{
	u8 *p;

	if (!lebuf || !le_len || !*le_len)
		return SC_ERROR_INVALID_ARGUMENTS;

	p = realloc(*lebuf, *le_len);
	if (!p)
		return SC_ERROR_OUT_OF_MEMORY;
	*lebuf = p;

	switch (*le_len) {
		case 1:
			p[0] = le & 0xff;
			break;
		case 2:
			p[0] = (le >> 8) & 0xff;
			p[1] = le & 0xff;
			break;
		case 3:
			p[0] = 0x00;
			p[1] = (le >> 8) & 0xff;
			p[2] = le & 0xff;
			break;
		default:
			return SC_ERROR_INVALID_ARGUMENTS;
	}

	sc_format_asn1_entry(le_entry, *lebuf, le_len, SC_ASN1_PRESENT);

	return SC_SUCCESS;
}

static int prefix_buf(u8 prefix, u8 *buf, size_t buflen, u8 **cat)
{
	u8 *p;

	p = realloc(*cat, buflen + 1);
	if (!p)
		return SC_ERROR_OUT_OF_MEMORY;

	if (*cat == buf) {
		memmove(p + 1, p, buflen);
	} else {
		/* Flawfinder: ignore */
		memcpy(p + 1, buf, buflen);
	}
	p[0] = prefix;

	*cat = p;

	return buflen + 1;
}

static int format_data(sc_card_t *card, const struct iso_sm_ctx *ctx,
		int prepend_padding_indicator, const u8 *data, size_t datalen,
		struct sc_asn1_entry *formatted_encrypted_data_entry,
		u8 **formatted_data, size_t *formatted_data_len)
{
	int r;
	u8 *pad_data = NULL;
	size_t pad_data_len = 0;

	if (!ctx || !formatted_data || !formatted_data_len) {
		r = SC_ERROR_INVALID_ARGUMENTS;
		goto err;
	}

	r = add_padding(ctx, data, datalen, &pad_data);
	if (r < 0) {
		sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Could not add padding to data: %s",
				sc_strerror(r));
		goto err;
	}
	pad_data_len = r;

	sc_log_hex(card->ctx, "Data to encrypt", pad_data, pad_data_len);
	r = ctx->encrypt(card, ctx, pad_data, pad_data_len, formatted_data);
	if (r < 0) {
		sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Could not encrypt the data");
		goto err;
	}
	sc_log_hex(card->ctx, "Cryptogram", *formatted_data, r);

	if (prepend_padding_indicator) {
		r = prefix_buf(ctx->padding_indicator, *formatted_data, r, formatted_data);
		if (r < 0) {
			sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Could not prepend padding indicator to formatted "
					"data: %s", sc_strerror(r));
			goto err;
		}
	}

	*formatted_data_len = r;
	sc_format_asn1_entry(formatted_encrypted_data_entry,
			*formatted_data, formatted_data_len, SC_ASN1_PRESENT);

	r = SC_SUCCESS;

err:
	if (pad_data) {
		sc_mem_clear(pad_data, pad_data_len);
		free(pad_data);
	}

	return r;
}

static int format_head(const struct iso_sm_ctx *ctx, const sc_apdu_t *apdu,
		u8 **formatted_head)
{
	u8 *p;

	if (!apdu || !formatted_head)
		return SC_ERROR_INVALID_ARGUMENTS;

	p = realloc(*formatted_head, 4);
	if (!p)
		return SC_ERROR_OUT_OF_MEMORY;

	p[0] = apdu->cla;
	p[1] = apdu->ins;
	p[2] = apdu->p1;
	p[3] = apdu->p2;
	*formatted_head = p;

	return add_padding(ctx, *formatted_head, 4, formatted_head);
}

static int sm_encrypt(const struct iso_sm_ctx *ctx, sc_card_t *card,
		const sc_apdu_t *apdu, sc_apdu_t **psm_apdu)
{
	struct sc_asn1_entry sm_capdu[5];
	u8 *p, *le = NULL, *sm_data = NULL, *fdata = NULL, *mac_data = NULL,
	   *asn1 = NULL, *mac = NULL, *resp_data = NULL;
	size_t sm_data_len, fdata_len, mac_data_len, asn1_len, mac_len, le_len;
	int r;
	sc_apdu_t *sm_apdu = NULL;

	if (!apdu || !ctx || !card || !card->reader || !psm_apdu) {
		r = SC_ERROR_INVALID_ARGUMENTS;
		goto err;
	}

	if ((apdu->cla & 0x0C) == 0x0C) {
		r = SC_ERROR_INVALID_ARGUMENTS;
		sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Given APDU is already protected with some secure messaging");
		goto err;
	}

	sc_copy_asn1_entry(c_sm_capdu, sm_capdu);

	sm_apdu = malloc(sizeof(sc_apdu_t));
	if (!sm_apdu) {
		r = SC_ERROR_OUT_OF_MEMORY;
		goto err;
	}
	sm_apdu->control = apdu->control;
	sm_apdu->flags = apdu->flags;
	sm_apdu->cla = apdu->cla|0x0C;
	sm_apdu->ins = apdu->ins;
	sm_apdu->p1 = apdu->p1;
	sm_apdu->p2 = apdu->p2;
	r = format_head(ctx, sm_apdu, &mac_data);
	if (r < 0) {
		sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Could not format header of SM apdu");
		goto err;
	}
	mac_data_len = r;

	switch (apdu->cse) {
		case SC_APDU_CASE_1:
			break;
	case SC_APDU_CASE_2_SHORT:
			le_len = 1;
			r = format_le(apdu->le, sm_capdu + 2, &le, &le_len);
			if (r < 0) {
				sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Could not format Le of SM apdu");
				goto err;
			}
			sc_log_hex(card->ctx, "Protected Le (plain)", le, le_len);
			break;
	case SC_APDU_CASE_2_EXT:
			if (card->reader->active_protocol == SC_PROTO_T0) {
				/* T0 extended APDUs look just like short APDUs */
				le_len = 1;
				r = format_le(apdu->le, sm_capdu + 2, &le, &le_len);
				if (r < 0) {
					sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Could not format Le of SM apdu");
					goto err;
				}
			} else {
				/* in case of T1 always use 2 bytes for length */
				le_len = 2;
				r = format_le(apdu->le, sm_capdu + 2, &le, &le_len);
				if (r < 0) {
					sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Could not format Le of SM apdu");
					goto err;
				}
			}
			sc_log_hex(card->ctx, "Protected Le (plain)", le, le_len);
			break;
		case SC_APDU_CASE_3_SHORT:
		case SC_APDU_CASE_3_EXT:
			if (apdu->ins & 1) {
				r = format_data(card, ctx, 0, apdu->data, apdu->datalen,
						sm_capdu + 0, &fdata, &fdata_len);
			} else {
				r = format_data(card, ctx, 1, apdu->data, apdu->datalen,
						sm_capdu + 1, &fdata, &fdata_len);
			}
			if (r < 0) {
				sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Could not format data of SM apdu");
				goto err;
			}
			sc_log_hex(card->ctx, "Padding-content indicator followed by cryptogram (plain)",
					fdata, fdata_len);
			break;
		case SC_APDU_CASE_4_SHORT:
			/* in case of T0 no Le byte is added */
			if (card->reader->active_protocol != SC_PROTO_T0) {
				le_len = 1;
				r = format_le(apdu->le, sm_capdu + 2, &le, &le_len);
				if (r < 0) {
					sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Could not format Le of SM apdu");
					goto err;
				}
				sc_log_hex(card->ctx, "Protected Le (plain)", le, le_len);
			}

			if (apdu->ins & 1) {
				r = format_data(card, ctx, 0, apdu->data, apdu->datalen,
						sm_capdu + 0, &fdata, &fdata_len);
			} else {
				r = format_data(card, ctx, 1, apdu->data, apdu->datalen,
						sm_capdu + 1, &fdata, &fdata_len);
			}
			if (r < 0) {
				sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Could not format data of SM apdu");
				goto err;
			}
			sc_log_hex(card->ctx, "Padding-content indicator followed by cryptogram (plain)",
					fdata, fdata_len);
			break;
		case SC_APDU_CASE_4_EXT:
			if (card->reader->active_protocol == SC_PROTO_T0) {
				/* again a T0 extended case 4 APDU looks just
				 * like a short APDU, the additional data is
				 * transferred using ENVELOPE and GET RESPONSE */
			} else {
				/* only 2 bytes are use to specify the length of the
				 * expected data */
				le_len = 2;
				r = format_le(apdu->le, sm_capdu + 2, &le, &le_len);
				if (r < 0) {
					sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Could not format Le of SM apdu");
					goto err;
				}
				sc_log_hex(card->ctx, "Protected Le (plain)", le, le_len);
			}

			if (apdu->ins & 1) {
				r = format_data(card, ctx, 0, apdu->data, apdu->datalen,
						sm_capdu + 0, &fdata, &fdata_len);
			} else {
				r = format_data(card, ctx, 1, apdu->data, apdu->datalen,
						sm_capdu + 1, &fdata, &fdata_len);
			}
			if (r < 0) {
				sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Could not format data of SM apdu");
				goto err;
			}
			sc_log_hex(card->ctx, "Padding-content indicator followed by cryptogram (plain)",
					fdata, fdata_len);
			break;
		default:
			sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Unhandled apdu case");
			r = SC_ERROR_INVALID_DATA;
			goto err;
	}


	r = sc_asn1_encode(card->ctx, sm_capdu, (u8 **) &asn1, &asn1_len);
	if (r < 0) {
		goto err;
	}
	if (asn1_len) {
		p = realloc(mac_data, mac_data_len + asn1_len);
		if (!p) {
			r = SC_ERROR_OUT_OF_MEMORY;
			goto err;
		}
		mac_data = p;
		/* Flawfinder: ignore */
		memcpy(mac_data + mac_data_len, asn1, asn1_len);
		mac_data_len += asn1_len;
		r = add_padding(ctx, mac_data, mac_data_len, &mac_data);
		if (r < 0) {
			goto err;
		}
		mac_data_len = r;
	}
	sc_log_hex(card->ctx, "Data to authenticate", mac_data, mac_data_len);

	r = ctx->authenticate(card, ctx, mac_data, mac_data_len,
			&mac);
	if (r < 0) {
		sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Could not get authentication code");
		goto err;
	}
	mac_len = r;
	sc_log_hex(card->ctx, "Cryptographic Checksum (plain)", mac, mac_len);


	/* format SM apdu */
	sc_format_asn1_entry(sm_capdu + 3, mac, &mac_len, SC_ASN1_PRESENT);
	r = sc_asn1_encode(card->ctx, sm_capdu, (u8 **) &sm_data, &sm_data_len);
	if (r < 0)
		goto err;
	sm_apdu->data = sm_data;
	sm_apdu->datalen = sm_data_len;
	sm_apdu->lc = sm_data_len;
	sm_apdu->le = 0;
	/* for encrypted APDUs we usually get authenticated status bytes (4B), a
	 * MAC (2B without data) and a cryptogram with padding indicator (2B tag
	 * and indicator, max. 2B/3B ASN.1 length, without data). The cryptogram is
	 * always padded to the block size. */
	if (apdu->cse & SC_APDU_EXT) {
		sm_apdu->cse = SC_APDU_CASE_4_EXT;
		sm_apdu->resplen = 4 + 2 + mac_len + 2 + 3 + ((apdu->resplen+1)/ctx->block_length+1)*ctx->block_length;
	} else {
		sm_apdu->cse = SC_APDU_CASE_4_SHORT;
		sm_apdu->resplen = 4 + 2 + mac_len + 2 + 2 + ((apdu->resplen+1)/ctx->block_length+1)*ctx->block_length;
	}
	resp_data = calloc(sm_apdu->resplen, 1);
	if (!resp_data) {
		r = SC_ERROR_OUT_OF_MEMORY;
		goto err;
	}
	sm_apdu->resp = resp_data;
	sc_log_hex(card->ctx, "ASN.1 encoded encrypted APDU data", sm_apdu->data, sm_apdu->datalen);

	*psm_apdu = sm_apdu;

err:
	free(fdata);
	free(asn1);
	free(mac_data);
	free(mac);
	free(le);
	if (r < 0) {
		free(resp_data);
		free(sm_apdu);
		free(sm_data);
	}

	return r;
}

static int sm_decrypt(const struct iso_sm_ctx *ctx, sc_card_t *card,
		const sc_apdu_t *sm_apdu, sc_apdu_t *apdu)
{
	int r;
	struct sc_asn1_entry sm_rapdu[5];
	struct sc_asn1_entry my_sm_rapdu[5];
	u8 sw[2], mac[8], fdata[SC_MAX_EXT_APDU_BUFFER_SIZE];
	size_t sw_len = sizeof sw, mac_len = sizeof mac, fdata_len = sizeof fdata,
		   buf_len, asn1_len, fdata_offset = 0;
	const u8 *buf;
	u8 *data = NULL, *mac_data = NULL, *asn1 = NULL;

	sc_copy_asn1_entry(c_sm_rapdu, sm_rapdu);
	sc_format_asn1_entry(sm_rapdu + 0, fdata, &fdata_len, 0);
	sc_format_asn1_entry(sm_rapdu + 1, fdata, &fdata_len, 0);
	sc_format_asn1_entry(sm_rapdu + 2, sw, &sw_len, 0);
	sc_format_asn1_entry(sm_rapdu + 3, mac, &mac_len, 0);

	r = sc_asn1_decode(card->ctx, sm_rapdu, sm_apdu->resp, sm_apdu->resplen,
			&buf, &buf_len);
	if (r < 0)
		goto err;
	if (buf_len > 0) {
		r = SC_ERROR_UNKNOWN_DATA_RECEIVED;
		goto err;
	}


	if (sm_rapdu[3].flags & SC_ASN1_PRESENT) {
		/* copy from sm_apdu to my_sm_apdu, but leave mac at default */
		sc_copy_asn1_entry(sm_rapdu, my_sm_rapdu);
		sc_copy_asn1_entry(&c_sm_rapdu[3], &my_sm_rapdu[3]);

		r = sc_asn1_encode(card->ctx, my_sm_rapdu, &asn1, &asn1_len);
		if (r < 0)
			goto err;
		r = add_padding(ctx, asn1, asn1_len, &mac_data);
		if (r < 0) {
			goto err;
		}
		
		r = ctx->verify_authentication(card, ctx, mac, mac_len,
				mac_data, r);
		if (r < 0)
			goto err;
	} else {
		sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Cryptographic Checksum missing");
		r = SC_ERROR_ASN1_OBJECT_NOT_FOUND;
		goto err;
	}


	if (sm_rapdu[1].flags & SC_ASN1_PRESENT) {
		if (ctx->padding_indicator != fdata[0]) {
			r = SC_ERROR_UNKNOWN_DATA_RECEIVED;
			goto err;
		}
		fdata_offset = 1;
	}
	if (sm_rapdu[0].flags & SC_ASN1_PRESENT
			|| sm_rapdu[1].flags & SC_ASN1_PRESENT) {
		r = ctx->decrypt(card, ctx, fdata + fdata_offset,
				fdata_len - fdata_offset, &data);
		if (r < 0)
			goto err;
		buf_len = r;

		r = rm_padding(ctx->padding_indicator, data, buf_len);
		if (r < 0) {
			sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Could not remove padding");
			goto err;
		}

		if (apdu->resplen < (size_t) r || (r && !apdu->resp)) {
			sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE,
					"Response of SM APDU %"SC_FORMAT_LEN_SIZE_T"u byte%s too long",
					r-apdu->resplen,
					r-apdu->resplen < 2 ? "" : "s");
			r = SC_ERROR_OUT_OF_MEMORY;
			goto err;
		}
		/* Flawfinder: ignore */
		memcpy(apdu->resp, data, r);
		apdu->resplen = r;
	} else {
		apdu->resplen = 0;
	}

	if (sm_rapdu[2].flags & SC_ASN1_PRESENT) {
		if (sw_len != 2) {
			sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Length of processing status bytes must be 2");
			r = SC_ERROR_ASN1_END_OF_CONTENTS;
			goto err;
		}
		apdu->sw1 = sw[0];
		apdu->sw2 = sw[1];
	} else {
		sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Authenticated status bytes are missing");
		r = SC_ERROR_ASN1_OBJECT_NOT_FOUND;
		goto err;
	}

	sc_log(card->ctx,  "Decrypted APDU sw1=%02x sw2=%02x",
			apdu->sw1, apdu->sw2);
	sc_log_hex(card->ctx, "Decrypted APDU response data",
			apdu->resp, apdu->resplen);

	r = SC_SUCCESS;

err:
	free(asn1);
	free(mac_data);
	if (data) {
		sc_mem_clear(data, buf_len);
		free(data);
	}

	return r;
}

static int iso_add_sm(struct iso_sm_ctx *sctx, sc_card_t *card,
		sc_apdu_t *apdu, sc_apdu_t **sm_apdu)
{
	if (!card || !sctx)
		return SC_ERROR_INVALID_ARGUMENTS;

	if ((apdu->cla & 0x0C) == 0x0C) {
		sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Given APDU is already protected with some secure messaging. Closing own SM context.");
		LOG_TEST_RET(card->ctx, sc_sm_stop(card),
				"Could not close ISO SM session");
		return SC_ERROR_SM_NOT_APPLIED;
	}

	if (sctx->pre_transmit)
		LOG_TEST_RET(card->ctx, sctx->pre_transmit(card, sctx, apdu),
				"Could not complete SM specific pre transmit routine");
	LOG_TEST_RET(card->ctx, sm_encrypt(sctx, card, apdu, sm_apdu),
			"Could not encrypt APDU");

	return SC_SUCCESS;
}

static int iso_rm_sm(struct iso_sm_ctx *sctx, sc_card_t *card,
		sc_apdu_t *sm_apdu, sc_apdu_t *apdu)
{
	if (!sctx)
		LOG_TEST_RET(card->ctx, SC_ERROR_INVALID_ARGUMENTS,
			"Invalid SM context. No SM processing performed.");

	if (sctx->post_transmit)
		LOG_TEST_RET(card->ctx, sctx->post_transmit(card, sctx, sm_apdu),
				"Could not complete SM specific post transmit routine");
	LOG_TEST_RET(card->ctx, sm_decrypt(sctx, card, sm_apdu, apdu),
			"Could not decrypt APDU");
	if (sctx->finish)
		LOG_TEST_RET(card->ctx, sctx->finish(card, sctx, apdu),
				"Could not complete SM specific post transmit routine");

	return SC_SUCCESS;
}

int iso_sm_close(struct sc_card *card)
{
	if (card) {
		iso_sm_ctx_clear_free(card->sm_ctx.info.cmd_data);
		card->sm_ctx.info.cmd_data = NULL;
	}

	return SC_SUCCESS;
}

int iso_get_sm_apdu(struct sc_card *card, struct sc_apdu *apdu, struct sc_apdu **sm_apdu)
{
	return iso_add_sm(card->sm_ctx.info.cmd_data, card, apdu, sm_apdu);
}

int iso_free_sm_apdu(struct sc_card *card, struct sc_apdu *apdu, struct sc_apdu **sm_apdu)
{
	struct sc_apdu *p;
	int r;

	if (!sm_apdu)
		return SC_ERROR_INVALID_ARGUMENTS;

	p = *sm_apdu;

	r = iso_rm_sm(card->sm_ctx.info.cmd_data, card, p, apdu);

	if (p) {
		free((unsigned char *) p->data);
		free((unsigned char *) p->resp);
	}
	free(*sm_apdu);
	*sm_apdu = NULL;

	return r;
}

struct iso_sm_ctx *iso_sm_ctx_create(void)
{
	struct iso_sm_ctx *sctx = malloc(sizeof *sctx);
	if (!sctx)
		return NULL;

	sctx->priv_data = NULL;
	sctx->padding_indicator = SM_ISO_PADDING;
	sctx->block_length = 0;
	sctx->authenticate = NULL;
	sctx->verify_authentication = NULL;
	sctx->encrypt = NULL;
	sctx->decrypt = NULL;
	sctx->pre_transmit = NULL;
	sctx->post_transmit = NULL;
	sctx->finish = NULL;
	sctx->clear_free = NULL;

	return sctx;
}

void iso_sm_ctx_clear_free(struct iso_sm_ctx *sctx)
{
	if (sctx && sctx->clear_free)
		sctx->clear_free(sctx);
	free(sctx);
}

int iso_sm_start(struct sc_card *card, struct iso_sm_ctx *sctx)
{
	if (!card)
		return SC_ERROR_INVALID_ARGUMENTS;

	if (card->sm_ctx.ops.close)
		card->sm_ctx.ops.close(card);

	card->sm_ctx.info.cmd_data = sctx;
	card->sm_ctx.ops.close = iso_sm_close;
	card->sm_ctx.ops.free_sm_apdu = iso_free_sm_apdu;
	card->sm_ctx.ops.get_sm_apdu = iso_get_sm_apdu;
	card->sm_ctx.sm_mode = SM_MODE_TRANSMIT;

	return SC_SUCCESS;
}

#else

int iso_sm_close(struct sc_card *card)
{
	return SC_ERROR_NOT_SUPPORTED;
}

int iso_get_sm_apdu(struct sc_card *card, struct sc_apdu *apdu, struct sc_apdu **sm_apdu)
{
	return SC_ERROR_NOT_SUPPORTED;
}

struct iso_sm_ctx *iso_sm_ctx_create(void)
{
	return NULL;
}

void iso_sm_ctx_clear_free(struct iso_sm_ctx *sctx)
{
}

int iso_sm_start(struct sc_card *card, struct iso_sm_ctx *sctx)
{
	return SC_ERROR_NOT_SUPPORTED;
}

#endif
