diff options
Diffstat (limited to 'src/sim/sim.c')
-rw-r--r-- | src/sim/sim.c | 1438 |
1 files changed, 1438 insertions, 0 deletions
diff --git a/src/sim/sim.c b/src/sim/sim.c new file mode 100644 index 0000000..fba9417 --- /dev/null +++ b/src/sim/sim.c @@ -0,0 +1,1438 @@ +/* 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, (data[1] << 8) | data[2], 5); + } + + if (sicherung) + my_ultostr(sicherung, (data[3] << 8) | data[4], 1); + + if (karten) + my_ultostr(karten, data[5] >> 5, 1); + + if (sonder) + my_ultostr(sonder, ((data[5] & 0x1f) << 8) | data[6], 1); + + if (wartung) + my_ultostr(wartung, (data[7] << 8) | 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); + + /* if PIN is required */ + if (sim->pin_required) { + return_pin_not_ok(sim); + return; + } + + /* check 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; + } + + /* respond */ + sim->app = app; + 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, " RD-EBDT\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]); + + 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; + } +} |