--- /dev/null
+#-------------------------------------------------
+#
+# 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
+}
+
--- /dev/null
+/*
+ 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 <http://www.gnu.org/licenses/>.
+
+ Written by Jeffrey Malone <ieatlint@tehinterweb.com>
+ http://blog.tehinterweb.com
+*/
+#include "audioinput.h"
+
+#include <QDebug>
+
+#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();
+}
+
--- /dev/null
+/*
+ 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 <http://www.gnu.org/licenses/>.
+
+ Written by Jeffrey Malone <ieatlint@tehinterweb.com>
+ http://blog.tehinterweb.com
+*/
+#ifndef AUDIOINPUT_H
+#define AUDIOINPUT_H
+
+#include <QThread>
+#include <QVarLengthArray>
+#include <QDateTime>
+#include <pulse/pulseaudio.h>
+#include <pulse/simple.h>
+
+#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<qint16, 20000> pcmData;
+
+ MagCard card;
+
+ msData *ms;
+};
+
+#endif // AUDIOINPUT_H
--- /dev/null
+/*
+ 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 <http://www.gnu.org/licenses/>.
+
+ Written by Jeffrey Malone <ieatlint@tehinterweb.com>
+ 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 );
+}
--- /dev/null
+/*
+ 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 <http://www.gnu.org/licenses/>.
+
+ Written by Jeffrey Malone <ieatlint@tehinterweb.com>
+ http://blog.tehinterweb.com
+*/
+#ifndef CARDDETECT_H
+#define CARDDETECT_H
+
+#include <QMap>
+#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<QString,struct issuer> issuerList;
+
+};
+
+
+#endif // CARDDETECT_H
--- /dev/null
+magread (0.1.0) unstable; urgency=low
+
+ * Initial release
+
+ -- Jeffrey Malone <ieatlint@tehinterweb.com> Thu, 04 Nov 2010 06:10:14 -0700
--- /dev/null
+Source: magread
+Section: user/utilities
+Priority: extra
+Maintainer: Jeffrey Malone <ieatlint@tehinterweb.com>
+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==
+
--- /dev/null
+This package was debianized by Jeffrey Malone <ieatlint@tehinterweb.com> on
+Thu, 04 Nov 2010 06:10:14 -0700.
+
+Upstream Author(s):
+
+ Jeffrey Malone <ieatlint@tehinterweb.com>
+
+Copyright:
+
+ Jeffrey Malone <ieatlint@tehinterweb.com>
+
+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 <http://www.gnu.org/licenses/>.
+
+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 <ieatlint@tehinterweb.com> 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.
--- /dev/null
+#!/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
--- /dev/null
+#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;
+}
--- /dev/null
+#ifndef LLIST_H
+#include <stdlib.h>
+
+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
--- /dev/null
+[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
--- /dev/null
+/*
+ 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 <http://www.gnu.org/licenses/>.
+
+ Written by Jeffrey Malone <ieatlint@tehinterweb.com>
+ http://blog.tehinterweb.com
+*/
+#ifndef MAGCARD_H
+#define MAGCARD_H
+
+#include <QDate>
+
+#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
--- /dev/null
+/*
+ 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 <http://www.gnu.org/licenses/>.
+
+ Written by Jeffrey Malone <ieatlint@tehinterweb.com>
+ http://blog.tehinterweb.com
+*/
+#include "magread.h"
+
+#include <QDebug>
+
+MagRead::MagRead(QWidget *parent) : QMainWindow(parent) {
+ mainPage();
+
+ captureAudio = false;
+ partialRead = false;
+
+ qRegisterMetaType<MagCard>( "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( "<span style=\"font-size:48pt;\">MagRead</span>" );
+ 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<QPushButton *>( 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( "<span style=\"font-size:48pt;\"><br>\n" );
+ accountNumber.append( "</span>" );
+
+ if( !card.accountHolder.isEmpty() ) {
+ accountNumber.append( "\n<span style=\"font-size:36pt;\"><div align=center>" );
+ accountNumber.append( card.accountHolder );
+ accountNumber.append( "</div></span>" );
+ }
+
+ label = new QLabel( accountNumber );
+ layout->addWidget( label, 0, 0, 1, 2, Qt::AlignHCenter );
+
+ label = new QLabel( "<span style=\"font-size:24pt;\">Expiration Date</span>" );
+ layout->addWidget( label, 1, 0, 1, 1, Qt::AlignHCenter | Qt::AlignBottom );
+
+ label = new QLabel( "<span style=\"font-size:24pt;\">Issuer</span>" );
+ 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( "<font color=\"red\">" );
+ expDate.append( "</font>" );
+ }
+
+ label = new QLabel( QString( "<span style=\"font-size:32pt;\">%1</span>" ).arg( expDate ) );
+ layout->addWidget( label, 2, 0, 1, 1, Qt::AlignHCenter | Qt::AlignTop );
+
+ label = new QLabel( QString( "<span style=\"font-size:32pt;\">%1</span>" ).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( "<span style=\"font-size:36pt;\">" + card.aamvaIssuerName + "</span>" );
+ layout->addWidget( label, 0, 0, 1, 2, Qt::AlignHCenter );
+
+ label = new QLabel( "<span style=\"font-size:32pt;\">" + card.accountNumber + "</span>" );
+ 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 ( "<span style=\"font-size:32pt;\">Age %1 </span>" ).arg( age );
+ if( age < 18 ) {
+ ageStr.prepend( "<font color=\"red\">" );
+ ageStr.append( "</font>" );
+ } else if( age < 21 ) {
+ ageStr.prepend( "<font color=\"yellow\">" );
+ ageStr.append( "</font>" );
+ }
+
+ label = new QLabel( ageStr );
+ layout->addWidget( label, 2, 0, 1, 2, Qt::AlignHCenter );
+
+ label = new QLabel( "<span style=\"font-size:24pt;\">Expiration Date</font>" );
+ layout->addWidget( label, 3, 0, 1, 1, Qt::AlignHCenter );
+
+ label = new QLabel( "<span style=\"font-size:24pt;\">Date of Birth</span>" );
+ layout->addWidget( label, 3, 1, 1, 1, Qt::AlignHCenter );
+
+ QString expDate = card.expirationDate.toString( "MM/dd/yy" );
+ expDate.prepend( "<span style=\"font-size:24pt;\">" );
+ if( card.expirationDate < QDate::currentDate() ) {
+ expDate.prepend( "<font color=\"red\"><div align=\"center\">" );
+ expDate.append( "<br>\nEXPIRED</font></div>" );
+ }
+ expDate.append( "</span>" );
+ label = new QLabel( expDate );
+ layout->addWidget( label, 4, 0, 1, 1, Qt::AlignHCenter );
+
+
+ label = new QLabel( "<span style=\"font-size:24pt;\">" + card.aamvaBirthday.toString( "MM/dd/yy" ) + "</span>" );
+ 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( '^', "<br>\n[Field Separator]<br>\n" );
+ } else {
+ tmpStr.replace( '=', "<br>\n[Field Separator]<br>\n" );
+ }
+ tmpStr.replace( '|', "<font color=\"red\">|</font>" );
+
+ tmpStr.prepend( "<span style=\"font-size:36pt;\"><div align=\"center\">" );
+ tmpStr.append( "</div></span>" );
+
+ 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 );
+}
+
--- /dev/null
+/*
+ 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 <http://www.gnu.org/licenses/>.
+
+ Written by Jeffrey Malone <ieatlint@tehinterweb.com>
+ http://blog.tehinterweb.com
+*/
+#ifndef MAGREAD_H
+#define MAGREAD_H
+
+#include <QtGui/QMainWindow>
+#include <QGridLayout>
+#include <QWidget>
+#include <QLabel>
+#include <QPushButton>
+#include <QDate>
+#include <QCheckBox>
+#include <QScrollArea>
+
+#include <QMetaType>
+
+#include <QMaemo5InformationBox>
+
+#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
--- /dev/null
+/*
+ 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 <http://www.gnu.org/licenses/>.
+
+ Written by Jeffrey Malone <ieatlint@tehinterweb.com>
+ http://blog.tehinterweb.com
+*/
+#include <QtGui/QApplication>
+#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();
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+#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 );
+}
+
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+#ifndef MSLIB_H
+#define MSLIB_H
+
+#ifdef __cplusplus /* If this is a C++ compiler, use C linkage */
+extern "C" {
+#endif
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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