Initial push to maemo garage. This is version 0.1.0.
authorJeffrey Malone <ieatlint@tehinterweb.com>
Fri, 5 Nov 2010 17:25:47 +0000 (10:25 -0700)
committerJeffrey Malone <ieatlint@tehinterweb.com>
Fri, 5 Nov 2010 17:25:47 +0000 (10:25 -0700)
21 files changed:
MagRead.pro [new file with mode: 0644]
audioinput.cpp [new file with mode: 0644]
audioinput.h [new file with mode: 0644]
carddetect.cpp [new file with mode: 0644]
carddetect.h [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/optify [new file with mode: 0644]
debian/rules [new file with mode: 0755]
llist.c [new file with mode: 0644]
llist.h [new file with mode: 0644]
maemofiles/magread.desktop [new file with mode: 0644]
maemofiles/magread.png [new file with mode: 0644]
magcard.h [new file with mode: 0644]
magread.cpp [new file with mode: 0644]
magread.h [new file with mode: 0644]
main.cpp [new file with mode: 0644]
mslib.c [new file with mode: 0644]
mslib.h [new file with mode: 0644]

diff --git a/MagRead.pro b/MagRead.pro
new file mode 100644 (file)
index 0000000..812f365
--- /dev/null
@@ -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 (file)
index 0000000..33b748c
--- /dev/null
@@ -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 <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();
+}
+
diff --git a/audioinput.h b/audioinput.h
new file mode 100644 (file)
index 0000000..1d35e10
--- /dev/null
@@ -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 <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
diff --git a/carddetect.cpp b/carddetect.cpp
new file mode 100644 (file)
index 0000000..58473a4
--- /dev/null
@@ -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 <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 );
+}
diff --git a/carddetect.h b/carddetect.h
new file mode 100644 (file)
index 0000000..9abfe1f
--- /dev/null
@@ -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 <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
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..37ea9ba
--- /dev/null
@@ -0,0 +1,5 @@
+magread (0.1.0) unstable; urgency=low
+
+  * Initial release
+
+ -- Jeffrey Malone <ieatlint@tehinterweb.com>  Thu, 04 Nov 2010 06:10:14 -0700
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..7ed6ff8
--- /dev/null
@@ -0,0 +1 @@
+5
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..6b607be
--- /dev/null
@@ -0,0 +1,26 @@
+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==
+
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..6212357
--- /dev/null
@@ -0,0 +1,35 @@
+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.
diff --git a/debian/optify b/debian/optify
new file mode 100644 (file)
index 0000000..865faf1
--- /dev/null
@@ -0,0 +1 @@
+auto
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..c9b2876
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..5f12696
--- /dev/null
+++ b/llist.h
@@ -0,0 +1,27 @@
+#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
diff --git a/maemofiles/magread.desktop b/maemofiles/magread.desktop
new file mode 100644 (file)
index 0000000..5208c15
--- /dev/null
@@ -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 (file)
index 0000000..f301ddf
Binary files /dev/null and b/maemofiles/magread.png differ
diff --git a/magcard.h b/magcard.h
new file mode 100644 (file)
index 0000000..cfa4d37
--- /dev/null
+++ b/magcard.h
@@ -0,0 +1,71 @@
+/*
+    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
diff --git a/magread.cpp b/magread.cpp
new file mode 100644 (file)
index 0000000..71a7199
--- /dev/null
@@ -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 <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 );
+}
+
diff --git a/magread.h b/magread.h
new file mode 100644 (file)
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 <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
diff --git a/main.cpp b/main.cpp
new file mode 100644 (file)
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 <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();
+}
diff --git a/mslib.c b/mslib.c
new file mode 100644 (file)
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 <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 );
+}
+
diff --git a/mslib.h b/mslib.h
new file mode 100644 (file)
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 <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