Skip to content

Instantly share code, notes, and snippets.

@sebastiengoddard
Created March 31, 2016 16:38
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sebastiengoddard/9c727dd7bb1ef30651437e3d638f83bd to your computer and use it in GitHub Desktop.
Save sebastiengoddard/9c727dd7bb1ef30651437e3d638f83bd to your computer and use it in GitHub Desktop.
Boarding pass barcode decoder
#!/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
print 'Leg %d:' % leg
pos, saved = print_and_save(bc, pos, flight_mandatory_fields, indent=' ')
varsize = saved[6]
if varsize:
endpos = pos+varsize
print
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:
print
pos, _ = print_and_save(bc, pos, flight_conditional_unique_body_fields, stop=pos+ussize, indent=' ')
print
print " Conditional repeated:"
pos, saved = print_and_save(bc, pos, flight_conditional_repeated_header_fields, indent=' ')
rssize = saved[17]
if rssize:
print
pos, _ = print_and_save(bc, pos, flight_conditional_repeated_body_fields, stop=pos+rssize, indent=' ')
print
print " Conditional coda:"
pos, _ = print_and_save(bc, pos, flight_conditional_coda_fields, stop=endpos, indent=' ')
if pos < len(bc):
print
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 : ])
@sebastiengoddard
Copy link
Author

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:

$ echo -n "M1TESSAR/MATTHEW D    EQR2L8Q DENLAXWN 1420 180T017A0017 125>30B0WW0179BWN 0E             0T0101L^460MEQCIC4E6a7 GJNwAfTHnyxgY3VQdsPORVOuPZifjf51WrzqAiA3Zp59 Vw5M1YOUc4snjSh86yeyVK0LxthZdoZBBT/2A==" | ./bcbp_decode.py

Header:
    [000    ] 001: Format Code: 'M'
    [001    ] 005: Number of Legs Encoded: 1
    [002-021] 011: Passenger Name: 'TESSAR/MATTHEW D    '
    [022    ] 253: Electronic Ticket Indicator: 'E'

Leg 1:
    [023-029] 007: Operating carrier PNR Code: 'QR2L8Q '
    [030-032] 026: From City Airport Code: 'DEN'
    [033-035] 038: To City Airport Code: 'LAX'
    [036-038] 042: Operating carrier Designator: 'WN '
    [039-043] 043: Flight Number: '1420 '
    [044-046] 046: Date of Flight (Julian Date): (6, 29)
    [047    ] 071: Compartment Code: 'T'
    [048-051] 104: Seat Number: '017A'
    [052-056] 107: Check-In Sequence Number: '0017 '
    [057    ] 113: Passenger Status: '1'
    [058-059] 006: Field size of following variable size field: 37

    Conditional unique:
        [060    ] 008: Beginning of version number: '>'
        [061    ] 009: Version number: 3
        [062-063] 010: Field size of following structured message - unique: 11

        [064    ] 015: Passenger Description: '0'
        [065    ] 012: Source of check-in: 'W'
        [066    ] 014: Source of Boarding Pass Issuance: 'W'
        [067-070] 022: Date of Issue of Boarding Pass (Julian Date): datetime.date(2010, 6, 28)
        [071    ] 016: Document Type: 'B'
        [072-074] 021: Airline Designator of boarding pass issuer: 'WN '

    Conditional repeated:
        [075-076] 017: Field size of following structured message - repeated: 14

        [077-079] 142: Airline Numeric Code: '   '
        [080-089] 143: Document Form/Serial Number: '          '
        [090    ] 018: Selectee indicator: '0'

    Conditional coda:
        [091-096] 004: For individual airline use: 'T0101L'

    Security:
        [097    ] 025: Beginning of Security Data: '^'
        [098    ] 028: Type of Security Data: '4'
        [099-100] 029: Length of Security Data: 96
        [101-196] 030: Security Data: 304402202e04e9aec624dc007d31e7cb1818dd541db0f39154eb8f6627e37f9d56af3a80880dd9a79f55c3933560e51ce2c9e34a1f3ac9ec952b42f1b6165da190414ffd80

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment