Created
March 31, 2016 16:38
-
-
Save sebastiengoddard/9c727dd7bb1ef30651437e3d638f83bd to your computer and use it in GitHub Desktop.
Boarding pass barcode decoder
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python2 | |
# based on bcbp_implementation_guidev4_jun2009.pdf | |
# and http://www.airlineinfo.com/ostpdf88/98.pdf | |
import sys | |
from datetime import date, timedelta | |
from binascii import hexlify, a2b_base64 | |
from collections import OrderedDict as odict | |
today = date.today() | |
def Fixed(val): | |
def checker(encoded): | |
if encoded != val: | |
raise RuntimeError('expected fixed value %s but got %s' % (val, encoded)) | |
return val | |
return checker | |
def Decimal(val): | |
return int(val, 10) | |
def Hex(val): | |
return int(val, 16) | |
def YearAndDayOfYear(val): | |
assert len(val)==4 | |
if val==' ': | |
return None | |
else: | |
y0, doy = int(val[0], 10), int(val[1:], 10) | |
d = today.replace(year=10*(today.year//10)+y0, month=1, day=1) + timedelta(days=doy-1) | |
#if date seems far in the future it must be off by 10 years | |
if d > today+timedelta(days=1): | |
d = d.replace(year=d.year-10) | |
return d | |
# return y0, doy | |
def DayOfYear(val): | |
doy = int(val,10) | |
#don't have year so guess not leap year | |
d = date(year=2015, month=1, day=1) + timedelta(days=doy-1) | |
return d.month, d.day | |
# return doy | |
class hexbytes(bytes): | |
def __repr__(self): | |
return hexlify(self) | |
def Base64(val): | |
return hexbytes(a2b_base64(val)) | |
header_fields = [ | |
dict(num=1, name='Format Code', size=1, unique='U', decode=Fixed('M')), | |
dict(num=5, name='Number of Legs Encoded', size=1, unique='U', decode=Decimal), | |
dict(num=11, name='Passenger Name', size=20, unique='U'), | |
dict(num=253, name='Electronic Ticket Indicator', size=1, unique='U'), | |
] | |
flight_mandatory_fields = [ | |
dict(num=7, name='Operating carrier PNR Code', size=7, unique='R'), | |
dict(num=26, name='From City Airport Code', size=3, unique='R'), | |
dict(num=38, name='To City Airport Code', size=3, unique='R'), | |
dict(num=42, name='Operating carrier Designator', size=3, unique='R'), | |
dict(num=43, name='Flight Number', size=5, unique='R'), | |
dict(num=46, name='Date of Flight (Julian Date)', size=3, unique='R', decode=DayOfYear), | |
dict(num=71, name='Compartment Code', size=1, unique='R'), | |
dict(num=104, name='Seat Number', size=4, unique='R'), | |
dict(num=107, name='Check-In Sequence Number', size=5, unique='R'), | |
dict(num=113, name='Passenger Status', size=1, unique='R'), | |
dict(num=6, name='Field size of following variable size field', size=2, unique='R', decode=Hex), | |
] | |
flight_conditional_unique_header_fields = [ | |
dict(num=8, name='Beginning of version number', size=1, unique='U', decode=Fixed('>')), | |
dict(num=9, name='Version number', size=1, unique='U', decode=Decimal), | |
dict(num=10, name='Field size of following structured message - unique', size=2, unique='U', decode=Hex), | |
] | |
flight_conditional_unique_body_fields = [ | |
dict(num=15, name='Passenger Description', size=1, unique='U'), | |
dict(num=12, name='Source of check-in', size=1, unique='U'), | |
dict(num=14, name='Source of Boarding Pass Issuance', size=1, unique='U'), | |
dict(num=22, name='Date of Issue of Boarding Pass (Julian Date)', size=4, unique='U', decode=YearAndDayOfYear), | |
dict(num=16, name='Document Type', size=1, unique='U'), | |
dict(num=21, name='Airline Designator of boarding pass issuer', size=3, unique='U'), | |
dict(num=23, name='Baggage Tag Licence Plate Number (s)', size=13, unique='U'), | |
] | |
flight_conditional_repeated_header_fields = [ | |
dict(num=17, name='Field size of following structured message - repeated', size=2, unique='R', decode=Hex), | |
] | |
flight_conditional_repeated_body_fields = [ | |
dict(num=142, name='Airline Numeric Code', size=3, unique='R'), | |
dict(num=143, name='Document Form/Serial Number', size=10, unique='R'), | |
dict(num=18, name='Selectee indicator', size=1, unique='R'), | |
dict(num=108, name='International Documentation Verification', size=1, unique='R'), | |
dict(num=19, name='Marketing carrier designator', size=3, unique='R'), | |
dict(num=20, name='Frequent Flyer Airline Designator', size=3, unique='R'), | |
dict(num=236, name='Frequent Flyer Number', size=16, unique='R'), | |
dict(num=89, name='ID/AD Indicator', size=1, unique='R'), | |
dict(num=118, name='Free Baggage Allowance', size=3, unique='R'), | |
] | |
flight_conditional_coda_fields = [ | |
dict(num=4, name='For individual airline use', size=None, unique='R'), | |
] | |
security_header_fields = [ | |
dict(num=25, name='Beginning of Security Data', size=1, unique='U', decode=Fixed('^')), | |
dict(num=28, name='Type of Security Data', size=1, unique='U'), | |
dict(num=29, name='Length of Security Data', size=2, unique='U', decode=Hex), | |
] | |
security_coda_fields = [ | |
dict(num=30, name='Security Data', size=None, unique='U', decode=Base64), | |
] | |
def print_and_save(bc, pos, iter_fields, stop=None, indent=''): | |
saved = odict() | |
if stop is None: | |
stop = len(bc) | |
for f in iter_fields: | |
if f['size'] is not None: | |
end_pos = pos+f['size'] | |
assert end_pos <= stop | |
else: | |
end_pos = stop | |
encoded = bc[pos : end_pos] | |
saved[f['num']] = decoded = f['decode'](encoded) if 'decode' in f else encoded | |
span = '[%03d-%03d]' % (pos, end_pos-1) if end_pos-pos>1 else '[ %03d ]' % pos | |
print '%s%s %03d: %s: %s' % (indent, span, f['num'], f['name'], repr(decoded)) | |
pos = end_pos | |
if pos==stop: | |
break | |
return end_pos, saved | |
bc = sys.stdin.read() | |
print 'Header:' | |
pos, saved = print_and_save(bc, 0, header_fields, indent=' ') | |
nlegs = saved[5] | |
for leg in range(1, nlegs+1): | |
print 'Leg %d:' % leg | |
pos, saved = print_and_save(bc, pos, flight_mandatory_fields, indent=' ') | |
varsize = saved[6] | |
if varsize: | |
endpos = pos+varsize | |
if leg==1: | |
print " Conditional unique:" | |
pos, saved = print_and_save(bc, pos, flight_conditional_unique_header_fields, stop=endpos, indent=' ') | |
uver, ussize = saved[9], saved[10] | |
if ussize: | |
pos, _ = print_and_save(bc, pos, flight_conditional_unique_body_fields, stop=pos+ussize, indent=' ') | |
print " Conditional repeated:" | |
pos, saved = print_and_save(bc, pos, flight_conditional_repeated_header_fields, indent=' ') | |
rssize = saved[17] | |
if rssize: | |
pos, _ = print_and_save(bc, pos, flight_conditional_repeated_body_fields, stop=pos+rssize, indent=' ') | |
print " Conditional coda:" | |
pos, _ = print_and_save(bc, pos, flight_conditional_coda_fields, stop=endpos, indent=' ') | |
if pos < len(bc): | |
print " Security:" | |
pos, saved = print_and_save(bc, pos, security_header_fields, indent=' ') | |
sdsize = saved[29] | |
if sdsize: | |
pos, _ = print_and_save(bc, pos, security_coda_fields, stop=pos+sdsize, indent=' ') | |
if pos < len(bc): | |
print "Leftover bytes at end: %s" % repr(bc[pos : ]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This script analyzes the text of a barcode from a boarding pass. Paper boarding passes usually PDF417 and mobile boarding passes usually Aztec Code. You can use online barcode decoders or get the text directly from .pkpass mobile pass file.
Pass the barcode string exactly to the script as standard input. Dates are approximative because the year is not stored.
Here is the output from a 2010 example barcode I found online: