/*
 * boxing.c: implementation related to boxing commands with pseudo APDUs
 *
 * Copyright (C) 2013  Frank Morgner  <frankmorgner@gmail.com>
 *
 * This file is part of libnpa.
 *
 * libnpa 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 3 of the License, or (at your option) any later
 * version.
 *
 * libnpa 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
 * libnpa.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "ccid-types.h"
#include <libopensc/asn1.h>
#include <libopensc/log.h>
#include <libopensc/opensc.h>
#include <libopensc/pace.h>
#include <npa/boxing.h>
#include <npa/scutil.h>
#include <stdlib.h>
#include <string.h>

#if _WIN32
/* FIXME might not always work */
#define htole16(x) (x)
#define htole32(x) (x)
#elif __APPLE__
#include <libkern/OSByteOrder.h>
#define htole16(x) OSSwapHostToLittleInt16(x)
#define htole32(x) OSSwapHostToLittleInt32(x)
#else
#ifndef _BSD_SOURCE
#define _BSD_SOURCE             /* See feature_test_macros(7) */
#endif
#include <endian.h>
#endif

static const u8 boxing_cla                          = 0xff;
static const u8 boxing_ins                          = 0x9a;
static const u8 boxing_p1                           = 0x04;
static const u8 boxing_p2_GetReaderPACECapabilities = 0x01;
static const u8 boxing_p2_EstablishPACEChannel      = 0x02;
static const u8 boxing_p2_DestroyPACEChannel        = 0x03;
static const u8 boxing_p2_PC_to_RDR_Secure          = 0x10;

struct sc_asn1_entry g_boolean[] = {
    { "boolean",
        SC_ASN1_BOOLEAN, SC_ASN1_TAG_BOOLEAN, 0, NULL, NULL },
    { NULL , 0 , 0 , 0 , NULL , NULL }
};
struct sc_asn1_entry g_int_as_octet_string[] = {
    { "int as octet string",
        SC_ASN1_OCTET_STRING, SC_ASN1_TAG_INTEGER, 0, NULL, NULL },
    { NULL , 0 , 0 , 0 , NULL , NULL }
};
struct sc_asn1_entry g_octet_string[] = {
    { "octet string",
        SC_ASN1_OCTET_STRING, SC_ASN1_TAG_OCTET_STRING, SC_ASN1_ALLOC, NULL, NULL },
    { NULL , 0 , 0 , 0 , NULL , NULL }
};
struct sc_asn1_entry g_numeric_string_as_octet_string[] = {
    { "utf8string",
        SC_ASN1_OCTET_STRING, SC_ASN1_TAG_NUMERICSTRING, SC_ASN1_ALLOC, NULL, NULL },
    { NULL , 0 , 0 , 0 , NULL , NULL }
};

static const struct sc_asn1_entry g_EstablishPACEChannelInput_data[] = {
    { "passwordID",
        /* use an OCTET STRING to avoid a conversion to int */
        SC_ASN1_STRUCT, SC_ASN1_CTX|0x01|SC_ASN1_CONS, 0, NULL, NULL },
    { "transmittedPassword",
        SC_ASN1_STRUCT, SC_ASN1_CTX|0x02|SC_ASN1_CONS, SC_ASN1_OPTIONAL|SC_ASN1_ALLOC, NULL, NULL },
    { "cHAT",
        SC_ASN1_STRUCT, SC_ASN1_CTX|0x03|SC_ASN1_CONS, SC_ASN1_OPTIONAL|SC_ASN1_ALLOC, NULL, NULL },
    { "certificateDescription",
        SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x04|SC_ASN1_CONS, SC_ASN1_OPTIONAL|SC_ASN1_ALLOC, NULL, NULL },
    { "hashOID",
        /* use an OCTET STRING to avoid a conversion to struct sc_object_id */
        SC_ASN1_STRUCT, SC_ASN1_CTX|0x05|SC_ASN1_CONS, SC_ASN1_OPTIONAL|SC_ASN1_ALLOC, NULL, NULL },
    { NULL , 0 , 0 , 0 , NULL , NULL }
};
static const struct sc_asn1_entry g_EstablishPACEChannelOutput_data[] = {
    { "errorCode",
        SC_ASN1_STRUCT, SC_ASN1_CTX|0x01|SC_ASN1_CONS, 0, NULL, NULL },
    { "statusMSESetAT",
        SC_ASN1_STRUCT, SC_ASN1_CTX|0x02|SC_ASN1_CONS, 0, NULL, NULL },
    { "efCardAccess",
        SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x03|SC_ASN1_CONS, SC_ASN1_ALLOC, NULL, NULL },
    { "idPICC",
        SC_ASN1_STRUCT, SC_ASN1_CTX|0x04|SC_ASN1_CONS, SC_ASN1_OPTIONAL|SC_ASN1_ALLOC, NULL, NULL },
    { "curCAR",
        SC_ASN1_STRUCT, SC_ASN1_CTX|0x05|SC_ASN1_CONS, SC_ASN1_OPTIONAL|SC_ASN1_ALLOC, NULL, NULL },
    { "prevCAR",
        SC_ASN1_STRUCT, SC_ASN1_CTX|0x06|SC_ASN1_CONS, SC_ASN1_OPTIONAL|SC_ASN1_ALLOC, NULL, NULL },
    { NULL , 0 , 0 , 0 , NULL , NULL }
};
static const struct sc_asn1_entry g_EstablishPACEChannel[] = {
    { "EstablishPACEChannel",
        SC_ASN1_STRUCT, SC_ASN1_TAG_SEQUENCE|SC_ASN1_CONS, 0, NULL, NULL },
    { NULL , 0 , 0 , 0 , NULL , NULL }
};

