osmocom-analog/src/sim/sim.c

1442 lines
34 KiB
C

/* SIM card emulator
*
* (C) 2020 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* This program 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.
*
* 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 General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#ifndef ARDUINO
#include "../libdebug/debug.h"
#endif
#include "sim.h"
#include "eeprom.h"
#ifdef ARDUINO
#define PDEBUG(cat, level, fmt, arg...) while(0)
#define EINVAL 22
static uint32_t my_strtoul(const char *nptr, char **endptr, int base)
{
uint32_t number = 0;
while (*nptr >= '0' && *nptr <= '9')
number = number * 10 + (*nptr++ - '0');
return number;
}
#else
#define my_strtoul strtoul
#endif
static void my_ultostr(char *nptr, uint32_t value, int zeros)
{
int digits = 0;
uint32_t temp;
/* count digits */
temp = value;
while (temp) {
temp /= 10;
digits++;
}
/* minium digits to fill up with '0' */
if (digits < zeros)
digits = zeros;
/* go to end and terminate */
nptr += digits;
*nptr-- = '\0';
/* apply digits backwards */
while (digits--) {
*nptr-- = (value % 10) + '0';
value /= 10;
}
}
static void tx_sdu(sim_sim_t *sim, uint8_t ccrc, uint8_t *data, int length);
static void tx_pdu(sim_sim_t *sim, uint8_t *data, int length);
static void tx_block(sim_sim_t *sim, enum l2_cmd cmd, uint8_t *data, int length);
/* read flags from eeprom */
static void read_flags(sim_sim_t *sim)
{
uint8_t flags;
flags = eeprom_read(EEPROM_FLAGS);
sim->pin_len = (flags >> EEPROM_FLAG_PIN_LEN) & 0xf;
sim->pin_try = (flags >> EEPROM_FLAG_PIN_TRY) & 0x3;
if ((flags >> EEPROM_FLAG_GEBZ) & 0x1)
sim->gebz_locked = 1;
if ((flags >> EEPROM_FLAG_APP) & 0x1)
sim->app_locked = 1;
}
/* write flags to eeprom */
static void write_flags(sim_sim_t *sim)
{
uint8_t flags = 0;
flags |= sim->pin_len << EEPROM_FLAG_PIN_LEN;
flags |= sim->pin_try << EEPROM_FLAG_PIN_TRY;
if (sim->gebz_locked)
flags |= (1 << EEPROM_FLAG_GEBZ);
if (sim->app_locked)
flags |= (1 << EEPROM_FLAG_APP);
eeprom_write(EEPROM_FLAGS, flags);
}
/* encode EBDT from strings */
int encode_ebdt(uint8_t *data, const char *futln, const char *sicherung, const char *karten, const char *sonder, const char *wartung)
{
uint32_t temp;
int i;
if (futln) {
temp = strlen(futln);
if (temp < 7 || temp > 8) {
PDEBUG(DSIM7, DEBUG_NOTICE, "Given FUTLN '%s' invalid length. (Must be 7 or 8 Digits)\n", futln);
return -EINVAL;
}
if (futln[0] < '0' || futln[0] > '7') {
PDEBUG(DSIM7, DEBUG_NOTICE, "Given FUTLN '%s' has invalid first digit. (Must be '0' .. '7')\n", futln);
return -EINVAL;
}
data[0] = (futln[0] - '0') << 5;
futln++;
if (temp == 8) {
/* 8 digits */
temp = (futln[0] - '0') * 10 + (futln[1] - '0');
if (futln[0] < '0' || futln[0] > '9' || futln[1] < '0' || futln[1] > '9' || temp > 31) {
PDEBUG(DSIM7, DEBUG_NOTICE, "Given FUTLN '%s' has invalid second and third digit. (Must be '00' .. '31')\n", futln);
return -EINVAL;
}
data[0] |= temp;
futln += 2;
} else {
/* 7 digits */
if (futln[0] < '0' || futln[0] > '9') {
PDEBUG(DSIM7, DEBUG_NOTICE, "Given FUTLN '%s' has invalid second digit. (Must be '0' .. '9')\n", futln);
return -EINVAL;
}
data[0] |= (futln[0] - '0');
futln++;
}
for (i = 0; i < 5; i++) {
if (futln[i] < '0' || futln[i] > '9')
break;
}
temp = my_strtoul(futln, NULL, 0);
if (i < 5 || temp > 65535) {
PDEBUG(DSIM7, DEBUG_NOTICE, "Given FUTLN '%s' has invalid last digits. (Must be '00000' .. '65535')\n", futln);
return -EINVAL;
}
data[1] = temp >> 8;
data[2] = temp;
}
if (sicherung) {
temp = my_strtoul(sicherung, NULL, 0);
if (temp > 65535) {
PDEBUG(DSIM7, DEBUG_NOTICE, "Given security code '%s' has invalid digits. (Must be '0' .. '65535')\n", sicherung);
return -EINVAL;
}
data[3] = temp >> 8;
data[4] = temp;
}
if (karten) {
temp = my_strtoul(karten, NULL, 0);
if (temp > 7) {
PDEBUG(DSIM7, DEBUG_NOTICE, "Given card number '%s' has invalid digit. (Must be '0' .. '7')\n", karten);
return -EINVAL;
}
data[5] = (data[5] & 0x1f) | ((karten[0] - '0') << 5);
}
if (sonder) {
temp = my_strtoul(sonder, NULL, 0);
if (temp > 8191) {
PDEBUG(DSIM7, DEBUG_NOTICE, "Given spacial code '%s' has invalid digits. (Must be '0' .. '8191')\n", sonder);
return -EINVAL;
}
data[5] = (data[5] & 0xe0) | (temp >> 8);
data[6] = temp;
}
if (wartung) {
temp = my_strtoul(wartung, NULL, 0);
if (temp > 65535) {
PDEBUG(DSIM7, DEBUG_NOTICE, "Given maintenance code '%s' has invalid digits. (Must be '0' .. '65535')\n", wartung);
return -EINVAL;
}
data[7] = temp >> 8;
data[8] = temp;
}
return 0;
}
/* convert EBDT to string */
void decode_ebdt(uint8_t *data, char *futln, char *sicherung, char *karten, char *sonder, char *wartung)
{
if (futln) {
/* second value becomes two digits automatically, if > 9 */
my_ultostr(futln++, data[0] >> 5, 1);
my_ultostr(futln++, data[0] & 0x1f, 1);
if (*futln)
futln++;
my_ultostr(futln, ((uint16_t)data[1] << 8) | (uint16_t)data[2], 5);
}
if (sicherung)
my_ultostr(sicherung, ((uint16_t)data[3] << 8) | (uint16_t)data[4], 1);
if (karten)
my_ultostr(karten, data[5] >> 5, 1);
if (sonder)
my_ultostr(sonder, ((uint16_t)(data[5] & 0x1f) << 8) | (uint16_t)data[6], 1);
if (wartung)
my_ultostr(wartung, ((uint16_t)data[7] << 8) | (uint16_t)data[8], 1);
}
/* get size of phone directory (including allocation map) */
int directory_size(void)
{
/* get size from space in eeprom */
int size = (eeprom_length() - EEPROM_RUFN) / 24;
/* may have 184 entries (23*8) plus allocation map (entry 0) */
if (size > 184 + 1)
size = 184 + 1;
return size;
}
/* store one phone number in the directory; also set allocation mask) */
int save_directory(int location, uint8_t *data)
{
int size, i, pos;
uint8_t mask;
size = directory_size();
if (location < 1 || location >= size) {
PDEBUG(DSIM7, DEBUG_NOTICE, "Given location for phone number '%d' is out of range. (Must be '01' .. '%02d')\n", location, size - 1);
return -EINVAL;
}
/* store entry */
for (i = 0; i < 24; i++)
eeprom_write(EEPROM_RUFN + 24 * location + i, data[i]);
/* set bit mask */
pos = EEPROM_RUFN + 1 + ((location - 1) >> 3);
mask = eeprom_read(pos);
if ((data[7] & 0xf) == 0xf)
mask |= (0x80 >> ((location - 1) & 7));
else
mask &= ~(0x80 >> ((location - 1) & 7));
eeprom_write(pos, mask);
return 0;
}
/* load one phone number from the directory; location 0 is the allocation mask) */
void load_directory(int location, uint8_t *data)
{
int i;
for (i = 0; i < 24; i++)
data[i] = eeprom_read(EEPROM_RUFN + 24 * location + i);
/* set directory size, on allocation map */
if (location == 0)
data[0] = directory_size() - 1;
}
/* encode number an name into directory data */
int encode_directory(uint8_t *data, const char *number, const char *name)
{
int len, pos, i;
len = strlen(number);
if (len > 16) {
PDEBUG(DSIM7, DEBUG_NOTICE, "Given phone number '%s' has too many digits. (Must be <= 16)\n", number);
return -EINVAL;
}
memset(data, 0xff, 8);
memset(data + 8, ' ', 16);
for (i = 0; i < len; i++) {
if (number[i] < '0' || number[i] > '9') {
PDEBUG(DSIM7, DEBUG_NOTICE, "Given phone number '%s' has illegal digits. (Must be '0' .. '9')\n", number);
return -EINVAL;
}
pos = 16 - len + i;
if ((pos & 1) == 0)
data[pos >> 1] += ((number[i] - '0') << 4) - 0xf0;
else
data[pos >> 1] += number[i] - '0' - 0xf;
}
len = strlen(name);
if (len > 16)
len = 16;
for (i = 0; i < len; i++) {
pos = 8 + i;
data[pos] = name[i];
}
return 0;
}
void decode_directory(uint8_t *data, char *number, char *name)
{
int i, j;
char digit;
if (number) {
j = 0;
for (i = 0; i < 16; i++) {
if ((i & 1) == 0)
digit = (data[i >> 1] >> 4) + '0';
else
digit = (data[i >> 1] & 0xf) + '0';
if (digit <= '9')
number[j++] = digit;
}
number[j] = '\0';
}
if (name) {
memcpy(name, data + 8, 16);
name[16] = '\0';
/* remove spaces in the end of the string */
for (i = 16 - 1; i >= 0; i--) {
if (name[i] != ' ')
break;
name[i] = '\0';
}
}
}
/* get APRC of NETZ-C application */
static uint8_t get_aprc(sim_sim_t *sim)
{
uint8_t aprc = 0x00;
if (sim->pin_required)
aprc |= APRC_PIN_REQ;
if (sim->app_locked)
aprc |= APRC_APP_LOCKED;
if (sim->gebz_locked)
aprc |= APRC_GEBZ_LOCK;
if (sim->gebz_full)
aprc |= APRC_GEBZ_FULL;
return aprc;
}
/* validate PIN and change states */
static int validate_pin(sim_sim_t *sim, uint8_t *data, int length)
{
uint8_t valid = 0, program_mode = 0;
int i;
if (!sim->pin_required)
return 0;
/* no PIN mode */
if (length == 4 && data[0] == '0' && data[1] == '0' && data[2] == '0' && data[3] >= '0' && data[3] <= '0' + MAX_CARDS) {
valid = 1;
if (data[3] > '0')
sim->card = data[3] - '1';
PDEBUG(DSIM1, DEBUG_INFO, "System PIN '000%c' entered. Selecting card #%d.\n", data[3], sim->card + 1);
}
/* programming mode */
if (length == 4 && data[0] == '9' && data[1] == '9' && data[2] == '9' && data[3] >= '0' && data[3] <= '0' + MAX_CARDS) {
program_mode = 1;
valid = 1;
if (data[3] > '0')
sim->card = data[3] - '1';
PDEBUG(DSIM1, DEBUG_INFO, "Configuration PIN '999%c' entered. Selecting card #%d in configuration mode.\n", data[3], sim->card + 1);
}
/* if not 'program mode' and PIN matches EEPROM */
if (!valid && length == sim->pin_len) {
for (i = 0; i < length; i++) {
if (data[i] != eeprom_read(EEPROM_PIN_DATA + i))
break;
}
if (i == length) {
valid = 1;
PDEBUG(DSIM1, DEBUG_INFO, "Correct PIN was entered. Selecting card #%d.\n", sim->card + 1);
}
}
if (valid) {
/* prevent permanent write when not needed */
if (sim->pin_try != MAX_PIN_TRY) {
sim->pin_try = MAX_PIN_TRY;
write_flags(sim);
}
sim->pin_required = 0;
if (program_mode)
sim->program_mode = 1;
return 0;
} else {
PDEBUG(DSIM1, DEBUG_INFO, "Wrong PIN was entered.\n");
#ifndef ARDUINO
/* decrement error counter */
if (sim->pin_try) {
sim->pin_try--;
write_flags(sim);
}
#endif
return -EINVAL;
}
}
/* message buffer handling */
/* get space for return message */
uint8_t *alloc_msg(sim_sim_t *sim, int size)
{
/* we add 4, because we push 4 bytes (ICL and L2 header later) */
if (size + 4 > (int)sizeof(sim->block_tx_data))
PDEBUG(DSIM1, DEBUG_NOTICE, "TX buffer overflow: size+4=%d > buffer size (%d)\n", size + 4, (int)sizeof(sim->block_tx_data));
return sim->block_tx_data;
}
/* push space in front of a message */
uint8_t *push_msg(uint8_t *data, int length, int offset)
{
int i;
for (i = length - 1; i >= 0; --i)
data[i + offset] = data[i];
return data;
}
/* Layer 7 */
static void return_error(sim_sim_t *sim)
{
uint8_t *data;
data = alloc_msg(sim, 0);
tx_sdu(sim, CCRC_ERROR, data, 0);
}
static void return_pin_not_ok(sim_sim_t *sim)
{
uint8_t *data;
data = alloc_msg(sim, 0);
tx_sdu(sim, CCRC_PIN_NOK, data, 0);
}
/* command: open application */
static void sl_appl(sim_sim_t *sim, uint8_t *data, int length)
{
uint8_t app;
if (length < 11) {
PDEBUG(DSIM7, DEBUG_NOTICE, "SL-APPL too short\n");
return_error(sim);
return;
}
/* application number */
app = (data[6] - '0') * 100;
app += (data[7] - '0') * 10;
app += data[8] - '0';
PDEBUG(DSIM7, DEBUG_INFO, " SL-APPL app %d\n", app);
/* check and set application */
if (app != APP_NETZ_C && app != APP_RUFN_GEBZ) {
PDEBUG(DSIM7, DEBUG_NOTICE, "SL-APPL invalid app %d\n", sim->app);
return_error(sim);
return;
}
sim->app = app;
/* if PIN is required, we request it, but we've already selected the app */
if (sim->pin_required) {
return_pin_not_ok(sim);
return;
}
/* respond */
data = alloc_msg(sim, 0);
tx_sdu(sim, 0, data, 0);
}
/* command: close application */
static void cl_appl(sim_sim_t *sim)
{
uint8_t *data;
PDEBUG(DSIM7, DEBUG_INFO, " CL-APPL\n");
/* remove app */
sim->app = 0;
/* respond */
data = alloc_msg(sim, 0);
tx_sdu(sim, 0, data, 0);
}
/* command: show application */
static void sh_appl(sim_sim_t *sim)
{
uint8_t *data;
PDEBUG(DSIM7, DEBUG_INFO, " SH-APPL\n");
/* respond */
data = alloc_msg(sim, 33);
switch (sim->sh_appl_count) {
case 0: // first application is shown
/* L */
data[0] = 11;
/* APP-IDN */
data[1] = '8'; data[2] = '9';
data[3] = '4'; data[4] = '9';
data[5] = '0'; data[6] = '1';
data[7] = '0'; data[8] = '0'; data[9] = '3';
data[10] = '0'; data[11] = '1';
/* APP-TXT */
memcpy(data + 12, "Netz C ", 20);
/* APP-STS */
data[32] = get_aprc(sim);
tx_sdu(sim, 0, data, 33);
sim->sh_appl_count++;
break;
default: // no more application
tx_sdu(sim, 0, data, 0);
sim->sh_appl_count = 0;
}
}
/* command: show state of chip card */
static void chk_kon(sim_sim_t *sim)
{
uint8_t *data;
PDEBUG(DSIM7, DEBUG_INFO, " CHK-KON\n");
/* respond */
data = alloc_msg(sim, 0);
tx_sdu(sim, 0, data, 0);
}
/* command: read subscriber data */
static void rd_ebdt(sim_sim_t *sim)
{
uint8_t *data;
PDEBUG(DSIM7, DEBUG_INFO, " RD-EBDT\n");
/* respond */
data = alloc_msg(sim, 9);
if (sim->program_mode) {
/* SERVICE MODE */
data[0] = 0;
data[1] = 0;
data[2] = sim->card + 1;
data[3] = 12345 >> 8;
data[4] = 12345 & 0xff;
data[5] = 3 << 5;
data[6] = 0;
data[7] = 0x0ff;
data[8] = 0x0ff;
} else {
data[0] = eeprom_read(EEPROM_FUTLN_H + sim->card);
data[1] = eeprom_read(EEPROM_FUTLN_M + sim->card);
data[2] = eeprom_read(EEPROM_FUTLN_L + sim->card);
data[3] = eeprom_read(EEPROM_SICH_H + sim->card);
data[4] = eeprom_read(EEPROM_SICH_L + sim->card);
data[5] = eeprom_read(EEPROM_SONDER_H + sim->card);
data[6] = eeprom_read(EEPROM_SONDER_L + sim->card);
data[7] = eeprom_read(EEPROM_WARTUNG_H + sim->card);
data[8] = eeprom_read(EEPROM_WARTUNG_L + sim->card);
}
tx_sdu(sim, 0, data, 9);
}
/* command: read phone directory */
static void rd_rufn(sim_sim_t *sim, uint8_t *data, int length)
{
uint8_t rufn = data[0];
int size;
if (length < 1) {
PDEBUG(DSIM7, DEBUG_NOTICE, "RD_RUFN too short\n");
return_error(sim);
return;
}
PDEBUG(DSIM7, DEBUG_INFO, " RD-RUFN (loc=%d)\n", rufn);
/* SERVICE MODE */
if (sim->program_mode) {
char number[16];
/* respond */
data = alloc_msg(sim, 24);
switch (rufn) {
case 0: /* send bitmap for service mode */
memset(data, 0xff, 24);
data[0] = 5; /* 5 entries */
data[1] = 0x07; /* upper 5 bits = 0 */
break;
case 1: /* FUTLN */
data[0] = eeprom_read(EEPROM_FUTLN_H + sim->card);
data[1] = eeprom_read(EEPROM_FUTLN_M + sim->card);
data[2] = eeprom_read(EEPROM_FUTLN_L + sim->card);
decode_ebdt(data, number, NULL, NULL, NULL, NULL);
encode_directory(data, number, "FUTLN");
PDEBUG(DSIM7, DEBUG_INFO, "service mode: FUTLN = %s\n", number);
break;
case 2: /* security code */
data[3] = eeprom_read(EEPROM_SICH_H + sim->card);
data[4] = eeprom_read(EEPROM_SICH_L + sim->card);
decode_ebdt(data, NULL, number, NULL, NULL, NULL);
encode_directory(data, number, "Sicherungscode");
PDEBUG(DSIM7, DEBUG_INFO, "service mode: security = %s\n", number);
break;
case 3: /* card ID */
data[5] = eeprom_read(EEPROM_SONDER_H + sim->card);
decode_ebdt(data, NULL, NULL, number, NULL, NULL);
encode_directory(data, number, "Kartenkennung");
PDEBUG(DSIM7, DEBUG_INFO, "service mode: card = %s\n", number);
break;
case 4: /* special key */
data[5] = eeprom_read(EEPROM_SONDER_H + sim->card);
data[6] = eeprom_read(EEPROM_SONDER_L + sim->card);
decode_ebdt(data, NULL, NULL, NULL, number, NULL);
encode_directory(data, number, "Sonderheitsschl.");
PDEBUG(DSIM7, DEBUG_INFO, "service mode: special = %s\n", number);
break;
case 5: /* maintenance key */
data[7] = eeprom_read(EEPROM_WARTUNG_H + sim->card);
data[8] = eeprom_read(EEPROM_WARTUNG_L + sim->card);
decode_ebdt(data, NULL, NULL, NULL, NULL, number);
encode_directory(data, number, "Wartungsschl.");
PDEBUG(DSIM7, DEBUG_INFO, "service mode: maintenance = %s\n", number);
break;
}
tx_sdu(sim, 0, data, 24);
return;
}
size = directory_size();
/* first entry (0) is used as allocation map */
PDEBUG(DSIM7, DEBUG_INFO, " %d numbers can be stored in EEPROM\n", size - 1);
if (rufn >= size) {
PDEBUG(DSIM7, DEBUG_NOTICE, "RD_RUFN entry #%d out of range\n", rufn);
return_error(sim);
return;
}
/* respond */
data = alloc_msg(sim, 24);
load_directory(rufn, data);
tx_sdu(sim, 0, data, 24);
}
/* command: write phone directory */
static void wt_rufn(sim_sim_t *sim, uint8_t *data, int length)
{
uint8_t rufn = data[0];
if (length < 25) {
PDEBUG(DSIM7, DEBUG_NOTICE, "WT_RUFN too short\n");
return_error(sim);
return;
}
PDEBUG(DSIM7, DEBUG_INFO, " WT-RUFN (loc=%d)\n", rufn);
/* SERVICE MODE */
if (sim->program_mode) {
int rc;
char number[17];
decode_directory(data + 1, number, NULL);
/* if number is cleared, we ignore that */
if (number[0] == '\0')
goto respond;
switch (rufn) {
case 1: /* FUTLN */
PDEBUG(DSIM7, DEBUG_INFO, "service mode: FUTLN = %s\n", number);
rc = encode_ebdt(data, number, NULL, NULL, NULL, NULL);
if (rc < 0)
break;
eeprom_write(EEPROM_FUTLN_H + sim->card, data[0]);
eeprom_write(EEPROM_FUTLN_M + sim->card, data[1]);
eeprom_write(EEPROM_FUTLN_L + sim->card, data[2]);
break;
case 2: /* security code */
PDEBUG(DSIM7, DEBUG_INFO, "service mode: security = %s\n", number);
rc = encode_ebdt(data, NULL, number, NULL, NULL, NULL);
if (rc < 0)
break;
eeprom_write(EEPROM_SICH_H + sim->card, data[3]);
eeprom_write(EEPROM_SICH_L + sim->card, data[4]);
break;
case 3: /* card ID */
PDEBUG(DSIM7, DEBUG_INFO, "service mode: card = %s\n", number);
data[5] = eeprom_read(EEPROM_SONDER_H + sim->card);
rc = encode_ebdt(data, NULL, NULL, number, NULL, NULL);
if (rc < 0)
break;
eeprom_write(EEPROM_SONDER_H + sim->card, data[5]);
break;
case 4: /* special key */
PDEBUG(DSIM7, DEBUG_INFO, "service mode: special = %s\n", number);
data[5] = eeprom_read(EEPROM_SONDER_H + sim->card);
rc = encode_ebdt(data, NULL, NULL, NULL, number, NULL);
if (rc < 0)
break;
eeprom_write(EEPROM_SONDER_H + sim->card, data[5]);
eeprom_write(EEPROM_SONDER_L + sim->card, data[6]);
break;
case 5: /* maintenance key */
PDEBUG(DSIM7, DEBUG_INFO, "service mode: maintenance = %s\n", number);
rc = encode_ebdt(data, NULL, NULL, NULL, NULL, number);
if (rc < 0)
break;
eeprom_write(EEPROM_WARTUNG_H + sim->card, data[7]);
eeprom_write(EEPROM_WARTUNG_L + sim->card, data[8]);
break;
}
/* respond */
goto respond;
}
if (rufn >= directory_size() || rufn < 1) {
PDEBUG(DSIM7, DEBUG_NOTICE, "WT_RUFN entry #%d out of range\n", rufn);
return_error(sim);
return;
}
save_directory(data[0], data + 1);
/* respond */
respond:
data = alloc_msg(sim, 0);
tx_sdu(sim, 0, data, 0);
}
/* command: check PIN (enter PIN and unlock) */
static void chk_pin(sim_sim_t *sim, uint8_t *data, int length)
{
int rc;
PDEBUG(DSIM7, DEBUG_INFO, " CHK-PIN\n");
if (length < 4 || length > 8) {
PDEBUG(DSIM7, DEBUG_NOTICE, "SET-PIN wrong length: %d\n", length);
return_error(sim);
return;
}
/* validate PIN */
rc = validate_pin(sim, data, length);
if (rc) {
return_pin_not_ok(sim);
return;
}
/* respond */
data = alloc_msg(sim, 0);
tx_sdu(sim, 0, data, 0);
}
/* command: set PIN */
static void set_pin(sim_sim_t *sim, uint8_t *data, int length)
{
uint8_t len_old, len_new;
uint8_t *pin_old, *pin_new;
int i;
int rc;
PDEBUG(DSIM7, DEBUG_INFO, " SET-PIN\n");
if (length < 1) {
PDEBUG(DSIM7, DEBUG_NOTICE, "SET-PIN too short\n");
return_error(sim);
return;
}
len_old = data[0];
pin_old = data + 1;
len_new = length - len_old - 1;
pin_new = data + 1 + len_old;
if (len_new < 4 || len_new > 8) {
PDEBUG(DSIM7, DEBUG_NOTICE, "New PIN wrong length %d!\n", len_new);
return_error(sim);
return;
}
/* validate PIN */
rc = validate_pin(sim, pin_old, length);
if (rc) {
return_pin_not_ok(sim);
return;
}
/* write PIN */
sim->pin_len = len_new;
write_flags(sim);
for (i = 0; i < len_new; i++)
eeprom_write(EEPROM_PIN_DATA + i, pin_new[i]);
/* respond */
data = alloc_msg(sim, 0);
tx_sdu(sim, 0, data, 0);
}
/* command: increment metering counter */
static void eh_gebz(sim_sim_t *sim, uint8_t *data, int length)
{
uint32_t gebz;
PDEBUG(DSIM7, DEBUG_INFO, " EH-GEBZ\n");
if (length < 1) {
PDEBUG(DSIM7, DEBUG_NOTICE, "EH-GEBZ wrong length: %d\n", length);
return_error(sim);
return;
}
/* increment counter */
gebz = eeprom_read(EEPROM_GEBZ_H) << 16;
gebz |= eeprom_read(EEPROM_GEBZ_M) << 8;
gebz |= eeprom_read(EEPROM_GEBZ_L);
gebz += data[0];
eeprom_write(EEPROM_GEBZ_H, gebz >> 16);
eeprom_write(EEPROM_GEBZ_M, gebz >> 8);
eeprom_write(EEPROM_GEBZ_L, gebz);
/* respond */
data = alloc_msg(sim, 0);
tx_sdu(sim, 0, data, 0);
}
/* command: clear metering counter */
static void cl_gebz(sim_sim_t *sim)
{
uint8_t *data;
PDEBUG(DSIM7, DEBUG_INFO, " CL-GEBZ\n");
/* clear counter */
eeprom_write(EEPROM_GEBZ_H, 0);
eeprom_write(EEPROM_GEBZ_M, 0);
eeprom_write(EEPROM_GEBZ_L, 0);
/* respond */
data = alloc_msg(sim, 0);
tx_sdu(sim, 0, data, 0);
}
/* command: read metering counter */
static void rd_gebz(sim_sim_t *sim)
{
uint8_t *data;
PDEBUG(DSIM7, DEBUG_INFO, " RD-GEBZ\n");
/* respond */
data = alloc_msg(sim, 3);
data[0] = eeprom_read(EEPROM_GEBZ_H);
data[1] = eeprom_read(EEPROM_GEBZ_M);
data[2] = eeprom_read(EEPROM_GEBZ_L);
tx_sdu(sim, 0, data, 3);
}
/* command: lock metering counter and directory */
static void sp_gzrv(sim_sim_t *sim)
{
uint8_t *data;
PDEBUG(DSIM7, DEBUG_INFO, " SP-GZRV\n");
sim->gebz_locked = 1;
write_flags(sim);
/* respond */
data = alloc_msg(sim, 0);
tx_sdu(sim, 0, data, 0);
}
/* command: unlock metering counter and directory */
static void fr_gzrv(sim_sim_t *sim)
{
uint8_t *data;
PDEBUG(DSIM7, DEBUG_INFO, " FR-GZRV\n");
sim->gebz_locked = 0;
write_flags(sim);
/* respond */
data = alloc_msg(sim, 0);
tx_sdu(sim, 0, data, 0);
}
/* command: authenticate */
static void aut_1(sim_sim_t *sim)
{
uint8_t *data;
int i;
PDEBUG(DSIM7, DEBUG_INFO, " AUTH-1\n");
/* respond */
data = alloc_msg(sim, 1);
for (i = 0; i < 8; i++)
data[i] = eeprom_read(EEPROM_AUTH_DATA + i);
tx_sdu(sim, 0, data, 8);
}
/* command: UNKNOWN */
static void rd_f4(sim_sim_t *sim)
{
uint8_t *data;
PDEBUG(DSIM7, DEBUG_INFO, " RD-F4\n");
/* respond */
data = alloc_msg(sim, 2);
data[0] = 0x00;
data[1] = 0x13;
tx_sdu(sim, 0, data, 2);
}
/* command: UNKNOWN */
static void rd_f5(sim_sim_t *sim)
{
uint8_t *data;
PDEBUG(DSIM7, DEBUG_INFO, " RD-F5\n");
/* respond */
data = alloc_msg(sim, 0);
tx_sdu(sim, 0, data, 0);
}
/* command: UNKNOWN */
static void rd_04(sim_sim_t *sim)
{
uint8_t *data;
PDEBUG(DSIM7, DEBUG_INFO, " RD-04\n");
/* respond */
data = alloc_msg(sim, 25);
data[0] = 0x63;
memset(data + 1, 0x00, 24);
tx_sdu(sim, 0, data, 25);
}
/* parse layer 7 header */
static void rx_sdu(sim_sim_t *sim, uint8_t *data, int length)
{
uint8_t cla, ins, dlng;
if (length < 3) {
PDEBUG(DSIM7, DEBUG_NOTICE, "SDU too short\n");
return;
}
/* skip all responses, because we don't send commands */
if (*data & CCRC_IDENT) {
PDEBUG(DSIM7, DEBUG_NOTICE, "Skipping SDU with response\n");
return;
}
/* read application layer header */
cla = *data++ & 0x7f;
ins = *data++;
dlng = *data++;
length -= 3;
/* check length */
if (dlng != length) {
PDEBUG(DSIM7, DEBUG_NOTICE, "Skipping SDU with invalid length\n");
return;
}
/* select command */
switch (cla) {
case CLA_CNTR:
switch (ins) {
case SL_APPL: sl_appl(sim, data, length); return;
case CL_APPL: cl_appl(sim); return;
case SH_APPL: sh_appl(sim); return;
}
break;
case CLA_STAT:
switch (ins) {
case CHK_KON: chk_kon(sim); return;
}
break;
case CLA_WRTE:
switch (ins) {
case WT_RUFN: wt_rufn(sim, data, length); return;
}
break;
case CLA_READ:
switch (ins) {
case RD_EBDT: rd_ebdt(sim); return;
case RD_RUFN: rd_rufn(sim, data, length); return;
case RD_GEBZ: rd_gebz(sim); return;
case 0xf4: rd_f4(sim); return;
case 0xf5: rd_f5(sim); return;
case 0x04: rd_04(sim); return;
}
break;
case CLA_EXEC:
switch (ins) {
case CHK_PIN: chk_pin(sim, data, length); return;
case SET_PIN: set_pin(sim, data, length); return;
}
if (sim->app == APP_NETZ_C) switch (ins) {
case EH_GEBZ: eh_gebz(sim, data, length); return;
case CL_GEBZ: cl_gebz(sim); return;
}
if (sim->app == APP_RUFN_GEBZ) switch (ins) {
case SP_GZRV: sp_gzrv(sim); return;
case FR_GZRV: fr_gzrv(sim); return;
}
break;
case CLA_AUTO:
switch (ins) {
case AUT_1: aut_1(sim); return;
}
break;
}
/* unsupported message */
PDEBUG(DSIM7, DEBUG_NOTICE, "CLA 0x%02x INS 0x%02x uknown\n", cla, ins);
data = alloc_msg(sim, 0);
tx_sdu(sim, CCRC_ERROR, data, 0);
}
/* create layer 7 message for ICL layer */
static void tx_sdu(sim_sim_t *sim, uint8_t ccrc, uint8_t *data, int length)
{
/* header */
data = push_msg(data, length, 3);
data[0] = CCRC_IDENT | ccrc | CCRC_APRC_VALID;
data[1] = 0;
if (sim->pin_try == 0)
data[0] |= CCRC_AFBZ_NULL;
data[1] = get_aprc(sim);
data[2] = length;
length += 3;
/* forward to ICL layer */
tx_pdu(sim, data, length);
}
/* ICL layer */
/* parse ICL header */
static void rx_pdu(sim_sim_t *sim, uint8_t *data, int length)
{
uint8_t ext = 1;
if (length < 1) {
too_short:
PDEBUG(DSIMI, DEBUG_NOTICE, "PDU too short\n");
return;
}
/* read ICB1 */
sim->icl_online = (*data & ICB1_ONLINE) != 0;
sim->icl_master = (*data & ICB1_MASTER) != 0;
sim->icl_error = (*data & ICB1_ERROR) != 0;
sim->icl_chaining = (*data & ICB1_CHAINING) != 0;
/* skip all ICBx (should only one exist) */
while (ext) {
if (length < 1)
goto too_short;
ext = (*data++ & ICB_EXT) != 0;
length--;
}
rx_sdu(sim, data, length);
}
/* create ICL layer message for layer 2 */
static void tx_pdu(sim_sim_t *sim, uint8_t *data, int length)
{
/* header */
data = push_msg(data, length, 1);
data[0] = 0;
if (sim->icl_online)
data[0] |= ICB1_ONLINE;
if (!sim->icl_master)
data[0] |= ICB1_MASTER;
if (sim->icl_error)
data[0] |= ICB1_ERROR | ICB1_CONFIRM;
if (sim->icl_chaining)
data[0] |= ICB1_CHAINING | ICB1_CONFIRM;
length++;
tx_block(sim, L2_I, data, length);
}
/* Layer 2 */
/* process received L2 message */
static void rx_block(sim_sim_t *sim)
{
uint8_t ns, nr;
uint8_t *data;
/* NOTE: This procedure is simplified, it does not comply with the specs. */
PDEBUG(DSIM2, DEBUG_INFO, "RX message\n");
sim->addr_src = sim->block_address >> 4;
sim->addr_dst = sim->block_address & 0xf;
if (sim->block_checksum != 0) {
PDEBUG(DSIM2, DEBUG_NOTICE, "Checksum error!\n");
goto reject;
}
if ((sim->block_control & 0x11) == 0x00) {
ns = (sim->block_control >> 1) & 7;
nr = sim->block_control >> 5;
PDEBUG(DSIM2, DEBUG_INFO, " control I: N(S)=%d N(R)=%d\n", ns, nr);
if (ns == sim->vr && nr == sim->vs) {
/* receive data */
sim->vr = (sim->vr + 1) & 0x7;
rx_pdu(sim, sim->block_rx_data, sim->block_rx_length);
return;
} else {
PDEBUG(DSIM2, DEBUG_NOTICE, "Seqeuence error!\n");
reject:
/* reject (or send resync after 3 times) */
data = alloc_msg(sim, 0);
if (1) { // if (sim->reject_count < 3) {
tx_block(sim, L2_REJ, data, 0);
sim->reject_count++;
} else {
tx_block(sim, L2_RES, data, 0);
}
return;
}
return;
}
if ((sim->block_control & 0x1f) == 0x09) {
nr = sim->block_control >> 5;
PDEBUG(DSIM2, DEBUG_INFO, " control REJ: N(R)=%d\n", nr);
/* repeat last message */
if (sim->block_tx_length) {
tx_block(sim, L2_I, sim->block_tx_data, sim->block_tx_length);
return;
}
/* no block sent yet, sending resync */
data = alloc_msg(sim, 0);
tx_block(sim, L2_RES, data, 0);
return;
}
if (sim->block_control == 0xef) {
PDEBUG(DSIM2, DEBUG_INFO, " control RES\n");
sim->vr = sim->vs = 0;
sim->reject_count = 0;
if (sim->resync_sent == 0) {
/* resync */
data = alloc_msg(sim, 0);
tx_block(sim, L2_RES, data, 0);
return;
}
return;
}
}
/* receive data from layer 1 and create layer 2 message */
static int rx_char(sim_sim_t *sim, uint8_t c)
{
sim->block_checksum ^= c;
switch (sim->block_state) {
case BLOCK_STATE_ADDRESS:
sim->block_address = c;
sim->block_state = BLOCK_STATE_CONTROL;
sim->block_checksum = c;
return 0;
case BLOCK_STATE_CONTROL:
sim->block_control = c;
sim->block_state = BLOCK_STATE_LENGTH;
return 0;
case BLOCK_STATE_LENGTH:
if (c > sizeof(sim->block_rx_data)) {
c = sizeof(sim->block_rx_data);
PDEBUG(DSIM1, DEBUG_NOTICE, "RX buffer overflow: length=%d > buffer size (%d)\n", c, (int)sizeof(sim->block_rx_data));
}
sim->block_rx_length = c;
sim->block_count = 0;
sim->block_state = BLOCK_STATE_DATA;
return 0;
case BLOCK_STATE_DATA:
if (sim->block_count < sim->block_rx_length) {
sim->block_rx_data[sim->block_count++] = c;
return 0;
}
sim->l1_state = L1_STATE_IDLE;
rx_block(sim);
}
return -1;
}
/* create layer 2 message for layer 1 */
static void tx_block(sim_sim_t *sim, enum l2_cmd cmd, uint8_t __attribute__((unused)) *data, int length)
{
PDEBUG(DSIM2, DEBUG_INFO, "TX resonse\n");
/* header */
sim->block_address = (sim->addr_dst << 4) | sim->addr_src;
switch (cmd) {
case L2_I:
PDEBUG(DSIM2, DEBUG_INFO, " control I: N(S)=%d N(R)=%d\n", sim->vs, sim->vr);
sim->block_control = (sim->vr << 5) | (sim->vs << 1);
sim->vs = (sim->vs + 1) & 0x7;
sim->resync_sent = 0;
break;
case L2_REJ:
PDEBUG(DSIM2, DEBUG_INFO, " control REJ: N(R)=%d\n", sim->vr);
sim->block_control = (sim->vr << 5) | 0x09;
sim->resync_sent = 0;
break;
case L2_RES:
PDEBUG(DSIM2, DEBUG_INFO, " control RES\n");
sim->block_control = 0xef;
sim->resync_sent = 1;
break;
}
sim->block_tx_length = length;
sim->l1_state = L1_STATE_SEND;
sim->block_state = BLOCK_STATE_ADDRESS;
}
/* transmit character of current message to layer 1 */
static uint8_t tx_char(sim_sim_t *sim)
{
uint8_t c = -1;
switch (sim->block_state) {
case BLOCK_STATE_ADDRESS:
c = sim->block_address;
sim->block_state = BLOCK_STATE_CONTROL;
sim->block_checksum = 0;
break;
case BLOCK_STATE_CONTROL:
c = sim->block_control;
sim->block_state = BLOCK_STATE_LENGTH;
break;
case BLOCK_STATE_LENGTH:
c = sim->block_tx_length;
sim->block_count = 0;
sim->block_state = BLOCK_STATE_DATA;
break;
case BLOCK_STATE_DATA:
if (sim->block_count < sim->block_tx_length) {
c = sim->block_tx_data[sim->block_count++];
break;
}
c = sim->block_checksum;
sim->l1_state = L1_STATE_IDLE;
break;
}
sim->block_checksum ^= c;
return c;
}
/* ATR */
static uint8_t atr[] = {
0x3b, 0x88, /* TS, T0 */
0x8e,
0xfe,
0x53, 0x2a, 0x03, 0x1e,
0x04,
0x92, 0x80, 0x00, 0x41, 0x32, 0x36, 0x01, 0x11,
0xe4, /* TCK */
};
static uint8_t tx_atr(sim_sim_t *sim)
{
uint8_t c;
c = atr[sim->atr_count++];
if (sim->atr_count == sizeof(atr))
sim->l1_state = L1_STATE_IDLE;
return c;
}
/* Layer 1 */
int sim_init_eeprom(void)
{
uint8_t ebdt_data[9];
int i, rc;
/* init EEPROM with all bits '1' */
for (i = 0; i < (int)eeprom_length(); i++)
eeprom_write(i, 0xff);
/* set default values in eeprom */
rc = encode_ebdt(ebdt_data, FUTLN_DEFAULT, SICHERUNG_DEFAULT, KARTEN_DEFAULT, SONDER_DEFAULT, WARTUNG_DEFAULT);
if (rc < 0)
return rc;
for (i = 0; i < MAX_CARDS; i++) {
eeprom_write(EEPROM_FUTLN_H + i, ebdt_data[0]);
eeprom_write(EEPROM_FUTLN_M + i, ebdt_data[1]);
eeprom_write(EEPROM_FUTLN_L + i, ebdt_data[2] + i);
eeprom_write(EEPROM_SICH_H + i, ebdt_data[3]);
eeprom_write(EEPROM_SICH_L + i, ebdt_data[4]);
eeprom_write(EEPROM_SONDER_H + i, ebdt_data[5]);
eeprom_write(EEPROM_SONDER_L + i, ebdt_data[6]);
eeprom_write(EEPROM_WARTUNG_H + i, ebdt_data[7]);
eeprom_write(EEPROM_WARTUNG_L + i, ebdt_data[8]);
}
eeprom_write(EEPROM_GEBZ_H, 0);
eeprom_write(EEPROM_GEBZ_M, 0);
eeprom_write(EEPROM_GEBZ_L, 0);
eeprom_write(EEPROM_FLAGS, (strlen(PIN_DEFAULT) << EEPROM_FLAG_PIN_LEN) | (MAX_PIN_TRY << EEPROM_FLAG_PIN_TRY));
for (i = 0; i < (int)strlen(PIN_DEFAULT); i++)
eeprom_write(EEPROM_PIN_DATA + i, PIN_DEFAULT[i]);
for (i = 0; i < 8; i++)
eeprom_write(EEPROM_AUTH_DATA + i, AUTH_DEFAULT >> ((7 - i) * 8));
/* now write magic characters to identify virgin or initialized EEPROM */
eeprom_write(EEPROM_MAGIC + 0, 'C');
eeprom_write(EEPROM_MAGIC + 1, '0' + EEPROM_VERSION);
return 0;
}
void sim_reset(sim_sim_t *sim, int reset)
{
int i;
char pin[8];
PDEBUG(DSIM1, DEBUG_INFO, "Reset singnal %s\n", (reset) ? "on (low)" : "off (high)");
memset(sim, 0, sizeof(*sim));
if (reset)
return;
/* read flags from EEPROM data */
read_flags(sim);
/* check PIN and set flags */
for (i = 0; i < sim->pin_len; i++)
pin[i] = eeprom_read(EEPROM_PIN_DATA + i);
sim->pin_required = 1;
/* 'system' PIN = 0000, 0001, 0002, ... */
if (sim->pin_len == 4 && pin[0] == '0' && pin[1] == '0' && pin[2] == '0' && pin[3] >= '0' && pin[3] <= '0' + MAX_CARDS) {
sim->pin_required = 0;
if (pin[3] > '0')
sim->card = pin[3] - '1';
PDEBUG(DSIM1, DEBUG_INFO, "Card has disabled PIN (system PIN '000%c') Selecting card #%d.\n", pin[3], sim->card + 1);
}
PDEBUG(DSIM1, DEBUG_INFO, "Sending ATR\n");
sim->l1_state = L1_STATE_ATR;
}
int sim_rx(sim_sim_t *sim, uint8_t c)
{
int rc = -1;
PDEBUG(DSIM1, DEBUG_DEBUG, "Serial RX '0x%02x'\n", c);
switch (sim->l1_state) {
case L1_STATE_IDLE:
sim->l1_state = L1_STATE_RECEIVE;
sim->block_state = BLOCK_STATE_ADDRESS;
/* fall through */
case L1_STATE_RECEIVE:
rc = rx_char(sim, c);
break;
default:
break;
}
return rc;
}
int sim_tx(sim_sim_t *sim)
{
int c = -1;
switch (sim->l1_state) {
case L1_STATE_ATR:
c = tx_atr(sim);
break;
case L1_STATE_SEND:
c = tx_char(sim);
break;
default:
break;
}
if (c >= 0)
PDEBUG(DSIM1, DEBUG_DEBUG, "Serial TX '0x%02x'\n", c);
return c;
}
void sim_timeout(sim_sim_t *sim)
{
switch (sim->l1_state) {
case L1_STATE_ATR:
PDEBUG(DSIM1, DEBUG_NOTICE, "Timeout while transmitting ATR!\n");
sim->l1_state = L1_STATE_RESET;
break;
case L1_STATE_RECEIVE:
PDEBUG(DSIM1, DEBUG_NOTICE, "Timeout while receiving message!\n");
sim->block_state = BLOCK_STATE_ADDRESS;
break;
case L1_STATE_SEND:
PDEBUG(DSIM1, DEBUG_NOTICE, "Timeout while sending message!\n");
sim->l1_state = L1_STATE_IDLE;
break;
default:
break;
}
}