mirror of https://gerrit.osmocom.org/pysim
cosmetic: Switch to consistent four-spaces indent; run autopep8
We had a mixture of tab and 4space based indenting, which is a bad idea. 4space is the standard in python, so convert all our code to that. The result unfortuantely still shoed even more inconsistencies, so I've decided to run autopep8 on the entire code base. Change-Id: I4a4b1b444a2f43fab05fc5d2c8a7dd6ddecb5f07
This commit is contained in:
parent
181c7c5930
commit
c91085e744
|
@ -41,6 +41,7 @@ from pySim.ts_51_011 import EF, EF_AD
|
|||
from pySim.card_handler import *
|
||||
from pySim.utils import *
|
||||
|
||||
|
||||
def parse_options():
|
||||
|
||||
parser = OptionParser(usage="usage: %prog [options]")
|
||||
|
@ -190,7 +191,6 @@ def parse_options():
|
|||
parser.add_option("--read-csv", dest="read_csv", metavar="FILE",
|
||||
help="Read parameters from CSV file rather than command line")
|
||||
|
||||
|
||||
parser.add_option("--write-csv", dest="write_csv", metavar="FILE",
|
||||
help="Append generated parameters in CSV file",
|
||||
)
|
||||
|
@ -215,12 +215,14 @@ def parse_options():
|
|||
|
||||
if options.source == 'csv':
|
||||
if (options.imsi is None) and (options.batch_mode is False) and (options.read_imsi is False) and (options.read_iccid is False):
|
||||
parser.error("CSV mode needs either an IMSI, --read-imsi, --read-iccid or batch mode")
|
||||
parser.error(
|
||||
"CSV mode needs either an IMSI, --read-imsi, --read-iccid or batch mode")
|
||||
if options.read_csv is None:
|
||||
parser.error("CSV mode requires a CSV input file")
|
||||
elif options.source == 'cmdline':
|
||||
if ((options.imsi is None) or (options.iccid is None)) and (options.num is None):
|
||||
parser.error("If either IMSI or ICCID isn't specified, num is required")
|
||||
parser.error(
|
||||
"If either IMSI or ICCID isn't specified, num is required")
|
||||
else:
|
||||
parser.error("Only `cmdline' and `csv' sources supported")
|
||||
|
||||
|
@ -232,7 +234,8 @@ def parse_options():
|
|||
|
||||
if (options.batch_mode):
|
||||
if (options.imsi is not None) or (options.iccid is not None):
|
||||
parser.error("Can't give ICCID/IMSI for batch mode, need to use automatic parameters ! see --num and --secret for more informations")
|
||||
parser.error(
|
||||
"Can't give ICCID/IMSI for batch mode, need to use automatic parameters ! see --num and --secret for more information")
|
||||
|
||||
if args:
|
||||
parser.error("Extraneous arguments")
|
||||
|
@ -246,18 +249,22 @@ def _digits(secret, usage, len, num):
|
|||
d = ''.join(['%02d' % x for x in s.digest()])
|
||||
return d[0:len]
|
||||
|
||||
|
||||
def _mcc_mnc_digits(mcc, mnc):
|
||||
return '%s%s' % (mcc, mnc)
|
||||
|
||||
|
||||
def _cc_digits(cc):
|
||||
return ('%03d' if cc > 100 else '%02d') % cc
|
||||
|
||||
|
||||
def _isnum(s, l=-1):
|
||||
return s.isdigit() and ((l== -1) or (len(s) == l))
|
||||
return s.isdigit() and ((l == -1) or (len(s) == l))
|
||||
|
||||
|
||||
def _ishex(s, l=-1):
|
||||
hc = '0123456789abcdef'
|
||||
return all([x in hc for x in s.lower()]) and ((l== -1) or (len(s) == l))
|
||||
return all([x in hc for x in s.lower()]) and ((l == -1) or (len(s) == l))
|
||||
|
||||
|
||||
def _dbi_binary_quote(s):
|
||||
|
@ -272,7 +279,8 @@ def _dbi_binary_quote(s):
|
|||
for i in range(1, 256):
|
||||
if i == 39:
|
||||
continue
|
||||
sum_ = cnt.get(i, 0) + cnt.get((i+1)&0xff, 0) + cnt.get((i+39)&0xff, 0)
|
||||
sum_ = cnt.get(i, 0) + cnt.get((i+1) & 0xff, 0) + \
|
||||
cnt.get((i+39) & 0xff, 0)
|
||||
if sum_ < m:
|
||||
m = sum_
|
||||
e = i
|
||||
|
@ -281,7 +289,7 @@ def _dbi_binary_quote(s):
|
|||
|
||||
# Generate output
|
||||
out = []
|
||||
out.append( chr(e) ) # Offset
|
||||
out.append(chr(e)) # Offset
|
||||
for c in s:
|
||||
x = (256 + ord(c) - e) % 256
|
||||
if x in (0, 1, 39):
|
||||
|
@ -292,6 +300,7 @@ def _dbi_binary_quote(s):
|
|||
|
||||
return ''.join(out)
|
||||
|
||||
|
||||
def gen_parameters(opts):
|
||||
"""Generates Name, ICCID, MCC, MNC, IMSI, SMSP, Ki, PIN-ADM from the
|
||||
options given by the user"""
|
||||
|
@ -331,7 +340,8 @@ def gen_parameters(opts):
|
|||
'Start with \'+\' for international numbers.')
|
||||
if len(msisdn) > 10 * 2:
|
||||
# TODO: Support MSISDN of length > 20 (10 Bytes)
|
||||
raise ValueError('MSISDNs longer than 20 digits are not (yet) supported.')
|
||||
raise ValueError(
|
||||
'MSISDNs longer than 20 digits are not (yet) supported.')
|
||||
|
||||
# ICCID (19 digits, E.118), though some phase1 vendors use 20 :(
|
||||
if opts.iccid is not None:
|
||||
|
@ -406,7 +416,8 @@ def gen_parameters(opts):
|
|||
else:
|
||||
smsc = '00%d' % opts.country + '5555' # Hack ...
|
||||
|
||||
smsc = '%02d' % ((len(smsc) + 3)//2,) + ton + swap_nibbles(rpad(smsc, 20))
|
||||
smsc = '%02d' % ((len(smsc) + 3)//2,) + ton + \
|
||||
swap_nibbles(rpad(smsc, 20))
|
||||
|
||||
smsp = (
|
||||
'e1' + # Parameters indicator
|
||||
|
@ -434,7 +445,7 @@ def gen_parameters(opts):
|
|||
if not re.match('^[0-9a-fA-F]{32}$', ki):
|
||||
raise ValueError('Ki needs to be 128 bits, in hex format')
|
||||
else:
|
||||
ki = ''.join(['%02x' % random.randrange(0,256) for i in range(16)])
|
||||
ki = ''.join(['%02x' % random.randrange(0, 256) for i in range(16)])
|
||||
|
||||
# OPC (random)
|
||||
if opts.opc is not None:
|
||||
|
@ -445,7 +456,7 @@ def gen_parameters(opts):
|
|||
elif opts.op is not None:
|
||||
opc = derive_milenage_opc(ki, opts.op)
|
||||
else:
|
||||
opc = ''.join(['%02x' % random.randrange(0,256) for i in range(16)])
|
||||
opc = ''.join(['%02x' % random.randrange(0, 256) for i in range(16)])
|
||||
|
||||
pin_adm = sanitize_pin_adm(opts.pin_adm, opts.pin_adm_hex)
|
||||
|
||||
|
@ -456,27 +467,28 @@ def gen_parameters(opts):
|
|||
epdg_mcc = opts.epdgSelection[:3]
|
||||
epdg_mnc = opts.epdgSelection[3:]
|
||||
if not epdg_mcc.isdigit() or not epdg_mnc.isdigit():
|
||||
raise ValueError('PLMN for ePDG Selection must only contain decimal digits')
|
||||
raise ValueError(
|
||||
'PLMN for ePDG Selection must only contain decimal digits')
|
||||
|
||||
# Return that
|
||||
return {
|
||||
'name' : opts.name,
|
||||
'iccid' : iccid,
|
||||
'mcc' : mcc,
|
||||
'mnc' : mnc,
|
||||
'imsi' : imsi,
|
||||
'smsp' : smsp,
|
||||
'ki' : ki,
|
||||
'opc' : opc,
|
||||
'acc' : acc,
|
||||
'pin_adm' : pin_adm,
|
||||
'msisdn' : opts.msisdn,
|
||||
'epdgid' : opts.epdgid,
|
||||
'epdgSelection' : opts.epdgSelection,
|
||||
'pcscf' : opts.pcscf,
|
||||
'name': opts.name,
|
||||
'iccid': iccid,
|
||||
'mcc': mcc,
|
||||
'mnc': mnc,
|
||||
'imsi': imsi,
|
||||
'smsp': smsp,
|
||||
'ki': ki,
|
||||
'opc': opc,
|
||||
'acc': acc,
|
||||
'pin_adm': pin_adm,
|
||||
'msisdn': opts.msisdn,
|
||||
'epdgid': opts.epdgid,
|
||||
'epdgSelection': opts.epdgSelection,
|
||||
'pcscf': opts.pcscf,
|
||||
'ims_hdomain': opts.ims_hdomain,
|
||||
'impi' : opts.impi,
|
||||
'impu' : opts.impu,
|
||||
'impi': opts.impi,
|
||||
'impu': opts.impu,
|
||||
'opmode': opts.opmode,
|
||||
}
|
||||
|
||||
|
@ -511,13 +523,14 @@ def write_params_csv(opts, params):
|
|||
cw.writerow([params[x] for x in row])
|
||||
f.close()
|
||||
|
||||
|
||||
def _read_params_csv(opts, iccid=None, imsi=None):
|
||||
import csv
|
||||
f = open(opts.read_csv, 'r')
|
||||
cr = csv.DictReader(f)
|
||||
|
||||
# Lower-case fieldnames
|
||||
cr.fieldnames = [ field.lower() for field in cr.fieldnames ]
|
||||
cr.fieldnames = [field.lower() for field in cr.fieldnames]
|
||||
|
||||
i = 0
|
||||
if not 'iccid' in cr.fieldnames:
|
||||
|
@ -539,6 +552,7 @@ def _read_params_csv(opts, iccid=None, imsi=None):
|
|||
f.close()
|
||||
return None
|
||||
|
||||
|
||||
def read_params_csv(opts, imsi=None, iccid=None):
|
||||
row = _read_params_csv(opts, iccid=iccid, imsi=imsi)
|
||||
if row is not None:
|
||||
|
@ -548,10 +562,10 @@ def read_params_csv(opts, imsi=None, iccid=None):
|
|||
pin_adm = None
|
||||
# We need to escape the pin_adm we get from the csv
|
||||
if 'pin_adm' in row:
|
||||
pin_adm = ''.join(['%02x'%(ord(x)) for x in row['pin_adm']])
|
||||
pin_adm = ''.join(['%02x' % (ord(x)) for x in row['pin_adm']])
|
||||
# Stay compatible to the odoo csv format
|
||||
elif 'adm1' in row:
|
||||
pin_adm = ''.join(['%02x'%(ord(x)) for x in row['adm1']])
|
||||
pin_adm = ''.join(['%02x' % (ord(x)) for x in row['adm1']])
|
||||
if pin_adm:
|
||||
row['pin_adm'] = rpad(pin_adm, 16)
|
||||
|
||||
|
@ -565,9 +579,11 @@ def read_params_csv(opts, imsi=None, iccid=None):
|
|||
try:
|
||||
try_encode = h2b(pin_adm)
|
||||
except ValueError:
|
||||
raise ValueError("pin_adm_hex needs to be hex encoded using this option")
|
||||
raise ValueError(
|
||||
"pin_adm_hex needs to be hex encoded using this option")
|
||||
else:
|
||||
raise ValueError("pin_adm_hex needs to be exactly 16 digits (hex encoded)")
|
||||
raise ValueError(
|
||||
"pin_adm_hex needs to be exactly 16 digits (hex encoded)")
|
||||
|
||||
return row
|
||||
|
||||
|
@ -597,20 +613,23 @@ def write_params_hlr(opts, params):
|
|||
'(subscriber_id, algorithm_id, a3a8_ki)' +
|
||||
'VALUES ' +
|
||||
'(?,?,?)',
|
||||
[ sub_id, 2, sqlite3.Binary(_dbi_binary_quote(h2b(params['ki']))) ],
|
||||
[sub_id, 2, sqlite3.Binary(
|
||||
_dbi_binary_quote(h2b(params['ki'])))],
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def write_parameters(opts, params):
|
||||
write_params_csv(opts, params)
|
||||
write_params_hlr(opts, params)
|
||||
|
||||
|
||||
BATCH_STATE = [ 'name', 'country', 'mcc', 'mnc', 'smsp', 'secret', 'num' ]
|
||||
BATCH_STATE = ['name', 'country', 'mcc', 'mnc', 'smsp', 'secret', 'num']
|
||||
BATCH_INCOMPATIBLE = ['iccid', 'imsi', 'ki']
|
||||
|
||||
|
||||
def init_batch(opts):
|
||||
# Need to do something ?
|
||||
if not opts.batch_mode:
|
||||
|
@ -634,7 +653,7 @@ def init_batch(opts):
|
|||
d = json.loads(fh.read())
|
||||
fh.close()
|
||||
|
||||
for k,v in d.iteritems():
|
||||
for k, v in d.iteritems():
|
||||
setattr(opts, k, v)
|
||||
|
||||
|
||||
|
@ -643,7 +662,7 @@ def save_batch(opts):
|
|||
if not opts.batch_mode or not opts.batch_state:
|
||||
return
|
||||
|
||||
d = json.dumps(dict([(k,getattr(opts,k)) for k in BATCH_STATE]))
|
||||
d = json.dumps(dict([(k, getattr(opts, k)) for k in BATCH_STATE]))
|
||||
fh = open(opts.batch_state, 'w')
|
||||
fh.write(d)
|
||||
fh.close()
|
||||
|
@ -682,13 +701,13 @@ def process_card(opts, first, ch):
|
|||
if opts.dry_run:
|
||||
# Connect transport
|
||||
ch.get(False)
|
||||
(res,_) = scc.read_binary(['3f00', '2fe2'], length=10)
|
||||
(res, _) = scc.read_binary(['3f00', '2fe2'], length=10)
|
||||
iccid = dec_iccid(res)
|
||||
elif opts.read_imsi:
|
||||
if opts.dry_run:
|
||||
# Connect transport
|
||||
ch.get(False)
|
||||
(res,_) = scc.read_binary(EF['IMSI'])
|
||||
(res, _) = scc.read_binary(EF['IMSI'])
|
||||
imsi = swap_nibbles(res)[3:]
|
||||
else:
|
||||
imsi = opts.imsi
|
||||
|
|
|
@ -45,7 +45,8 @@ option_parser = argparse.ArgumentParser(prog='pySim-read',
|
|||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
argparse_add_reader_args(option_parser)
|
||||
|
||||
def select_app(adf:str, card:SimCard):
|
||||
|
||||
def select_app(adf: str, card: SimCard):
|
||||
"""Select application by its AID"""
|
||||
sw = 0
|
||||
try:
|
||||
|
@ -63,6 +64,7 @@ def select_app(adf:str, card:SimCard):
|
|||
|
||||
return sw
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# Parse options
|
||||
|
@ -265,18 +267,19 @@ if __name__ == '__main__':
|
|||
except Exception as e:
|
||||
print("USIM Service Table: Can't read file -- " + str(e))
|
||||
|
||||
#EF.ePDGId - Home ePDG Identifier
|
||||
# EF.ePDGId - Home ePDG Identifier
|
||||
try:
|
||||
if usim_card.file_exists(EF_USIM_ADF_map['ePDGId']):
|
||||
(res, sw) = usim_card.read_epdgid()
|
||||
if sw == '9000':
|
||||
print("ePDGId:\n%s" % (len(res) and res or '\tNot available\n',))
|
||||
print("ePDGId:\n%s" %
|
||||
(len(res) and res or '\tNot available\n',))
|
||||
else:
|
||||
print("ePDGId: Can't read, response code = %s" % (sw,))
|
||||
except Exception as e:
|
||||
print("ePDGId: Can't read file -- " + str(e))
|
||||
|
||||
#EF.ePDGSelection - ePDG Selection Information
|
||||
# EF.ePDGSelection - ePDG Selection Information
|
||||
try:
|
||||
if usim_card.file_exists(EF_USIM_ADF_map['ePDGSelection']):
|
||||
(res, sw) = usim_card.read_ePDGSelection()
|
||||
|
@ -293,11 +296,12 @@ if __name__ == '__main__':
|
|||
# Select USIM profile
|
||||
isim_card = IsimCard(scc)
|
||||
|
||||
#EF.P-CSCF - P-CSCF Address
|
||||
# EF.P-CSCF - P-CSCF Address
|
||||
try:
|
||||
if isim_card.file_exists(EF_ISIM_ADF_map['PCSCF']):
|
||||
res = isim_card.read_pcscf()
|
||||
print("P-CSCF:\n%s" % (len(res) and res or '\tNot available\n',))
|
||||
print("P-CSCF:\n%s" %
|
||||
(len(res) and res or '\tNot available\n',))
|
||||
except Exception as e:
|
||||
print("P-CSCF: Can't read file -- " + str(e))
|
||||
|
||||
|
@ -306,9 +310,11 @@ if __name__ == '__main__':
|
|||
if isim_card.file_exists(EF_ISIM_ADF_map['DOMAIN']):
|
||||
(res, sw) = isim_card.read_domain()
|
||||
if sw == '9000':
|
||||
print("Home Network Domain Name: %s" % (len(res) and res or 'Not available',))
|
||||
print("Home Network Domain Name: %s" %
|
||||
(len(res) and res or 'Not available',))
|
||||
else:
|
||||
print("Home Network Domain Name: Can't read, response code = %s" % (sw,))
|
||||
print(
|
||||
"Home Network Domain Name: Can't read, response code = %s" % (sw,))
|
||||
except Exception as e:
|
||||
print("Home Network Domain Name: Can't read file -- " + str(e))
|
||||
|
||||
|
@ -317,9 +323,11 @@ if __name__ == '__main__':
|
|||
if isim_card.file_exists(EF_ISIM_ADF_map['IMPI']):
|
||||
(res, sw) = isim_card.read_impi()
|
||||
if sw == '9000':
|
||||
print("IMS private user identity: %s" % (len(res) and res or 'Not available',))
|
||||
print("IMS private user identity: %s" %
|
||||
(len(res) and res or 'Not available',))
|
||||
else:
|
||||
print("IMS private user identity: Can't read, response code = %s" % (sw,))
|
||||
print(
|
||||
"IMS private user identity: Can't read, response code = %s" % (sw,))
|
||||
except Exception as e:
|
||||
print("IMS private user identity: Can't read file -- " + str(e))
|
||||
|
||||
|
@ -327,7 +335,8 @@ if __name__ == '__main__':
|
|||
try:
|
||||
if isim_card.file_exists(EF_ISIM_ADF_map['IMPU']):
|
||||
res = isim_card.read_impu()
|
||||
print("IMS public user identity:\n%s" % (len(res) and res or '\tNot available\n',))
|
||||
print("IMS public user identity:\n%s" %
|
||||
(len(res) and res or '\tNot available\n',))
|
||||
except Exception as e:
|
||||
print("IMS public user identity: Can't read file -- " + str(e))
|
||||
|
||||
|
@ -335,7 +344,8 @@ if __name__ == '__main__':
|
|||
try:
|
||||
if isim_card.file_exists(EF_ISIM_ADF_map['UICCIARI']):
|
||||
res = isim_card.read_iari()
|
||||
print("UICC IARI:\n%s" % (len(res) and res or '\tNot available\n',))
|
||||
print("UICC IARI:\n%s" %
|
||||
(len(res) and res or '\tNot available\n',))
|
||||
except Exception as e:
|
||||
print("UICC IARI: Can't read file -- " + str(e))
|
||||
|
||||
|
|
195
pySim-shell.py
195
pySim-shell.py
|
@ -61,6 +61,7 @@ import pySim.sysmocom_sja2
|
|||
|
||||
from pySim.card_key_provider import CardKeyProviderCsv, card_key_provider_register, card_key_provider_get_field
|
||||
|
||||
|
||||
def init_card(sl):
|
||||
"""
|
||||
Detect card in reader and setup card profile and runtime state. This
|
||||
|
@ -117,16 +118,18 @@ def init_card(sl):
|
|||
|
||||
return rs, card
|
||||
|
||||
|
||||
class PysimApp(cmd2.Cmd):
|
||||
CUSTOM_CATEGORY = 'pySim Commands'
|
||||
def __init__(self, card, rs, sl, ch, script = None):
|
||||
|
||||
def __init__(self, card, rs, sl, ch, script=None):
|
||||
super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False,
|
||||
use_ipython=True, auto_load_commands=False, startup_script=script)
|
||||
self.intro = style('Welcome to pySim-shell!', fg=fg.red)
|
||||
self.default_category = 'pySim-shell built-in commands'
|
||||
self.card = None
|
||||
self.rs = None
|
||||
self.py_locals = { 'card': self.card, 'rs' : self.rs }
|
||||
self.py_locals = {'card': self.card, 'rs': self.rs}
|
||||
self.sl = sl
|
||||
self.ch = ch
|
||||
|
||||
|
@ -137,7 +140,8 @@ class PysimApp(cmd2.Cmd):
|
|||
self.add_settable(cmd2.Settable('conserve_write', bool, 'Read and compare before write',
|
||||
onchange_cb=self._onchange_conserve_write))
|
||||
self.json_pretty_print = True
|
||||
self.add_settable(cmd2.Settable('json_pretty_print', bool, 'Pretty-Print JSON output'))
|
||||
self.add_settable(cmd2.Settable('json_pretty_print',
|
||||
bool, 'Pretty-Print JSON output'))
|
||||
self.apdu_trace = False
|
||||
self.add_settable(cmd2.Settable('apdu_trace', bool, 'Trace and display APDUs exchanged with card',
|
||||
onchange_cb=self._onchange_apdu_trace))
|
||||
|
@ -166,7 +170,8 @@ class PysimApp(cmd2.Cmd):
|
|||
# When a card object and a runtime state is present, (re)equip pySim-shell with everything that is
|
||||
# needed to operate on cards.
|
||||
if self.card and self.rs:
|
||||
self._onchange_conserve_write('conserve_write', False, self.conserve_write)
|
||||
self._onchange_conserve_write(
|
||||
'conserve_write', False, self.conserve_write)
|
||||
self._onchange_apdu_trace('apdu_trace', False, self.apdu_trace)
|
||||
self.register_command_set(Iso7816Commands())
|
||||
self.register_command_set(PySimCommands())
|
||||
|
@ -179,7 +184,7 @@ class PysimApp(cmd2.Cmd):
|
|||
self.update_prompt()
|
||||
return rc
|
||||
|
||||
def poutput_json(self, data, force_no_pretty = False):
|
||||
def poutput_json(self, data, force_no_pretty=False):
|
||||
"""like cmd2.poutput() but for a JSON serializable dict."""
|
||||
if force_no_pretty or self.json_pretty_print == False:
|
||||
output = json.dumps(data, cls=JsonEncoder)
|
||||
|
@ -211,7 +216,8 @@ class PysimApp(cmd2.Cmd):
|
|||
|
||||
def update_prompt(self):
|
||||
if self.rs:
|
||||
path_list = self.rs.selected_file.fully_qualified_path(not self.numeric_path)
|
||||
path_list = self.rs.selected_file.fully_qualified_path(
|
||||
not self.numeric_path)
|
||||
self.prompt = 'pySIM-shell (%s)> ' % ('/'.join(path_list))
|
||||
else:
|
||||
self.prompt = 'pySIM-shell (no card)> '
|
||||
|
@ -234,10 +240,12 @@ class PysimApp(cmd2.Cmd):
|
|||
class InterceptStderr(list):
|
||||
def __init__(self):
|
||||
self._stderr_backup = sys.stderr
|
||||
|
||||
def __enter__(self):
|
||||
self._stringio_stderr = StringIO()
|
||||
sys.stderr = self._stringio_stderr
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.stderr = self._stringio_stderr.getvalue().strip()
|
||||
del self._stringio_stderr
|
||||
|
@ -305,7 +313,8 @@ class PysimApp(cmd2.Cmd):
|
|||
return -1
|
||||
|
||||
bulk_script_parser = argparse.ArgumentParser()
|
||||
bulk_script_parser.add_argument('script_path', help="path to the script file")
|
||||
bulk_script_parser.add_argument(
|
||||
'script_path', help="path to the script file")
|
||||
bulk_script_parser.add_argument('--halt_on_error', help='stop card handling if an exeption occurs',
|
||||
action='store_true')
|
||||
bulk_script_parser.add_argument('--tries', type=int, default=2,
|
||||
|
@ -350,13 +359,14 @@ class PysimApp(cmd2.Cmd):
|
|||
if rc == 0:
|
||||
success_count = success_count + 1
|
||||
self._show_success_sign()
|
||||
self.poutput("Statistics: success :%i, failure: %i" % (success_count, fail_count))
|
||||
self.poutput("Statistics: success :%i, failure: %i" % (
|
||||
success_count, fail_count))
|
||||
break
|
||||
else:
|
||||
fail_count = fail_count + 1
|
||||
self._show_failure_sign()
|
||||
self.poutput("Statistics: success :%i, failure: %i" % (success_count, fail_count))
|
||||
|
||||
self.poutput("Statistics: success :%i, failure: %i" % (
|
||||
success_count, fail_count))
|
||||
|
||||
# Depending on success or failure, the card goes either in the "error" bin or in the
|
||||
# "done" bin.
|
||||
|
@ -391,7 +401,8 @@ class PysimApp(cmd2.Cmd):
|
|||
self.poutput("")
|
||||
fail_count = fail_count + 1
|
||||
self._show_failure_sign()
|
||||
self.poutput("Statistics: success :%i, failure: %i" % (success_count, fail_count))
|
||||
self.poutput("Statistics: success :%i, failure: %i" %
|
||||
(success_count, fail_count))
|
||||
|
||||
first = False
|
||||
|
||||
|
@ -404,16 +415,21 @@ class PysimApp(cmd2.Cmd):
|
|||
"""Echo (print) a string on the console"""
|
||||
self.poutput(opts.string)
|
||||
|
||||
|
||||
@with_default_category('pySim Commands')
|
||||
class PySimCommands(CommandSet):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
dir_parser = argparse.ArgumentParser()
|
||||
dir_parser.add_argument('--fids', help='Show file identifiers', action='store_true')
|
||||
dir_parser.add_argument('--names', help='Show file names', action='store_true')
|
||||
dir_parser.add_argument('--apps', help='Show applications', action='store_true')
|
||||
dir_parser.add_argument('--all', help='Show all selectable identifiers and names', action='store_true')
|
||||
dir_parser.add_argument(
|
||||
'--fids', help='Show file identifiers', action='store_true')
|
||||
dir_parser.add_argument(
|
||||
'--names', help='Show file names', action='store_true')
|
||||
dir_parser.add_argument(
|
||||
'--apps', help='Show applications', action='store_true')
|
||||
dir_parser.add_argument(
|
||||
'--all', help='Show all selectable identifiers and names', action='store_true')
|
||||
|
||||
@cmd2.with_argparser(dir_parser)
|
||||
def do_dir(self, opts):
|
||||
|
@ -430,8 +446,10 @@ class PySimCommands(CommandSet):
|
|||
flags += ['ANAMES', 'AIDS']
|
||||
else:
|
||||
flags = ['PARENT', 'SELF', 'FNAMES', 'ANAMES']
|
||||
selectables = list(self._cmd.rs.selected_file.get_selectable_names(flags = flags))
|
||||
directory_str = tabulate_str_list(selectables, width = 79, hspace = 2, lspace = 1, align_left = True)
|
||||
selectables = list(
|
||||
self._cmd.rs.selected_file.get_selectable_names(flags=flags))
|
||||
directory_str = tabulate_str_list(
|
||||
selectables, width=79, hspace=2, lspace=1, align_left=True)
|
||||
path_list = self._cmd.rs.selected_file.fully_qualified_path(True)
|
||||
self._cmd.poutput('/'.join(path_list))
|
||||
path_list = self._cmd.rs.selected_file.fully_qualified_path(False)
|
||||
|
@ -439,9 +457,10 @@ class PySimCommands(CommandSet):
|
|||
self._cmd.poutput(directory_str)
|
||||
self._cmd.poutput("%d files" % len(selectables))
|
||||
|
||||
def walk(self, indent = 0, action = None, context = None):
|
||||
def walk(self, indent=0, action=None, context=None):
|
||||
"""Recursively walk through the file system, starting at the currently selected DF"""
|
||||
files = self._cmd.rs.selected_file.get_selectables(flags = ['FNAMES', 'ANAMES'])
|
||||
files = self._cmd.rs.selected_file.get_selectables(
|
||||
flags=['FNAMES', 'ANAMES'])
|
||||
for f in files:
|
||||
if not action:
|
||||
output_str = " " * indent + str(f) + (" " * 250)
|
||||
|
@ -454,14 +473,15 @@ class PySimCommands(CommandSet):
|
|||
self._cmd.poutput(output_str)
|
||||
|
||||
if isinstance(files[f], CardDF):
|
||||
skip_df=False
|
||||
skip_df = False
|
||||
try:
|
||||
fcp_dec = self._cmd.rs.select(f, self._cmd)
|
||||
except Exception as e:
|
||||
skip_df=True
|
||||
skip_df = True
|
||||
df = self._cmd.rs.selected_file
|
||||
df_path_list = df.fully_qualified_path(True)
|
||||
df_skip_reason_str = '/'.join(df_path_list) + "/" + str(f) + ", " + str(e)
|
||||
df_skip_reason_str = '/'.join(df_path_list) + \
|
||||
"/" + str(f) + ", " + str(e)
|
||||
if context:
|
||||
context['DF_SKIP'] += 1
|
||||
context['DF_SKIP_REASON'].append(df_skip_reason_str)
|
||||
|
@ -492,7 +512,8 @@ class PySimCommands(CommandSet):
|
|||
df = self._cmd.rs.selected_file
|
||||
|
||||
if not isinstance(df, CardDF):
|
||||
raise RuntimeError("currently selected file %s is not a DF or ADF" % str(df))
|
||||
raise RuntimeError(
|
||||
"currently selected file %s is not a DF or ADF" % str(df))
|
||||
|
||||
df_path_list = df.fully_qualified_path(True)
|
||||
df_path_list_fid = df.fully_qualified_path(False)
|
||||
|
@ -500,10 +521,12 @@ class PySimCommands(CommandSet):
|
|||
file_str = '/'.join(df_path_list) + "/" + str(filename)
|
||||
self._cmd.poutput(boxed_heading_str(file_str))
|
||||
|
||||
self._cmd.poutput("# directory: %s (%s)" % ('/'.join(df_path_list), '/'.join(df_path_list_fid)))
|
||||
self._cmd.poutput("# directory: %s (%s)" %
|
||||
('/'.join(df_path_list), '/'.join(df_path_list_fid)))
|
||||
try:
|
||||
fcp_dec = self._cmd.rs.select(filename, self._cmd)
|
||||
self._cmd.poutput("# file: %s (%s)" % (self._cmd.rs.selected_file.name, self._cmd.rs.selected_file.fid))
|
||||
self._cmd.poutput("# file: %s (%s)" % (
|
||||
self._cmd.rs.selected_file.name, self._cmd.rs.selected_file.fid))
|
||||
|
||||
fd = fcp_dec['file_descriptor']
|
||||
structure = fd['structure']
|
||||
|
@ -522,7 +545,8 @@ class PySimCommands(CommandSet):
|
|||
num_of_rec = fd['num_of_rec']
|
||||
for r in range(1, num_of_rec + 1):
|
||||
result = self._cmd.rs.read_record(r)
|
||||
self._cmd.poutput("update_record %d %s" % (r, str(result[0])))
|
||||
self._cmd.poutput("update_record %d %s" %
|
||||
(r, str(result[0])))
|
||||
# When the select response does not return the number of records, read until we hit the
|
||||
# first record that cannot be read.
|
||||
else:
|
||||
|
@ -537,7 +561,8 @@ class PySimCommands(CommandSet):
|
|||
# Some other problem occurred
|
||||
else:
|
||||
raise e
|
||||
self._cmd.poutput("update_record %d %s" % (r, str(result[0])))
|
||||
self._cmd.poutput("update_record %d %s" %
|
||||
(r, str(result[0])))
|
||||
r = r + 1
|
||||
elif structure == 'ber_tlv':
|
||||
tags = self._cmd.rs.retrieve_tags()
|
||||
|
@ -546,9 +571,11 @@ class PySimCommands(CommandSet):
|
|||
(tag, l, val, remainer) = bertlv_parse_one(h2b(result[0]))
|
||||
self._cmd.poutput("set_data 0x%02x %s" % (t, b2h(val)))
|
||||
else:
|
||||
raise RuntimeError('Unsupported structure "%s" of file "%s"' % (structure, filename))
|
||||
raise RuntimeError(
|
||||
'Unsupported structure "%s" of file "%s"' % (structure, filename))
|
||||
except Exception as e:
|
||||
bad_file_str = '/'.join(df_path_list) + "/" + str(filename) + ", " + str(e)
|
||||
bad_file_str = '/'.join(df_path_list) + \
|
||||
"/" + str(filename) + ", " + str(e)
|
||||
self._cmd.poutput("# bad file: %s" % bad_file_str)
|
||||
context['ERR'] += 1
|
||||
context['BAD'].append(bad_file_str)
|
||||
|
@ -562,12 +589,14 @@ class PySimCommands(CommandSet):
|
|||
self._cmd.poutput("#")
|
||||
|
||||
export_parser = argparse.ArgumentParser()
|
||||
export_parser.add_argument('--filename', type=str, default=None, help='only export specific file')
|
||||
export_parser.add_argument(
|
||||
'--filename', type=str, default=None, help='only export specific file')
|
||||
|
||||
@cmd2.with_argparser(export_parser)
|
||||
def do_export(self, opts):
|
||||
"""Export files to script that can be imported back later"""
|
||||
context = {'ERR':0, 'COUNT':0, 'BAD':[], 'DF_SKIP':0, 'DF_SKIP_REASON':[]}
|
||||
context = {'ERR': 0, 'COUNT': 0, 'BAD': [],
|
||||
'DF_SKIP': 0, 'DF_SKIP_REASON': []}
|
||||
if opts.filename:
|
||||
self.export(opts.filename, context)
|
||||
else:
|
||||
|
@ -580,16 +609,20 @@ class PySimCommands(CommandSet):
|
|||
for b in context['BAD']:
|
||||
self._cmd.poutput("# " + b)
|
||||
|
||||
self._cmd.poutput("# skipped dedicated files(s): %u" % context['DF_SKIP'])
|
||||
self._cmd.poutput("# skipped dedicated files(s): %u" %
|
||||
context['DF_SKIP'])
|
||||
for b in context['DF_SKIP_REASON']:
|
||||
self._cmd.poutput("# " + b)
|
||||
|
||||
if context['ERR'] and context['DF_SKIP']:
|
||||
raise RuntimeError("unable to export %i elementary file(s) and %i dedicated file(s)" % (context['ERR'], context['DF_SKIP']))
|
||||
raise RuntimeError("unable to export %i elementary file(s) and %i dedicated file(s)" % (
|
||||
context['ERR'], context['DF_SKIP']))
|
||||
elif context['ERR']:
|
||||
raise RuntimeError("unable to export %i elementary file(s)" % context['ERR'])
|
||||
raise RuntimeError(
|
||||
"unable to export %i elementary file(s)" % context['ERR'])
|
||||
elif context['DF_SKIP']:
|
||||
raise RuntimeError("unable to export %i dedicated files(s)" % context['ERR'])
|
||||
raise RuntimeError(
|
||||
"unable to export %i dedicated files(s)" % context['ERR'])
|
||||
|
||||
def do_reset(self, opts):
|
||||
"""Reset the Card."""
|
||||
|
@ -612,18 +645,22 @@ class PySimCommands(CommandSet):
|
|||
pin_adm = sanitize_pin_adm(arg)
|
||||
else:
|
||||
# try to find an ADM-PIN if none is specified
|
||||
result = card_key_provider_get_field('ADM1', key='ICCID', value=self._cmd.iccid)
|
||||
result = card_key_provider_get_field(
|
||||
'ADM1', key='ICCID', value=self._cmd.iccid)
|
||||
pin_adm = sanitize_pin_adm(result)
|
||||
if pin_adm:
|
||||
self._cmd.poutput("found ADM-PIN '%s' for ICCID '%s'" % (result, self._cmd.iccid))
|
||||
self._cmd.poutput(
|
||||
"found ADM-PIN '%s' for ICCID '%s'" % (result, self._cmd.iccid))
|
||||
else:
|
||||
raise ValueError("cannot find ADM-PIN for ICCID '%s'" % (self._cmd.iccid))
|
||||
raise ValueError(
|
||||
"cannot find ADM-PIN for ICCID '%s'" % (self._cmd.iccid))
|
||||
|
||||
if pin_adm:
|
||||
self._cmd.card.verify_adm(h2b(pin_adm))
|
||||
else:
|
||||
raise ValueError("error: cannot authenticate, no adm-pin!")
|
||||
|
||||
|
||||
@with_default_category('ISO7816 Commands')
|
||||
class Iso7816Commands(CommandSet):
|
||||
def __init__(self):
|
||||
|
@ -633,8 +670,10 @@ class Iso7816Commands(CommandSet):
|
|||
"""SELECT a File (ADF/DF/EF)"""
|
||||
if len(opts.arg_list) == 0:
|
||||
path_list = self._cmd.rs.selected_file.fully_qualified_path(True)
|
||||
path_list_fid = self._cmd.rs.selected_file.fully_qualified_path(False)
|
||||
self._cmd.poutput("currently selected file: " + '/'.join(path_list) + " (" + '/'.join(path_list_fid) + ")")
|
||||
path_list_fid = self._cmd.rs.selected_file.fully_qualified_path(
|
||||
False)
|
||||
self._cmd.poutput("currently selected file: " +
|
||||
'/'.join(path_list) + " (" + '/'.join(path_list_fid) + ")")
|
||||
return
|
||||
|
||||
path = opts.arg_list[0]
|
||||
|
@ -644,7 +683,7 @@ class Iso7816Commands(CommandSet):
|
|||
|
||||
def complete_select(self, text, line, begidx, endidx) -> List[str]:
|
||||
"""Command Line tab completion for SELECT"""
|
||||
index_dict = { 1: self._cmd.rs.selected_file.get_selectable_names() }
|
||||
index_dict = {1: self._cmd.rs.selected_file.get_selectable_names()}
|
||||
return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
|
||||
|
||||
def get_code(self, code):
|
||||
|
@ -654,17 +693,22 @@ class Iso7816Commands(CommandSet):
|
|||
if str(code).upper() not in auto:
|
||||
return sanitize_pin_adm(code)
|
||||
|
||||
result = card_key_provider_get_field(str(code), key='ICCID', value=self._cmd.iccid)
|
||||
result = card_key_provider_get_field(
|
||||
str(code), key='ICCID', value=self._cmd.iccid)
|
||||
result = sanitize_pin_adm(result)
|
||||
if result:
|
||||
self._cmd.poutput("found %s '%s' for ICCID '%s'" % (code.upper(), result, self._cmd.iccid))
|
||||
self._cmd.poutput("found %s '%s' for ICCID '%s'" %
|
||||
(code.upper(), result, self._cmd.iccid))
|
||||
else:
|
||||
self._cmd.poutput("cannot find %s for ICCID '%s'" % (code.upper(), self._cmd.iccid))
|
||||
self._cmd.poutput("cannot find %s for ICCID '%s'" %
|
||||
(code.upper(), self._cmd.iccid))
|
||||
return result
|
||||
|
||||
verify_chv_parser = argparse.ArgumentParser()
|
||||
verify_chv_parser.add_argument('--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
|
||||
verify_chv_parser.add_argument('pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
|
||||
verify_chv_parser.add_argument(
|
||||
'--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
|
||||
verify_chv_parser.add_argument(
|
||||
'pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
|
||||
|
||||
@cmd2.with_argparser(verify_chv_parser)
|
||||
def do_verify_chv(self, opts):
|
||||
|
@ -674,34 +718,44 @@ class Iso7816Commands(CommandSet):
|
|||
self._cmd.poutput("CHV verification successful")
|
||||
|
||||
unblock_chv_parser = argparse.ArgumentParser()
|
||||
unblock_chv_parser.add_argument('--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
|
||||
unblock_chv_parser.add_argument('puk_code', type=str, help='PUK code digits \"PUK1\" or \"PUK2\" to get PUK code from external data source')
|
||||
unblock_chv_parser.add_argument('new_pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
|
||||
unblock_chv_parser.add_argument(
|
||||
'--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
|
||||
unblock_chv_parser.add_argument(
|
||||
'puk_code', type=str, help='PUK code digits \"PUK1\" or \"PUK2\" to get PUK code from external data source')
|
||||
unblock_chv_parser.add_argument(
|
||||
'new_pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
|
||||
|
||||
@cmd2.with_argparser(unblock_chv_parser)
|
||||
def do_unblock_chv(self, opts):
|
||||
"""Unblock PIN code using specified PUK code"""
|
||||
new_pin = self.get_code(opts.new_pin_code)
|
||||
puk = self.get_code(opts.puk_code)
|
||||
(data, sw) = self._cmd.card._scc.unblock_chv(opts.pin_nr, h2b(puk), h2b(new_pin))
|
||||
(data, sw) = self._cmd.card._scc.unblock_chv(
|
||||
opts.pin_nr, h2b(puk), h2b(new_pin))
|
||||
self._cmd.poutput("CHV unblock successful")
|
||||
|
||||
change_chv_parser = argparse.ArgumentParser()
|
||||
change_chv_parser.add_argument('--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
|
||||
change_chv_parser.add_argument('pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
|
||||
change_chv_parser.add_argument('new_pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
|
||||
change_chv_parser.add_argument(
|
||||
'--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
|
||||
change_chv_parser.add_argument(
|
||||
'pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
|
||||
change_chv_parser.add_argument(
|
||||
'new_pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
|
||||
|
||||
@cmd2.with_argparser(change_chv_parser)
|
||||
def do_change_chv(self, opts):
|
||||
"""Change PIN code to a new PIN code"""
|
||||
new_pin = self.get_code(opts.new_pin_code)
|
||||
pin = self.get_code(opts.pin_code)
|
||||
(data, sw) = self._cmd.card._scc.change_chv(opts.pin_nr, h2b(pin), h2b(new_pin))
|
||||
(data, sw) = self._cmd.card._scc.change_chv(
|
||||
opts.pin_nr, h2b(pin), h2b(new_pin))
|
||||
self._cmd.poutput("CHV change successful")
|
||||
|
||||
disable_chv_parser = argparse.ArgumentParser()
|
||||
disable_chv_parser.add_argument('--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
|
||||
disable_chv_parser.add_argument('pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
|
||||
disable_chv_parser.add_argument(
|
||||
'--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
|
||||
disable_chv_parser.add_argument(
|
||||
'pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
|
||||
|
||||
@cmd2.with_argparser(disable_chv_parser)
|
||||
def do_disable_chv(self, opts):
|
||||
|
@ -711,8 +765,10 @@ class Iso7816Commands(CommandSet):
|
|||
self._cmd.poutput("CHV disable successful")
|
||||
|
||||
enable_chv_parser = argparse.ArgumentParser()
|
||||
enable_chv_parser.add_argument('--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
|
||||
enable_chv_parser.add_argument('pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
|
||||
enable_chv_parser.add_argument(
|
||||
'--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
|
||||
enable_chv_parser.add_argument(
|
||||
'pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
|
||||
|
||||
@cmd2.with_argparser(enable_chv_parser)
|
||||
def do_enable_chv(self, opts):
|
||||
|
@ -732,24 +788,28 @@ class Iso7816Commands(CommandSet):
|
|||
|
||||
def complete_activate_file(self, text, line, begidx, endidx) -> List[str]:
|
||||
"""Command Line tab completion for ACTIVATE FILE"""
|
||||
index_dict = { 1: self._cmd.rs.selected_file.get_selectable_names() }
|
||||
index_dict = {1: self._cmd.rs.selected_file.get_selectable_names()}
|
||||
return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
|
||||
|
||||
open_chan_parser = argparse.ArgumentParser()
|
||||
open_chan_parser.add_argument('chan_nr', type=int, default=0, help='Channel Number')
|
||||
open_chan_parser.add_argument(
|
||||
'chan_nr', type=int, default=0, help='Channel Number')
|
||||
|
||||
@cmd2.with_argparser(open_chan_parser)
|
||||
def do_open_channel(self, opts):
|
||||
"""Open a logical channel."""
|
||||
(data, sw) = self._cmd.card._scc.manage_channel(mode='open', lchan_nr=opts.chan_nr)
|
||||
(data, sw) = self._cmd.card._scc.manage_channel(
|
||||
mode='open', lchan_nr=opts.chan_nr)
|
||||
|
||||
close_chan_parser = argparse.ArgumentParser()
|
||||
close_chan_parser.add_argument('chan_nr', type=int, default=0, help='Channel Number')
|
||||
close_chan_parser.add_argument(
|
||||
'chan_nr', type=int, default=0, help='Channel Number')
|
||||
|
||||
@cmd2.with_argparser(close_chan_parser)
|
||||
def do_close_channel(self, opts):
|
||||
"""Close a logical channel."""
|
||||
(data, sw) = self._cmd.card._scc.manage_channel(mode='close', lchan_nr=opts.chan_nr)
|
||||
(data, sw) = self._cmd.card._scc.manage_channel(
|
||||
mode='close', lchan_nr=opts.chan_nr)
|
||||
|
||||
def do_status(self, opts):
|
||||
"""Perform the STATUS command."""
|
||||
|
@ -768,7 +828,8 @@ class Iso7816Commands(CommandSet):
|
|||
"""Perform the SUSPEND UICC command. Only supported on some UICC."""
|
||||
(duration, token, sw) = self._cmd.card._scc.suspend_uicc(min_len_secs=opts.min_duration_secs,
|
||||
max_len_secs=opts.max_duration_secs)
|
||||
self._cmd.poutput('Negotiated Duration: %u secs, Token: %s, SW: %s' % (duration, token, sw))
|
||||
self._cmd.poutput(
|
||||
'Negotiated Duration: %u secs, Token: %s, SW: %s' % (duration, token, sw))
|
||||
|
||||
|
||||
option_parser = argparse.ArgumentParser(prog='pySim-shell', description='interactive SIM card shell',
|
||||
|
@ -778,7 +839,8 @@ argparse_add_reader_args(option_parser)
|
|||
global_group = option_parser.add_argument_group('General Options')
|
||||
global_group.add_argument('--script', metavar='PATH', default=None,
|
||||
help='script with pySim-shell commands to be executed automatically at start-up')
|
||||
global_group.add_argument('--csv', metavar='FILE', default=None, help='Read card data from CSV file')
|
||||
global_group.add_argument('--csv', metavar='FILE',
|
||||
default=None, help='Read card data from CSV file')
|
||||
global_group.add_argument("--card_handler", dest="card_handler_config", metavar="FILE",
|
||||
help="Use automatic card handling machine")
|
||||
|
||||
|
@ -834,7 +896,8 @@ if __name__ == '__main__':
|
|||
traceback.print_exc()
|
||||
print("---------------------8<---------------------")
|
||||
print("(you may still try to recover from this manually by using the 'equip' command.)")
|
||||
print(" it should also be noted that some readers may behave strangely when no card")
|
||||
print(
|
||||
" it should also be noted that some readers may behave strangely when no card")
|
||||
print(" is inserted.)")
|
||||
print("")
|
||||
app = PysimApp(None, None, sl, ch, opts.script)
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -34,29 +34,35 @@ from pySim.tlv import *
|
|||
|
||||
# various BER-TLV encoded Data Objects (DOs)
|
||||
|
||||
|
||||
class AidRefDO(BER_TLV_IE, tag=0x4f):
|
||||
# SEID v1.1 Table 6-3
|
||||
_construct = HexAdapter(GreedyBytes)
|
||||
|
||||
|
||||
class AidRefEmptyDO(BER_TLV_IE, tag=0xc0):
|
||||
# SEID v1.1 Table 6-3
|
||||
pass
|
||||
|
||||
|
||||
class DevAppIdRefDO(BER_TLV_IE, tag=0xc1):
|
||||
# SEID v1.1 Table 6-4
|
||||
_construct = HexAdapter(GreedyBytes)
|
||||
|
||||
|
||||
class PkgRefDO(BER_TLV_IE, tag=0xca):
|
||||
# Android UICC Carrier Privileges specific extension, see https://source.android.com/devices/tech/config/uicc
|
||||
_construct = Struct('package_name_string'/GreedyString("ascii"))
|
||||
|
||||
class RefDO(BER_TLV_IE, tag=0xe1, nested=[AidRefDO,AidRefEmptyDO,DevAppIdRefDO,PkgRefDO]):
|
||||
|
||||
class RefDO(BER_TLV_IE, tag=0xe1, nested=[AidRefDO, AidRefEmptyDO, DevAppIdRefDO, PkgRefDO]):
|
||||
# SEID v1.1 Table 6-5
|
||||
pass
|
||||
|
||||
|
||||
class ApduArDO(BER_TLV_IE, tag=0xd0):
|
||||
# SEID v1.1 Table 6-8
|
||||
def _from_bytes(self, do:bytes):
|
||||
def _from_bytes(self, do: bytes):
|
||||
if len(do) == 1:
|
||||
if do[0] == 0x00:
|
||||
self.decoded = {'generic_access_rule': 'never'}
|
||||
|
@ -76,6 +82,7 @@ class ApduArDO(BER_TLV_IE, tag=0xd0):
|
|||
'mask': b2h(do[offset+4:offset+8])}
|
||||
self.decoded = res
|
||||
return res
|
||||
|
||||
def _to_bytes(self):
|
||||
if 'generic_access_rule' in self.decoded:
|
||||
if self.decoded['generic_access_rule'] == 'never':
|
||||
|
@ -99,94 +106,118 @@ class ApduArDO(BER_TLV_IE, tag=0xd0):
|
|||
res += header_b + mask_b
|
||||
return res
|
||||
|
||||
|
||||
class NfcArDO(BER_TLV_IE, tag=0xd1):
|
||||
# SEID v1.1 Table 6-9
|
||||
_construct = Struct('nfc_event_access_rule'/Enum(Int8ub, never=0, always=1))
|
||||
_construct = Struct('nfc_event_access_rule' /
|
||||
Enum(Int8ub, never=0, always=1))
|
||||
|
||||
|
||||
class PermArDO(BER_TLV_IE, tag=0xdb):
|
||||
# Android UICC Carrier Privileges specific extension, see https://source.android.com/devices/tech/config/uicc
|
||||
_construct = Struct('permissions'/HexAdapter(Bytes(8)))
|
||||
|
||||
|
||||
class ArDO(BER_TLV_IE, tag=0xe3, nested=[ApduArDO, NfcArDO, PermArDO]):
|
||||
# SEID v1.1 Table 6-7
|
||||
pass
|
||||
|
||||
|
||||
class RefArDO(BER_TLV_IE, tag=0xe2, nested=[RefDO, ArDO]):
|
||||
# SEID v1.1 Table 6-6
|
||||
pass
|
||||
|
||||
|
||||
class ResponseAllRefArDO(BER_TLV_IE, tag=0xff40, nested=[RefArDO]):
|
||||
# SEID v1.1 Table 4-2
|
||||
pass
|
||||
|
||||
|
||||
class ResponseArDO(BER_TLV_IE, tag=0xff50, nested=[ArDO]):
|
||||
# SEID v1.1 Table 4-3
|
||||
pass
|
||||
|
||||
|
||||
class ResponseRefreshTagDO(BER_TLV_IE, tag=0xdf20):
|
||||
# SEID v1.1 Table 4-4
|
||||
_construct = Struct('refresh_tag'/HexAdapter(Bytes(8)))
|
||||
|
||||
|
||||
class DeviceInterfaceVersionDO(BER_TLV_IE, tag=0xe6):
|
||||
# SEID v1.1 Table 6-12
|
||||
_construct = Struct('major'/Int8ub, 'minor'/Int8ub, 'patch'/Int8ub)
|
||||
|
||||
|
||||
class DeviceConfigDO(BER_TLV_IE, tag=0xe4, nested=[DeviceInterfaceVersionDO]):
|
||||
# SEID v1.1 Table 6-10
|
||||
pass
|
||||
|
||||
|
||||
class ResponseDeviceConfigDO(BER_TLV_IE, tag=0xff7f, nested=[DeviceConfigDO]):
|
||||
# SEID v1.1 Table 5-14
|
||||
pass
|
||||
|
||||
|
||||
class AramConfigDO(BER_TLV_IE, tag=0xe5, nested=[DeviceInterfaceVersionDO]):
|
||||
# SEID v1.1 Table 6-11
|
||||
pass
|
||||
|
||||
|
||||
class ResponseAramConfigDO(BER_TLV_IE, tag=0xdf21, nested=[AramConfigDO]):
|
||||
# SEID v1.1 Table 4-5
|
||||
pass
|
||||
|
||||
|
||||
class CommandStoreRefArDO(BER_TLV_IE, tag=0xf0, nested=[RefArDO]):
|
||||
# SEID v1.1 Table 5-2
|
||||
pass
|
||||
|
||||
|
||||
class CommandDelete(BER_TLV_IE, tag=0xf1, nested=[AidRefDO, AidRefEmptyDO, RefDO, RefArDO]):
|
||||
# SEID v1.1 Table 5-4
|
||||
pass
|
||||
|
||||
|
||||
class CommandUpdateRefreshTagDO(BER_TLV_IE, tag=0xf2):
|
||||
# SEID V1.1 Table 5-6
|
||||
pass
|
||||
|
||||
|
||||
class CommandRegisterClientAidsDO(BER_TLV_IE, tag=0xf7, nested=[AidRefDO, AidRefEmptyDO]):
|
||||
# SEID v1.1 Table 5-7
|
||||
pass
|
||||
|
||||
|
||||
class CommandGet(BER_TLV_IE, tag=0xf3, nested=[AidRefDO, AidRefEmptyDO]):
|
||||
# SEID v1.1 Table 5-8
|
||||
pass
|
||||
|
||||
|
||||
class CommandGetAll(BER_TLV_IE, tag=0xf4):
|
||||
# SEID v1.1 Table 5-9
|
||||
pass
|
||||
|
||||
|
||||
class CommandGetClientAidsDO(BER_TLV_IE, tag=0xf6):
|
||||
# SEID v1.1 Table 5-10
|
||||
pass
|
||||
|
||||
|
||||
class CommandGetNext(BER_TLV_IE, tag=0xf5):
|
||||
# SEID v1.1 Table 5-11
|
||||
pass
|
||||
|
||||
|
||||
class CommandGetDeviceConfigDO(BER_TLV_IE, tag=0xf8):
|
||||
# SEID v1.1 Table 5-12
|
||||
pass
|
||||
|
||||
|
||||
class ResponseAracAidDO(BER_TLV_IE, tag=0xff70, nested=[AidRefDO, AidRefEmptyDO]):
|
||||
# SEID v1.1 Table 5-13
|
||||
pass
|
||||
|
||||
|
||||
class BlockDO(BER_TLV_IE, tag=0xe7):
|
||||
# SEID v1.1 Table 6-13
|
||||
_construct = Struct('offset'/Int16ub, 'length'/Int8ub)
|
||||
|
@ -197,11 +228,15 @@ class GetCommandDoCollection(TLV_IE_Collection, nested=[RefDO, DeviceConfigDO]):
|
|||
pass
|
||||
|
||||
# SEID v1.1 Table 4-2
|
||||
|
||||
|
||||
class GetResponseDoCollection(TLV_IE_Collection, nested=[ResponseAllRefArDO, ResponseArDO,
|
||||
ResponseRefreshTagDO, ResponseAramConfigDO]):
|
||||
pass
|
||||
|
||||
# SEID v1.1 Table 5-1
|
||||
|
||||
|
||||
class StoreCommandDoCollection(TLV_IE_Collection,
|
||||
nested=[BlockDO, CommandStoreRefArDO, CommandDelete,
|
||||
CommandUpdateRefreshTagDO, CommandRegisterClientAidsDO,
|
||||
|
@ -215,6 +250,7 @@ class StoreResponseDoCollection(TLV_IE_Collection,
|
|||
nested=[ResponseAllRefArDO, ResponseAracAidDO, ResponseDeviceConfigDO]):
|
||||
pass
|
||||
|
||||
|
||||
class ADF_ARAM(CardADF):
|
||||
def __init__(self, aid='a00000015141434c00', name='ADF.ARA-M', fid=None, sfid=None,
|
||||
desc='ARA-M Application'):
|
||||
|
@ -224,7 +260,7 @@ class ADF_ARAM(CardADF):
|
|||
self.add_files(files)
|
||||
|
||||
@staticmethod
|
||||
def xceive_apdu_tlv(tp, hdr:Hexstr, cmd_do, resp_cls, exp_sw='9000'):
|
||||
def xceive_apdu_tlv(tp, hdr: Hexstr, cmd_do, resp_cls, exp_sw='9000'):
|
||||
"""Transceive an APDU with the card, transparently encoding the command data from TLV
|
||||
and decoding the response data tlv."""
|
||||
if cmd_do:
|
||||
|
@ -259,7 +295,8 @@ class ADF_ARAM(CardADF):
|
|||
@staticmethod
|
||||
def get_config(tp, v_major=0, v_minor=0, v_patch=1):
|
||||
cmd_do = DeviceConfigDO()
|
||||
cmd_do.from_dict([{'DeviceInterfaceVersionDO': {'major': v_major, 'minor': v_minor, 'patch': v_patch }}])
|
||||
cmd_do.from_dict([{'DeviceInterfaceVersionDO': {
|
||||
'major': v_major, 'minor': v_minor, 'patch': v_patch}}])
|
||||
return ADF_ARAM.xceive_apdu_tlv(tp, '80cadf21', cmd_do, ResponseAramConfigDO)
|
||||
|
||||
@with_default_category('Application-Specific Commands')
|
||||
|
@ -281,20 +318,30 @@ class ADF_ARAM(CardADF):
|
|||
|
||||
store_ref_ar_do_parse = argparse.ArgumentParser()
|
||||
# REF-DO
|
||||
store_ref_ar_do_parse.add_argument('--device-app-id', required=True, help='Identifies the specific device application that the rule appplies to. Hash of Certificate of Application Provider, or UUID. (20/32 hex bytes)')
|
||||
store_ref_ar_do_parse.add_argument(
|
||||
'--device-app-id', required=True, help='Identifies the specific device application that the rule appplies to. Hash of Certificate of Application Provider, or UUID. (20/32 hex bytes)')
|
||||
aid_grp = store_ref_ar_do_parse.add_mutually_exclusive_group()
|
||||
aid_grp.add_argument('--aid', help='Identifies the specific SE application for which rules are to be stored. Can be a partial AID, containing for example only the RID. (5-16 hex bytes)')
|
||||
aid_grp.add_argument('--aid-empty', action='store_true', help='No specific SE application, applies to all applications')
|
||||
store_ref_ar_do_parse.add_argument('--pkg-ref', help='Full Android Java package name (up to 127 chars ASCII)')
|
||||
aid_grp.add_argument(
|
||||
'--aid', help='Identifies the specific SE application for which rules are to be stored. Can be a partial AID, containing for example only the RID. (5-16 hex bytes)')
|
||||
aid_grp.add_argument('--aid-empty', action='store_true',
|
||||
help='No specific SE application, applies to all applications')
|
||||
store_ref_ar_do_parse.add_argument(
|
||||
'--pkg-ref', help='Full Android Java package name (up to 127 chars ASCII)')
|
||||
# AR-DO
|
||||
apdu_grp = store_ref_ar_do_parse.add_mutually_exclusive_group()
|
||||
apdu_grp.add_argument('--apdu-never', action='store_true', help='APDU access is not allowed')
|
||||
apdu_grp.add_argument('--apdu-always', action='store_true', help='APDU access is allowed')
|
||||
apdu_grp.add_argument('--apdu-filter', help='APDU filter: 4 byte CLA/INS/P1/P2 followed by 4 byte mask (8 hex bytes)')
|
||||
apdu_grp.add_argument(
|
||||
'--apdu-never', action='store_true', help='APDU access is not allowed')
|
||||
apdu_grp.add_argument(
|
||||
'--apdu-always', action='store_true', help='APDU access is allowed')
|
||||
apdu_grp.add_argument(
|
||||
'--apdu-filter', help='APDU filter: 4 byte CLA/INS/P1/P2 followed by 4 byte mask (8 hex bytes)')
|
||||
nfc_grp = store_ref_ar_do_parse.add_mutually_exclusive_group()
|
||||
nfc_grp.add_argument('--nfc-always', action='store_true', help='NFC event access is allowed')
|
||||
nfc_grp.add_argument('--nfc-never', action='store_true', help='NFC event access is not allowed')
|
||||
store_ref_ar_do_parse.add_argument('--android-permissions', help='Android UICC Carrier Privilege Permissions (8 hex bytes)')
|
||||
nfc_grp.add_argument('--nfc-always', action='store_true',
|
||||
help='NFC event access is allowed')
|
||||
nfc_grp.add_argument('--nfc-never', action='store_true',
|
||||
help='NFC event access is not allowed')
|
||||
store_ref_ar_do_parse.add_argument(
|
||||
'--android-permissions', help='Android UICC Carrier Privilege Permissions (8 hex bytes)')
|
||||
|
||||
@cmd2.with_argparser(store_ref_ar_do_parse)
|
||||
def do_aram_store_ref_ar_do(self, opts):
|
||||
|
@ -323,7 +370,7 @@ class ADF_ARAM(CardADF):
|
|||
ar_do_content += [{'NfcArDO': {'nfc_event_access_rule': 'never'}}]
|
||||
if opts.android_permissions:
|
||||
ar_do_content += [{'PermArDO': {'permissions': opts.android_permissions}}]
|
||||
d = [{'RefArDO': [{ 'RefDO': ref_do_content}, {'ArDO': ar_do_content }]}]
|
||||
d = [{'RefArDO': [{'RefDO': ref_do_content}, {'ArDO': ar_do_content}]}]
|
||||
csrado = CommandStoreRefArDO()
|
||||
csrado.from_dict(d)
|
||||
res_do = ADF_ARAM.store_data(self._cmd.card._scc._tp, csrado)
|
||||
|
@ -359,6 +406,7 @@ sw_aram = {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
class CardApplicationARAM(CardApplication):
|
||||
def __init__(self):
|
||||
super().__init__('ARA-M', adf=ADF_ARAM(), sw=sw_aram)
|
||||
|
|
|
@ -30,13 +30,14 @@ import subprocess
|
|||
import sys
|
||||
import yaml
|
||||
|
||||
|
||||
class CardHandlerBase:
|
||||
"""Abstract base class representing a mechanism for card insertion/removal."""
|
||||
|
||||
def __init__(self, sl:LinkBase):
|
||||
def __init__(self, sl: LinkBase):
|
||||
self.sl = sl
|
||||
|
||||
def get(self, first:bool = False):
|
||||
def get(self, first: bool = False):
|
||||
"""Method called when pySim needs a new card to be inserted.
|
||||
|
||||
Args:
|
||||
|
@ -60,7 +61,7 @@ class CardHandlerBase:
|
|||
print("Programming successful: ", end='')
|
||||
self._done()
|
||||
|
||||
def _get(self, first:bool = False):
|
||||
def _get(self, first: bool = False):
|
||||
pass
|
||||
|
||||
def _error(self):
|
||||
|
@ -73,7 +74,7 @@ class CardHandlerBase:
|
|||
class CardHandler(CardHandlerBase):
|
||||
"""Manual card handler: User is prompted to insert/remove card from the reader."""
|
||||
|
||||
def _get(self, first:bool = False):
|
||||
def _get(self, first: bool = False):
|
||||
print("Insert card now (or CTRL-C to cancel)")
|
||||
self.sl.wait_for_card(newcardonly=not first)
|
||||
|
||||
|
@ -91,7 +92,7 @@ class CardHandlerAuto(CardHandlerBase):
|
|||
|
||||
verbose = True
|
||||
|
||||
def __init__(self, sl:LinkBase, config_file:str):
|
||||
def __init__(self, sl: LinkBase, config_file: str):
|
||||
super().__init__(sl)
|
||||
print("Card handler Config-file: " + str(config_file))
|
||||
with open(config_file) as cfg:
|
||||
|
@ -116,7 +117,8 @@ class CardHandlerAuto(CardHandlerBase):
|
|||
def __exec_cmd(self, command):
|
||||
print("Card handler Commandline: " + str(command))
|
||||
|
||||
proc = subprocess.Popen([command], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
proc = subprocess.Popen(
|
||||
[command], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
out = proc.communicate()
|
||||
rc = proc.returncode
|
||||
|
||||
|
@ -128,7 +130,7 @@ class CardHandlerAuto(CardHandlerBase):
|
|||
print("Error: Card handler failure! (rc=" + str(rc) + ")")
|
||||
sys.exit(rc)
|
||||
|
||||
def _get(self, first:bool = False):
|
||||
def _get(self, first: bool = False):
|
||||
print("Transporting card into the reader-bay...")
|
||||
self.__exec_cmd(self.cmds['get'])
|
||||
if self.sl:
|
||||
|
|
|
@ -35,13 +35,15 @@ import csv
|
|||
|
||||
card_key_providers = [] # type: List['CardKeyProvider']
|
||||
|
||||
|
||||
class CardKeyProvider(abc.ABC):
|
||||
"""Base class, not containing any concrete implementation."""
|
||||
|
||||
VALID_FIELD_NAMES = ['ICCID', 'ADM1', 'IMSI', 'PIN1', 'PIN2', 'PUK1', 'PUK2']
|
||||
VALID_FIELD_NAMES = ['ICCID', 'ADM1',
|
||||
'IMSI', 'PIN1', 'PIN2', 'PUK1', 'PUK2']
|
||||
|
||||
# check input parameters, but do nothing concrete yet
|
||||
def _verify_get_data(self, fields:List[str]=[], key:str='ICCID', value:str="") -> Dict[str,str]:
|
||||
def _verify_get_data(self, fields: List[str] = [], key: str = 'ICCID', value: str = "") -> Dict[str, str]:
|
||||
"""Verify multiple fields for identified card.
|
||||
|
||||
Args:
|
||||
|
@ -62,14 +64,14 @@ class CardKeyProvider(abc.ABC):
|
|||
|
||||
return {}
|
||||
|
||||
def get_field(self, field:str, key:str='ICCID', value:str="") -> Optional[str]:
|
||||
def get_field(self, field: str, key: str = 'ICCID', value: str = "") -> Optional[str]:
|
||||
"""get a single field from CSV file using a specified key/value pair"""
|
||||
fields = [field]
|
||||
result = self.get(fields, key, value)
|
||||
return result.get(field)
|
||||
|
||||
@abc.abstractmethod
|
||||
def get(self, fields:List[str], key:str, value:str) -> Dict[str,str]:
|
||||
def get(self, fields: List[str], key: str, value: str) -> Dict[str, str]:
|
||||
"""Get multiple card-individual fields for identified card.
|
||||
|
||||
Args:
|
||||
|
@ -80,12 +82,13 @@ class CardKeyProvider(abc.ABC):
|
|||
dictionary of {field, value} strings for each requested field from 'fields'
|
||||
"""
|
||||
|
||||
|
||||
class CardKeyProviderCsv(CardKeyProvider):
|
||||
"""Card key provider implementation that allows to query against a specified CSV file"""
|
||||
csv_file = None
|
||||
filename = None
|
||||
|
||||
def __init__(self, filename:str):
|
||||
def __init__(self, filename: str):
|
||||
"""
|
||||
Args:
|
||||
filename : file name (path) of CSV file containing card-individual key/data
|
||||
|
@ -95,28 +98,29 @@ class CardKeyProviderCsv(CardKeyProvider):
|
|||
raise RuntimeError("Could not open CSV file '%s'" % filename)
|
||||
self.filename = filename
|
||||
|
||||
def get(self, fields:List[str], key:str, value:str) -> Dict[str,str]:
|
||||
def get(self, fields: List[str], key: str, value: str) -> Dict[str, str]:
|
||||
super()._verify_get_data(fields, key, value)
|
||||
|
||||
self.csv_file.seek(0)
|
||||
cr = csv.DictReader(self.csv_file)
|
||||
if not cr:
|
||||
raise RuntimeError("Could not open DictReader for CSV-File '%s'" % self.filename)
|
||||
cr.fieldnames = [ field.upper() for field in cr.fieldnames ]
|
||||
raise RuntimeError(
|
||||
"Could not open DictReader for CSV-File '%s'" % self.filename)
|
||||
cr.fieldnames = [field.upper() for field in cr.fieldnames]
|
||||
|
||||
rc = {}
|
||||
for row in cr:
|
||||
if row[key] == value:
|
||||
for f in fields:
|
||||
if f in row:
|
||||
rc.update({f : row[f]})
|
||||
rc.update({f: row[f]})
|
||||
else:
|
||||
raise RuntimeError("CSV-File '%s' lacks column '%s'" %
|
||||
(self.filename, f))
|
||||
return rc
|
||||
|
||||
|
||||
def card_key_provider_register(provider:CardKeyProvider, provider_list=card_key_providers):
|
||||
def card_key_provider_register(provider: CardKeyProvider, provider_list=card_key_providers):
|
||||
"""Register a new card key provider.
|
||||
|
||||
Args:
|
||||
|
@ -128,7 +132,7 @@ def card_key_provider_register(provider:CardKeyProvider, provider_list=card_key_
|
|||
provider_list.append(provider)
|
||||
|
||||
|
||||
def card_key_provider_get(fields, key:str, value:str, provider_list=card_key_providers) -> Dict[str,str]:
|
||||
def card_key_provider_get(fields, key: str, value: str, provider_list=card_key_providers) -> Dict[str, str]:
|
||||
"""Query all registered card data providers for card-individual [key] data.
|
||||
|
||||
Args:
|
||||
|
@ -141,14 +145,15 @@ def card_key_provider_get(fields, key:str, value:str, provider_list=card_key_pro
|
|||
"""
|
||||
for p in provider_list:
|
||||
if not isinstance(p, CardKeyProvider):
|
||||
raise ValueError("provider list contains element which is not a card data provier")
|
||||
raise ValueError(
|
||||
"provider list contains element which is not a card data provier")
|
||||
result = p.get(fields, key, value)
|
||||
if result:
|
||||
return result
|
||||
return {}
|
||||
|
||||
|
||||
def card_key_provider_get_field(field:str, key:str, value:str, provider_list=card_key_providers) -> Optional[str]:
|
||||
def card_key_provider_get_field(field: str, key: str, value: str, provider_list=card_key_providers) -> Optional[str]:
|
||||
"""Query all registered card data providers for a single field.
|
||||
|
||||
Args:
|
||||
|
@ -161,7 +166,8 @@ def card_key_provider_get_field(field:str, key:str, value:str, provider_list=car
|
|||
"""
|
||||
for p in provider_list:
|
||||
if not isinstance(p, CardKeyProvider):
|
||||
raise ValueError("provider list contains element which is not a card data provier")
|
||||
raise ValueError(
|
||||
"provider list contains element which is not a card data provier")
|
||||
result = p.get_field(field, key, value)
|
||||
if result:
|
||||
return result
|
||||
|
|
266
pySim/cards.py
266
pySim/cards.py
|
@ -32,7 +32,8 @@ from pySim.utils import *
|
|||
from smartcard.util import toBytes
|
||||
from pytlv.TLV import *
|
||||
|
||||
def format_addr(addr:str, addr_type:str) -> str:
|
||||
|
||||
def format_addr(addr: str, addr_type: str) -> str:
|
||||
"""
|
||||
helper function to format an FQDN (addr_type = '00') or IPv4
|
||||
(addr_type = '01') address string into a printable string that
|
||||
|
@ -40,9 +41,9 @@ def format_addr(addr:str, addr_type:str) -> str:
|
|||
string (addr)
|
||||
"""
|
||||
res = ""
|
||||
if addr_type == '00': #FQDN
|
||||
if addr_type == '00': # FQDN
|
||||
res += "\t%s # %s\n" % (s2h(addr), addr)
|
||||
elif addr_type == '01': #IPv4
|
||||
elif addr_type == '01': # IPv4
|
||||
octets = addr.split(".")
|
||||
addr_hex = ""
|
||||
for o in octets:
|
||||
|
@ -50,6 +51,7 @@ def format_addr(addr:str, addr_type:str) -> str:
|
|||
res += "\t%s # %s\n" % (addr_hex, addr)
|
||||
return res
|
||||
|
||||
|
||||
class SimCard(object):
|
||||
|
||||
name = 'SIM'
|
||||
|
@ -126,7 +128,8 @@ class SimCard(object):
|
|||
size = len(data[0]) // 2
|
||||
hplmn = enc_plmn(mcc, mnc)
|
||||
content = hplmn + access_tech
|
||||
data, sw = self._scc.update_binary(EF['HPLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1))
|
||||
data, sw = self._scc.update_binary(
|
||||
EF['HPLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1))
|
||||
return sw
|
||||
|
||||
def read_oplmn_act(self):
|
||||
|
@ -142,7 +145,8 @@ class SimCard(object):
|
|||
size = len(data[0]) // 2
|
||||
hplmn = enc_plmn(mcc, mnc)
|
||||
content = hplmn + access_tech
|
||||
data, sw = self._scc.update_binary(EF['OPLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1))
|
||||
data, sw = self._scc.update_binary(
|
||||
EF['OPLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1))
|
||||
return sw
|
||||
|
||||
def read_plmn_act(self):
|
||||
|
@ -158,14 +162,16 @@ class SimCard(object):
|
|||
size = len(data[0]) // 2
|
||||
hplmn = enc_plmn(mcc, mnc)
|
||||
content = hplmn + access_tech
|
||||
data, sw = self._scc.update_binary(EF['PLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1))
|
||||
data, sw = self._scc.update_binary(
|
||||
EF['PLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1))
|
||||
return sw
|
||||
|
||||
def update_plmnsel(self, mcc, mnc):
|
||||
data = self._scc.read_binary(EF['PLMNsel'], length=None, offset=0)
|
||||
size = len(data[0]) // 2
|
||||
hplmn = enc_plmn(mcc, mnc)
|
||||
data, sw = self._scc.update_binary(EF['PLMNsel'], hplmn + 'ff' * (size-3))
|
||||
data, sw = self._scc.update_binary(
|
||||
EF['PLMNsel'], hplmn + 'ff' * (size-3))
|
||||
return sw
|
||||
|
||||
def update_smsp(self, smsp):
|
||||
|
@ -193,7 +199,8 @@ class SimCard(object):
|
|||
ad = EF_AD()
|
||||
|
||||
# read from card
|
||||
raw_hex_data, sw = self._scc.read_binary(EF['AD'], length=None, offset=0)
|
||||
raw_hex_data, sw = self._scc.read_binary(
|
||||
EF['AD'], length=None, offset=0)
|
||||
abstract_data = ad.decode_hex(raw_hex_data)
|
||||
|
||||
# perform updates
|
||||
|
@ -231,9 +238,9 @@ class SimCard(object):
|
|||
|
||||
def update_spn(self, name="", show_in_hplmn=False, hide_in_oplmn=False):
|
||||
abstract_data = {
|
||||
'hide_in_oplmn' : hide_in_oplmn,
|
||||
'show_in_hplmn' : show_in_hplmn,
|
||||
'spn' : name,
|
||||
'hide_in_oplmn': hide_in_oplmn,
|
||||
'show_in_hplmn': show_in_hplmn,
|
||||
'spn': name,
|
||||
}
|
||||
content = EF_SPN().encode_hex(abstract_data)
|
||||
data, sw = self._scc.update_binary(EF['SPN'], content)
|
||||
|
@ -321,7 +328,8 @@ class SimCard(object):
|
|||
def erase_record(self, ef, rec_no):
|
||||
"""Erase the contents of a single record"""
|
||||
len = self._scc.record_size(ef)
|
||||
self._scc.update_record(ef, rec_no, "ff" * len, force_len=False, verify=True)
|
||||
self._scc.update_record(ef, rec_no, "ff" * len,
|
||||
force_len=False, verify=True)
|
||||
|
||||
def set_apdu_parameter(self, cla, sel_ctrl):
|
||||
"""Set apdu parameters (class byte and selection control bytes)"""
|
||||
|
@ -332,6 +340,7 @@ class SimCard(object):
|
|||
"""Get apdu parameters (class byte and selection control bytes)"""
|
||||
return (self._scc.cla_byte, self._scc.sel_ctrl)
|
||||
|
||||
|
||||
class UsimCard(SimCard):
|
||||
|
||||
name = 'USIM'
|
||||
|
@ -347,7 +356,8 @@ class UsimCard(SimCard):
|
|||
return (None, sw)
|
||||
|
||||
def update_ehplmn(self, mcc, mnc):
|
||||
data = self._scc.read_binary(EF_USIM_ADF_map['EHPLMN'], length=None, offset=0)
|
||||
data = self._scc.read_binary(
|
||||
EF_USIM_ADF_map['EHPLMN'], length=None, offset=0)
|
||||
size = len(data[0]) // 2
|
||||
ehplmn = enc_plmn(mcc, mnc)
|
||||
data, sw = self._scc.update_binary(EF_USIM_ADF_map['EHPLMN'], ehplmn)
|
||||
|
@ -370,7 +380,8 @@ class UsimCard(SimCard):
|
|||
if len(epdgid) > 0:
|
||||
addr_type = get_addr_type(epdgid)
|
||||
if addr_type == None:
|
||||
raise ValueError("Unknown ePDG Id address type or invalid address provided")
|
||||
raise ValueError(
|
||||
"Unknown ePDG Id address type or invalid address provided")
|
||||
epdgid_tlv = rpad(enc_addr_tlv(epdgid, ('%02x' % addr_type)), size)
|
||||
else:
|
||||
epdgid_tlv = rpad('ff', size)
|
||||
|
@ -386,13 +397,16 @@ class UsimCard(SimCard):
|
|||
return (None, sw)
|
||||
|
||||
def update_ePDGSelection(self, mcc, mnc):
|
||||
(res, sw) = self._scc.read_binary(EF_USIM_ADF_map['ePDGSelection'], length=None, offset=0)
|
||||
(res, sw) = self._scc.read_binary(
|
||||
EF_USIM_ADF_map['ePDGSelection'], length=None, offset=0)
|
||||
if sw == '9000' and (len(mcc) == 0 or len(mnc) == 0):
|
||||
# Reset contents
|
||||
# 80 - Tag value
|
||||
(res, sw) = self._scc.update_binary(EF_USIM_ADF_map['ePDGSelection'], rpad('', len(res)))
|
||||
(res, sw) = self._scc.update_binary(
|
||||
EF_USIM_ADF_map['ePDGSelection'], rpad('', len(res)))
|
||||
elif sw == '9000':
|
||||
(res, sw) = self._scc.update_binary(EF_USIM_ADF_map['ePDGSelection'], enc_ePDGSelection(res, mcc, mnc))
|
||||
(res, sw) = self._scc.update_binary(
|
||||
EF_USIM_ADF_map['ePDGSelection'], enc_ePDGSelection(res, mcc, mnc))
|
||||
return sw
|
||||
|
||||
def read_ust(self):
|
||||
|
@ -407,9 +421,11 @@ class UsimCard(SimCard):
|
|||
(res, sw) = self._scc.read_binary(EF_USIM_ADF_map['UST'])
|
||||
if sw == '9000':
|
||||
content = enc_st(res, service, bit)
|
||||
(res, sw) = self._scc.update_binary(EF_USIM_ADF_map['UST'], content)
|
||||
(res, sw) = self._scc.update_binary(
|
||||
EF_USIM_ADF_map['UST'], content)
|
||||
return sw
|
||||
|
||||
|
||||
class IsimCard(SimCard):
|
||||
|
||||
name = 'ISIM'
|
||||
|
@ -429,29 +445,33 @@ class IsimCard(SimCard):
|
|||
addr = None
|
||||
addr_type = None
|
||||
content = format_addr(addr, addr_type)
|
||||
pcscf_recs += "%s" % (len(content) and content or '\tNot available\n')
|
||||
pcscf_recs += "%s" % (len(content)
|
||||
and content or '\tNot available\n')
|
||||
else:
|
||||
pcscf_recs += "\tP-CSCF: Can't read, response code = %s\n" % (sw)
|
||||
pcscf_recs += "\tP-CSCF: Can't read, response code = %s\n" % (
|
||||
sw)
|
||||
return pcscf_recs
|
||||
|
||||
def update_pcscf(self, pcscf):
|
||||
if len(pcscf) > 0:
|
||||
addr_type = get_addr_type(pcscf)
|
||||
if addr_type == None:
|
||||
raise ValueError("Unknown PCSCF address type or invalid address provided")
|
||||
raise ValueError(
|
||||
"Unknown PCSCF address type or invalid address provided")
|
||||
content = enc_addr_tlv(pcscf, ('%02x' % addr_type))
|
||||
else:
|
||||
# Just the tag value
|
||||
content = '80'
|
||||
rec_size_bytes = self._scc.record_size(EF_ISIM_ADF_map['PCSCF'])
|
||||
pcscf_tlv = rpad(content, rec_size_bytes*2)
|
||||
data, sw = self._scc.update_record(EF_ISIM_ADF_map['PCSCF'], 1, pcscf_tlv)
|
||||
data, sw = self._scc.update_record(
|
||||
EF_ISIM_ADF_map['PCSCF'], 1, pcscf_tlv)
|
||||
return sw
|
||||
|
||||
def read_domain(self):
|
||||
(res, sw) = self._scc.read_binary(EF_ISIM_ADF_map['DOMAIN'])
|
||||
if sw == '9000':
|
||||
# Skip the inital tag value ('80') byte and get length of contents
|
||||
# Skip the initial tag value ('80') byte and get length of contents
|
||||
length = int(res[2:4], 16)
|
||||
content = h2s(res[4:4+(length*2)])
|
||||
return (content, sw)
|
||||
|
@ -472,13 +492,14 @@ class IsimCard(SimCard):
|
|||
content = tlv.build({'80': hex_str})
|
||||
|
||||
bin_size_bytes = self._scc.binary_size(EF_ISIM_ADF_map['DOMAIN'])
|
||||
data, sw = self._scc.update_binary(EF_ISIM_ADF_map['DOMAIN'], rpad(content, bin_size_bytes*2))
|
||||
data, sw = self._scc.update_binary(
|
||||
EF_ISIM_ADF_map['DOMAIN'], rpad(content, bin_size_bytes*2))
|
||||
return sw
|
||||
|
||||
def read_impi(self):
|
||||
(res, sw) = self._scc.read_binary(EF_ISIM_ADF_map['IMPI'])
|
||||
if sw == '9000':
|
||||
# Skip the inital tag value ('80') byte and get length of contents
|
||||
# Skip the initial tag value ('80') byte and get length of contents
|
||||
length = int(res[2:4], 16)
|
||||
content = h2s(res[4:4+(length*2)])
|
||||
return (content, sw)
|
||||
|
@ -494,7 +515,8 @@ class IsimCard(SimCard):
|
|||
content = tlv.build({'80': hex_str})
|
||||
|
||||
bin_size_bytes = self._scc.binary_size(EF_ISIM_ADF_map['IMPI'])
|
||||
data, sw = self._scc.update_binary(EF_ISIM_ADF_map['IMPI'], rpad(content, bin_size_bytes*2))
|
||||
data, sw = self._scc.update_binary(
|
||||
EF_ISIM_ADF_map['IMPI'], rpad(content, bin_size_bytes*2))
|
||||
return sw
|
||||
|
||||
def read_impu(self):
|
||||
|
@ -503,12 +525,14 @@ class IsimCard(SimCard):
|
|||
for i in range(0, rec_cnt):
|
||||
(res, sw) = self._scc.read_record(EF_ISIM_ADF_map['IMPU'], i + 1)
|
||||
if sw == '9000':
|
||||
# Skip the inital tag value ('80') byte and get length of contents
|
||||
# Skip the initial tag value ('80') byte and get length of contents
|
||||
length = int(res[2:4], 16)
|
||||
content = h2s(res[4:4+(length*2)])
|
||||
impu_recs += "\t%s\n" % (len(content) and content or 'Not available')
|
||||
impu_recs += "\t%s\n" % (len(content)
|
||||
and content or 'Not available')
|
||||
else:
|
||||
impu_recs += "IMS public user identity: Can't read, response code = %s\n" % (sw)
|
||||
impu_recs += "IMS public user identity: Can't read, response code = %s\n" % (
|
||||
sw)
|
||||
return impu_recs
|
||||
|
||||
def update_impu(self, impu=None):
|
||||
|
@ -521,23 +545,28 @@ class IsimCard(SimCard):
|
|||
|
||||
rec_size_bytes = self._scc.record_size(EF_ISIM_ADF_map['IMPU'])
|
||||
impu_tlv = rpad(content, rec_size_bytes*2)
|
||||
data, sw = self._scc.update_record(EF_ISIM_ADF_map['IMPU'], 1, impu_tlv)
|
||||
data, sw = self._scc.update_record(
|
||||
EF_ISIM_ADF_map['IMPU'], 1, impu_tlv)
|
||||
return sw
|
||||
|
||||
def read_iari(self):
|
||||
rec_cnt = self._scc.record_count(EF_ISIM_ADF_map['UICCIARI'])
|
||||
uiari_recs = ""
|
||||
for i in range(0, rec_cnt):
|
||||
(res, sw) = self._scc.read_record(EF_ISIM_ADF_map['UICCIARI'], i + 1)
|
||||
(res, sw) = self._scc.read_record(
|
||||
EF_ISIM_ADF_map['UICCIARI'], i + 1)
|
||||
if sw == '9000':
|
||||
# Skip the inital tag value ('80') byte and get length of contents
|
||||
# Skip the initial tag value ('80') byte and get length of contents
|
||||
length = int(res[2:4], 16)
|
||||
content = h2s(res[4:4+(length*2)])
|
||||
uiari_recs += "\t%s\n" % (len(content) and content or 'Not available')
|
||||
uiari_recs += "\t%s\n" % (len(content)
|
||||
and content or 'Not available')
|
||||
else:
|
||||
uiari_recs += "UICC IARI: Can't read, response code = %s\n" % (sw)
|
||||
uiari_recs += "UICC IARI: Can't read, response code = %s\n" % (
|
||||
sw)
|
||||
return uiari_recs
|
||||
|
||||
|
||||
class MagicSimBase(abc.ABC, SimCard):
|
||||
"""
|
||||
Theses cards uses several record based EFs to store the provider infos,
|
||||
|
@ -558,7 +587,7 @@ class MagicSimBase(abc.ABC, SimCard):
|
|||
|
||||
"""
|
||||
|
||||
_files = { } # type: Dict[str, Tuple[str, int, bool]]
|
||||
_files = {} # type: Dict[str, Tuple[str, int, bool]]
|
||||
_ki_file = None # type: Optional[str]
|
||||
|
||||
@classmethod
|
||||
|
@ -583,7 +612,7 @@ class MagicSimBase(abc.ABC, SimCard):
|
|||
|
||||
r = self._scc.select_path(['3f00', '7f4d', f[0]])
|
||||
rec_len = int(r[-1][28:30], 16)
|
||||
tlen = int(r[-1][4:8],16)
|
||||
tlen = int(r[-1][4:8], 16)
|
||||
rec_cnt = (tlen // rec_len) - 1
|
||||
|
||||
if (rec_cnt < 1) or (rec_len != f[1]):
|
||||
|
@ -600,7 +629,8 @@ class MagicSimBase(abc.ABC, SimCard):
|
|||
|
||||
# Operator name ( 3f00/7f4d/8f0c )
|
||||
self._scc.update_record(self._files['name'][0], 2,
|
||||
rpad(b2h(p['name']), 32) + ('%02x' % len(p['name'])) + '01'
|
||||
rpad(b2h(p['name']), 32) + ('%02x' %
|
||||
len(p['name'])) + '01'
|
||||
)
|
||||
|
||||
# ICCID/IMSI/Ki/HPLMN ( 3f00/7f4d/8f0d )
|
||||
|
@ -621,13 +651,13 @@ class MagicSimBase(abc.ABC, SimCard):
|
|||
v += self._ki_file + '10' + p['ki']
|
||||
|
||||
# PLMN_Sel
|
||||
v+= '6f30' + '18' + rpad(hplmn, 36)
|
||||
v += '6f30' + '18' + rpad(hplmn, 36)
|
||||
|
||||
# ACC
|
||||
# This doesn't work with "fake" SuperSIM cards,
|
||||
# but will hopefully work with real SuperSIMs.
|
||||
if p.get('acc') is not None:
|
||||
v+= '6f78' + '02' + lpad(p['acc'], 4)
|
||||
v += '6f78' + '02' + lpad(p['acc'], 4)
|
||||
|
||||
self._scc.update_record(self._files['b_ef'][0], 1,
|
||||
rpad(v, self._files['b_ef'][1]*2)
|
||||
|
@ -655,7 +685,7 @@ class MagicSimBase(abc.ABC, SimCard):
|
|||
df[v[0]] = (fv, ofs)
|
||||
|
||||
# Write
|
||||
for n in range(0,self._get_count()):
|
||||
for n in range(0, self._get_count()):
|
||||
for k, (msg, ofs) in df.items():
|
||||
self._scc.update_record(['3f00', '7f4d', k], n + ofs, msg)
|
||||
|
||||
|
@ -665,9 +695,9 @@ class SuperSim(MagicSimBase):
|
|||
name = 'supersim'
|
||||
|
||||
_files = {
|
||||
'name' : ('8f0c', 18, True),
|
||||
'b_ef' : ('8f0d', 74, True),
|
||||
'r_ef' : ('8f0e', 50, True),
|
||||
'name': ('8f0c', 18, True),
|
||||
'b_ef': ('8f0d', 74, True),
|
||||
'r_ef': ('8f0e', 50, True),
|
||||
}
|
||||
|
||||
_ki_file = None
|
||||
|
@ -678,9 +708,9 @@ class MagicSim(MagicSimBase):
|
|||
name = 'magicsim'
|
||||
|
||||
_files = {
|
||||
'name' : ('8f0c', 18, True),
|
||||
'b_ef' : ('8f0d', 130, True),
|
||||
'r_ef' : ('8f0e', 102, False),
|
||||
'name': ('8f0c', 18, True),
|
||||
'b_ef': ('8f0d', 130, True),
|
||||
'r_ef': ('8f0e', 102, False),
|
||||
}
|
||||
|
||||
_ki_file = '6f1b'
|
||||
|
@ -713,7 +743,7 @@ class FakeMagicSim(SimCard):
|
|||
|
||||
r = self._scc.select_path(['3f00', '000c'])
|
||||
rec_len = int(r[-1][28:30], 16)
|
||||
tlen = int(r[-1][4:8],16)
|
||||
tlen = int(r[-1][4:8], 16)
|
||||
rec_cnt = (tlen // rec_len) - 1
|
||||
|
||||
if (rec_cnt < 1) or (rec_len != 0x5a):
|
||||
|
@ -830,6 +860,7 @@ class SysmoSIMgr1(GrcardSim):
|
|||
return None
|
||||
return None
|
||||
|
||||
|
||||
class SysmoUSIMgr1(UsimCard):
|
||||
"""
|
||||
sysmocom sysmoUSIM-GR1
|
||||
|
@ -845,10 +876,11 @@ class SysmoUSIMgr1(UsimCard):
|
|||
# TODO: check if verify_chv could be used or what it needs
|
||||
# self._scc.verify_chv(0x0A, [0x33,0x32,0x32,0x31,0x33,0x32,0x33,0x32])
|
||||
# Unlock the card..
|
||||
data, sw = self._scc._tp.send_apdu_checksw("0020000A083332323133323332")
|
||||
data, sw = self._scc._tp.send_apdu_checksw(
|
||||
"0020000A083332323133323332")
|
||||
|
||||
# TODO: move into SimCardCommands
|
||||
par = ( p['ki'] + # 16b K
|
||||
par = (p['ki'] + # 16b K
|
||||
p['opc'] + # 32b OPC
|
||||
enc_iccid(p['iccid']) + # 10b ICCID
|
||||
enc_imsi(p['imsi']) # 9b IMSI_len + id_type(9) + IMSI
|
||||
|
@ -941,7 +973,7 @@ class SysmoUSIMSJS1(UsimCard):
|
|||
def __init__(self, ssc):
|
||||
super(SysmoUSIMSJS1, self).__init__(ssc)
|
||||
self._scc.cla_byte = "00"
|
||||
self._scc.sel_ctrl = "0004" #request an FCP
|
||||
self._scc.sel_ctrl = "0004" # request an FCP
|
||||
|
||||
@classmethod
|
||||
def autodetect(kls, scc):
|
||||
|
@ -956,7 +988,8 @@ class SysmoUSIMSJS1(UsimCard):
|
|||
def verify_adm(self, key):
|
||||
# authenticate as ADM using default key (written on the card..)
|
||||
if not key:
|
||||
raise ValueError("Please provide a PIN-ADM as there is no default one")
|
||||
raise ValueError(
|
||||
"Please provide a PIN-ADM as there is no default one")
|
||||
(res, sw) = self._scc.verify_chv(0x0A, key)
|
||||
return sw
|
||||
|
||||
|
@ -994,25 +1027,25 @@ class SysmoUSIMSJS1(UsimCard):
|
|||
if p.get('mcc') and p.get('mnc'):
|
||||
sw = self.update_plmnsel(p['mcc'], p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming PLMNsel failed with code %s"%sw)
|
||||
print("Programming PLMNsel failed with code %s" % sw)
|
||||
|
||||
# EF.PLMNwAcT
|
||||
if p.get('mcc') and p.get('mnc'):
|
||||
sw = self.update_plmn_act(p['mcc'], p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming PLMNwAcT failed with code %s"%sw)
|
||||
print("Programming PLMNwAcT failed with code %s" % sw)
|
||||
|
||||
# EF.OPLMNwAcT
|
||||
if p.get('mcc') and p.get('mnc'):
|
||||
sw = self.update_oplmn_act(p['mcc'], p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming OPLMNwAcT failed with code %s"%sw)
|
||||
print("Programming OPLMNwAcT failed with code %s" % sw)
|
||||
|
||||
# EF.HPLMNwAcT
|
||||
if p.get('mcc') and p.get('mnc'):
|
||||
sw = self.update_hplmn_act(p['mcc'], p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming HPLMNwAcT failed with code %s"%sw)
|
||||
print("Programming HPLMNwAcT failed with code %s" % sw)
|
||||
|
||||
# EF.AD
|
||||
if (p.get('mcc') and p.get('mnc')) or p.get('opmode'):
|
||||
|
@ -1022,12 +1055,13 @@ class SysmoUSIMSJS1(UsimCard):
|
|||
mnc = None
|
||||
sw = self.update_ad(mnc=mnc, opmode=p.get('opmode'))
|
||||
if sw != '9000':
|
||||
print("Programming AD failed with code %s"%sw)
|
||||
print("Programming AD failed with code %s" % sw)
|
||||
|
||||
# EF.SMSP
|
||||
if p.get('smsp'):
|
||||
r = self._scc.select_path(['3f00', '7f10'])
|
||||
data, sw = self._scc.update_record('6f42', 1, lpad(p['smsp'], 104), force_len=True)
|
||||
data, sw = self._scc.update_record(
|
||||
'6f42', 1, lpad(p['smsp'], 104), force_len=True)
|
||||
|
||||
# EF.MSISDN
|
||||
# TODO: Alpha Identifier (currently 'ff'O * 20)
|
||||
|
@ -1069,7 +1103,6 @@ class FairwavesSIM(UsimCard):
|
|||
self._adm_chv_num = 0x11
|
||||
self._adm2_chv_num = 0x12
|
||||
|
||||
|
||||
@classmethod
|
||||
def autodetect(kls, scc):
|
||||
try:
|
||||
|
@ -1080,7 +1113,6 @@ class FairwavesSIM(UsimCard):
|
|||
return None
|
||||
return None
|
||||
|
||||
|
||||
def verify_adm2(self, key):
|
||||
'''
|
||||
Authenticate with ADM2 key.
|
||||
|
@ -1093,7 +1125,6 @@ class FairwavesSIM(UsimCard):
|
|||
(res, sw) = self._scc.verify_chv(self._adm2_chv_num, key)
|
||||
return sw
|
||||
|
||||
|
||||
def read_ki(self):
|
||||
"""
|
||||
Read Ki in proprietary file.
|
||||
|
@ -1102,7 +1133,6 @@ class FairwavesSIM(UsimCard):
|
|||
"""
|
||||
return self._scc.read_binary(self._EF['Ki'])
|
||||
|
||||
|
||||
def update_ki(self, ki):
|
||||
"""
|
||||
Set Ki in proprietary file.
|
||||
|
@ -1112,7 +1142,6 @@ class FairwavesSIM(UsimCard):
|
|||
data, sw = self._scc.update_binary(self._EF['Ki'], ki)
|
||||
return sw
|
||||
|
||||
|
||||
def read_op_opc(self):
|
||||
"""
|
||||
Read Ki in proprietary file.
|
||||
|
@ -1123,7 +1152,6 @@ class FairwavesSIM(UsimCard):
|
|||
type = 'OP' if ef[0:2] == '00' else 'OPC'
|
||||
return ((type, ef[2:]), sw)
|
||||
|
||||
|
||||
def update_op(self, op):
|
||||
"""
|
||||
Set OP in proprietary file.
|
||||
|
@ -1134,7 +1162,6 @@ class FairwavesSIM(UsimCard):
|
|||
data, sw = self._scc.update_binary(self._EF['OP/OPC'], content)
|
||||
return sw
|
||||
|
||||
|
||||
def update_opc(self, opc):
|
||||
"""
|
||||
Set OPC in proprietary file.
|
||||
|
@ -1166,35 +1193,37 @@ class FairwavesSIM(UsimCard):
|
|||
def _program(self, p):
|
||||
# authenticate as ADM1
|
||||
if not p['pin_adm']:
|
||||
raise ValueError("Please provide a PIN-ADM as there is no default one")
|
||||
raise ValueError(
|
||||
"Please provide a PIN-ADM as there is no default one")
|
||||
self.verify_adm(h2b(p['pin_adm']))
|
||||
|
||||
# TODO: Set operator name
|
||||
if p.get('smsp') is not None:
|
||||
sw = self.update_smsp(p['smsp'])
|
||||
if sw != '9000':
|
||||
print("Programming SMSP failed with code %s"%sw)
|
||||
print("Programming SMSP failed with code %s" % sw)
|
||||
# This SIM doesn't support changing ICCID
|
||||
if p.get('mcc') is not None and p.get('mnc') is not None:
|
||||
sw = self.update_hplmn_act(p['mcc'], p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming MCC/MNC failed with code %s"%sw)
|
||||
print("Programming MCC/MNC failed with code %s" % sw)
|
||||
if p.get('imsi') is not None:
|
||||
sw = self.update_imsi(p['imsi'])
|
||||
if sw != '9000':
|
||||
print("Programming IMSI failed with code %s"%sw)
|
||||
print("Programming IMSI failed with code %s" % sw)
|
||||
if p.get('ki') is not None:
|
||||
sw = self.update_ki(p['ki'])
|
||||
if sw != '9000':
|
||||
print("Programming Ki failed with code %s"%sw)
|
||||
print("Programming Ki failed with code %s" % sw)
|
||||
if p.get('opc') is not None:
|
||||
sw = self.update_opc(p['opc'])
|
||||
if sw != '9000':
|
||||
print("Programming OPC failed with code %s"%sw)
|
||||
print("Programming OPC failed with code %s" % sw)
|
||||
if p.get('acc') is not None:
|
||||
sw = self.update_acc(p['acc'])
|
||||
if sw != '9000':
|
||||
print("Programming ACC failed with code %s"%sw)
|
||||
print("Programming ACC failed with code %s" % sw)
|
||||
|
||||
|
||||
class OpenCellsSim(SimCard):
|
||||
"""
|
||||
|
@ -1208,7 +1237,6 @@ class OpenCellsSim(SimCard):
|
|||
super(OpenCellsSim, self).__init__(ssc)
|
||||
self._adm_chv_num = 0x0A
|
||||
|
||||
|
||||
@classmethod
|
||||
def autodetect(kls, scc):
|
||||
try:
|
||||
|
@ -1219,10 +1247,10 @@ class OpenCellsSim(SimCard):
|
|||
return None
|
||||
return None
|
||||
|
||||
|
||||
def program(self, p):
|
||||
if not p['pin_adm']:
|
||||
raise ValueError("Please provide a PIN-ADM as there is no default one")
|
||||
raise ValueError(
|
||||
"Please provide a PIN-ADM as there is no default one")
|
||||
self._scc.verify_chv(0x0A, h2b(p['pin_adm']))
|
||||
|
||||
# select MF
|
||||
|
@ -1245,6 +1273,7 @@ class OpenCellsSim(SimCard):
|
|||
# write EF.IMSI
|
||||
data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi']))
|
||||
|
||||
|
||||
class WavemobileSim(UsimCard):
|
||||
"""
|
||||
WavemobileSim
|
||||
|
@ -1257,7 +1286,7 @@ class WavemobileSim(UsimCard):
|
|||
super(WavemobileSim, self).__init__(ssc)
|
||||
self._adm_chv_num = 0x0A
|
||||
self._scc.cla_byte = "00"
|
||||
self._scc.sel_ctrl = "0004" #request an FCP
|
||||
self._scc.sel_ctrl = "0004" # request an FCP
|
||||
|
||||
@classmethod
|
||||
def autodetect(kls, scc):
|
||||
|
@ -1271,59 +1300,63 @@ class WavemobileSim(UsimCard):
|
|||
|
||||
def program(self, p):
|
||||
if not p['pin_adm']:
|
||||
raise ValueError("Please provide a PIN-ADM as there is no default one")
|
||||
raise ValueError(
|
||||
"Please provide a PIN-ADM as there is no default one")
|
||||
self.verify_adm(h2b(p['pin_adm']))
|
||||
|
||||
# EF.ICCID
|
||||
# TODO: Add programming of the ICCID
|
||||
if p.get('iccid'):
|
||||
print("Warning: Programming of the ICCID is not implemented for this type of card.")
|
||||
print(
|
||||
"Warning: Programming of the ICCID is not implemented for this type of card.")
|
||||
|
||||
# KI (Presumably a propritary file)
|
||||
# TODO: Add programming of KI
|
||||
if p.get('ki'):
|
||||
print("Warning: Programming of the KI is not implemented for this type of card.")
|
||||
print(
|
||||
"Warning: Programming of the KI is not implemented for this type of card.")
|
||||
|
||||
# OPc (Presumably a propritary file)
|
||||
# TODO: Add programming of OPc
|
||||
if p.get('opc'):
|
||||
print("Warning: Programming of the OPc is not implemented for this type of card.")
|
||||
print(
|
||||
"Warning: Programming of the OPc is not implemented for this type of card.")
|
||||
|
||||
# EF.SMSP
|
||||
if p.get('smsp'):
|
||||
sw = self.update_smsp(p['smsp'])
|
||||
if sw != '9000':
|
||||
print("Programming SMSP failed with code %s"%sw)
|
||||
print("Programming SMSP failed with code %s" % sw)
|
||||
|
||||
# EF.IMSI
|
||||
if p.get('imsi'):
|
||||
sw = self.update_imsi(p['imsi'])
|
||||
if sw != '9000':
|
||||
print("Programming IMSI failed with code %s"%sw)
|
||||
print("Programming IMSI failed with code %s" % sw)
|
||||
|
||||
# EF.ACC
|
||||
if p.get('acc'):
|
||||
sw = self.update_acc(p['acc'])
|
||||
if sw != '9000':
|
||||
print("Programming ACC failed with code %s"%sw)
|
||||
print("Programming ACC failed with code %s" % sw)
|
||||
|
||||
# EF.PLMNsel
|
||||
if p.get('mcc') and p.get('mnc'):
|
||||
sw = self.update_plmnsel(p['mcc'], p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming PLMNsel failed with code %s"%sw)
|
||||
print("Programming PLMNsel failed with code %s" % sw)
|
||||
|
||||
# EF.PLMNwAcT
|
||||
if p.get('mcc') and p.get('mnc'):
|
||||
sw = self.update_plmn_act(p['mcc'], p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming PLMNwAcT failed with code %s"%sw)
|
||||
print("Programming PLMNwAcT failed with code %s" % sw)
|
||||
|
||||
# EF.OPLMNwAcT
|
||||
if p.get('mcc') and p.get('mnc'):
|
||||
sw = self.update_oplmn_act(p['mcc'], p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming OPLMNwAcT failed with code %s"%sw)
|
||||
print("Programming OPLMNwAcT failed with code %s" % sw)
|
||||
|
||||
# EF.AD
|
||||
if (p.get('mcc') and p.get('mnc')) or p.get('opmode'):
|
||||
|
@ -1333,7 +1366,7 @@ class WavemobileSim(UsimCard):
|
|||
mnc = None
|
||||
sw = self.update_ad(mnc=mnc, opmode=p.get('opmode'))
|
||||
if sw != '9000':
|
||||
print("Programming AD failed with code %s"%sw)
|
||||
print("Programming AD failed with code %s" % sw)
|
||||
|
||||
return None
|
||||
|
||||
|
@ -1348,7 +1381,7 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
|
|||
def __init__(self, ssc):
|
||||
super(SysmoISIMSJA2, self).__init__(ssc)
|
||||
self._scc.cla_byte = "00"
|
||||
self._scc.sel_ctrl = "0004" #request an FCP
|
||||
self._scc.sel_ctrl = "0004" # request an FCP
|
||||
|
||||
@classmethod
|
||||
def autodetect(kls, scc):
|
||||
|
@ -1374,7 +1407,8 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
|
|||
def verify_adm(self, key):
|
||||
# authenticate as ADM using default key (written on the card..)
|
||||
if not key:
|
||||
raise ValueError("Please provide a PIN-ADM as there is no default one")
|
||||
raise ValueError(
|
||||
"Please provide a PIN-ADM as there is no default one")
|
||||
(res, sw) = self._scc.verify_chv(0x0A, key)
|
||||
return sw
|
||||
|
||||
|
@ -1386,7 +1420,8 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
|
|||
# license management, so the ICCID must be kept at its factory
|
||||
# setting!
|
||||
if p.get('iccid'):
|
||||
print("Warning: Programming of the ICCID is not implemented for this type of card.")
|
||||
print(
|
||||
"Warning: Programming of the ICCID is not implemented for this type of card.")
|
||||
|
||||
# select DF_GSM
|
||||
self._scc.select_path(['7f20'])
|
||||
|
@ -1403,25 +1438,25 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
|
|||
if p.get('mcc') and p.get('mnc'):
|
||||
sw = self.update_plmnsel(p['mcc'], p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming PLMNsel failed with code %s"%sw)
|
||||
print("Programming PLMNsel failed with code %s" % sw)
|
||||
|
||||
# EF.PLMNwAcT
|
||||
if p.get('mcc') and p.get('mnc'):
|
||||
sw = self.update_plmn_act(p['mcc'], p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming PLMNwAcT failed with code %s"%sw)
|
||||
print("Programming PLMNwAcT failed with code %s" % sw)
|
||||
|
||||
# EF.OPLMNwAcT
|
||||
if p.get('mcc') and p.get('mnc'):
|
||||
sw = self.update_oplmn_act(p['mcc'], p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming OPLMNwAcT failed with code %s"%sw)
|
||||
print("Programming OPLMNwAcT failed with code %s" % sw)
|
||||
|
||||
# EF.HPLMNwAcT
|
||||
if p.get('mcc') and p.get('mnc'):
|
||||
sw = self.update_hplmn_act(p['mcc'], p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming HPLMNwAcT failed with code %s"%sw)
|
||||
print("Programming HPLMNwAcT failed with code %s" % sw)
|
||||
|
||||
# EF.AD
|
||||
if (p.get('mcc') and p.get('mnc')) or p.get('opmode'):
|
||||
|
@ -1431,12 +1466,13 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
|
|||
mnc = None
|
||||
sw = self.update_ad(mnc=mnc, opmode=p.get('opmode'))
|
||||
if sw != '9000':
|
||||
print("Programming AD failed with code %s"%sw)
|
||||
print("Programming AD failed with code %s" % sw)
|
||||
|
||||
# EF.SMSP
|
||||
if p.get('smsp'):
|
||||
r = self._scc.select_path(['3f00', '7f10'])
|
||||
data, sw = self._scc.update_record('6f42', 1, lpad(p['smsp'], 104), force_len=True)
|
||||
data, sw = self._scc.update_record(
|
||||
'6f42', 1, lpad(p['smsp'], 104), force_len=True)
|
||||
|
||||
# EF.MSISDN
|
||||
# TODO: Alpha Identifier (currently 'ff'O * 20)
|
||||
|
@ -1447,13 +1483,14 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
|
|||
content = 'ff' * 20 + msisdn
|
||||
|
||||
r = self._scc.select_path(['3f00', '7f10'])
|
||||
data, sw = self._scc.update_record('6F40', 1, content, force_len=True)
|
||||
data, sw = self._scc.update_record(
|
||||
'6F40', 1, content, force_len=True)
|
||||
|
||||
# EF.ACC
|
||||
if p.get('acc'):
|
||||
sw = self.update_acc(p['acc'])
|
||||
if sw != '9000':
|
||||
print("Programming ACC failed with code %s"%sw)
|
||||
print("Programming ACC failed with code %s" % sw)
|
||||
|
||||
# Populate AIDs
|
||||
self.read_aids()
|
||||
|
@ -1482,8 +1519,7 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
|
|||
else:
|
||||
sw = self.update_pcscf("")
|
||||
if sw != '9000':
|
||||
print("Programming P-CSCF failed with code %s"%sw)
|
||||
|
||||
print("Programming P-CSCF failed with code %s" % sw)
|
||||
|
||||
# update EF.DOMAIN in ADF.ISIM
|
||||
if self.file_exists(EF_ISIM_ADF_map['DOMAIN']):
|
||||
|
@ -1493,7 +1529,8 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
|
|||
sw = self.update_domain()
|
||||
|
||||
if sw != '9000':
|
||||
print("Programming Home Network Domain Name failed with code %s"%sw)
|
||||
print(
|
||||
"Programming Home Network Domain Name failed with code %s" % sw)
|
||||
|
||||
# update EF.IMPI in ADF.ISIM
|
||||
# TODO: Validate IMPI input
|
||||
|
@ -1503,7 +1540,7 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
|
|||
else:
|
||||
sw = self.update_impi()
|
||||
if sw != '9000':
|
||||
print("Programming IMPI failed with code %s"%sw)
|
||||
print("Programming IMPI failed with code %s" % sw)
|
||||
|
||||
# update EF.IMPU in ADF.ISIM
|
||||
# TODO: Validate IMPU input
|
||||
|
@ -1514,7 +1551,7 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
|
|||
else:
|
||||
sw = self.update_impu()
|
||||
if sw != '9000':
|
||||
print("Programming IMPU failed with code %s"%sw)
|
||||
print("Programming IMPU failed with code %s" % sw)
|
||||
|
||||
data, sw = self.select_adf_by_aid(adf="usim")
|
||||
if sw == '9000':
|
||||
|
@ -1529,7 +1566,7 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
|
|||
if p.get('mcc') and p.get('mnc'):
|
||||
sw = self.update_ehplmn(p['mcc'], p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming EHPLMN failed with code %s"%sw)
|
||||
print("Programming EHPLMN failed with code %s" % sw)
|
||||
|
||||
# update EF.ePDGId in ADF.USIM
|
||||
if self.file_exists(EF_USIM_ADF_map['ePDGId']):
|
||||
|
@ -1538,18 +1575,18 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
|
|||
else:
|
||||
sw = self.update_epdgid("")
|
||||
if sw != '9000':
|
||||
print("Programming ePDGId failed with code %s"%sw)
|
||||
print("Programming ePDGId failed with code %s" % sw)
|
||||
|
||||
# update EF.ePDGSelection in ADF.USIM
|
||||
if self.file_exists(EF_USIM_ADF_map['ePDGSelection']):
|
||||
if p.get('epdgSelection'):
|
||||
epdg_plmn = p['epdgSelection']
|
||||
sw = self.update_ePDGSelection(epdg_plmn[:3], epdg_plmn[3:])
|
||||
sw = self.update_ePDGSelection(
|
||||
epdg_plmn[:3], epdg_plmn[3:])
|
||||
else:
|
||||
sw = self.update_ePDGSelection("", "")
|
||||
if sw != '9000':
|
||||
print("Programming ePDGSelection failed with code %s"%sw)
|
||||
|
||||
print("Programming ePDGSelection failed with code %s" % sw)
|
||||
|
||||
# After successfully programming EF.ePDGId and EF.ePDGSelection,
|
||||
# Set service 106 and 107 as available in EF.UST
|
||||
|
@ -1558,28 +1595,29 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
|
|||
if p.get('epdgSelection') and p.get('epdgid'):
|
||||
sw = self.update_ust(106, 1)
|
||||
if sw != '9000':
|
||||
print("Programming UST failed with code %s"%sw)
|
||||
print("Programming UST failed with code %s" % sw)
|
||||
sw = self.update_ust(107, 1)
|
||||
if sw != '9000':
|
||||
print("Programming UST failed with code %s"%sw)
|
||||
print("Programming UST failed with code %s" % sw)
|
||||
|
||||
sw = self.update_ust(95, 0)
|
||||
if sw != '9000':
|
||||
print("Programming UST failed with code %s"%sw)
|
||||
print("Programming UST failed with code %s" % sw)
|
||||
sw = self.update_ust(99, 0)
|
||||
if sw != '9000':
|
||||
print("Programming UST failed with code %s"%sw)
|
||||
print("Programming UST failed with code %s" % sw)
|
||||
sw = self.update_ust(115, 0)
|
||||
if sw != '9000':
|
||||
print("Programming UST failed with code %s"%sw)
|
||||
print("Programming UST failed with code %s" % sw)
|
||||
|
||||
return
|
||||
|
||||
|
||||
# In order for autodetection ...
|
||||
_cards_classes = [ FakeMagicSim, SuperSim, MagicSim, GrcardSim,
|
||||
_cards_classes = [FakeMagicSim, SuperSim, MagicSim, GrcardSim,
|
||||
SysmoSIMgr1, SysmoSIMgr2, SysmoUSIMgr1, SysmoUSIMSJS1,
|
||||
FairwavesSIM, OpenCellsSim, WavemobileSim, SysmoISIMSJA2 ]
|
||||
FairwavesSIM, OpenCellsSim, WavemobileSim, SysmoISIMSJA2]
|
||||
|
||||
|
||||
def card_detect(ctype, scc):
|
||||
# Detect type if needed
|
||||
|
|
42
pySim/cat.py
42
pySim/cat.py
|
@ -24,34 +24,48 @@ from construct import *
|
|||
# Tag values as per TS 101 220 Table 7.23
|
||||
|
||||
# TS 102 223 Section 8.1
|
||||
|
||||
|
||||
class Address(COMPR_TLV_IE, tag=0x06):
|
||||
_construct = Struct('ton_npi'/Int8ub,
|
||||
'call_number'/BcdAdapter(Bytes(this._.total_len-1)))
|
||||
|
||||
# TS 102 223 Section 8.2
|
||||
|
||||
|
||||
class AlphaIdentifier(COMPR_TLV_IE, tag=0x05):
|
||||
# FIXME: like EF.ADN
|
||||
pass
|
||||
|
||||
# TS 102 223 Section 8.3
|
||||
|
||||
|
||||
class Subaddress(COMPR_TLV_IE, tag=0x08):
|
||||
pass
|
||||
|
||||
# TS 102 223 Section 8.4
|
||||
|
||||
|
||||
class CapabilityConfigParams(COMPR_TLV_IE, tag=0x07):
|
||||
pass
|
||||
|
||||
# TS 31.111 Section 8.5
|
||||
|
||||
|
||||
class CBSPage(COMPR_TLV_IE, tag=0x0C):
|
||||
pass
|
||||
|
||||
# TS 102 223 Section 8.6
|
||||
|
||||
|
||||
class CommandDetails(COMPR_TLV_IE, tag=0x01):
|
||||
_construct = Struct('command_number'/Int8ub,
|
||||
'type_of_command'/Int8ub,
|
||||
'command_qualifier'/Int8ub)
|
||||
|
||||
# TS 102 223 Section 8.7
|
||||
|
||||
|
||||
class DeviceIdentities(COMPR_TLV_IE, tag=0x82):
|
||||
DEV_IDS = bidict({
|
||||
0x01: 'keypad',
|
||||
|
@ -91,7 +105,8 @@ class DeviceIdentities(COMPR_TLV_IE, tag=0x82):
|
|||
0x82: 'terminal',
|
||||
0x83: 'network',
|
||||
})
|
||||
def _from_bytes(self, do:bytes):
|
||||
|
||||
def _from_bytes(self, do: bytes):
|
||||
return {'source_dev_id': self.DEV_IDS[do[0]], 'dest_dev_id': self.DEV_IDS[do[1]]}
|
||||
|
||||
def _to_bytes(self):
|
||||
|
@ -100,65 +115,84 @@ class DeviceIdentities(COMPR_TLV_IE, tag=0x82):
|
|||
return bytes([src, dst])
|
||||
|
||||
# TS 102 223 Section 8.8
|
||||
|
||||
|
||||
class Duration(COMPR_TLV_IE, tag=0x04):
|
||||
_construct = Struct('time_unit'/Int8ub,
|
||||
'time_interval'/Int8ub)
|
||||
|
||||
# TS 102 223 Section 8.9
|
||||
|
||||
|
||||
class Item(COMPR_TLV_IE, tag=0x0f):
|
||||
_construct = Struct('identifier'/Int8ub,
|
||||
'text_string'/GsmStringAdapter(GreedyBytes))
|
||||
|
||||
# TS 102 223 Section 8.10
|
||||
|
||||
|
||||
class ItemIdentifier(COMPR_TLV_IE, tag=0x10):
|
||||
_construct = Struct('identifier'/Int8ub)
|
||||
|
||||
# TS 102 223 Section 8.11
|
||||
|
||||
|
||||
class ResponseLength(COMPR_TLV_IE, tag=0x11):
|
||||
_construct = Struct('minimum_length'/Int8ub,
|
||||
'maximum_length'/Int8ub)
|
||||
|
||||
# TS 102 223 Section 8.12
|
||||
|
||||
|
||||
class Result(COMPR_TLV_IE, tag=0x03):
|
||||
_construct = Struct('general_result'/Int8ub,
|
||||
'additional_information'/HexAdapter(GreedyBytes))
|
||||
|
||||
|
||||
|
||||
# TS 102 223 Section 8.13 + TS 31.111 Section 8.13
|
||||
class SMS_TPDU(COMPR_TLV_IE, tag=0x8B):
|
||||
_construct = Struct('tpdu'/HexAdapter(GreedyBytes))
|
||||
|
||||
# TS 102 223 Section 8.15
|
||||
|
||||
|
||||
class TextString(COMPR_TLV_IE, tag=0x0d):
|
||||
_construct = Struct('dcs'/Int8ub,
|
||||
'text_string'/HexAdapter(GreedyBytes))
|
||||
|
||||
# TS 102 223 Section 8.16
|
||||
|
||||
|
||||
class Tone(COMPR_TLV_IE, tag=0x0e):
|
||||
_construct = Struct('tone'/Int8ub)
|
||||
|
||||
# TS 31 111 Section 8.17
|
||||
|
||||
|
||||
class USSDString(COMPR_TLV_IE, tag=0x0a):
|
||||
_construct = Struct('dcs'/Int8ub,
|
||||
'ussd_string'/HexAdapter(GreedyBytes))
|
||||
|
||||
|
||||
|
||||
# TS 101 220 Table 7.17
|
||||
class ProactiveCommand(BER_TLV_IE, tag=0xD0):
|
||||
pass
|
||||
|
||||
# TS 101 220 Table 7.17 + 31.111 7.1.1.2
|
||||
|
||||
|
||||
class SMSPPDownload(BER_TLV_IE, tag=0xD1,
|
||||
nested=[DeviceIdentities, Address, SMS_TPDU]):
|
||||
pass
|
||||
|
||||
# TS 101 220 Table 7.17 + 31.111 7.1.1.3
|
||||
|
||||
|
||||
class SMSCBDownload(BER_TLV_IE, tag=0xD2,
|
||||
nested=[DeviceIdentities, CBSPage]):
|
||||
pass
|
||||
|
||||
|
||||
class USSDDownload(BER_TLV_IE, tag=0xD9,
|
||||
nested=[DeviceIdentities, USSDString]):
|
||||
pass
|
||||
|
@ -166,7 +200,7 @@ class USSDDownload(BER_TLV_IE, tag=0xD9,
|
|||
|
||||
# reasonable default for playing with OTA
|
||||
# 010203040506070809101112131415161718192021222324252627282930313233
|
||||
#'7fe1e10e000000000000001f43000000ff00000000000000000000000000000000'
|
||||
# '7fe1e10e000000000000001f43000000ff00000000000000000000000000000000'
|
||||
|
||||
# TS 102 223 Section 5.2
|
||||
term_prof_bits = {
|
||||
|
|
|
@ -26,6 +26,7 @@ from pySim.construct import LV
|
|||
from pySim.utils import rpad, b2h, h2b, sw_match, bertlv_encode_len, Hexstr, h2i, str_sanitize
|
||||
from pySim.exceptions import SwMatchError
|
||||
|
||||
|
||||
class SimCardCommands(object):
|
||||
def __init__(self, transport):
|
||||
self._tp = transport
|
||||
|
@ -37,13 +38,15 @@ class SimCardCommands(object):
|
|||
# see also: ETSI TS 102 221, chapter 11.1.1.3.1 Response for MF,
|
||||
# DF or ADF
|
||||
from pytlv.TLV import TLV
|
||||
tlvparser = TLV(['82', '83', '84', 'a5', '8a', '8b', '8c', '80', 'ab', 'c6', '81', '88'])
|
||||
tlvparser = TLV(['82', '83', '84', 'a5', '8a', '8b',
|
||||
'8c', '80', 'ab', 'c6', '81', '88'])
|
||||
|
||||
# pytlv is case sensitive!
|
||||
fcp = fcp.lower()
|
||||
|
||||
if fcp[0:2] != '62':
|
||||
raise ValueError('Tag of the FCP template does not match, expected 62 but got %s'%fcp[0:2])
|
||||
raise ValueError(
|
||||
'Tag of the FCP template does not match, expected 62 but got %s' % fcp[0:2])
|
||||
|
||||
# Unfortunately the spec is not very clear if the FCP length is
|
||||
# coded as one or two byte vale, so we have to try it out by
|
||||
|
@ -100,7 +103,8 @@ class SimCardCommands(object):
|
|||
if type(dir_list) is not list:
|
||||
dir_list = [dir_list]
|
||||
for i in dir_list:
|
||||
data, sw = self._tp.send_apdu(self.cla_byte + "a4" + self.sel_ctrl + "02" + i)
|
||||
data, sw = self._tp.send_apdu(
|
||||
self.cla_byte + "a4" + self.sel_ctrl + "02" + i)
|
||||
rv.append((data, sw))
|
||||
if sw != '9000':
|
||||
return rv
|
||||
|
@ -123,7 +127,7 @@ class SimCardCommands(object):
|
|||
rv.append(data)
|
||||
return rv
|
||||
|
||||
def select_file(self, fid:str):
|
||||
def select_file(self, fid: str):
|
||||
"""Execute SELECT a given file by FID.
|
||||
|
||||
Args:
|
||||
|
@ -132,7 +136,7 @@ class SimCardCommands(object):
|
|||
|
||||
return self._tp.send_apdu_checksw(self.cla_byte + "a4" + self.sel_ctrl + "02" + fid)
|
||||
|
||||
def select_adf(self, aid:str):
|
||||
def select_adf(self, aid: str):
|
||||
"""Execute SELECT a given Applicaiton ADF.
|
||||
|
||||
Args:
|
||||
|
@ -142,7 +146,7 @@ class SimCardCommands(object):
|
|||
aidlen = ("0" + format(len(aid) // 2, 'x'))[-2:]
|
||||
return self._tp.send_apdu_checksw(self.cla_byte + "a4" + "0404" + aidlen + aid)
|
||||
|
||||
def read_binary(self, ef, length:int=None, offset:int=0):
|
||||
def read_binary(self, ef, length: int = None, offset: int = 0):
|
||||
"""Execute READD BINARY.
|
||||
|
||||
Args:
|
||||
|
@ -162,16 +166,18 @@ class SimCardCommands(object):
|
|||
chunk_offset = 0
|
||||
while chunk_offset < length:
|
||||
chunk_len = min(255, length-chunk_offset)
|
||||
pdu = self.cla_byte + 'b0%04x%02x' % (offset + chunk_offset, chunk_len)
|
||||
pdu = self.cla_byte + \
|
||||
'b0%04x%02x' % (offset + chunk_offset, chunk_len)
|
||||
try:
|
||||
data, sw = self._tp.send_apdu_checksw(pdu)
|
||||
except Exception as e:
|
||||
raise ValueError('%s, failed to read (offset %d)' % (str_sanitize(str(e)), offset))
|
||||
raise ValueError('%s, failed to read (offset %d)' %
|
||||
(str_sanitize(str(e)), offset))
|
||||
total_data += data
|
||||
chunk_offset += chunk_len
|
||||
return total_data, sw
|
||||
|
||||
def update_binary(self, ef, data:str, offset:int=0, verify:bool=False, conserve:bool=False):
|
||||
def update_binary(self, ef, data: str, offset: int = 0, verify: bool = False, conserve: bool = False):
|
||||
"""Execute UPDATE BINARY.
|
||||
|
||||
Args:
|
||||
|
@ -194,11 +200,13 @@ class SimCardCommands(object):
|
|||
while chunk_offset < data_length:
|
||||
chunk_len = min(255, data_length - chunk_offset)
|
||||
# chunk_offset is bytes, but data slicing is hex chars, so we need to multiply by 2
|
||||
pdu = self.cla_byte + 'd6%04x%02x' % (offset + chunk_offset, chunk_len) + data[chunk_offset*2 : (chunk_offset+chunk_len)*2]
|
||||
pdu = self.cla_byte + \
|
||||
'd6%04x%02x' % (offset + chunk_offset, chunk_len) + \
|
||||
data[chunk_offset*2: (chunk_offset+chunk_len)*2]
|
||||
try:
|
||||
chunk_data, chunk_sw = self._tp.send_apdu_checksw(pdu)
|
||||
except Exception as e:
|
||||
raise ValueError('%s, failed to write chunk (chunk_offset %d, chunk_len %d)' % \
|
||||
raise ValueError('%s, failed to write chunk (chunk_offset %d, chunk_len %d)' %
|
||||
(str_sanitize(str(e)), chunk_offset, chunk_len))
|
||||
total_data += data
|
||||
chunk_offset += chunk_len
|
||||
|
@ -206,7 +214,7 @@ class SimCardCommands(object):
|
|||
self.verify_binary(ef, data, offset)
|
||||
return total_data, chunk_sw
|
||||
|
||||
def verify_binary(self, ef, data:str, offset:int=0):
|
||||
def verify_binary(self, ef, data: str, offset: int = 0):
|
||||
"""Verify contents of transparent EF.
|
||||
|
||||
Args:
|
||||
|
@ -216,9 +224,10 @@ class SimCardCommands(object):
|
|||
"""
|
||||
res = self.read_binary(ef, len(data) // 2, offset)
|
||||
if res[0].lower() != data.lower():
|
||||
raise ValueError('Binary verification failed (expected %s, got %s)' % (data.lower(), res[0].lower()))
|
||||
raise ValueError('Binary verification failed (expected %s, got %s)' % (
|
||||
data.lower(), res[0].lower()))
|
||||
|
||||
def read_record(self, ef, rec_no:int):
|
||||
def read_record(self, ef, rec_no: int):
|
||||
"""Execute READ RECORD.
|
||||
|
||||
Args:
|
||||
|
@ -230,8 +239,8 @@ class SimCardCommands(object):
|
|||
pdu = self.cla_byte + 'b2%02x04%02x' % (rec_no, rec_length)
|
||||
return self._tp.send_apdu_checksw(pdu)
|
||||
|
||||
def update_record(self, ef, rec_no:int, data:str, force_len:bool=False, verify:bool=False,
|
||||
conserve:bool=False):
|
||||
def update_record(self, ef, rec_no: int, data: str, force_len: bool = False, verify: bool = False,
|
||||
conserve: bool = False):
|
||||
"""Execute UPDATE RECORD.
|
||||
|
||||
Args:
|
||||
|
@ -253,7 +262,8 @@ class SimCardCommands(object):
|
|||
# exceed we throw an exception.
|
||||
rec_length = self.__record_len(res)
|
||||
if (len(data) // 2 > rec_length):
|
||||
raise ValueError('Data length exceeds record length (expected max %d, got %d)' % (rec_length, len(data) // 2))
|
||||
raise ValueError('Data length exceeds record length (expected max %d, got %d)' % (
|
||||
rec_length, len(data) // 2))
|
||||
elif (len(data) // 2 < rec_length):
|
||||
data = rpad(data, rec_length * 2)
|
||||
|
||||
|
@ -270,7 +280,7 @@ class SimCardCommands(object):
|
|||
self.verify_record(ef, rec_no, data)
|
||||
return res
|
||||
|
||||
def verify_record(self, ef, rec_no:int, data:str):
|
||||
def verify_record(self, ef, rec_no: int, data: str):
|
||||
"""Verify record against given data
|
||||
|
||||
Args:
|
||||
|
@ -280,7 +290,8 @@ class SimCardCommands(object):
|
|||
"""
|
||||
res = self.read_record(ef, rec_no)
|
||||
if res[0].lower() != data.lower():
|
||||
raise ValueError('Record verification failed (expected %s, got %s)' % (data.lower(), res[0].lower()))
|
||||
raise ValueError('Record verification failed (expected %s, got %s)' % (
|
||||
data.lower(), res[0].lower()))
|
||||
|
||||
def record_size(self, ef):
|
||||
"""Determine the record size of given file.
|
||||
|
@ -310,14 +321,14 @@ class SimCardCommands(object):
|
|||
return self.__len(r)
|
||||
|
||||
# TS 102 221 Section 11.3.1 low-level helper
|
||||
def _retrieve_data(self, tag:int, first:bool=True):
|
||||
def _retrieve_data(self, tag: int, first: bool = True):
|
||||
if first:
|
||||
pdu = '80cb008001%02x' % (tag)
|
||||
else:
|
||||
pdu = '80cb000000'
|
||||
return self._tp.send_apdu_checksw(pdu)
|
||||
|
||||
def retrieve_data(self, ef, tag:int):
|
||||
def retrieve_data(self, ef, tag: int):
|
||||
"""Execute RETRIEVE DATA, see also TS 102 221 Section 11.3.1.
|
||||
|
||||
Args
|
||||
|
@ -337,7 +348,7 @@ class SimCardCommands(object):
|
|||
return total_data, sw
|
||||
|
||||
# TS 102 221 Section 11.3.2 low-level helper
|
||||
def _set_data(self, data:str, first:bool=True):
|
||||
def _set_data(self, data: str, first: bool = True):
|
||||
if first:
|
||||
p1 = 0x80
|
||||
else:
|
||||
|
@ -347,7 +358,7 @@ class SimCardCommands(object):
|
|||
pdu = '80db00%02x%02x%s' % (p1, len(data)//2, data)
|
||||
return self._tp.send_apdu_checksw(pdu)
|
||||
|
||||
def set_data(self, ef, tag:int, value:str, verify:bool=False, conserve:bool=False):
|
||||
def set_data(self, ef, tag: int, value: str, verify: bool = False, conserve: bool = False):
|
||||
"""Execute SET DATA.
|
||||
|
||||
Args
|
||||
|
@ -378,7 +389,7 @@ class SimCardCommands(object):
|
|||
remaining = remaining[255:]
|
||||
return rdata, sw
|
||||
|
||||
def run_gsm(self, rand:str):
|
||||
def run_gsm(self, rand: str):
|
||||
"""Execute RUN GSM ALGORITHM.
|
||||
|
||||
Args:
|
||||
|
@ -389,7 +400,7 @@ class SimCardCommands(object):
|
|||
self.select_path(['3f00', '7f20'])
|
||||
return self._tp.send_apdu(self.cla_byte + '88000010' + rand)
|
||||
|
||||
def authenticate(self, rand:str, autn:str, context='3g'):
|
||||
def authenticate(self, rand: str, autn: str, context='3g'):
|
||||
"""Execute AUTHENTICATE (USIM/ISIM).
|
||||
|
||||
Args:
|
||||
|
@ -400,7 +411,8 @@ class SimCardCommands(object):
|
|||
# 3GPP TS 31.102 Section 7.1.2.1
|
||||
AuthCmd3G = Struct('rand'/LV, 'autn'/Optional(LV))
|
||||
AuthResp3GSyncFail = Struct(Const(b'\xDC'), 'auts'/LV)
|
||||
AuthResp3GSuccess = Struct(Const(b'\xDB'), 'res'/LV, 'ck'/LV, 'ik'/LV, 'kc'/Optional(LV))
|
||||
AuthResp3GSuccess = Struct(
|
||||
Const(b'\xDB'), 'res'/LV, 'ck'/LV, 'ik'/LV, 'kc'/Optional(LV))
|
||||
AuthResp3G = Select(AuthResp3GSyncFail, AuthResp3GSuccess)
|
||||
# build parameters
|
||||
cmd_data = {'rand': rand, 'autn': autn}
|
||||
|
@ -408,7 +420,8 @@ class SimCardCommands(object):
|
|||
p2 = '81'
|
||||
elif context == 'gsm':
|
||||
p2 = '80'
|
||||
(data, sw) = self._tp.send_apdu_constr_checksw(self.cla_byte, '88', '00', p2, AuthCmd3G, cmd_data, AuthResp3G)
|
||||
(data, sw) = self._tp.send_apdu_constr_checksw(
|
||||
self.cla_byte, '88', '00', p2, AuthCmd3G, cmd_data, AuthResp3G)
|
||||
if 'auts' in data:
|
||||
ret = {'synchronisation_failure': data}
|
||||
else:
|
||||
|
@ -456,7 +469,7 @@ class SimCardCommands(object):
|
|||
elif (sw != '9000'):
|
||||
raise SwMatchError(sw, '9000')
|
||||
|
||||
def verify_chv(self, chv_no:int, code:str):
|
||||
def verify_chv(self, chv_no: int, code: str):
|
||||
"""Verify a given CHV (Card Holder Verification == PIN)
|
||||
|
||||
Args:
|
||||
|
@ -464,11 +477,12 @@ class SimCardCommands(object):
|
|||
code : chv code as hex string
|
||||
"""
|
||||
fc = rpad(b2h(code), 16)
|
||||
data, sw = self._tp.send_apdu(self.cla_byte + '2000' + ('%02X' % chv_no) + '08' + fc)
|
||||
data, sw = self._tp.send_apdu(
|
||||
self.cla_byte + '2000' + ('%02X' % chv_no) + '08' + fc)
|
||||
self._chv_process_sw('verify', chv_no, code, sw)
|
||||
return (data, sw)
|
||||
|
||||
def unblock_chv(self, chv_no:int, puk_code:str, pin_code:str):
|
||||
def unblock_chv(self, chv_no: int, puk_code: str, pin_code: str):
|
||||
"""Unblock a given CHV (Card Holder Verification == PIN)
|
||||
|
||||
Args:
|
||||
|
@ -477,11 +491,12 @@ class SimCardCommands(object):
|
|||
pin_code : new chv code as hex string
|
||||
"""
|
||||
fc = rpad(b2h(puk_code), 16) + rpad(b2h(pin_code), 16)
|
||||
data, sw = self._tp.send_apdu(self.cla_byte + '2C00' + ('%02X' % chv_no) + '10' + fc)
|
||||
data, sw = self._tp.send_apdu(
|
||||
self.cla_byte + '2C00' + ('%02X' % chv_no) + '10' + fc)
|
||||
self._chv_process_sw('unblock', chv_no, pin_code, sw)
|
||||
return (data, sw)
|
||||
|
||||
def change_chv(self, chv_no:int, pin_code:str, new_pin_code:str):
|
||||
def change_chv(self, chv_no: int, pin_code: str, new_pin_code: str):
|
||||
"""Change a given CHV (Card Holder Verification == PIN)
|
||||
|
||||
Args:
|
||||
|
@ -490,11 +505,12 @@ class SimCardCommands(object):
|
|||
new_pin_code : new chv code as hex string
|
||||
"""
|
||||
fc = rpad(b2h(pin_code), 16) + rpad(b2h(new_pin_code), 16)
|
||||
data, sw = self._tp.send_apdu(self.cla_byte + '2400' + ('%02X' % chv_no) + '10' + fc)
|
||||
data, sw = self._tp.send_apdu(
|
||||
self.cla_byte + '2400' + ('%02X' % chv_no) + '10' + fc)
|
||||
self._chv_process_sw('change', chv_no, pin_code, sw)
|
||||
return (data, sw)
|
||||
|
||||
def disable_chv(self, chv_no:int, pin_code:str):
|
||||
def disable_chv(self, chv_no: int, pin_code: str):
|
||||
"""Disable a given CHV (Card Holder Verification == PIN)
|
||||
|
||||
Args:
|
||||
|
@ -503,11 +519,12 @@ class SimCardCommands(object):
|
|||
new_pin_code : new chv code as hex string
|
||||
"""
|
||||
fc = rpad(b2h(pin_code), 16)
|
||||
data, sw = self._tp.send_apdu(self.cla_byte + '2600' + ('%02X' % chv_no) + '08' + fc)
|
||||
data, sw = self._tp.send_apdu(
|
||||
self.cla_byte + '2600' + ('%02X' % chv_no) + '08' + fc)
|
||||
self._chv_process_sw('disable', chv_no, pin_code, sw)
|
||||
return (data, sw)
|
||||
|
||||
def enable_chv(self, chv_no:int, pin_code:str):
|
||||
def enable_chv(self, chv_no: int, pin_code: str):
|
||||
"""Enable a given CHV (Card Holder Verification == PIN)
|
||||
|
||||
Args:
|
||||
|
@ -515,11 +532,12 @@ class SimCardCommands(object):
|
|||
pin_code : chv code as hex string
|
||||
"""
|
||||
fc = rpad(b2h(pin_code), 16)
|
||||
data, sw = self._tp.send_apdu(self.cla_byte + '2800' + ('%02X' % chv_no) + '08' + fc)
|
||||
data, sw = self._tp.send_apdu(
|
||||
self.cla_byte + '2800' + ('%02X' % chv_no) + '08' + fc)
|
||||
self._chv_process_sw('enable', chv_no, pin_code, sw)
|
||||
return (data, sw)
|
||||
|
||||
def envelope(self, payload:str):
|
||||
def envelope(self, payload: str):
|
||||
"""Send one ENVELOPE command to the SIM
|
||||
|
||||
Args:
|
||||
|
@ -527,7 +545,7 @@ class SimCardCommands(object):
|
|||
"""
|
||||
return self._tp.send_apdu_checksw('80c20000%02x%s' % (len(payload)//2, payload))
|
||||
|
||||
def terminal_profile(self, payload:str):
|
||||
def terminal_profile(self, payload: str):
|
||||
"""Send TERMINAL PROFILE to card
|
||||
|
||||
Args:
|
||||
|
@ -538,14 +556,14 @@ class SimCardCommands(object):
|
|||
return (data, sw)
|
||||
|
||||
# ETSI TS 102 221 11.1.22
|
||||
def suspend_uicc(self, min_len_secs:int=60, max_len_secs:int=43200):
|
||||
def suspend_uicc(self, min_len_secs: int = 60, max_len_secs: int = 43200):
|
||||
"""Send SUSPEND UICC to the card.
|
||||
|
||||
Args:
|
||||
min_len_secs : mimumum suspend time seconds
|
||||
max_len_secs : maximum suspend time seconds
|
||||
"""
|
||||
def encode_duration(secs:int) -> Hexstr:
|
||||
def encode_duration(secs: int) -> Hexstr:
|
||||
if secs >= 10*24*60*60:
|
||||
return '04%02x' % (secs // (10*24*60*60))
|
||||
elif secs >= 24*60*60:
|
||||
|
@ -556,7 +574,8 @@ class SimCardCommands(object):
|
|||
return '01%02x' % (secs // 60)
|
||||
else:
|
||||
return '00%02x' % secs
|
||||
def decode_duration(enc:Hexstr) -> int:
|
||||
|
||||
def decode_duration(enc: Hexstr) -> int:
|
||||
time_unit = enc[:2]
|
||||
length = h2i(enc[2:4])
|
||||
if time_unit == '04':
|
||||
|
@ -573,7 +592,8 @@ class SimCardCommands(object):
|
|||
raise ValueError('Time unit must be 0x00..0x04')
|
||||
min_dur_enc = encode_duration(min_len_secs)
|
||||
max_dur_enc = encode_duration(max_len_secs)
|
||||
data, sw = self._tp.send_apdu_checksw('8076000004' + min_dur_enc + max_dur_enc)
|
||||
data, sw = self._tp.send_apdu_checksw(
|
||||
'8076000004' + min_dur_enc + max_dur_enc)
|
||||
negotiated_duration_secs = decode_duration(data[:4])
|
||||
resume_token = data[4:]
|
||||
return (negotiated_duration_secs, resume_token, sw)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from construct.lib.containers import Container, ListContainer
|
||||
from construct.core import EnumIntegerString
|
||||
import typing
|
||||
from construct import *
|
||||
from pySim.utils import b2h, h2b, swap_nibbles
|
||||
|
@ -23,18 +25,24 @@ import gsm0338
|
|||
|
||||
class HexAdapter(Adapter):
|
||||
"""convert a bytes() type to a string of hex nibbles."""
|
||||
|
||||
def _decode(self, obj, context, path):
|
||||
return b2h(obj)
|
||||
|
||||
def _encode(self, obj, context, path):
|
||||
return h2b(obj)
|
||||
|
||||
|
||||
class BcdAdapter(Adapter):
|
||||
"""convert a bytes() type to a string of BCD nibbles."""
|
||||
|
||||
def _decode(self, obj, context, path):
|
||||
return swap_nibbles(b2h(obj))
|
||||
|
||||
def _encode(self, obj, context, path):
|
||||
return h2b(swap_nibbles(obj))
|
||||
|
||||
|
||||
class Rpad(Adapter):
|
||||
"""
|
||||
Encoder appends padding bytes (b'\\xff') up to target size.
|
||||
|
@ -54,9 +62,11 @@ class Rpad(Adapter):
|
|||
|
||||
def _encode(self, obj, context, path):
|
||||
if len(obj) > self.sizeof():
|
||||
raise SizeofError("Input ({}) exceeds target size ({})".format(len(obj), self.sizeof()))
|
||||
raise SizeofError("Input ({}) exceeds target size ({})".format(
|
||||
len(obj), self.sizeof()))
|
||||
return obj + self.pattern * (self.sizeof() - len(obj))
|
||||
|
||||
|
||||
class GsmStringAdapter(Adapter):
|
||||
"""Convert GSM 03.38 encoded bytes to a string."""
|
||||
|
||||
|
@ -71,6 +81,7 @@ class GsmStringAdapter(Adapter):
|
|||
def _encode(self, obj, context, path):
|
||||
return obj.encode(self.codec, self.err)
|
||||
|
||||
|
||||
def filter_dict(d, exclude_prefix='_'):
|
||||
"""filter the input dict to ensure no keys starting with 'exclude_prefix' remain."""
|
||||
if not isinstance(d, dict):
|
||||
|
@ -85,8 +96,6 @@ def filter_dict(d, exclude_prefix='_'):
|
|||
res[key] = value
|
||||
return res
|
||||
|
||||
from construct.lib.containers import Container, ListContainer
|
||||
from construct.core import EnumIntegerString
|
||||
|
||||
def normalize_construct(c):
|
||||
"""Convert a construct specific type to a related base type, mostly useful
|
||||
|
@ -95,7 +104,7 @@ def normalize_construct(c):
|
|||
# in the dict: '_io': <_io.BytesIO object at 0x7fdb64e05860> which we cannot json-serialize
|
||||
c = filter_dict(c)
|
||||
if isinstance(c, Container) or isinstance(c, dict):
|
||||
r = {k : normalize_construct(v) for (k, v) in c.items()}
|
||||
r = {k: normalize_construct(v) for (k, v) in c.items()}
|
||||
elif isinstance(c, ListContainer):
|
||||
r = [normalize_construct(x) for x in c]
|
||||
elif isinstance(c, list):
|
||||
|
@ -106,13 +115,15 @@ def normalize_construct(c):
|
|||
r = c
|
||||
return r
|
||||
|
||||
def parse_construct(c, raw_bin_data:bytes, length:typing.Optional[int]=None, exclude_prefix:str='_'):
|
||||
|
||||
def parse_construct(c, raw_bin_data: bytes, length: typing.Optional[int] = None, exclude_prefix: str = '_'):
|
||||
"""Helper function to wrap around normalize_construct() and filter_dict()."""
|
||||
if not length:
|
||||
length = len(raw_bin_data)
|
||||
parsed = c.parse(raw_bin_data, total_len=length)
|
||||
return normalize_construct(parsed)
|
||||
|
||||
|
||||
# here we collect some shared / common definitions of data types
|
||||
LV = Prefixed(Int8ub, HexAdapter(GreedyBytes))
|
||||
|
||||
|
@ -129,6 +140,7 @@ ByteRFU = Default(Byte, __RFU_VALUE)
|
|||
# Field that packs all remaining Reserved for Future Use (RFU) bytes
|
||||
GreedyBytesRFU = Default(GreedyBytes, b'')
|
||||
|
||||
|
||||
def BitsRFU(n=1):
|
||||
'''
|
||||
Field that packs Reserved for Future Use (RFU) bit(s)
|
||||
|
@ -143,6 +155,7 @@ def BitsRFU(n=1):
|
|||
'''
|
||||
return Default(BitsInteger(n), __RFU_VALUE)
|
||||
|
||||
|
||||
def BytesRFU(n=1):
|
||||
'''
|
||||
Field that packs Reserved for Future Use (RFU) byte(s)
|
||||
|
@ -157,6 +170,7 @@ def BytesRFU(n=1):
|
|||
'''
|
||||
return Default(Bytes(n), __RFU_VALUE)
|
||||
|
||||
|
||||
def GsmString(n):
|
||||
'''
|
||||
GSM 03.38 encoded byte string of fixed length n.
|
||||
|
|
|
@ -21,22 +21,27 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
|
||||
class NoCardError(Exception):
|
||||
"""No card was found in the reader."""
|
||||
pass
|
||||
|
||||
|
||||
class ProtocolError(Exception):
|
||||
"""Some kind of protocol level error interfacing with the card."""
|
||||
pass
|
||||
|
||||
|
||||
class ReaderError(Exception):
|
||||
"""Some kind of general error with the card reader."""
|
||||
pass
|
||||
|
||||
|
||||
class SwMatchError(Exception):
|
||||
"""Raised when an operation specifies an expected SW but the actual SW from
|
||||
the card doesn't match."""
|
||||
def __init__(self, sw_actual:str, sw_expected:str, rs=None):
|
||||
|
||||
def __init__(self, sw_actual: str, sw_expected: str, rs=None):
|
||||
"""
|
||||
Args:
|
||||
sw_actual : the SW we actually received from the card (4 hex digits)
|
||||
|
@ -46,6 +51,7 @@ class SwMatchError(Exception):
|
|||
self.sw_actual = sw_actual
|
||||
self.sw_expected = sw_expected
|
||||
self.rs = rs
|
||||
|
||||
def __str__(self):
|
||||
if self.rs:
|
||||
r = self.rs.interpret_sw(self.sw_actual)
|
||||
|
|
|
@ -44,6 +44,7 @@ from pySim.exceptions import *
|
|||
from pySim.jsonpath import js_path_find, js_path_modify
|
||||
from pySim.commands import SimCardCommands
|
||||
|
||||
|
||||
class CardFile(object):
|
||||
"""Base class for all objects in the smart card filesystem.
|
||||
Serve as a common ancestor to all other file types; rarely used directly.
|
||||
|
@ -51,8 +52,8 @@ class CardFile(object):
|
|||
RESERVED_NAMES = ['..', '.', '/', 'MF']
|
||||
RESERVED_FIDS = ['3f00']
|
||||
|
||||
def __init__(self, fid:str=None, sfid:str=None, name:str=None, desc:str=None,
|
||||
parent:Optional['CardDF']=None, profile:Optional['CardProfile']=None):
|
||||
def __init__(self, fid: str = None, sfid: str = None, name: str = None, desc: str = None,
|
||||
parent: Optional['CardDF'] = None, profile: Optional['CardProfile'] = None):
|
||||
"""
|
||||
Args:
|
||||
fid : File Identifier (4 hex digits)
|
||||
|
@ -86,13 +87,13 @@ class CardFile(object):
|
|||
else:
|
||||
return self.fid
|
||||
|
||||
def _path_element(self, prefer_name:bool) -> Optional[str]:
|
||||
def _path_element(self, prefer_name: bool) -> Optional[str]:
|
||||
if prefer_name and self.name:
|
||||
return self.name
|
||||
else:
|
||||
return self.fid
|
||||
|
||||
def fully_qualified_path(self, prefer_name:bool=True) -> List[str]:
|
||||
def fully_qualified_path(self, prefer_name: bool = True) -> List[str]:
|
||||
"""Return fully qualified path to file as list of FID or name strings.
|
||||
|
||||
Args:
|
||||
|
@ -117,7 +118,7 @@ class CardFile(object):
|
|||
node = node.parent
|
||||
return cast(CardMF, node)
|
||||
|
||||
def _get_self_selectables(self, alias:str=None, flags = []) -> Dict[str, 'CardFile']:
|
||||
def _get_self_selectables(self, alias: str = None, flags=[]) -> Dict[str, 'CardFile']:
|
||||
"""Return a dict of {'identifier': self} tuples.
|
||||
|
||||
Args:
|
||||
|
@ -136,7 +137,7 @@ class CardFile(object):
|
|||
sels.update({self.name: self})
|
||||
return sels
|
||||
|
||||
def get_selectables(self, flags = []) -> Dict[str, 'CardFile']:
|
||||
def get_selectables(self, flags=[]) -> Dict[str, 'CardFile']:
|
||||
"""Return a dict of {'identifier': File} that is selectable from the current file.
|
||||
|
||||
Args:
|
||||
|
@ -158,11 +159,11 @@ class CardFile(object):
|
|||
if flags == [] or 'MF' in flags:
|
||||
mf = self.get_mf()
|
||||
if mf:
|
||||
sels.update(mf._get_self_selectables(flags = flags))
|
||||
sels.update(mf.get_app_selectables(flags = flags))
|
||||
sels.update(mf._get_self_selectables(flags=flags))
|
||||
sels.update(mf.get_app_selectables(flags=flags))
|
||||
return sels
|
||||
|
||||
def get_selectable_names(self, flags = []) -> List[str]:
|
||||
def get_selectable_names(self, flags=[]) -> List[str]:
|
||||
"""Return a dict of {'identifier': File} that is selectable from the current file.
|
||||
|
||||
Args:
|
||||
|
@ -176,7 +177,7 @@ class CardFile(object):
|
|||
sel_keys.sort()
|
||||
return sel_keys
|
||||
|
||||
def decode_select_response(self, data_hex:str):
|
||||
def decode_select_response(self, data_hex: str):
|
||||
"""Decode the response to a SELECT command.
|
||||
|
||||
Args:
|
||||
|
@ -206,6 +207,7 @@ class CardFile(object):
|
|||
return self.parent.get_profile()
|
||||
return None
|
||||
|
||||
|
||||
class CardDF(CardFile):
|
||||
"""DF (Dedicated File) in the smart card filesystem. Those are basically sub-directories."""
|
||||
|
||||
|
@ -225,7 +227,7 @@ class CardDF(CardFile):
|
|||
def __str__(self):
|
||||
return "DF(%s)" % (super().__str__())
|
||||
|
||||
def add_file(self, child:CardFile, ignore_existing:bool=False):
|
||||
def add_file(self, child: CardFile, ignore_existing: bool = False):
|
||||
"""Add a child (DF/EF) to this DF.
|
||||
Args:
|
||||
child: The new DF/EF to be added
|
||||
|
@ -233,7 +235,7 @@ class CardDF(CardFile):
|
|||
"""
|
||||
if not isinstance(child, CardFile):
|
||||
raise TypeError("Expected a File instance")
|
||||
if not is_hex(child.fid, minlen = 4, maxlen = 4):
|
||||
if not is_hex(child.fid, minlen=4, maxlen=4):
|
||||
raise ValueError("File name %s is not a valid fid" % (child.fid))
|
||||
if child.name in CardFile.RESERVED_NAMES:
|
||||
raise ValueError("File name %s is a reserved name" % (child.name))
|
||||
|
@ -242,17 +244,20 @@ class CardDF(CardFile):
|
|||
if child.fid in self.children:
|
||||
if ignore_existing:
|
||||
return
|
||||
raise ValueError("File with given fid %s already exists in %s" % (child.fid, self))
|
||||
raise ValueError(
|
||||
"File with given fid %s already exists in %s" % (child.fid, self))
|
||||
if self.lookup_file_by_sfid(child.sfid):
|
||||
raise ValueError("File with given sfid %s already exists in %s" % (child.sfid, self))
|
||||
raise ValueError(
|
||||
"File with given sfid %s already exists in %s" % (child.sfid, self))
|
||||
if self.lookup_file_by_name(child.name):
|
||||
if ignore_existing:
|
||||
return
|
||||
raise ValueError("File with given name %s already exists in %s" % (child.name, self))
|
||||
raise ValueError(
|
||||
"File with given name %s already exists in %s" % (child.name, self))
|
||||
self.children[child.fid] = child
|
||||
child.parent = self
|
||||
|
||||
def add_files(self, children:Iterable[CardFile], ignore_existing:bool=False):
|
||||
def add_files(self, children: Iterable[CardFile], ignore_existing: bool = False):
|
||||
"""Add a list of child (DF/EF) to this DF
|
||||
|
||||
Args:
|
||||
|
@ -262,7 +267,7 @@ class CardDF(CardFile):
|
|||
for child in children:
|
||||
self.add_file(child, ignore_existing)
|
||||
|
||||
def get_selectables(self, flags = []) -> dict:
|
||||
def get_selectables(self, flags=[]) -> dict:
|
||||
"""Return a dict of {'identifier': File} that is selectable from the current DF.
|
||||
|
||||
Args:
|
||||
|
@ -280,7 +285,7 @@ class CardDF(CardFile):
|
|||
sels.update({x.name: x for x in self.children.values() if x.name})
|
||||
return sels
|
||||
|
||||
def lookup_file_by_name(self, name:Optional[str]) -> Optional[CardFile]:
|
||||
def lookup_file_by_name(self, name: Optional[str]) -> Optional[CardFile]:
|
||||
"""Find a file with given name within current DF."""
|
||||
if name == None:
|
||||
return None
|
||||
|
@ -289,7 +294,7 @@ class CardDF(CardFile):
|
|||
return i
|
||||
return None
|
||||
|
||||
def lookup_file_by_sfid(self, sfid:Optional[str]) -> Optional[CardFile]:
|
||||
def lookup_file_by_sfid(self, sfid: Optional[str]) -> Optional[CardFile]:
|
||||
"""Find a file with given short file ID within current DF."""
|
||||
if sfid == None:
|
||||
return None
|
||||
|
@ -298,7 +303,7 @@ class CardDF(CardFile):
|
|||
return i
|
||||
return None
|
||||
|
||||
def lookup_file_by_fid(self, fid:str) -> Optional[CardFile]:
|
||||
def lookup_file_by_fid(self, fid: str) -> Optional[CardFile]:
|
||||
"""Find a file with given file ID within current DF."""
|
||||
if fid in self.children:
|
||||
return self.children[fid]
|
||||
|
@ -307,6 +312,7 @@ class CardDF(CardFile):
|
|||
|
||||
class CardMF(CardDF):
|
||||
"""MF (Master File) in the smart card filesystem"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
# can be overridden; use setdefault
|
||||
kwargs.setdefault('fid', '3f00')
|
||||
|
@ -320,20 +326,20 @@ class CardMF(CardDF):
|
|||
def __str__(self):
|
||||
return "MF(%s)" % (self.fid)
|
||||
|
||||
def add_application_df(self, app:'CardADF'):
|
||||
def add_application_df(self, app: 'CardADF'):
|
||||
"""Add an Application to the MF"""
|
||||
if not isinstance(app, CardADF):
|
||||
raise TypeError("Expected an ADF instance")
|
||||
if app.aid in self.applications:
|
||||
raise ValueError("AID %s already exists" % (app.aid))
|
||||
self.applications[app.aid] = app
|
||||
app.parent=self
|
||||
app.parent = self
|
||||
|
||||
def get_app_names(self):
|
||||
"""Get list of completions (AID names)"""
|
||||
return [x.name for x in self.applications]
|
||||
|
||||
def get_selectables(self, flags = []) -> dict:
|
||||
def get_selectables(self, flags=[]) -> dict:
|
||||
"""Return a dict of {'identifier': File} that is selectable from the current DF.
|
||||
|
||||
Args:
|
||||
|
@ -347,16 +353,17 @@ class CardMF(CardDF):
|
|||
sels.update(self.get_app_selectables(flags))
|
||||
return sels
|
||||
|
||||
def get_app_selectables(self, flags = []) -> dict:
|
||||
def get_app_selectables(self, flags=[]) -> dict:
|
||||
"""Get applications by AID + name"""
|
||||
sels = {}
|
||||
if flags == [] or 'AIDS' in flags:
|
||||
sels.update({x.aid: x for x in self.applications.values()})
|
||||
if flags == [] or 'ANAMES' in flags:
|
||||
sels.update({x.name: x for x in self.applications.values() if x.name})
|
||||
sels.update(
|
||||
{x.name: x for x in self.applications.values() if x.name})
|
||||
return sels
|
||||
|
||||
def decode_select_response(self, data_hex:str) -> object:
|
||||
def decode_select_response(self, data_hex: str) -> object:
|
||||
"""Decode the response to a SELECT command.
|
||||
|
||||
This is the fall-back method which automatically defers to the standard decoding
|
||||
|
@ -372,9 +379,11 @@ class CardMF(CardDF):
|
|||
else:
|
||||
return data_hex
|
||||
|
||||
|
||||
class CardADF(CardDF):
|
||||
"""ADF (Application Dedicated File) in the smart card filesystem"""
|
||||
def __init__(self, aid:str, **kwargs):
|
||||
|
||||
def __init__(self, aid: str, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
# reference to CardApplication may be set from CardApplication constructor
|
||||
self.application = None # type: Optional[CardApplication]
|
||||
|
@ -386,7 +395,7 @@ class CardADF(CardDF):
|
|||
def __str__(self):
|
||||
return "ADF(%s)" % (self.aid)
|
||||
|
||||
def _path_element(self, prefer_name:bool):
|
||||
def _path_element(self, prefer_name: bool):
|
||||
if self.name and prefer_name:
|
||||
return self.name
|
||||
else:
|
||||
|
@ -395,6 +404,7 @@ class CardADF(CardDF):
|
|||
|
||||
class CardEF(CardFile):
|
||||
"""EF (Entry File) in the smart card filesystem"""
|
||||
|
||||
def __init__(self, *, fid, **kwargs):
|
||||
kwargs['fid'] = fid
|
||||
super().__init__(**kwargs)
|
||||
|
@ -402,7 +412,7 @@ class CardEF(CardFile):
|
|||
def __str__(self):
|
||||
return "EF(%s)" % (super().__str__())
|
||||
|
||||
def get_selectables(self, flags = []) -> dict:
|
||||
def get_selectables(self, flags=[]) -> dict:
|
||||
"""Return a dict of {'identifier': File} that is selectable from the current DF.
|
||||
|
||||
Args:
|
||||
|
@ -412,9 +422,10 @@ class CardEF(CardFile):
|
|||
dict containing all selectable items. Key is identifier (string), value
|
||||
a reference to a CardFile (or derived class) instance.
|
||||
"""
|
||||
#global selectable names + those of the parent DF
|
||||
# global selectable names + those of the parent DF
|
||||
sels = super().get_selectables(flags)
|
||||
sels.update({x.name:x for x in self.parent.children.values() if x != self})
|
||||
sels.update(
|
||||
{x.name: x for x in self.parent.children.values() if x != self})
|
||||
return sels
|
||||
|
||||
|
||||
|
@ -427,12 +438,16 @@ class TransparentEF(CardEF):
|
|||
@with_default_category('Transparent EF Commands')
|
||||
class ShellCommands(CommandSet):
|
||||
"""Shell commands specific for transparent EFs."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
read_bin_parser = argparse.ArgumentParser()
|
||||
read_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read')
|
||||
read_bin_parser.add_argument('--length', type=int, help='Number of bytes to read')
|
||||
read_bin_parser.add_argument(
|
||||
'--offset', type=int, default=0, help='Byte offset for start of read')
|
||||
read_bin_parser.add_argument(
|
||||
'--length', type=int, help='Number of bytes to read')
|
||||
|
||||
@cmd2.with_argparser(read_bin_parser)
|
||||
def do_read_binary(self, opts):
|
||||
"""Read binary data from a transparent EF"""
|
||||
|
@ -442,6 +457,7 @@ class TransparentEF(CardEF):
|
|||
read_bin_dec_parser = argparse.ArgumentParser()
|
||||
read_bin_dec_parser.add_argument('--oneline', action='store_true',
|
||||
help='No JSON pretty-printing, dump as a single line')
|
||||
|
||||
@cmd2.with_argparser(read_bin_dec_parser)
|
||||
def do_read_binary_decoded(self, opts):
|
||||
"""Read + decode data from a transparent EF"""
|
||||
|
@ -449,8 +465,11 @@ class TransparentEF(CardEF):
|
|||
self._cmd.poutput_json(data, opts.oneline)
|
||||
|
||||
upd_bin_parser = argparse.ArgumentParser()
|
||||
upd_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read')
|
||||
upd_bin_parser.add_argument('data', help='Data bytes (hex format) to write')
|
||||
upd_bin_parser.add_argument(
|
||||
'--offset', type=int, default=0, help='Byte offset for start of read')
|
||||
upd_bin_parser.add_argument(
|
||||
'data', help='Data bytes (hex format) to write')
|
||||
|
||||
@cmd2.with_argparser(upd_bin_parser)
|
||||
def do_update_binary(self, opts):
|
||||
"""Update (Write) data of a transparent EF"""
|
||||
|
@ -459,15 +478,18 @@ class TransparentEF(CardEF):
|
|||
self._cmd.poutput(data)
|
||||
|
||||
upd_bin_dec_parser = argparse.ArgumentParser()
|
||||
upd_bin_dec_parser.add_argument('data', help='Abstract data (JSON format) to write')
|
||||
upd_bin_dec_parser.add_argument(
|
||||
'data', help='Abstract data (JSON format) to write')
|
||||
upd_bin_dec_parser.add_argument('--json-path', type=str,
|
||||
help='JSON path to modify specific element of file only')
|
||||
|
||||
@cmd2.with_argparser(upd_bin_dec_parser)
|
||||
def do_update_binary_decoded(self, opts):
|
||||
"""Encode + Update (Write) data of a transparent EF"""
|
||||
if opts.json_path:
|
||||
(data_json, sw) = self._cmd.rs.read_binary_dec()
|
||||
js_path_modify(data_json, opts.json_path, json.loads(opts.data))
|
||||
js_path_modify(data_json, opts.json_path,
|
||||
json.loads(opts.data))
|
||||
else:
|
||||
data_json = json.loads(opts.data)
|
||||
(data, sw) = self._cmd.rs.update_binary_dec(data_json)
|
||||
|
@ -493,9 +515,8 @@ class TransparentEF(CardEF):
|
|||
if data:
|
||||
self._cmd.poutput_json(data)
|
||||
|
||||
|
||||
def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None,
|
||||
size={1,None}):
|
||||
def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
|
||||
size={1, None}):
|
||||
"""
|
||||
Args:
|
||||
fid : File Identifier (4 hex digits)
|
||||
|
@ -511,7 +532,7 @@ class TransparentEF(CardEF):
|
|||
self.size = size
|
||||
self.shell_commands = [self.ShellCommands()]
|
||||
|
||||
def decode_bin(self, raw_bin_data:bytearray) -> dict:
|
||||
def decode_bin(self, raw_bin_data: bytearray) -> dict:
|
||||
"""Decode raw (binary) data into abstract representation.
|
||||
|
||||
A derived class would typically provide a _decode_bin() or _decode_hex() method
|
||||
|
@ -537,7 +558,7 @@ class TransparentEF(CardEF):
|
|||
return t.to_dict()
|
||||
return {'raw': raw_bin_data.hex()}
|
||||
|
||||
def decode_hex(self, raw_hex_data:str) -> dict:
|
||||
def decode_hex(self, raw_hex_data: str) -> dict:
|
||||
"""Decode raw (hex string) data into abstract representation.
|
||||
|
||||
A derived class would typically provide a _decode_bin() or _decode_hex() method
|
||||
|
@ -564,7 +585,7 @@ class TransparentEF(CardEF):
|
|||
return t.to_dict()
|
||||
return {'raw': raw_bin_data.hex()}
|
||||
|
||||
def encode_bin(self, abstract_data:dict) -> bytearray:
|
||||
def encode_bin(self, abstract_data: dict) -> bytearray:
|
||||
"""Encode abstract representation into raw (binary) data.
|
||||
|
||||
A derived class would typically provide an _encode_bin() or _encode_hex() method
|
||||
|
@ -588,9 +609,10 @@ class TransparentEF(CardEF):
|
|||
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
|
||||
t.from_dict(abstract_data)
|
||||
return t.to_tlv()
|
||||
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
|
||||
raise NotImplementedError(
|
||||
"%s encoder not yet implemented. Patches welcome." % self)
|
||||
|
||||
def encode_hex(self, abstract_data:dict) -> str:
|
||||
def encode_hex(self, abstract_data: dict) -> str:
|
||||
"""Encode abstract representation into raw (hex string) data.
|
||||
|
||||
A derived class would typically provide an _encode_bin() or _encode_hex() method
|
||||
|
@ -615,7 +637,8 @@ class TransparentEF(CardEF):
|
|||
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
|
||||
t.from_dict(abstract_data)
|
||||
return b2h(t.to_tlv())
|
||||
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
|
||||
raise NotImplementedError(
|
||||
"%s encoder not yet implemented. Patches welcome." % self)
|
||||
|
||||
|
||||
class LinFixedEF(CardEF):
|
||||
|
@ -627,12 +650,16 @@ class LinFixedEF(CardEF):
|
|||
@with_default_category('Linear Fixed EF Commands')
|
||||
class ShellCommands(CommandSet):
|
||||
"""Shell commands specific for Linear Fixed EFs."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
read_rec_parser = argparse.ArgumentParser()
|
||||
read_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
|
||||
read_rec_parser.add_argument('--count', type=int, default=1, help='Number of records to be read, beginning at record_nr')
|
||||
read_rec_parser.add_argument(
|
||||
'record_nr', type=int, help='Number of record to be read')
|
||||
read_rec_parser.add_argument(
|
||||
'--count', type=int, default=1, help='Number of records to be read, beginning at record_nr')
|
||||
|
||||
@cmd2.with_argparser(read_rec_parser)
|
||||
def do_read_record(self, opts):
|
||||
"""Read one or multiple records from a record-oriented EF"""
|
||||
|
@ -646,9 +673,11 @@ class LinFixedEF(CardEF):
|
|||
self._cmd.poutput("%03d %s" % (recnr, recstr))
|
||||
|
||||
read_rec_dec_parser = argparse.ArgumentParser()
|
||||
read_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
|
||||
read_rec_dec_parser.add_argument(
|
||||
'record_nr', type=int, help='Number of record to be read')
|
||||
read_rec_dec_parser.add_argument('--oneline', action='store_true',
|
||||
help='No JSON pretty-printing, dump as a single line')
|
||||
|
||||
@cmd2.with_argparser(read_rec_dec_parser)
|
||||
def do_read_record_decoded(self, opts):
|
||||
"""Read + decode a record from a record-oriented EF"""
|
||||
|
@ -656,6 +685,7 @@ class LinFixedEF(CardEF):
|
|||
self._cmd.poutput_json(data, opts.oneline)
|
||||
|
||||
read_recs_parser = argparse.ArgumentParser()
|
||||
|
||||
@cmd2.with_argparser(read_recs_parser)
|
||||
def do_read_records(self, opts):
|
||||
"""Read all records from a record-oriented EF"""
|
||||
|
@ -671,6 +701,7 @@ class LinFixedEF(CardEF):
|
|||
read_recs_dec_parser = argparse.ArgumentParser()
|
||||
read_recs_dec_parser.add_argument('--oneline', action='store_true',
|
||||
help='No JSON pretty-printing, dump as a single line')
|
||||
|
||||
@cmd2.with_argparser(read_recs_dec_parser)
|
||||
def do_read_records_decoded(self, opts):
|
||||
"""Read + decode all records from a record-oriented EF"""
|
||||
|
@ -683,8 +714,11 @@ class LinFixedEF(CardEF):
|
|||
self._cmd.poutput_json(data_list, opts.oneline)
|
||||
|
||||
upd_rec_parser = argparse.ArgumentParser()
|
||||
upd_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
|
||||
upd_rec_parser.add_argument('data', help='Data bytes (hex format) to write')
|
||||
upd_rec_parser.add_argument(
|
||||
'record_nr', type=int, help='Number of record to be read')
|
||||
upd_rec_parser.add_argument(
|
||||
'data', help='Data bytes (hex format) to write')
|
||||
|
||||
@cmd2.with_argparser(upd_rec_parser)
|
||||
def do_update_record(self, opts):
|
||||
"""Update (write) data to a record-oriented EF"""
|
||||
|
@ -693,24 +727,31 @@ class LinFixedEF(CardEF):
|
|||
self._cmd.poutput(data)
|
||||
|
||||
upd_rec_dec_parser = argparse.ArgumentParser()
|
||||
upd_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
|
||||
upd_rec_dec_parser.add_argument('data', help='Abstract data (JSON format) to write')
|
||||
upd_rec_dec_parser.add_argument(
|
||||
'record_nr', type=int, help='Number of record to be read')
|
||||
upd_rec_dec_parser.add_argument(
|
||||
'data', help='Abstract data (JSON format) to write')
|
||||
upd_rec_dec_parser.add_argument('--json-path', type=str,
|
||||
help='JSON path to modify specific element of record only')
|
||||
|
||||
@cmd2.with_argparser(upd_rec_dec_parser)
|
||||
def do_update_record_decoded(self, opts):
|
||||
"""Encode + Update (write) data to a record-oriented EF"""
|
||||
if opts.json_path:
|
||||
(data_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
|
||||
js_path_modify(data_json, opts.json_path, json.loads(opts.data))
|
||||
js_path_modify(data_json, opts.json_path,
|
||||
json.loads(opts.data))
|
||||
else:
|
||||
data_json = json.loads(opts.data)
|
||||
(data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, data_json)
|
||||
(data, sw) = self._cmd.rs.update_record_dec(
|
||||
opts.record_nr, data_json)
|
||||
if data:
|
||||
self._cmd.poutput(data)
|
||||
|
||||
edit_rec_dec_parser = argparse.ArgumentParser()
|
||||
edit_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be edited')
|
||||
edit_rec_dec_parser.add_argument(
|
||||
'record_nr', type=int, help='Number of record to be edited')
|
||||
|
||||
@cmd2.with_argparser(edit_rec_dec_parser)
|
||||
def do_edit_record_decoded(self, opts):
|
||||
"""Edit the JSON representation of one record in an editor."""
|
||||
|
@ -727,13 +768,13 @@ class LinFixedEF(CardEF):
|
|||
if edited_json == orig_json:
|
||||
self._cmd.poutput("Data not modified, skipping write")
|
||||
else:
|
||||
(data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, edited_json)
|
||||
(data, sw) = self._cmd.rs.update_record_dec(
|
||||
opts.record_nr, edited_json)
|
||||
if data:
|
||||
self._cmd.poutput_json(data)
|
||||
|
||||
|
||||
def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None,
|
||||
parent:Optional[CardDF]=None, rec_len={1,None}):
|
||||
def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None,
|
||||
parent: Optional[CardDF] = None, rec_len={1, None}):
|
||||
"""
|
||||
Args:
|
||||
fid : File Identifier (4 hex digits)
|
||||
|
@ -749,7 +790,7 @@ class LinFixedEF(CardEF):
|
|||
self._construct = None
|
||||
self._tlv = None
|
||||
|
||||
def decode_record_hex(self, raw_hex_data:str) -> dict:
|
||||
def decode_record_hex(self, raw_hex_data: str) -> dict:
|
||||
"""Decode raw (hex string) data into abstract representation.
|
||||
|
||||
A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
|
||||
|
@ -776,7 +817,7 @@ class LinFixedEF(CardEF):
|
|||
return t.to_dict()
|
||||
return {'raw': raw_bin_data.hex()}
|
||||
|
||||
def decode_record_bin(self, raw_bin_data:bytearray) -> dict:
|
||||
def decode_record_bin(self, raw_bin_data: bytearray) -> dict:
|
||||
"""Decode raw (binary) data into abstract representation.
|
||||
|
||||
A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
|
||||
|
@ -803,7 +844,7 @@ class LinFixedEF(CardEF):
|
|||
return t.to_dict()
|
||||
return {'raw': raw_hex_data}
|
||||
|
||||
def encode_record_hex(self, abstract_data:dict) -> str:
|
||||
def encode_record_hex(self, abstract_data: dict) -> str:
|
||||
"""Encode abstract representation into raw (hex string) data.
|
||||
|
||||
A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
|
||||
|
@ -828,9 +869,10 @@ class LinFixedEF(CardEF):
|
|||
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
|
||||
t.from_dict(abstract_data)
|
||||
return b2h(t.to_tlv())
|
||||
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
|
||||
raise NotImplementedError(
|
||||
"%s encoder not yet implemented. Patches welcome." % self)
|
||||
|
||||
def encode_record_bin(self, abstract_data:dict) -> bytearray:
|
||||
def encode_record_bin(self, abstract_data: dict) -> bytearray:
|
||||
"""Encode abstract representation into raw (binary) data.
|
||||
|
||||
A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
|
||||
|
@ -854,14 +896,19 @@ class LinFixedEF(CardEF):
|
|||
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
|
||||
t.from_dict(abstract_data)
|
||||
return t.to_tlv()
|
||||
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
|
||||
raise NotImplementedError(
|
||||
"%s encoder not yet implemented. Patches welcome." % self)
|
||||
|
||||
|
||||
class CyclicEF(LinFixedEF):
|
||||
"""Cyclic EF (Entry File) in the smart card filesystem"""
|
||||
# we don't really have any special support for those; just recycling LinFixedEF here
|
||||
def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None,
|
||||
rec_len={1,None}):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, rec_len=rec_len)
|
||||
|
||||
def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
|
||||
rec_len={1, None}):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name,
|
||||
desc=desc, parent=parent, rec_len=rec_len)
|
||||
|
||||
|
||||
class TransRecEF(TransparentEF):
|
||||
"""Transparent EF (Entry File) containing fixed-size records.
|
||||
|
@ -872,8 +919,9 @@ class TransRecEF(TransparentEF):
|
|||
We add a special class for those, so the user only has to provide encoder/decoder functions
|
||||
for a record, while this class takes care of split / merge of records.
|
||||
"""
|
||||
def __init__(self, fid:str, rec_len:int, sfid:str=None, name:str=None, desc:str=None,
|
||||
parent:Optional[CardDF]=None, size={1,None}):
|
||||
|
||||
def __init__(self, fid: str, rec_len: int, sfid: str = None, name: str = None, desc: str = None,
|
||||
parent: Optional[CardDF] = None, size={1, None}):
|
||||
"""
|
||||
Args:
|
||||
fid : File Identifier (4 hex digits)
|
||||
|
@ -887,7 +935,7 @@ class TransRecEF(TransparentEF):
|
|||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size)
|
||||
self.rec_len = rec_len
|
||||
|
||||
def decode_record_hex(self, raw_hex_data:str) -> dict:
|
||||
def decode_record_hex(self, raw_hex_data: str) -> dict:
|
||||
"""Decode raw (hex string) data into abstract representation.
|
||||
|
||||
A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
|
||||
|
@ -914,7 +962,7 @@ class TransRecEF(TransparentEF):
|
|||
return t.to_dict()
|
||||
return {'raw': raw_hex_data}
|
||||
|
||||
def decode_record_bin(self, raw_bin_data:bytearray) -> dict:
|
||||
def decode_record_bin(self, raw_bin_data: bytearray) -> dict:
|
||||
"""Decode raw (binary) data into abstract representation.
|
||||
|
||||
A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
|
||||
|
@ -941,7 +989,7 @@ class TransRecEF(TransparentEF):
|
|||
return t.to_dict()
|
||||
return {'raw': raw_hex_data}
|
||||
|
||||
def encode_record_hex(self, abstract_data:dict) -> str:
|
||||
def encode_record_hex(self, abstract_data: dict) -> str:
|
||||
"""Encode abstract representation into raw (hex string) data.
|
||||
|
||||
A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
|
||||
|
@ -965,9 +1013,10 @@ class TransRecEF(TransparentEF):
|
|||
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
|
||||
t.from_dict(abstract_data)
|
||||
return b2h(t.to_tlv())
|
||||
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
|
||||
raise NotImplementedError(
|
||||
"%s encoder not yet implemented. Patches welcome." % self)
|
||||
|
||||
def encode_record_bin(self, abstract_data:dict) -> bytearray:
|
||||
def encode_record_bin(self, abstract_data: dict) -> bytearray:
|
||||
"""Encode abstract representation into raw (binary) data.
|
||||
|
||||
A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
|
||||
|
@ -991,10 +1040,12 @@ class TransRecEF(TransparentEF):
|
|||
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
|
||||
t.from_dict(abstract_data)
|
||||
return t.to_tlv()
|
||||
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
|
||||
raise NotImplementedError(
|
||||
"%s encoder not yet implemented. Patches welcome." % self)
|
||||
|
||||
def _decode_bin(self, raw_bin_data:bytearray):
|
||||
chunks = [raw_bin_data[i:i+self.rec_len] for i in range(0, len(raw_bin_data), self.rec_len)]
|
||||
def _decode_bin(self, raw_bin_data: bytearray):
|
||||
chunks = [raw_bin_data[i:i+self.rec_len]
|
||||
for i in range(0, len(raw_bin_data), self.rec_len)]
|
||||
return [self.decode_record_bin(x) for x in chunks]
|
||||
|
||||
def _encode_bin(self, abstract_data) -> bytes:
|
||||
|
@ -1014,11 +1065,14 @@ class BerTlvEF(CardEF):
|
|||
@with_default_category('BER-TLV EF Commands')
|
||||
class ShellCommands(CommandSet):
|
||||
"""Shell commands specific for BER-TLV EFs."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
retrieve_data_parser = argparse.ArgumentParser()
|
||||
retrieve_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to retrieve')
|
||||
retrieve_data_parser.add_argument(
|
||||
'tag', type=auto_int, help='BER-TLV Tag of value to retrieve')
|
||||
|
||||
@cmd2.with_argparser(retrieve_data_parser)
|
||||
def do_retrieve_data(self, opts):
|
||||
"""Retrieve (Read) data from a BER-TLV EF"""
|
||||
|
@ -1031,8 +1085,11 @@ class BerTlvEF(CardEF):
|
|||
self._cmd.poutput(tags)
|
||||
|
||||
set_data_parser = argparse.ArgumentParser()
|
||||
set_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to set')
|
||||
set_data_parser.add_argument('data', help='Data bytes (hex format) to write')
|
||||
set_data_parser.add_argument(
|
||||
'tag', type=auto_int, help='BER-TLV Tag of value to set')
|
||||
set_data_parser.add_argument(
|
||||
'data', help='Data bytes (hex format) to write')
|
||||
|
||||
@cmd2.with_argparser(set_data_parser)
|
||||
def do_set_data(self, opts):
|
||||
"""Set (Write) data for a given tag in a BER-TLV EF"""
|
||||
|
@ -1041,7 +1098,9 @@ class BerTlvEF(CardEF):
|
|||
self._cmd.poutput(data)
|
||||
|
||||
del_data_parser = argparse.ArgumentParser()
|
||||
del_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to set')
|
||||
del_data_parser.add_argument(
|
||||
'tag', type=auto_int, help='BER-TLV Tag of value to set')
|
||||
|
||||
@cmd2.with_argparser(del_data_parser)
|
||||
def do_delete_data(self, opts):
|
||||
"""Delete data for a given tag in a BER-TLV EF"""
|
||||
|
@ -1049,9 +1108,8 @@ class BerTlvEF(CardEF):
|
|||
if data:
|
||||
self._cmd.poutput(data)
|
||||
|
||||
|
||||
def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None,
|
||||
size={1,None}):
|
||||
def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
|
||||
size={1, None}):
|
||||
"""
|
||||
Args:
|
||||
fid : File Identifier (4 hex digits)
|
||||
|
@ -1069,7 +1127,8 @@ class BerTlvEF(CardEF):
|
|||
|
||||
class RuntimeState(object):
|
||||
"""Represent the runtime state of a session with a card."""
|
||||
def __init__(self, card, profile:'CardProfile'):
|
||||
|
||||
def __init__(self, card, profile: 'CardProfile'):
|
||||
"""
|
||||
Args:
|
||||
card : pysim.cards.Card instance
|
||||
|
@ -1082,7 +1141,8 @@ class RuntimeState(object):
|
|||
|
||||
# make sure the class and selection control bytes, which are specified
|
||||
# by the card profile are used
|
||||
self.card.set_apdu_parameter(cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl)
|
||||
self.card.set_apdu_parameter(
|
||||
cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl)
|
||||
|
||||
# add application ADFs + MF-files from profile
|
||||
apps = self._match_applications()
|
||||
|
@ -1170,7 +1230,7 @@ class RuntimeState(object):
|
|||
node = node.parent
|
||||
return None
|
||||
|
||||
def interpret_sw(self, sw:str):
|
||||
def interpret_sw(self, sw: str):
|
||||
"""Interpret a given status word relative to the currently selected application
|
||||
or the underlying card profile.
|
||||
|
||||
|
@ -1191,11 +1251,12 @@ class RuntimeState(object):
|
|||
res = app.interpret_sw(sw)
|
||||
return res or self.profile.interpret_sw(sw)
|
||||
|
||||
def probe_file(self, fid:str, cmd_app=None):
|
||||
def probe_file(self, fid: str, cmd_app=None):
|
||||
"""Blindly try to select a file and automatically add a matching file
|
||||
object if the file actually exists."""
|
||||
if not is_hex(fid, 4, 4):
|
||||
raise ValueError("Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid)
|
||||
raise ValueError(
|
||||
"Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid)
|
||||
|
||||
try:
|
||||
(data, sw) = self.card._scc.select_file(fid)
|
||||
|
@ -1207,18 +1268,21 @@ class RuntimeState(object):
|
|||
|
||||
select_resp = self.selected_file.decode_select_response(data)
|
||||
if (select_resp['file_descriptor']['file_type'] == 'df'):
|
||||
f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(), desc="dedicated file, manually added at runtime")
|
||||
f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(),
|
||||
desc="dedicated file, manually added at runtime")
|
||||
else:
|
||||
if (select_resp['file_descriptor']['structure'] == 'transparent'):
|
||||
f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(), desc="elementary file, manually added at runtime")
|
||||
f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
|
||||
desc="elementary file, manually added at runtime")
|
||||
else:
|
||||
f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(), desc="elementary file, manually added at runtime")
|
||||
f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
|
||||
desc="elementary file, manually added at runtime")
|
||||
|
||||
self.selected_file.add_files([f])
|
||||
self.selected_file = f
|
||||
return select_resp
|
||||
|
||||
def select(self, name:str, cmd_app=None):
|
||||
def select(self, name: str, cmd_app=None):
|
||||
"""Select a file (EF, DF, ADF, MF, ...).
|
||||
|
||||
Args:
|
||||
|
@ -1265,14 +1329,14 @@ class RuntimeState(object):
|
|||
(data, sw) = self.card._scc.status()
|
||||
return self.selected_file.decode_select_response(data)
|
||||
|
||||
def activate_file(self, name:str):
|
||||
def activate_file(self, name: str):
|
||||
"""Request ACTIVATE FILE of specified file."""
|
||||
sels = self.selected_file.get_selectables()
|
||||
f = sels[name]
|
||||
data, sw = self.card._scc.activate_file(f.fid)
|
||||
return data, sw
|
||||
|
||||
def read_binary(self, length:int=None, offset:int=0):
|
||||
def read_binary(self, length: int = None, offset: int = 0):
|
||||
"""Read [part of] a transparent EF binary data.
|
||||
|
||||
Args:
|
||||
|
@ -1298,7 +1362,7 @@ class RuntimeState(object):
|
|||
dec_data = self.selected_file.decode_hex(data)
|
||||
return (dec_data, sw)
|
||||
|
||||
def update_binary(self, data_hex:str, offset:int=0):
|
||||
def update_binary(self, data_hex: str, offset: int = 0):
|
||||
"""Update transparent EF binary data.
|
||||
|
||||
Args:
|
||||
|
@ -1309,7 +1373,7 @@ class RuntimeState(object):
|
|||
raise TypeError("Only works with TransparentEF")
|
||||
return self.card._scc.update_binary(self.selected_file.fid, data_hex, offset, conserve=self.conserve_write)
|
||||
|
||||
def update_binary_dec(self, data:dict):
|
||||
def update_binary_dec(self, data: dict):
|
||||
"""Update transparent EF from abstract data. Encodes the data to binary and
|
||||
then updates the EF with it.
|
||||
|
||||
|
@ -1319,7 +1383,7 @@ class RuntimeState(object):
|
|||
data_hex = self.selected_file.encode_hex(data)
|
||||
return self.update_binary(data_hex)
|
||||
|
||||
def read_record(self, rec_nr:int=0):
|
||||
def read_record(self, rec_nr: int = 0):
|
||||
"""Read a record as binary data.
|
||||
|
||||
Args:
|
||||
|
@ -1332,7 +1396,7 @@ class RuntimeState(object):
|
|||
# returns a string of hex nibbles
|
||||
return self.card._scc.read_record(self.selected_file.fid, rec_nr)
|
||||
|
||||
def read_record_dec(self, rec_nr:int=0) -> Tuple[dict, str]:
|
||||
def read_record_dec(self, rec_nr: int = 0) -> Tuple[dict, str]:
|
||||
"""Read a record and decode it to abstract data.
|
||||
|
||||
Args:
|
||||
|
@ -1343,7 +1407,7 @@ class RuntimeState(object):
|
|||
(data, sw) = self.read_record(rec_nr)
|
||||
return (self.selected_file.decode_record_hex(data), sw)
|
||||
|
||||
def update_record(self, rec_nr:int, data_hex:str):
|
||||
def update_record(self, rec_nr: int, data_hex: str):
|
||||
"""Update a record with given binary data
|
||||
|
||||
Args:
|
||||
|
@ -1354,7 +1418,7 @@ class RuntimeState(object):
|
|||
raise TypeError("Only works with Linear Fixed EF")
|
||||
return self.card._scc.update_record(self.selected_file.fid, rec_nr, data_hex, conserve=self.conserve_write)
|
||||
|
||||
def update_record_dec(self, rec_nr:int, data:dict):
|
||||
def update_record_dec(self, rec_nr: int, data: dict):
|
||||
"""Update a record with given abstract data. Will encode abstract to binary data
|
||||
and then write it to the given record on the card.
|
||||
|
||||
|
@ -1365,7 +1429,7 @@ class RuntimeState(object):
|
|||
data_hex = self.selected_file.encode_record_hex(data)
|
||||
return self.update_record(rec_nr, data_hex)
|
||||
|
||||
def retrieve_data(self, tag:int=0):
|
||||
def retrieve_data(self, tag: int = 0):
|
||||
"""Read a DO/TLV as binary data.
|
||||
|
||||
Args:
|
||||
|
@ -1390,7 +1454,7 @@ class RuntimeState(object):
|
|||
tag, length, value, remainder = bertlv_parse_one(h2b(data))
|
||||
return list(value)
|
||||
|
||||
def set_data(self, tag:int, data_hex:str):
|
||||
def set_data(self, tag: int, data_hex: str):
|
||||
"""Update a TLV/DO with given binary data
|
||||
|
||||
Args:
|
||||
|
@ -1408,15 +1472,15 @@ class RuntimeState(object):
|
|||
cmd_app.unregister_command_set(c)
|
||||
|
||||
|
||||
|
||||
class FileData(object):
|
||||
"""Represent the runtime, on-card data."""
|
||||
|
||||
def __init__(self, fdesc):
|
||||
self.desc = fdesc
|
||||
self.fcp = None
|
||||
|
||||
|
||||
def interpret_sw(sw_data:dict, sw:str):
|
||||
def interpret_sw(sw_data: dict, sw: str):
|
||||
"""Interpret a given status word.
|
||||
|
||||
Args:
|
||||
|
@ -1435,10 +1499,12 @@ def interpret_sw(sw_data:dict, sw:str):
|
|||
return (class_str, descr)
|
||||
return None
|
||||
|
||||
|
||||
class CardApplication(object):
|
||||
"""A card application is represented by an ADF (with contained hierarchy) and optionally
|
||||
some SW definitions."""
|
||||
def __init__(self, name, adf:Optional[CardADF]=None, aid:str=None, sw:dict=None):
|
||||
|
||||
def __init__(self, name, adf: Optional[CardADF] = None, aid: str = None, sw: dict = None):
|
||||
"""
|
||||
Args:
|
||||
adf : ADF name
|
||||
|
@ -1477,11 +1543,11 @@ class CardModel(abc.ABC):
|
|||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def add_files(cls, rs:RuntimeState):
|
||||
def add_files(cls, rs: RuntimeState):
|
||||
"""Add model specific files to given RuntimeState."""
|
||||
|
||||
@classmethod
|
||||
def match(cls, scc:SimCardCommands) -> bool:
|
||||
def match(cls, scc: SimCardCommands) -> bool:
|
||||
"""Test if given card matches this model."""
|
||||
card_atr = scc.get_atr()
|
||||
for atr in cls._atrs:
|
||||
|
@ -1492,7 +1558,7 @@ class CardModel(abc.ABC):
|
|||
return False
|
||||
|
||||
@staticmethod
|
||||
def apply_matching_models(scc:SimCardCommands, rs:RuntimeState):
|
||||
def apply_matching_models(scc: SimCardCommands, rs: RuntimeState):
|
||||
"""Check if any of the CardModel sub-classes 'match' the currently inserted card
|
||||
(by ATR or overriding the 'match' method). If so, call their 'add_files'
|
||||
method."""
|
||||
|
|
103
pySim/gsm_r.py
103
pySim/gsm_r.py
|
@ -43,26 +43,32 @@ import pySim.ts_51_011
|
|||
# DF.EIRENE (FFFIS for GSM-R SIM Cards)
|
||||
######################################################################
|
||||
|
||||
|
||||
class FuncNTypeAdapter(Adapter):
|
||||
def _decode(self, obj, context, path):
|
||||
bcd = swap_nibbles(b2h(obj))
|
||||
last_digit = bcd[-1]
|
||||
return {'functional_number': bcd[:-1],
|
||||
'presentation_of_only_this_fn': last_digit & 4,
|
||||
'permanent_fn': last_digit & 8 }
|
||||
'permanent_fn': last_digit & 8}
|
||||
|
||||
def _encode(self, obj, context, path):
|
||||
return 'FIXME'
|
||||
|
||||
|
||||
class EF_FN(LinFixedEF):
|
||||
"""Section 7.2"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(fid='6ff1', sfid=None, name='EF.EN', desc='Functional numbers', rec_len={9,9})
|
||||
super().__init__(fid='6ff1', sfid=None, name='EF.EN',
|
||||
desc='Functional numbers', rec_len={9, 9})
|
||||
self._construct = Struct('functional_number_and_type'/FuncNTypeAdapter(Bytes(8)),
|
||||
'list_number'/Int8ub)
|
||||
|
||||
|
||||
class PlConfAdapter(Adapter):
|
||||
"""Section 7.4.3"""
|
||||
|
||||
def _decode(self, obj, context, path):
|
||||
num = int(obj) & 0x7
|
||||
if num == 0:
|
||||
|
@ -77,6 +83,7 @@ class PlConfAdapter(Adapter):
|
|||
return 1
|
||||
elif num == 5:
|
||||
return 0
|
||||
|
||||
def _encode(self, obj, context, path):
|
||||
if obj == 'None':
|
||||
return 0
|
||||
|
@ -92,8 +99,10 @@ class PlConfAdapter(Adapter):
|
|||
elif obj == 0:
|
||||
return 5
|
||||
|
||||
|
||||
class PlCallAdapter(Adapter):
|
||||
"""Section 7.4.12"""
|
||||
|
||||
def _decode(self, obj, context, path):
|
||||
num = int(obj) & 0x7
|
||||
if num == 0:
|
||||
|
@ -112,6 +121,7 @@ class PlCallAdapter(Adapter):
|
|||
return 'B'
|
||||
elif num == 7:
|
||||
return 'A'
|
||||
|
||||
def _encode(self, obj, context, path):
|
||||
if obj == 'None':
|
||||
return 0
|
||||
|
@ -130,12 +140,16 @@ class PlCallAdapter(Adapter):
|
|||
elif obj == 'A':
|
||||
return 7
|
||||
|
||||
NextTableType = Enum(Byte, decision=0xf0, predefined=0xf1, num_dial_digits=0xf2, ic=0xf3, empty=0xff)
|
||||
|
||||
NextTableType = Enum(Byte, decision=0xf0, predefined=0xf1,
|
||||
num_dial_digits=0xf2, ic=0xf3, empty=0xff)
|
||||
|
||||
|
||||
class EF_CallconfC(TransparentEF):
|
||||
"""Section 7.3"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(fid='6ff2', sfid=None, name='EF.CallconfC', size={24,24},
|
||||
super().__init__(fid='6ff2', sfid=None, name='EF.CallconfC', size={24, 24},
|
||||
desc='Call Configuration of emergency calls Configuration')
|
||||
self._construct = Struct('pl_conf'/PlConfAdapter(Int8ub),
|
||||
'conf_nr'/BcdAdapter(Bytes(8)),
|
||||
|
@ -147,29 +161,39 @@ class EF_CallconfC(TransparentEF):
|
|||
'shunting_emergency_gid'/Int8ub,
|
||||
'imei'/BcdAdapter(Bytes(8)))
|
||||
|
||||
|
||||
class EF_CallconfI(LinFixedEF):
|
||||
"""Section 7.5"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(fid='6ff3', sfid=None, name='EF.CallconfI', rec_len={21,21},
|
||||
super().__init__(fid='6ff3', sfid=None, name='EF.CallconfI', rec_len={21, 21},
|
||||
desc='Call Configuration of emergency calls Information')
|
||||
self._construct = Struct('t_dur'/Int24ub,
|
||||
't_relcalc'/Int32ub,
|
||||
'pl_call'/PlCallAdapter(Int8ub),
|
||||
'cause'/FlagsEnum(Int8ub, powered_off=1, radio_link_error=2, user_command=5),
|
||||
'cause' /
|
||||
FlagsEnum(Int8ub, powered_off=1,
|
||||
radio_link_error=2, user_command=5),
|
||||
'gcr'/BcdAdapter(Bytes(4)),
|
||||
'fnr'/BcdAdapter(Bytes(8)))
|
||||
|
||||
|
||||
class EF_Shunting(TransparentEF):
|
||||
"""Section 7.6"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(fid='6ff4', sfid=None, name='EF.Shunting', desc='Shunting', size={8,8})
|
||||
super().__init__(fid='6ff4', sfid=None,
|
||||
name='EF.Shunting', desc='Shunting', size={8, 8})
|
||||
self._construct = Struct('common_gid'/Int8ub,
|
||||
'shunting_gid'/Bytes(7))
|
||||
|
||||
|
||||
class EF_GsmrPLMN(LinFixedEF):
|
||||
"""Section 7.7"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(fid='6ff5', sfid=None, name='EF.GsmrPLMN', desc='GSM-R network selection', rec_len={9,9})
|
||||
super().__init__(fid='6ff5', sfid=None, name='EF.GsmrPLMN',
|
||||
desc='GSM-R network selection', rec_len={9, 9})
|
||||
self._construct = Struct('plmn'/BcdAdapter(Bytes(3)),
|
||||
'class_of_network'/BitStruct('supported'/FlagsEnum(BitsInteger(5), vbs=1, vgcs=2, emlpp=4, fn=8, eirene=16),
|
||||
'preference'/BitsInteger(3)),
|
||||
|
@ -177,34 +201,46 @@ class EF_GsmrPLMN(LinFixedEF):
|
|||
'outgoing_ref_tbl'/HexAdapter(Bytes(2)),
|
||||
'ic_table_ref'/HexAdapter(Bytes(1)))
|
||||
|
||||
|
||||
class EF_IC(LinFixedEF):
|
||||
"""Section 7.8"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(fid='6f8d', sfid=None, name='EF.IC', desc='International Code', rec_len={7,7})
|
||||
super().__init__(fid='6f8d', sfid=None, name='EF.IC',
|
||||
desc='International Code', rec_len={7, 7})
|
||||
self._construct = Struct('next_table_type'/NextTableType,
|
||||
'id_of_next_table'/HexAdapter(Bytes(2)),
|
||||
'ic_decision_value'/BcdAdapter(Bytes(2)),
|
||||
'network_string_table_index'/Int8ub)
|
||||
|
||||
|
||||
class EF_NW(LinFixedEF):
|
||||
"""Section 7.9"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(fid='6f80', sfid=None, name='EF.NW', desc='Network Name', rec_len={8,8})
|
||||
super().__init__(fid='6f80', sfid=None, name='EF.NW',
|
||||
desc='Network Name', rec_len={8, 8})
|
||||
self._construct = GsmString(8)
|
||||
|
||||
|
||||
class EF_Switching(LinFixedEF):
|
||||
"""Section 8.4"""
|
||||
|
||||
def __init__(self, fid, name, desc):
|
||||
super().__init__(fid=fid, sfid=None, name=name, desc=desc, rec_len={6,6})
|
||||
super().__init__(fid=fid, sfid=None,
|
||||
name=name, desc=desc, rec_len={6, 6})
|
||||
self._construct = Struct('next_table_type'/NextTableType,
|
||||
'id_of_next_table'/HexAdapter(Bytes(2)),
|
||||
'decision_value'/BcdAdapter(Bytes(2)),
|
||||
'string_table_index'/Int8ub)
|
||||
|
||||
|
||||
class EF_Predefined(LinFixedEF):
|
||||
"""Section 8.5"""
|
||||
|
||||
def __init__(self, fid, name, desc):
|
||||
super().__init__(fid=fid, sfid=None, name=name, desc=desc, rec_len={3,3})
|
||||
super().__init__(fid=fid, sfid=None,
|
||||
name=name, desc=desc, rec_len={3, 3})
|
||||
# header and other records have different structure. WTF !?!
|
||||
self._construct = Struct('next_table_type'/NextTableType,
|
||||
'id_of_next_table'/HexAdapter(Bytes(2)),
|
||||
|
@ -212,10 +248,12 @@ class EF_Predefined(LinFixedEF):
|
|||
'string_table_index1'/Int8ub)
|
||||
# TODO: predefined value n, ...
|
||||
|
||||
|
||||
class EF_DialledVals(TransparentEF):
|
||||
"""Section 8.6"""
|
||||
|
||||
def __init__(self, fid, name, desc):
|
||||
super().__init__(fid=fid, sfid=None, name=name, desc=desc, size={4,4})
|
||||
super().__init__(fid=fid, sfid=None, name=name, desc=desc, size={4, 4})
|
||||
self._construct = Struct('next_table_type'/NextTableType,
|
||||
'id_of_next_table'/HexAdapter(Bytes(2)),
|
||||
'dialed_digits'/BcdAdapter(Bytes(1)))
|
||||
|
@ -238,18 +276,31 @@ class DF_EIRENE(CardDF):
|
|||
EF_Switching(fid='6f8e', name='EF.CT', desc='Call Type'),
|
||||
EF_Switching(fid='6f8f', name='EF.SC', desc='Short Code'),
|
||||
EF_Predefined(fid='6f88', name='EF.FC', desc='Function Code'),
|
||||
EF_Predefined(fid='6f89', name='EF.Service', desc='VGCS/VBS Service Code'),
|
||||
EF_Predefined(fid='6f8a', name='EF.Call', desc='First digit of the group ID'),
|
||||
EF_Predefined(fid='6f8b', name='EF.FctTeam', desc='Call Type 6 Team Type + Team member function'),
|
||||
EF_Predefined(fid='6f92', name='EF.Controller', desc='Call Type 7 Controller function code'),
|
||||
EF_Predefined(fid='6f8c', name='EF.Gateway', desc='Access to external networks'),
|
||||
EF_DialledVals(fid='6f81', name='EF.5to8digits', desc='Call Type 2 User Identity Number length'),
|
||||
EF_DialledVals(fid='6f82', name='EF.2digits', desc='2 digits input'),
|
||||
EF_DialledVals(fid='6f83', name='EF.8digits', desc='8 digits input'),
|
||||
EF_DialledVals(fid='6f84', name='EF.9digits', desc='9 digits input'),
|
||||
EF_DialledVals(fid='6f85', name='EF.SSSSS', desc='Group call area input'),
|
||||
EF_DialledVals(fid='6f86', name='EF.LLLLL', desc='Location number Call Type 6'),
|
||||
EF_DialledVals(fid='6f91', name='EF.Location', desc='Location number Call Type 7'),
|
||||
EF_DialledVals(fid='6f87', name='EF.FreeNumber', desc='Free Number Call Type 0 and 8'),
|
||||
EF_Predefined(fid='6f89', name='EF.Service',
|
||||
desc='VGCS/VBS Service Code'),
|
||||
EF_Predefined(fid='6f8a', name='EF.Call',
|
||||
desc='First digit of the group ID'),
|
||||
EF_Predefined(fid='6f8b', name='EF.FctTeam',
|
||||
desc='Call Type 6 Team Type + Team member function'),
|
||||
EF_Predefined(fid='6f92', name='EF.Controller',
|
||||
desc='Call Type 7 Controller function code'),
|
||||
EF_Predefined(fid='6f8c', name='EF.Gateway',
|
||||
desc='Access to external networks'),
|
||||
EF_DialledVals(fid='6f81', name='EF.5to8digits',
|
||||
desc='Call Type 2 User Identity Number length'),
|
||||
EF_DialledVals(fid='6f82', name='EF.2digits',
|
||||
desc='2 digits input'),
|
||||
EF_DialledVals(fid='6f83', name='EF.8digits',
|
||||
desc='8 digits input'),
|
||||
EF_DialledVals(fid='6f84', name='EF.9digits',
|
||||
desc='9 digits input'),
|
||||
EF_DialledVals(fid='6f85', name='EF.SSSSS',
|
||||
desc='Group call area input'),
|
||||
EF_DialledVals(fid='6f86', name='EF.LLLLL',
|
||||
desc='Location number Call Type 6'),
|
||||
EF_DialledVals(fid='6f91', name='EF.Location',
|
||||
desc='Location number Call Type 7'),
|
||||
EF_DialledVals(fid='6f87', name='EF.FreeNumber',
|
||||
desc='Free Number Call Type 0 and 8'),
|
||||
]
|
||||
self.add_files(files)
|
||||
|
|
|
@ -24,39 +24,57 @@ from pySim.filesystem import *
|
|||
from pySim.tlv import *
|
||||
|
||||
# Table 91 + Section 8.2.1.2
|
||||
|
||||
|
||||
class ApplicationId(BER_TLV_IE, tag=0x4f):
|
||||
_construct = GreedyBytes
|
||||
|
||||
# Table 91
|
||||
|
||||
|
||||
class ApplicationLabel(BER_TLV_IE, tag=0x50):
|
||||
_construct = GreedyBytes
|
||||
|
||||
# Table 91 + Section 5.3.1.2
|
||||
|
||||
|
||||
class FileReference(BER_TLV_IE, tag=0x51):
|
||||
_construct = GreedyBytes
|
||||
|
||||
# Table 91
|
||||
|
||||
|
||||
class CommandApdu(BER_TLV_IE, tag=0x52):
|
||||
_construct = GreedyBytes
|
||||
|
||||
# Table 91
|
||||
|
||||
|
||||
class DiscretionaryData(BER_TLV_IE, tag=0x53):
|
||||
_construct = GreedyBytes
|
||||
|
||||
# Table 91
|
||||
|
||||
|
||||
class DiscretionaryTemplate(BER_TLV_IE, tag=0x73):
|
||||
_construct = GreedyBytes
|
||||
|
||||
# Table 91 + RFC1738 / RFC2396
|
||||
|
||||
|
||||
class URL(BER_TLV_IE, tag=0x5f50):
|
||||
_construct = GreedyString('ascii')
|
||||
|
||||
# Table 91
|
||||
|
||||
|
||||
class ApplicationRelatedDOSet(BER_TLV_IE, tag=0x61):
|
||||
_construct = GreedyBytes
|
||||
|
||||
# Section 8.2.1.3 Application Template
|
||||
|
||||
|
||||
class ApplicationTemplate(BER_TLV_IE, tag=0x61, nested=[ApplicationId, ApplicationLabel, FileReference,
|
||||
CommandApdu, DiscretionaryData, DiscretionaryTemplate,URL,
|
||||
CommandApdu, DiscretionaryData, DiscretionaryTemplate, URL,
|
||||
ApplicationRelatedDOSet]):
|
||||
pass
|
||||
|
|
|
@ -25,6 +25,7 @@ of a file or record in its JSON representation.
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
def js_path_find(js_dict, js_path):
|
||||
"""Find/Match a JSON path within a given JSON-serializable dict.
|
||||
Args:
|
||||
|
@ -35,6 +36,7 @@ def js_path_find(js_dict, js_path):
|
|||
jsonpath_expr = jsonpath_ng.parse(js_path)
|
||||
return jsonpath_expr.find(js_dict)
|
||||
|
||||
|
||||
def js_path_modify(js_dict, js_path, new_val):
|
||||
"""Find/Match a JSON path within a given JSON-serializable dict.
|
||||
Args:
|
||||
|
@ -45,4 +47,3 @@ def js_path_modify(js_dict, js_path, new_val):
|
|||
jsonpath_expr = jsonpath_ng.parse(js_path)
|
||||
jsonpath_expr.find(js_dict)
|
||||
jsonpath_expr.update(js_dict, new_val)
|
||||
|
||||
|
|
|
@ -27,7 +27,8 @@ from pySim.utils import all_subclasses
|
|||
import abc
|
||||
import operator
|
||||
|
||||
def _mf_select_test(scc:SimCardCommands, cla_byte:str, sel_ctrl:str) -> bool:
|
||||
|
||||
def _mf_select_test(scc: SimCardCommands, cla_byte: str, sel_ctrl: str) -> bool:
|
||||
cla_byte_bak = scc.cla_byte
|
||||
sel_ctrl_bak = scc.sel_ctrl
|
||||
scc.reset_card()
|
||||
|
@ -45,19 +46,22 @@ def _mf_select_test(scc:SimCardCommands, cla_byte:str, sel_ctrl:str) -> bool:
|
|||
scc.sel_ctrl = sel_ctrl_bak
|
||||
return rc
|
||||
|
||||
def match_uicc(scc:SimCardCommands) -> bool:
|
||||
|
||||
def match_uicc(scc: SimCardCommands) -> bool:
|
||||
""" Try to access MF via UICC APDUs (3GPP TS 102.221), if this works, the
|
||||
card is considered a UICC card.
|
||||
"""
|
||||
return _mf_select_test(scc, "00", "0004")
|
||||
|
||||
def match_sim(scc:SimCardCommands) -> bool:
|
||||
|
||||
def match_sim(scc: SimCardCommands) -> bool:
|
||||
""" Try to access MF via 2G APDUs (3GPP TS 11.11), if this works, the card
|
||||
is also a simcard. This will be the case for most UICC cards, but there may
|
||||
also be plain UICC cards without 2G support as well.
|
||||
"""
|
||||
return _mf_select_test(scc, "a0", "0000")
|
||||
|
||||
|
||||
class CardProfile(object):
|
||||
"""A Card Profile describes a card, it's filesystem hierarchy, an [initial] list of
|
||||
applications as well as profile-specific SW and shell commands. Every card has
|
||||
|
@ -86,7 +90,7 @@ class CardProfile(object):
|
|||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def add_application(self, app:CardApplication):
|
||||
def add_application(self, app: CardApplication):
|
||||
"""Add an application to a card profile.
|
||||
|
||||
Args:
|
||||
|
@ -94,7 +98,7 @@ class CardProfile(object):
|
|||
"""
|
||||
self.applications.append(app)
|
||||
|
||||
def interpret_sw(self, sw:str):
|
||||
def interpret_sw(self, sw: str):
|
||||
"""Interpret a given status word within the profile.
|
||||
|
||||
Args:
|
||||
|
@ -106,7 +110,7 @@ class CardProfile(object):
|
|||
return interpret_sw(self.sw, sw)
|
||||
|
||||
@staticmethod
|
||||
def decode_select_response(data_hex:str) -> object:
|
||||
def decode_select_response(data_hex: str) -> object:
|
||||
"""Decode the response to a SELECT command.
|
||||
|
||||
This is the fall-back method which doesn't perform any decoding. It mostly
|
||||
|
@ -121,7 +125,7 @@ class CardProfile(object):
|
|||
|
||||
@staticmethod
|
||||
@abc.abstractmethod
|
||||
def match_with_card(scc:SimCardCommands) -> bool:
|
||||
def match_with_card(scc: SimCardCommands) -> bool:
|
||||
"""Check if the specific profile matches the card. This method is a
|
||||
placeholder that is overloaded by specific dirived classes. The method
|
||||
actively probes the card to make sure the profile class matches the
|
||||
|
@ -137,7 +141,7 @@ class CardProfile(object):
|
|||
return False
|
||||
|
||||
@staticmethod
|
||||
def pick(scc:SimCardCommands):
|
||||
def pick(scc: SimCardCommands):
|
||||
profiles = list(all_subclasses(CardProfile))
|
||||
profiles.sort(key=operator.attrgetter('ORDER'))
|
||||
|
||||
|
|
|
@ -43,9 +43,11 @@ mac_length = {
|
|||
1: 4
|
||||
}
|
||||
|
||||
|
||||
class EF_PIN(TransparentEF):
|
||||
def __init__(self, fid, name):
|
||||
super().__init__(fid, name=name, desc='%s PIN file' % name)
|
||||
|
||||
def _decode_bin(self, raw_bin_data):
|
||||
u = unpack('!BBB8s', raw_bin_data[:11])
|
||||
res = {'enabled': (True, False)[u[0] & 0x01],
|
||||
|
@ -65,9 +67,11 @@ class EF_PIN(TransparentEF):
|
|||
res['puk'] = u2[2].hex()
|
||||
return res
|
||||
|
||||
|
||||
class EF_MILENAGE_CFG(TransparentEF):
|
||||
def __init__(self, fid='6f21', name='EF.MILENAGE_CFG', desc='Milenage connfiguration'):
|
||||
super().__init__(fid, name=name, desc=desc)
|
||||
|
||||
def _decode_bin(self, raw_bin_data):
|
||||
u = unpack('!BBBBB16s16s16s16s16s', raw_bin_data)
|
||||
return {'r1': u[0], 'r2': u[1], 'r3': u[2], 'r4': u[3], 'r5': u[4],
|
||||
|
@ -78,9 +82,11 @@ class EF_MILENAGE_CFG(TransparentEF):
|
|||
'c5': u[9].hex(),
|
||||
}
|
||||
|
||||
|
||||
class EF_0348_KEY(LinFixedEF):
|
||||
def __init__(self, fid='6f22', name='EF.0348_KEY', desc='TS 03.48 OTA Keys'):
|
||||
super().__init__(fid, name=name, desc=desc, rec_len={27,35})
|
||||
super().__init__(fid, name=name, desc=desc, rec_len={27, 35})
|
||||
|
||||
def _decode_record_bin(self, raw_bin_data):
|
||||
u = unpack('!BBB', raw_bin_data[0:3])
|
||||
key_algo = (u[2] >> 6) & 1
|
||||
|
@ -94,32 +100,40 @@ class EF_0348_KEY(LinFixedEF):
|
|||
'key': raw_bin_data[3:key_length].hex()
|
||||
}
|
||||
|
||||
|
||||
class EF_0348_COUNT(LinFixedEF):
|
||||
def __init__(self, fid='6f23', name='EF.0348_COUNT', desc='TS 03.48 OTA Counters'):
|
||||
super().__init__(fid, name=name, desc=desc, rec_len={7,7})
|
||||
super().__init__(fid, name=name, desc=desc, rec_len={7, 7})
|
||||
|
||||
def _decode_record_bin(self, raw_bin_data):
|
||||
u = unpack('!BB5s', raw_bin_data)
|
||||
return {'sec_domain': u[0], 'key_set_version': u[1], 'counter': u[2]}
|
||||
|
||||
|
||||
class EF_SIM_AUTH_COUNTER(TransparentEF):
|
||||
def __init__(self, fid='af24', name='EF.SIM_AUTH_COUNTER'):
|
||||
super().__init__(fid, name=name, desc='Number of remaining RUN GSM ALGORITHM executions')
|
||||
self._construct = Struct('num_run_gsm_algo_remain'/Int32ub)
|
||||
|
||||
|
||||
class EF_GP_COUNT(LinFixedEF):
|
||||
def __init__(self, fid='6f26', name='EF.GP_COUNT', desc='GP SCP02 Counters'):
|
||||
super().__init__(fid, name=name, desc=desc, rec_len={5,5})
|
||||
super().__init__(fid, name=name, desc=desc, rec_len={5, 5})
|
||||
|
||||
def _decode_record_bin(self, raw_bin_data):
|
||||
u = unpack('!BBHB', raw_bin_data)
|
||||
return {'sec_domain': u[0], 'key_set_version': u[1], 'counter': u[2], 'rfu': u[3]}
|
||||
|
||||
|
||||
class EF_GP_DIV_DATA(LinFixedEF):
|
||||
def __init__(self, fid='6f27', name='EF.GP_DIV_DATA', desc='GP SCP02 key diversification data'):
|
||||
super().__init__(fid, name=name, desc=desc, rec_len={12,12})
|
||||
super().__init__(fid, name=name, desc=desc, rec_len={12, 12})
|
||||
|
||||
def _decode_record_bin(self, raw_bin_data):
|
||||
u = unpack('!BB8s', raw_bin_data)
|
||||
return {'sec_domain': u[0], 'key_set_version': u[1], 'key_div_data': u[2].hex()}
|
||||
|
||||
|
||||
class EF_SIM_AUTH_KEY(TransparentEF):
|
||||
def __init__(self, fid='6f20', name='EF.SIM_AUTH_KEY'):
|
||||
super().__init__(fid, name=name, desc='USIM authentication key')
|
||||
|
@ -129,10 +143,15 @@ class EF_SIM_AUTH_KEY(TransparentEF):
|
|||
'algorithm'/Enum(Nibble, milenage=4, comp128v1=1, comp128v2=2, comp128v3=3))
|
||||
self._construct = Struct('cfg'/CfgByte,
|
||||
'key'/Bytes(16),
|
||||
'op'/If(this.cfg.algorithm=='milenage' and not this.cfg.use_opc_instead_of_op, Bytes(16)),
|
||||
'opc'/If(this.cfg.algorithm=='milenage' and this.cfg.use_opc_instead_of_op, Bytes(16))
|
||||
'op' /
|
||||
If(this.cfg.algorithm == 'milenage' and not this.cfg.use_opc_instead_of_op, Bytes(
|
||||
16)),
|
||||
'opc' /
|
||||
If(this.cfg.algorithm == 'milenage' and this.cfg.use_opc_instead_of_op, Bytes(
|
||||
16))
|
||||
)
|
||||
|
||||
|
||||
class DF_SYSTEM(CardDF):
|
||||
def __init__(self):
|
||||
super().__init__(fid='a515', name='DF.SYSTEM', desc='CardOS specifics')
|
||||
|
@ -156,6 +175,7 @@ class DF_SYSTEM(CardDF):
|
|||
def decode_select_response(self, resp_hex):
|
||||
return pySim.ts_102_221.CardProfileUICC.decode_select_response(resp_hex)
|
||||
|
||||
|
||||
class EF_USIM_SQN(TransparentEF):
|
||||
def __init__(self, fid='af30', name='EF.USIM_SQN'):
|
||||
super().__init__(fid, name=name, desc='SQN parameters for AKA')
|
||||
|
@ -165,9 +185,11 @@ class EF_USIM_SQN(TransparentEF):
|
|||
Flag2 = BitStruct('rfu'/BitsRFU(5), 'dont_clear_amf_for_macs'/Bit,
|
||||
'aus_concealed'/Bit, 'autn_concealed'/Bit)
|
||||
self._construct = Struct('flag1'/Flag1, 'flag2'/Flag2,
|
||||
'delta_max'/BytesInteger(6), 'age_limit'/BytesInteger(6),
|
||||
'delta_max' /
|
||||
BytesInteger(6), 'age_limit'/BytesInteger(6),
|
||||
'freshness'/GreedyRange(BytesInteger(6)))
|
||||
|
||||
|
||||
class EF_USIM_AUTH_KEY(TransparentEF):
|
||||
def __init__(self, fid='af20', name='EF.USIM_AUTH_KEY'):
|
||||
super().__init__(fid, name=name, desc='USIM authentication key')
|
||||
|
@ -177,9 +199,15 @@ class EF_USIM_AUTH_KEY(TransparentEF):
|
|||
'algorithm'/Enum(Nibble, milenage=4, sha1_aka=5, xor=15))
|
||||
self._construct = Struct('cfg'/CfgByte,
|
||||
'key'/Bytes(16),
|
||||
'op'/If(this.cfg.algorithm=='milenage' and not this.cfg.use_opc_instead_of_op, Bytes(16)),
|
||||
'opc'/If(this.cfg.algorithm=='milenage' and this.cfg.use_opc_instead_of_op, Bytes(16))
|
||||
'op' /
|
||||
If(this.cfg.algorithm == 'milenage' and not this.cfg.use_opc_instead_of_op, Bytes(
|
||||
16)),
|
||||
'opc' /
|
||||
If(this.cfg.algorithm == 'milenage' and this.cfg.use_opc_instead_of_op, Bytes(
|
||||
16))
|
||||
)
|
||||
|
||||
|
||||
class EF_USIM_AUTH_KEY_2G(TransparentEF):
|
||||
def __init__(self, fid='af22', name='EF.USIM_AUTH_KEY_2G'):
|
||||
super().__init__(fid, name=name, desc='USIM authentication key in 2G context')
|
||||
|
@ -189,33 +217,42 @@ class EF_USIM_AUTH_KEY_2G(TransparentEF):
|
|||
'algorithm'/Enum(Nibble, milenage=4, comp128v1=1, comp128v2=2, comp128v3=3))
|
||||
self._construct = Struct('cfg'/CfgByte,
|
||||
'key'/Bytes(16),
|
||||
'op'/If(this.cfg.algorithm=='milenage' and not this.cfg.use_opc_instead_of_op, Bytes(16)),
|
||||
'opc'/If(this.cfg.algorithm=='milenage' and this.cfg.use_opc_instead_of_op, Bytes(16))
|
||||
'op' /
|
||||
If(this.cfg.algorithm == 'milenage' and not this.cfg.use_opc_instead_of_op, Bytes(
|
||||
16)),
|
||||
'opc' /
|
||||
If(this.cfg.algorithm == 'milenage' and this.cfg.use_opc_instead_of_op, Bytes(
|
||||
16))
|
||||
)
|
||||
|
||||
|
||||
class EF_GBA_SK(TransparentEF):
|
||||
def __init__(self, fid='af31', name='EF.GBA_SK'):
|
||||
super().__init__(fid, name=name, desc='Secret key for GBA key derivation')
|
||||
self._construct = GreedyBytes
|
||||
|
||||
|
||||
class EF_GBA_REC_LIST(TransparentEF):
|
||||
def __init__(self, fid='af32', name='EF.GBA_REC_LIST'):
|
||||
super().__init__(fid, name=name, desc='Secret key for GBA key derivation')
|
||||
# integers representing record numbers in EF-GBANL
|
||||
self._construct = GreedyRange(Int8ub)
|
||||
|
||||
|
||||
class EF_GBA_INT_KEY(LinFixedEF):
|
||||
def __init__(self, fid='af33', name='EF.GBA_INT_KEY'):
|
||||
super().__init__(fid, name=name, desc='Secret key for GBA key derivation', rec_len={32,32})
|
||||
super().__init__(fid, name=name,
|
||||
desc='Secret key for GBA key derivation', rec_len={32, 32})
|
||||
self._construct = GreedyBytes
|
||||
|
||||
|
||||
|
||||
class SysmocomSJA2(CardModel):
|
||||
_atrs = [ "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 30 34 05 4B A9",
|
||||
_atrs = ["3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 30 34 05 4B A9",
|
||||
"3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 31 33 02 51 B2",
|
||||
"3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 52 75 31 04 51 D5" ]
|
||||
"3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 52 75 31 04 51 D5"]
|
||||
|
||||
@classmethod
|
||||
def add_files(cls, rs:RuntimeState):
|
||||
def add_files(cls, rs: RuntimeState):
|
||||
"""Add sysmocom SJA2 specific files to given RuntimeState."""
|
||||
rs.mf.add_file(DF_SYSTEM())
|
||||
# optional USIM application
|
||||
|
|
56
pySim/tlv.py
56
pySim/tlv.py
|
@ -32,6 +32,7 @@ from pySim.exceptions import *
|
|||
import inspect
|
||||
import abc
|
||||
|
||||
|
||||
class TlvMeta(abc.ABCMeta):
|
||||
"""Metaclass which we use to set some class variables at the time of defining a subclass.
|
||||
This allows us to create subclasses for each TLV/IE type, where the class represents fixed
|
||||
|
@ -54,6 +55,7 @@ class TlvMeta(abc.ABCMeta):
|
|||
x.nested_collection_cls = cls
|
||||
return x
|
||||
|
||||
|
||||
class TlvCollectionMeta(abc.ABCMeta):
|
||||
"""Metaclass which we use to set some class variables at the time of defining a subclass.
|
||||
This allows us to create subclasses for each Collection type, where the class represents fixed
|
||||
|
@ -72,6 +74,7 @@ class Transcodable(abc.ABC):
|
|||
* via a 'construct' object stored in a derived class' _construct variable, or
|
||||
* via a 'construct' object stored in an instance _construct variable, or
|
||||
* via a derived class' _{to,from}_bytes() methods."""
|
||||
|
||||
def __init__(self):
|
||||
self.encoded = None
|
||||
self.decoded = None
|
||||
|
@ -95,7 +98,7 @@ class Transcodable(abc.ABC):
|
|||
def _to_bytes(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def from_bytes(self, do:bytes):
|
||||
def from_bytes(self, do: bytes):
|
||||
"""Convert from binary bytes to internal representation. Store the decoded result
|
||||
in the internal state and return it."""
|
||||
self.encoded = do
|
||||
|
@ -110,9 +113,10 @@ class Transcodable(abc.ABC):
|
|||
return self.decoded
|
||||
|
||||
# not an abstractmethod, as it is only required if no _construct exists
|
||||
def _from_bytes(self, do:bytes):
|
||||
def _from_bytes(self, do: bytes):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class IE(Transcodable, metaclass=TlvMeta):
|
||||
# we specify the metaclass so any downstream subclasses will automatically use it
|
||||
"""Base class for various Information Elements. We understand the notion of a hierarchy
|
||||
|
@ -146,7 +150,7 @@ class IE(Transcodable, metaclass=TlvMeta):
|
|||
v = self.decoded
|
||||
return {type(self).__name__: v}
|
||||
|
||||
def from_dict(self, decoded:dict):
|
||||
def from_dict(self, decoded: dict):
|
||||
"""Set the IE internal decoded representation to data from the argument.
|
||||
If this is a nested IE, the child IE instance list is re-created."""
|
||||
if self.nested_collection:
|
||||
|
@ -177,7 +181,7 @@ class IE(Transcodable, metaclass=TlvMeta):
|
|||
else:
|
||||
return super().to_bytes()
|
||||
|
||||
def from_bytes(self, do:bytes):
|
||||
def from_bytes(self, do: bytes):
|
||||
"""Parse _the value part_ from binary bytes to internal representation."""
|
||||
if self.nested_collection:
|
||||
self.children = self.nested_collection.from_bytes(do)
|
||||
|
@ -188,6 +192,7 @@ class IE(Transcodable, metaclass=TlvMeta):
|
|||
|
||||
class TLV_IE(IE):
|
||||
"""Abstract base class for various TLV type Information Elements."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
@ -197,12 +202,12 @@ class TLV_IE(IE):
|
|||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def _parse_tag_raw(cls, do:bytes) -> Tuple[int, bytes]:
|
||||
def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
|
||||
"""Obtain the raw TAG at the start of the bytes provided by the user."""
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def _parse_len(cls, do:bytes) -> Tuple[int, bytes]:
|
||||
def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
|
||||
"""Obtain the length encoded at the start of the bytes provided by the user."""
|
||||
|
||||
@abc.abstractmethod
|
||||
|
@ -210,7 +215,7 @@ class TLV_IE(IE):
|
|||
"""Encode the tag part. Must be provided by derived (TLV format specific) class."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def _encode_len(self, val:bytes) -> bytes:
|
||||
def _encode_len(self, val: bytes) -> bytes:
|
||||
"""Encode the length part assuming a certain binary value. Must be provided by
|
||||
derived (TLV format specific) class."""
|
||||
|
||||
|
@ -222,7 +227,7 @@ class TLV_IE(IE):
|
|||
val = self.to_bytes()
|
||||
return self._encode_tag() + self._encode_len(val) + val
|
||||
|
||||
def from_tlv(self, do:bytes):
|
||||
def from_tlv(self, do: bytes):
|
||||
(rawtag, remainder) = self.__class__._parse_tag_raw(do)
|
||||
if rawtag:
|
||||
if rawtag != self.tag:
|
||||
|
@ -240,50 +245,52 @@ class TLV_IE(IE):
|
|||
|
||||
class BER_TLV_IE(TLV_IE):
|
||||
"""TLV_IE formatted as ASN.1 BER described in ITU-T X.690 8.1.2."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@classmethod
|
||||
def _decode_tag(cls, do:bytes) -> Tuple[dict, bytes]:
|
||||
def _decode_tag(cls, do: bytes) -> Tuple[dict, bytes]:
|
||||
return bertlv_parse_tag(do)
|
||||
|
||||
@classmethod
|
||||
def _parse_tag_raw(cls, do:bytes) -> Tuple[int, bytes]:
|
||||
def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
|
||||
return bertlv_parse_tag_raw(do)
|
||||
|
||||
@classmethod
|
||||
def _parse_len(cls, do:bytes) -> Tuple[int, bytes]:
|
||||
def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
|
||||
return bertlv_parse_len(do)
|
||||
|
||||
def _encode_tag(self) -> bytes:
|
||||
return bertlv_encode_tag(self._compute_tag())
|
||||
|
||||
def _encode_len(self, val:bytes) -> bytes:
|
||||
def _encode_len(self, val: bytes) -> bytes:
|
||||
return bertlv_encode_len(len(val))
|
||||
|
||||
|
||||
class COMPR_TLV_IE(TLV_IE):
|
||||
"""TLV_IE formated as COMPREHENSION-TLV as described in ETSI TS 101 220."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.comprehension = False
|
||||
|
||||
@classmethod
|
||||
def _decode_tag(cls, do:bytes) -> Tuple[dict, bytes]:
|
||||
def _decode_tag(cls, do: bytes) -> Tuple[dict, bytes]:
|
||||
return comprehensiontlv_parse_tag(do)
|
||||
|
||||
@classmethod
|
||||
def _parse_tag_raw(cls, do:bytes) -> Tuple[int, bytes]:
|
||||
def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
|
||||
return comprehensiontlv_parse_tag_raw(do)
|
||||
|
||||
@classmethod
|
||||
def _parse_len(cls, do:bytes) -> Tuple[int, bytes]:
|
||||
def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
|
||||
return bertlv_parse_len(do)
|
||||
|
||||
def _encode_tag(self) -> bytes:
|
||||
return comprehensiontlv_encode_tag(self._compute_tag())
|
||||
|
||||
def _encode_len(self, val:bytes) -> bytes:
|
||||
def _encode_len(self, val: bytes) -> bytes:
|
||||
return bertlv_encode_len(len(val))
|
||||
|
||||
|
||||
|
@ -294,14 +301,15 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta):
|
|||
of each DO."""
|
||||
# this is overridden by the TlvCollectionMeta metaclass, if it is used to create subclasses
|
||||
possible_nested = []
|
||||
|
||||
def __init__(self, desc=None, **kwargs):
|
||||
self.desc = desc
|
||||
#print("possible_nested: ", self.possible_nested)
|
||||
self.members = kwargs.get('nested', self.possible_nested)
|
||||
self.members_by_tag = {}
|
||||
self.members_by_name = {}
|
||||
self.members_by_tag = { m.tag:m for m in self.members }
|
||||
self.members_by_name = { m.__name__:m for m in self.members }
|
||||
self.members_by_tag = {m.tag: m for m in self.members}
|
||||
self.members_by_name = {m.__name__: m for m in self.members}
|
||||
# if we are a constructed IE, [ordered] list of actual child-IE instances
|
||||
self.children = kwargs.get('children', [])
|
||||
self.encoded = None
|
||||
|
@ -322,11 +330,11 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta):
|
|||
return TLV_IE_Collection(self.desc, nested=members)
|
||||
elif inspect.isclass(other) and issubclass(other, TLV_IE):
|
||||
# adding a member to a collection
|
||||
return TLV_IE_Collection(self.desc, nested = self.members + [other])
|
||||
return TLV_IE_Collection(self.desc, nested=self.members + [other])
|
||||
else:
|
||||
raise TypeError
|
||||
|
||||
def from_bytes(self, binary:bytes) -> List[TLV_IE]:
|
||||
def from_bytes(self, binary: bytes) -> List[TLV_IE]:
|
||||
"""Create a list of TLV_IEs from the collection based on binary input data.
|
||||
Args:
|
||||
binary : binary bytes of encoded data
|
||||
|
@ -353,9 +361,9 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta):
|
|||
else:
|
||||
# unknown tag; create the related class on-the-fly using the same base class
|
||||
name = 'unknown_%s_%X' % (first.__base__.__name__, tag)
|
||||
cls = type(name, (first.__base__,), {'tag':tag, 'possible_nested':[],
|
||||
'nested_collection_cls':None})
|
||||
cls._from_bytes = lambda s, a : {'raw': a.hex()}
|
||||
cls = type(name, (first.__base__,), {'tag': tag, 'possible_nested': [],
|
||||
'nested_collection_cls': None})
|
||||
cls._from_bytes = lambda s, a: {'raw': a.hex()}
|
||||
cls._to_bytes = lambda s: bytes.fromhex(s.decoded['raw'])
|
||||
# create an instance and parse accordingly
|
||||
inst = cls()
|
||||
|
@ -364,7 +372,7 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta):
|
|||
self.children = res
|
||||
return res
|
||||
|
||||
def from_dict(self, decoded:List[dict]) -> List[TLV_IE]:
|
||||
def from_dict(self, decoded: List[dict]) -> List[TLV_IE]:
|
||||
"""Create a list of TLV_IE instances from the collection based on an array
|
||||
of dicts, where they key indicates the name of the TLV_IE subclass to use."""
|
||||
# list of instances of TLV_IE collection member classes appearing in the data
|
||||
|
|
|
@ -29,6 +29,7 @@ from pySim.utils import sw_match, b2h, h2b, i2h
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
|
||||
class ApduTracer:
|
||||
def trace_command(self, cmd):
|
||||
pass
|
||||
|
@ -45,7 +46,7 @@ class LinkBase(abc.ABC):
|
|||
self.apdu_tracer = apdu_tracer
|
||||
|
||||
@abc.abstractmethod
|
||||
def _send_apdu_raw(self, pdu:str) -> Tuple[str, str]:
|
||||
def _send_apdu_raw(self, pdu: str) -> Tuple[str, str]:
|
||||
"""Implementation specific method for sending the PDU."""
|
||||
|
||||
def set_sw_interpreter(self, interp):
|
||||
|
@ -53,7 +54,7 @@ class LinkBase(abc.ABC):
|
|||
self.sw_interpreter = interp
|
||||
|
||||
@abc.abstractmethod
|
||||
def wait_for_card(self, timeout:int=None, newcardonly:bool=False):
|
||||
def wait_for_card(self, timeout: int = None, newcardonly: bool = False):
|
||||
"""Wait for a card and connect to it
|
||||
|
||||
Args:
|
||||
|
@ -76,7 +77,7 @@ class LinkBase(abc.ABC):
|
|||
"""Resets the card (power down/up)
|
||||
"""
|
||||
|
||||
def send_apdu_raw(self, pdu:str):
|
||||
def send_apdu_raw(self, pdu: str):
|
||||
"""Sends an APDU with minimal processing
|
||||
|
||||
Args:
|
||||
|
@ -105,7 +106,7 @@ class LinkBase(abc.ABC):
|
|||
"""
|
||||
data, sw = self.send_apdu_raw(pdu)
|
||||
|
||||
# When whe have sent the first APDU, the SW may indicate that there are response bytes
|
||||
# When we have sent the first APDU, the SW may indicate that there are response bytes
|
||||
# available. There are two SWs commonly used for this 9fxx (sim) and 61xx (usim), where
|
||||
# xx is the number of response bytes available.
|
||||
# See also:
|
||||
|
@ -118,7 +119,7 @@ class LinkBase(abc.ABC):
|
|||
if sw[0:2] == '6c':
|
||||
# SW1=6C: ETSI TS 102 221 Table 7.1: Procedure byte coding
|
||||
pdu_gr = pdu[0:8] + sw[2:4]
|
||||
data,sw = self.send_apdu_raw(pdu_gr)
|
||||
data, sw = self.send_apdu_raw(pdu_gr)
|
||||
|
||||
return data, sw
|
||||
|
||||
|
@ -185,11 +186,13 @@ class LinkBase(abc.ABC):
|
|||
Returns:
|
||||
Tuple of (decoded_data, sw)
|
||||
"""
|
||||
(rsp, sw) = self.send_apdu_constr(cla, ins, p1, p2, cmd_constr, cmd_data, resp_constr)
|
||||
(rsp, sw) = self.send_apdu_constr(cla, ins,
|
||||
p1, p2, cmd_constr, cmd_data, resp_constr)
|
||||
if not sw_match(sw, sw_exp):
|
||||
raise SwMatchError(sw, sw_exp.lower(), self.sw_interpreter)
|
||||
return (rsp, sw)
|
||||
|
||||
|
||||
def argparse_add_reader_args(arg_parser):
|
||||
"""Add all reader related arguments to the given argparse.Argumentparser instance."""
|
||||
serial_group = arg_parser.add_argument_group('Serial Reader')
|
||||
|
@ -214,6 +217,7 @@ def argparse_add_reader_args(arg_parser):
|
|||
|
||||
return arg_parser
|
||||
|
||||
|
||||
def init_reader(opts, **kwargs) -> Optional[LinkBase]:
|
||||
"""
|
||||
Init card reader driver
|
||||
|
@ -231,15 +235,18 @@ def init_reader(opts, **kwargs) -> Optional[LinkBase]:
|
|||
elif opts.modem_dev is not None:
|
||||
print("Using modem for Generic SIM Access (3GPP TS 27.007)")
|
||||
from pySim.transport.modem_atcmd import ModemATCommandLink
|
||||
sl = ModemATCommandLink(device=opts.modem_dev, baudrate=opts.modem_baud, **kwargs)
|
||||
sl = ModemATCommandLink(
|
||||
device=opts.modem_dev, baudrate=opts.modem_baud, **kwargs)
|
||||
else: # Serial reader is default
|
||||
print("Using serial reader interface")
|
||||
from pySim.transport.serial import SerialSimLink
|
||||
sl = SerialSimLink(device=opts.device, baudrate=opts.baudrate, **kwargs)
|
||||
sl = SerialSimLink(device=opts.device,
|
||||
baudrate=opts.baudrate, **kwargs)
|
||||
return sl
|
||||
except Exception as e:
|
||||
if str(e):
|
||||
print("Card reader initialization failed with exception:\n" + str(e))
|
||||
else:
|
||||
print("Card reader initialization failed with an exception of type:\n" + str(type(e)))
|
||||
print(
|
||||
"Card reader initialization failed with an exception of type:\n" + str(type(e)))
|
||||
return None
|
||||
|
|
|
@ -25,6 +25,7 @@ from pySim.transport import LinkBase
|
|||
from pySim.exceptions import *
|
||||
from pySim.utils import h2b, b2h
|
||||
|
||||
|
||||
class L1CTLMessage(object):
|
||||
|
||||
# Every (encoded) L1CTL message has the following structure:
|
||||
|
@ -35,13 +36,14 @@ class L1CTLMessage(object):
|
|||
# - padding (2 spare bytes)
|
||||
# - ... payload ...
|
||||
|
||||
def __init__(self, msg_type, flags = 0x00):
|
||||
def __init__(self, msg_type, flags=0x00):
|
||||
# Init L1CTL message header
|
||||
self.data = struct.pack("BBxx", msg_type, flags)
|
||||
|
||||
def gen_msg(self):
|
||||
return struct.pack("!H", len(self.data)) + self.data
|
||||
|
||||
|
||||
class L1CTLMessageReset(L1CTLMessage):
|
||||
|
||||
# L1CTL message types
|
||||
|
@ -54,10 +56,11 @@ class L1CTLMessageReset(L1CTLMessage):
|
|||
L1CTL_RES_T_FULL = 0x01
|
||||
L1CTL_RES_T_SCHED = 0x02
|
||||
|
||||
def __init__(self, type = L1CTL_RES_T_FULL):
|
||||
def __init__(self, type=L1CTL_RES_T_FULL):
|
||||
super(L1CTLMessageReset, self).__init__(self.L1CTL_RESET_REQ)
|
||||
self.data += struct.pack("Bxxx", type)
|
||||
|
||||
|
||||
class L1CTLMessageSIM(L1CTLMessage):
|
||||
|
||||
# SIM related message types
|
||||
|
@ -68,14 +71,16 @@ class L1CTLMessageSIM(L1CTLMessage):
|
|||
super(L1CTLMessageSIM, self).__init__(self.L1CTL_SIM_REQ)
|
||||
self.data += pdu
|
||||
|
||||
|
||||
class CalypsoSimLink(LinkBase):
|
||||
"""Transport Link for Calypso based phones."""
|
||||
|
||||
def __init__(self, sock_path:str = "/tmp/osmocom_l2", **kwargs):
|
||||
def __init__(self, sock_path: str = "/tmp/osmocom_l2", **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
# Make sure that a given socket path exists
|
||||
if not os.path.exists(sock_path):
|
||||
raise ReaderError("There is no such ('%s') UNIX socket" % sock_path)
|
||||
raise ReaderError(
|
||||
"There is no such ('%s') UNIX socket" % sock_path)
|
||||
|
||||
print("Connecting to osmocon at '%s'..." % sock_path)
|
||||
|
||||
|
@ -86,7 +91,7 @@ class CalypsoSimLink(LinkBase):
|
|||
def __del__(self):
|
||||
self.sock.close()
|
||||
|
||||
def wait_for_rsp(self, exp_len = 128):
|
||||
def wait_for_rsp(self, exp_len=128):
|
||||
# Wait for incoming data (timeout is 3 seconds)
|
||||
s, _, _ = select.select([self.sock], [], [], 3.0)
|
||||
if not s:
|
||||
|
@ -113,7 +118,7 @@ class CalypsoSimLink(LinkBase):
|
|||
def disconnect(self):
|
||||
pass # Nothing to do really ...
|
||||
|
||||
def wait_for_card(self, timeout = None, newcardonly = False):
|
||||
def wait_for_card(self, timeout=None, newcardonly=False):
|
||||
pass # Nothing to do really ...
|
||||
|
||||
def _send_apdu_raw(self, pdu):
|
||||
|
|
|
@ -27,9 +27,11 @@ from pySim.exceptions import *
|
|||
# HACK: if somebody needs to debug this thing
|
||||
# log.root.setLevel(log.DEBUG)
|
||||
|
||||
|
||||
class ModemATCommandLink(LinkBase):
|
||||
"""Transport Link for 3GPP TS 27.007 compliant modems."""
|
||||
def __init__(self, device:str='/dev/ttyUSB0', baudrate:int=115200, **kwargs):
|
||||
|
||||
def __init__(self, device: str = '/dev/ttyUSB0', baudrate: int = 115200, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._sl = serial.Serial(device, baudrate, timeout=5)
|
||||
self._echo = False # this will be auto-detected by _check_echo()
|
||||
|
@ -82,7 +84,8 @@ class ModemATCommandLink(LinkBase):
|
|||
break
|
||||
time.sleep(patience)
|
||||
its += 1
|
||||
log.debug('Command took %0.6fs (%d cycles a %fs)', time.time() - t_start, its, patience)
|
||||
log.debug('Command took %0.6fs (%d cycles a %fs)',
|
||||
time.time() - t_start, its, patience)
|
||||
|
||||
if self._echo:
|
||||
# Skip echo chars
|
||||
|
@ -113,7 +116,8 @@ class ModemATCommandLink(LinkBase):
|
|||
elif result[-1] == b'AT\r\r\nOK':
|
||||
self._echo = True
|
||||
return
|
||||
raise ReaderError('Interface \'%s\' does not respond to \'AT\' command' % self._device)
|
||||
raise ReaderError(
|
||||
'Interface \'%s\' does not respond to \'AT\' command' % self._device)
|
||||
|
||||
def reset_card(self):
|
||||
# Reset the modem, just to be sure
|
||||
|
|
|
@ -30,7 +30,7 @@ from pySim.utils import h2i, i2h
|
|||
class PcscSimLink(LinkBase):
|
||||
""" pySim: PCSC reader transport link."""
|
||||
|
||||
def __init__(self, reader_number:int=0, **kwargs):
|
||||
def __init__(self, reader_number: int = 0, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
r = readers()
|
||||
if reader_number >= len(r):
|
||||
|
@ -46,8 +46,9 @@ class PcscSimLink(LinkBase):
|
|||
pass
|
||||
return
|
||||
|
||||
def wait_for_card(self, timeout:int=None, newcardonly:bool=False):
|
||||
cr = CardRequest(readers=[self._reader], timeout=timeout, newcardonly=newcardonly)
|
||||
def wait_for_card(self, timeout: int = None, newcardonly: bool = False):
|
||||
cr = CardRequest(readers=[self._reader],
|
||||
timeout=timeout, newcardonly=newcardonly)
|
||||
try:
|
||||
cr.waitforcard()
|
||||
except CardRequestTimeoutException:
|
||||
|
|
|
@ -28,20 +28,20 @@ from pySim.utils import h2b, b2h
|
|||
class SerialSimLink(LinkBase):
|
||||
""" pySim: Transport Link for serial (RS232) based readers included with simcard"""
|
||||
|
||||
def __init__(self, device:str='/dev/ttyUSB0', baudrate:int=9600, rst:str='-rts',
|
||||
debug:bool=False, **kwargs):
|
||||
def __init__(self, device: str = '/dev/ttyUSB0', baudrate: int = 9600, rst: str = '-rts',
|
||||
debug: bool = False, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
if not os.path.exists(device):
|
||||
raise ValueError("device file %s does not exist -- abort" % device)
|
||||
self._sl = serial.Serial(
|
||||
port = device,
|
||||
parity = serial.PARITY_EVEN,
|
||||
bytesize = serial.EIGHTBITS,
|
||||
stopbits = serial.STOPBITS_TWO,
|
||||
timeout = 1,
|
||||
xonxoff = 0,
|
||||
rtscts = 0,
|
||||
baudrate = baudrate,
|
||||
port=device,
|
||||
parity=serial.PARITY_EVEN,
|
||||
bytesize=serial.EIGHTBITS,
|
||||
stopbits=serial.STOPBITS_TWO,
|
||||
timeout=1,
|
||||
xonxoff=0,
|
||||
rtscts=0,
|
||||
baudrate=baudrate,
|
||||
)
|
||||
self._rst_pin = rst
|
||||
self._debug = debug
|
||||
|
@ -111,7 +111,7 @@ class SerialSimLink(LinkBase):
|
|||
'rts': self._sl.setRTS,
|
||||
'dtr': self._sl.setDTR,
|
||||
}
|
||||
rst_val_map = { '+':0, '-':1 }
|
||||
rst_val_map = {'+': 0, '-': 1}
|
||||
|
||||
try:
|
||||
rst_meth = rst_meth_map[self._rst_pin[1:]]
|
||||
|
@ -168,7 +168,8 @@ class SerialSimLink(LinkBase):
|
|||
self._sl.write(b)
|
||||
r = self._sl.read()
|
||||
if r != b: # TX and RX are tied, so we must clear the echo
|
||||
raise ProtocolError("Bad echo value. Expected %02x, got %s)" % (ord(b), '%02x'%ord(r) if r else '(nil)'))
|
||||
raise ProtocolError("Bad echo value. Expected %02x, got %s)" % (
|
||||
ord(b), '%02x' % ord(r) if r else '(nil)'))
|
||||
|
||||
def _tx_string(self, s):
|
||||
"""This is only safe if it's guaranteed the card won't send any data
|
||||
|
@ -176,7 +177,8 @@ class SerialSimLink(LinkBase):
|
|||
self._sl.write(s)
|
||||
r = self._sl.read(len(s))
|
||||
if r != s: # TX and RX are tied, so we must clear the echo
|
||||
raise ProtocolError("Bad echo value (Expected: %s, got %s)" % (b2h(s), b2h(r)))
|
||||
raise ProtocolError(
|
||||
"Bad echo value (Expected: %s, got %s)" % (b2h(s), b2h(r)))
|
||||
|
||||
def _rx_byte(self):
|
||||
return self._sl.read()
|
||||
|
|
|
@ -76,7 +76,7 @@ ts_102_22x_cmdset = CardCommandSet('TS 102 22x', [
|
|||
CardCommand('TERMINATE EF', 0xE8, ['0X', '4X']),
|
||||
CardCommand('TERMINATE CARD USAGE', 0xFE, ['0X', '4X']),
|
||||
CardCommand('RESIZE FILE', 0xD4, ['8X', 'CX']),
|
||||
])
|
||||
])
|
||||
|
||||
|
||||
FCP_TLV_MAP = {
|
||||
|
@ -92,7 +92,7 @@ FCP_TLV_MAP = {
|
|||
'80': 'file_size',
|
||||
'81': 'total_file_size',
|
||||
'88': 'short_file_id',
|
||||
}
|
||||
}
|
||||
|
||||
# ETSI TS 102 221 11.1.1.4.6
|
||||
FCP_Proprietary_TLV_MAP = {
|
||||
|
@ -107,9 +107,11 @@ FCP_Proprietary_TLV_MAP = {
|
|||
'88': 'specific_uicc_env_cond',
|
||||
'89': 'p2p_cat_secured_apdu',
|
||||
# Additional private TLV objects (bits b7 and b8 of the first byte of the tag set to '1')
|
||||
}
|
||||
}
|
||||
|
||||
# ETSI TS 102 221 11.1.1.4.3
|
||||
|
||||
|
||||
def interpret_file_descriptor(in_hex):
|
||||
in_bin = h2b(in_hex)
|
||||
out = {}
|
||||
|
@ -140,6 +142,8 @@ def interpret_file_descriptor(in_hex):
|
|||
return out
|
||||
|
||||
# ETSI TS 102 221 11.1.1.4.9
|
||||
|
||||
|
||||
def interpret_life_cycle_sts_int(in_hex):
|
||||
lcsi = int(in_hex, 16)
|
||||
if lcsi == 0x00:
|
||||
|
@ -157,35 +161,40 @@ def interpret_life_cycle_sts_int(in_hex):
|
|||
else:
|
||||
return in_hex
|
||||
|
||||
|
||||
# ETSI TS 102 221 11.1.1.4.10
|
||||
FCP_Pin_Status_TLV_MAP = {
|
||||
'90': 'ps_do',
|
||||
'95': 'usage_qualifier',
|
||||
'83': 'key_reference',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def interpret_ps_templ_do(in_hex):
|
||||
# cannot use the 'TLV' parser due to repeating tags
|
||||
#psdo_tlv = TLV(FCP_Pin_Status_TLV_MAP)
|
||||
#return psdo_tlv.parse(in_hex)
|
||||
# return psdo_tlv.parse(in_hex)
|
||||
return in_hex
|
||||
|
||||
|
||||
# 'interpreter' functions for each tag
|
||||
FCP_interpreter_map = {
|
||||
'80': lambda x: int(x, 16),
|
||||
'82': interpret_file_descriptor,
|
||||
'8A': interpret_life_cycle_sts_int,
|
||||
'C6': interpret_ps_templ_do,
|
||||
}
|
||||
}
|
||||
|
||||
FCP_prorietary_interpreter_map = {
|
||||
'83': lambda x: int(x, 16),
|
||||
}
|
||||
}
|
||||
|
||||
# pytlv unfortunately doesn't have a setting using which we can make it
|
||||
# accept unknown tags. It also doesn't raise a specific exception type but
|
||||
# just the generic ValueError, so we cannot ignore those either. Instead,
|
||||
# we insert a dict entry for every possible proprietary tag permitted
|
||||
|
||||
|
||||
def fixup_fcp_proprietary_tlv_map(tlv_map):
|
||||
if 'D0' in tlv_map:
|
||||
return
|
||||
|
@ -204,6 +213,7 @@ def tlv_key_replace(inmap, indata):
|
|||
return key
|
||||
return {newkey(inmap, d[0]): d[1] for d in indata.items()}
|
||||
|
||||
|
||||
def tlv_val_interpret(inmap, indata):
|
||||
def newval(inmap, key, val):
|
||||
if key in inmap:
|
||||
|
@ -214,11 +224,12 @@ def tlv_val_interpret(inmap, indata):
|
|||
|
||||
# ETSI TS 102 221 Section 9.2.7 + ISO7816-4 9.3.3/9.3.4
|
||||
|
||||
|
||||
class _AM_DO_DF(DataObject):
|
||||
def __init__(self):
|
||||
super().__init__('access_mode', 'Access Mode', tag=0x80)
|
||||
|
||||
def from_bytes(self, do:bytes):
|
||||
def from_bytes(self, do: bytes):
|
||||
res = []
|
||||
if len(do) != 1:
|
||||
raise ValueError("We only support single-byte AMF inside AM-DO")
|
||||
|
@ -262,10 +273,11 @@ class _AM_DO_DF(DataObject):
|
|||
|
||||
class _AM_DO_EF(DataObject):
|
||||
"""ISO7816-4 9.3.2 Table 18 + 9.3.3.1 Table 31"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__('access_mode', 'Access Mode', tag=0x80)
|
||||
|
||||
def from_bytes(self, do:bytes):
|
||||
def from_bytes(self, do: bytes):
|
||||
res = []
|
||||
if len(do) != 1:
|
||||
raise ValueError("We only support single-byte AMF inside AM-DO")
|
||||
|
@ -306,12 +318,14 @@ class _AM_DO_EF(DataObject):
|
|||
val |= 0x01
|
||||
return val.to_bytes(1, 'big')
|
||||
|
||||
|
||||
class _AM_DO_CHDR(DataObject):
|
||||
"""Command Header Access Mode DO according to ISO 7816-4 Table 32."""
|
||||
|
||||
def __init__(self, tag):
|
||||
super().__init__('command_header', 'Command Header Description', tag=tag)
|
||||
|
||||
def from_bytes(self, do:bytes):
|
||||
def from_bytes(self, do: bytes):
|
||||
res = {}
|
||||
i = 0
|
||||
if self.tag & 0x08:
|
||||
|
@ -353,6 +367,7 @@ class _AM_DO_CHDR(DataObject):
|
|||
res.append(self.decoded['P2'])
|
||||
return res
|
||||
|
||||
|
||||
AM_DO_CHDR = DataObjectChoice('am_do_chdr', members=[
|
||||
_AM_DO_CHDR(0x81), _AM_DO_CHDR(0x82), _AM_DO_CHDR(0x83), _AM_DO_CHDR(0x84),
|
||||
_AM_DO_CHDR(0x85), _AM_DO_CHDR(0x86), _AM_DO_CHDR(0x87), _AM_DO_CHDR(0x88),
|
||||
|
@ -393,12 +408,15 @@ pin_names = bidict({
|
|||
0x8c: 'ADM8',
|
||||
0x8d: 'ADM9',
|
||||
0x8e: 'ADM10',
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
class CRT_DO(DataObject):
|
||||
"""Control Reference Template as per TS 102 221 9.5.1"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__('control_reference_template', 'Control Reference Template', tag=0xA4)
|
||||
super().__init__('control_reference_template',
|
||||
'Control Reference Template', tag=0xA4)
|
||||
|
||||
def from_bytes(self, do: bytes):
|
||||
"""Decode a Control Reference Template DO."""
|
||||
|
@ -407,7 +425,8 @@ class CRT_DO(DataObject):
|
|||
if do[0] != 0x83 or do[1] != 0x01:
|
||||
raise ValueError('Unsupported Key Ref Tag or Len in CRT DO %s', do)
|
||||
if do[3:] != b'\x95\x01\x08':
|
||||
raise ValueError('Unsupported Usage Qualifier Tag or Len in CRT DO %s', do)
|
||||
raise ValueError(
|
||||
'Unsupported Usage Qualifier Tag or Len in CRT DO %s', do)
|
||||
self.encoded = do[0:6]
|
||||
self.decoded = pin_names[do[2]]
|
||||
return do[6:]
|
||||
|
@ -417,11 +436,13 @@ class CRT_DO(DataObject):
|
|||
return b'\x83\x01' + pin.to_bytes(1, 'big') + b'\x95\x01\x08'
|
||||
|
||||
# ISO7816-4 9.3.3 Table 33
|
||||
|
||||
|
||||
class SecCondByte_DO(DataObject):
|
||||
def __init__(self, tag=0x9d):
|
||||
super().__init__('security_condition_byte', tag=tag)
|
||||
|
||||
def from_bytes(self, binary:bytes):
|
||||
def from_bytes(self, binary: bytes):
|
||||
if len(binary) != 1:
|
||||
raise ValueError
|
||||
inb = binary[0]
|
||||
|
@ -440,7 +461,7 @@ class SecCondByte_DO(DataObject):
|
|||
res.append('external_auth')
|
||||
if inb & 0x10:
|
||||
res.append('user_auth')
|
||||
rd = {'mode': cond }
|
||||
rd = {'mode': cond}
|
||||
if len(res):
|
||||
rd['conditions'] = res
|
||||
self.decoded = rd
|
||||
|
@ -470,25 +491,31 @@ class SecCondByte_DO(DataObject):
|
|||
raise ValueError('Unknown condition %s' % c)
|
||||
return res.to_bytes(1, 'big')
|
||||
|
||||
|
||||
Always_DO = TL0_DataObject('always', 'Always', 0x90)
|
||||
Never_DO = TL0_DataObject('never', 'Never', 0x97)
|
||||
|
||||
|
||||
class Nested_DO(DataObject):
|
||||
"""A DO that nests another DO/Choice/Sequence"""
|
||||
|
||||
def __init__(self, name, tag, choice):
|
||||
super().__init__(name, tag=tag)
|
||||
self.children = choice
|
||||
def from_bytes(self, binary:bytes) -> list:
|
||||
|
||||
def from_bytes(self, binary: bytes) -> list:
|
||||
remainder = binary
|
||||
self.decoded = []
|
||||
while remainder:
|
||||
rc, remainder = self.children.decode(remainder)
|
||||
self.decoded.append(rc)
|
||||
return self.decoded
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
encoded = [self.children.encode(d) for d in self.decoded]
|
||||
return b''.join(encoded)
|
||||
|
||||
|
||||
OR_Template = DataObjectChoice('or_template', 'OR-Template',
|
||||
members=[Always_DO, Never_DO, SecCondByte_DO(), SecCondByte_DO(0x9e), CRT_DO()])
|
||||
OR_DO = Nested_DO('or', 0xa0, OR_Template)
|
||||
|
@ -503,6 +530,8 @@ SC_DO = DataObjectChoice('security_condition', 'Security Condition',
|
|||
OR_DO, AND_DO, NOT_DO])
|
||||
|
||||
# TS 102 221 Section 13.1
|
||||
|
||||
|
||||
class EF_DIR(LinFixedEF):
|
||||
class ApplicationLabel(BER_TLV_IE, tag=0x50):
|
||||
# TODO: UCS-2 coding option as per Annex A of TS 102 221
|
||||
|
@ -518,13 +547,15 @@ class EF_DIR(LinFixedEF):
|
|||
pass
|
||||
|
||||
def __init__(self, fid='2f00', sfid=0x1e, name='EF.DIR', desc='Application Directory'):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={5,54})
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={5, 54})
|
||||
self._tlv = EF_DIR.ApplicationTemplate
|
||||
|
||||
# TS 102 221 Section 13.2
|
||||
|
||||
|
||||
class EF_ICCID(TransparentEF):
|
||||
def __init__(self, fid='2fe2', sfid=0x02, name='EF.ICCID', desc='ICC Identification'):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size={10,10})
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size={10, 10})
|
||||
|
||||
def _decode_hex(self, raw_hex):
|
||||
return {'iccid': dec_iccid(raw_hex)}
|
||||
|
@ -533,14 +564,19 @@ class EF_ICCID(TransparentEF):
|
|||
return enc_iccid(abstract['iccid'])
|
||||
|
||||
# TS 102 221 Section 13.3
|
||||
|
||||
|
||||
class EF_PL(TransRecEF):
|
||||
def __init__(self, fid='2f05', sfid=0x05, name='EF.PL', desc='Preferred Languages'):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=2, size={2,None})
|
||||
super().__init__(fid, sfid=sfid, name=name,
|
||||
desc=desc, rec_len=2, size={2, None})
|
||||
|
||||
def _decode_record_bin(self, bin_data):
|
||||
if bin_data == b'\xff\xff':
|
||||
return None
|
||||
else:
|
||||
return bin_data.decode('ascii')
|
||||
|
||||
def _encode_record_bin(self, in_json):
|
||||
if in_json is None:
|
||||
return b'\xff\xff'
|
||||
|
@ -556,7 +592,7 @@ class EF_ARR(LinFixedEF):
|
|||
self.shell_commands += [self.AddlShellCommands()]
|
||||
|
||||
@staticmethod
|
||||
def flatten(inp:list):
|
||||
def flatten(inp: list):
|
||||
"""Flatten the somewhat deep/complex/nested data returned from decoder."""
|
||||
def sc_abbreviate(sc):
|
||||
if 'always' in sc:
|
||||
|
@ -584,7 +620,7 @@ class EF_ARR(LinFixedEF):
|
|||
cla = None
|
||||
cmd = ts_102_22x_cmdset.lookup(ins, cla)
|
||||
if cmd:
|
||||
name = cmd.name.lower().replace(' ','_')
|
||||
name = cmd.name.lower().replace(' ', '_')
|
||||
by_mode[name] = sc_abbr
|
||||
else:
|
||||
raise ValueError
|
||||
|
@ -594,7 +630,7 @@ class EF_ARR(LinFixedEF):
|
|||
|
||||
def _decode_record_bin(self, raw_bin_data):
|
||||
# we can only guess if we should decode for EF or DF here :(
|
||||
arr_seq = DataObjectSequence('arr', sequence = [AM_DO_EF, SC_DO])
|
||||
arr_seq = DataObjectSequence('arr', sequence=[AM_DO_EF, SC_DO])
|
||||
dec = arr_seq.decode_multi(raw_bin_data)
|
||||
# we cannot pass the result through flatten() here, as we don't have a related
|
||||
# 'un-flattening' decoder, and hence would be unable to encode :(
|
||||
|
@ -628,15 +664,18 @@ class EF_ARR(LinFixedEF):
|
|||
# TS 102 221 Section 13.6
|
||||
class EF_UMPC(TransparentEF):
|
||||
def __init__(self, fid='2f08', sfid=0x08, name='EF.UMPC', desc='UICC Maximum Power Consumption'):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size={5,5})
|
||||
addl_info = FlagsEnum(Byte, req_inc_idle_current=1, support_uicc_suspend=2)
|
||||
self._construct = Struct('max_current_mA'/Int8ub, 't_op_s'/Int8ub, 'addl_info'/addl_info)
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size={5, 5})
|
||||
addl_info = FlagsEnum(Byte, req_inc_idle_current=1,
|
||||
support_uicc_suspend=2)
|
||||
self._construct = Struct(
|
||||
'max_current_mA'/Int8ub, 't_op_s'/Int8ub, 'addl_info'/addl_info)
|
||||
|
||||
|
||||
class CardProfileUICC(CardProfile):
|
||||
|
||||
ORDER = 1
|
||||
|
||||
def __init__(self, name = 'UICC'):
|
||||
def __init__(self, name='UICC'):
|
||||
files = [
|
||||
EF_DIR(),
|
||||
EF_ICCID(),
|
||||
|
@ -714,10 +753,11 @@ class CardProfileUICC(CardProfile):
|
|||
},
|
||||
}
|
||||
|
||||
super().__init__(name, desc='ETSI TS 102 221', cla="00", sel_ctrl="0004", files_in_mf=files, sw=sw)
|
||||
super().__init__(name, desc='ETSI TS 102 221', cla="00",
|
||||
sel_ctrl="0004", files_in_mf=files, sw=sw)
|
||||
|
||||
@staticmethod
|
||||
def decode_select_response(resp_hex:str) -> object:
|
||||
def decode_select_response(resp_hex: str) -> object:
|
||||
"""ETSI TS 102 221 Section 11.1.1.3"""
|
||||
fixup_fcp_proprietary_tlv_map(FCP_Proprietary_TLV_MAP)
|
||||
resp_hex = resp_hex.upper()
|
||||
|
@ -738,9 +778,10 @@ class CardProfileUICC(CardProfile):
|
|||
return tlv_key_replace(FCP_TLV_MAP, r)
|
||||
|
||||
@staticmethod
|
||||
def match_with_card(scc:SimCardCommands) -> bool:
|
||||
def match_with_card(scc: SimCardCommands) -> bool:
|
||||
return match_uicc(scc)
|
||||
|
||||
|
||||
class CardProfileUICCSIM(CardProfileUICC):
|
||||
"""Same as above, but including 2G SIM support"""
|
||||
|
||||
|
@ -754,5 +795,5 @@ class CardProfileUICCSIM(CardProfileUICC):
|
|||
self.files_in_mf.append(DF_GSM())
|
||||
|
||||
@staticmethod
|
||||
def match_with_card(scc:SimCardCommands) -> bool:
|
||||
def match_with_card(scc: SimCardCommands) -> bool:
|
||||
return match_uicc(scc) and match_sim(scc)
|
||||
|
|
|
@ -27,6 +27,21 @@ Various constants from 3GPP TS 31.102 V16.6.0
|
|||
#
|
||||
|
||||
# Mapping between USIM Service Number and its description
|
||||
import pySim.ts_102_221
|
||||
from pySim.ts_51_011 import EF_ACMmax, EF_AAeM, EF_eMLPP, EF_CMI, EF_PNN
|
||||
from pySim.ts_51_011 import EF_MMSN, EF_MMSICP, EF_MMSUP, EF_MMSUCP, EF_VGCS, EF_VGCSS, EF_NIA
|
||||
from pySim.ts_51_011 import EF_SMSR, EF_DCK, EF_EXT, EF_CNL, EF_OPL, EF_MBI, EF_MWIS
|
||||
from pySim.ts_51_011 import EF_CBMID, EF_CBMIR, EF_ADN, EF_SMS, EF_MSISDN, EF_SMSP, EF_SMSS
|
||||
from pySim.ts_51_011 import EF_IMSI, EF_xPLMNwAcT, EF_SPN, EF_CBMI, EF_ACC, EF_PLMNsel
|
||||
from pySim.ts_102_221 import EF_ARR
|
||||
from pySim.tlv import *
|
||||
from pySim.filesystem import *
|
||||
from pySim.construct import *
|
||||
from construct import Optional as COptional
|
||||
from construct import *
|
||||
from typing import Tuple
|
||||
from struct import unpack, pack
|
||||
import enum
|
||||
EF_UST_map = {
|
||||
1: 'Local Phone Book',
|
||||
2: 'Fixed Dialling Numbers (FDN)',
|
||||
|
@ -287,24 +302,9 @@ EF_USIM_ADF_map = {
|
|||
# ADF.USIM
|
||||
######################################################################
|
||||
|
||||
import enum
|
||||
from struct import unpack, pack
|
||||
from typing import Tuple
|
||||
from construct import *
|
||||
from construct import Optional as COptional
|
||||
from pySim.construct import *
|
||||
from pySim.filesystem import *
|
||||
from pySim.tlv import *
|
||||
from pySim.ts_102_221 import EF_ARR
|
||||
from pySim.ts_51_011 import EF_IMSI, EF_xPLMNwAcT, EF_SPN, EF_CBMI, EF_ACC, EF_PLMNsel
|
||||
from pySim.ts_51_011 import EF_CBMID, EF_CBMIR, EF_ADN, EF_SMS, EF_MSISDN, EF_SMSP, EF_SMSS
|
||||
from pySim.ts_51_011 import EF_SMSR, EF_DCK, EF_EXT, EF_CNL, EF_OPL, EF_MBI, EF_MWIS
|
||||
from pySim.ts_51_011 import EF_MMSN, EF_MMSICP, EF_MMSUP, EF_MMSUCP, EF_VGCS, EF_VGCSS, EF_NIA
|
||||
from pySim.ts_51_011 import EF_ACMmax, EF_AAeM, EF_eMLPP, EF_CMI, EF_PNN
|
||||
|
||||
import pySim.ts_102_221
|
||||
|
||||
# 3GPP TS 31.102 Section 4.4.11.4 (EF_5GS3GPPNSC)
|
||||
|
||||
class EF_5GS3GPPNSC(LinFixedEF):
|
||||
class NgKSI(BER_TLV_IE, tag=0x80):
|
||||
_construct = Int8ub
|
||||
|
@ -338,6 +338,8 @@ class EF_5GS3GPPNSC(LinFixedEF):
|
|||
self._tlv = EF_5GS3GPPNSC.FiveGSNasSecurityContext
|
||||
|
||||
# 3GPP TS 31.102 Section 4.4.11.6
|
||||
|
||||
|
||||
class EF_5GAUTHKEYS(TransparentEF):
|
||||
class K_AUSF(BER_TLV_IE, tag=0x80):
|
||||
_construct = HexAdapter(GreedyBytes)
|
||||
|
@ -354,25 +356,33 @@ class EF_5GAUTHKEYS(TransparentEF):
|
|||
self._tlv = EF_5GAUTHKEYS.FiveGAuthKeys
|
||||
|
||||
# 3GPP TS 31.102 Section 4.4.11.8
|
||||
|
||||
|
||||
class ProtSchemeIdList(BER_TLV_IE, tag=0xa0):
|
||||
# FIXME: 3GPP TS 24.501 Protection Scheme Identifier
|
||||
# repeated sequence of (id, index) tuples
|
||||
_construct = GreedyRange(Struct('id'/Enum(Byte, null=0, A=1, B=2), 'index'/Int8ub))
|
||||
_construct = GreedyRange(
|
||||
Struct('id'/Enum(Byte, null=0, A=1, B=2), 'index'/Int8ub))
|
||||
|
||||
|
||||
class HomeNetPubKeyId(BER_TLV_IE, tag=0x80):
|
||||
# 3GPP TS 24.501 / 3GPP TS 23.003
|
||||
_construct = Int8ub
|
||||
|
||||
|
||||
class HomeNetPubKey(BER_TLV_IE, tag=0x81):
|
||||
# FIXME: RFC 5480
|
||||
_construct = HexAdapter(GreedyBytes)
|
||||
|
||||
|
||||
class HomeNetPubKeyList(BER_TLV_IE, tag=0xa1,
|
||||
nested=[HomeNetPubKeyId, HomeNetPubKey]):
|
||||
pass
|
||||
|
||||
# 3GPP TS 31.102 Section 4.4.11.6
|
||||
class SUCI_CalcInfo(TLV_IE_Collection, nested=[ProtSchemeIdList,HomeNetPubKeyList]):
|
||||
|
||||
|
||||
class SUCI_CalcInfo(TLV_IE_Collection, nested=[ProtSchemeIdList, HomeNetPubKeyList]):
|
||||
pass
|
||||
|
||||
|
||||
|
@ -414,7 +424,8 @@ class EF_SUCI_Calc_Info(TransparentEF):
|
|||
return out_bytes
|
||||
|
||||
def _encode_hex(self, in_json):
|
||||
out_bytes = self._encode_prot_scheme_id_list(in_json['prot_scheme_id_list'])
|
||||
out_bytes = self._encode_prot_scheme_id_list(
|
||||
in_json['prot_scheme_id_list'])
|
||||
out_bytes += self._encode_hnet_pubkey_list(in_json['hnet_pubkey_list'])
|
||||
return "".join(["%02X" % i for i in out_bytes])
|
||||
|
||||
|
@ -447,7 +458,8 @@ class EF_SUCI_Calc_Info(TransparentEF):
|
|||
print("missing Home Network Public Key Identifier tag")
|
||||
return {}
|
||||
pos += 1
|
||||
hnet_pubkey_id_len = in_bytes[pos] # TODO might be more than 1 byte?
|
||||
# TODO might be more than 1 byte?
|
||||
hnet_pubkey_id_len = in_bytes[pos]
|
||||
pos += 1
|
||||
hnet_pubkey_id = in_bytes[pos:pos+hnet_pubkey_id_len][0]
|
||||
pos += hnet_pubkey_id_len
|
||||
|
@ -482,7 +494,8 @@ class EF_SUCI_Calc_Info(TransparentEF):
|
|||
prot_scheme_id_list_len = in_bytes[pos] # TODO maybe more than 1 byte
|
||||
pos += 1
|
||||
# decode Protection Scheme Identifier List data object
|
||||
prot_scheme_id_list = self._decode_prot_scheme_id_list(in_bytes[pos:pos+prot_scheme_id_list_len])
|
||||
prot_scheme_id_list = self._decode_prot_scheme_id_list(
|
||||
in_bytes[pos:pos+prot_scheme_id_list_len])
|
||||
pos += prot_scheme_id_list_len
|
||||
|
||||
# remaining data holds Home Network Public Key Data Object
|
||||
|
@ -496,16 +509,19 @@ class EF_SUCI_Calc_Info(TransparentEF):
|
|||
def _encode_bin(self, in_json):
|
||||
return h2b(self._encode_hex(in_json))
|
||||
|
||||
|
||||
class EF_LI(TransRecEF):
|
||||
def __init__(self, fid='6f05', sfid=None, name='EF.LI', size={2,None}, rec_len=2,
|
||||
def __init__(self, fid='6f05', sfid=None, name='EF.LI', size={2, None}, rec_len=2,
|
||||
desc='Language Indication'):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len)
|
||||
|
||||
def _decode_record_bin(self, in_bin):
|
||||
if in_bin == b'\xff\xff':
|
||||
return None
|
||||
else:
|
||||
# officially this is 7-bit GSM alphabet with one padding bit in each byte
|
||||
return in_bin.decode('ascii')
|
||||
|
||||
def _encode_record_bin(self, in_json):
|
||||
if in_json == None:
|
||||
return b'\xff\xff'
|
||||
|
@ -513,37 +529,45 @@ class EF_LI(TransRecEF):
|
|||
# officially this is 7-bit GSM alphabet with one padding bit in each byte
|
||||
return in_json.encode('ascii')
|
||||
|
||||
|
||||
class EF_Keys(TransparentEF):
|
||||
def __init__(self, fid='6f08', sfid=0x08, name='EF.Keys', size={33,33},
|
||||
def __init__(self, fid='6f08', sfid=0x08, name='EF.Keys', size={33, 33},
|
||||
desc='Ciphering and Integrity Keys'):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
|
||||
self._construct = Struct('ksi'/Int8ub, 'ck'/HexAdapter(Bytes(16)), 'ik'/HexAdapter(Bytes(16)))
|
||||
self._construct = Struct(
|
||||
'ksi'/Int8ub, 'ck'/HexAdapter(Bytes(16)), 'ik'/HexAdapter(Bytes(16)))
|
||||
|
||||
# TS 31.102 Section 4.2.6
|
||||
|
||||
|
||||
class EF_HPPLMN(TransparentEF):
|
||||
def __init__(self, fid='6f31', sfid=0x12, name='EF.HPPLMN', size={1,1},
|
||||
def __init__(self, fid='6f31', sfid=0x12, name='EF.HPPLMN', size={1, 1},
|
||||
desc='Higher Priority PLMN search period'):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
|
||||
self._construct = Int8ub
|
||||
|
||||
# TS 31.102 Section 4.2.8
|
||||
|
||||
|
||||
class EF_UServiceTable(TransparentEF):
|
||||
def __init__(self, fid, sfid, name, desc, size, table):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, size=size)
|
||||
self.table = table
|
||||
# add those commands to the general commands of a TransparentEF
|
||||
self.shell_commands += [self.AddlShellCommands()]
|
||||
|
||||
@staticmethod
|
||||
def _bit_byte_offset_for_service(service:int) -> Tuple[int, int]:
|
||||
def _bit_byte_offset_for_service(service: int) -> Tuple[int, int]:
|
||||
i = service - 1
|
||||
byte_offset = i//8
|
||||
bit_offset = (i % 8)
|
||||
return (byte_offset, bit_offset)
|
||||
|
||||
def _decode_bin(self, in_bin):
|
||||
ret = {}
|
||||
for i in range (0, len(in_bin)):
|
||||
for i in range(0, len(in_bin)):
|
||||
byte = in_bin[i]
|
||||
for bitno in range(0,7):
|
||||
for bitno in range(0, 7):
|
||||
service_nr = i * 8 + bitno + 1
|
||||
ret[service_nr] = {
|
||||
'activated': True if byte & (1 << bitno) else False
|
||||
|
@ -551,25 +575,29 @@ class EF_UServiceTable(TransparentEF):
|
|||
if service_nr in self.table:
|
||||
ret[service_nr]['description'] = self.table[service_nr]
|
||||
return ret
|
||||
|
||||
def _encode_bin(self, in_json):
|
||||
# compute the required binary size
|
||||
bin_len = 0
|
||||
for srv in in_json.keys():
|
||||
service_nr = int(srv)
|
||||
(byte_offset, bit_offset) = EF_UServiceTable._bit_byte_offset_for_service(service_nr)
|
||||
(byte_offset, bit_offset) = EF_UServiceTable._bit_byte_offset_for_service(
|
||||
service_nr)
|
||||
if byte_offset >= bin_len:
|
||||
bin_len = byte_offset+1
|
||||
# encode the actual data
|
||||
out = bytearray(b'\x00' * bin_len)
|
||||
for srv in in_json.keys():
|
||||
service_nr = int(srv)
|
||||
(byte_offset, bit_offset) = EF_UServiceTable._bit_byte_offset_for_service(service_nr)
|
||||
(byte_offset, bit_offset) = EF_UServiceTable._bit_byte_offset_for_service(
|
||||
service_nr)
|
||||
if in_json[srv]['activated'] == True:
|
||||
bit = 1
|
||||
else:
|
||||
bit = 0
|
||||
out[byte_offset] |= (bit) << bit_offset
|
||||
return out
|
||||
|
||||
@with_default_category('File-Specific Commands')
|
||||
class AddlShellCommands(CommandSet):
|
||||
def __init__(self):
|
||||
|
@ -584,14 +612,18 @@ class EF_UServiceTable(TransparentEF):
|
|||
self._cmd.card.update_ust(int(arg), 0)
|
||||
|
||||
# TS 31.103 Section 4.2.7 - *not* the same as DF.GSM/EF.ECC!
|
||||
|
||||
|
||||
class EF_ECC(LinFixedEF):
|
||||
cc_construct = Rpad(BcdAdapter(Rpad(Bytes(3))), pattern='f')
|
||||
category_construct = FlagsEnum(Byte, police=1, ambulance=2, fire_brigade=3, marine_guard=4,
|
||||
mountain_rescue=5, manual_ecall=6, automatic_ecall=7)
|
||||
alpha_construct = GsmStringAdapter(Rpad(GreedyBytes))
|
||||
|
||||
def __init__(self, fid='6fb7', sfid=0x01, name='EF.ECC',
|
||||
desc='Emergency Call Codes'):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={4,20})
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={4, 20})
|
||||
|
||||
def _decode_record_bin(self, in_bin):
|
||||
# mandatory parts
|
||||
code = in_bin[:3]
|
||||
|
@ -599,12 +631,13 @@ class EF_ECC(LinFixedEF):
|
|||
return None
|
||||
svc_category = in_bin[-1:]
|
||||
ret = {'call_code': parse_construct(EF_ECC.cc_construct, code),
|
||||
'service_category': parse_construct(EF_ECC.category_construct, svc_category) }
|
||||
'service_category': parse_construct(EF_ECC.category_construct, svc_category)}
|
||||
# optional alpha identifier
|
||||
if len(in_bin) > 4:
|
||||
alpha_id = in_bin[3:-1]
|
||||
ret['alpha_id'] = parse_construct(EF_ECC.alpha_construct, alpha_id)
|
||||
return ret
|
||||
|
||||
def _encode_record_bin(self, in_json):
|
||||
if in_json is None:
|
||||
return b'\xff\xff\xff\xff'
|
||||
|
@ -617,11 +650,13 @@ class EF_ECC(LinFixedEF):
|
|||
|
||||
# TS 31.102 Section 4.2.17
|
||||
class EF_LOCI(TransparentEF):
|
||||
def __init__(self, fid='6f7e', sfid=0x0b, name='EF.LOCI', desc='Location information', size={11,11}):
|
||||
def __init__(self, fid='6f7e', sfid=0x0b, name='EF.LOCI', desc='Location information', size={11, 11}):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
|
||||
self._construct = Struct('tmsi'/HexAdapter(Bytes(4)), 'lai'/HexAdapter(Bytes(5)), 'rfu'/Int8ub,
|
||||
'lu_status'/Int8ub)
|
||||
# TS 31.102 Section 4.2.18
|
||||
|
||||
|
||||
class EF_AD(TransparentEF):
|
||||
class OP_MODE(enum.IntEnum):
|
||||
normal = 0x00
|
||||
|
@ -631,7 +666,7 @@ class EF_AD(TransparentEF):
|
|||
maintenance_off_line = 0x02
|
||||
cell_test = 0x04
|
||||
|
||||
def __init__(self, fid='6fad', sfid=0x03, name='EF.AD', desc='Administrative Data', size={4,6}):
|
||||
def __init__(self, fid='6fad', sfid=0x03, name='EF.AD', desc='Administrative Data', size={4, 6}):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
|
||||
self._construct = BitStruct(
|
||||
# Byte 1
|
||||
|
@ -645,15 +680,19 @@ class EF_AD(TransparentEF):
|
|||
)
|
||||
|
||||
# TS 31.102 Section 4.2.23
|
||||
|
||||
|
||||
class EF_PSLOCI(TransparentEF):
|
||||
def __init__(self, fid='6f73', sfid=0x0c, name='EF.PSLOCI', desc='PS Location information', size={14,14}):
|
||||
def __init__(self, fid='6f73', sfid=0x0c, name='EF.PSLOCI', desc='PS Location information', size={14, 14}):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
|
||||
self._construct = Struct('ptmsi'/HexAdapter(Bytes(4)), 'ptmsi_sig'/HexAdapter(Bytes(3)),
|
||||
'rai'/HexAdapter(Bytes(6)), 'rau_status'/Int8ub)
|
||||
|
||||
# TS 31.102 Section 4.2.33
|
||||
|
||||
|
||||
class EF_ICI(CyclicEF):
|
||||
def __init__(self, fid='6f80', sfid=0x14, name='EF.ICI', rec_len={28,48},
|
||||
def __init__(self, fid='6f80', sfid=0x14, name='EF.ICI', rec_len={28, 48},
|
||||
desc='Incoming Call Information'):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len)
|
||||
self._construct = Struct('alpha_id'/Bytes(this._.total_len-28),
|
||||
|
@ -668,8 +707,10 @@ class EF_ICI(CyclicEF):
|
|||
'link_to_phonebook'/Bytes(3))
|
||||
|
||||
# TS 31.102 Section 4.2.34
|
||||
|
||||
|
||||
class EF_OCI(CyclicEF):
|
||||
def __init__(self, fid='6f81', sfid=0x15, name='EF.OCI', rec_len={27,47},
|
||||
def __init__(self, fid='6f81', sfid=0x15, name='EF.OCI', rec_len={27, 47},
|
||||
desc='Outgoing Call Information'):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len)
|
||||
self._construct = Struct('alpha_id'/Bytes(this._.total_len-27),
|
||||
|
@ -683,97 +724,133 @@ class EF_OCI(CyclicEF):
|
|||
'link_to_phonebook'/Bytes(3))
|
||||
|
||||
# TS 31.102 Section 4.2.35
|
||||
|
||||
|
||||
class EF_ICT(CyclicEF):
|
||||
def __init__(self, fid='6f82', sfid=None, name='EF.ICT', rec_len={3,3},
|
||||
def __init__(self, fid='6f82', sfid=None, name='EF.ICT', rec_len={3, 3},
|
||||
desc='Incoming Call Timer'):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len)
|
||||
self._construct = Struct('accumulated_call_timer'/Int24ub)
|
||||
|
||||
# TS 31.102 Section 4.2.38
|
||||
|
||||
|
||||
class EF_CCP2(LinFixedEF):
|
||||
def __init__(self, fid='6f4f', sfid=0x16, name='EF.CCP2', desc='Capability Configuration Parameters 2'):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len={15,None})
|
||||
super().__init__(fid=fid, sfid=sfid,
|
||||
name=name, desc=desc, rec_len={15, None})
|
||||
|
||||
# TS 31.102 Section 4.2.48
|
||||
|
||||
|
||||
class EF_ACL(TransparentEF):
|
||||
def __init__(self, fid='6f57', sfid=None, name='EF.ACL', size={32,None},
|
||||
def __init__(self, fid='6f57', sfid=None, name='EF.ACL', size={32, None},
|
||||
desc='Access Point Name Control List'):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
|
||||
self._construct = Struct('num_of_apns'/Int8ub, 'tlvs'/GreedyBytes)
|
||||
|
||||
# TS 31.102 Section 4.2.51
|
||||
|
||||
|
||||
class EF_START_HFN(TransparentEF):
|
||||
def __init__(self, fid='6f5b', sfid=0x0f, name='EF.START-HFN', size={6,6},
|
||||
def __init__(self, fid='6f5b', sfid=0x0f, name='EF.START-HFN', size={6, 6},
|
||||
desc='Initialisation values for Hyperframe number'):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
|
||||
self._construct = Struct('start_cs'/Int24ub, 'start_ps'/Int24ub)
|
||||
|
||||
# TS 31.102 Section 4.2.52
|
||||
|
||||
|
||||
class EF_THRESHOLD(TransparentEF):
|
||||
def __init__(self, fid='6f5c', sfid=0x10, name='EF.THRESHOLD', size={3,3},
|
||||
def __init__(self, fid='6f5c', sfid=0x10, name='EF.THRESHOLD', size={3, 3},
|
||||
desc='Maximum value of START'):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
|
||||
self._construct = Struct('max_start'/Int24ub)
|
||||
|
||||
# TS 31.102 Section 4.2.77
|
||||
|
||||
|
||||
class EF_VGCSCA(TransRecEF):
|
||||
def __init__(self, fid='6fd4', sfid=None, name='EF.VGCSCA', size={2,100}, rec_len=2,
|
||||
def __init__(self, fid='6fd4', sfid=None, name='EF.VGCSCA', size={2, 100}, rec_len=2,
|
||||
desc='Voice Group Call Service Ciphering Algorithm'):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len)
|
||||
self._construct = Struct('alg_v_ki_1'/Int8ub, 'alg_v_ki_2'/Int8ub)
|
||||
|
||||
# TS 31.102 Section 4.2.79
|
||||
|
||||
|
||||
class EF_GBABP(TransparentEF):
|
||||
def __init__(self, fid='6fd6', sfid=None, name='EF.GBABP', size={3,50},
|
||||
def __init__(self, fid='6fd6', sfid=None, name='EF.GBABP', size={3, 50},
|
||||
desc='GBA Bootstrapping parameters'):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
|
||||
self._construct = Struct('rand'/LV, 'b_tid'/LV, 'key_lifetime'/LV)
|
||||
|
||||
# TS 31.102 Section 4.2.80
|
||||
|
||||
|
||||
class EF_MSK(LinFixedEF):
|
||||
def __init__(self, fid='6fd7', sfid=None, name='EF.MSK', desc='MBMS Service Key List'):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len={20,None})
|
||||
super().__init__(fid=fid, sfid=sfid,
|
||||
name=name, desc=desc, rec_len={20, None})
|
||||
msk_ts_constr = Struct('msk_id'/Int32ub, 'timestamp_counter'/Int32ub)
|
||||
self._construct = Struct('key_domain_id'/Bytes(3),
|
||||
'num_msk_id'/Int8ub,
|
||||
'msk_ids'/msk_ts_constr[this.num_msk_id])
|
||||
# TS 31.102 Section 4.2.81
|
||||
|
||||
|
||||
class EF_MUK(LinFixedEF):
|
||||
class MUK_Idr(BER_TLV_IE, tag=0x80):
|
||||
_construct = HexAdapter(GreedyBytes)
|
||||
|
||||
class MUK_Idi(BER_TLV_IE, tag=0x82):
|
||||
_construct = HexAdapter(GreedyBytes)
|
||||
|
||||
class MUK_ID(BER_TLV_IE, tag=0xA0, nested=[MUK_Idr, MUK_Idi]):
|
||||
pass
|
||||
|
||||
class TimeStampCounter(BER_TLV_IE, tag=0x81):
|
||||
pass
|
||||
|
||||
class EF_MUK_Collection(TLV_IE_Collection, nested=[MUK_ID, TimeStampCounter]):
|
||||
pass
|
||||
|
||||
def __init__(self, fid='6fd8', sfid=None, name='EF.MUK', desc='MBMS User Key'):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len={None,None})
|
||||
super().__init__(fid=fid, sfid=sfid, name=name,
|
||||
desc=desc, rec_len={None, None})
|
||||
self._tlv = EF_MUK.EF_MUK_Collection
|
||||
|
||||
# TS 31.102 Section 4.2.83
|
||||
|
||||
|
||||
class EF_GBANL(LinFixedEF):
|
||||
class NAF_ID(BER_TLV_IE, tag=0x80):
|
||||
_construct = HexAdapter(GreedyBytes)
|
||||
|
||||
class B_TID(BER_TLV_IE, tag=0x81):
|
||||
_construct = HexAdapter(GreedyBytes)
|
||||
|
||||
class EF_GBANL_Collection(BER_TLV_IE, nested=[NAF_ID, B_TID]):
|
||||
pass
|
||||
|
||||
def __init__(self, fid='6fda', sfid=None, name='EF.GBANL', desc='GBA NAF List'):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len={None,None})
|
||||
super().__init__(fid=fid, sfid=sfid, name=name,
|
||||
desc=desc, rec_len={None, None})
|
||||
self._tlv = EF_GBANL.EF_GBANL_Collection
|
||||
|
||||
# TS 31.102 Section 4.2.85
|
||||
|
||||
|
||||
class EF_EHPLMNPI(TransparentEF):
|
||||
def __init__(self, fid='6fdb', sfid=None, name='EF.EHPLMNPI', size={1,1},
|
||||
def __init__(self, fid='6fdb', sfid=None, name='EF.EHPLMNPI', size={1, 1},
|
||||
desc='Equivalent HPLMN Presentation Indication'):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
|
||||
self._construct = Struct('presentation_ind'/
|
||||
self._construct = Struct('presentation_ind' /
|
||||
Enum(Byte, no_preference=0, display_highest_prio_only=1, display_all=2))
|
||||
|
||||
# TS 31.102 Section 4.2.87
|
||||
|
||||
|
||||
class EF_NAFKCA(LinFixedEF):
|
||||
class NAF_KeyCentreAddress(BER_TLV_IE, tag=0x80):
|
||||
_construct = HexAdapter(GreedyBytes)
|
||||
|
@ -783,23 +860,30 @@ class EF_NAFKCA(LinFixedEF):
|
|||
self._tlv = EF_NAFKCA.NAF_KeyCentreAddress
|
||||
|
||||
# TS 31.102 Section 4.2.90
|
||||
|
||||
|
||||
class EF_NCP_IP(LinFixedEF):
|
||||
class DataDestAddrRange(TLV_IE, tag=0x83):
|
||||
_construct = Struct('type_of_address'/Enum(Byte, IPv4=0x21, IPv6=0x56),
|
||||
'prefix_length'/Int8ub,
|
||||
'prefix'/HexAdapter(GreedyBytes))
|
||||
|
||||
class AccessPointName(TLV_IE, tag=0x80):
|
||||
# coded as per TS 23.003
|
||||
_construct = HexAdapter(GreedyBytes)
|
||||
|
||||
class Login(TLV_IE, tag=0x81):
|
||||
# as per SMS DCS TS 23.038
|
||||
_construct = GsmStringAdapter(GreedyBytes)
|
||||
|
||||
class Password(TLV_IE, tag=0x82):
|
||||
# as per SMS DCS TS 23.038
|
||||
_construct = GsmStringAdapter(GreedyBytes)
|
||||
|
||||
class BearerDescription(TLV_IE, tag=0x84):
|
||||
# Bearer descriptionTLV DO as per TS 31.111
|
||||
pass
|
||||
|
||||
class EF_NCP_IP_Collection(TLV_IE_Collection,
|
||||
nested=[AccessPointName, Login, Password, BearerDescription]):
|
||||
pass
|
||||
|
@ -809,65 +893,85 @@ class EF_NCP_IP(LinFixedEF):
|
|||
self._tlv = EF_NCP_IP.EF_NCP_IP_Collection
|
||||
|
||||
# TS 31.102 Section 4.2.91
|
||||
|
||||
|
||||
class EF_EPSLOCI(TransparentEF):
|
||||
def __init__(self, fid='6fe3', sfid=0x1e, name='EF.EPSLOCI', size={18,18},
|
||||
def __init__(self, fid='6fe3', sfid=0x1e, name='EF.EPSLOCI', size={18, 18},
|
||||
desc='EPS Location Information'):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
|
||||
upd_status_constr = Enum(Byte, updated=0, not_updated=1, roaming_not_allowed=2)
|
||||
upd_status_constr = Enum(
|
||||
Byte, updated=0, not_updated=1, roaming_not_allowed=2)
|
||||
self._construct = Struct('guti'/Bytes(12), 'last_visited_registered_tai'/Bytes(5),
|
||||
'eps_update_status'/upd_status_constr)
|
||||
|
||||
# TS 31.102 Section 4.2.92
|
||||
|
||||
|
||||
class EF_EPSNSC(LinFixedEF):
|
||||
class KSI_ASME(BER_TLV_IE, tag= 0x80):
|
||||
class KSI_ASME(BER_TLV_IE, tag=0x80):
|
||||
_construct = Int8ub
|
||||
class K_ASME(BER_TLV_IE, tag= 0x81):
|
||||
|
||||
class K_ASME(BER_TLV_IE, tag=0x81):
|
||||
_construct = HexAdapter(GreedyBytes)
|
||||
|
||||
class UplinkNASCount(BER_TLV_IE, tag=0x82):
|
||||
_construct = Int32ub
|
||||
|
||||
class DownlinkNASCount(BER_TLV_IE, tag=0x83):
|
||||
_construct = Int32ub
|
||||
|
||||
class IDofNASAlgorithms(BER_TLV_IE, tag=0x84):
|
||||
_construct = HexAdapter(GreedyBytes)
|
||||
|
||||
class EPS_NAS_Security_Context(BER_TLV_IE, tag=0xa0,
|
||||
nested=[KSI_ASME, K_ASME, UplinkNASCount, DownlinkNASCount,
|
||||
IDofNASAlgorithms]):
|
||||
pass
|
||||
def __init__(self,fid='6fe4', sfid=0x18, name='EF.EPSNSC', rec_len={54,128},
|
||||
def __init__(self, fid='6fe4', sfid=0x18, name='EF.EPSNSC', rec_len={54, 128},
|
||||
desc='EPS NAS Security Context'):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len)
|
||||
self._tlv = EF_EPSNSC.EPS_NAS_Security_Context
|
||||
|
||||
# TS 31.102 Section 4.2.96
|
||||
|
||||
|
||||
class EF_PWS(TransparentEF):
|
||||
def __init__(self, fid='6fec', sfid=None, name='EF.PWS', desc='Public Warning System', size={1,1}):
|
||||
def __init__(self, fid='6fec', sfid=None, name='EF.PWS', desc='Public Warning System', size={1, 1}):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
|
||||
pws_config = FlagsEnum(Byte, ignore_pws_in_hplmn_and_equivalent=1, ignore_pws_in_vplmn=2)
|
||||
pws_config = FlagsEnum(
|
||||
Byte, ignore_pws_in_hplmn_and_equivalent=1, ignore_pws_in_vplmn=2)
|
||||
self._construct = Struct('pws_configuration'/pws_config)
|
||||
|
||||
# TS 31.102 Section 4.2.101
|
||||
|
||||
|
||||
class EF_IPS(CyclicEF):
|
||||
def __init__(self, fid='6ff1', sfid=None, name='EF.IPS', rec_len={4,4},
|
||||
def __init__(self, fid='6ff1', sfid=None, name='EF.IPS', rec_len={4, 4},
|
||||
desc='IMEI(SV) Pairing Status'):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len)
|
||||
self._construct = Struct('status'/PaddedString(2, 'ascii'),
|
||||
'link_to_ef_ipd'/Int8ub, 'rfu'/Byte)
|
||||
|
||||
# TS 31.102 Section 4.2.103
|
||||
|
||||
|
||||
class EF_ePDGId(TransparentEF):
|
||||
class ePDGId(BER_TLV_IE, tag=0x80, nested=[]):
|
||||
_construct = Struct('type_of_ePDG_address'/Enum(Byte, FQDN=0, IPv4=1, IPv6=2),
|
||||
'ePDG_address'/Switch(this.type_of_address,
|
||||
{ 'FQDN': GreedyString("utf8"),
|
||||
{'FQDN': GreedyString("utf8"),
|
||||
'IPv4': HexAdapter(GreedyBytes),
|
||||
'IPv6': HexAdapter(GreedyBytes) }))
|
||||
'IPv6': HexAdapter(GreedyBytes)}))
|
||||
|
||||
def __init__(self, fid='6ff3', sfid=None, name='EF.eDPDGId', desc='Home ePDG Identifier'):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc)
|
||||
self._tlv = EF_ePDGId.ePDGId
|
||||
|
||||
# TS 31.102 Section 4.2.106
|
||||
|
||||
|
||||
class EF_FromPreferred(TransparentEF):
|
||||
def __init__(self, fid='6ff7', sfid=None, name='EF.FromPreferred', size={1,1},
|
||||
def __init__(self, fid='6ff7', sfid=None, name='EF.FromPreferred', size={1, 1},
|
||||
desc='From Preferred'):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
|
||||
self._construct = BitStruct('rfu'/BitsRFU(7), 'from_preferred'/Bit)
|
||||
|
@ -877,17 +981,22 @@ class EF_FromPreferred(TransparentEF):
|
|||
######################################################################
|
||||
|
||||
# TS 31.102 Section 4.4.11.2
|
||||
|
||||
|
||||
class EF_5GS3GPPLOCI(TransparentEF):
|
||||
def __init__(self, fid='4f01', sfid=0x01, name='EF.5GS3GPPLOCI', size={20,20},
|
||||
def __init__(self, fid='4f01', sfid=0x01, name='EF.5GS3GPPLOCI', size={20, 20},
|
||||
desc='5S 3GP location information'):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
|
||||
upd_status_constr = Enum(Byte, updated=0, not_updated=1, roaming_not_allowed=2)
|
||||
upd_status_constr = Enum(
|
||||
Byte, updated=0, not_updated=1, roaming_not_allowed=2)
|
||||
self._construct = Struct('5g_guti'/Bytes(13), 'last_visited_registered_tai_in_5gs'/Bytes(6),
|
||||
'5gs_update_status'/upd_status_constr)
|
||||
|
||||
# TS 31.102 Section 4.4.11.7
|
||||
|
||||
|
||||
class EF_UAC_AIC(TransparentEF):
|
||||
def __init__(self, fid='4f06', sfid=0x06, name='EF.UAC_AIC', size={4,4},
|
||||
def __init__(self, fid='4f06', sfid=0x06, name='EF.UAC_AIC', size={4, 4},
|
||||
desc='UAC Access Identities Configuration'):
|
||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
|
||||
cfg_constr = FlagsEnum(Byte, multimedia_priority_service=1,
|
||||
|
@ -895,22 +1004,30 @@ class EF_UAC_AIC(TransparentEF):
|
|||
self._construct = Struct('uac_access_id_config'/cfg_constr)
|
||||
|
||||
# TS 31.102 Section 4.4.11.9
|
||||
|
||||
|
||||
class EF_OPL5G(LinFixedEF):
|
||||
def __init__(self, fid='6f08', sfid=0x08, name='EF.OPL5G', desc='5GS Operator PLMN List'):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len={10,None})
|
||||
super().__init__(fid=fid, sfid=sfid,
|
||||
name=name, desc=desc, rec_len={10, None})
|
||||
self._construct = Struct('tai'/Bytes(9), 'pnn_record_id'/Int8ub)
|
||||
|
||||
# TS 31.102 Section 4.4.11.10
|
||||
|
||||
|
||||
class EF_SUPI_NAI(TransparentEF):
|
||||
class NetworkSpecificIdentifier(TLV_IE, tag=0x80):
|
||||
# RFC 7542 encoded as UTF-8 string
|
||||
_construct = GreedyString("utf8")
|
||||
|
||||
class GlobalLineIdentifier(TLV_IE, tag=0x81):
|
||||
# TS 23.003 clause 28.16.2
|
||||
pass
|
||||
|
||||
class GlobalCableIdentifier(TLV_IE, tag=0x82):
|
||||
# TS 23.003 clause 28.15.2
|
||||
pass
|
||||
|
||||
class NAI_TLV_Collection(TLV_IE_Collection,
|
||||
nested=[NetworkSpecificIdentifier, GlobalLineIdentifier, GlobalCableIdentifier]):
|
||||
pass
|
||||
|
@ -919,6 +1036,7 @@ class EF_SUPI_NAI(TransparentEF):
|
|||
super().__init__(fid, sfid=sfid, name=name, desc=desc)
|
||||
self._tlv = EF_SUPI_NAI.NAI_TLV_Collection
|
||||
|
||||
|
||||
class EF_TN3GPPSNN(TransparentEF):
|
||||
class ServingNetworkName(BER_TLV_IE, tag=0x80):
|
||||
_construct = GreedyString("utf8")
|
||||
|
@ -928,25 +1046,39 @@ class EF_TN3GPPSNN(TransparentEF):
|
|||
self._tlv = EF_TN3GPPSNN.ServingNetworkName
|
||||
|
||||
# TS 31.102 Section 4.4.5
|
||||
|
||||
|
||||
class DF_WLAN(CardDF):
|
||||
def __init__(self, fid='5f40', name='DF.WLAN', desc='Files for WLAN purpose'):
|
||||
super().__init__(fid=fid, name=name, desc=desc)
|
||||
files = [
|
||||
TransparentEF('4f41', 0x01, 'EF.Pseudo', 'Pseudonym'),
|
||||
TransparentEF('4f42', 0x02, 'EF.UPLMNWLAN', 'User controlled PLMN selector for I-WLAN Access'),
|
||||
TransparentEF('4f43', 0x03, 'EF.OPLMNWLAN', 'Operator controlled PLMN selector for I-WLAN Access'),
|
||||
LinFixedEF('4f44', 0x04, 'EF.UWSIDL', 'User controlled WLAN Specific Identifier List'),
|
||||
LinFixedEF('4f45', 0x05, 'EF.OWSIDL', 'Operator controlled WLAN Specific Identifier List'),
|
||||
TransparentEF('4f46', 0x06, 'EF.WRI', 'WLAN Reauthentication Identity'),
|
||||
LinFixedEF('4f47', 0x07, 'EF.HWSIDL', 'Home I-WLAN Specific Identifier List'),
|
||||
TransparentEF('4f48', 0x08, 'EF.WEHPLMNPI', 'I-WLAN Equivalent HPLMN Presentation Indication'),
|
||||
TransparentEF('4f49', 0x09, 'EF.WHPI', 'I-WLAN HPLMN Priority Indication'),
|
||||
TransparentEF('4f4a', 0x0a, 'EF.WLRPLMN', 'I-WLAN Last Registered PLMN'),
|
||||
TransparentEF('4f4b', 0x0b, 'EF.HPLMNDAI', 'HPLMN Direct Access Indicator'),
|
||||
TransparentEF('4f42', 0x02, 'EF.UPLMNWLAN',
|
||||
'User controlled PLMN selector for I-WLAN Access'),
|
||||
TransparentEF('4f43', 0x03, 'EF.OPLMNWLAN',
|
||||
'Operator controlled PLMN selector for I-WLAN Access'),
|
||||
LinFixedEF('4f44', 0x04, 'EF.UWSIDL',
|
||||
'User controlled WLAN Specific Identifier List'),
|
||||
LinFixedEF('4f45', 0x05, 'EF.OWSIDL',
|
||||
'Operator controlled WLAN Specific Identifier List'),
|
||||
TransparentEF('4f46', 0x06, 'EF.WRI',
|
||||
'WLAN Reauthentication Identity'),
|
||||
LinFixedEF('4f47', 0x07, 'EF.HWSIDL',
|
||||
'Home I-WLAN Specific Identifier List'),
|
||||
TransparentEF('4f48', 0x08, 'EF.WEHPLMNPI',
|
||||
'I-WLAN Equivalent HPLMN Presentation Indication'),
|
||||
TransparentEF('4f49', 0x09, 'EF.WHPI',
|
||||
'I-WLAN HPLMN Priority Indication'),
|
||||
TransparentEF('4f4a', 0x0a, 'EF.WLRPLMN',
|
||||
'I-WLAN Last Registered PLMN'),
|
||||
TransparentEF('4f4b', 0x0b, 'EF.HPLMNDAI',
|
||||
'HPLMN Direct Access Indicator'),
|
||||
]
|
||||
self.add_files(files)
|
||||
|
||||
# TS 31.102 Section 4.4.6
|
||||
|
||||
|
||||
class DF_HNB(CardDF):
|
||||
def __init__(self, fid='5f50', name='DF.HNB', desc='Files for HomeNodeB purpose'):
|
||||
super().__init__(fid=fid, name=name, desc=desc)
|
||||
|
@ -961,47 +1093,65 @@ class DF_HNB(CardDF):
|
|||
self.add_files(files)
|
||||
|
||||
# TS 31.102 Section 4.4.8
|
||||
|
||||
|
||||
class DF_ProSe(CardDF):
|
||||
def __init__(self, fid='5f90', name='DF.ProSe', desc='Files for ProSe purpose'):
|
||||
super().__init__(fid=fid, name=name, desc=desc)
|
||||
files = [
|
||||
LinFixedEF('4f01', 0x01, 'EF.PROSE_MON', 'ProSe Monitoring Parameters'),
|
||||
LinFixedEF('4f02', 0x02, 'EF.PROSE_ANN', 'ProSe Announcing Parameters'),
|
||||
LinFixedEF('4f01', 0x01, 'EF.PROSE_MON',
|
||||
'ProSe Monitoring Parameters'),
|
||||
LinFixedEF('4f02', 0x02, 'EF.PROSE_ANN',
|
||||
'ProSe Announcing Parameters'),
|
||||
LinFixedEF('4f03', 0x03, 'EF.PROSEFUNC', 'HPLMN ProSe Function'),
|
||||
TransparentEF('4f04', 0x04, 'EF.PROSE_RADIO_COM', 'ProSe Direct Communication Radio Parameters'),
|
||||
TransparentEF('4f05', 0x05, 'EF.PROSE_RADIO_MON', 'ProSe Direct Discovery Monitoring Radio Parameters'),
|
||||
TransparentEF('4f06', 0x06, 'EF.PROSE_RADIO_ANN', 'ProSe Direct Discovery Announcing Radio Parameters'),
|
||||
LinFixedEF('4f07', 0x07, 'EF.PROSE_POLICY', 'ProSe Policy Parameters'),
|
||||
TransparentEF('4f04', 0x04, 'EF.PROSE_RADIO_COM',
|
||||
'ProSe Direct Communication Radio Parameters'),
|
||||
TransparentEF('4f05', 0x05, 'EF.PROSE_RADIO_MON',
|
||||
'ProSe Direct Discovery Monitoring Radio Parameters'),
|
||||
TransparentEF('4f06', 0x06, 'EF.PROSE_RADIO_ANN',
|
||||
'ProSe Direct Discovery Announcing Radio Parameters'),
|
||||
LinFixedEF('4f07', 0x07, 'EF.PROSE_POLICY',
|
||||
'ProSe Policy Parameters'),
|
||||
LinFixedEF('4f08', 0x08, 'EF.PROSE_PLMN', 'ProSe PLMN Parameters'),
|
||||
TransparentEF('4f09', 0x09, 'EF.PROSE_GC', 'ProSe Group Counter'),
|
||||
TransparentEF('4f10', 0x10, 'EF.PST', 'ProSe Service Table'),
|
||||
TransparentEF('4f11', 0x11, 'EF.UIRC', 'ProSe UsageInformationReportingConfiguration'),
|
||||
LinFixedEF('4f12', 0x12, 'EF.PROSE_GM_DISCOVERY', 'ProSe Group Member Discovery Parameters'),
|
||||
LinFixedEF('4f13', 0x13, 'EF.PROSE_RELAY', 'ProSe Relay Parameters'),
|
||||
TransparentEF('4f14', 0x14, 'EF.PROSE_RELAY_DISCOVERY', 'ProSe Relay Discovery Parameters'),
|
||||
TransparentEF('4f11', 0x11, 'EF.UIRC',
|
||||
'ProSe UsageInformationReportingConfiguration'),
|
||||
LinFixedEF('4f12', 0x12, 'EF.PROSE_GM_DISCOVERY',
|
||||
'ProSe Group Member Discovery Parameters'),
|
||||
LinFixedEF('4f13', 0x13, 'EF.PROSE_RELAY',
|
||||
'ProSe Relay Parameters'),
|
||||
TransparentEF('4f14', 0x14, 'EF.PROSE_RELAY_DISCOVERY',
|
||||
'ProSe Relay Discovery Parameters'),
|
||||
]
|
||||
self.add_files(files)
|
||||
|
||||
|
||||
class DF_USIM_5GS(CardDF):
|
||||
def __init__(self, fid='5FC0', name='DF.5GS', desc='5GS related files'):
|
||||
super().__init__(fid=fid, name=name, desc=desc)
|
||||
files = [
|
||||
# I'm looking at 31.102 R16.6
|
||||
EF_5GS3GPPLOCI(),
|
||||
EF_5GS3GPPLOCI('4f02', 0x02, 'EF.5GSN3GPPLOCI', '5GS non-3GPP location information'),
|
||||
EF_5GS3GPPLOCI('4f02', 0x02, 'EF.5GSN3GPPLOCI',
|
||||
'5GS non-3GPP location information'),
|
||||
EF_5GS3GPPNSC(),
|
||||
EF_5GS3GPPNSC('4f04', 0x04, 'EF.5GSN3GPPNSC', '5GS non-3GPP Access NAS Security Context'),
|
||||
EF_5GS3GPPNSC('4f04', 0x04, 'EF.5GSN3GPPNSC',
|
||||
'5GS non-3GPP Access NAS Security Context'),
|
||||
EF_5GAUTHKEYS(),
|
||||
EF_UAC_AIC(),
|
||||
EF_SUCI_Calc_Info(),
|
||||
EF_OPL5G(),
|
||||
EF_SUPI_NAI(),
|
||||
TransparentEF('4F0A', 0x0a, 'EF.Routing_Indicator', 'Routing Indicator', size={4,4}),
|
||||
TransparentEF('4F0B', 0x0b, 'EF.URSP', 'UE Route Selector Policies per PLMN'),
|
||||
TransparentEF('4F0A', 0x0a, 'EF.Routing_Indicator',
|
||||
'Routing Indicator', size={4, 4}),
|
||||
TransparentEF('4F0B', 0x0b, 'EF.URSP',
|
||||
'UE Route Selector Policies per PLMN'),
|
||||
EF_TN3GPPSNN(),
|
||||
]
|
||||
self.add_files(files)
|
||||
|
||||
|
||||
class ADF_USIM(CardADF):
|
||||
def __init__(self, aid='a0000000871002', name='ADF.USIM', fid=None, sfid=None,
|
||||
desc='USIM Application'):
|
||||
|
@ -1013,20 +1163,25 @@ class ADF_USIM(CardADF):
|
|||
EF_LI(sfid=0x02),
|
||||
EF_IMSI(sfid=0x07),
|
||||
EF_Keys(),
|
||||
EF_Keys('6f09', 0x09, 'EF.KeysPS', desc='Ciphering and Integrity Keys for PS domain'),
|
||||
EF_Keys('6f09', 0x09, 'EF.KeysPS',
|
||||
desc='Ciphering and Integrity Keys for PS domain'),
|
||||
EF_xPLMNwAcT('6f60', 0x0a, 'EF.PLMNwAcT',
|
||||
'User controlled PLMN Selector with Access Technology'),
|
||||
EF_HPPLMN(),
|
||||
EF_ACMmax(),
|
||||
EF_UServiceTable('6f38', 0x04, 'EF.UST', 'USIM Service Table', size={1,17}, table=EF_UST_map),
|
||||
CyclicEF('6f39', None, 'EF.ACM', 'Accumulated call meter', rec_len={3,3}),
|
||||
EF_UServiceTable('6f38', 0x04, 'EF.UST', 'USIM Service Table', size={
|
||||
1, 17}, table=EF_UST_map),
|
||||
CyclicEF('6f39', None, 'EF.ACM',
|
||||
'Accumulated call meter', rec_len={3, 3}),
|
||||
TransparentEF('6f3e', None, 'EF.GID1', 'Group Identifier Level 1'),
|
||||
TransparentEF('6f3f', None, 'EF.GID2', 'Group Identifier Level 2'),
|
||||
EF_SPN(),
|
||||
TransparentEF('6f41', None, 'EF.PUCT', 'Price per unit and currency table', size={5,5}),
|
||||
TransparentEF('6f41', None, 'EF.PUCT',
|
||||
'Price per unit and currency table', size={5, 5}),
|
||||
EF_CBMI(),
|
||||
EF_ACC(sfid=0x06),
|
||||
EF_PLMNsel('6f7b', 0x0d, 'EF.FPLMN', 'Forbidden PLMNs', size={12,None}),
|
||||
EF_PLMNsel('6f7b', 0x0d, 'EF.FPLMN',
|
||||
'Forbidden PLMNs', size={12, None}),
|
||||
EF_LOCI(),
|
||||
EF_AD(),
|
||||
EF_CBMID(sfid=0x0e),
|
||||
|
@ -1054,7 +1209,8 @@ class ADF_USIM(CardADF):
|
|||
EF_ADN('6f4d', None, 'EF.BDN', 'Barred Dialling Numbers'),
|
||||
EF_EXT('6f55', None, 'EF.EXT4', 'Extension4 (BDN/SSC)'),
|
||||
EF_CMI(),
|
||||
EF_UServiceTable('6f56', 0x05, 'EF.EST', 'Enabled Services Table', size={1,None}, table=EF_EST_map),
|
||||
EF_UServiceTable('6f56', 0x05, 'EF.EST', 'Enabled Services Table', size={
|
||||
1, None}, table=EF_EST_map),
|
||||
EF_ACL(),
|
||||
EF_DCK(),
|
||||
EF_CNL(),
|
||||
|
@ -1069,9 +1225,11 @@ class ADF_USIM(CardADF):
|
|||
EF_ADN('6fc7', None, 'EF.MBDN', 'Mailbox Dialling Numbers'),
|
||||
EF_MBI(),
|
||||
EF_MWIS(),
|
||||
EF_ADN('6fcb', None, 'EF.CFIS', 'Call Forwarding Indication Status'),
|
||||
EF_ADN('6fcb', None, 'EF.CFIS',
|
||||
'Call Forwarding Indication Status'),
|
||||
EF_EXT('6fcc', None, 'EF.EXT7', 'Extension7 (CFIS)'),
|
||||
TransparentEF('6fcd', None, 'EF.SPDI', 'Service Provider Display Information'),
|
||||
TransparentEF('6fcd', None, 'EF.SPDI',
|
||||
'Service Provider Display Information'),
|
||||
EF_MMSN(),
|
||||
EF_EXT('6fcf', None, 'EF.EXT8', 'Extension8 (MMSN)'),
|
||||
EF_MMSICP(),
|
||||
|
@ -1081,28 +1239,37 @@ class ADF_USIM(CardADF):
|
|||
EF_VGCS(),
|
||||
EF_VGCSS(),
|
||||
EF_VGCS('6fb3', None, 'EF.VBS', 'Voice Broadcast Service'),
|
||||
EF_VGCSS('6fb4', None, 'EF.VBSS', 'Voice Broadcast Service Status'),
|
||||
EF_VGCSS('6fb4', None, 'EF.VBSS',
|
||||
'Voice Broadcast Service Status'),
|
||||
EF_VGCSCA(),
|
||||
EF_VGCSCA('6fd5', None, 'EF.VBCSCA', 'Voice Broadcast Service Ciphering Algorithm'),
|
||||
EF_VGCSCA('6fd5', None, 'EF.VBCSCA',
|
||||
'Voice Broadcast Service Ciphering Algorithm'),
|
||||
EF_GBABP(),
|
||||
EF_MSK(),
|
||||
EF_MUK(),
|
||||
EF_GBANL(),
|
||||
EF_PLMNsel('6fd9', 0x1d, 'EF.EHPLMN', 'Equivalent HPLMN', size={12,None}),
|
||||
EF_PLMNsel('6fd9', 0x1d, 'EF.EHPLMN',
|
||||
'Equivalent HPLMN', size={12, None}),
|
||||
EF_EHPLMNPI(),
|
||||
EF_NAFKCA(),
|
||||
TransparentEF('6fde', None, 'EF.SPNI', 'Service Provider Name Icon'),
|
||||
TransparentEF('6fde', None, 'EF.SPNI',
|
||||
'Service Provider Name Icon'),
|
||||
LinFixedEF('6fdf', None, 'EF.PNNI', 'PLMN Network Name Icon'),
|
||||
EF_NCP_IP(),
|
||||
EF_EPSLOCI('6fe3', 0x1e, 'EF.EPSLOCI', 'EPS location information'),
|
||||
EF_EPSNSC(),
|
||||
TransparentEF('6fe6', None, 'EF.UFC', 'USAT Facility Control', size={1,16}),
|
||||
TransparentEF('6fe8', None, 'EF.NASCONFIG', 'Non Access Stratum Configuration'),
|
||||
TransparentEF('6fe6', None, 'EF.UFC',
|
||||
'USAT Facility Control', size={1, 16}),
|
||||
TransparentEF('6fe8', None, 'EF.NASCONFIG',
|
||||
'Non Access Stratum Configuration'),
|
||||
# UICC IARI (only in cards that have no ISIM)
|
||||
EF_PWS(),
|
||||
LinFixedEF('6fed', None, 'EF.FDNURI', 'Fixed Dialling Numbers URI'),
|
||||
LinFixedEF('6fee', None, 'EF.BDNURI', 'Barred Dialling Numbers URI'),
|
||||
LinFixedEF('6fef', None, 'EF.SDNURI', 'Service Dialling Numbers URI'),
|
||||
LinFixedEF('6fed', None, 'EF.FDNURI',
|
||||
'Fixed Dialling Numbers URI'),
|
||||
LinFixedEF('6fee', None, 'EF.BDNURI',
|
||||
'Barred Dialling Numbers URI'),
|
||||
LinFixedEF('6fef', None, 'EF.SDNURI',
|
||||
'Service Dialling Numbers URI'),
|
||||
EF_IPS(),
|
||||
EF_ePDGId(),
|
||||
# FIXME: from EF_ePDGSelection onwards
|
||||
|
@ -1131,6 +1298,7 @@ class ADF_USIM(CardADF):
|
|||
authenticate_parser.add_argument('rand', help='Random challenge')
|
||||
authenticate_parser.add_argument('autn', help='Authentication Nonce')
|
||||
#authenticate_parser.add_argument('--context', help='Authentication context', default='3G')
|
||||
|
||||
@cmd2.with_argparser(authenticate_parser)
|
||||
def do_authenticate(self, opts):
|
||||
"""Perform Authentication and Key Agreement (AKA)."""
|
||||
|
@ -1151,7 +1319,8 @@ class ADF_USIM(CardADF):
|
|||
"""Send an ENVELOPE command to the card."""
|
||||
tpdu_ie = SMS_TPDU()
|
||||
tpdu_ie.from_bytes(h2b(arg))
|
||||
dev_ids = DeviceIdentities(decoded={'source_dev_id':'network','dest_dev_id':'uicc'})
|
||||
dev_ids = DeviceIdentities(
|
||||
decoded={'source_dev_id': 'network', 'dest_dev_id': 'uicc'})
|
||||
sms_dl = SMSPPDownload(children=[dev_ids, tpdu_ie])
|
||||
(data, sw) = self._cmd.card._scc.envelope(b2h(sms_dl.to_tlv()))
|
||||
self._cmd.poutput('SW: %s, data: %s' % (sw, data))
|
||||
|
@ -1168,6 +1337,7 @@ sw_usim = {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
class CardApplicationUSIM(CardApplication):
|
||||
def __init__(self):
|
||||
super().__init__('USIM', adf=ADF_USIM(), sw=sw_usim)
|
||||
|
|
|
@ -78,83 +78,114 @@ EF_ISIM_ADF_map = {
|
|||
}
|
||||
|
||||
# TS 31.103 Section 4.2.2
|
||||
|
||||
|
||||
class EF_IMPI(TransparentEF):
|
||||
class nai(BER_TLV_IE, tag=0x80):
|
||||
_construct = GreedyString("utf8")
|
||||
|
||||
def __init__(self, fid='6f02', sfid=0x02, name='EF.IMPI', desc='IMS private user identity'):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
|
||||
self._tlv = EF_IMPI.nai
|
||||
|
||||
# TS 31.103 Section 4.2.3
|
||||
|
||||
|
||||
class EF_DOMAIN(TransparentEF):
|
||||
class domain(BER_TLV_IE, tag=0x80):
|
||||
_construct = GreedyString("utf8")
|
||||
|
||||
def __init__(self, fid='6f05', sfid=0x05, name='EF.DOMAIN', desc='Home Network Domain Name'):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
|
||||
self._tlv = EF_DOMAIN.domain
|
||||
|
||||
# TS 31.103 Section 4.2.4
|
||||
|
||||
|
||||
class EF_IMPU(LinFixedEF):
|
||||
class impu(BER_TLV_IE, tag=0x80):
|
||||
_construct = GreedyString("utf8")
|
||||
|
||||
def __init__(self, fid='6f04', sfid=0x04, name='EF.IMPU', desc='IMS public user identity'):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
|
||||
self._tlv = EF_IMPU.impu
|
||||
|
||||
# TS 31.103 Section 4.2.8
|
||||
|
||||
|
||||
class EF_PCSCF(LinFixedEF):
|
||||
def __init__(self, fid='6f09', sfid=None, name='EF.P-CSCF', desc='P-CSCF Address'):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
|
||||
|
||||
def _decode_record_hex(self, raw_hex):
|
||||
addr, addr_type = dec_addr_tlv(raw_hex)
|
||||
return {"addr": addr, "addr_type": addr_type}
|
||||
|
||||
def _encode_record_hex(self, json_in):
|
||||
addr = json_in['addr']
|
||||
addr_type = json_in['addr_type']
|
||||
return enc_addr_tlv(addr, addr_type)
|
||||
|
||||
# TS 31.103 Section 4.2.9
|
||||
|
||||
|
||||
class EF_GBABP(TransparentEF):
|
||||
def __init__(self, fid='6fd5', sfid=None, name='EF.GBABP', desc='GBA Bootstrapping'):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
|
||||
|
||||
# TS 31.103 Section 4.2.10
|
||||
|
||||
|
||||
class EF_GBANL(LinFixedEF):
|
||||
def __init__(self, fid='6fd7', sfid=None, name='EF.GBANL', desc='GBA NAF List'):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
|
||||
|
||||
# TS 31.103 Section 4.2.11
|
||||
|
||||
|
||||
class EF_NAFKCA(LinFixedEF):
|
||||
def __init__(self, fid='6fdd', sfid=None, name='EF.NAFKCA', desc='NAF Key Centre Address'):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
|
||||
|
||||
# TS 31.103 Section 4.2.16
|
||||
|
||||
|
||||
class EF_UICCIARI(LinFixedEF):
|
||||
class iari(BER_TLV_IE, tag=0x80):
|
||||
_construct = GreedyString("utf8")
|
||||
|
||||
def __init__(self, fid='6fe7', sfid=None, name='EF.UICCIARI', desc='UICC IARI'):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
|
||||
self._tlv = EF_UICCIARI.iari
|
||||
|
||||
# TS 31.103 Section 4.2.18
|
||||
|
||||
|
||||
class EF_IMSConfigData(BerTlvEF):
|
||||
def __init__(self, fid='6ff8', sfid=None, name='EF.IMSConfigData', desc='IMS Configuration Data'):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
|
||||
|
||||
# TS 31.103 Section 4.2.19
|
||||
|
||||
|
||||
class EF_XCAPConfigData(BerTlvEF):
|
||||
def __init__(self, fid='6ffc', sfid=None, name='EF.XCAPConfigData', desc='XCAP Configuration Data'):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
|
||||
|
||||
# TS 31.103 Section 4.2.20
|
||||
|
||||
|
||||
class EF_WebRTCURI(TransparentEF):
|
||||
class uri(BER_TLV_IE, tag=0x80):
|
||||
_construct = GreedyString("utf8")
|
||||
|
||||
def __init__(self, fid='6ffa', sfid=None, name='EF.WebRTCURI', desc='WebRTC URI'):
|
||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
|
||||
self._tlv = EF_WebRTCURI.uri
|
||||
|
||||
# TS 31.103 Section 4.2.21
|
||||
|
||||
|
||||
class EF_MuDMiDConfigData(BerTlvEF):
|
||||
def __init__(self, fid='6ffe', sfid=None, name='EF.MuDMiDConfigData',
|
||||
desc='MuD and MiD Configuration Data'):
|
||||
|
@ -172,7 +203,8 @@ class ADF_ISIM(CardADF):
|
|||
EF_IMPU(),
|
||||
EF_AD(),
|
||||
EF_ARR('6f06', 0x06),
|
||||
EF_UServiceTable('6f07', 0x07, 'EF.IST', 'ISIM Service Table', {1,None}, EF_IST_map),
|
||||
EF_UServiceTable('6f07', 0x07, 'EF.IST',
|
||||
'ISIM Service Table', {1, None}, EF_IST_map),
|
||||
EF_PCSCF(),
|
||||
EF_GBABP(),
|
||||
EF_GBANL(),
|
||||
|
@ -195,6 +227,7 @@ class ADF_ISIM(CardADF):
|
|||
def decode_select_response(self, data_hex):
|
||||
return pySim.ts_102_221.CardProfileUICC.decode_select_response(data_hex)
|
||||
|
||||
|
||||
# TS 31.103 Section 7.1
|
||||
sw_isim = {
|
||||
'Security management': {
|
||||
|
@ -203,6 +236,7 @@ sw_isim = {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
class CardApplicationISIM(CardApplication):
|
||||
def __init__(self):
|
||||
super().__init__('ISIM', adf=ADF_ISIM(), sw=sw_isim)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
326
pySim/utils.py
326
pySim/utils.py
|
@ -29,42 +29,51 @@ from typing import Optional, List, Dict, Any, Tuple
|
|||
# just to differentiate strings of hex nibbles from everything else
|
||||
Hexstr = str
|
||||
|
||||
def h2b(s:Hexstr) -> bytearray:
|
||||
|
||||
def h2b(s: Hexstr) -> bytearray:
|
||||
"""convert from a string of hex nibbles to a sequence of bytes"""
|
||||
return bytearray.fromhex(s)
|
||||
|
||||
def b2h(b:bytearray) -> Hexstr:
|
||||
|
||||
def b2h(b: bytearray) -> Hexstr:
|
||||
"""convert from a sequence of bytes to a string of hex nibbles"""
|
||||
return ''.join(['%02x'%(x) for x in b])
|
||||
return ''.join(['%02x' % (x) for x in b])
|
||||
|
||||
def h2i(s:Hexstr) -> List[int]:
|
||||
|
||||
def h2i(s: Hexstr) -> List[int]:
|
||||
"""convert from a string of hex nibbles to a list of integers"""
|
||||
return [(int(x,16)<<4)+int(y,16) for x,y in zip(s[0::2], s[1::2])]
|
||||
return [(int(x, 16) << 4)+int(y, 16) for x, y in zip(s[0::2], s[1::2])]
|
||||
|
||||
def i2h(s:List[int]) -> Hexstr:
|
||||
|
||||
def i2h(s: List[int]) -> Hexstr:
|
||||
"""convert from a list of integers to a string of hex nibbles"""
|
||||
return ''.join(['%02x'%(x) for x in s])
|
||||
return ''.join(['%02x' % (x) for x in s])
|
||||
|
||||
def h2s(s:Hexstr) -> str:
|
||||
|
||||
def h2s(s: Hexstr) -> str:
|
||||
"""convert from a string of hex nibbles to an ASCII string"""
|
||||
return ''.join([chr((int(x,16)<<4)+int(y,16)) for x,y in zip(s[0::2], s[1::2])
|
||||
return ''.join([chr((int(x, 16) << 4)+int(y, 16)) for x, y in zip(s[0::2], s[1::2])
|
||||
if int(x + y, 16) != 0xff])
|
||||
|
||||
def s2h(s:str) -> Hexstr:
|
||||
|
||||
def s2h(s: str) -> Hexstr:
|
||||
"""convert from an ASCII string to a string of hex nibbles"""
|
||||
b = bytearray()
|
||||
b.extend(map(ord, s))
|
||||
return b2h(b)
|
||||
|
||||
def i2s(s:List[int]) -> str:
|
||||
|
||||
def i2s(s: List[int]) -> str:
|
||||
"""convert from a list of integers to an ASCII string"""
|
||||
return ''.join([chr(x) for x in s])
|
||||
|
||||
def swap_nibbles(s:Hexstr) -> Hexstr:
|
||||
"""swap the nibbles in a hex string"""
|
||||
return ''.join([x+y for x,y in zip(s[1::2], s[0::2])])
|
||||
|
||||
def rpad(s:str, l:int, c='f') -> str:
|
||||
def swap_nibbles(s: Hexstr) -> Hexstr:
|
||||
"""swap the nibbles in a hex string"""
|
||||
return ''.join([x+y for x, y in zip(s[1::2], s[0::2])])
|
||||
|
||||
|
||||
def rpad(s: str, l: int, c='f') -> str:
|
||||
"""pad string on the right side.
|
||||
Args:
|
||||
s : string to pad
|
||||
|
@ -75,7 +84,8 @@ def rpad(s:str, l:int, c='f') -> str:
|
|||
"""
|
||||
return s + c * (l - len(s))
|
||||
|
||||
def lpad(s:str, l:int, c='f') -> str:
|
||||
|
||||
def lpad(s: str, l: int, c='f') -> str:
|
||||
"""pad string on the left side.
|
||||
Args:
|
||||
s : string to pad
|
||||
|
@ -86,10 +96,12 @@ def lpad(s:str, l:int, c='f') -> str:
|
|||
"""
|
||||
return c * (l - len(s)) + s
|
||||
|
||||
def half_round_up(n:int) -> int:
|
||||
|
||||
def half_round_up(n: int) -> int:
|
||||
return (n + 1)//2
|
||||
|
||||
def str_sanitize(s:str) -> str:
|
||||
|
||||
def str_sanitize(s: str) -> str:
|
||||
"""replace all non printable chars, line breaks and whitespaces, with ' ', make sure that
|
||||
there are no whitespaces at the end and at the beginning of the string.
|
||||
|
||||
|
@ -107,10 +119,12 @@ def str_sanitize(s:str) -> str:
|
|||
# poor man's COMPREHENSION-TLV decoder.
|
||||
#########################################################################
|
||||
|
||||
def comprehensiontlv_parse_tag_raw(binary:bytes) -> Tuple[int, bytes]:
|
||||
|
||||
def comprehensiontlv_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]:
|
||||
"""Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
|
||||
if binary[0] in [0x00, 0x80, 0xff]:
|
||||
raise ValueError("Found illegal value 0x%02x in %s" % (binary[0], binary))
|
||||
raise ValueError("Found illegal value 0x%02x in %s" %
|
||||
(binary[0], binary))
|
||||
if binary[0] == 0x7f:
|
||||
# three-byte tag
|
||||
tag = binary[0] << 16 | binary[1] << 8 | binary[2]
|
||||
|
@ -122,10 +136,12 @@ def comprehensiontlv_parse_tag_raw(binary:bytes) -> Tuple[int, bytes]:
|
|||
tag = binary[0]
|
||||
return (tag, binary[1:])
|
||||
|
||||
def comprehensiontlv_parse_tag(binary:bytes) -> Tuple[dict, bytes]:
|
||||
|
||||
def comprehensiontlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]:
|
||||
"""Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
|
||||
if binary[0] in [0x00, 0x80, 0xff]:
|
||||
raise ValueError("Found illegal value 0x%02x in %s" % (binary[0], binary))
|
||||
raise ValueError("Found illegal value 0x%02x in %s" %
|
||||
(binary[0], binary))
|
||||
if binary[0] == 0x7f:
|
||||
# three-byte tag
|
||||
tag = (binary[1] & 0x7f) << 8
|
||||
|
@ -138,6 +154,7 @@ def comprehensiontlv_parse_tag(binary:bytes) -> Tuple[dict, bytes]:
|
|||
compr = True if binary[0] & 0x80 else False
|
||||
return ({'comprehension': compr, 'tag': tag}, binary[1:])
|
||||
|
||||
|
||||
def comprehensiontlv_encode_tag(tag) -> bytes:
|
||||
"""Encode a single Tag according to ETSI TS 101 220 Section 7.1.1"""
|
||||
# permit caller to specify tag also as integer value
|
||||
|
@ -161,7 +178,8 @@ def comprehensiontlv_encode_tag(tag) -> bytes:
|
|||
|
||||
# length value coding is equal to BER-TLV
|
||||
|
||||
def comprehensiontlv_parse_one(binary:bytes) -> Tuple[dict, int, bytes, bytes]:
|
||||
|
||||
def comprehensiontlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]:
|
||||
"""Parse a single TLV IE at the start of the given binary data.
|
||||
Args:
|
||||
binary : binary input data of BER-TLV length field
|
||||
|
@ -175,12 +193,11 @@ def comprehensiontlv_parse_one(binary:bytes) -> Tuple[dict, int, bytes, bytes]:
|
|||
return (tagdict, length, value, remainder)
|
||||
|
||||
|
||||
|
||||
#########################################################################
|
||||
# poor man's BER-TLV decoder. To be a more sophisticated OO library later
|
||||
#########################################################################
|
||||
|
||||
def bertlv_parse_tag_raw(binary:bytes) -> Tuple[int, bytes]:
|
||||
def bertlv_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]:
|
||||
"""Get a single raw Tag from start of input according to ITU-T X.690 8.1.2
|
||||
Args:
|
||||
binary : binary input data of BER-TLV length field
|
||||
|
@ -204,7 +221,8 @@ def bertlv_parse_tag_raw(binary:bytes) -> Tuple[int, bytes]:
|
|||
i += 1
|
||||
return tag, binary[i:]
|
||||
|
||||
def bertlv_parse_tag(binary:bytes) -> Tuple[dict, bytes]:
|
||||
|
||||
def bertlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]:
|
||||
"""Parse a single Tag value according to ITU-T X.690 8.1.2
|
||||
Args:
|
||||
binary : binary input data of BER-TLV length field
|
||||
|
@ -215,7 +233,7 @@ def bertlv_parse_tag(binary:bytes) -> Tuple[dict, bytes]:
|
|||
constructed = True if binary[0] & 0x20 else False
|
||||
tag = binary[0] & 0x1f
|
||||
if tag <= 30:
|
||||
return ({'class':cls, 'constructed':constructed, 'tag': tag}, binary[1:])
|
||||
return ({'class': cls, 'constructed': constructed, 'tag': tag}, binary[1:])
|
||||
else: # multi-byte tag
|
||||
tag = 0
|
||||
i = 1
|
||||
|
@ -225,12 +243,13 @@ def bertlv_parse_tag(binary:bytes) -> Tuple[dict, bytes]:
|
|||
tag <<= 7
|
||||
tag |= binary[i] & 0x7f
|
||||
i += 1
|
||||
return ({'class':cls, 'constructed':constructed, 'tag':tag}, binary[i:])
|
||||
return ({'class': cls, 'constructed': constructed, 'tag': tag}, binary[i:])
|
||||
|
||||
|
||||
def bertlv_encode_tag(t) -> bytes:
|
||||
"""Encode a single Tag value according to ITU-T X.690 8.1.2
|
||||
"""
|
||||
def get_top7_bits(inp:int) -> Tuple[int, int]:
|
||||
def get_top7_bits(inp: int) -> Tuple[int, int]:
|
||||
"""Get top 7 bits of integer. Returns those 7 bits as integer and the remaining LSBs."""
|
||||
remain_bits = inp.bit_length()
|
||||
if remain_bits >= 7:
|
||||
|
@ -272,7 +291,8 @@ def bertlv_encode_tag(t) -> bytes:
|
|||
break
|
||||
return tag_bytes
|
||||
|
||||
def bertlv_parse_len(binary:bytes) -> Tuple[int, bytes]:
|
||||
|
||||
def bertlv_parse_len(binary: bytes) -> Tuple[int, bytes]:
|
||||
"""Parse a single Length value according to ITU-T X.690 8.1.3;
|
||||
only the definite form is supported here.
|
||||
Args:
|
||||
|
@ -290,7 +310,8 @@ def bertlv_parse_len(binary:bytes) -> Tuple[int, bytes]:
|
|||
length |= binary[i]
|
||||
return (length, binary[1+num_len_oct:])
|
||||
|
||||
def bertlv_encode_len(length:int) -> bytes:
|
||||
|
||||
def bertlv_encode_len(length: int) -> bytes:
|
||||
"""Encode a single Length value according to ITU-T X.690 8.1.3;
|
||||
only the definite form is supported here.
|
||||
Args:
|
||||
|
@ -311,7 +332,8 @@ def bertlv_encode_len(length:int) -> bytes:
|
|||
else:
|
||||
raise ValueError("Length > 32bits not supported")
|
||||
|
||||
def bertlv_parse_one(binary:bytes) -> Tuple[dict, int, bytes, bytes]:
|
||||
|
||||
def bertlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]:
|
||||
"""Parse a single TLV IE at the start of the given binary data.
|
||||
Args:
|
||||
binary : binary input data of BER-TLV length field
|
||||
|
@ -325,7 +347,6 @@ def bertlv_parse_one(binary:bytes) -> Tuple[dict, int, bytes, bytes]:
|
|||
return (tagdict, length, value, remainder)
|
||||
|
||||
|
||||
|
||||
# IMSI encoded format:
|
||||
# For IMSI 0123456789ABCDE:
|
||||
#
|
||||
|
@ -341,14 +362,16 @@ def bertlv_parse_one(binary:bytes) -> Tuple[dict, int, bytes, bytes]:
|
|||
# Because of this, an odd length IMSI fits exactly into len(imsi) + 1 // 2 bytes, whereas an
|
||||
# even length IMSI only uses half of the last byte.
|
||||
|
||||
def enc_imsi(imsi:str):
|
||||
def enc_imsi(imsi: str):
|
||||
"""Converts a string IMSI into the encoded value of the EF"""
|
||||
l = half_round_up(len(imsi) + 1) # Required bytes - include space for odd/even indicator
|
||||
l = half_round_up(
|
||||
len(imsi) + 1) # Required bytes - include space for odd/even indicator
|
||||
oe = len(imsi) & 1 # Odd (1) / Even (0)
|
||||
ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe<<3)|1, rpad(imsi, 15)))
|
||||
ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe << 3) | 1, rpad(imsi, 15)))
|
||||
return ei
|
||||
|
||||
def dec_imsi(ef:Hexstr) -> Optional[str]:
|
||||
|
||||
def dec_imsi(ef: Hexstr) -> Optional[str]:
|
||||
"""Converts an EF value to the IMSI string representation"""
|
||||
if len(ef) < 4:
|
||||
return None
|
||||
|
@ -357,7 +380,7 @@ def dec_imsi(ef:Hexstr) -> Optional[str]:
|
|||
swapped = swap_nibbles(ef[2:]).rstrip('f')
|
||||
if len(swapped) < 1:
|
||||
return None
|
||||
oe = (int(swapped[0])>>3) & 1 # Odd (1) / Even (0)
|
||||
oe = (int(swapped[0]) >> 3) & 1 # Odd (1) / Even (0)
|
||||
if not oe:
|
||||
# if even, only half of last byte was used
|
||||
l = l-1
|
||||
|
@ -366,13 +389,16 @@ def dec_imsi(ef:Hexstr) -> Optional[str]:
|
|||
imsi = swapped[1:]
|
||||
return imsi
|
||||
|
||||
def dec_iccid(ef:Hexstr) -> str:
|
||||
|
||||
def dec_iccid(ef: Hexstr) -> str:
|
||||
return swap_nibbles(ef).strip('f')
|
||||
|
||||
def enc_iccid(iccid:str) -> Hexstr:
|
||||
|
||||
def enc_iccid(iccid: str) -> Hexstr:
|
||||
return swap_nibbles(rpad(iccid, 20))
|
||||
|
||||
def enc_plmn(mcc:Hexstr, mnc:Hexstr) -> Hexstr:
|
||||
|
||||
def enc_plmn(mcc: Hexstr, mnc: Hexstr) -> Hexstr:
|
||||
"""Converts integer MCC/MNC into 3 bytes for EF"""
|
||||
|
||||
# Make sure there are no excess whitespaces in the input
|
||||
|
@ -398,13 +424,15 @@ def enc_plmn(mcc:Hexstr, mnc:Hexstr) -> Hexstr:
|
|||
|
||||
return (mcc[1] + mcc[0]) + (mnc[2] + mcc[2]) + (mnc[1] + mnc[0])
|
||||
|
||||
def dec_plmn(threehexbytes:Hexstr) -> dict:
|
||||
res = {'mcc': "0", 'mnc': "0" }
|
||||
|
||||
def dec_plmn(threehexbytes: Hexstr) -> dict:
|
||||
res = {'mcc': "0", 'mnc': "0"}
|
||||
dec_mcc_from_plmn_str(threehexbytes)
|
||||
res['mcc'] = dec_mcc_from_plmn_str(threehexbytes)
|
||||
res['mnc'] = dec_mnc_from_plmn_str(threehexbytes)
|
||||
return res
|
||||
|
||||
|
||||
def dec_spn(ef):
|
||||
"""Obsolete, kept for API compatibility"""
|
||||
from ts_51_011 import EF_SPN
|
||||
|
@ -414,21 +442,25 @@ def dec_spn(ef):
|
|||
name = abstract_data['spn']
|
||||
return (name, show_in_hplmn, hide_in_oplmn)
|
||||
|
||||
def enc_spn(name:str, show_in_hplmn=False, hide_in_oplmn=False):
|
||||
|
||||
def enc_spn(name: str, show_in_hplmn=False, hide_in_oplmn=False):
|
||||
"""Obsolete, kept for API compatibility"""
|
||||
from ts_51_011 import EF_SPN
|
||||
abstract_data = {
|
||||
'hide_in_oplmn' : hide_in_oplmn,
|
||||
'show_in_hplmn' : show_in_hplmn,
|
||||
'spn' : name,
|
||||
'hide_in_oplmn': hide_in_oplmn,
|
||||
'show_in_hplmn': show_in_hplmn,
|
||||
'spn': name,
|
||||
}
|
||||
return EF_SPN().encode_hex(abstract_data)
|
||||
|
||||
|
||||
def hexstr_to_Nbytearr(s, nbytes):
|
||||
return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2)) ]
|
||||
return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2))]
|
||||
|
||||
# Accepts hex string representing three bytes
|
||||
def dec_mcc_from_plmn(plmn:Hexstr) -> int:
|
||||
|
||||
|
||||
def dec_mcc_from_plmn(plmn: Hexstr) -> int:
|
||||
ia = h2i(plmn)
|
||||
digit1 = ia[0] & 0x0F # 1st byte, LSB
|
||||
digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB
|
||||
|
@ -437,14 +469,16 @@ def dec_mcc_from_plmn(plmn:Hexstr) -> int:
|
|||
return 0xFFF # 4095
|
||||
return derive_mcc(digit1, digit2, digit3)
|
||||
|
||||
def dec_mcc_from_plmn_str(plmn:Hexstr) -> str:
|
||||
|
||||
def dec_mcc_from_plmn_str(plmn: Hexstr) -> str:
|
||||
digit1 = plmn[1] # 1st byte, LSB
|
||||
digit2 = plmn[0] # 1st byte, MSB
|
||||
digit3 = plmn[3] # 2nd byte, LSB
|
||||
res = digit1 + digit2 + digit3
|
||||
return res.upper().strip("F")
|
||||
|
||||
def dec_mnc_from_plmn(plmn:Hexstr) -> int:
|
||||
|
||||
def dec_mnc_from_plmn(plmn: Hexstr) -> int:
|
||||
ia = h2i(plmn)
|
||||
digit1 = ia[2] & 0x0F # 3rd byte, LSB
|
||||
digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB
|
||||
|
@ -453,14 +487,16 @@ def dec_mnc_from_plmn(plmn:Hexstr) -> int:
|
|||
return 0xFFF # 4095
|
||||
return derive_mnc(digit1, digit2, digit3)
|
||||
|
||||
def dec_mnc_from_plmn_str(plmn:Hexstr) -> str:
|
||||
|
||||
def dec_mnc_from_plmn_str(plmn: Hexstr) -> str:
|
||||
digit1 = plmn[5] # 3rd byte, LSB
|
||||
digit2 = plmn[4] # 3rd byte, MSB
|
||||
digit3 = plmn[2] # 2nd byte, MSB
|
||||
res = digit1 + digit2 + digit3
|
||||
return res.upper().strip("F")
|
||||
|
||||
def dec_act(twohexbytes:Hexstr) -> List[str]:
|
||||
|
||||
def dec_act(twohexbytes: Hexstr) -> List[str]:
|
||||
act_list = [
|
||||
{'bit': 15, 'name': "UTRAN"},
|
||||
{'bit': 14, 'name': "E-UTRAN"},
|
||||
|
@ -471,7 +507,7 @@ def dec_act(twohexbytes:Hexstr) -> List[str]:
|
|||
{'bit': 4, 'name': "cdma2000 1xRTT"},
|
||||
]
|
||||
ia = h2i(twohexbytes)
|
||||
u16t = (ia[0] << 8)|ia[1]
|
||||
u16t = (ia[0] << 8) | ia[1]
|
||||
sel = []
|
||||
for a in act_list:
|
||||
if u16t & (1 << a['bit']):
|
||||
|
@ -491,17 +527,21 @@ def dec_act(twohexbytes:Hexstr) -> List[str]:
|
|||
sel.append(a['name'])
|
||||
return sel
|
||||
|
||||
def dec_xplmn_w_act(fivehexbytes:Hexstr) -> Dict[str,Any]:
|
||||
|
||||
def dec_xplmn_w_act(fivehexbytes: Hexstr) -> Dict[str, Any]:
|
||||
res = {'mcc': "0", 'mnc': "0", 'act': []}
|
||||
plmn_chars = 6
|
||||
act_chars = 4
|
||||
plmn_str = fivehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
|
||||
act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars] # two bytes after first three bytes
|
||||
# first three bytes (six ascii hex chars)
|
||||
plmn_str = fivehexbytes[:plmn_chars]
|
||||
# two bytes after first three bytes
|
||||
act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars]
|
||||
res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
|
||||
res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
|
||||
res['act'] = dec_act(act_str)
|
||||
return res
|
||||
|
||||
|
||||
def format_xplmn_w_act(hexstr):
|
||||
s = ""
|
||||
for rec_data in hexstr_to_Nbytearr(hexstr, 5):
|
||||
|
@ -509,10 +549,12 @@ def format_xplmn_w_act(hexstr):
|
|||
if rec_info['mcc'] == "" and rec_info['mnc'] == "":
|
||||
rec_str = "unused"
|
||||
else:
|
||||
rec_str = "MCC: %s MNC: %s AcT: %s" % (rec_info['mcc'], rec_info['mnc'], ", ".join(rec_info['act']))
|
||||
rec_str = "MCC: %s MNC: %s AcT: %s" % (
|
||||
rec_info['mcc'], rec_info['mnc'], ", ".join(rec_info['act']))
|
||||
s += "\t%s # %s\n" % (rec_data, rec_str)
|
||||
return s
|
||||
|
||||
|
||||
def dec_loci(hexstr):
|
||||
res = {'tmsi': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0}
|
||||
res['tmsi'] = hexstr[:8]
|
||||
|
@ -522,8 +564,10 @@ def dec_loci(hexstr):
|
|||
res['status'] = h2i(hexstr[20:22])
|
||||
return res
|
||||
|
||||
|
||||
def dec_psloci(hexstr):
|
||||
res = {'p-tmsi': '', 'p-tmsi-sig': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'rac': '', 'status': 0}
|
||||
res = {'p-tmsi': '', 'p-tmsi-sig': '', 'mcc': 0,
|
||||
'mnc': 0, 'lac': '', 'rac': '', 'status': 0}
|
||||
res['p-tmsi'] = hexstr[:8]
|
||||
res['p-tmsi-sig'] = hexstr[8:14]
|
||||
res['mcc'] = dec_mcc_from_plmn(hexstr[14:20])
|
||||
|
@ -533,6 +577,7 @@ def dec_psloci(hexstr):
|
|||
res['status'] = h2i(hexstr[26:28])
|
||||
return res
|
||||
|
||||
|
||||
def dec_epsloci(hexstr):
|
||||
res = {'guti': '', 'mcc': 0, 'mnc': 0, 'tac': '', 'status': 0}
|
||||
res['guti'] = hexstr[:24]
|
||||
|
@ -543,26 +588,31 @@ def dec_epsloci(hexstr):
|
|||
res['status'] = h2i(hexstr[34:36])
|
||||
return res
|
||||
|
||||
def dec_xplmn(threehexbytes:Hexstr) -> dict:
|
||||
|
||||
def dec_xplmn(threehexbytes: Hexstr) -> dict:
|
||||
res = {'mcc': 0, 'mnc': 0, 'act': []}
|
||||
plmn_chars = 6
|
||||
plmn_str = threehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
|
||||
# first three bytes (six ascii hex chars)
|
||||
plmn_str = threehexbytes[:plmn_chars]
|
||||
res['mcc'] = dec_mcc_from_plmn(plmn_str)
|
||||
res['mnc'] = dec_mnc_from_plmn(plmn_str)
|
||||
return res
|
||||
|
||||
def format_xplmn(hexstr:Hexstr) -> str:
|
||||
|
||||
def format_xplmn(hexstr: Hexstr) -> str:
|
||||
s = ""
|
||||
for rec_data in hexstr_to_Nbytearr(hexstr, 3):
|
||||
rec_info = dec_xplmn(rec_data)
|
||||
if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
|
||||
rec_str = "unused"
|
||||
else:
|
||||
rec_str = "MCC: %03d MNC: %03d" % (rec_info['mcc'], rec_info['mnc'])
|
||||
rec_str = "MCC: %03d MNC: %03d" % (
|
||||
rec_info['mcc'], rec_info['mnc'])
|
||||
s += "\t%s # %s\n" % (rec_data, rec_str)
|
||||
return s
|
||||
|
||||
def derive_milenage_opc(ki_hex:Hexstr, op_hex:Hexstr) -> Hexstr:
|
||||
|
||||
def derive_milenage_opc(ki_hex: Hexstr, op_hex: Hexstr) -> Hexstr:
|
||||
"""
|
||||
Run the milenage algorithm to calculate OPC from Ki and OP
|
||||
"""
|
||||
|
@ -578,15 +628,18 @@ def derive_milenage_opc(ki_hex:Hexstr, op_hex:Hexstr) -> Hexstr:
|
|||
opc_bytes = aes.encrypt(op_bytes)
|
||||
return b2h(strxor(opc_bytes, op_bytes))
|
||||
|
||||
|
||||
def calculate_luhn(cc) -> int:
|
||||
"""
|
||||
Calculate Luhn checksum used in e.g. ICCID and IMEI
|
||||
"""
|
||||
num = list(map(int, str(cc)))
|
||||
check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10
|
||||
check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10))
|
||||
for d in num[::-2]]) % 10
|
||||
return 0 if check_digit == 10 else check_digit
|
||||
|
||||
def mcc_from_imsi(imsi:str) -> Optional[str]:
|
||||
|
||||
def mcc_from_imsi(imsi: str) -> Optional[str]:
|
||||
"""
|
||||
Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
|
||||
"""
|
||||
|
@ -598,7 +651,8 @@ def mcc_from_imsi(imsi:str) -> Optional[str]:
|
|||
else:
|
||||
return None
|
||||
|
||||
def mnc_from_imsi(imsi:str, long:bool=False) -> Optional[str]:
|
||||
|
||||
def mnc_from_imsi(imsi: str, long: bool = False) -> Optional[str]:
|
||||
"""
|
||||
Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
|
||||
"""
|
||||
|
@ -613,7 +667,8 @@ def mnc_from_imsi(imsi:str, long:bool=False) -> Optional[str]:
|
|||
else:
|
||||
return None
|
||||
|
||||
def derive_mcc(digit1:int, digit2:int, digit3:int) -> int:
|
||||
|
||||
def derive_mcc(digit1: int, digit2: int, digit3: int) -> int:
|
||||
"""
|
||||
Derive decimal representation of the MCC (Mobile Country Code)
|
||||
from three given digits.
|
||||
|
@ -630,7 +685,8 @@ def derive_mcc(digit1:int, digit2:int, digit3:int) -> int:
|
|||
|
||||
return mcc
|
||||
|
||||
def derive_mnc(digit1:int, digit2:int, digit3:int=0x0f) -> int:
|
||||
|
||||
def derive_mnc(digit1: int, digit2: int, digit3: int = 0x0f) -> int:
|
||||
"""
|
||||
Derive decimal representation of the MNC (Mobile Network Code)
|
||||
from two or (optionally) three given digits.
|
||||
|
@ -650,7 +706,8 @@ def derive_mnc(digit1:int, digit2:int, digit3:int=0x0f) -> int:
|
|||
|
||||
return mnc
|
||||
|
||||
def dec_msisdn(ef_msisdn:Hexstr) -> Optional[Tuple[int,int,Optional[str]]]:
|
||||
|
||||
def dec_msisdn(ef_msisdn: Hexstr) -> Optional[Tuple[int, int, Optional[str]]]:
|
||||
"""
|
||||
Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
|
||||
See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
|
||||
|
@ -673,7 +730,8 @@ def dec_msisdn(ef_msisdn:Hexstr) -> Optional[Tuple[int,int,Optional[str]]]:
|
|||
if bcd_len == 0xff:
|
||||
return None
|
||||
elif bcd_len > 11 or bcd_len < 1:
|
||||
raise ValueError("Length of MSISDN (%d bytes) is out of range" % bcd_len)
|
||||
raise ValueError(
|
||||
"Length of MSISDN (%d bytes) is out of range" % bcd_len)
|
||||
|
||||
# Parse ToN / NPI
|
||||
ton = (msisdn_lhv[1] >> 4) & 0x07
|
||||
|
@ -691,7 +749,8 @@ def dec_msisdn(ef_msisdn:Hexstr) -> Optional[Tuple[int,int,Optional[str]]]:
|
|||
|
||||
return (npi, ton, msisdn)
|
||||
|
||||
def enc_msisdn(msisdn:str, npi:int=0x01, ton:int=0x03) -> Hexstr:
|
||||
|
||||
def enc_msisdn(msisdn: str, npi: int = 0x01, ton: int = 0x03) -> Hexstr:
|
||||
"""
|
||||
Encode MSISDN as LHV so it can be stored to EF.MSISDN.
|
||||
See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
|
||||
|
@ -742,7 +801,7 @@ def dec_st(st, table="sim") -> str:
|
|||
from pySim.ts_51_011 import EF_SST_map
|
||||
lookup_map = EF_SST_map
|
||||
|
||||
st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
|
||||
st_bytes = [st[i:i+2] for i in range(0, len(st), 2)]
|
||||
|
||||
avail_st = ""
|
||||
# Get each byte and check for available services
|
||||
|
@ -753,14 +812,16 @@ def dec_st(st, table="sim") -> str:
|
|||
# MSB - Service (8i+8)
|
||||
# LSB - Service (8i+1)
|
||||
for j in range(1, 9):
|
||||
if byte&0x01 == 0x01 and ((8*i) + j in lookup_map):
|
||||
if byte & 0x01 == 0x01 and ((8*i) + j in lookup_map):
|
||||
# Byte X contains info about Services num (8X-7) to num (8X)
|
||||
# bit = 1: service available
|
||||
# bit = 0: service not available
|
||||
avail_st += '\tService %d - %s\n' % ((8*i) + j, lookup_map[(8*i) + j])
|
||||
avail_st += '\tService %d - %s\n' % (
|
||||
(8*i) + j, lookup_map[(8*i) + j])
|
||||
byte = byte >> 1
|
||||
return avail_st
|
||||
|
||||
|
||||
def first_TLV_parser(bytelist):
|
||||
'''
|
||||
first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
|
||||
|
@ -779,6 +840,7 @@ def first_TLV_parser(bytelist):
|
|||
Val = bytelist[2:2+Len]
|
||||
return (Tag, Len, Val)
|
||||
|
||||
|
||||
def TLV_parser(bytelist):
|
||||
'''
|
||||
TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
|
||||
|
@ -792,14 +854,15 @@ def TLV_parser(bytelist):
|
|||
if T == 0xFF:
|
||||
# padding bytes
|
||||
break
|
||||
ret.append( (T, L, V) )
|
||||
ret.append((T, L, V))
|
||||
# need to manage length of L
|
||||
if L > 0xFE:
|
||||
bytelist = bytelist[ L+4 : ]
|
||||
bytelist = bytelist[L+4:]
|
||||
else:
|
||||
bytelist = bytelist[ L+2 : ]
|
||||
bytelist = bytelist[L+2:]
|
||||
return ret
|
||||
|
||||
|
||||
def enc_st(st, service, state=1):
|
||||
"""
|
||||
Encodes the EF S/U/IST/EST and returns the updated Service Table
|
||||
|
@ -815,7 +878,7 @@ def enc_st(st, service, state=1):
|
|||
Default values:
|
||||
- state: 1 - Sets the particular Service bit to 1
|
||||
"""
|
||||
st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
|
||||
st_bytes = [st[i:i+2] for i in range(0, len(st), 2)]
|
||||
|
||||
s = ""
|
||||
# Check whether the requested service is present in each byte
|
||||
|
@ -832,9 +895,9 @@ def enc_st(st, service, state=1):
|
|||
for j in range(1, 9):
|
||||
mod_byte = mod_byte >> 1
|
||||
if service == (8*i) + j:
|
||||
mod_byte = state == 1 and mod_byte|0x80 or mod_byte&0x7f
|
||||
mod_byte = state == 1 and mod_byte | 0x80 or mod_byte & 0x7f
|
||||
else:
|
||||
mod_byte = byte&0x01 == 0x01 and mod_byte|0x80 or mod_byte&0x7f
|
||||
mod_byte = byte & 0x01 == 0x01 and mod_byte | 0x80 or mod_byte & 0x7f
|
||||
byte = byte >> 1
|
||||
|
||||
s += ('%02x' % (mod_byte))
|
||||
|
@ -843,6 +906,7 @@ def enc_st(st, service, state=1):
|
|||
|
||||
return s
|
||||
|
||||
|
||||
def dec_addr_tlv(hexstr):
|
||||
"""
|
||||
Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm.
|
||||
|
@ -873,12 +937,12 @@ def dec_addr_tlv(hexstr):
|
|||
addr_type = tlv[2][0]
|
||||
# TODO: Support parsing of IPv6
|
||||
# Address Type: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), other (Reserved)
|
||||
if addr_type == 0x00: #FQDN
|
||||
if addr_type == 0x00: # FQDN
|
||||
# Skip address tye byte i.e. first byte in value list
|
||||
content = tlv[2][1:]
|
||||
return (i2s(content), '00')
|
||||
|
||||
elif addr_type == 0x01: #IPv4
|
||||
elif addr_type == 0x01: # IPv4
|
||||
# Skip address tye byte i.e. first byte in value list
|
||||
# Skip the unused byte in Octect 4 after address type byte as per 3GPP TS 31.102
|
||||
ipv4 = tlv[2][2:]
|
||||
|
@ -889,6 +953,7 @@ def dec_addr_tlv(hexstr):
|
|||
|
||||
return (None, None)
|
||||
|
||||
|
||||
def enc_addr_tlv(addr, addr_type='00'):
|
||||
"""
|
||||
Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm.
|
||||
|
@ -901,10 +966,10 @@ def enc_addr_tlv(addr, addr_type='00'):
|
|||
s = ""
|
||||
|
||||
# TODO: Encoding of IPv6 address
|
||||
if addr_type == '00': #FQDN
|
||||
if addr_type == '00': # FQDN
|
||||
hex_str = s2h(addr)
|
||||
s += '80' + ('%02x' % ((len(hex_str)//2)+1)) + '00' + hex_str
|
||||
elif addr_type == '01': #IPv4
|
||||
elif addr_type == '01': # IPv4
|
||||
ipv4_list = addr.split('.')
|
||||
ipv4_str = ""
|
||||
for i in ipv4_list:
|
||||
|
@ -916,7 +981,8 @@ def enc_addr_tlv(addr, addr_type='00'):
|
|||
|
||||
return s
|
||||
|
||||
def is_hex(string:str, minlen:int=2, maxlen:Optional[int]=None) -> bool:
|
||||
|
||||
def is_hex(string: str, minlen: int = 2, maxlen: Optional[int] = None) -> bool:
|
||||
"""
|
||||
Check if a string is a valid hexstring
|
||||
"""
|
||||
|
@ -938,7 +1004,8 @@ def is_hex(string:str, minlen:int=2, maxlen:Optional[int]=None) -> bool:
|
|||
except:
|
||||
return False
|
||||
|
||||
def sanitize_pin_adm(pin_adm, pin_adm_hex = None) -> Hexstr:
|
||||
|
||||
def sanitize_pin_adm(pin_adm, pin_adm_hex=None) -> Hexstr:
|
||||
"""
|
||||
The ADM pin can be supplied either in its hexadecimal form or as
|
||||
ascii string. This function checks the supplied opts parameter and
|
||||
|
@ -948,7 +1015,7 @@ def sanitize_pin_adm(pin_adm, pin_adm_hex = None) -> Hexstr:
|
|||
|
||||
if pin_adm is not None:
|
||||
if len(pin_adm) <= 8:
|
||||
pin_adm = ''.join(['%02x'%(ord(x)) for x in pin_adm])
|
||||
pin_adm = ''.join(['%02x' % (ord(x)) for x in pin_adm])
|
||||
pin_adm = rpad(pin_adm, 16)
|
||||
|
||||
else:
|
||||
|
@ -961,12 +1028,15 @@ def sanitize_pin_adm(pin_adm, pin_adm_hex = None) -> Hexstr:
|
|||
try:
|
||||
try_encode = h2b(pin_adm)
|
||||
except ValueError:
|
||||
raise ValueError("PIN-ADM needs to be hex encoded using this option")
|
||||
raise ValueError(
|
||||
"PIN-ADM needs to be hex encoded using this option")
|
||||
else:
|
||||
raise ValueError("PIN-ADM needs to be exactly 16 digits (hex encoded)")
|
||||
raise ValueError(
|
||||
"PIN-ADM needs to be exactly 16 digits (hex encoded)")
|
||||
|
||||
return pin_adm
|
||||
|
||||
|
||||
def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='00'):
|
||||
"""
|
||||
Encode ePDGSelection so it can be stored at EF.ePDGSelection or EF.ePDGSelectionEm.
|
||||
|
@ -983,6 +1053,7 @@ def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='
|
|||
content = rpad(content, len(hexstr))
|
||||
return content
|
||||
|
||||
|
||||
def dec_ePDGSelection(sixhexbytes):
|
||||
"""
|
||||
Decode ePDGSelection to get EF.ePDGSelection or EF.ePDGSelectionEm.
|
||||
|
@ -996,15 +1067,18 @@ def dec_ePDGSelection(sixhexbytes):
|
|||
# first three bytes (six ascii hex chars)
|
||||
plmn_str = sixhexbytes[:plmn_chars]
|
||||
# two bytes after first three bytes
|
||||
epdg_priority_str = sixhexbytes[plmn_chars:plmn_chars + epdg_priority_chars]
|
||||
epdg_priority_str = sixhexbytes[plmn_chars:plmn_chars +
|
||||
epdg_priority_chars]
|
||||
# one byte after first five bytes
|
||||
epdg_fqdn_format_str = sixhexbytes[plmn_chars + epdg_priority_chars:plmn_chars + epdg_priority_chars + epdg_fqdn_format_chars]
|
||||
epdg_fqdn_format_str = sixhexbytes[plmn_chars +
|
||||
epdg_priority_chars:plmn_chars + epdg_priority_chars + epdg_fqdn_format_chars]
|
||||
res['mcc'] = dec_mcc_from_plmn(plmn_str)
|
||||
res['mnc'] = dec_mnc_from_plmn(plmn_str)
|
||||
res['epdg_priority'] = epdg_priority_str
|
||||
res['epdg_fqdn_format'] = epdg_fqdn_format_str == '00' and 'Operator Identifier FQDN' or 'Location based FQDN'
|
||||
return res
|
||||
|
||||
|
||||
def format_ePDGSelection(hexstr):
|
||||
ePDGSelection_info_tag_chars = 2
|
||||
ePDGSelection_info_tag_str = hexstr[:2]
|
||||
|
@ -1031,17 +1105,20 @@ def format_ePDGSelection(hexstr):
|
|||
|
||||
content_str = hexstr[ePDGSelection_info_tag_chars+len_chars:]
|
||||
# Right pad to prevent index out of range - multiple of 6 bytes
|
||||
content_str = rpad(content_str, len(content_str) + (12 - (len(content_str) % 12)))
|
||||
content_str = rpad(content_str, len(content_str) +
|
||||
(12 - (len(content_str) % 12)))
|
||||
for rec_data in hexstr_to_Nbytearr(content_str, 6):
|
||||
rec_info = dec_ePDGSelection(rec_data)
|
||||
if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
|
||||
rec_str = "unused"
|
||||
else:
|
||||
rec_str = "MCC: %03d MNC: %03d ePDG Priority: %s ePDG FQDN format: %s" % \
|
||||
(rec_info['mcc'], rec_info['mnc'], rec_info['epdg_priority'], rec_info['epdg_fqdn_format'])
|
||||
(rec_info['mcc'], rec_info['mnc'],
|
||||
rec_info['epdg_priority'], rec_info['epdg_fqdn_format'])
|
||||
s += "\t%s # %s\n" % (rec_data, rec_str)
|
||||
return s
|
||||
|
||||
|
||||
def get_addr_type(addr):
|
||||
"""
|
||||
Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
|
||||
|
@ -1093,7 +1170,8 @@ def get_addr_type(addr):
|
|||
|
||||
return None
|
||||
|
||||
def sw_match(sw:str, pattern:str) -> bool:
|
||||
|
||||
def sw_match(sw: str, pattern: str) -> bool:
|
||||
"""Match given SW against given pattern."""
|
||||
# Create a masked version of the returned status word
|
||||
sw_lower = sw.lower()
|
||||
|
@ -1108,8 +1186,9 @@ def sw_match(sw:str, pattern:str) -> bool:
|
|||
# Compare the masked version against the pattern
|
||||
return sw_masked == pattern
|
||||
|
||||
def tabulate_str_list(str_list, width:int = 79, hspace:int = 2, lspace:int = 1,
|
||||
align_left:bool = True) -> str:
|
||||
|
||||
def tabulate_str_list(str_list, width: int = 79, hspace: int = 2, lspace: int = 1,
|
||||
align_left: bool = True) -> str:
|
||||
"""Pretty print a list of strings into a tabulated form.
|
||||
|
||||
Args:
|
||||
|
@ -1140,17 +1219,21 @@ def tabulate_str_list(str_list, width:int = 79, hspace:int = 2, lspace:int = 1,
|
|||
table.append(format_str_row % tuple(str_list_row))
|
||||
return '\n'.join(table)
|
||||
|
||||
|
||||
def auto_int(x):
|
||||
"""Helper function for argparse to accept hexadecimal integers."""
|
||||
return int(x, 0)
|
||||
|
||||
|
||||
class JsonEncoder(json.JSONEncoder):
|
||||
"""Extend the standard library JSONEncoder with support for more types."""
|
||||
|
||||
def default(self, o):
|
||||
if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
|
||||
return b2h(o)
|
||||
return json.JSONEncoder.default(self, o)
|
||||
|
||||
|
||||
def boxed_heading_str(heading, width=80):
|
||||
"""Generate a string that contains a boxed heading."""
|
||||
# Auto-enlarge box if heading exceeds length
|
||||
|
@ -1164,13 +1247,13 @@ def boxed_heading_str(heading, width=80):
|
|||
return res
|
||||
|
||||
|
||||
|
||||
class DataObject(abc.ABC):
|
||||
"""A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
|
||||
simply has any number of different TLVs that may occur in any order at any point, ISO 7816
|
||||
has the habit of specifying TLV data but with very spcific ordering, or specific choices of
|
||||
tags at specific points in a stream. This class tries to represent this."""
|
||||
def __init__(self, name:str, desc:str = None, tag:int = None):
|
||||
|
||||
def __init__(self, name: str, desc: str = None, tag: int = None):
|
||||
"""
|
||||
Args:
|
||||
name: A brief, all-lowercase, underscore separated string identifier
|
||||
|
@ -1214,7 +1297,7 @@ class DataObject(abc.ABC):
|
|||
return {self.name: self.decoded}
|
||||
|
||||
@abc.abstractmethod
|
||||
def from_bytes(self, do:bytes):
|
||||
def from_bytes(self, do: bytes):
|
||||
"""Parse the value part of the DO into the internal state of this instance.
|
||||
Args:
|
||||
do : binary encoded bytes
|
||||
|
@ -1227,7 +1310,7 @@ class DataObject(abc.ABC):
|
|||
binary bytes encoding the internal state
|
||||
"""
|
||||
|
||||
def from_tlv(self, do:bytes) -> bytes:
|
||||
def from_tlv(self, do: bytes) -> bytes:
|
||||
"""Parse binary TLV representation into internal state. The resulting decoded
|
||||
representation is _not_ returned, but just internalized in the object instance!
|
||||
Args:
|
||||
|
@ -1236,7 +1319,8 @@ class DataObject(abc.ABC):
|
|||
bytes remaining at end of 'do' after parsing one TLV/DO.
|
||||
"""
|
||||
if do[0] != self.tag:
|
||||
raise ValueError('%s: Can only decode tag 0x%02x' % (self, self.tag))
|
||||
raise ValueError('%s: Can only decode tag 0x%02x' %
|
||||
(self, self.tag))
|
||||
length = do[1]
|
||||
val = do[2:2+length]
|
||||
self.from_bytes(val)
|
||||
|
@ -1252,7 +1336,7 @@ class DataObject(abc.ABC):
|
|||
return bytes(self._compute_tag()) + bytes(len(val)) + val
|
||||
|
||||
# 'codec' interface
|
||||
def decode(self, binary:bytes) -> Tuple[dict, bytes]:
|
||||
def decode(self, binary: bytes) -> Tuple[dict, bytes]:
|
||||
"""Decode a single DOs from the input data.
|
||||
Args:
|
||||
binary : binary bytes of encoded data
|
||||
|
@ -1270,13 +1354,15 @@ class DataObject(abc.ABC):
|
|||
def encode(self) -> bytes:
|
||||
return self.to_tlv()
|
||||
|
||||
|
||||
class TL0_DataObject(DataObject):
|
||||
"""Data Object that has Tag, Len=0 and no Value part."""
|
||||
def __init__(self, name:str, desc:str, tag:int, val=None):
|
||||
|
||||
def __init__(self, name: str, desc: str, tag: int, val=None):
|
||||
super().__init__(name, desc, tag)
|
||||
self.val = val
|
||||
|
||||
def from_bytes(self, binary:bytes):
|
||||
def from_bytes(self, binary: bytes):
|
||||
if len(binary) != 0:
|
||||
raise ValueError
|
||||
self.decoded = self.val
|
||||
|
@ -1289,14 +1375,15 @@ class DataObjectCollection:
|
|||
"""A DataObjectCollection consits of multiple Data Objects identified by their tags.
|
||||
A given encoded DO may contain any of them in any order, and may contain multiple instances
|
||||
of each DO."""
|
||||
def __init__(self, name:str, desc:str = None, members=None):
|
||||
|
||||
def __init__(self, name: str, desc: str = None, members=None):
|
||||
self.name = name
|
||||
self.desc = desc
|
||||
self.members = members or []
|
||||
self.members_by_tag = {}
|
||||
self.members_by_name = {}
|
||||
self.members_by_tag = { m.tag:m for m in members }
|
||||
self.members_by_name = { m.name:m for m in members }
|
||||
self.members_by_tag = {m.tag: m for m in members}
|
||||
self.members_by_name = {m.name: m for m in members}
|
||||
|
||||
def __str__(self) -> str:
|
||||
member_strs = [str(x) for x in self.members]
|
||||
|
@ -1319,7 +1406,7 @@ class DataObjectCollection:
|
|||
raise TypeError
|
||||
|
||||
# 'codec' interface
|
||||
def decode(self, binary:bytes) -> Tuple[List, bytes]:
|
||||
def decode(self, binary: bytes) -> Tuple[List, bytes]:
|
||||
"""Decode any number of DOs from the collection until the end of the input data,
|
||||
or uninitialized memory (0xFF) is found.
|
||||
Args:
|
||||
|
@ -1352,10 +1439,12 @@ class DataObjectCollection:
|
|||
res.append(obj.to_tlv())
|
||||
return res
|
||||
|
||||
|
||||
class DataObjectChoice(DataObjectCollection):
|
||||
"""One Data Object from within a choice, identified by its tag.
|
||||
This means that exactly one member of the choice must occur, and which one occurs depends
|
||||
on the tag."""
|
||||
|
||||
def __add__(self, other):
|
||||
"""We overload the add operator here to avoid inheriting it from DataObjecCollection."""
|
||||
raise TypeError
|
||||
|
@ -1373,7 +1462,7 @@ class DataObjectChoice(DataObjectCollection):
|
|||
raise TypeError
|
||||
|
||||
# 'codec' interface
|
||||
def decode(self, binary:bytes) -> Tuple[dict, bytes]:
|
||||
def decode(self, binary: bytes) -> Tuple[dict, bytes]:
|
||||
"""Decode a single DOs from the choice based on the tag.
|
||||
Args:
|
||||
binary : binary bytes of encoded data
|
||||
|
@ -1395,12 +1484,14 @@ class DataObjectChoice(DataObjectCollection):
|
|||
obj = self.members_by_name(decoded[0])
|
||||
return obj.to_tlv()
|
||||
|
||||
|
||||
class DataObjectSequence:
|
||||
"""A sequence of DataObjects or DataObjectChoices. This allows us to express a certain
|
||||
ordered sequence of DOs or choices of DOs that have to appear as per the specification.
|
||||
By wrapping them into this formal DataObjectSequence, we can offer convenience methods
|
||||
for encoding or decoding an entire sequence."""
|
||||
def __init__(self, name:str, desc:str=None, sequence=None):
|
||||
|
||||
def __init__(self, name: str, desc: str = None, sequence=None):
|
||||
self.sequence = sequence or []
|
||||
self.name = name
|
||||
self.desc = desc
|
||||
|
@ -1423,7 +1514,7 @@ class DataObjectSequence:
|
|||
return DataObjectSequence(self.name, self.desc, self.sequence + other.sequence)
|
||||
|
||||
# 'codec' interface
|
||||
def decode(self, binary:bytes) -> Tuple[list, bytes]:
|
||||
def decode(self, binary: bytes) -> Tuple[list, bytes]:
|
||||
"""Decode a sequence by calling the decoder of each element in the sequence.
|
||||
Args:
|
||||
binary : binary bytes of encoded data
|
||||
|
@ -1439,7 +1530,7 @@ class DataObjectSequence:
|
|||
return (res, remainder)
|
||||
|
||||
# 'codec' interface
|
||||
def decode_multi(self, do:bytes) -> Tuple[list, bytes]:
|
||||
def decode_multi(self, do: bytes) -> Tuple[list, bytes]:
|
||||
"""Decode multiple occurrences of the sequence from the binary input data.
|
||||
Args:
|
||||
do : binary input data to be decoded
|
||||
|
@ -1469,8 +1560,10 @@ class DataObjectSequence:
|
|||
i += 1
|
||||
return encoded
|
||||
|
||||
|
||||
class CardCommand:
|
||||
"""A single card command / instruction."""
|
||||
|
||||
def __init__(self, name, ins, cla_list=None, desc=None):
|
||||
self.name = name
|
||||
self.ins = ins
|
||||
|
@ -1503,9 +1596,10 @@ class CardCommand:
|
|||
|
||||
class CardCommandSet:
|
||||
"""A set of card instructions, typically specified within one spec."""
|
||||
|
||||
def __init__(self, name, cmds=[]):
|
||||
self.name = name
|
||||
self.cmds = { c.ins : c for c in cmds }
|
||||
self.cmds = {c.ins: c for c in cmds}
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
@ -1523,7 +1617,8 @@ class CardCommandSet:
|
|||
for c in other.cmds.keys():
|
||||
self.cmds[c] = other.cmds[c]
|
||||
else:
|
||||
raise ValueError('%s: Unsupported type to add operator: %s' % (self, other))
|
||||
raise ValueError(
|
||||
'%s: Unsupported type to add operator: %s' % (self, other))
|
||||
|
||||
def lookup(self, ins, cla=None):
|
||||
"""look-up the command within the CommandSet."""
|
||||
|
@ -1535,6 +1630,7 @@ class CardCommandSet:
|
|||
return None
|
||||
return cmd
|
||||
|
||||
|
||||
def all_subclasses(cls) -> set:
|
||||
"""Recursively get all subclasses of a specified class"""
|
||||
return set(cls.__subclasses__()).union([s for c in cls.__subclasses__() for s in all_subclasses(c)])
|
||||
|
|
Loading…
Reference in New Issue