int boxing_pace_input_to_buf(sc_context_t *ctx,
        const struct establish_pace_channel_input *input,
        unsigned char **asn1, size_t *asn1_len)
{
    size_t pin_id_len = sizeof input->pin_id;
    struct sc_asn1_entry EstablishPACEChannelInput_data[
        sizeof g_EstablishPACEChannelInput_data/
        sizeof *g_EstablishPACEChannelInput_data];
    struct sc_asn1_entry EstablishPACEChannel[
        sizeof g_EstablishPACEChannel/
        sizeof *g_EstablishPACEChannel];
    struct sc_asn1_entry passwordID[
        sizeof g_int_as_octet_string/
        sizeof *g_int_as_octet_string];
    struct sc_asn1_entry transmittedPassword[
        sizeof g_numeric_string_as_octet_string/
        sizeof *g_numeric_string_as_octet_string];
    struct sc_asn1_entry cHAT[
        sizeof g_octet_string/
        sizeof *g_octet_string];

    sc_copy_asn1_entry(g_EstablishPACEChannel,
            EstablishPACEChannel);
    sc_format_asn1_entry(EstablishPACEChannel,
            EstablishPACEChannelInput_data, 0, 1);

    sc_copy_asn1_entry(g_EstablishPACEChannelInput_data,
            EstablishPACEChannelInput_data);

    sc_format_asn1_entry(EstablishPACEChannelInput_data+0,
            passwordID, 0, 1);
    sc_copy_asn1_entry(g_int_as_octet_string,
            passwordID);
    sc_format_asn1_entry(passwordID,
            (unsigned char *) &input->pin_id, &pin_id_len, 1);

    if (input->pin) {
        sc_format_asn1_entry(EstablishPACEChannelInput_data+1,
                transmittedPassword,
                0, 1);
        sc_copy_asn1_entry(g_numeric_string_as_octet_string,
                transmittedPassword);
        sc_format_asn1_entry(transmittedPassword,
                (unsigned char *) input->pin,
                (size_t *) &input->pin_length, 1);
    }

    if (input->chat) {
        sc_format_asn1_entry(EstablishPACEChannelInput_data+2,
                cHAT,
                0, 1);
        sc_copy_asn1_entry(g_octet_string,
                cHAT);
        sc_format_asn1_entry(cHAT,
                (unsigned char *) input->chat,
                (size_t *) &input->chat_length, 1);
    }

    if (input->certificate_description) {
        sc_format_asn1_entry(EstablishPACEChannelInput_data+3,
                (unsigned char *) input->certificate_description,
                (size_t *) &input->certificate_description_length, 1);
    }

    return sc_asn1_encode(ctx, EstablishPACEChannel, asn1, asn1_len);
}

int boxing_buf_to_pace_input(sc_context_t *ctx,
        const unsigned char *asn1, size_t asn1_len,
        struct establish_pace_channel_input *input)
{
    size_t pin_id_len = sizeof input->pin_id;
    struct sc_asn1_entry EstablishPACEChannelInput_data[
        sizeof g_EstablishPACEChannelInput_data/
        sizeof *g_EstablishPACEChannelInput_data];
    struct sc_asn1_entry EstablishPACEChannel[
        sizeof g_EstablishPACEChannel/
        sizeof *g_EstablishPACEChannel];
    struct sc_asn1_entry passwordID[
        sizeof g_int_as_octet_string/
        sizeof *g_int_as_octet_string];
    struct sc_asn1_entry transmittedPassword[
        sizeof g_numeric_string_as_octet_string/
        sizeof *g_numeric_string_as_octet_string];
    struct sc_asn1_entry cHAT[
        sizeof g_octet_string/
        sizeof *g_octet_string];
    /* FIXME handle hashOID */

    sc_copy_asn1_entry(g_EstablishPACEChannel,
            EstablishPACEChannel);
    sc_format_asn1_entry(EstablishPACEChannel,
            EstablishPACEChannelInput_data, 0, 0);

    sc_copy_asn1_entry(g_EstablishPACEChannelInput_data,
            EstablishPACEChannelInput_data);

    sc_format_asn1_entry(EstablishPACEChannelInput_data+0,
            passwordID, 0, 0);
    sc_copy_asn1_entry(g_int_as_octet_string,
            passwordID);
    sc_format_asn1_entry(passwordID,
            &input->pin_id, &pin_id_len, 0);

    if (input->pin) {
        sc_format_asn1_entry(EstablishPACEChannelInput_data+1,
                transmittedPassword, 0, 0);
        sc_copy_asn1_entry(g_numeric_string_as_octet_string,
                transmittedPassword);
        sc_format_asn1_entry(transmittedPassword,
                (unsigned char *) &input->pin, &input->pin_length, 0);
    }

