Skip to content

Instantly share code, notes, and snippets.

@ic32k
Forked from sebastiengoddard/bcbp_decode.py
Created December 6, 2025 22:20
Show Gist options
  • Select an option

  • Save ic32k/a52952511a27c4742abd193d6d6f4a74 to your computer and use it in GitHub Desktop.

Select an option

Save ic32k/a52952511a27c4742abd193d6d6f4a74 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 : ])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment