From d7e68ff50b365863c7c5298b3945dfaa5577b9a9 Mon Sep 17 00:00:00 2001 From: Jeffrey Malone Date: Fri, 5 Nov 2010 10:25:47 -0700 Subject: [PATCH 1/1] Initial push to maemo garage. This is version 0.1.0. --- MagRead.pro | 50 +++++ audioinput.cpp | 153 +++++++++++++++ audioinput.h | 60 ++++++ carddetect.cpp | 258 +++++++++++++++++++++++++ carddetect.h | 51 +++++ debian/changelog | 5 + debian/compat | 1 + debian/control | 26 +++ debian/copyright | 35 ++++ debian/optify | 1 + debian/rules | 54 ++++++ llist.c | 92 +++++++++ llist.h | 27 +++ maemofiles/magread.desktop | 9 + maemofiles/magread.png | Bin 0 -> 265 bytes magcard.h | 71 +++++++ magread.cpp | 277 ++++++++++++++++++++++++++ magread.h | 68 +++++++ main.cpp | 34 ++++ mslib.c | 461 ++++++++++++++++++++++++++++++++++++++++++++ mslib.h | 208 ++++++++++++++++++++ 21 files changed, 1941 insertions(+) create mode 100644 MagRead.pro create mode 100644 audioinput.cpp create mode 100644 audioinput.h create mode 100644 carddetect.cpp create mode 100644 carddetect.h create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/optify create mode 100755 debian/rules create mode 100644 llist.c create mode 100644 llist.h create mode 100644 maemofiles/magread.desktop create mode 100644 maemofiles/magread.png create mode 100644 magcard.h create mode 100644 magread.cpp create mode 100644 magread.h create mode 100644 main.cpp create mode 100644 mslib.c create mode 100644 mslib.h diff --git a/MagRead.pro b/MagRead.pro new file mode 100644 index 0000000..812f365 --- /dev/null +++ b/MagRead.pro @@ -0,0 +1,50 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2010-07-21T09:30:18 +# +#------------------------------------------------- + +QT += core gui + +TARGET = MagRead +TEMPLATE = app + +maemo5 { + QT+= maemo5 +} + + +SOURCES += main.cpp\ + magread.cpp \ + carddetect.cpp \ + audioinput.cpp \ + mslib.c \ + llist.c + +HEADERS += magread.h \ + carddetect.h \ + audioinput.h \ + mslib.h \ + llist.h \ + magcard.h + +LIBS += -lpulse-simple -lpulse + +CONFIG += link_pkconfig + +symbian { + TARGET.UID3 = 0xe2c961e1 + # TARGET.CAPABILITY += + TARGET.EPOCSTACKSIZE = 0x14000 + TARGET.EPOCHEAPSIZE = 0x020000 0x800000 +} + +unix { + INSTALLS += target desktop icon48 + target.path = /usr/bin/magread + desktop.path = /usr/share/applications/hildon + desktop.files += maemofiles/magread.desktop + icon48.path = /usr/share/icons/hicolor/48x48/hildon + icon48.files += maemofiles/magread.png +} + diff --git a/audioinput.cpp b/audioinput.cpp new file mode 100644 index 0000000..33b748c --- /dev/null +++ b/audioinput.cpp @@ -0,0 +1,153 @@ +/* + This file is part of MagRead. + + MagRead 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. + + MagRead 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 MagRead. If not, see . + + Written by Jeffrey Malone + http://blog.tehinterweb.com +*/ +#include "audioinput.h" + +#include + +#define BUF_SIZE 1024 +//#define DEBUG + +AudioInput::AudioInput(QObject *parent) : QThread(parent) { + paSpec.format = PA_SAMPLE_S16LE; + paSpec.channels = 1; + paSpec.rate = 48000; + + paServer = NULL; + captureAudio = false; + silenceThresh = 750; + + ms = 0; +} + +AudioInput::~AudioInput() { + if( paServer ) + pa_simple_free( paServer ); +} + +void AudioInput::run() { + #ifdef DEBUG + qDebug() << "AudioInput::run() start"; + #endif + + paServer = pa_simple_new( NULL, "MagRead", PA_STREAM_RECORD, "source.hw0", "Recording", &paSpec, NULL, NULL, &paError ); + if( !paServer ) { + QString err = "Failed to open PulseAudio server:\n"; + err.append( pa_strerror( paError ) ); + emit error( err ); + return; + } + + captureAudio = true; + + + qint16 pcmDataBlock[ BUF_SIZE ]; + int i, silence = 0; + bool noise = false; + pcmData.clear(); + while( captureAudio ) { + if( pa_simple_read( paServer, pcmDataBlock, ( sizeof( qint16 ) * BUF_SIZE ), &paError ) ) { + QString err = "Failed to read a block of data:\n"; + err.append( pa_strerror( paError ) ); + emit error( err ); + captureAudio = false; + noise = false; + } + + for( i = 0; i < BUF_SIZE; i++ ) { + if( qAbs( pcmDataBlock[ i ] ) > silenceThresh ) { + noise = true; + silence = 0; + } else { + silence++; + } + } + + if( noise ) { + pcmData.append( pcmDataBlock, BUF_SIZE ); + } + + if( noise && silence > 200 ) { + captureAudio = false; + } else if( noise && ( pcmData.count() / BUF_SIZE ) > 30 ) { + /* Stream is too fucking long, we're clearing and restarting */ + pcmData.clear(); + silence = 0; + noise = false; + } + } + + pa_simple_free( paServer ); + paServer = NULL; + + if( noise ) { + processSwipe(); + } + #ifdef DEBUG + qDebug() << "AudioInput::run() stop"; + #endif +} + +void AudioInput::stop() { + #ifdef DEBUG + qDebug() << "AudioInput::stop() "; + #endif + captureAudio = false; +} + +void AudioInput::processSwipe() { + int retval; + + #ifdef DEBUG + qDebug() << "AudioInput::processSwipe() start"; + #endif + + if( ms ) + ms_free( ms ); + + ms = ms_create( pcmData.data(), pcmData.count() ); + #ifdef DEBUG + qDebug() << "AudioInput::processSwipe() 1"; + #endif + + //ms_peaks_find( ms ); + ms_peaks_find_walk( ms ); + ms_peaks_filter_group( ms ); + #ifdef DEBUG + qDebug() << "AudioInput::processSwipe() 2"; + #endif + + ms_decode_peaks( ms ); + #ifdef DEBUG + qDebug() << "AudioInput::processSwipe() 3"; + #endif + + retval = ( ms_decode_bits( ms ) == 0 ) ; + + card.charStream = ms_get_charStream( ms ); + if( !card.charStream.isEmpty() ) { + card.encoding = ms->dataType; + card.swipeValid = retval; + + emit cardRead( card ); + } + + run(); +} + diff --git a/audioinput.h b/audioinput.h new file mode 100644 index 0000000..1d35e10 --- /dev/null +++ b/audioinput.h @@ -0,0 +1,60 @@ +/* + This file is part of MagRead. + + MagRead 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. + + MagRead 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 MagRead. If not, see . + + Written by Jeffrey Malone + http://blog.tehinterweb.com +*/ +#ifndef AUDIOINPUT_H +#define AUDIOINPUT_H + +#include +#include +#include +#include +#include + +#include "mslib.h" +#include "magcard.h" + +class AudioInput : public QThread { + Q_OBJECT + public: + explicit AudioInput( QObject *parent = 0 ); + ~AudioInput(); + + void run(); + void stop(); + void processSwipe(); + + signals: + void cardRead( const MagCard& ); + void error( const QString& ); + + private: + pa_simple *paServer; + pa_sample_spec paSpec; + int paError; + + bool captureAudio; + int silenceThresh; + QVarLengthArray pcmData; + + MagCard card; + + msData *ms; +}; + +#endif // AUDIOINPUT_H diff --git a/carddetect.cpp b/carddetect.cpp new file mode 100644 index 0000000..58473a4 --- /dev/null +++ b/carddetect.cpp @@ -0,0 +1,258 @@ +/* + This file is part of MagRead. + + MagRead 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. + + MagRead 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 MagRead. If not, see . + + Written by Jeffrey Malone + http://blog.tehinterweb.com +*/ +#include "carddetect.h" + +CardDetect::CardDetect( MagCard *_card ) { + aamvaIssuerList(); + if( _card ) + setCard( _card ); +} + +void CardDetect::setCard( MagCard *_card ) { + card = _card; + processCard(); +} + +void CardDetect::processCard() { + if( !card ) + return; + + card->type = MagCard::CARD_UNKNOWN; + + /* Fill in the fields.. */ + QString expDate; + int acctLen = 0; + if( card->encoding == ABA && card->charStream.startsWith( ';' ) ) { + acctLen = card->charStream.indexOf( '=' ) - 1; + if( acctLen > 0 ) { + card->accountNumber = card->charStream.mid( 1, acctLen ); + + expDate = card->charStream.mid( acctLen + 2, 4 ); + card->miscData = card->charStream.mid( acctLen + 6 ).remove( '?' ); + } + } else if( card->encoding == IATA && card->charStream.startsWith( "%B" ) ) { + acctLen = card->charStream.indexOf( '^', 1 ) - 2; + if( acctLen > 0 ) { + card->accountNumber = card->charStream.mid( 2, acctLen ); + + card->miscData = card->charStream.section( '^', 2 ).remove( '?' ); + expDate = card->miscData.left( 4 ); + card->miscData.remove( 0, 4 ); + } + + } + + + //if the above didn't yield us an account number, just copy the charStream */ + if( card->accountNumber.isEmpty() ) { + card->accountNumber = card->charStream; + card->charStream.remove( ';' ); + card->charStream.remove( '%' ); + card->charStream.remove( '?' ); + } + + if( acctLen ) + luhnCheck(); + + if( card->accountValid ) + creditCardCheck(); + + /* This code allows partial credit card swipes to be treated as valid, as long at least 3 digits + * of extra data that passes checksum are present */ + if( card->accountValid && !card->swipeValid && card->type & MagCard::CARD_CC ) { + if( 3 > card->miscData.length() || card->miscData.left( 3 ).contains( BAD_CHAR ) ) { + card->accountValid = false; + } + } + + if( card->swipeValid && card->type == MagCard::CARD_UNKNOWN ) + aamvaCardCheck( expDate ); + + if( !expDate.isEmpty() && card->type != MagCard::CARD_AAMVA ) { + card->expirationDate = QDate::fromString( expDate, "yyMM" ); + if( card->expirationDate.year() < 1980 )//qdate defaults to 19nn. Adjust for this. + card->expirationDate = card->expirationDate.addYears( 100 ); + + if( card->expirationDate.isValid() ) {//Make it so it expires on the last day of the given month + card->expirationDate = card->expirationDate.addDays( card->expirationDate.daysInMonth() - 1); + } else { + card->type = MagCard::CARD_UNKNOWN; // must contain a valid date to be known + } + } +} + +void CardDetect::aamvaIssuerList() { + issuerList[ "636026" ] = (struct issuer) { "Arizona", "AZ", "L" }; + issuerList[ "0636021" ] = (struct issuer) { "Arkansas", "AR", "" }; + issuerList[ "636014" ] = (struct issuer) { "California", "CA", "L" }; + issuerList[ "636020" ] = (struct issuer) { "Colorado", "CO", "NN-NNN-NNNN" }; + issuerList[ "636010" ] = (struct issuer) { "Florida", "FL", "LNNN-NNN-NN-NNN-N" }; + issuerList[ "636018" ] = (struct issuer) { "Iowa", "IA", "NNNLLNNNN" }; + issuerList[ "636022" ] = (struct issuer) { "Kansas", "KS", "KNN-NN-NNNN" }; + issuerList[ "636007" ] = (struct issuer) { "Louisiana", "LA", "" }; + issuerList[ "636003" ] = (struct issuer) { "Maryland", "MD", "L-NNN-NNN-NNN-NNN" }; + issuerList[ "636032" ] = (struct issuer) { "Michigan", "MI", "L NNN NNN NNN NNN" }; + issuerList[ "636038" ] = (struct issuer) { "Minnesota", "MN", "L" }; + issuerList[ "636030" ] = (struct issuer) { "Missouri", "MO", "L" }; + issuerList[ "636039" ] = (struct issuer) { "New Hampshire", "NH", "NNLLLNNNN" }; + issuerList[ "636009" ] = (struct issuer) { "New Mexico", "NM", "" }; + issuerList[ "636023" ] = (struct issuer) { "Ohio", "OH", "LLNNNNNN" }; + issuerList[ "636025" ] = (struct issuer) { "Pennsylvania", "PA", "NN NNN NNN" }; + issuerList[ "636005" ] = (struct issuer) { "South Carolina", "SC", "" }; + issuerList[ "636015" ] = (struct issuer) { "Texas", "TX", "" }; + issuerList[ "636024" ] = (struct issuer) { "Vermont", "VT", "NNNNNNNL" }; + issuerList[ "636031" ] = (struct issuer) { "Wisconsin", "WI", "LNNN-NNNN-NNNN-NN" }; + issuerList[ "636027" ] = (struct issuer) { "State Dept (USA)", "US-DoS", "" }; + + /* Exceptions: + * Arkansas -- Inexplicably, they put a leading 0 on their IIN. This + * is handled below in the lookup. + * Vermont -- I'm told they have additional format to the one listed + * above that is 8 numbers (no letter). As the two + * formats have different field widths, I automatically + * handle this in the formatting algorithm. + */ + + //Formatting information is not available for Canada right now + issuerList[ "636028" ] = (struct issuer) { "British Columbia", "BC", "" }; + issuerList[ "636017" ] = (struct issuer) { "New Brunswick", "NB", "" }; + issuerList[ "636016" ] = (struct issuer) { "Newfoundland", "NL", "" }; + issuerList[ "636013" ] = (struct issuer) { "Nova Scotia", "NS", "" }; + issuerList[ "636012" ] = (struct issuer) { "Ontario", "ON", "" }; + issuerList[ "636044" ] = (struct issuer) { "Saskatchewan", "SK", "" }; + + //These may or may not have magstripes. I'm including them in case they do + issuerList[ "604427" ] = (struct issuer) { "American Samoa", "AS", "" }; + issuerList[ "636019" ] = (struct issuer) { "Guam", "GU", "" }; + issuerList[ "636062" ] = (struct issuer) { "US Virgin Islands", "US-VI", "" }; + issuerList[ "636056" ] = (struct issuer) { "Coahuila", "MX-COA", "" }; + issuerList[ "636057" ] = (struct issuer) { "Hidalgo", "MX-HID", "" }; + +} +void CardDetect::aamvaCardCheck( QString expDate ) { + if( card->encoding == IATA ) + return; //we're only going to support ABA for now + struct issuer issuerInfo; + + QString iin = card->accountNumber.left( 6 ); + + issuerInfo = issuerList.value( iin ); + if( issuerInfo.name.isEmpty() ) { + iin = card->accountNumber.mid( 1, 6 ); + issuerInfo = issuerList.value( iin ); + if( issuerInfo.name.isEmpty() ) + return; // this is not a known AAMVA card, abort + } + + card->type = MagCard::CARD_AAMVA; + + card->aamvaIssuer = iin; + card->aamvaIssuerName = issuerInfo.name; + card->aamvaIssuerAbr = issuerInfo.abbreviation; + + card->accountNumber.remove( iin ); + if( card->miscData.length() > 8 ) + card->accountNumber.append( card->miscData.mid( 8 ) ); + + //format the id number if applicable + if( !issuerInfo.format.isEmpty() ) { + for( int i = 0; i < issuerInfo.format.length(); i++ ) { + if( issuerInfo.format.at( i ) == 'L' ) { + if( card->accountNumber.length() - i < 2 ) + continue; + QChar letter = card->accountNumber.mid( i, 2 ).toInt() + 64; + if( letter.isLetter() ) + card->accountNumber.replace( i, 2, letter ); + } else if( issuerInfo.format.at( i ) != 'N' ) { + card->accountNumber.insert( i, issuerInfo.format.at( i ) ); + } + } + } + + //set the birthday + QString bday = card->miscData.left( 8 ); + if( bday.mid( 4, 2 ) > "12" ) { //some (Calif) violate AAMVA standard and switch the exp and bday month values + QString exp = bday.mid( 4, 2 ); + bday.replace( 4, 2, expDate.right( 2 ) ); + expDate.replace( 2, 2, exp ); + } + card->aamvaBirthday = QDate::fromString( bday, "yyyyMMdd" ); + + //set the expiration date + if( expDate.endsWith( "99" ) ) { // expires on the birth day and month in the given year + expDate.replace( 2, 4, bday.mid( 4, 4 ) ); + expDate.prepend( "20" ); + + card->expirationDate = QDate::fromString( expDate, "yyyyMMdd" ); + } else if( !expDate.endsWith( "77" ) ) { + if( expDate.endsWith( "88" ) )// expires on last day of the month of birth in given year + expDate.replace( 2, 2, bday.mid( 4, 2 ) ); + + expDate.prepend( "20" ); + card->expirationDate = QDate::fromString( expDate, "yyyyMM" ); + card->expirationDate.addDays( card->expirationDate.daysInMonth() - 1 ); + } + +} + +void CardDetect::creditCardCheck() { + int acctLen = card->accountNumber.length(); + if( acctLen == 16 ) { + if( card->accountNumber.startsWith( '4' ) ) { + card->type = MagCard::CARD_VISA; + card->accountIssuer = "Visa"; + } else if( card->accountNumber.left( 2 ) >= "51" && card->accountNumber.left( 2 ) <= "55" ) { + card->type = MagCard::CARD_MC; + card->accountIssuer = "MasterCard"; + } else if( card->accountNumber.startsWith( "6011" ) || card->accountNumber.startsWith( "65" ) ) { + card->type = MagCard::CARD_DISC; + card->accountIssuer = "Discover"; + } + } else if( acctLen == 15 ) { + if( card->accountNumber.startsWith( "34" ) || card->accountNumber.startsWith( "37" ) ) { + card->type = MagCard::CARD_CC | MagCard::CARD_AMEX; + card->accountIssuer = "American Express"; + } + } + + if( card->encoding == IATA && card->type & MagCard::CARD_CC ) { + card->accountHolder = card->charStream.section( '^', 1, 1 ).trimmed(); + if( card->accountHolder.contains( '/' ) ) { // fix the formatting from "LAST/FIRST" on some cards + card->accountHolder = card->accountHolder.section( '/', 1, 1 ) + ' ' + card->accountHolder.section( '/', 0, 0 ); + } + } +} + +/* verifies the card->accountNumber against the luhn algorithm and sets card->accountValid */ +void CardDetect::luhnCheck() { + int total = 0; + for( int i = 0; i < card->accountNumber.length(); i++ ) { + if( i & 1 ) { // if odd + total += card->accountNumber.at( i ).digitValue(); + } else { + int x = card->accountNumber.at( i ).digitValue() * 2; + total += x % 10; + if( x > 9 ) + total++; + } + } + + card->accountValid = !( total % 10 ); +} diff --git a/carddetect.h b/carddetect.h new file mode 100644 index 0000000..9abfe1f --- /dev/null +++ b/carddetect.h @@ -0,0 +1,51 @@ +/* + This file is part of MagRead. + + MagRead 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. + + MagRead 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 MagRead. If not, see . + + Written by Jeffrey Malone + http://blog.tehinterweb.com +*/ +#ifndef CARDDETECT_H +#define CARDDETECT_H + +#include +#include "magcard.h" + +struct issuer { + QString name; + QString abbreviation; + QString format; +}; + +class CardDetect { + public: + CardDetect( MagCard *_card = 0 ); + void setCard( MagCard *_card ); + + private: + MagCard *card; + + void processCard(); + + void luhnCheck(); + void creditCardCheck(); + void aamvaCardCheck( QString expDate ); + void aamvaIssuerList(); + QMap issuerList; + +}; + + +#endif // CARDDETECT_H diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..37ea9ba --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +magread (0.1.0) unstable; urgency=low + + * Initial release + + -- Jeffrey Malone Thu, 04 Nov 2010 06:10:14 -0700 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +5 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..6b607be --- /dev/null +++ b/debian/control @@ -0,0 +1,26 @@ +Source: magread +Section: user/utilities +Priority: extra +Maintainer: Jeffrey Malone +Build-Depends: debhelper (>= 5), libqt4-dev (>=4.6.1), libpulse-dev +Standards-Version: 3.7.3 + +Package: magread +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, libqt4-core (>= 4.6.1), + libqt4-gui (>= 4.6.1), libqt4-maemo5 (>= 4.6.1), + libpulse0 +Description: Reads magnetic stripe cards + Using a hardware audio dongle, MagRead can read most magnetic stripe cards. + For credit cards, and ID cards issued in North America, it will also parse the + data and display it in a familiar format. + *Requires that you have a hardware dongle, such as those provided by Square.* + See http://blog.tehinterweb.com/ for more details +XB-Maemo-Icon-26: + iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c + 6QAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0 + SU1FB9oLBQozCb9fDIgAAACJSURBVGje7dk7DgIxEINhD+FeuRryxU2xLKKG + yuxvKf18SpTHZJKoOTeVBwAAAAAA/JYkSiLbkVQxbOes+118W05EZfGfiDlW + Ued9aGbYhQAAAAAAAAAAAAAAuDBgXu9MZgAAAAAAvspdOrpzlWfAjJbtx967 + ErDW+oPeaHt3evgnBgAAAIBLA56qTbfaUaUizgAAAABJRU5ErkJggg== + diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..6212357 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,35 @@ +This package was debianized by Jeffrey Malone on +Thu, 04 Nov 2010 06:10:14 -0700. + +Upstream Author(s): + + Jeffrey Malone + +Copyright: + + Jeffrey Malone + +License: + + 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 . + +On Debian systems, the complete text of the GNU General +Public License can be found in `/usr/share/common-licenses/GPL'. + +The Debian packaging is (C) 2010, Jeffrey Malone and +is licensed under the GPL, see above. + + +# Please also look if there are files or directories which have a +# different copyright/license attached and list them here. diff --git a/debian/optify b/debian/optify new file mode 100644 index 0000000..865faf1 --- /dev/null +++ b/debian/optify @@ -0,0 +1 @@ +auto diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..c9b2876 --- /dev/null +++ b/debian/rules @@ -0,0 +1,54 @@ +#!/usr/bin/make -f +APPNAME := magread +builddir: + mkdir -p builddir + +builddir/Makefile: builddir + cd builddir && qmake-qt4 PREFIX=/usr ../MagRead.pro + +build: build-stamp + +build-stamp: builddir/Makefile + dh_testdir + # Add here commands to compile the package. + cd builddir && $(MAKE) + touch $@ + +clean: + dh_testdir + dh_testroot + rm -f build-stamp + # Add here commands to clean up after the build process. + rm -rf builddir + dh_clean +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + # Add here commands to install the package into debian/your_appname + cd builddir && $(MAKE) INSTALL_ROOT=$(CURDIR)/debian/$(APPNAME) install +# Build architecture-independent files here. +binary-indep: build install +# We have nothing to do by default. + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir + dh_testroot + dh_installdocs + dh_installexamples + dh_installman + dh_link +# dh_strip --dbg-package=magread-dbg + dh_compress + dh_fixperms + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/llist.c b/llist.c new file mode 100644 index 0000000..e9dc05b --- /dev/null +++ b/llist.c @@ -0,0 +1,92 @@ +#include "llist.h" + + +LListH *llist_init() { + LListH *list; + + list = ( LListH * ) malloc( sizeof( LListH ) ); + if( list == NULL ) + return NULL; + + list->first = NULL; + list->last = NULL; + list->len = 0; + + return list; +} + + +void llist_append( LListH *list, int idx, short amp ) { + LList *node; + + node = ( LList * ) malloc( sizeof( LList ) ); + if( node == NULL ) + return; + + node->idx = idx; + node->amp = amp; + node->next = NULL; + + if( list->first == NULL ) + list->first = node; + if( list->last != NULL ) + list->last->next = node; + + list->last = node; + + list->len++; +} + +void llist_remove_idx( LListH *list, int idx ) { + LList *trav,*prev; + + for( trav = list->first, prev = NULL; trav != NULL; trav = trav->next ) { + if( trav->idx == idx ) + break; + prev = trav; + } + + if( trav != NULL ) {//if found + if( prev != NULL ) { + prev->next = trav->next; + } else { + list->first = trav->next; + } + if( trav == list->last ) + list->last = prev; + + free( trav ); + list->len--; + } +} + +LListH *llist_free( LListH *list ) { + if( list == NULL ) + return NULL; + + llist_reinit( list ); + + free( list ); + + return NULL; +} + +void llist_reinit( LListH *list ) { + LList *trav,*prev; + + for( trav = list->first, prev = NULL; trav != NULL; ) { + prev = trav; + trav = trav->next; + if( prev ) { + free( prev ); + prev = NULL; + } + } + + if( prev ) + free( prev ); + + list->first = NULL; + list->last = NULL; + list->len = 0; +} diff --git a/llist.h b/llist.h new file mode 100644 index 0000000..5f12696 --- /dev/null +++ b/llist.h @@ -0,0 +1,27 @@ +#ifndef LLIST_H +#include + +typedef struct llist_n { + int idx; + short amp; + struct llist_n *next; +} LList; + +typedef struct llist_h { + LList *first; + LList *last; + int len; +} LListH; + + +LListH *llist_init(); + +void llist_append( LListH *list, int idx, short amp ); + +void llist_remove_idx( LListH *list, int idx ); + +LListH *llist_free( LListH *list ); + +void llist_reinit( LListH *list ); + +#endif diff --git a/maemofiles/magread.desktop b/maemofiles/magread.desktop new file mode 100644 index 0000000..5208c15 --- /dev/null +++ b/maemofiles/magread.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Encoding=UTF-8 +Version=0.1 +Type=Application +Name=MagRead +Icon=magread +Exec=/usr/bin/magread/MagRead +X-HildonDesk-ShowInToolbar=true +X-Osso-Type=application/x-executable diff --git a/maemofiles/magread.png b/maemofiles/magread.png new file mode 100644 index 0000000000000000000000000000000000000000..f301ddf64d87192d6a1619228a98889f002ec800 GIT binary patch literal 265 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC#^NA%Cx&(BWL^R}Y)RhkE+;?|st1oPDQat#%bpO+U~x?d|b. + + Written by Jeffrey Malone + http://blog.tehinterweb.com +*/ +#ifndef MAGCARD_H +#define MAGCARD_H + +#include + +#include "mslib.h" + +class MagCard { + public: + QString charStream; + QString bitStream; + ms_dataType encoding; + bool swipeValid; + enum Type { + CARD_UNSET = 0, // 0000000 + CARD_UNKNOWN = 1,//0000001 + CARD_AAMVA = 2, // 0000010 + CARD_CC = 64, // 1000000 + CARD_AMEX = 68, // 1000100 + CARD_DISC = 72, // 1001000 + CARD_MC = 80, // 1010000 + CARD_VISA = 96 // 1100000 + }; + Q_DECLARE_FLAGS( Types, Type ); + Types type; + + QString accountNumber; + QString accountHolder; + QString accountIssuer; + QDate expirationDate; + QString miscData; + + + bool accountValid; + + QString aamvaIssuer; + QString aamvaIssuerName; + QString aamvaIssuerAbr; + QDate aamvaBirthday; + + MagCard() { + encoding = UNSET; + type = 0; + accountValid = false; + swipeValid = false; + } + +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( MagCard::Types ) + +#endif // MAGCARD_H diff --git a/magread.cpp b/magread.cpp new file mode 100644 index 0000000..71a7199 --- /dev/null +++ b/magread.cpp @@ -0,0 +1,277 @@ +/* + This file is part of MagRead. + + MagRead 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. + + MagRead 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 MagRead. If not, see . + + Written by Jeffrey Malone + http://blog.tehinterweb.com +*/ +#include "magread.h" + +#include + +MagRead::MagRead(QWidget *parent) : QMainWindow(parent) { + mainPage(); + + captureAudio = false; + partialRead = false; + + qRegisterMetaType( "MagCard" ); + + connect( &audioInput, SIGNAL( cardRead( const MagCard& ) ), this, SLOT( cardRead( const MagCard& ) ) ); + connect( &audioInput, SIGNAL( error( const QString& ) ), this, SLOT( audioInputError( const QString& ) ) ); +} + +void MagRead::maemoNotice( QString msg, int msec ) { + QMaemo5InformationBox infoBox; + + infoBox.information( 0, msg, msec ); + infoBox.show(); +} + +void MagRead::cardRead( const MagCard _card ) { + card = _card; + cardDetect.setCard( &card ); + + if( card.type & MagCard::CARD_CC && card.accountValid ) { + creditPage(); + } else if( card.type == MagCard::CARD_AAMVA ) { + aamvaPage(); + } else if( card.swipeValid ) { + miscPage(); + } else if( partialRead ) { + maemoNotice( "Show data from a partial read; may be incomplete/invalid!", 1000 ); + miscPage( true ); + } else { + maemoNotice( "Swipe Failed! Please Retry", 1000 ); + } +} + +void MagRead::audioInputError( const QString msg ) { + maemoNotice( msg, 5000 ); + captureAudio = false; +} + +/* Main Page */ +void MagRead::mainPage() { + QWidget *widget = new QWidget; + QVBoxLayout *layout = new QVBoxLayout( widget ); + + widget->setLayout( layout ); + + QLabel *label; + label = new QLabel( "MagRead" ); + layout->addWidget( label, 1, Qt::AlignHCenter ); + + QCheckBox *cbox = new QCheckBox( "Show Partial Data" ); + layout->addWidget( cbox ); + connect( cbox, SIGNAL( toggled( bool ) ), this, SLOT( togglePartialRead( bool ) ) ); + + QPushButton *button = new QPushButton( "Start" ); + layout->addWidget( button ); + connect( button, SIGNAL( clicked() ), this, SLOT( toggleRead() ) ); + + onMainPage = true; + + setCentralWidget( widget ); +} + +void MagRead::toggleRead() { + QPushButton *button = qobject_cast( QObject::sender() ); + + if( captureAudio ) { + audioInput.stop(); + if( onMainPage ) + button->setText( "Start" ); + else + button->setText( "Back to Main Page" ); + captureAudio = false; + } else if( onMainPage ) { + audioInput.start(); + button->setText( "Stop" ); + captureAudio = true; + } else { + mainPage(); + } + +} + +void MagRead::togglePartialRead( bool _partialRead ) { + partialRead = _partialRead; +} + +/* Credit Page */ +void MagRead::creditPage() { + maemoNotice( "Successfully Read Credit Card", 1000 ); + QWidget *widget = new QWidget; + QGridLayout *layout = new QGridLayout( widget ); + + onMainPage = false; + + widget->setLayout( layout ); + + QLabel *label; + + QString accountNumber = card.accountNumber; + if( card.type == MagCard::CARD_AMEX ) { + accountNumber.insert( 10, '\t' ); + accountNumber.insert( 4, '\t' ); + } else { + accountNumber.insert( 12, '\t' ); + accountNumber.insert( 8, '\t' ); + accountNumber.insert( 4, '\t' ); + } + + accountNumber.prepend( "
\n" ); + accountNumber.append( "
" ); + + if( !card.accountHolder.isEmpty() ) { + accountNumber.append( "\n
" ); + accountNumber.append( card.accountHolder ); + accountNumber.append( "
" ); + } + + label = new QLabel( accountNumber ); + layout->addWidget( label, 0, 0, 1, 2, Qt::AlignHCenter ); + + label = new QLabel( "Expiration Date" ); + layout->addWidget( label, 1, 0, 1, 1, Qt::AlignHCenter | Qt::AlignBottom ); + + label = new QLabel( "Issuer" ); + layout->addWidget( label, 1, 1, 1, 1, Qt::AlignHCenter | Qt::AlignBottom ); + + QString expDate = card.expirationDate.toString( "MM/yy" ); + if( card.expirationDate < QDate::currentDate() ) { + expDate.prepend( "" ); + expDate.append( "" ); + } + + label = new QLabel( QString( "%1" ).arg( expDate ) ); + layout->addWidget( label, 2, 0, 1, 1, Qt::AlignHCenter | Qt::AlignTop ); + + label = new QLabel( QString( "%1" ).arg( card.accountIssuer ) ); + layout->addWidget( label, 2, 1, 1, 1, Qt::AlignHCenter | Qt::AlignTop ); + + QPushButton *button = new QPushButton( "Stop" ); + layout->addWidget( button, 3, 0, 1, 2 ); + connect( button, SIGNAL( clicked() ), this, SLOT( toggleRead() ) ); + + setCentralWidget( widget ); +} + +void MagRead::aamvaPage() { + maemoNotice( "Successfully Read AAMVA Card", 1000 ); + QWidget *widget = new QWidget; + QGridLayout *layout = new QGridLayout( widget ); + + onMainPage = false; + + widget->setLayout( layout ); + + QLabel *label; + + label = new QLabel( "" + card.aamvaIssuerName + "" ); + layout->addWidget( label, 0, 0, 1, 2, Qt::AlignHCenter ); + + label = new QLabel( "" + card.accountNumber + "" ); + layout->addWidget( label, 1, 0, 1 ,2, Qt::AlignHCenter | Qt::AlignTop ); + layout->setRowStretch( 1, 1 ); + + /* Calculate the age */ + QDate curDate = QDate::currentDate(); + + /* FIXME a leap year can offset this by a day ... */ + int age = QDate::currentDate().year() - card.aamvaBirthday.year(); + if( card.aamvaBirthday.dayOfYear() > QDate::currentDate().dayOfYear() ) { + age--; + } + + QString ageStr = QString ( "Age %1 " ).arg( age ); + if( age < 18 ) { + ageStr.prepend( "" ); + ageStr.append( "" ); + } else if( age < 21 ) { + ageStr.prepend( "" ); + ageStr.append( "" ); + } + + label = new QLabel( ageStr ); + layout->addWidget( label, 2, 0, 1, 2, Qt::AlignHCenter ); + + label = new QLabel( "Expiration Date" ); + layout->addWidget( label, 3, 0, 1, 1, Qt::AlignHCenter ); + + label = new QLabel( "Date of Birth" ); + layout->addWidget( label, 3, 1, 1, 1, Qt::AlignHCenter ); + + QString expDate = card.expirationDate.toString( "MM/dd/yy" ); + expDate.prepend( "" ); + if( card.expirationDate < QDate::currentDate() ) { + expDate.prepend( "
" ); + expDate.append( "
\nEXPIRED
" ); + } + expDate.append( "
" ); + label = new QLabel( expDate ); + layout->addWidget( label, 4, 0, 1, 1, Qt::AlignHCenter ); + + + label = new QLabel( "" + card.aamvaBirthday.toString( "MM/dd/yy" ) + "" ); + layout->addWidget( label, 4, 1, 1, 1, Qt::AlignHCenter ); + + QPushButton *button = new QPushButton( "Stop" ); + layout->addWidget( button, 5, 0, 1, 2 ); + connect( button, SIGNAL( clicked() ), this, SLOT( toggleRead() ) ); + + setCentralWidget( widget ); +} + +/* Misc Page */ +void MagRead::miscPage( bool partial ) { + if( !partial ) + maemoNotice( "Successfully Read an Unknown Card", 1000 ); + + QWidget *widget = new QWidget; + QGridLayout *layout = new QGridLayout( widget ); + + onMainPage = false; + + widget->setLayout( layout ); + + QScrollArea *scroll = new QScrollArea; + + QString tmpStr = card.charStream; + if( tmpStr.contains( '%' ) ) { + tmpStr.replace( '^', "
\n[Field Separator]
\n" ); + } else { + tmpStr.replace( '=', "
\n[Field Separator]
\n" ); + } + tmpStr.replace( '|', "|" ); + + tmpStr.prepend( "
" ); + tmpStr.append( "
" ); + + QLabel *label = new QLabel( tmpStr ); + scroll->setWidget( label ); + scroll->setWidgetResizable( true ); + layout->addWidget( scroll, 0, 0, 1, 2, Qt::AlignHCenter ); + + label->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); + + QPushButton *button = new QPushButton( "Stop" ); + layout->addWidget( button, 1, 0, 1, 2 ); + connect( button, SIGNAL( clicked() ), this, SLOT( toggleRead() ) ); + + setCentralWidget( widget ); +} + diff --git a/magread.h b/magread.h new file mode 100644 index 0000000..bebc71a --- /dev/null +++ b/magread.h @@ -0,0 +1,68 @@ +/* + This file is part of MagRead. + + MagRead 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. + + MagRead 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 MagRead. If not, see . + + Written by Jeffrey Malone + http://blog.tehinterweb.com +*/ +#ifndef MAGREAD_H +#define MAGREAD_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "carddetect.h" +#include "audioinput.h" +#include "magcard.h" + + +class MagRead : public QMainWindow { + Q_OBJECT + + public: + MagRead( QWidget *parent = 0 ); + + private: + MagCard card; + CardDetect cardDetect; + AudioInput audioInput; + bool captureAudio; + bool partialRead; + bool onMainPage; + + void mainPage(); + void creditPage(); + void aamvaPage(); + void miscPage( bool partial = false ); + + private slots: + void cardRead( const MagCard _card ); + void maemoNotice( QString msg, int msec ); + void audioInputError( const QString msg ); + void toggleRead(); + void togglePartialRead( bool _partialRead ); +}; + +#endif // MAGREAD_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..505ba47 --- /dev/null +++ b/main.cpp @@ -0,0 +1,34 @@ +/* + This file is part of MagRead. + + MagRead 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. + + MagRead 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 MagRead. If not, see . + + Written by Jeffrey Malone + http://blog.tehinterweb.com +*/ +#include +#include "magread.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MagRead w; +#if defined(Q_WS_S60) + w.showMaximized(); +#else + w.show(); +#endif + + return a.exec(); +} diff --git a/mslib.c b/mslib.c new file mode 100644 index 0000000..c45f9f5 --- /dev/null +++ b/mslib.c @@ -0,0 +1,461 @@ +/* + * This file is part of mslib. + * mslib 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. + * + * mslib 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 nosebus. If not, see . + */ +#include "mslib.h" + +/* Data Create/Free functions */ +msData *_ms_create() { + msData *ms; + + ms = ( msData * ) calloc( sizeof( msData ), 1 ); + if ( !ms ) + return NULL; + ms->peakThreshold = PEAK_THRESHOLD; + ms->peakOffset = PEAK_OFFSET; + + return ms; +} + +msData *ms_create( const short *pcmData, int pcmDataLen ) { + msData *ms; + + ms = _ms_create(); + if( !ms ) + return NULL; + + ms->pcmData = ( short * )pcmData; + ms->pcmDataLen = pcmDataLen; + + return ms; +} + +msData *ms_free( msData *ms ) { + if( !ms ) + return NULL; + + if( ms->peakList ) + llist_free( ms->peakList ); + + if( ms->bitStream ) + free( ms->bitStream ); + + if( ms->charStream ) + free( ms->charStream ); + + free( ms ); + + return NULL; +} + +void ms_reinit( msData *ms ) { + if( !ms ) + return; + + if( ms->peakList ) { + llist_free( ms->peakList ); + ms->peakList = NULL; + } + + if( ms->bitStream ) { + free( ms->bitStream ); + ms->bitStream = NULL; + } + + if( ms->charStream ) { + free( ms->charStream ); + ms->charStream = NULL; + } +} + +/* Misc User Functions */ +void ms_set_peakThreshold( msData *ms, int peakThreshold ) { + if( !ms ) + return; + ms->peakThreshold = peakThreshold; +} + +void ms_set_peakOffset( msData *ms, int peakOffset ) { + if( !ms ) + return; + ms->peakOffset = peakOffset; +} + +const char *ms_get_bitStream( msData *ms ) { + if( !ms ) + return NULL; + return ms->bitStream; +} + +const char *ms_get_charStream( msData *ms ) { + if( !ms ) + return NULL; + return ms->charStream; +} + + +/* Finding Peaks */ +int ms_range( int a, int b1, int b2 ) { + if( ( a >= b1 && a <= b2 ) || ( a >= b2 && a <= b1 ) ) + return 1; + return 0; +} + +#define RANGE_OFFSET 2 +void ms_peaks_find( msData *ms ) { + int i; + short *pcmData, *pcmDataOffset; + + if( ms == NULL || ( ms->pcmData == NULL ) ) + return; + + if( ms->peakList == NULL ) + ms->peakList = llist_init(); + else + llist_reinit( ms->peakList ); + + pcmData = ms->pcmData + RANGE_OFFSET; + pcmDataOffset = pcmData + ms->peakOffset; + + for( i = ms->peakOffset + RANGE_OFFSET; i < ms->pcmDataLen; i++, pcmData++, pcmDataOffset++ ) { + if( abs( *pcmData ) > ms->peakThreshold && + ms_range( *pcmData, *( pcmDataOffset - RANGE_OFFSET ), *( pcmDataOffset + RANGE_OFFSET ) ) ) { + + llist_append( ms->peakList, i, ms->pcmData[ i ] ); + } + } +} + +void ms_peaks_find_walk( msData *ms ) { + int i; + short *pcmData; + + + if( ms == NULL || ( ms->pcmData == NULL ) ) + return; + + if( ms->peakList == NULL ) + ms->peakList = llist_init(); + else + llist_reinit( ms->peakList ); + + for( i = 0, pcmData = ms->pcmData; i+1 < ms->pcmDataLen; i++, pcmData++ ) { + if( abs( *pcmData ) > ms->peakThreshold ) { + if( abs( pcmData[ 1 ] ) < abs( *pcmData ) ) { + llist_append( ms->peakList, i, *pcmData ); + } + } + } +} + + +void ms_peaks_filter_group( msData *ms ) { + LList *trav; + LListH *groupList; + + int pos;//indicates pos/neg (not position) + + if( !ms || ms->peakList->len < 2 ) + return; + + pos = ( ms->peakList->first->amp > 0 ); + + groupList = llist_init(); + for( trav = ms->peakList->first; trav != NULL; trav = trav->next ) { + + if( ( trav->amp > 0 ) != pos ) { + pos = !pos; + _ms_peaks_filter_groupFind( ms, groupList ); + } + + llist_append( groupList, trav->idx, trav->amp ); + } + + if( groupList->len ) + _ms_peaks_filter_groupFind( ms, groupList ); + + groupList = llist_free( groupList ); +} + + + +LListH *_ms_peaks_filter_groupFind( msData *ms, LListH *groupList ) { + LList *trav; + struct { + int idx; + short amp; + } bigPeak; + + if( !ms || groupList == NULL || groupList->len < 2 ) + return NULL; + + bigPeak.idx = groupList->first->idx; + bigPeak.amp = abs( groupList->first->amp ); + + for( trav = groupList->first->next; trav != NULL; trav = trav->next ) { + if( abs( trav->amp ) > bigPeak.amp ) { + llist_remove_idx( ms->peakList, bigPeak.idx ); + bigPeak.idx = trav->idx; + bigPeak.amp = abs( trav->amp ); + } else { + llist_remove_idx( ms->peakList, trav->idx ); + } + } + + llist_reinit( groupList ); + return groupList; +} + + +/* Peak Decode functions */ +char _ms_closer( int *oneClock, int dif ) { + int oneDif = abs( *oneClock - dif ); + int zeroDif = abs( ( *oneClock * 2 ) - dif ); + char bit; + + if( oneDif < zeroDif ) { + *oneClock = dif; + bit = '1'; + } else { + *oneClock = dif / 2; + bit = '0'; + } + + return bit; +} + +void ms_decode_peaks( msData *ms ) { + LList *trav; + int clock, len; + int lastPeakidx; + char lastBit, curBit, bitStream[ MAX_BITSTREAM_LEN + 1 ]; + + if( !ms || ms->peakList == NULL || ms->peakList->len < 3 ) + return; + + if( ms->bitStream ) { + free( ms->bitStream ); + ms->bitStream = NULL; + } + + lastPeakidx = ms->peakList->first->next->idx; + clock = ( ms->peakList->first->next->next->idx - lastPeakidx ) / 2; + + len = 0; + lastBit = '\0'; + + for( trav = ms->peakList->first->next; trav != NULL && len < MAX_BITSTREAM_LEN; trav = trav->next ) { + curBit = _ms_closer( &clock, ( trav->idx - lastPeakidx ) ); + if( curBit == '0' ) { + bitStream[ len++ ] = curBit; + } else if( curBit == lastBit ) { + bitStream[ len++ ] = curBit; + curBit = '\0'; + } + + lastBit = curBit; + lastPeakidx = trav->idx; + } + + bitStream[ len ] = '\0'; + + ms->bitStream = ( char * ) malloc( sizeof( char ) * strlen( bitStream ) + 1 ); + strcpy( ms->bitStream, bitStream ); +} + +/* String Reverse Function */ +void strrev( char *str ) { + int f, l; + char tmp; + + for( f = 0, l = strlen( str ) - 1; l > f; f++, l-- ) { + tmp = str[ f ]; + str[ f ] = str[ l ]; + str[ l ] = tmp; + } +} + +/* Bit Decode functions */ +int ms_decode_typeDetect( msData *ms ) { + char *bitStream; + int loop = 2; + + if( !ms || !ms->bitStream ) + return 1; + + + do { + bitStream = strchr( ms->bitStream, '1' ); + if( bitStream == NULL ) + break; + + if( !strncmp( bitStream, ABA_SS, ABA_CHAR_LEN ) ) { + ms->dataType = ABA; + return 0; + } else if( !strncmp( bitStream, IATA_SS, IATA_CHAR_LEN ) ) { + ms->dataType = IATA; + return 0; + } + + strrev( ms->bitStream ); + loop--; + } while( loop ); + + + ms->dataType = UNKNOWN; + return 1; +} + + +int ms_decode_bits( msData *ms ) { + char *bitStream; + char charStream[ MAX_IATA_LEN + 1 ], curChar; + char LRC[ IATA_CHAR_LEN ] = { 0 }; + int bitStreamLen, i, x, len, validSwipe; + int maxLen, charLen, badChars; + + if( !ms || !ms->bitStream ) + return -1; + + if( ms_decode_typeDetect( ms ) ) + return -1; + + if( ms->dataType == ABA ) { + maxLen = MAX_ABA_LEN; + charLen = ABA_CHAR_LEN; + } else { + maxLen = MAX_IATA_LEN; + charLen = IATA_CHAR_LEN; + } + + if( ms->charStream ) { + free( ms->charStream ); + ms->charStream = NULL; + } + + validSwipe = 0; + + bitStream = strchr( ms->bitStream, '1' ); + if( bitStream == NULL ) // if stream contains no 1s, it's bad, just quit + return 1; + + bitStreamLen = strlen( bitStream ); + + /* Traverse the bitstream to decode all the bits into a charstream */ + curChar = '\0'; + badChars = 0; + for( i = 0, len = 0; ( i + charLen ) < bitStreamLen && len < maxLen && curChar != '?'; i += charLen, len++ ) { + curChar = _ms_decode_bits_char( bitStream + i, LRC, ms->dataType ); + charStream[ len ] = curChar; + if( curChar == BAD_CHAR ) + badChars++; // count the bad chars + } + charStream[ len ] = '\0'; + + /* Print warning about any detected bad characters */ + if( badChars ) { + fprintf( stderr, "ms_decode_bits(): Warning: %d chars failed parity check\n", badChars ); + validSwipe = 1; + } + + ms->charStream = ( char * ) malloc( sizeof( char ) * strlen( charStream ) + 1 ); + strcpy( ms->charStream, charStream ); + if( !ms->charStream ) + return 1; + + + /* Calculate the parity bit for the LRC */ + LRC[ ( charLen - 1 ) ] = 1; + for( x = 0; x < ( charLen - 1 ); x++ ) { + if( LRC[ x ] == 1 ) + LRC[ ( charLen - 1 ) ] = !LRC[ ( charLen - 1 ) ]; + LRC[ x ] += NUM_ASCII_OFFSET; + } + LRC[ ( charLen - 1 ) ] += NUM_ASCII_OFFSET; + + /* Verify the LRC */ + if( strncmp( LRC, bitStream + i, charLen ) ) { + fprintf( stderr, "ms_decode_bits(): Warning: LRC error decoding stream\n" ); + validSwipe = 1; + } + + return validSwipe; +} + +char _ms_decode_bits_char( char *bitStream, char *LRC, ms_dataType type ) { + int parity = 0, i; + char out; + int len; // char length not including parity + int offset; // offset to make it ASCII + + if( type == ABA ) { + len = ABA_CHAR_LEN - 1; + offset = ABA_ASCII_OFFSET; + } else { + len = IATA_CHAR_LEN - 1; + offset = IATA_ASCII_OFFSET; + } + + for( i = 0, out = 0; i < len; i++ ) { + out |= ( bitStream[ i ] - NUM_ASCII_OFFSET ) << i; // using OR to assign the bits into the char + if( bitStream[ i ] == '1' ) { + LRC[ i ] = !LRC[ i ]; // flip the bit in the LRC for all 1 bits in the char + parity++; // count the number of 1 bits for the parity bit + } + } + out += offset; + + if( ( parity & 1 ) == ( bitStream[ len ] - NUM_ASCII_OFFSET ) ) + out = BAD_CHAR; // return the error char if the calculated parity bit doesn't match the recorded one + + return out; +} + + +void ms_save( msData *ms, const char *filenamebase ) { + LList *trav; + FILE *output; + char filename[256]; + int fnlen; + + if( ms == NULL ) + return; + + fnlen = strlen( filenamebase ); + strncpy( filename, filenamebase, 256 ); + + if( ms->peakList != NULL ) { + strncpy( filename + fnlen, ".peaks", 256 - fnlen ); + + output = fopen( filename, "w" ); + if( output == NULL ) + return; + + for( trav = ms->peakList->first; trav != NULL; trav = trav->next ) { + fprintf( output, "%d %d\n", trav->idx, trav->amp ); + } + + fclose( output ); + } + + strncpy( filename + fnlen, ".pcm", 256 - fnlen ); + output = fopen( filename, "w" ); + if( output == NULL ) + return; + + fwrite( ms->pcmData, sizeof( short ), ms->pcmDataLen, output ); + + fclose( output ); +} + diff --git a/mslib.h b/mslib.h new file mode 100644 index 0000000..096e212 --- /dev/null +++ b/mslib.h @@ -0,0 +1,208 @@ +/* + * This file is part of mslib. + * mslib 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. + * + * mslib 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 nosebus. If not, see . + */ +#ifndef MSLIB_H +#define MSLIB_H + +#ifdef __cplusplus /* If this is a C++ compiler, use C linkage */ +extern "C" { +#endif + + +#include +#include +#include + +#include "llist.h" + +#define PEAK_THRESHOLD 500 +#define PEAK_OFFSET 3 + +#define MAX_BITSTREAM_LEN 1024 + +#define ABA_SS "11010" +#define MAX_ABA_LEN 41 +#define ABA_CHAR_LEN 5 +#define ABA_ASCII_OFFSET 48 + +#define IATA_SS "1010001" +#define MAX_IATA_LEN 83 // some reports say max len is 79, but AAMVA says 82 ... +#define IATA_CHAR_LEN 7 +#define IATA_ASCII_OFFSET 32 + +#define NUM_ASCII_OFFSET 48 + +/* This char will be put into the stream whenever the parity bit doesn't match. + * The default char, | (PIPE), is chosen because it does not appear in either + * of the two encoding schemes. */ +#define BAD_CHAR '|' + +typedef enum { + UNSET = 0, + IATA, + ABA, + UNKNOWN = 9 +} ms_dataType; + + +typedef struct { + int idx; + short amp; +} peakData; + +typedef struct { + /* PCM Data */ + short *pcmData; + int pcmDataLen; + + /* Peaks */ + LListH *peakList; + int peakThreshold; + int peakOffset; + + /* Decode */ + ms_dataType dataType; + char *bitStream; + char *charStream; +} msData; + + +/* Init Functions */ + +/* internal helper function, do not use */ +msData *_ms_create(); + +/* Create an msData object using the given 16bit array of PCM data + * pcmData - A 16bit signed array of PCM data. + * pcmDataLen - the number of elements in pcmData + * Returns an msData object on success and NULL on failure */ +msData *ms_create( const short *pcmData, int pcmDataLen ); + +/* Garbage Collection */ + +/* Frees all data allocated for the given msData objected + * Always returns NULL. + * This function will not free any data that mslib did not allocate itself */ +msData *ms_free( msData *ms ); + +void ms_reinit( msData *ms ); + +/* internal helper function, do not use */ +void ms_free_peakList( msData *ms ); + +/* Configuration functions */ + +/* Set the peakThreshold value. + * The value determines the minimum amplitude to be considered a peak + * You probably can stay with the default value */ +void ms_set_peakThreshold( msData *ms, int peakThreshold ); + +/* Set the peakOffset value. + * The value determines the offset to use when looking for intersects + * You probably can stay with the default value */ +void ms_set_peakOffset( msData *ms, int peakOffset ); + + +/* Get the bit stream. + * This would be the binary data that the peaks are decoded to. + * Can only be run after ms_decode_peaks(). Format is a NULL-terminated + * const char array. + * Returns NULL on error (such as if ms_decode_peaks() has not been run) */ +const char *ms_get_bitStream( msData *ms ); + +/* Get the char stream. + * This is the decoded binary data that is human readable. + * Can only be run after ms_decode_bits(). Format is a NULL-terminated + * const char array. + * Returns NULL on error (such as if ms_decode_bits() has not been run) */ +const char *ms_get_charStream( msData *ms ); + +/* Reverses a string in place */ +void strrev( char *str ); + + + +/* Find Peaks Functions */ + +/* internal helper function, do not use */ +int ms_range( int a, int b1, int b2 ); + +/* Finds the peaks in the stream. + * This should be run immediately after ms_create(). + * Compares the stream against an offset of itself and searches for intersects. + * Intersections are marked as peaks. + */ +void ms_peaks_find( msData *ms ); + +/* Finds the peaks in the stream. + * Marks the apex of any amplitude apex as a peak. + */ +void ms_peaks_find_walk( msData *ms ); + +/* Filters the list of peaks found using a grouping method based on signedness + * of the amplitude. + * This typically should be run after ms_peaks_find(). + */ +void ms_peaks_filter_group( msData *ms ); + +/* internal helper function, do not use */ +LListH *_ms_peaks_filter_groupFind( msData *ms, LListH *groupList ); + + +/* Peak Decoding Functions */ + +/* internal helper function, do not use */ +char _ms_closer( int *oneClock, int dif ); + +/* This decodes the peaks found through the ms_peaks*() functions. + * It should be run after them. + */ +void ms_decode_peaks( msData *ms ); + +/* Bit Decoding functions */ + +/* This determines the type of encoding used: ABA or IATA. + * At present, only ABA is supported (as found on track 2 of most cards). + * + * This is generally used as a helper function internally, but can be run + * directly. It will return 1 on error and 0 on success. + * Success means it was able to detect either ABA or IATA. */ +int ms_decode_typeDetect( msData *ms ); + +/* This decodes the bits into the charstream. + * It should be run after ms_decode_peaks(). + * The encoding is auto-detected using ms_decode_typeDetect() + * Returns -1 on error, 0 on success, and from the enum ms_dataType UNKNOWN (9) + * if the card format was not detected (de facto error) */ +int ms_decode_bits( msData *ms ); + +/* internal helper function, do not use */ +char _ms_decode_bits_char( char *bitStream, char *LRC, ms_dataType type ); + +/* Debug function. + * This saves the stream to the given filename with ".pcm" appended. + * It also saves the contents of the peakList to the filename with a ".peaks" + * extension. + * + * gnuplot is ideal for viewing the contents: + * plot "x.pcm" binary format="%int16" using l 1 w l, "x.peaks" + */ +void ms_save( msData *ms, const char *filename ); + +#ifdef __cplusplus /* If this is a C++ compiler, end C linkage */ +} +#endif + +#endif -- 1.7.9.5