    if (input->chat) {
        sc_format_asn1_entry(EstablishPACEChannelInput_data+2,
                cHAT, 0, 0);
        sc_copy_asn1_entry(g_octet_string,
                cHAT);
        sc_format_asn1_entry(cHAT,
                (unsigned char *) &input->chat, &input->chat_length, 0);
    }

    if (input->certificate_description) {
        sc_format_asn1_entry(EstablishPACEChannelInput_data+3,
                (unsigned char *) &input->certificate_description,
                &input->certificate_description_length, 0);
    }

    LOG_TEST_RET(ctx,
            sc_asn1_decode(ctx, EstablishPACEChannel, asn1, asn1_len, NULL, NULL),
            "Error decoding EstablishPACEChannel");

    if (pin_id_len != sizeof input->pin_id)
        return SC_ERROR_UNKNOWN_DATA_RECEIVED;

    return SC_SUCCESS;
}

int boxing_pace_output_to_buf(sc_context_t *ctx,
        const struct establish_pace_channel_output *output,
        unsigned char **asn1, size_t *asn1_len)
{
    uint16_t status_mse_set_at = ((output->mse_set_at_sw1 & 0xff) << 8) | output->mse_set_at_sw2;
    size_t result_len = sizeof output->result,
           status_mse_set_at_len = sizeof status_mse_set_at;
    struct sc_asn1_entry EstablishPACEChannelOutput_data[
        sizeof g_EstablishPACEChannelOutput_data/
        sizeof *g_EstablishPACEChannelOutput_data];
    struct sc_asn1_entry EstablishPACEChannel[
        sizeof g_EstablishPACEChannel/
        sizeof *g_EstablishPACEChannel];
    struct sc_asn1_entry errorCode[
        sizeof g_octet_string/
        sizeof *g_octet_string];
    struct sc_asn1_entry statusMSESetAT[
        sizeof g_octet_string/
        sizeof *g_octet_string];
    struct sc_asn1_entry idPICC[
        sizeof g_octet_string/
        sizeof *g_octet_string];
    struct sc_asn1_entry curCAR[
        sizeof g_octet_string/
        sizeof *g_octet_string];
    struct sc_asn1_entry prevCAR[
        sizeof g_octet_string/
        sizeof *g_octet_string];

    sc_copy_asn1_entry(g_EstablishPACEChannel,
            EstablishPACEChannel);
    sc_format_asn1_entry(EstablishPACEChannel,
            EstablishPACEChannelOutput_data, 0, 1);

    sc_copy_asn1_entry(g_EstablishPACEChannelOutput_data,
            EstablishPACEChannelOutput_data);

    sc_format_asn1_entry(EstablishPACEChannelOutput_data+0,
            errorCode, 0, 1);
    sc_copy_asn1_entry(g_octet_string,
            errorCode);
    sc_format_asn1_entry(errorCode,
            (unsigned char *) &output->result, &result_len, 1);

    sc_format_asn1_entry(EstablishPACEChannelOutput_data+1,
            statusMSESetAT, 0, 1);
    sc_copy_asn1_entry(g_octet_string,
            statusMSESetAT);
    sc_format_asn1_entry(statusMSESetAT,
            &status_mse_set_at, &status_mse_set_at_len, 1);

    if (output->ef_cardaccess) {
        sc_format_asn1_entry(EstablishPACEChannelOutput_data+2,
                output->ef_cardaccess, (size_t *) &output->ef_cardaccess_length, 1);
    }

    if (output->id_icc) {
        sc_format_asn1_entry(EstablishPACEChannelOutput_data+3,
                idPICC, 0, 1);
        sc_copy_asn1_entry(g_octet_string,
                idPICC);
        sc_format_asn1_entry(idPICC,
                output->id_icc, (size_t *) &output->id_icc_length, 1);
    }

    if (output->recent_car) {
        sc_format_asn1_entry(EstablishPACEChannelOutput_data+4,
                curCAR, 0, 1);
        sc_copy_asn1_entry(g_octet_string,
                curCAR);
        sc_format_asn1_entry(curCAR,
                output->recent_car, (size_t *) &output->recent_car_length, 1);
    }

    if (output->previous_car) {
        sc_format_asn1_entry(EstablishPACEChannelOutput_data+5,
            prevCAR, 0, 1);
        sc_copy_asn1_entry(g_octet_string,
                prevCAR);
        sc_format_asn1_entry(prevCAR,
            output->previous_car, (size_t *) &output->previous_car_length, 1);
    }

    return sc_asn1_encode(ctx, EstablishPACEChannel, asn1, asn1_len);
}

int boxing_buf_to_pace_output(sc_context_t *ctx,
        const unsigned char *asn1, size_t asn1_len,
        struct establish_pace_channel_output *output)
{
    uint16_t status_mse_set_at;
    size_t result_len = sizeof output->result,
           status_mse_set_at_len = sizeof status_mse_set_at;
    struct sc_asn1_entry EstablishPACEChannelOutput_data[
        sizeof g_EstablishPACEChannelOutput_data/
        sizeof *g_EstablishPACEChannelOutput_data];
    struct sc_asn1_entry EstablishPACEChannel[
        sizeof g_EstablishPACEChannel/
        sizeof *g_EstablishPACEChannel];
    struct sc_asn1_entry errorCode[
        sizeof g_octet_string/
        sizeof *g_octet_string];
    struct sc_asn1_entry statusMSESetAT[
        sizeof g_octet_string/
        sizeof *g_octet_string];
    struct sc_asn1_entry idPICC[
        sizeof g_octet_string/
        sizeof *g_octet_string];
    struct sc_asn1_entry curCAR[
        sizeof g_octet_string/
        sizeof *g_octet_string];
    struct sc_asn1_entry prevCAR[
        sizeof g_octet_string/
        sizeof *g_octet_string];

    sc_copy_asn1_entry(g_EstablishPACEChannel,
            EstablishPACEChannel);
    sc_format_asn1_entry(EstablishPACEChannel,
            EstablishPACEChannelOutput_data, 0, 0);

    sc_copy_asn1_entry(g_EstablishPACEChannelOutput_data,
            EstablishPACEChannelOutput_data);
    sc_format_asn1_entry(EstablishPACEChannelOutput_data+0,
            errorCode, 0, 0);
    sc_format_asn1_entry(EstablishPACEChannelOutput_data+1,
            statusMSESetAT, 0, 0);
    sc_format_asn1_entry(EstablishPACEChannelOutput_data+2,
            &output->ef_cardaccess, &output->ef_cardaccess_length, 0);
    sc_format_asn1_entry(EstablishPACEChannelOutput_data+3,
            idPICC, 0, 0);
    sc_format_asn1_entry(EstablishPACEChannelOutput_data+4,
            curCAR, 0, 0);
    sc_format_asn1_entry(EstablishPACEChannelOutput_data+5,
            prevCAR, 0, 0);

    sc_copy_asn1_entry(g_octet_string,
            errorCode);
    sc_format_asn1_entry(errorCode,
            &output->result, &result_len, 0);
    /* we already allocated memory for the result */
    errorCode->flags = 0;

    sc_copy_asn1_entry(g_octet_string,
            statusMSESetAT);
    sc_format_asn1_entry(statusMSESetAT,
            &status_mse_set_at, &status_mse_set_at_len, 0);
    /* we already allocated memory for the result */
    statusMSESetAT->flags = 0;

    sc_copy_asn1_entry(g_octet_string,
            idPICC);
    sc_format_asn1_entry(idPICC,
            &output->id_icc, &output->id_icc_length, 0);

    sc_copy_asn1_entry(g_octet_string,
            curCAR);
    sc_format_asn1_entry(curCAR,
            &output->recent_car, &output->recent_car_length, 0);

    sc_copy_asn1_entry(g_octet_string,
            prevCAR);
    sc_format_asn1_entry(prevCAR,
            &output->previous_car, &output->previous_car_length, 0);

    LOG_TEST_RET(ctx,
            sc_asn1_decode(ctx, EstablishPACEChannel,
                asn1, asn1_len, NULL, NULL),
            "Error decoding EstablishPACEChannel");

    if (status_mse_set_at_len != sizeof status_mse_set_at
            || result_len != sizeof output->result)
        return SC_ERROR_UNKNOWN_DATA_RECEIVED;

    output->mse_set_at_sw1 = (status_mse_set_at >> 8) & 0xff;
    output->mse_set_at_sw2 =  status_mse_set_at       & 0xff;

    return SC_SUCCESS;
}

