/** * Public platform independent Near Field Communication (NFC) library * * Copyright (C) 2009, Roel Verdult * * This program 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 3 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 Lesser General Public License * along with this program. If not, see * * * @file acr122.c * @brief */ #include #include #include #include #include "acr122.h" #include "../drivers.h" // Bus #include #ifdef __APPLE__ #include #endif #include "nfc-messages.h" // WINDOWS: #define IOCTL_CCID_ESCAPE_SCARD_CTL_CODE SCARD_CTL_CODE(3500) #define IOCTL_CCID_ESCAPE_SCARD_CTL_CODE (((0x31) << 16) | ((3500) << 2)) #define SCARD_OPERATION_SUCCESS 0x61 #define SCARD_OPERATION_ERROR 0x63 #ifndef SCARD_PROTOCOL_UNDEFINED #define SCARD_PROTOCOL_UNDEFINED SCARD_PROTOCOL_UNSET #endif #define FIRMWARE_TEXT "ACR122U" // Tested on: ACR122U101(ACS), ACR122U102(Tikitag), ACR122U203(ACS) #define ACR122_WRAP_LEN 5 #define ACR122_COMMAND_LEN 266 #define ACR122_RESPONSE_LEN 268 typedef struct { SCARDCONTEXT hCtx; SCARDHANDLE hCard; SCARD_IO_REQUEST ioCard; } acr122_spec_t; nfc_device_desc_t * acr122_pick_device (void) { nfc_device_desc_t *pndd; if ((pndd = malloc (sizeof (*pndd)))) { size_t szN; if (!acr122_list_devices (&pndd, 1, &szN)) { ERR("acr122_list_devices failed"); return NULL; } if (szN == 0) { ERR("No device found"); return NULL; } } return pndd; } /** * @fn bool acr122_list_devices(nfc_device_desc_t *pnddDevices[], size_t szDevices, size_t *pszDeviceFound) * @brief List connected devices * * Probe PCSC to find NFC capable hardware. * * @param pnddDevices Array of nfc_device_desc_t previously allocated by the caller. * @param szDevices size of the pnddDevices array. * @param pszDeviceFound number of devices found. * @return true if succeeded, false otherwise. */ bool acr122_list_devices(nfc_device_desc_t *pnddDevices[], size_t szDevices, size_t *pszDeviceFound) { size_t szPos = 0; char acDeviceNames[256+64*DRIVERS_MAX_DEVICES]; size_t szDeviceNamesLen = sizeof(acDeviceNames); acr122_spec_t as; uint32_t uiBusIndex = 0; char *pcFirmware; // Clear the reader list memset(acDeviceNames, '\0', szDeviceNamesLen); *pszDeviceFound = 0; // Test if context succeeded if (SCardEstablishContext(SCARD_SCOPE_USER,NULL,NULL,&(as.hCtx)) != SCARD_S_SUCCESS) return false; // Retrieve the string array of all available pcsc readers if (SCardListReaders(as.hCtx,NULL,acDeviceNames,(void*)&szDeviceNamesLen) != SCARD_S_SUCCESS) return false; DBG("PCSC reports following device(s):"); while ((acDeviceNames[szPos] != '\0') && ((*pszDeviceFound) < szDevices)) { uiBusIndex++; DBG("- %s (pos=%d)", acDeviceNames + szPos, szPos); // Test if we were able to connect to the "emulator" card if (SCardConnect(as.hCtx,acDeviceNames + szPos,SCARD_SHARE_EXCLUSIVE,SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1,&(as.hCard),(void*)&(as.ioCard.dwProtocol)) == SCARD_S_SUCCESS) { // Configure I/O settings for card communication as.ioCard.cbPciLength = sizeof(SCARD_IO_REQUEST); // Retrieve the current firmware version pcFirmware = acr122_firmware((nfc_device_t*)&as); if (strstr(pcFirmware,FIRMWARE_TEXT) != NULL) { // Supported ACR122 device found strncpy(pnddDevices[*pszDeviceFound]->acDevice, acDeviceNames + szPos, BUFSIZ - 1); pnddDevices[*pszDeviceFound]->acDevice[BUFSIZ - 1] = '\0'; pnddDevices[*pszDeviceFound]->pcDriver = ACR122_DRIVER_NAME; pnddDevices[*pszDeviceFound]->uiBusIndex = uiBusIndex; (*pszDeviceFound)++; } else { DBG("Firmware version mismatch"); } SCardDisconnect(as.hCard,SCARD_LEAVE_CARD); SCardReleaseContext(as.hCtx); } else { DBG("Can't contact emulator card"); } // Find next device name position while (acDeviceNames[szPos++] != '\0'); } return true; } nfc_device_t* acr122_connect(const nfc_device_desc_t* pndd) { nfc_device_t* pnd = NULL; acr122_spec_t as; acr122_spec_t* pas; char* pcFirmware; bool bPnddLocallyAllocated = false; // If no description is provided, pick a device automagically. if (pndd == NULL) { pndd = acr122_pick_device(); if (pndd == NULL) return NULL; bPnddLocallyAllocated = true; } // Test if context succeeded if (SCardEstablishContext(SCARD_SCOPE_USER,NULL,NULL,&(as.hCtx)) != SCARD_S_SUCCESS) { if (bPnddLocallyAllocated == true) free (pndd); return NULL; } // Test if we were able to connect to the "emulator" card if (SCardConnect(as.hCtx,pndd->acDevice,SCARD_SHARE_EXCLUSIVE,SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1,&(as.hCard),(void*)&(as.ioCard.dwProtocol)) != SCARD_S_SUCCESS) { // Connect to ACR122 firmware version >2.0 if (SCardConnect(as.hCtx,pndd->acDevice,SCARD_SHARE_DIRECT,0,&(as.hCard),(void*)&(as.ioCard.dwProtocol)) != SCARD_S_SUCCESS) { // We can not connect to this device. if (bPnddLocallyAllocated == true) free (pndd); return NULL; } } // Configure I/O settings for card communication as.ioCard.cbPciLength = sizeof(SCARD_IO_REQUEST); // Retrieve the current firmware version pcFirmware = acr122_firmware((nfc_device_t*)&as); if (strstr(pcFirmware,FIRMWARE_TEXT) != NULL) { // Allocate memory and store the device specification pas = malloc(sizeof(acr122_spec_t)); *pas = as; // Done, we found the reader we are looking for pnd = malloc(sizeof(nfc_device_t)); strncpy(pnd->acName,pcFirmware, DEVICE_NAME_LENGTH - 1); pnd->acName[DEVICE_NAME_LENGTH - 1] = '\0'; pnd->nc = NC_PN532; pnd->nds = (nfc_device_spec_t)pas; pnd->bActive = true; pnd->bCrc = true; pnd->bPar = true; pnd->ui8TxBits = 0; } if (bPnddLocallyAllocated == true) free (pndd); return pnd; } void acr122_disconnect(nfc_device_t* pnd) { acr122_spec_t* pas = (acr122_spec_t*)pnd->nds; SCardDisconnect(pas->hCard,SCARD_LEAVE_CARD); SCardReleaseContext(pas->hCtx); free(pas); free(pnd); } bool acr122_transceive(const nfc_device_spec_t nds, const byte_t* pbtTx, const size_t szTxLen, byte_t* pbtRx, size_t* pszRxLen) { byte_t abtRxCmd[5] = { 0xFF,0xC0,0x00,0x00 }; size_t szRxCmdLen = sizeof(abtRxCmd); byte_t abtRxBuf[ACR122_RESPONSE_LEN]; size_t szRxBufLen; byte_t abtTxBuf[ACR122_WRAP_LEN+ACR122_COMMAND_LEN] = { 0xFF, 0x00, 0x00, 0x00 }; acr122_spec_t* pas = (acr122_spec_t*)nds; // Make sure the command does not overflow the send buffer if (szTxLen > ACR122_COMMAND_LEN) return false; // Store the length of the command we are going to send abtTxBuf[4] = szTxLen; // Prepare and transmit the send buffer memcpy(abtTxBuf+5,pbtTx,szTxLen); szRxBufLen = sizeof(abtRxBuf); #ifdef DEBUG printf(" TX: "); print_hex(abtTxBuf,szTxLen+5); #endif if (pas->ioCard.dwProtocol == SCARD_PROTOCOL_UNDEFINED) { if (SCardControl(pas->hCard,IOCTL_CCID_ESCAPE_SCARD_CTL_CODE,abtTxBuf,szTxLen+5,abtRxBuf,szRxBufLen,(void*)&szRxBufLen) != SCARD_S_SUCCESS) return false; } else { if (SCardTransmit(pas->hCard,&(pas->ioCard),abtTxBuf,szTxLen+5,NULL,abtRxBuf,(void*)&szRxBufLen) != SCARD_S_SUCCESS) return false; } if (pas->ioCard.dwProtocol == SCARD_PROTOCOL_T0) { // Make sure we received the byte-count we expected if (szRxBufLen != 2) return false; // Check if the operation was successful, so an answer is available if (*abtRxBuf == SCARD_OPERATION_ERROR) return false; // Retrieve the response bytes abtRxCmd[4] = abtRxBuf[1]; szRxBufLen = sizeof(abtRxBuf); if (SCardTransmit(pas->hCard,&(pas->ioCard),abtRxCmd,szRxCmdLen,NULL,abtRxBuf,(void*)&szRxBufLen) != SCARD_S_SUCCESS) return false; } #ifdef DEBUG printf(" RX: "); print_hex(abtRxBuf,szRxBufLen); #endif // When the answer should be ignored, just return a succesful result if (pbtRx == NULL || pszRxLen == NULL) return true; // Make sure we have an emulated answer that fits the return buffer if (szRxBufLen < 4 || (szRxBufLen-4) > *pszRxLen) return false; // Wipe out the 4 APDU emulation bytes: D5 4B .. .. .. 90 00 *pszRxLen = ((size_t)szRxBufLen)-4; memcpy(pbtRx,abtRxBuf+2,*pszRxLen); // Transmission went successful return true; } char* acr122_firmware(const nfc_device_spec_t nds) { byte_t abtGetFw[5] = { 0xFF,0x00,0x48,0x00,0x00 }; uint32_t uiResult; acr122_spec_t* pas = (acr122_spec_t*)nds; static char abtFw[11]; size_t szFwLen = sizeof(abtFw); memset(abtFw,0x00,szFwLen); if (pas->ioCard.dwProtocol == SCARD_PROTOCOL_UNDEFINED) { uiResult = SCardControl(pas->hCard,IOCTL_CCID_ESCAPE_SCARD_CTL_CODE,abtGetFw,sizeof(abtGetFw),abtFw,szFwLen,(void*)&szFwLen); } else { uiResult = SCardTransmit(pas->hCard,&(pas->ioCard),abtGetFw,sizeof(abtGetFw),NULL,(byte_t*)abtFw,(void*)&szFwLen); } #ifdef DEBUG if (uiResult != SCARD_S_SUCCESS) { printf("No ACR122 firmware received, Error: %08x\n",uiResult); } #endif return abtFw; } bool acr122_led_red(const nfc_device_spec_t nds, bool bOn) { byte_t abtLed[9] = { 0xFF,0x00,0x40,0x05,0x04,0x00,0x00,0x00,0x00 }; acr122_spec_t* pas = (acr122_spec_t*)nds; byte_t abtBuf[2]; size_t szBufLen = sizeof(abtBuf); if (pas->ioCard.dwProtocol == SCARD_PROTOCOL_UNDEFINED) { return (SCardControl(pas->hCard,IOCTL_CCID_ESCAPE_SCARD_CTL_CODE,abtLed,sizeof(abtLed),abtBuf,szBufLen,(void*)&szBufLen) == SCARD_S_SUCCESS); } else { return (SCardTransmit(pas->hCard,&(pas->ioCard),abtLed,sizeof(abtLed),NULL,(byte_t*)abtBuf,(void*)&szBufLen) == SCARD_S_SUCCESS); } }