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.
This commit is contained in:
Andreas Eversberg 2021-08-28 20:59:20 +02:00
parent 922b4af362
commit 465445aac5
22 changed files with 16899 additions and 0 deletions

1
.gitignore vendored
View File

@ -75,6 +75,7 @@ src/radio/osmoradio
src/datenklo/datenklo
src/zeitansage/zeitansage
src/sim/cnetz_sim
src/magnetic/cnetz_magnetic
src/fuvst/fuvst
src/fuvst/fuvst_sniffer
extra/cnetz_memory_card_generator

18
cad/coil.scad Normal file
View File

@ -0,0 +1,18 @@
$fn = 100;
durchmesser=30;
rille=8;
wand=1;
module torus(d1, d2) {
rotate_extrude(convexity = 10)
translate([d2/2+d1/2, 0, 0])
circle(r = d1/2);
}
intersection() {
difference() {
torus(rille+wand*2,durchmesser-wand*2);
torus(rille,durchmesser);
}
translate([0,0,-5]) cylinder(h=10, r=(durchmesser+rille)/2);
}

BIN
cad/coil.stl Normal file

Binary file not shown.

View File

@ -110,6 +110,7 @@ AC_OUTPUT(
src/datenklo/Makefile
src/zeitansage/Makefile
src/sim/Makefile
src/magnetic/Makefile
src/fuvst/Makefile
src/test/Makefile
src/Makefile

View File

@ -122,6 +122,7 @@ Additional features:
<li><a href="datenklo.html">Das Datenklo</a></li>
<li><a href="tv.html">Osmo TV</a></li>
<li><a href="sim.html">C-Netz Sim Card</a></li>
<li><a href="magnetic.html">C-Netz Magnetic Card</a></li>
<li>Zeitansage (German talking clock)</li>
<li>C-Netz FuVSt (MSC to control a real base station)</li>
</ul>

BIN
docs/mag1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

BIN
docs/mag2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

BIN
docs/mag3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

BIN
docs/mag4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
docs/magnetic-layout.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/magnetic-schematic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

304
docs/magnetic.html Normal file
View File

@ -0,0 +1,304 @@
<html>
<head>
<link href="style.css" rel="stylesheet" type="text/css" />
<title>osmocom-analog</title>
</head>
<body>
<center><table><tr><td>
<h2><center>C-Netz Magnetic Card Emulator</center></h2>
<center><img src="magnetic.jpg"/></center>
<p>
Why emulating a magnetic card with a magnetic strip?
Maybe you got a C-Netz phone with magnetic card reader from the attic, friend or Ebay?
But a card is missing. Without the card you cannot use that C-Netz phone at all.
Then you buy at high price on Ebay to get a used card.
This card might make your phone work, but 'BSA 44' phone requires service reset to disable theft protection.
<font color="red">The BSA 44 phone requires a magnetic service card to unlock the phone after power loss.</font>
The emulator can help you in this case.
And it is really a cool way.
</p>
<ul>
<li><a href="#emu">Emulating Magnetic Card</a>
<li><a href="#byo">Build Your Own Magnetic Card</a>
<li><a href="#usage">Using the Magnetic Card</a>
<li><a href="#service">BSA 44 Service Cards</a>
</ul>
<p class="toppic">
<a name="emu"></a>
Emulating Magnetic Card
</p>
<center><img src="mag4.jpg"/></center>
<p>
I put a card inside a bowl with fine rust (iron oxide) and water.
As depicted above, the third track of the card attracts tiny patricles of iron oxide.
The bits can be seen under a microsope.
The rusy-looking lines on the left represent a 0-bit per line.
The wider lines which appear brighter are actually two lines, representing a 1-bit.
Each line is a reversal in the magnetic flux.
For deeper understanding, refer to ISO 7811 standard.
</p>
<p>
The idea is to transmit a strong magnetic signal to the card reader's head from outside the case, rather than connecting wires inside the reader.
This way no modification to the card reader is required.
This idea is not new.
'MagSpoof' is a project to emulate credit and debit cards.
The MagSpoof hardware and my hardware is almost the same.
You can run the C-Netz magnetic card emulator on MagSpoof hardware, if you just add another switch.
</p>
<p>
<font color="red">
I strongly suggest connecting an oscilloscope to the output (not directly to the head) of the phone's card reader.
This helps you to find out if your transmitter is strong enough and the phone's card reader receives a signal.
</font>
</p>
<p>
You can generate the signal with the sound output of your PC.
Your PC must be connected to an amplifier.
Instead of using a speaker, connect a coil to your amplifier.
I used a 0.5mm (diameter!!) copper wire with 50 turns and a diameter of 30mm.
</p>
<pre>
# src/magnetic/cnetz_magnetic -a hw:0,0 1234567
String to send: ;10234567=123450000?
2^0: ... 0 0 0 0 1 1 0 0 1 0 1 0 1 1 1 0 1 0 1 0 0 0 0 1 0 0 0 0 0 ...
2^1: ... 0 0 0 0 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 0 0 0 0 1 0 0 0 0 0 ...
2^2: ... 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 1 1 0 0 0 0 1 0 0 0 0 0 ...
2^3: ... 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 ...
Par: ... 0 0 0 0 0 0 1 0 1 0 1 1 0 0 0 0 1 0 1 1 1 1 1 1 0 0 0 0 0 ...
main.c: 258 info : Total bits: 915
main.c: 259 info : Samples per bit: 20
main.c: 260 info : Total samples: 18300 (duration: 0.381 seconds)
-&gt; TX &lt;-
</pre>
<p>
In this example the first sound card is used.
The card to be emulated has the phone number 1234567.
See <a href="software.html">Software usage</a> for more info about selecting sound card.
A cracking sound, followed by rectangular tone is played.
During the tone, the switch inside the card reader must be briefly closed for a short fraction of a second.
The switch is used to detect insertion and removal of the card.
</p>
<center><img src="mag2.jpg"/></center>
<p>
In order to close the switch inside the card reader, just punch a hole into some platic card.
Don't use anything other than an ISO platic card.
I tried cardboard, but it did not work.
The thickness of the card must be close to 0.8mm to make the switch work correctly.
I just drilled four holes into the card, so I do not need to care what orientation the card must be inserted.
The center of the hole must be exactly 10mm away from each edge of the card.
The diameter is 5mm, but use 6mm, so you don't need to be exactly 10mm away from the edge.
Don't forget to deburr the edge inside the hole.
</p>
<p>
Slide the card half way into the card reader.
Match the hole with the arrow or dot shown on the slot of the card reader.
On the other side of the card reader you will find the head.
(Just open it to see where it is located.)
Hold the coil close to the head. Try different angles of the coil.
When you hear the cracking noise or you see the 'TX' flashing on the terminal, quickly push the card all the way in.
This needs to be done before the sound or 'TX' disappears.
It takes a little parctice.
You may also pull card quickly half way out, after fully inserting it.
It does not matter what direction the card is moved, as long as the switch is only closed for a short time.
If you do it slowly, you will hear the two clicks, one when the switch is closed and one when it is opened.
</p>
<p class="toppic">
<a name="byo"></a>
Build Your Own Magnetic Card
</p>
<p>
The idea is to drive a coil with a step motor driver IC.
This way you actually transmit the magnetic card's signal over several centimeters to the card reader's head.
The coil is a 0.5mm thick wire and has 50 windings with about 30 mm in diameter.
In the 'cad' directory of the git repository You will find the OpenSCAD and STL files of a 'thing' to wind the coil around.
Alternatively wind the coil around two fingers 50 times and then fix the windings by wrapping tape around it..
</p>
<center><img src="magnetic-schematic.png"/></center>
<p>
It is a quite simple schematics.
The difference between Magspoof and this one is just an additional switch.
</p>
<center><img src="magnetic-layout.png"/></center>
<p>
You find the PCB drawings inside the "layout" directory of the git repository.
Be sure to print it without scaling!
</p>
<ul>
<li>ATTINY85
<li>L293D (or stronger)
<li>two switches
<li>LED of you choise
<li>500 Ohms resistor (if LED is not that bright)
<li>100uF buffering capacitor for power input
</ul>
<center><img src="mag1.jpg"/></center>
<p>
Leave the fuses of the ATTINY85 as it is shipped by default.
The fuses are set to use the internal 8 MHz clock with scaling to 1 MHz.
The Arduino file is located inside the "src/magnetic" directory of the git repository.
It runs at 8 MHz.
You don't need to disable the 1 MHz scaling via fuses.
It is done in software.
</p>
<p class="toppic">
<a name="usage"></a>
Using the Magnetic Card
</p>
<p>
Power it up using three AA batteries.
Be sure not to reverse the power, it will destroy both ICs.
The LED will blink 3 times, which takes about 1 second.
If it does not take about 1 second, check fuses!
Afterwards it will flash every two seconds, to indicate that the power is on.
</p>
<p>
Normal mode:
<ul>
<li>Turn power on.
<li>The LED will flash 3 times.
<li>Press the switch 1 ("Card") to send a subscriber card sequence (1234567 by default).
<li>The LED will light up for a fraction of a second.
<li>Press the switch 2 ("Service") to send a BSA44 service card sequence.
<li>The LED will light up for a fraction of a second.
</ul>
</p>
<p>
Quickly after pressing the switch, insert the card.
If the card is inserted within 150 ms after pressing the switch, you may hear a beep from the phone.
This means that the card was accepted.
</p>
<p>
<font color="red">
Test the emulator by location the coil close to an AM radio and you should hear the signal when you press a button.
You may also connect some coil to an oscilloscope/headphone and hold the emulator's coil close to it.
</font>
</p>
<p>
Debug mode:
<ul>
<li>Press and hold switch 1 or 2.
<li>Turn power on.
<li>Release the switch.
<li>The LED will continuously light.
<li>Hold switch 1 ("Card") to send sequence of 0s.
<li>Hold switch 2 ("Service") to send sequence of 1s.
</ul>
</p>
<p>
<font color="red">
Do not hold longer than two seconds, as the H-bridge becomes hot very quickly.
Touch it, to feel when it becomes too hot.
</font>
</p>
<p>
Programming mode:
<ul>
<li>Turn power on.
<li>The LED will flash 3 times.
<li>Press both switches simultaniously and release.
<li>The LED will continuously flash.
<li>Press switch 1 ("Card") to change subscriber number.
<li>or Press switch 2 ("Service") to change security code. (Sicherungsschl&uuml;ssel)
<li>The LED will blink several times, indicating the first digit. '0' is indicated by blinking 10 times.
<li>After blinking the first digit, a pause is made and then the LED will blink the second digit and so on.
<li>After blinking all digits, the LED will light half a second, to indicate input of the first digit.
<li>Press switch 1 as many times as your first digit's value you want to enter. 10 times for a '0'.
<li>Press switch 2 to complete your digit input. The LED will light half a second to indicate input the next digit.
<li>If you have entered all digits, press switch 2 again to store the entered sequence.
<li>The LED will softly flash one time. This inidcates correct number. The number is stored.
<li>On error, LED will start flashing again. Repeat input!
<li>To abort input and return to normal mode: Press both switches simultaniously and release.
</ul>
</p>
<p>
Example to program subscriber number '3839349":
<ul>
<li>Press both switches simultaniously and release.
<li>The LED will continuously flash.
<li>Press switch 1 ("Card") to change subscriber number.
<li>The current subscriber number will show up blinks of the LED.
<li>Press any switch to abort blinking and directly enter new number.
<li>The LED light half a second to indicate input.
<li>Press switch 1 three times.
<li>Press switch 2, the LED will light half a second.
<li>Press switch 1 eight times.
<li>Press switch 2, the LED will light half a second.
<li>Press switch 1 three times.
<li>Press switch 2, the LED will light half a second.
<li>Press switch 1 nine times.
<li>Press switch 2, the LED will light half a second.
<li>Press switch 1 three times.
<li>Press switch 2, the LED will light half a second.
<li>Press switch 1 four times.
<li>Press switch 2, the LED will light half a second.
<li>Press switch 1 nine times.
<li>Press switch 2, the LED will light half a second.
<li>Press switch 2 again. The LED will softly flash one time. The number is stored.
</ul>
</p>
<p>
The Subscriber number entered must conform to a 7-digits or 8-digits C-Netz subscriber number.
The Security code must be a 16 bit unsigned integer, entered in decimal notation (from 0 to 65535).
</p>
<p class="toppic">
<a name="service"></a>
BSA 44 Service Cards
</p>
<p>
When inserting (simulating) a service card, a BSA44 phone will show "Wartungskarte" on its LC display.
Turn off the phone and then turn it on again, but leave card inserted and power connected.
Press the top-left button two times, so that is shows "SA....".
Press X 0 X 1 X 2 to reset password to '0000' and disable theft protection.
Turn off the phone and remove card, but leave power connected.
If the power is disconnected, the theft protection will be activated and unlocking as described above is required again.
</p>
<p>
Older 'LED' display phones directly show "SA....", so they must not be restarted before using the service menu.
You may directly press X 0 X 1 X 2 to reset it.
</p>
<hr><center>[<a href="index.html">Back to main page</a>]</center><hr>
</td></tr></table></center>
</body>
</html>

BIN
docs/magnetic.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

2770
layout/magspoof.brd Executable file

File diff suppressed because it is too large Load Diff

12722
layout/magspoof.sch Executable file

File diff suppressed because it is too large Load Diff

View File

@ -59,6 +59,7 @@ SUBDIRS += \
radio \
zeitansage \
sim \
magnetic \
fuvst
if HAVE_ALSA

28
src/magnetic/Makefile.am Normal file
View File

@ -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

32
src/magnetic/image.c Normal file
View File

@ -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 */

162
src/magnetic/iso7811.c Normal file
View File

@ -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;
}

5
src/magnetic/iso7811.h Normal file
View File

@ -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);

486
src/magnetic/magnetic.ino Executable file
View File

@ -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;
}
}

368
src/magnetic/main.c Normal file
View File

@ -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 */