#define CCID_PIN_TIMEOUT	30
#define CCID_DISPLAY_DEFAULT    0xff
static int boxing_pin_cmd_to_buf(sc_context_t *ctx,
        const struct sc_pin_cmd_data *data,
        unsigned char **pc_to_rdr_secure, size_t *pc_to_rdr_secure_len)
{
    PC_to_RDR_Secure_t *secure;
    abPINDataStucture_Modification_t *modify;
    abPINDataStucture_Verification_t *verify;
    uint16_t wLangId = 0,
             bTeoPrologue2 = 0,
             wPINMaxExtraDigit;
    uint8_t bTimeOut = CCID_PIN_TIMEOUT,
            bNumberMessage = CCID_DISPLAY_DEFAULT,
            bTeoPrologue1 = 0,
            bMsgIndex = 0,
            bMessageType = 0x69,
            bSlot = 0,
            bSeq = 0,
            bBWI = 0xff,
            wLevelParameter = 0,
            bEntryValidationCondition = CCID_ENTRY_VALIDATE,
            bmFormatString, bmPINLengthFormat, bmPINBlockString;
    const struct sc_pin_cmd_pin *pin_ref;
    int r;
    unsigned char *pinapdu = NULL;
    size_t pinapdu_len = 0;

    if (!data || !pc_to_rdr_secure || !pc_to_rdr_secure_len) {
        r = SC_ERROR_INVALID_ARGUMENTS;
        goto err;
    }

    pin_ref = data->flags & SC_PIN_CMD_IMPLICIT_CHANGE ?
        &data->pin2 : &data->pin1;

    wPINMaxExtraDigit = htole16(
            (0xff & pin_ref->min_length) << 8)
            | (pin_ref->max_length & 0xff);

    bmFormatString = CCID_PIN_UNITS_BYTES
        | ((pin_ref->offset & 0xf) << 3);
    switch (pin_ref->encoding) {
        case SC_PIN_ENCODING_ASCII:
            bmFormatString |= CCID_PIN_ENCODING_ASCII;
            break;
        case SC_PIN_ENCODING_BCD:
            bmFormatString |= CCID_PIN_ENCODING_BCD;
            break;
        default:
            r = SC_ERROR_INVALID_ARGUMENTS;
            goto err;
    }

    /* GLP PINs expect the effective PIN length from bit 4 */
	bmPINLengthFormat = pin_ref->encoding == SC_PIN_ENCODING_GLP ?
        0x04 : 0x00;

	if (pin_ref->encoding == SC_PIN_ENCODING_GLP) {
		/* GLP PIN length is encoded in 4 bits and block size is always 8 bytes */
		bmPINBlockString = 0x40 | 0x08;
	} else if (pin_ref->encoding == SC_PIN_ENCODING_ASCII && data->flags & SC_PIN_CMD_NEED_PADDING) {
		bmPINBlockString = pin_ref->pad_length;
	} else {
        bmPINBlockString = 0x00;
    }

    r = sc_apdu_get_octets(ctx, data->apdu, &pinapdu, &pinapdu_len,
            SC_PROTO_T1);
    if (r < 0)
        goto err;

    switch (data->cmd) {
        case SC_PIN_CMD_VERIFY:
            *pc_to_rdr_secure_len = sizeof *secure + 1
                + sizeof *verify + pinapdu_len;
            break;

        case SC_PIN_CMD_CHANGE:
            *pc_to_rdr_secure_len = sizeof *secure + 1
                + sizeof *modify + 3 + pinapdu_len;
            break;

        default:
            r = SC_ERROR_INVALID_ARGUMENTS;
            goto err;
    }

    *pc_to_rdr_secure = malloc(*pc_to_rdr_secure_len);
    if (!*pc_to_rdr_secure) {
        r = SC_ERROR_OUT_OF_MEMORY;
        goto err;
    }
    secure = (PC_to_RDR_Secure_t *) *pc_to_rdr_secure;
    secure->bMessageType = bMessageType;
    secure->dwLength = htole32((*pc_to_rdr_secure_len) - sizeof *secure);
    secure->bSlot = bSlot;
    secure->bSeq = bSeq;
    secure->bBWI = bBWI;
    secure->wLevelParameter = wLevelParameter;

    switch (data->cmd) {
        case SC_PIN_CMD_VERIFY:
            /* bPINOperation */
            *((*pc_to_rdr_secure) + sizeof *secure) = CCID_OPERATION_VERIFY;
            verify = (abPINDataStucture_Verification_t *)
                ((*pc_to_rdr_secure) + sizeof *secure + 1);
            verify->bTimeOut = bTimeOut;
            verify->bmFormatString = bmFormatString;
            verify->bmPINBlockString = bmPINBlockString;
            verify->bmPINLengthFormat = bmPINLengthFormat;
            verify->wPINMaxExtraDigit = wPINMaxExtraDigit;
            verify->bEntryValidationCondition = bEntryValidationCondition;
            verify->bNumberMessage = bNumberMessage;
            verify->wLangId = wLangId;
            verify->bMsgIndex = bMsgIndex;
            verify->bTeoPrologue1 = bTeoPrologue1;
            verify->bTeoPrologue2 = bTeoPrologue2;

            memcpy((*pc_to_rdr_secure) + sizeof *secure + 1 + sizeof *verify,
                    pinapdu, pinapdu_len);
            break;

        case SC_PIN_CMD_CHANGE:
            /* bPINOperation */
            *((*pc_to_rdr_secure) + sizeof *secure) = CCID_OPERATION_MODIFY;
            modify = (abPINDataStucture_Modification_t *)
                ((*pc_to_rdr_secure) + sizeof *secure + 1);
            modify->bTimeOut = bTimeOut;
            modify->bmFormatString = bmFormatString;
            modify->bmPINBlockString = bmPINBlockString;
            modify->bmPINLengthFormat = bmPINLengthFormat;
            if (!(data->flags & SC_PIN_CMD_IMPLICIT_CHANGE)
                    && data->pin1.offset) {
                modify->bInsertionOffsetOld = data->pin1.offset - 5;
            } else {
                modify->bInsertionOffsetOld = 0;
            }
            modify->bInsertionOffsetNew = data->pin2.offset ? data->pin2.offset - 5 : 0;
            modify->wPINMaxExtraDigit = wPINMaxExtraDigit;
            modify->bConfirmPIN = CCID_PIN_CONFIRM_NEW
                | (data->flags & SC_PIN_CMD_IMPLICIT_CHANGE ? 0 : CCID_PIN_INSERT_OLD);
            modify->bEntryValidationCondition = bEntryValidationCondition;
            modify->bNumberMessage = bNumberMessage;
            modify->wLangId = wLangId;
            modify->bMsgIndex1 = bMsgIndex;
            *((*pc_to_rdr_secure) + sizeof *secure + 1 + sizeof *modify + 0) =
                bTeoPrologue1;
            *((*pc_to_rdr_secure) + sizeof *secure + 1 + sizeof *modify + 1) =
                bTeoPrologue1;
            *((*pc_to_rdr_secure) + sizeof *secure + 1 + sizeof *modify + 2) =
                bTeoPrologue1;

            memcpy((*pc_to_rdr_secure) + sizeof *secure + 1 + sizeof *modify + 3,
                    pinapdu, pinapdu_len);
            break;

        default:
            r = SC_ERROR_INVALID_ARGUMENTS;
            goto err;
    }

    r = SC_SUCCESS;

err:
    free(pinapdu);
    if (r < 0 && pc_to_rdr_secure && *pc_to_rdr_secure) {
        free(*pc_to_rdr_secure);
        *pc_to_rdr_secure = NULL;
    }

    return r;
}

