Превращаем Android в smart card reader
Сегодня будем создавать драйвер для pcsc под линукс, который будет соединятся с устройством на базе android.
Для этого понадобится:
Устройство с Android 2.3.3 и выше
Компьютер с Ubuntu и usb разъёмами)))
Netbeans 7.1 http://netbeans.org/downloads/index.html
Android sdk http://developer.android.com/sdk/index.html
Скелеты - исходники самого драйвера http://www.linuxnet.com/sourcedrivers.html
Драйвер будет работать по такому алгоритму,
при подключении устройства с определенному vid и pid, который мы укажем, будет вызываться функция, IFDHCreateChannel(DWORD Lun, DWORD Channel),
в функции переводиться устройство в режим accessory и выдает ошибку,
устройство меняет vid и pid и пытаться соединится снова.
Происходит подключение и инициализация устройства.
RESPONSECODE IFDHCreateChannel(DWORD Lun, DWORD Channel)
{
int ret = 0;
int isInit = 0;
int i = 0;
for (i = 0; i < sizeof (UsbIDs) / sizeof (UsbIDs[0]); i++)
{
ret = usb_InitAll(UsbIDs[i].vendorID, UsbIDs[i].productID);
if (ret == 255)
isInit = 1;
}
if (isInit)
{
Log("Initialization devices");
return IFD_COMMUNICATION_ERROR;
}
if (readers[ReaderNum].init != 0)
{
Log("Device is opened");
return IFD_COMMUNICATION_ERROR;
}
ret = usb_OpenReader(&readers[ReaderNum], readers);
if (ret == 0)
{
ret = InitReader(&readers[ReaderNum]);
if (ret != 0)
{
usb_CloseReader(&readers[ReaderNum]);
Logd("Init fail not response to android", ret);
return IFD_COMMUNICATION_ERROR;
}
ogd("Init OK %ld", ReaderNum);
return IFD_SUCCESS;
}
usb_CloseReader(&readers[ReaderNum]);
return IFD_COMMUNICATION_ERROR;
}
Далее pcsc посылает запрос сколько в ридере слотов, у нас он один, а так бывает контактная карта, без контактная и магнитная карта.
RESPONSECODE IFDHGetCapabilities(DWORD Lun, DWORD Tag,
PDWORD Length, PUCHAR Value)
{
Log("");
switch (Tag)
{
case 0x090303://для старых версий pcsc
case TAG_IFD_ATR:
{
syslog(LOG_INFO, "TAG_IFD_ATR\n");
memcpy(Value, mAtr, MAX_ATR_SIZE);
*Length = 18;
break;
}
case TAG_IFD_SIMULTANEOUS_ACCESS:
{
syslog(LOG_INFO, "TAG_IFD_SIMULTANEOUS_ACCESS\n");
*Length = 1;
*Value = 2;
break;
}
case TAG_IFD_SLOTS_NUMBER:
{
syslog(LOG_INFO, "TAG_IFD_SLOTS_NUMBER\n");
*Length = 1;
*Value = 1;
break;
}
default:
*Length = 0;
return IFD_ERROR_TAG;
}
return IFD_SUCCESS;
}
еще в нашей функции pcsc определяет сколько ридеров поддерживает драйвер и запрашивает атр.
После pcsc пытается подать напряжение на карточку и получить atr карточки,
в ней просто возвращаем атр от карточки, которую я скопировал с тестовой карточки у себя.
RESPONSECODE IFDHPowerICC(DWORD Lun, DWORD Action,
PUCHAR Atr, PDWORD AtrLength)
{
Log("");
if (Action == IFD_RESET || Action == IFD_POWER_UP)
{
memcpy(Atr, mAtr, MAX_ATR_SIZE);
*AtrLength = 18;
}
return IFD_SUCCESS;
}
По атр pcsc определят по какому протоколу работает ридер с картой, мы будем работать по T=0.
Теперь pcsc приняла все настройки и работает в штатном режиме, то есть каждые пол секунды считывает вставлена ли карта, вызывая функцию IFDHICCPresence(DWORD Lun), здесь мы будем получать от программы отклик, и тем самым определять работает ли она.
RESPONSECODE IFDHICCPresence(DWORD Lun)
{
int ret = 0;
unsigned char tmpbuf[MAX_BUFFER_SIZE];
int len = 1;
tmpbuf[0] = 0x85;
ret = Send(&readers[ReaderNum], tmpbuf, len);
if (ret != 0)
return IFD_ICC_NOT_PRESENT;
len = MAX_BUFFER_SIZE;
ret = Recv(&readers[ReaderNum], tmpbuf, &len);
return ret == 0 ? IFD_ICC_PRESENT : IFD_ICC_NOT_PRESENT;
}
Еще одна важная функция, это общение с самим «ридром», отсылать и принимать данные.
RESPONSECODE IFDHTransmitToICC(DWORD Lun, SCARD_IO_HEADER SendPci,
PUCHAR TxBuffer, DWORD TxLength,
PUCHAR RxBuffer, PDWORD RxLength,
PSCARD_IO_HEADER RecvPci)
{
Log("");
int ret = 0;
ret = SendTask(&readers[ReaderNum], TxBuffer, (int) TxLength);
if (ret == LIBUSB_ERROR_TIMEOUT)
return IFD_RESPONSE_TIMEOUT;
else
if (ret != 0)
return IFD_COMMUNICATION_ERROR;
unsigned char tmpbuf[MAX_BUFFER_SIZE];
int len = MAX_BUFFER_SIZE;
ret = Recv(&readers[ReaderNum], tmpbuf, &len);
*RxLength = 0;
if (ret == LIBUSB_ERROR_TIMEOUT)
return IFD_RESPONSE_TIMEOUT;
else
if (ret != 0)
return IFD_COMMUNICATION_ERROR;
memcpy(RxBuffer, tmpbuf, len);
*RxLength = len;
return IFD_SUCCESS;
}
функция отсылает данные и сразу принимает. В ответе мы получаем правильно ли распарсило и отработало устройство, так как мне не нужно получать данные от устройства.
Работа с usb
Инициализация и перевод устройства в режим accessory происходит в функции,
int usb_InitAll(int vid, int pid) эта функци посылает управляющие коды устройству, которые честно взяты из примера adk.
Сначала посылается код для получение версии протокола
ret = libusb_control_transfer(handle,
LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR |
LIBUSB_RECIPIENT_DEVICE, ADK_COMMAND_GETPROTOCOL, 0, 0,
(unsigned char *) &version, sizeof (version), 0);
далее отсылаются строки которые характеризую что подключено к андроиду, и следом код активирующий режим адк
ret = libusb_control_transfer(handle,
LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR |
LIBUSB_RECIPIENT_DEVICE, ADK_COMMAND_START, 0, 0, NULL, 0, 0);
usb_OpenReader(usbDevice* reader, usbDevice* allreaders);
usb_CloseReader(usbDevice* reader);
usb_Read(usbDevice* reader, unsigned char *buf, int *len, unsigned int TimeOut);
usb_Write(usbDevice* reader, unsigned char *buf, int len, unsigned int TimeOut);
эти функции выполняют открытие, закрытие и передачу, прием данных, в них осуществляется работа с библиотекой libusb-1.0
Данные на устройство передаются по собственному протоколу,
в начале идет байт начало сообщения, далее 2 байта размер сообщения, само сообщение и 2 байта crc сумма.
продолжение следует...