#!/usr/bin/perl # # Display MetroCard Raw And Parsed Data # Version 0.01 # Copyright (c) 2004-2005 Joseph Battaglia # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # # # Notes on input: # A line that begins with a '#' is not processed and not printed # A line that begins with a '%' is printed as a comment # $card_type = 0; #FIXME: this should be set by track 3, but since I only have # a single-track reader... $typesfile = 'types.txt'; $lidfile = 'lids.txt'; # track 1-2 field names @T12_FIELDS = ( 'Time', 'Sub-Type', 'Time', 'Date', 'Times Used', 'Expiration', 'Transfer', 'Last ID', 'Value', 'Purch ID', 'Unknown' ); # track 3 field names @T3_FIELDS = ( 'Type', 'Unknown', 'Expiration', 'Unkonwn', 'Unknown', 'Unknown', 'Serial', 'Unused', 'Unkonwn', 'Constant' ); # generate regexps for track 1-2 @t12_lens = (2, 6, 6, 10, 6, 10, 1, 15, 16, 16); # lengths of track 1-2 fields $t12_re1 = '11010111'; # start sentinel $t12_re1 .= join('', map("([01]{$_})", @t12_lens)); # track 1-2 fields $t12_re2 .= $t12_re1 . '(.*)' . $t12_re1; # regexp for dual records # generate regexp for track 3 @t3_lens = (4, 4, 12, 4, 8, 8, 80, 16, 16); # lengths of track 3 fields $t3_re = '000011000111'; # start sentinel $t3_re .= join('', map("([01]{$_})", @t3_lens)); # track 3 fields $t3_re .= '0010010100110010011010010110010101001100101001'; # end sentinel $t3_re .= '0100110011010101001101001010100110100101011010'; # lookup card type/subtype in typesfile and return its name sub lookup_type($$) { ($in_type, $in_subtype) = @_; # read arguments open(FH, $typesfile) or die "Can't open $typesfile: $!"; # open typesfile while () { # loop through each record ($type, $subtype, $name) = split(/:/); # split fields if (($type eq $in_type) and ($subtype eq $in_subtype)) { # look for match chomp($name); # remove newline return $name; # return name } } return 'UNKNOWN'; # could not find type/subtype; return 'UNKNOWN' } # lookup last id in lidfile and return its name sub lookup_lid($) { ($in_value) = @_; # read arguments open(FH, $lidfile) or die "Can't open $lidfile: $!"; # open lidfile while () { # loop through each record ($value, $name) = split(/:/); # split fields if (($in_value eq $value)) { # look for match chomp($name); # remove newline return $name; # return name } } return 'UNKNOWN'; # could not find lid; return 'UNKNOWN' } # print header sub print_header($) { ($title) = @_; # read arguments print("$title\n"); # print title print("Field Hex Decimal Parsed\n"); # print header print("--------------- ---------- ---------- ------\n"); } # print field as hex and decimal and return decimal value sub print_field($$$) { ($track, $field, $value) = @_; # read arguments $tvar = 'T' . $track . '_FIELDS'; # create variable name $value = oct('0b' . $value); # convert to decimal printf('%2d: %-11s ', $field, $$tvar[$field - 1]); printf('%10X %10d ', $value, $value); # print base 10/16 values return $value; # return decimal value } # parse field sub parse_field($$$) { ($track, $field, $value) = @_; # read arguments $decvalue = oct('0b' . $value); if ($track == 12) { # track 1-2 fields if ($field == 1 or $field == 3) { # time if ($card_type == 0) { $time = $decvalue * 6; $hr = int($time / 60) - 1; $min = $time % 60; printf("%.2d:%.2d", $hr, $min); } } if ($field == 2) { # subtype print(lookup_type($card_type, $decvalue)); } if ($field == 5) { # times used print($decvalue); } if ($field == 7) { # transfer print(($decvalue == 1) ? "YES" : "NO"); } if ($field == 8) { # last id print(lookup_lid($decvalue)); } if ($field == 9) { # value $dollars = int($decvalue / 100); $cents = $decvalue % 100; printf("\$%d.%.2d", $dollars, $cents); } } if ($track == 3) { # track 3 fields if ($field == 1) { # type print(lookup_type('M', $decvalue)); $card_type = $decvalue; } if ($card_type == 0 and $field == 3) { # expiration $day = 30; if (!$decvalue % 2) { $decvalue++; } $decvalue /= 2; my $year = int ($decvalue / 12) + 1992; my $month = $decvalue % 12; if ($month > 1) { $month--; } else { $year--; $month = 12; } if ($month < 8) { if ($month % 2) { $day = "31"; } } else { if (!($month % 2)) { $day = "31"; } } if ($month == 2) { if (!($year % 4)) { $day = 29; } else { $day = 28; } } print("$month/$day/$year"); } if ($field == 7) { # serial printf("%.10d", $decvalue); } } print("\n"); } # main loop while () { if (/^%(.*)/) { # process printed comments print("### $1\n"); # print next; # process next input line } if (/^#/) { # process ignored comments next; # process next input line } # track 3 if (/$t3_re/ or reverse =~ /$t3_re/) { print_header 'Track 3 Record'; for ($i = 1; $i <= ($#t3_lens + 2); $i++) { $field = $i; # set field print_field(3, $field, $$i); # print each field parse_field(3, $field, $$i); # parse each field } print "\n"; } # track 1-2 dual records if (/$t12_re2/ or reverse =~ /$t12_re2/) { # regexp print_header 'Track 1-2 Record 1'; # print header for ($i = 1; $i <= ($#t12_lens + 2); $i++) { $field = $i; # set field print_field(12, $field, $$i); # print/parse each field if ($field == 1 or $field == 3) { # time field is split in two parse_field(12, $field, $1 . $3); # combine time fields } else { # everything else is normal parse_field(12, $field, $$i); # parse each field } } print "\n"; print_header 'Track 1-2 Record 2'; # print header for ($i = ($#t12_lens + 3); $i <= (($#t12_lens * 2) + 4); $i++) { $field = $i - ($#t12_lens + 2); # set field print_field(12, $field, $$i); # print/parse each field if ($field == 1 or $field == 3) { # time field is split in two $timevar1 = ($#t12_lens + 3); $timevar2 = ($#t12_lens + 5); parse_field(12, $field, $$timevar1 . $$timevar2); # combine time fields } else { # everything else is normal parse_field(12, $field, $$i); # parse each field } } print "\n"; next; # process next input line } # track 1-2 single record if (/$t12_re1/ or reverse =~ /$t12_re1/) { # regexp print_header 'Track 1-2 (Read Error)'; # print header for ($i = 1; $i <= ($#t12_lens + 2); $i++) { $field = $i; # set field print_field(12, $field, $$i); # print each field if ($field == 1 or $field == 3) { # time field is split in two parse_field(12, $field, $1 . $3); # combine time fields } else { # everything else is normal parse_field(12, $field, $$i); # parse each field } } print "\n"; next; # process next input line } }