#define CCID_BSTATUS_OK_ACTIVE 0x00 /** No error. An ICC is present and active */
static int boxing_buf_to_verify_result(sc_context_t *ctx,
        const unsigned char *rdr_to_pc_datablock,
        size_t rdr_to_pc_datablock_len,
        sc_apdu_t *apdu)
{
    RDR_to_PC_DataBlock_t *datablock =
        (RDR_to_PC_DataBlock_t *) rdr_to_pc_datablock;

    if (!rdr_to_pc_datablock
            || rdr_to_pc_datablock_len < sizeof *datablock
            || datablock->bMessageType != 0x80)
        return SC_ERROR_UNKNOWN_DATA_RECEIVED;

    if (datablock->bStatus != CCID_BSTATUS_OK_ACTIVE)
        return SC_ERROR_TRANSMIT_FAILED;

    return sc_apdu_set_resp(ctx, apdu,
            rdr_to_pc_datablock + sizeof *datablock,
            htole32(datablock->dwLength));
}

static int boxing_perform_verify(struct sc_reader *reader,
        struct sc_pin_cmd_data *data)
{
    u8 rbuf[0xff];
    sc_apdu_t apdu;
    int r;

	memset(&apdu, 0, sizeof(apdu));
    apdu.cse     = SC_APDU_CASE_4_SHORT;
	apdu.cla     = boxing_cla;
	apdu.ins     = boxing_ins;
	apdu.p1      = boxing_p1;
	apdu.p2      = boxing_p2_PC_to_RDR_Secure;
    apdu.resp    = rbuf;
    apdu.resplen = sizeof rbuf;
    apdu.le      = sizeof rbuf;

    if (!reader || !reader->ops || !reader->ops->transmit) {
        r = SC_ERROR_NOT_SUPPORTED;
        goto err;
    }

    r = boxing_pin_cmd_to_buf(reader->ctx, data,
            (unsigned char **) &apdu.data, &apdu.datalen);
    if (r < 0) {
        sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL,
                "Error encoding PC_to_RDR_Secure");
        goto err;
    }
    apdu.lc = apdu.datalen;

    r = SC_SUCCESS;

    r = reader->ops->transmit(reader, &apdu);
    if (r < 0) {
        sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL,
                "Error performing PC_to_RDR_Secure");
        goto err;
    }

    if (apdu.sw1 != 0x90 && apdu.sw2 != 0x00) {
        sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL,
                "Error decoding PC_to_RDR_Secure");
        r = SC_ERROR_NOT_SUPPORTED;
        goto err;
    }

    r = boxing_buf_to_verify_result(reader->ctx, apdu.resp, apdu.resplen,
            data->apdu);

err:
    free((unsigned char *) apdu.data);

    return r;
}

static int boxing_perform_pace(struct sc_reader *reader,
        void *establish_pace_channel_input,
        void *establish_pace_channel_output)
{
    u8 rbuf[0xffff];
    sc_apdu_t apdu;
    int r;
    struct establish_pace_channel_input  *input  =
        establish_pace_channel_input;
    struct establish_pace_channel_output *output =
        establish_pace_channel_output;

	memset(&apdu, 0, sizeof(apdu));
    apdu.cse     = SC_APDU_CASE_4_EXT;
	apdu.cla     = boxing_cla;
	apdu.ins     = boxing_ins;
	apdu.p1      = boxing_p1;
	apdu.p2      = boxing_p2_EstablishPACEChannel;
    apdu.resp    = rbuf;
    apdu.resplen = sizeof rbuf;
    apdu.le      = sizeof rbuf;

    if (!reader || !reader->ops || !reader->ops->transmit) {
        r = SC_ERROR_NOT_SUPPORTED;
        goto err;
    }

    r = boxing_pace_input_to_buf(reader->ctx, input,
            (unsigned char **) &apdu.data, &apdu.datalen);
    if (r < 0) {
        sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL,
                "Error encoding EstablishPACEChannel");
        goto err;
    }
    apdu.lc = apdu.datalen;

    r = reader->ops->transmit(reader, &apdu);
    if (r < 0) {
        sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL,
                "Error performing EstablishPACEChannel");
        goto err;
    }

    if (apdu.sw1 != 0x90 && apdu.sw2 != 0x00) {
        sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL,
                "Error decoding EstablishPACEChannel");
        r = SC_ERROR_NOT_SUPPORTED;
        goto err;
    }

    r = boxing_buf_to_pace_output(reader->ctx, apdu.resp, apdu.resplen,
            output);

err:
    free((unsigned char *) apdu.data);

    return r;
}

