mbed-os/features/nfc/stack/tech/iso7816/iso7816.c

367 lines
11 KiB
C

/*
* Copyright (c) 2015-2018, ARM Limited, All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* \file iso7816.c
* \copyright Copyright (c) ARM Ltd 2015
* \author Donatien Garnier
*/
#define __DEBUG__ 0
#ifndef __MODULE__
#define __MODULE__ "iso7816.c"
#endif
#include "inc/nfc.h"
#include "iso7816.h"
#include "iso7816_app.h"
#include "iso7816_defs.h"
#include "tech/isodep/isodep_target.h"
static void iso7816_disconnected(nfc_tech_iso7816_t* pIso7816, bool deselected);
static nfc_err_t iso7816_parse(nfc_tech_iso7816_t* pIso7816);
static void iso7816_receive(nfc_tech_iso7816_t* pIso7816);
static nfc_err_t iso7816_transmit(nfc_tech_iso7816_t* pIso7816);
static bool iso7816_mf_command(nfc_tech_iso7816_t* pIso7816);
static void iso_dep_received_cb(nfc_tech_isodep_t* pIsodep, nfc_err_t ret, void* pUserData);
static void iso_dep_transmitted_cb(nfc_tech_isodep_t* pIsodep, nfc_err_t ret, void* pUserData);
static void iso_dep_disconnected_cb(nfc_tech_isodep_t* pIsodep, bool deselected, void* pUserData);
static void iso_dep_stream_transmit_cb(buffer_t* ppDataIn, bool* pClose, size_t maxLength, void* pUserParam);
static void iso_dep_stream_receive_cb(buffer_t* pDataOut, bool closed, void* pUserParam);
void nfc_tech_iso7816_init(nfc_tech_iso7816_t* pIso7816, nfc_transceiver_t* pTransceiver, nfc_tech_iso7816_disconnected_cb disconnectedCb, void* pUserData)
{
buffer_init(&pIso7816->hist, NULL, 0);
nfc_tech_isodep_target_init(&pIso7816->isoDepTarget, pTransceiver, &pIso7816->hist, iso_dep_disconnected_cb, pIso7816);
pIso7816->pAppList = NULL;
pIso7816->pSelectedApp = NULL;
pIso7816->disconnectedCb = disconnectedCb;
istream_init(&pIso7816->inputStream, iso_dep_stream_transmit_cb, pIso7816);
ostream_init(&pIso7816->outputStream, iso_dep_stream_receive_cb, pIso7816);
buffer_builder_init(&pIso7816->txBldr, pIso7816->txBuf, 2); //Just enough to fit sw
buffer_builder_init(&pIso7816->rxBldr, pIso7816->rxBuf, ISO7816_RX_BUFFER_SIZE);
pIso7816->pUserData = pUserData;
}
void nfc_tech_iso7816_connect(nfc_tech_iso7816_t* pIso7816)
{
pIso7816->disconnected = false;
pIso7816->responseReady = true;
iso7816_receive(pIso7816);
nfc_tech_isodep_target_connect(&pIso7816->isoDepTarget);
}
void nfc_tech_iso7816_disconnect(nfc_tech_iso7816_t* pIso7816)
{
nfc_tech_isodep_target_disconnect(&pIso7816->isoDepTarget);
}
void nfc_tech_iso7816_add_app(nfc_tech_iso7816_t* pIso7816, nfc_tech_iso7816_app_t* pIso7816App)
{
nfc_tech_iso7816_app_t** ppPrevApp = &pIso7816->pAppList;
while( *ppPrevApp != NULL )
{
ppPrevApp = &((*ppPrevApp)->pNext);
}
*ppPrevApp = pIso7816App;
pIso7816App->pNext = NULL;
}
nfc_err_t nfc_tech_iso7816_reply(nfc_tech_iso7816_t* pIso7816)
{
nfc_err_t ret;
//Serialize APDU and send
buffer_builder_reset(&pIso7816->txBldr);
buffer_builder_write_nu16(&pIso7816->txBldr, pIso7816->rApdu.sw);
buffer_append(&pIso7816->rApdu.dataOut, buffer_builder_buffer(&pIso7816->txBldr));
NFC_DBG("R-ADPU: (LE):%02X SW:%04X", buffer_reader_readable(&pIso7816->rApdu.dataOut), pIso7816->rApdu.sw);
DBG_BLOCK( buffer_dump(&pIso7816->rApdu.dataOut); )
ret = iso7816_transmit(pIso7816);
if(ret)
{
return ret;
}
return NFC_OK;
}
void iso7816_disconnected(nfc_tech_iso7816_t* pIso7816, bool deselected)
{
pIso7816->disconnected = true;
if(pIso7816->pSelectedApp != NULL)
{
//Deselect previous app
pIso7816->pSelectedApp->deselected(pIso7816->pSelectedApp, pIso7816->pSelectedApp->pUserData);
pIso7816->pSelectedApp = NULL;
}
pIso7816->disconnectedCb(pIso7816, deselected, pIso7816->pUserData);
}
nfc_err_t iso7816_parse(nfc_tech_iso7816_t* pIso7816)
{
//Reset R-APDU
buffer_init(&pIso7816->rApdu.dataOut, NULL, 0);
pIso7816->rApdu.sw = ISO7816_SW_OK;
DBG_BLOCK( buffer_dump(buffer_builder_buffer(&pIso7816->rxBldr)); )
if( buffer_reader_readable( buffer_builder_buffer(&pIso7816->rxBldr) ) < 4 )
{
NFC_ERR("C-APDU is too small");
pIso7816->rApdu.sw = ISO7816_SW_INVALID_CLASS;
nfc_tech_iso7816_reply(pIso7816);
return NFC_ERR_PROTOCOL;
}
pIso7816->cApdu.cla = buffer_read_nu8(buffer_builder_buffer(&pIso7816->rxBldr));
pIso7816->cApdu.ins = buffer_read_nu8(buffer_builder_buffer(&pIso7816->rxBldr));
pIso7816->cApdu.p1 = buffer_read_nu8(buffer_builder_buffer(&pIso7816->rxBldr));
pIso7816->cApdu.p2 = buffer_read_nu8(buffer_builder_buffer(&pIso7816->rxBldr));
buffer_init(&pIso7816->cApdu.dataIn, NULL, 0);
pIso7816->cApdu.maxRespLength = 0;
if( buffer_reader_readable(buffer_builder_buffer(&pIso7816->rxBldr)) > 1 )
{
size_t lc = buffer_read_nu8(buffer_builder_buffer(&pIso7816->rxBldr));
if( buffer_reader_readable(buffer_builder_buffer(&pIso7816->rxBldr)) >= lc )
{
buffer_split(&pIso7816->cApdu.dataIn, buffer_builder_buffer(&pIso7816->rxBldr), buffer_builder_buffer(&pIso7816->rxBldr), lc);
}
else
{
pIso7816->rApdu.sw = ISO7816_SW_WRONG_LENGTH;
nfc_tech_iso7816_reply(pIso7816);
return NFC_ERR_LENGTH; //Not a valid frame
}
}
if( buffer_reader_readable(buffer_builder_buffer(&pIso7816->rxBldr)) >= 1 )
{
pIso7816->cApdu.maxRespLength = buffer_read_nu8(buffer_builder_buffer(&pIso7816->rxBldr));
}
NFC_DBG("C-APDU: CLA:%02X INS:%02X P1:%02X P2:%02X LC:%02X LE:%02X", pIso7816->cApdu.cla, pIso7816->cApdu.ins, pIso7816->cApdu.p1, pIso7816->cApdu.p2,
buffer_reader_readable(&pIso7816->cApdu.dataIn), pIso7816->cApdu.maxRespLength);
if( buffer_reader_readable(buffer_builder_buffer(&pIso7816->rxBldr)) > 0 )
{
pIso7816->rApdu.sw = ISO7816_SW_WRONG_LENGTH;
nfc_tech_iso7816_reply(pIso7816);
return NFC_ERR_LENGTH; //Not a valid frame
}
//See if can select an app
if( iso7816_mf_command(pIso7816) )
{
nfc_tech_iso7816_reply(pIso7816);
return NFC_OK;
}
//Pass command to selected app
if( pIso7816->pSelectedApp == NULL )
{
pIso7816->rApdu.sw = ISO7816_SW_NOT_FOUND;
nfc_tech_iso7816_reply(pIso7816);
return NFC_ERR_NOT_FOUND; //Not a valid frame
}
pIso7816->pSelectedApp->apdu(pIso7816->pSelectedApp, pIso7816->pSelectedApp->pUserData);
return NFC_OK;
}
void iso7816_receive(nfc_tech_iso7816_t* pIso7816)
{
buffer_builder_reset(&pIso7816->rxBldr);
nfc_tech_isodep_target_receive(&pIso7816->isoDepTarget, &pIso7816->outputStream, iso_dep_received_cb, pIso7816);
}
nfc_err_t iso7816_transmit(nfc_tech_iso7816_t* pIso7816)
{
return nfc_tech_isodep_target_transmit(&pIso7816->isoDepTarget, &pIso7816->inputStream, iso_dep_transmitted_cb, pIso7816);
}
/** Handle ISO7816-4 command
* \param pTarget pointer to target instance
* \param CLA ISO7816-4 class byte
* \param INS ISO7816-4 instruction byte
* \param P1 ISO7816-4 P1 byte
* \param P2 ISO7816-4 P2 byte
* \param pDataIn ISO7816-4 command payload
* \param pDataOut ISO7816-4 response payload
* \param SW status word
* \return true if command was handled, false if it should be passed to the selected application
*/
bool iso7816_mf_command(nfc_tech_iso7816_t* pIso7816)
{
nfc_tech_iso7816_app_t* pApp;
if(pIso7816->cApdu.cla != 0x00)
{
return false;
}
switch(pIso7816->cApdu.ins)
{
case ISO7816_INS_SELECT:
switch(pIso7816->cApdu.p1)
{
case 0x04: //Selection by DF name
pApp = pIso7816->pAppList;
while(pApp != NULL)
{
if( buffer_reader_readable(&pIso7816->cApdu.dataIn) <= pApp->aidSize )
{
if( buffer_reader_cmp_bytes(&pIso7816->cApdu.dataIn, pApp->aid, buffer_reader_readable(&pIso7816->cApdu.dataIn)) )
{
if(pIso7816->pSelectedApp != NULL)
{
//Deselect previous app
pIso7816->pSelectedApp->deselected(pIso7816->pSelectedApp, pIso7816->pSelectedApp->pUserData);
}
pIso7816->pSelectedApp = pApp;
pIso7816->pSelectedApp->selected(pIso7816->pSelectedApp, pIso7816->pSelectedApp->pUserData);
pIso7816->rApdu.sw = ISO7816_SW_OK;
return true;
}
}
pApp = pApp->pNext;
}
pIso7816->rApdu.sw = ISO7816_SW_NOT_FOUND;
return true;
default:
if(pIso7816->pSelectedApp == NULL)
{
pIso7816->rApdu.sw = ISO7816_SW_NOT_FOUND;
return true;
}
else
{
return false;
}
}
break;
default:
if(pIso7816->pSelectedApp == NULL)
{
pIso7816->rApdu.sw = ISO7816_SW_INVALID_INS;
return true;
}
else
{
return false;
}
break;
}
}
void iso_dep_received_cb(nfc_tech_isodep_t* pIsodep, nfc_err_t ret, void* pUserData)
{
nfc_tech_iso7816_t* pIso7816 = (nfc_tech_iso7816_t*) pUserData;
(void) pIsodep;
if( ret )
{
NFC_WARN("Got error %d", ret);
return;
}
//Parse received APDU
ret = iso7816_parse(pIso7816);
if( ret )
{
NFC_WARN("Got error %d", ret);
return;
}
}
void iso_dep_transmitted_cb(nfc_tech_isodep_t* pIsodep, nfc_err_t ret, void* pUserData)
{
nfc_tech_iso7816_t* pIso7816 = (nfc_tech_iso7816_t*) pUserData;
(void) pIsodep;
if( ret )
{
NFC_WARN("Got error %d", ret);
return;
}
//Advertise that we have space in our buffer?
//Switch to receive mode!
iso7816_receive(pIso7816);
}
void iso_dep_disconnected_cb(nfc_tech_isodep_t* pIsodep, bool deselected, void* pUserData)
{
nfc_tech_iso7816_t* pIso7816 = (nfc_tech_iso7816_t*) pUserData;
(void) pIsodep;
NFC_DBG("ISO DEP disconnected");
iso7816_disconnected(pIso7816, deselected);
}
void iso_dep_stream_transmit_cb(buffer_t* pDataIn, bool* pClose, size_t maxLength, void* pUserParam)
{
nfc_tech_iso7816_t* pIso7816 = (nfc_tech_iso7816_t*) pUserParam;
//Only close if buffer fits in this frame
if( maxLength >= buffer_reader_readable(&pIso7816->rApdu.dataOut) )
//if( buffer_total_length(&pLlcp->tx) <= maxLength )
{
maxLength = buffer_reader_readable(&pIso7816->rApdu.dataOut);
*pClose = true;
}
else
{
*pClose = false;
}
buffer_split(pDataIn, &pIso7816->rApdu.dataOut, &pIso7816->rApdu.dataOut, maxLength);
}
void iso_dep_stream_receive_cb(buffer_t* pDataOut, bool closed, void* pUserParam)
{
nfc_tech_iso7816_t* pIso7816 = (nfc_tech_iso7816_t*) pUserParam;
(void) closed;
if( buffer_reader_readable(pDataOut) > buffer_builder_writeable(&pIso7816->rxBldr) )
{
NFC_ERR("Frame will not fit (%d > %d)", buffer_reader_readable(pDataOut), buffer_builder_writeable(&pIso7816->rxBldr) );
}
//Feed rx buffer
buffer_builder_copy_n_bytes(&pIso7816->rxBldr, pDataOut, buffer_reader_readable(pDataOut));
}