# Decode SMS messages in PDU mode -- Adam Sampson # Written for Deeps' IRC bot... # # Rubbish spec at: http://www.usbdeveloper.com/GSMPage/gsmpage.htm#SMS # More useful information here: http://www.dreamfabric.com/sms/ # The real spec, best of all: # http://www.allsoft.co.za/downloads/getfile.php?filename=GSM_03.40_6.0.0.pdf import sys def tobinary(n): s = "" for i in range(8): s = ("%1d" % (n & 1)) + s n >>= 1 return s def bcd_decode(bs): s = "".join(["%1x%1x" % (b & 0xF, b >> 4) for b in bs]) if s[-1] == "f": s = s[:-1] return s def timestamp_decode(bs): if len(bs) != 7: return "timestamp-length-not-7" bs = [((n & 0xf) * 10) + (n >> 4) for n in bs] if bs[0] >= 90: # I don't know if this is the right cut-off point... year = 1900 + bs[0] else: year = 2000 + bs[0] timezone = bs[6] if timezone > 0x80: zone = "-%.2f" % ((timezone - 0x80) / 4,) else: zone = "+%.2f" % (timezone / 4,) return "%04d-%02d-%02d %02d:%02d:%02d GMT%s" % (year, bs[1], bs[2], bs[3], bs[4], bs[5], zone) def unpack_sevenbit(bs, chop = 0): # Unpack 7-bit characters msgbytes = [] + bs msgbytes.reverse() asbinary = "".join(map(tobinary, msgbytes)) if chop != 0: asbinary = asbinary[:-chop] chars = [] while len(asbinary) >= 7: chars.append(int(asbinary[-7:], 2)) asbinary = asbinary[:-7] return "".join(map(chr, chars)) def number_decode(bs): num_type = bs[0] bs = bs[1:] if num_type == 0x91: return "+" + bcd_decode(bs) elif num_type == 0xD0: return unpack_sevenbit(bs) else: return "unknown-number-type:%02x:" % (num_type,) + "".join(["%02x" % (n,) for n in bs]) # Should probably do some sort of period clearing-out... fragment_cache = {} def decode_pdu(s, handle_message): print "Message:", s bytes = [int(s[i:i+2], 16) for i in range(0, len(s), 2)] # Parse the SMSC header smsc_len = bytes[0] print "SMSC address:", number_decode(bytes[1:1 + smsc_len]) pdu_pos = smsc_len + 1 # Parse the PDU first octet first = bytes[pdu_pos] tp_mti = first & 0x03 print "TP-MTI:", tp_mti if tp_mti != 0: print "Not an SMS-DELIVER message -- ignoring" return print "TP flags:", if first & 0x04 == 0: print "TP-MMS", # more messages to send if first & 0x20 != 0: print "TP-SRI", # status report indication tp_udhi = (first & 0x40) != 0 if tp_udhi: print "TP-UDHI", # user data header indicator if first & 0x80 != 0: print "TP-RP", # reply path print # This length is in digits (the number is padded if odd) address_len = bytes[pdu_pos + 1] address_len_octets = (address_len + 1) / 2 address_pos = pdu_pos + 2 pid_pos = address_pos + 1 + address_len_octets from_number = number_decode(bytes[address_pos:pid_pos]) print "Sender address:", from_number tp_pid = bytes[pid_pos] print "TP-PID Protocol identifier:", hex(tp_pid) if tp_pid != 0: print "Not a regular SMS message -- ignoring" return tp_dcs = bytes[pid_pos + 1] print "TP-DCS Data coding scheme:", hex(tp_dcs) # Make sure it's in the 7-bit packed format we understand if tp_dcs != 0: print "Not in 7-bit packed format -- ignoring" return print "TP-SCTS Time stamp:", timestamp_decode(bytes[pid_pos + 2:pid_pos + 9]) tp_udl = bytes[pid_pos + 9] print "TP-UDL User data length:", tp_udl tp_ud = bytes[pid_pos + 10:] print "TP-UD data:", repr(tp_ud) # Note this is in the GSM 03.38 7-bit alphabet, not ASCII (although # it's close for the US-ASCII bits...) if tp_udhi: is_csm = False csm_ref = 0 csm_total = 0 csm_seq = 0 # GDM 03.40 User Data Header present print "TP-UD:" udhl = tp_ud[0] udh = tp_ud[1:1 + udhl] print " UDH User data header:", repr(udh) part = 1 while len(udh) > 0: print " Information Element", part iei = udh[0] print " Identifier:", hex(iei) ie_len = udh[1] ie_data = udh[2:2 + ie_len] print " Data:", repr(ie_data) if iei == 0: # The user data to follow is part of a longer # message that needs reassembling. print " Data to follow is a fragment of a concatenated short message" is_csm = True (csm_ref, csm_total, csm_seq) = ie_data print " Ref number:", csm_ref print " Total:", csm_total print " Sequence:", csm_seq udh = udh[2 + ie_len:] part += 1 # We need to lose the padding bits before the start of the # seven-bit packed data, which means we need to figure out how # many there are... # See the diagram on page 58 of GSM_03.40_6.0.0.pdf. padding_size = ((7 * tp_udl) - (8 * (udhl + 1))) % 7 ud = unpack_sevenbit(tp_ud[1 + udhl:], padding_size) print " Remaining user data:", repr(ud) if is_csm: d = fragment_cache.setdefault(csm_ref, {}) d[csm_seq] = ud msg_complete = True for i in range(1, csm_total + 1): if not d.has_key(i): msg_complete = False if msg_complete: msg = "".join([d[i] for i in range(1, csm_total + 1)]) del fragment_cache[csm_ref] handle_message(msg, from_number) else: ud = unpack_sevenbit(tp_ud) print " User data:", repr(ud) handle_message(ud, from_number) print sys.stdout.flush() if __name__ == "__main__": pdus = [ "0791448720900253040C914497035290960000500151614414400DD4F29C9E769F41E17338ED06", "0791448720003023440C91449703529096000050015132532240A00500037A020190E9339A9D3EA3E920FA1B1466B341E472193E079DD3EE73D85DA7EB41E7B41C1407C1CBF43228CC26E3416137390F3AABCFEAB3FAAC3EABCFEAB3FAAC3EABCFEAB3FAAC3EABCFEAB3FADC3EB7CFED73FBDC3EBF5D4416D9457411596457137D87B7E16438194E86BBCF6D16D9055D429548A28BE822BA882E6370196C2A8950E291E822BA88", "0791448720003023440C91449703529096000050015132537240310500037A02025C4417D1D52422894EE5B17824BA8EC423F1483C129BC725315464118FCDE011247C4A8B44", "07914477790706520414D06176198F0EE361F2321900005001610013334014C324350B9287D12079180D92A3416134480E", "0791448720003023440C91449703529096000050016121855140A005000301060190F5F31C447F83C8E5327CEE0221EBE73988FE0691CB65F8DC05028190F5F31C447F83C8E5327CEE028140C8FA790EA2BF41E472193E7781402064FD3C07D1DF2072B90C9FBB402010B27E9E83E86F10B95C86CF5D2064FD3C07D1DF2072B90C9FBB40C8FA790EA2BF41E472193E7781402064FD3C07D1DF2072B90C9FBB402010B27E9E83E8", "0791448720003023440C91449703529096000050016121850240A0050003010602DE2072B90C9FBB402010B27E9E83E86F10B95C86CF5D201008593FCF41F437885C2EC3E72E100884AC9FE720FA1B442E97E1731708593FCF41F437885C2EC3E72E100884AC9FE720FA1B442E97E17317080442D6CF7310FD0D2297CBF0B90B040221EBE73988FE0691CB65F8DC05028190F5F31C447F83C8E5327CEE028140C8FA790EA2BF41", "0791448720003023440C91449703529096000050016121854240A0050003010603C8E5327CEE0221EBE73988FE0691CB65F8DC05028190F5F31C447F83C8E5327CEE028140C8FA790EA2BF41E472193E7781402064FD3C07D1DF2072B90C9FBB402010B27E9E83E86F10B95C86CF5D201008593FCF41F437885C2EC3E72E10B27E9E83E86F10B95C86CF5D201008593FCF41F437885C2EC3E72E100884AC9FE720FA1B442E97E1", "0791448720003023400C91449703529096000050016121858240A0050003010604E62E100884AC9FE720FA1B442E97E17317080442D6CF7310FD0D2297CBF0B90B040221EBE73988FE0691CB65F8DC0542D6CF7310FD0D2297CBF0B90B040221EBE73988FE0691CB65F8DC05028190F5F31C447F83C8E5327CEE028140C8FA790EA2BF41E472193E7781402064FD3C07D1DF2072B90C9FBB402010B27E9E83E86F10B95C86CF5D", "0791448720003023400C91449703529096000050016121853340A005000301060540C8FA790EA2BF41E472193E7781402064FD3C07D1DF2072B90C9FBB402010B27E9E83E86F10B95C86CF5D201008593FCF41F437885C2EC3E72E100884AC9FE720FA1B442E97E17317080442D6CF7310FD0D2297CBF0B90B84AC9FE720FA1B442E97E17317080442D6CF7310FD0D2297CBF0B90B040221EBE73988FE0691CB65F8DC05028190", "0791448720003023440C914497035290960000500161218563402A050003010606EAE73988FE0691CB65F8DC05028190F5F31C447F83C8E5327CEE0281402010", ] def handle_message(message, from_number): """Called once a complete message is received.""" print "Got message", repr(message), "from number", from_number for pdu in pdus: decode_pdu(pdu, handle_message)