struct sc_asn1_entry g_PACECapabilities_data[] = {
    { "capabilityPACE",
        SC_ASN1_STRUCT, SC_ASN1_CTX|0x01|SC_ASN1_CONS, SC_ASN1_ALLOC, NULL, NULL },
    { "capabilityEID",
        SC_ASN1_STRUCT, SC_ASN1_CTX|0x02|SC_ASN1_CONS, SC_ASN1_ALLOC, NULL, NULL },
    { "capabilityESign",
        SC_ASN1_STRUCT, SC_ASN1_CTX|0x03|SC_ASN1_CONS, SC_ASN1_ALLOC, NULL, NULL },
    { "capabilityDestroy",
        SC_ASN1_STRUCT, SC_ASN1_CTX|0x04|SC_ASN1_CONS, SC_ASN1_ALLOC, NULL, NULL },
    { NULL , 0 , 0 , 0 , NULL , NULL }
};
struct sc_asn1_entry g_PACECapabilities[] = {
    { "PACECapabilities",
        SC_ASN1_STRUCT, SC_ASN1_TAG_SEQUENCE|SC_ASN1_CONS, 0, NULL, NULL },
    { NULL , 0 , 0 , 0 , NULL , NULL }
};

int boxing_buf_to_pace_capabilities(sc_context_t *ctx,
        const unsigned char *asn1, size_t asn1_len,
        unsigned long *sc_reader_t_capabilities)
{
    int pace = 0, eid = 0, esign = 0, destroy = 0;
    struct sc_asn1_entry PACECapabilities_data[
        sizeof g_PACECapabilities_data/
        sizeof *g_PACECapabilities_data];
    struct sc_asn1_entry PACECapabilities[
        sizeof g_PACECapabilities/
        sizeof *g_PACECapabilities];
    struct sc_asn1_entry capabilityPACE[
        sizeof g_boolean/
        sizeof *g_boolean];
    struct sc_asn1_entry capabilityEID[
        sizeof g_boolean/
        sizeof *g_boolean];
    struct sc_asn1_entry capabilityESign[
        sizeof g_boolean/
        sizeof *g_boolean];
    struct sc_asn1_entry capabilityDestroy[
        sizeof g_boolean/
        sizeof *g_boolean];

    sc_copy_asn1_entry(g_PACECapabilities,
            PACECapabilities);
    sc_format_asn1_entry(PACECapabilities,
            PACECapabilities_data, 0, 1);

    sc_copy_asn1_entry(g_PACECapabilities_data,
            PACECapabilities_data);
    sc_format_asn1_entry(PACECapabilities_data+0,
            &capabilityPACE, NULL, 1);
    sc_format_asn1_entry(PACECapabilities_data+1,
            &capabilityEID, NULL, 1);
    sc_format_asn1_entry(PACECapabilities_data+2,
            &capabilityESign, NULL, 1);
    sc_format_asn1_entry(PACECapabilities_data+3,
            &capabilityDestroy, NULL, 1);

    sc_copy_asn1_entry(g_boolean,
            capabilityPACE);
    sc_format_asn1_entry(capabilityPACE+0,
            &pace, NULL, 0);

    sc_copy_asn1_entry(g_boolean,
            capabilityEID);
    sc_format_asn1_entry(capabilityEID+0,
            &eid, NULL, 0);

    sc_copy_asn1_entry(g_boolean,
            capabilityESign);
    sc_format_asn1_entry(capabilityESign+0,
            &esign, NULL, 0);

    sc_copy_asn1_entry(g_boolean,
            capabilityDestroy);
    sc_format_asn1_entry(capabilityDestroy+0,
            &destroy, NULL, 0);

    LOG_TEST_RET(ctx,
            sc_asn1_decode(ctx, PACECapabilities,
                asn1, asn1_len, NULL, NULL),
            "Error decoding PACECapabilities");

    /* We got a valid PACE Capabilities reply. There is currently no mechanism
     * to determine support PIN verification/modification with a boxing
     * command. Since the reader implements this mechanism it is reasonable to
     * assume that PIN verification/modification is available. */
    *sc_reader_t_capabilities = SC_READER_CAP_PIN_PAD;

    if (pace)
        *sc_reader_t_capabilities |= SC_READER_CAP_PACE_GENERIC;
    if (eid)
        *sc_reader_t_capabilities |= SC_READER_CAP_PACE_EID;
    if (esign)
        *sc_reader_t_capabilities |= SC_READER_CAP_PACE_ESIGN;
    if (destroy)
        *sc_reader_t_capabilities |= SC_READER_CAP_PACE_DESTROY_CHANNEL;

    return SC_SUCCESS;
}

