aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAndreas Eversberg <jolly@eversberg.eu>2021-08-28 20:59:20 +0200
committerAndreas Eversberg <jolly@eversberg.eu>2021-11-07 20:00:41 +0100
commit465445aac5121949d1a59e02500ce2e874812379 (patch)
treea55bf60a9ec3f96e036ee1017649dd92708b2018 /src
parent922b4af3621f333b9d69c37e1bbcc0b16089e58b (diff)
Add Magnetic card emulation for C-Netz
Emulation can be done with a coil connected to sound card. Alternatively an Attiny85 can be used to control a coil.
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am1
-rw-r--r--src/magnetic/Makefile.am28
-rw-r--r--src/magnetic/image.c32
-rw-r--r--src/magnetic/iso7811.c162
-rw-r--r--src/magnetic/iso7811.h5
-rwxr-xr-xsrc/magnetic/magnetic.ino486
-rw-r--r--src/magnetic/main.c368
7 files changed, 1082 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index e983c4e..57fb1fb 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -59,6 +59,7 @@ SUBDIRS += \
radio \
zeitansage \
sim \
+ magnetic \
fuvst
if HAVE_ALSA
diff --git a/src/magnetic/Makefile.am b/src/magnetic/Makefile.am
new file mode 100644
index 0000000..f5e9f83
--- /dev/null
+++ b/src/magnetic/Makefile.am
@@ -0,0 +1,28 @@
+AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
+
+bin_PROGRAMS = \
+ cnetz_magnetic
+
+cnetz_magnetic_SOURCES = \
+ iso7811.c \
+ image.c \
+ main.c
+
+cnetz_magnetic_LDADD = \
+ $(COMMON_LA) \
+ $(top_builddir)/src/libdebug/libdebug.a \
+ $(top_builddir)/src/liboptions/liboptions.a \
+ $(top_builddir)/src/libwave/libwave.a \
+ $(top_builddir)/src/libaaimage/libaaimage.a \
+ -lm
+
+if HAVE_ALSA
+cnetz_magnetic_LDADD += \
+ $(top_builddir)/src/libsound/libsound.a \
+ $(ALSA_LIBS)
+endif
+
+if HAVE_ALSA
+AM_CPPFLAGS += -DHAVE_ALSA
+endif
+
diff --git a/src/magnetic/image.c b/src/magnetic/image.c
new file mode 100644
index 0000000..89212e6
--- /dev/null
+++ b/src/magnetic/image.c
@@ -0,0 +1,32 @@
+#ifndef ARDUINO
+
+#include <stdio.h>
+
+const char *aaimage[] = {
+ "@w",
+ " @r___@g___",
+ " @WC-Netz @r/ _@g_ \\",
+ " @WMagnetstreifen- @r/ / @g \\ \\",
+ " @WEmulator @r/ / @g \\ \\",
+ " @r\\ \\ @g / /",
+ " @B ________________________@r\\ \\@B__@g/ /@B____________",
+ " @B|@y_________________________@r\\ \\@g/ /@y_____________@B|",
+ " @B|@y / / / / / / / / / / / / /@r\\_|@g|_/@y / / / / / / /@B|",
+ " @B|@y / / / / / / / / / / / / / / / / / / / / / / /@B|",
+ " @B|@y______________________________________________@B|",
+ " @B| |",
+ " @B| @w ____ ____ @B|",
+ " @B| @w/ \\ / \\ @B|",
+ " @B| @w\\____/ \\____/ @B|",
+ " @B| @w|----^ |----^ Die Servicenummer @B|",
+ " @B| @w . / \\ @B|",
+ " @B| @w|---\\| \\_/\\_/ rund um die Uhr -----\\ @B|",
+ " @B| @w ' / \\ @B/ \\ @w\\ @B|",
+ " @B| @w|----^ \\____/ @B\\ / @w/ @B|",
+ " @B| @w -----/ @B|",
+ " @B|______________________________________________|",
+ "",
+ NULL
+};
+
+#endif /* ARDUINO */
diff --git a/src/magnetic/iso7811.c b/src/magnetic/iso7811.c
new file mode 100644
index 0000000..214251e
--- /dev/null
+++ b/src/magnetic/iso7811.c
@@ -0,0 +1,162 @@
+/* ISO 7811 encoder/decoder
+ *
+ * (C) 2021 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 <stdint.h>
+#include <string.h>
+#include "iso7811.h"
+
+/* Given is a string with or without start and end sentinel. Returned are
+ * bytes containing 5 bits each. These bits shall be sent LSB first.
+ * A lead-in and a start sentinel is added prior encoded string data.
+ * An end sentinel, a LRC and a lead-out is added after string data.
+ */
+int encode_track(uint8_t *data, const char *string, int lead_in, int lead_out)
+{
+ int i;
+ uint8_t bits, lrc;
+
+ i = 0;
+ lrc = 0;
+
+ /* lead-in */
+ for (lead_in += i; i < lead_in; i++)
+ data[i] = 0;
+
+ /* start sentinel */
+ if (*string == ';')
+ string++;
+ bits = 0x0b;
+ data[i++] = bits;
+ lrc ^= bits;
+
+ /* string */
+ while (*string && *string != '?') {
+ if (*string >= 0x30 && *string < 0x40)
+ bits = *string - 0x30;
+ else
+ bits = 0;
+ data[i] = bits & 0x0f;
+ lrc ^= bits;
+ bits ^= bits >> 2;
+ bits ^= bits >> 1;
+ bits &= 1;
+ data[i] |= (bits ^ 1) << 4;
+ string++;
+ i++;
+ }
+
+ /* end sentinel */
+ bits = 0x1f;
+ data[i++] = bits;
+ lrc ^= bits;
+
+ /* LRC */
+ data[i] = lrc & 0x0f;
+ lrc ^= lrc >> 2;
+ lrc ^= lrc >> 1;
+ lrc &= 1;
+ data[i] |= (lrc ^ 1) << 4;
+ i++;
+
+ /* lead-out */
+ for (lead_out += i; i < lead_out; i++)
+ data[i] = 0;
+
+ return i;
+}
+
+/* n0nnnnnn=sssss0000 (in case of 7 digits) */
+int cnetz_card(char *string, const char *number, const char *sicherung)
+{
+ int len;
+
+ /* number */
+ len = strlen(number);
+ *string++ = *number++;
+ if (len == 7)
+ *string++ = '0';
+ else if (len == 8)
+ *string++ = *number++;
+ else
+ return 0;
+ *string++ = *number++;
+ *string++ = *number++;
+ *string++ = *number++;
+ *string++ = *number++;
+ *string++ = *number++;
+ *string++ = *number++;
+
+ /* field seperator */
+ *string++ = '=';
+
+ /* security code */
+ len = strlen(sicherung);
+ if (len < 5)
+ *string++ = '0';
+ else
+ *string++ = *sicherung++;
+ if (len < 4)
+ *string++ = '0';
+ else
+ *string++ = *sicherung++;
+ if (len < 3)
+ *string++ = '0';
+ else
+ *string++ = *sicherung++;
+ if (len < 2)
+ *string++ = '0';
+ else
+ *string++ = *sicherung++;
+ *string++ = *sicherung++;
+ *string++ = '0';
+ *string++ = '0';
+ *string++ = '0';
+ *string++ = '0';
+
+ *string++ = '\0';
+
+ return 18;
+}
+
+/* 0:500000=000000000 */
+int bsa44_service(char *string)
+{
+ *string++ = '0';
+ *string++ = ':';
+ *string++ = '5';
+ *string++ = '0';
+ *string++ = '0';
+ *string++ = '0';
+ *string++ = '0';
+ *string++ = '0';
+ *string++ = '=';
+ *string++ = '0';
+ *string++ = '0';
+ *string++ = '0';
+ *string++ = '0';
+ *string++ = '0';
+ *string++ = '0';
+ *string++ = '0';
+ *string++ = '0';
+ *string++ = '0';
+ *string++ = '\0';
+
+ return 18;
+}
+
diff --git a/src/magnetic/iso7811.h b/src/magnetic/iso7811.h
new file mode 100644
index 0000000..6ffe6ae
--- /dev/null
+++ b/src/magnetic/iso7811.h
@@ -0,0 +1,5 @@
+
+int encode_track(uint8_t *data, const char *string, int lead_in, int lead_out);
+int cnetz_card(char *string, const char *number, const char *sicherungscode);
+int bsa44_service(char *string);
+
diff --git a/src/magnetic/magnetic.ino b/src/magnetic/magnetic.ino
new file mode 100755
index 0000000..3d905ad
--- /dev/null
+++ b/src/magnetic/magnetic.ino
@@ -0,0 +1,486 @@
+/* Magnetic card emulator for ATMEL
+ *
+ * This sould work with the original 'MagSpoof' out of the box!
+ * In this case you should add a second switch, to allow test card and progrmming mode.
+ *
+ * (C) 2021 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/>.
+ */
+
+/* to set fused for ATTINY85: (This is default when shipped!)
+ * avrdude -c usbasp-clone -p t85 -U lfuse:w:0xc0:m -U hfuse:w:0xdf:m -U efuse:w:0xff:m
+ */
+
+/* Use a clock speed of 8 MHz. If you change it, also change the fuses!!!!
+ * The CLKPS bits are set to 0 by software, so that 8 MHz clock is not divided.
+ *
+ * Press switch 1 to emulate card, press switch 2 to emulate BSA44 service card.
+ * The LED will do short flashs, to indicate that power is on.
+ *
+ * Hold a switch while powering on, to enter test mode. The LED will light up.
+ * Press switch 1 to send continuous 0-bits.
+ * Press switch 2 to send continuous 1-bits.
+ * Press switch 1 and 2 to send continuos alternating 0- and 1-bits.
+ * WARNING: In test mode, H-bridge IC becomes quickly very hot. Don't fry it!
+ *
+ * To enter programming mode, press both buttons simultaniously.
+ * The LED will continously blink, to indicate programming mode.
+ * Press switch 1 to select subscriber number or switch 1 to select security code.
+ * The LED will then show the digit values by blinking. It can be aborted with any switch.
+ * After short blinking, a long blink shows that the first digit can be entered.
+ * Press switch 1 1-10 times to enter the digit. When done, press switch 2 and continue
+ * with the next digit. When all digits are entered, press switch 2 again.
+ * A soft flash of the LED (fading in and out) will indicate that the new digits are stored.
+ * A false input will abort the programming procedure and restart with continuous blinking.
+ * To abort programming mode, press both buttuns simultaniously.
+ */
+
+#define F_CPU 8000000 // Oscillator frequency
+
+extern "C"
+{
+ #include "iso7811.h"
+}
+#include <avr/eeprom.h>
+
+#define PORT_ENABLE 3 // PIN 2 -> connect to 1-2EN of L293D and to LED with 1K resistor to ground
+#define PORT_COIL1 0 // PIN 5 -> connect to 1A of L293D
+#define PORT_COIL2 1 // PIN 6 -> connect to 2A of L293D
+#define PORT_SWITCH1 2 // PIN 7 -> connect via switch 1 to ground
+#define PORT_SWITCH2 4 // PIN 3 -> connect via switch 2 to ground (optional, leave open when unused)
+#define PORT_RESET 5 // PIN 1 -> unused, leave open (Don't disable reset when programming fuses!!!)
+
+/* see main.c for more info */
+#define CNETZ_LEAD_IN 12
+#define CNETZ_LEAD_OUT 150
+
+#define CLOCK_US 200 // Time to wait for half a bit
+
+#define EEPROM_MAGIC 'c' // not equal to sim emulator, this has a capital 'C'
+#define EEPROM_VERSION '0'
+
+#define SWITCH1 (digitalRead(PORT_SWITCH1) == LOW)
+#define SWITCH2 (digitalRead(PORT_SWITCH2) == LOW)
+
+static char number[9] = "1234567\0";
+static char sicherung[6] = "12345";
+static char string[19];
+static uint8_t flux = 0;
+
+/* enable H-bridge, but set it to neutral */
+void enable_h_bridge(uint8_t enable)
+{
+ /* enable H-bridge and LED */
+ digitalWrite(PORT_ENABLE, enable);
+
+ /* set bridge to neutral */
+ digitalWrite(PORT_COIL1, LOW);
+ digitalWrite(PORT_COIL2, LOW);
+}
+
+/* send single bit with clock */
+void send_bit(uint8_t bit)
+{
+ digitalWrite(PORT_COIL1, flux);
+ flux ^= 1;
+ digitalWrite(PORT_COIL2, flux);
+ delayMicroseconds(CLOCK_US);
+
+ if (bit & 1) {
+ digitalWrite(PORT_COIL1, flux);
+ flux ^= 1;
+ digitalWrite(PORT_COIL2, flux);
+ }
+ delayMicroseconds(CLOCK_US);
+}
+
+/* blink exactly one second to confirm correct clock speed */
+void blink_led()
+{
+ int i;
+
+ for (i = 0; i < 3; i++) {
+ delay(200);
+ if (SWITCH1 || SWITCH2)
+ break;
+ enable_h_bridge(1);
+ delay(200);
+ if (SWITCH1 || SWITCH2)
+ break;
+ enable_h_bridge(0);
+ }
+}
+
+/* wait until release of single key, but abort when pressing both keys.
+ * if 2 is given, wait for release of all keys, don't abort when pressing both keys.
+ * compensate contact shattering (German: Tastenprellen)
+ */
+void wait_release(int keys)
+{
+ int i = 0;
+ while (i < 50) {
+ delay(1);
+ if (keys < 2) {
+ if (SWITCH1 && SWITCH2)
+ break;
+ }
+ if (SWITCH1 || SWITCH2)
+ i = 0;
+ else
+ i++;
+ }
+}
+
+/* test mode */
+void test_pattern(void)
+{
+ /* test pattern */
+ while (42) {
+ if (!SWITCH1 && !SWITCH2)
+ enable_h_bridge(1);
+ if (SWITCH1)
+ send_bit(0);
+ if (SWITCH2)
+ send_bit(1);
+ }
+}
+
+/* read eeprom, if version is correct */
+void read_eeprom(void)
+{
+ int i;
+
+ if (eeprom_read_byte(0) == EEPROM_MAGIC
+ && eeprom_read_byte(1) == EEPROM_VERSION) {
+ for (i = 0; i < 8; i++)
+ number[i] = eeprom_read_byte(i + 2);
+ number[i] = '\0';
+ for (i = 0; i < 5; i++)
+ sicherung[i] = eeprom_read_byte(i + 10);
+ sicherung[i] = '\0';
+ }
+}
+
+/* write eeprom, */
+void write_eeprom(void)
+{
+ int i;
+
+ eeprom_write_byte(0, EEPROM_MAGIC);
+ eeprom_write_byte(1, EEPROM_VERSION);
+ for (i = 0; i < 8; i++)
+ eeprom_write_byte(i + 2, number[i]);
+ for (i = 0; i < 5; i++)
+ eeprom_write_byte(i + 10, sicherung[i]);
+
+ /* show soft flash */
+ for (i = 0; i < 55; i++) {
+ enable_h_bridge(1);
+ delay(i/5);
+ enable_h_bridge(0);
+ delay(10 - i/5);
+ }
+ for (i = 54; i >= 0; i--) {
+ enable_h_bridge(1);
+ delay(i/5);
+ enable_h_bridge(0);
+ delay(10 - i/5);
+ }
+}
+
+void setup() {
+ /* setup clock speed to 8 MHz */
+ CLKPR = _BV(CLKPCE);
+ CLKPR = 0;
+
+ /* setup ports */
+ pinMode(PORT_ENABLE, OUTPUT);
+ digitalWrite(PORT_ENABLE, LOW);
+ pinMode(PORT_COIL1, OUTPUT);
+ digitalWrite(PORT_COIL1, LOW);
+ pinMode(PORT_COIL2, OUTPUT);
+ digitalWrite(PORT_COIL2, LOW);
+ pinMode(PORT_SWITCH1, INPUT_PULLUP);
+ pinMode(PORT_SWITCH2, INPUT_PULLUP);
+
+ /* blink with LED */
+ blink_led();
+
+ /* transmit test pattern */
+ if (SWITCH1 || SWITCH2) {
+ wait_release(1);
+ test_pattern();
+ }
+
+ /* read subscriber data from eeprom */
+ read_eeprom();
+}
+
+/* send card data */
+void send_string(const char *string)
+{
+ uint8_t data[CNETZ_LEAD_IN + 21 + CNETZ_LEAD_OUT];
+ int length, i;
+ uint8_t digit;
+
+ length = encode_track(data, string, CNETZ_LEAD_IN, CNETZ_LEAD_OUT);
+
+ /* enable H-bridge and LED */
+ enable_h_bridge(1);
+
+ /* send bits */
+ for (i = 0; i < length; i++) {
+ digit = data[i];
+ send_bit((digit >> 0) & 1);
+ send_bit((digit >> 1) & 1);
+ send_bit((digit >> 2) & 1);
+ send_bit((digit >> 3) & 1);
+ send_bit((digit >> 4) & 1);
+ /* abort when pressing both switches */
+ if (SWITCH1 && SWITCH2)
+ break;
+ }
+
+ /* disable H-bridge */
+ enable_h_bridge(0);
+}
+
+/* send zeros or ones depending on the key pressed, stop by pressing both keys */
+void program_mode(void)
+{
+ uint8_t blink;
+ uint8_t edit;
+ char io[9];
+ int i, b, d;
+
+error:
+ blink = 0;
+ edit = 0;
+ /* flash LED, wait for key press */
+ while (!edit) {
+ blink ^= 1;
+ enable_h_bridge(blink);
+ for (d = 0; d < 50; d++) {
+ delay(1);
+ if (SWITCH1) {
+ edit = 1;
+ break;
+ }
+ if (SWITCH2) {
+ edit = 2;
+ break;
+ }
+ }
+ }
+ enable_h_bridge(0);
+ wait_release(1);
+ if (SWITCH1 && SWITCH2)
+ goto done;
+
+ /* copy subscriber data to io-buffer */
+ switch (edit) {
+ case 1:
+ for (i = 0; i < 8; i++)
+ io[i] = number[i];
+ io[i] = '\0';
+ break;
+ case 2:
+ for (i = 0; i < 5; i++)
+ io[i] = sicherung[i];
+ io[i] = '\0';
+ break;
+ }
+
+ /* blink the io-buffer data */
+ for (i = 0; io[i]; i++) {
+ for (d = 0; d < 1000; d++) {
+ delay(1);
+ if (SWITCH1 || SWITCH2)
+ goto stop_blink;
+ }
+ if (io[i] > '0')
+ blink = io[i] - '0';
+ else
+ blink = 10;
+ for (b = 0; b < blink; b++) {
+ enable_h_bridge(1);
+ for (d = 0; d < 100; d++) {
+ delay(1);
+ if (SWITCH1 || SWITCH2) {
+ enable_h_bridge(0);
+ goto stop_blink;
+ }
+ }
+ enable_h_bridge(0);
+ for (d = 0; d < 400; d++) {
+ delay(1);
+ if (SWITCH1 || SWITCH2)
+ goto stop_blink;
+ }
+ }
+ }
+ for (d = 0; d < 1000; d++) {
+ delay(1);
+ if (SWITCH1 && SWITCH2)
+ goto stop_blink;
+ }
+ stop_blink:
+ wait_release(1);
+ if (SWITCH1 && SWITCH2)
+ goto done;
+ enable_h_bridge(1);
+ for (d = 0; d < 500; d++) {
+ delay(1);
+ if (SWITCH1 && SWITCH2)
+ goto done;
+ }
+ enable_h_bridge(0);
+
+ /* key in the data to io-buffer */
+ i = 0;
+ b = 0;
+ while (42) {
+ if (SWITCH1) {
+ enable_h_bridge(1);
+ for (d = 0; d < 100; d++) {
+ delay(1);
+ if (SWITCH1 && SWITCH2)
+ goto done;
+ }
+ enable_h_bridge(0);
+ wait_release(1);
+ if (SWITCH1 && SWITCH2)
+ goto done;
+ b++;
+ if (b > 10)
+ goto error;
+ }
+ if (SWITCH2) {
+ wait_release(1);
+ if (SWITCH1 && SWITCH2)
+ goto done;
+ if (b == 0) {
+ while (i < 9)
+ io[i++] = '\0';
+ break;
+ }
+ if (b == 10)
+ b = 0;
+ io[i++] = '0' + b;
+ b = 0;
+ if (i > 8)
+ goto error;
+ enable_h_bridge(1);
+ for (d = 0; d < 500; d++) {
+ delay(1);
+ if (SWITCH1 && SWITCH2)
+ goto done;
+ }
+ enable_h_bridge(0);
+ }
+ }
+
+ /* verify input */
+ switch (edit) {
+ case 1:
+ if (strlen(io) < 7)
+ goto error;
+ if (io[0] > '7')
+ goto error;
+ if (strlen(io) == 8) {
+ if ((io[1] - '0') * 10 + io[2] - '0' > 31)
+ goto error;
+ if (atoi(io + 3) > 65535)
+ goto error;
+ } else {
+ if (atoi(io + 2) > 65535)
+ goto error;
+ }
+ break;
+ case 2:
+ if (strlen(io) > 5)
+ goto error;
+ if (!io[0])
+ goto error;
+ if (io[0] == '0' && io[1] != '\0')
+ goto error;
+ if (atoi(io) > 65535)
+ goto error;
+ break;
+ }
+
+ /* copy io-buffer data to subscriber data */
+ switch (edit) {
+ case 1:
+ for (i = 0; i < 8; i++)
+ number[i] = io[i];
+ number[i] = '\0';
+ break;
+ case 2:
+ for (i = 0; i < 5; i++)
+ sicherung[i] = io[i];
+ sicherung[i] = '\0';
+ break;
+ }
+
+ /* write subscriber data to eeprom */
+ write_eeprom();
+
+done:
+ enable_h_bridge(0);
+ return;
+}
+
+static uint16_t flash = 0;
+
+void loop() {
+ /* go programming */
+ if (SWITCH1 && SWITCH2) {
+ flash = 0;
+ wait_release(2);
+ program_mode();
+ wait_release(2);
+ return;
+ }
+
+ /* send card */
+ if (SWITCH1) {
+ flash = 0;
+ cnetz_card(string, number, sicherung);
+ send_string(string);
+ wait_release(1);
+ return;
+ }
+
+ /* send service card */
+ if (SWITCH2) {
+ flash = 0;
+ bsa44_service(string);
+ send_string(string);
+ wait_release(1);
+ return;
+ }
+
+ /* slow blink to show that the device is powered on */
+ delay(1);
+ flash++;
+ if (flash == 1980)
+ enable_h_bridge(1);
+ if (flash == 2000) {
+ enable_h_bridge(0);
+ flash = 0;
+ }
+}
diff --git a/src/magnetic/main.c b/src/magnetic/main.c
new file mode 100644
index 0000000..2d32231
--- /dev/null
+++ b/src/magnetic/main.c
@@ -0,0 +1,368 @@
+/* main function
+ *
+ * (C) 2021 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/>.
+ */
+
+#ifndef ARDUINO
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include <errno.h>
+#include "../libsample/sample.h"
+#include "../libsound/sound.h"
+#include "../libwave/wave.h"
+#include "../libdebug/debug.h"
+#include "../liboptions/options.h"
+#include "../libaaimage/aaimage.h"
+#include "iso7811.h"
+
+int num_kanal = 1;
+static int quit = 0;
+#ifdef HAVE_ALSA
+static void *sound = NULL;
+static int dsp_buffer = 50;
+#endif
+static int dsp_samplerate = 48000;
+static const char *dsp_audiodev = "hw:0,0";
+static const char *wave_file = NULL;
+static int baudrate = 2666;
+static const char *sicherung = "12345";
+
+/* Measurements done in summer 2021 with an original card, applied with iron oxyde. */
+/* Conforms to Track 3 (210 bpi) with 60 bits lead-in, 20 digits data, about 550 bits lead out */
+/* Note that LEAD_OUT here is longer, because the switch must be manually pressed during lead-out. */
+#define CNETZ_LEAD_IN 12 /* number of zero-digits before start sentinel (60 bits) */
+#define CNETZ_LEAD_OUT 150 /* number of zero-digits after LRC sentinel */
+#define CNETZ_SWITCH_ON 27 /* switch closing during lead-out, in digit-duration */
+#define CNETZ_SWITCH_OFF 42 /* switch opening during lead-out, in digit-duration */
+
+void print_help(const char *arg0)
+{
+ printf("Usage: %s [options] -a hw:0,0 <number> | service\n", arg0);
+ /* - - */
+ printf("General options:\n");
+ printf(" -h --help\n");
+ printf(" This help\n");
+ printf(" --config [~/]<path to config file>\n");
+ printf(" Give a config file to use. If it starts with '~/', path is at home dir.\n");
+ printf(" Each line in config file is one option, '-' or '--' must not be given!\n");
+ debug_print_help();
+ printf(" -a --audio-device hw:<card>,<device>\n");
+ printf(" Input audio from sound card's device number\n");
+ printf(" -s --samplerate <sample rate>\n");
+ printf(" Give audio device sample rate in Hz. (default = %d)\n", dsp_samplerate);
+ printf(" -w --write-wave <filename>\n");
+ printf(" Output sound as wave file\n");
+ printf("\nMagnetic card simulator options:\n");
+ printf(" -B --baud-rate <baud>\n");
+ printf(" Playback baud rate (default = %d)\n", baudrate);
+ printf(" -S --sicherung <security code>\n");
+ printf(" Card's security code for simple authentication (default = '%s')\n", sicherung);
+ printf("\n<number>: Give any valid 7 digit (optionally 8 digit) subscriber number. May\n");
+ printf(" be prefixed with 0160.\n");
+ printf("\n'service': BSA44 service card (to unlock phone after battery replacement)\n");
+}
+
+void add_options(void)
+{
+ option_add('h', "help", 0);
+ option_add('v', "debug", 1);
+ option_add('a', "audio-device", 1);
+ option_add('s', "samplerate", 1);
+ option_add('w', "write-wave", 1);
+ option_add('B', "baud-rate", 1);
+ option_add('S', "sicherung", 1);
+};
+
+int handle_options(int short_option, int argi, char **argv)
+{
+ int rc;
+
+ switch (short_option) {
+ case 'h':
+ print_help(argv[0]);
+ return 0;
+ case 'v':
+ if (!strcasecmp(argv[argi], "list")) {
+ debug_list_cat();
+ return 0;
+ }
+ rc = parse_debug_opt(argv[argi]);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to parse debug option, please use -h for help.\n");
+ return rc;
+ }
+ break;
+ case 'a':
+ dsp_audiodev = options_strdup(argv[argi]);
+ break;
+ case 's':
+ dsp_samplerate = atof(argv[argi]);
+ break;
+ case 'w':
+ wave_file = options_strdup(argv[argi]);
+ break;
+ case 'B':
+ baudrate = atoi(argv[argi]);
+ break;
+ case 'S':
+ sicherung = options_strdup(argv[argi]);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 1;
+}
+
+void sighandler(int sigset)
+{
+ if (sigset == SIGHUP)
+ return;
+ if (sigset == SIGPIPE)
+ return;
+
+ printf("Signal received: %d\n", sigset);
+
+ quit = -1;
+}
+
+int main(int argc, char *argv[])
+{
+ const char *number;
+ char string[19];
+ uint8_t data[CNETZ_LEAD_IN + 21 + CNETZ_LEAD_OUT];
+ int length;
+ int rc, argi;
+ int i, j;
+
+ debuglevel = DEBUG_INFO;
+
+ add_options();
+ rc = options_config_file(argc, argv, "~/.osmocom/analog/magnetic.conf", handle_options);
+ if (rc < 0)
+ return 0;
+
+ /* parse command line */
+ argi = options_command_line(argc, argv, handle_options);
+ if (argi <= 0)
+ return argi;
+
+ if (argi >= argc) {
+ fprintf(stderr, "Expecting phone number, use '-h' for help!\n");
+ return 0;
+ } else if (!strcmp(argv[argi], "service")) {
+ bsa44_service(string);
+ } else {
+ number = argv[argi];
+ /* remove prefix, if given */
+ if (strlen(number) >= 10 && !strncmp(number, "0160", 4))
+ number += 4;
+ if (strlen(number) < 7 || strlen(number) > 8) {
+ fprintf(stderr, "Expecting phone number to be 7 or 8 digits, use '-h' for help!\n");
+ return 0;
+ }
+ for (i = 0; number[i]; i++) {
+ if (number[0] < '0' || number[i] > '9') {
+ fprintf(stderr, "Given phone number has invalid digits, use '-h' for help!\n");
+ return 0;
+ }
+ }
+ if (number[0] > '7') {
+inval_number:
+ fprintf(stderr, "Given digits of phone number are out of range for 'C-Netz', use '-h' for help!\n");
+ return 0;
+ }
+ if (strlen(number) == 8) {
+ if ((number[1] - '0') * 10 + (number[2] - '0') > 31)
+ goto inval_number;
+ if (atoi(number + 3) > 65535)
+ goto inval_number;
+ } else {
+ if (atoi(number + 2) > 65535)
+ goto inval_number;
+ }
+ for (i = 0; sicherung[i]; i++) {
+ if (sicherung[0] < '0' || sicherung[i] > '9') {
+ fprintf(stderr, "Given security code has invalid digits, use '-h' for help!\n");
+ return 0;
+ }
+ }
+ if (!sicherung[0] || (sicherung[0] == '0' && sicherung[1] == '0') || atoi(sicherung) > 65535) {
+ fprintf(stderr, "Given security code is out of range, use '-h' for help!\n");
+ return 0;
+ }
+ cnetz_card(string, number, sicherung);
+ }
+
+ length = encode_track(data, string, CNETZ_LEAD_IN, CNETZ_LEAD_OUT);
+ if (length > (int)sizeof(data)) {
+ fprintf(stderr, "Software error: Array too small, PLEASE FIX!\n");
+ return -1;
+ }
+
+ /* alloc space depending on bit rate (length of half-bit: round up to next integer) */
+ int samples_per_halfbit = (dsp_samplerate + (baudrate * 2) - 1) / (baudrate * 2);
+ int total_samples = samples_per_halfbit * 2 * 5 * length;
+ sample_t sample[total_samples], *samples[1], silence[dsp_samplerate], level = 1;
+#ifdef HAVE_ALSA
+ int switch_on_samples = samples_per_halfbit * 2 * 5 * (CNETZ_LEAD_IN + 21 + CNETZ_SWITCH_ON);
+ int switch_off_samples = samples_per_halfbit * 2 * 5 * (CNETZ_LEAD_IN + 21 + CNETZ_SWITCH_OFF);
+ int buffer_size = dsp_samplerate * dsp_buffer / 1000;
+#endif
+
+ /* generate sample */
+ int s, ss = 0;
+ for (i = 0; i < length; i++) {
+ for (j = 0; j < 5; j++) {
+ level = -level;
+ for (s = 0; s < samples_per_halfbit; s++)
+ sample[ss++] = level;
+ if (((data[i] >> j) & 1))
+ level = -level;
+ for (s = 0; s < samples_per_halfbit; s++)
+ sample[ss++] = level;
+ }
+ }
+ memset(silence, 0, sizeof(silence));
+
+ PDEBUG(DDSP, DEBUG_INFO, "Total bits: %d\n", length * 5);
+ PDEBUG(DDSP, DEBUG_INFO, "Samples per bit: %d\n", samples_per_halfbit * 2);
+ PDEBUG(DDSP, DEBUG_INFO, "Total samples: %d (duration: %.3f seconds)\n", total_samples, (double)total_samples / (double)dsp_samplerate);
+
+ if (wave_file) {
+ wave_rec_t wave_rec;
+
+ /* open wave file */
+ rc = wave_create_record(&wave_rec, wave_file, dsp_samplerate, 1, 1.0);
+ if (rc < 0) {
+ PDEBUG(DRADIO, DEBUG_ERROR, "Failed to create WAVE record instance!\n");
+ goto error;
+ }
+ samples[0] = silence;
+ wave_write(&wave_rec, samples, dsp_samplerate / 2);
+ samples[0] = sample;
+ wave_write(&wave_rec, samples, total_samples);
+ samples[0] = silence;
+ wave_write(&wave_rec, samples, dsp_samplerate / 2);
+ wave_destroy_record(&wave_rec);
+ goto done;
+ }
+
+#ifdef HAVE_ALSA
+ /* open audio device */
+ sound = sound_open(dsp_audiodev, NULL, NULL, NULL, 1, 0.0, dsp_samplerate, buffer_size, 1.0, 1.0, 0.0, 2.0);
+ if (!sound) {
+ rc = -EIO;
+ PDEBUG(DRADIO, DEBUG_ERROR, "Failed to open sound device!\n");
+ goto error;
+ }
+#else
+ rc = -ENOTSUP;
+ PDEBUG(DRADIO, DEBUG_ERROR, "No sound card support compiled in!\n");
+ goto error;
+#endif
+
+ print_aaimage();
+ printf("String to send: ;%s?\n", string);
+ for (i = 0; i < 5; i++) {
+ if (i < 4)
+ printf("2^%d: ...", i);
+ else
+ printf("Par: ...");
+ for (j = CNETZ_LEAD_IN - 4; j < CNETZ_LEAD_IN + 4 + 21; j++)
+ printf(" %d", (data[j] >> i) & 1);
+ printf(" ...\n");
+ }
+
+ /* catch signals */
+ signal(SIGINT, sighandler);
+ signal(SIGHUP, sighandler);
+ signal(SIGTERM, sighandler);
+ signal(SIGPIPE, sighandler);
+
+#ifdef HAVE_ALSA
+ sound_start(sound);
+
+ int count;
+ while (!quit) {
+ ss = 0;
+ while (!quit) {
+ usleep(1000);
+ count = sound_get_tosend(sound, buffer_size);
+ if (count <= 0)
+ continue;
+ samples[0] = silence + ss;
+ ss += count;
+ if (ss > dsp_samplerate) {
+ count -= ss - dsp_samplerate;
+ ss = dsp_samplerate;
+ }
+ sound_write(sound, samples, NULL, count, NULL, NULL, 1);
+ if (ss == dsp_samplerate)
+ break;
+ }
+ printf("\033[0;32m -> \033[1;31mTX\033[0;32m <-\033[0;39m\r"); fflush(stdout);
+ ss = 0;
+ while (!quit) {
+ usleep(1000);
+ count = sound_get_tosend(sound, buffer_size);
+ if (count <= 0)
+ continue;
+ if ((ss >= 0 && ss < count) || (ss - switch_off_samples >= 0 && ss - switch_off_samples < count)) {
+ printf("\033[0;32m -> \033[1;31mTX\033[0;32m <- \033[0;39m\r"); fflush(stdout);
+ }
+ if (ss - switch_on_samples >= 0 && ss - switch_on_samples < count) {
+ printf("\033[0;32m -> \033[1;31mTX\033[0;32m <- -> \033[1;33mCLICK\033[0;32m <-\033[0;39m\r"); fflush(stdout);
+ }
+ samples[0] = sample + ss;
+ ss += count;
+ if (ss > total_samples) {
+ count -= ss - total_samples;
+ ss = total_samples;
+ }
+ sound_write(sound, samples, NULL, count, NULL, NULL, 1);
+ if (ss == total_samples)
+ break;
+ }
+ printf(" \r"); fflush(stdout);
+ }
+#endif
+
+ /* reset signals */
+ signal(SIGINT, SIG_DFL);
+ signal(SIGHUP, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGPIPE, SIG_DFL);
+
+error:
+#ifdef HAVE_ALSA
+ if (sound)
+ sound_close(sound);
+#endif
+
+done:
+ options_free();
+
+ return 0;
+}
+
+#endif /* ARDUINO */