mirror of
https://github.com/RfidResearchGroup/ChameleonMini.git
synced 2026-05-12 11:20:37 -07:00
294 lines
9.9 KiB
C
294 lines
9.9 KiB
C
/*
|
|
* MifareUltralight.c
|
|
*
|
|
* Created on: 20.03.2013
|
|
* Author: skuser
|
|
*/
|
|
|
|
#include "MifareUltralight.h"
|
|
#include "ISO14443-3A.h"
|
|
#include "../Codec/ISO14443-2A.h"
|
|
#include "../Memory.h"
|
|
#include "../LEDHook.h"
|
|
|
|
|
|
#define ATQA_VALUE 0x0044
|
|
#define SAK_CL1_VALUE ISO14443A_SAK_INCOMPLETE
|
|
#define SAK_CL2_VALUE ISO14443A_SAK_COMPLETE_NOT_COMPLIANT
|
|
|
|
#define ACK_VALUE 0x0A
|
|
#define ACK_FRAME_SIZE 4 /* Bits */
|
|
#define NAK_INVALID_ARG 0x00
|
|
#define NAK_CRC_ERROR 0x01
|
|
#define NAK_EEPROM_ERROR 0x05
|
|
#define NAK_OTHER_ERROR 0x06
|
|
#define NAK_FRAME_SIZE 4
|
|
|
|
#define CMD_READ 0x30
|
|
#define CMD_READ_FRAME_SIZE 2 /* without CRC bytes */
|
|
#define CMD_WRITE 0xA2
|
|
#define CMD_WRITE_FRAME_SIZE 6 /* without CRC bytes */
|
|
#define CMD_COMPAT_WRITE 0xA0
|
|
#define CMD_COMPAT_WRITE_FRAME_SIZE 2
|
|
#define CMD_HALT 0x50
|
|
|
|
#define UID_CL1_ADDRESS 0x00 /* In Card Memory */
|
|
#define UID_CL1_SIZE 3 /* In Bytes */
|
|
#define UID_BCC1_ADDRESS 0x03
|
|
#define UID_CL2_ADDRESS 0x04
|
|
#define UID_CL2_SIZE 4
|
|
#define UID_BCC2_ADDRESS 0x08
|
|
|
|
#define BYTES_PER_PAGE 4
|
|
#define PAGE_ADDRESS_MASK 0x0F
|
|
|
|
#define BYTES_PER_READ 16
|
|
#define PAGE_READ_MIN 0x00
|
|
#define PAGE_READ_MAX 0x0F
|
|
|
|
#define BYTES_PER_WRITE 4
|
|
#define PAGE_WRITE_MIN 0x02
|
|
#define PAGE_WRITE_MAX 0x0F
|
|
|
|
#define BYTES_PER_COMPAT_WRITE 16
|
|
|
|
static enum {
|
|
STATE_HALT,
|
|
STATE_IDLE,
|
|
STATE_READY1,
|
|
STATE_READY2,
|
|
STATE_ACTIVE,
|
|
STATE_COMPAT_WRITE
|
|
} State;
|
|
|
|
static uint8_t CompatWritePageAddress;
|
|
static bool FromHalt = false;
|
|
|
|
void MifareUltralightAppInit(void)
|
|
{
|
|
State = STATE_IDLE;
|
|
FromHalt = false;
|
|
}
|
|
|
|
void MifareUltralightAppReset(void)
|
|
{
|
|
State = STATE_IDLE;
|
|
}
|
|
|
|
void MifareUltralightAppTask(void)
|
|
{
|
|
|
|
}
|
|
|
|
|
|
uint16_t MifareUltralightAppProcess(uint8_t* Buffer, uint16_t BitCount)
|
|
{
|
|
uint8_t Cmd = Buffer[0];
|
|
|
|
switch(State) {
|
|
case STATE_IDLE:
|
|
case STATE_HALT:
|
|
FromHalt = State == STATE_HALT;
|
|
if (ISO14443AWakeUp(Buffer, &BitCount, ATQA_VALUE, FromHalt)) {
|
|
/* We received a REQA or WUPA command, so wake up. */
|
|
State = STATE_READY1;
|
|
return BitCount;
|
|
}
|
|
break;
|
|
|
|
case STATE_READY1:
|
|
if (ISO14443AWakeUp(Buffer, &BitCount, ATQA_VALUE, FromHalt)) {
|
|
State = FromHalt ? STATE_HALT : STATE_IDLE;
|
|
return ISO14443A_APP_NO_RESPONSE;
|
|
} else if (Cmd == ISO14443A_CMD_SELECT_CL1) {
|
|
/* Load UID CL1 and perform anticollision. Since
|
|
* MF Ultralight use a double-sized UID, the first byte
|
|
* of CL1 has to be the cascade-tag byte. */
|
|
uint8_t UidCL1[ISO14443A_CL_UID_SIZE] = { [0] = ISO14443A_UID0_CT };
|
|
|
|
MemoryReadBlock(&UidCL1[1], UID_CL1_ADDRESS, UID_CL1_SIZE);
|
|
|
|
if (ISO14443ASelect(Buffer, &BitCount, UidCL1, SAK_CL1_VALUE)) {
|
|
/* CL1 stage has ended successfully */
|
|
State = STATE_READY2;
|
|
}
|
|
|
|
return BitCount;
|
|
} else {
|
|
/* Unknown command. Enter halt state */
|
|
State = STATE_IDLE;
|
|
}
|
|
break;
|
|
|
|
case STATE_READY2:
|
|
if (ISO14443AWakeUp(Buffer, &BitCount, ATQA_VALUE, FromHalt)) {
|
|
State = FromHalt ? STATE_HALT : STATE_IDLE;
|
|
return ISO14443A_APP_NO_RESPONSE;
|
|
} else if (Cmd == ISO14443A_CMD_SELECT_CL2) {
|
|
/* Load UID CL2 and perform anticollision */
|
|
uint8_t UidCL2[ISO14443A_CL_UID_SIZE];
|
|
|
|
MemoryReadBlock(UidCL2, UID_CL2_ADDRESS, UID_CL2_SIZE);
|
|
|
|
if (ISO14443ASelect(Buffer, &BitCount, UidCL2, SAK_CL2_VALUE)) {
|
|
/* CL2 stage has ended successfully. This means
|
|
* our complete UID has been sent to the reader. */
|
|
State = STATE_ACTIVE;
|
|
}
|
|
|
|
return BitCount;
|
|
} else {
|
|
/* Unknown command. Enter halt state */
|
|
State = STATE_IDLE;
|
|
}
|
|
break;
|
|
|
|
case STATE_ACTIVE:
|
|
if (ISO14443AWakeUp(Buffer, &BitCount, ATQA_VALUE, FromHalt)) {
|
|
State = FromHalt ? STATE_HALT : STATE_IDLE;
|
|
return ISO14443A_APP_NO_RESPONSE;
|
|
} else if (Cmd == CMD_READ) {
|
|
uint8_t PageAddress = Buffer[1];
|
|
|
|
if (ISO14443ACheckCRCA(Buffer, CMD_READ_FRAME_SIZE)) {
|
|
if ( (PageAddress >= PAGE_READ_MIN)
|
|
&& (PageAddress <= PAGE_READ_MAX) ) {
|
|
/* TODO: Missing address wrap around behaviour.
|
|
* Implement using a for-loop copying 4 bytes each iteration
|
|
* and mask pageaddress */
|
|
MemoryReadBlock(Buffer, PageAddress * BYTES_PER_PAGE, BYTES_PER_READ);
|
|
ISO14443AAppendCRCA(Buffer, BYTES_PER_READ);
|
|
return (BYTES_PER_READ + ISO14443A_CRCA_SIZE) * 8;
|
|
} else {
|
|
Buffer[0] = NAK_INVALID_ARG;
|
|
return NAK_FRAME_SIZE;
|
|
}
|
|
} else {
|
|
Buffer[0] = NAK_CRC_ERROR;
|
|
return NAK_FRAME_SIZE;
|
|
}
|
|
} else if (Cmd == CMD_WRITE) {
|
|
/* This is a write command containing 4 bytes of data that
|
|
* should be written to the given page address. */
|
|
uint8_t PageAddress = Buffer[1];
|
|
if (ISO14443ACheckCRCA(Buffer, CMD_WRITE_FRAME_SIZE)) {
|
|
/* CRC check passed */
|
|
if ( (PageAddress >= PAGE_WRITE_MIN)
|
|
&& (PageAddress <= PAGE_WRITE_MAX) ) {
|
|
/* PageAddress is within bounds. */
|
|
|
|
if (!ActiveConfiguration.ReadOnly) {
|
|
MemoryWriteBlock(&Buffer[2], PageAddress * BYTES_PER_PAGE, BYTES_PER_WRITE);
|
|
} else {
|
|
/* If the chameleon is in read only mode, it silently
|
|
* ignores any attempt to write data. */
|
|
}
|
|
|
|
Buffer[0] = ACK_VALUE;
|
|
return ACK_FRAME_SIZE;
|
|
|
|
} else {
|
|
Buffer[0] = NAK_INVALID_ARG;
|
|
return NAK_FRAME_SIZE;
|
|
}
|
|
} else {
|
|
Buffer[0] = NAK_CRC_ERROR;
|
|
return NAK_FRAME_SIZE;
|
|
}
|
|
} else if (Cmd == CMD_COMPAT_WRITE) {
|
|
/* The Mifare compatbility write command is a 2-frame command.
|
|
* The first frame contains the page-address and the second frame
|
|
* holds the data. */
|
|
uint8_t PageAddress = Buffer[1];
|
|
|
|
if (ISO14443ACheckCRCA(Buffer, CMD_COMPAT_WRITE_FRAME_SIZE)) {
|
|
if ( (PageAddress >= PAGE_WRITE_MIN)
|
|
&& (PageAddress <= PAGE_WRITE_MAX) ) {
|
|
/* CRC check passed and page-address is within bounds.
|
|
* Store address and proceed to receiving the data. */
|
|
CompatWritePageAddress = PageAddress;
|
|
State = STATE_COMPAT_WRITE;
|
|
|
|
Buffer[0] = ACK_VALUE;
|
|
return ACK_FRAME_SIZE;
|
|
} else {
|
|
Buffer[0] = NAK_INVALID_ARG;
|
|
return NAK_FRAME_SIZE;
|
|
}
|
|
} else {
|
|
Buffer[0] = NAK_CRC_ERROR;
|
|
return NAK_FRAME_SIZE;
|
|
}
|
|
} else if (Cmd == CMD_HALT) {
|
|
/* Halts the tag. According to the ISO14443, the second
|
|
* byte is supposed to be 0. */
|
|
if (Buffer[1] == 0) {
|
|
if (ISO14443ACheckCRCA(Buffer, 2)) {
|
|
/* According to ISO14443, we must not send anything
|
|
* in order to acknowledge the HALT command. */
|
|
State = STATE_HALT;
|
|
return ISO14443A_APP_NO_RESPONSE;
|
|
} else {
|
|
Buffer[0] = NAK_CRC_ERROR;
|
|
return NAK_FRAME_SIZE;
|
|
}
|
|
} else {
|
|
Buffer[0] = NAK_INVALID_ARG;
|
|
return NAK_FRAME_SIZE;
|
|
}
|
|
} else {
|
|
/* Unknown command. Enter halt state */
|
|
State = STATE_IDLE;
|
|
}
|
|
break;
|
|
|
|
case STATE_COMPAT_WRITE:
|
|
/* Compatibility write. Receiving 16 bytes of data of which 4 bytes are valid. */
|
|
if (ISO14443ACheckCRCA(Buffer, BYTES_PER_COMPAT_WRITE)) {
|
|
/* We don't perform any checks here. You will be able to program the
|
|
* whole memory. Also there is no OTP behaviour. */
|
|
if (!ActiveConfiguration.ReadOnly) {
|
|
MemoryWriteBlock(Buffer, CompatWritePageAddress * BYTES_PER_PAGE, BYTES_PER_WRITE);
|
|
} else {
|
|
/* If we are told to be read only, we silently ignore the write command
|
|
* and pretend to have written data. */
|
|
}
|
|
|
|
State = STATE_ACTIVE;
|
|
Buffer[0] = ACK_VALUE;
|
|
return ACK_FRAME_SIZE;
|
|
} else {
|
|
State = STATE_ACTIVE;
|
|
Buffer[0] = NAK_CRC_ERROR;
|
|
return NAK_FRAME_SIZE;
|
|
}
|
|
|
|
default:
|
|
/* Unknown state? Should never happen. */
|
|
break;
|
|
}
|
|
|
|
/* No response has been sent, when we reach here */
|
|
return ISO14443A_APP_NO_RESPONSE;
|
|
}
|
|
|
|
void MifareUltralightGetUid(ConfigurationUidType Uid)
|
|
{
|
|
/* Read UID from memory */
|
|
MemoryReadBlock(&Uid[0], UID_CL1_ADDRESS, UID_CL1_SIZE);
|
|
MemoryReadBlock(&Uid[UID_CL1_SIZE], UID_CL2_ADDRESS, UID_CL2_SIZE);
|
|
}
|
|
|
|
void MifareUltralightSetUid(ConfigurationUidType Uid)
|
|
{
|
|
/* Calculate check bytes and write everything into memory */
|
|
uint8_t BCC1 = ISO14443A_UID0_CT ^ Uid[0] ^ Uid[1] ^ Uid[2];
|
|
uint8_t BCC2 = Uid[3] ^ Uid[4] ^ Uid[5] ^ Uid[6];
|
|
|
|
MemoryWriteBlock(&Uid[0], UID_CL1_ADDRESS, UID_CL1_SIZE);
|
|
MemoryWriteBlock(&BCC1, UID_BCC1_ADDRESS, ISO14443A_CL_BCC_SIZE);
|
|
MemoryWriteBlock(&Uid[UID_CL1_SIZE], UID_CL2_ADDRESS, UID_CL2_SIZE);
|
|
MemoryWriteBlock(&BCC2, UID_BCC2_ADDRESS, ISO14443A_CL_BCC_SIZE);
|
|
}
|
|
|