int boxing_pace_capabilities_to_buf(sc_context_t *ctx,
        const unsigned long sc_reader_t_capabilities,
        unsigned char **asn1, size_t *asn1_len)
{
    int yes = 1, no = 0;
    struct sc_asn1_entry PACECapabilities_data[
        sizeof g_PACECapabilities_data/
        sizeof *g_PACECapabilities_data];
    struct sc_asn1_entry PACECapabilities[
        sizeof g_PACECapabilities/
        sizeof *g_PACECapabilities];
    struct sc_asn1_entry capabilityPACE[
        sizeof g_boolean/
        sizeof *g_boolean];
    struct sc_asn1_entry capabilityEID[
        sizeof g_boolean/
        sizeof *g_boolean];
    struct sc_asn1_entry capabilityESign[
        sizeof g_boolean/
        sizeof *g_boolean];
    struct sc_asn1_entry capabilityDestroy[
        sizeof g_boolean/
        sizeof *g_boolean];

    sc_copy_asn1_entry(g_EstablishPACEChannel,
            PACECapabilities);
    sc_format_asn1_entry(PACECapabilities,
            PACECapabilities_data, 0, 1);

    sc_copy_asn1_entry(g_PACECapabilities_data,
            PACECapabilities_data);
    sc_format_asn1_entry(PACECapabilities_data+0,
            &capabilityPACE, NULL, 1);
    sc_format_asn1_entry(PACECapabilities_data+1,
            &capabilityEID, NULL, 1);
    sc_format_asn1_entry(PACECapabilities_data+2,
            &capabilityESign, NULL, 1);
    sc_format_asn1_entry(PACECapabilities_data+3,
            &capabilityDestroy, NULL, 1);

    sc_copy_asn1_entry(g_boolean,
            capabilityPACE);
    sc_format_asn1_entry(capabilityPACE,
            sc_reader_t_capabilities & SC_READER_CAP_PACE_GENERIC
            ? &yes : &no, NULL, 1);

    sc_copy_asn1_entry(g_boolean,
            capabilityEID);
    sc_format_asn1_entry(capabilityEID,
            sc_reader_t_capabilities & SC_READER_CAP_PACE_EID
            ? &yes : &no, NULL, 1);

    sc_copy_asn1_entry(g_boolean,
            capabilityESign);
    sc_format_asn1_entry(capabilityESign,
            sc_reader_t_capabilities & SC_READER_CAP_PACE_ESIGN
            ? &yes : &no, NULL, 1);

    sc_copy_asn1_entry(g_boolean,
            capabilityDestroy);
    sc_format_asn1_entry(capabilityDestroy,
            sc_reader_t_capabilities & SC_READER_CAP_PACE_DESTROY_CHANNEL
            ? &yes : &no, NULL, 1);

    return sc_asn1_encode(ctx, PACECapabilities, asn1, asn1_len);
}

#ifndef DISABLE_GLOBAL_BOXING_INITIALIZATION
static
#endif
void sc_detect_boxing_cmds(sc_reader_t *reader)
{
    u8 rbuf[0xff];
    sc_apdu_t apdu;
    unsigned long capabilities;

	memset(&apdu, 0, sizeof(apdu));
    apdu.cse     = SC_APDU_CASE_1;
	apdu.cla     = boxing_cla;
	apdu.ins     = boxing_ins;
	apdu.p1      = boxing_p1;
	apdu.p2      = boxing_p2_GetReaderPACECapabilities;
    apdu.resp    = rbuf;
    apdu.resplen = sizeof rbuf;
    apdu.le      = sizeof rbuf;

    if (!reader || !reader->ops || !reader->ops->transmit
            || reader->ops->transmit(reader, &apdu) != SC_SUCCESS
            || apdu.sw1 != 0x90
            || apdu.sw2 != 0x00
            || boxing_buf_to_pace_capabilities(reader->ctx,
                apdu.resp, apdu.resplen, &capabilities) != SC_SUCCESS) {
        if (reader) {
            sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL,
                    "%s does not support boxing commands", reader->name);
            memset(&apdu, 0, sizeof(apdu));
            apdu.cse     = SC_APDU_CASE_3;
            apdu.cla     = 0x00;
            apdu.ins     = 0xA4;
            apdu.p1      = 8;
            apdu.p2      = 0x0C;
            apdu.data    = rbuf;
            rbuf[0] = 0x3F;
            rbuf[1] = 0x00;
            apdu.datalen = 2;
            apdu.lc      = 2;
            apdu.resp    = NULL;
            apdu.resplen = 0;
            apdu.le      = 0;
            reader->ops->transmit(reader, &apdu);
        }
    } else {
        if (capabilities & SC_READER_CAP_PIN_PAD
                && !(reader->capabilities & SC_READER_CAP_PIN_PAD)) {
            ((struct sc_reader_operations *) reader->ops)->perform_verify =
                boxing_perform_verify;
            sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL,
                    "Added boxing command wrappers for PIN verification/modification to '%s'", reader->name);
        }

        if (capabilities & SC_READER_CAP_PACE_GENERIC
                && !(reader->capabilities & SC_READER_CAP_PACE_GENERIC)) {
            ((struct sc_reader_operations *) reader->ops)->perform_pace =
                boxing_perform_pace;
            sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL,
                    "Added boxing command wrappers for PACE to '%s'", reader->name);
        }

        reader->capabilities |= capabilities;
    }
}

#ifndef DISABLE_GLOBAL_BOXING_INITIALIZATION

#ifndef HAVE_LIST_ITERATOR_NEXT
#include "opensc/src/common/simclist.c"
#endif

void sc_initialize_boxing_cmds(sc_context_t *ctx)
{
    sc_reader_t *reader;

    if (!ctx)
        return;

    if (!list_iterator_start(&ctx->readers))
        return;

    reader = list_iterator_next(&ctx->readers);
    while (reader) {
        reader = list_iterator_next(&ctx->readers);
        sc_detect_boxing_cmds(reader);
    }

    list_iterator_stop(&ctx->readers);
}
#endif
