split pySim/legacy/{cards,utils} from pySim/{cards,utils}

There are some functions / classes which are only needed by the legacy
tools pySim-{read,prog}, bypassing our modern per-file transcoder
classes.  Let's move this code to the pySim/legacy sub-directory,
rendering pySim.legacy.* module names.

The long-term goal is to get rid of those and have all code use the
modern pySim/filesystem classes for reading/decoding/encoding/writing
any kind of data on cards.

Change-Id: Ia8cf831929730c48f90679a83d69049475cc5077
diff --git a/pySim/legacy/__init__.py b/pySim/legacy/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pySim/legacy/__init__.py
diff --git a/pySim/legacy/cards.py b/pySim/legacy/cards.py
new file mode 100644
index 0000000..e63b044
--- /dev/null
+++ b/pySim/legacy/cards.py
@@ -0,0 +1,1666 @@
+################################################################################
+# LEGACY
+################################################################################
+
+import abc
+from smartcard.util import toBytes
+from pytlv.TLV import *
+
+from pySim.cards import SimCardBase, UiccCardBase
+from pySim.utils import dec_iccid, enc_iccid, dec_imsi, enc_imsi, dec_msisdn, enc_msisdn
+from pySim.utils import dec_addr_tlv, enc_addr_tlv
+from pySim.utils import enc_plmn, get_addr_type
+from pySim.utils import is_hex, h2b, b2h, h2s, s2h, lpad, rpad
+from pySim.legacy.utils import enc_ePDGSelection, format_xplmn_w_act, format_xplmn, dec_st, enc_st
+from pySim.legacy.utils import format_ePDGSelection
+
+from pySim.ts_51_011 import EF, DF, EF_AD, EF_SPN
+from pySim.ts_31_102 import EF_USIM_ADF_map
+from pySim.ts_31_103 import EF_ISIM_ADF_map
+
+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
+    contains the hexadecimal representation and the original address
+    string (addr)
+    """
+    res = ""
+    if addr_type == '00':  # FQDN
+        res += "\t%s # %s\n" % (s2h(addr), addr)
+    elif addr_type == '01':  # IPv4
+        octets = addr.split(".")
+        addr_hex = ""
+        for o in octets:
+            addr_hex += ("%02x" % int(o))
+        res += "\t%s # %s\n" % (addr_hex, addr)
+    return res
+
+
+
+class SimCard(SimCardBase):
+    """Higher-layer class that is used *only* by legacy pySim-{prog,read}."""
+
+    def __init__(self, scc):
+        self._adm_chv_num = 4
+        super().__init__(scc)
+
+    def read_binary(self, ef, length=None, offset=0):
+        ef_path = ef in EF and EF[ef] or ef
+        return self._scc.read_binary(ef_path, length, offset)
+
+    def read_record(self, ef, rec_no):
+        ef_path = ef in EF and EF[ef] or ef
+        return self._scc.read_record(ef_path, rec_no)
+
+    def verify_adm(self, key):
+        """Authenticate with ADM key"""
+        (res, sw) = self._scc.verify_chv(self._adm_chv_num, key)
+        return sw
+
+    def update_iccid(self, iccid):
+        data, sw = self._scc.update_binary(EF['ICCID'], enc_iccid(iccid))
+        return sw
+
+    def read_imsi(self):
+        (res, sw) = self._scc.read_binary(EF['IMSI'])
+        if sw == '9000':
+            return (dec_imsi(res), sw)
+        else:
+            return (None, sw)
+
+    def update_imsi(self, imsi):
+        data, sw = self._scc.update_binary(EF['IMSI'], enc_imsi(imsi))
+        return sw
+
+    def update_acc(self, acc):
+        data, sw = self._scc.update_binary(EF['ACC'], lpad(acc, 4, c='0'))
+        return sw
+
+    def read_hplmn_act(self):
+        (res, sw) = self._scc.read_binary(EF['HPLMNAcT'])
+        if sw == '9000':
+            return (format_xplmn_w_act(res), sw)
+        else:
+            return (None, sw)
+
+    def update_hplmn_act(self, mcc, mnc, access_tech='FFFF'):
+        """
+        Update Home PLMN with access technology bit-field
+
+        See Section "10.3.37 EFHPLMNwAcT (HPLMN Selector with Access Technology)"
+        in ETSI TS 151 011 for the details of the access_tech field coding.
+        Some common values:
+        access_tech = '0080' # Only GSM is selected
+        access_tech = 'FFFF' # All technologies selected, even Reserved for Future Use ones
+        """
+        # get size and write EF.HPLMNwAcT
+        data = self._scc.read_binary(EF['HPLMNwAcT'], length=None, offset=0)
+        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))
+        return sw
+
+    def read_oplmn_act(self):
+        (res, sw) = self._scc.read_binary(EF['OPLMNwAcT'])
+        if sw == '9000':
+            return (format_xplmn_w_act(res), sw)
+        else:
+            return (None, sw)
+
+    def update_oplmn_act(self, mcc, mnc, access_tech='FFFF'):
+        """get size and write EF.OPLMNwAcT, See note in update_hplmn_act()"""
+        data = self._scc.read_binary(EF['OPLMNwAcT'], length=None, offset=0)
+        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))
+        return sw
+
+    def read_plmn_act(self):
+        (res, sw) = self._scc.read_binary(EF['PLMNwAcT'])
+        if sw == '9000':
+            return (format_xplmn_w_act(res), sw)
+        else:
+            return (None, sw)
+
+    def update_plmn_act(self, mcc, mnc, access_tech='FFFF'):
+        """get size and write EF.PLMNwAcT, See note in update_hplmn_act()"""
+        data = self._scc.read_binary(EF['PLMNwAcT'], length=None, offset=0)
+        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))
+        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))
+        return sw
+
+    def update_smsp(self, smsp):
+        data, sw = self._scc.update_record(EF['SMSP'], 1, rpad(smsp, 84))
+        return sw
+
+    def update_ad(self, mnc=None, opmode=None, ofm=None, path=EF['AD']):
+        """
+        Update Administrative Data (AD)
+
+        See Sec. "4.2.18 EF_AD (Administrative Data)"
+        in 3GPP TS 31.102 for the details of the EF_AD contents.
+
+        Set any parameter to None to keep old value(s) on card.
+
+        Parameters:
+                mnc (str): MNC of IMSI
+                opmode (Hex-str, 1 Byte): MS Operation Mode
+                ofm (Hex-str, 1 Byte): Operational Feature Monitor (OFM) aka Ciphering Indicator
+                path (optional list with file path e.g. ['3f00', '7f20', '6fad'])
+
+        Returns:
+                str: Return code of write operation
+        """
+
+        ad = EF_AD()
+
+        # read from card
+        raw_hex_data, sw = self._scc.read_binary(
+            path, length=None, offset=0)
+        abstract_data = ad.decode_hex(raw_hex_data)
+
+        # perform updates
+        if mnc and abstract_data['extensions']:
+            # Note: Since we derive the length of the MNC by the string length
+            # of the mnc parameter, the caller must ensure that mnc has the
+            # correct length and is padded with zeros (if necessary).
+            mnclen = len(str(mnc))
+            if mnclen > 3 or mnclen < 2:
+                raise RuntimeError('invalid length of mnc "{}", expecting 2 or 3 digits'.format(mnc))
+            abstract_data['extensions']['mnc_len'] = mnclen
+        if opmode:
+            opmode_num = int(opmode, 16)
+            if opmode_num in [int(v) for v in EF_AD.OP_MODE]:
+                abstract_data['ms_operation_mode'] = opmode_num
+            else:
+                raise RuntimeError('invalid opmode "{}"'.format(opmode))
+        if ofm:
+            abstract_data['ofm'] = bool(int(ofm, 16))
+
+        # write to card
+        raw_hex_data = ad.encode_hex(abstract_data)
+        data, sw = self._scc.update_binary(path, raw_hex_data)
+        return sw
+
+    def read_spn(self):
+        (content, sw) = self._scc.read_binary(EF['SPN'])
+        if sw == '9000':
+            abstract_data = EF_SPN().decode_hex(content)
+            show_in_hplmn = abstract_data['show_in_hplmn']
+            hide_in_oplmn = abstract_data['hide_in_oplmn']
+            name = abstract_data['spn']
+            return ((name, show_in_hplmn, hide_in_oplmn), sw)
+        else:
+            return (None, sw)
+
+    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,
+        }
+        content = EF_SPN().encode_hex(abstract_data)
+        data, sw = self._scc.update_binary(EF['SPN'], content)
+        return sw
+
+    def read_gid1(self):
+        (res, sw) = self._scc.read_binary(EF['GID1'])
+        if sw == '9000':
+            return (res, sw)
+        else:
+            return (None, sw)
+
+    def read_msisdn(self):
+        (res, sw) = self._scc.read_record(EF['MSISDN'], 1)
+        if sw == '9000':
+            return (dec_msisdn(res), sw)
+        else:
+            return (None, sw)
+
+
+class UsimCard(UiccCardBase, SimCard):
+    """Higher-layer class that is used *only* by legacy pySim-{prog,read}."""
+
+    def read_ehplmn(self):
+        (res, sw) = self._scc.read_binary(EF_USIM_ADF_map['EHPLMN'])
+        if sw == '9000':
+            return (format_xplmn(res), sw)
+        else:
+            return (None, sw)
+
+    def update_ehplmn(self, mcc, mnc):
+        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)
+        return sw
+
+    def read_fplmn(self):
+        res, sw = self._scc.read_binary(EF_USIM_ADF_map['FPLMN'])
+        if sw == '9000':
+            return format_xplmn(res), sw
+        else:
+            return None, sw
+
+    def update_fplmn(self, fplmn):
+        self._scc.select_file('3f00')
+        self.select_adf_by_aid('USIM')
+        size = self._scc.binary_size(EF_USIM_ADF_map['FPLMN'])
+        encoded = ''.join([enc_plmn(plmn[:3], plmn[3:]) for plmn in fplmn])
+        encoded = rpad(encoded, size)
+        data, sw = self._scc.update_binary(EF_USIM_ADF_map['FPLMN'], encoded)
+        return sw
+
+    def read_epdgid(self):
+        (res, sw) = self._scc.read_binary(EF_USIM_ADF_map['ePDGId'])
+        if sw == '9000':
+            try:
+                addr, addr_type = dec_addr_tlv(res)
+            except:
+                addr = None
+                addr_type = None
+            return (format_addr(addr, addr_type), sw)
+        else:
+            return (None, sw)
+
+    def update_epdgid(self, epdgid):
+        size = self._scc.binary_size(EF_USIM_ADF_map['ePDGId']) * 2
+        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")
+            epdgid_tlv = rpad(enc_addr_tlv(epdgid, ('%02x' % addr_type)), size)
+        else:
+            epdgid_tlv = rpad('ff', size)
+        data, sw = self._scc.update_binary(
+            EF_USIM_ADF_map['ePDGId'], epdgid_tlv)
+        return sw
+
+    def read_ePDGSelection(self):
+        (res, sw) = self._scc.read_binary(EF_USIM_ADF_map['ePDGSelection'])
+        if sw == '9000':
+            return (format_ePDGSelection(res), sw)
+        else:
+            return (None, sw)
+
+    def update_ePDGSelection(self, mcc, mnc):
+        (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)))
+        elif sw == '9000':
+            (res, sw) = self._scc.update_binary(
+                EF_USIM_ADF_map['ePDGSelection'], enc_ePDGSelection(res, mcc, mnc))
+        return sw
+
+    def read_ust(self):
+        (res, sw) = self._scc.read_binary(EF_USIM_ADF_map['UST'])
+        if sw == '9000':
+            # Print those which are available
+            return ([res, dec_st(res, table="usim")], sw)
+        else:
+            return ([None, None], sw)
+
+    def update_ust(self, service, bit=1):
+        (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)
+        return sw
+
+    def update_est(self, service, bit=1):
+        (res, sw) = self._scc.read_binary(EF_USIM_ADF_map['EST'])
+        if sw == '9000':
+            content = enc_st(res, service, bit)
+            (res, sw) = self._scc.update_binary(
+                EF_USIM_ADF_map['EST'], content)
+        return sw
+
+
+
+class IsimCard(UiccCardBase):
+    """Higher-layer class that is used *only* by legacy pySim-{prog,read}."""
+
+    name = 'ISIM'
+
+    def read_pcscf(self):
+        rec_cnt = self._scc.record_count(EF_ISIM_ADF_map['PCSCF'])
+        pcscf_recs = ""
+        for i in range(0, rec_cnt):
+            (res, sw) = self._scc.read_record(EF_ISIM_ADF_map['PCSCF'], i + 1)
+            if sw == '9000':
+                try:
+                    addr, addr_type = dec_addr_tlv(res)
+                except:
+                    addr = None
+                    addr_type = None
+                content = format_addr(addr, addr_type)
+                pcscf_recs += "%s" % (len(content)
+                                      and content or '\tNot available\n')
+            else:
+                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")
+            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)
+        return sw
+
+    def read_domain(self):
+        (res, sw) = self._scc.read_binary(EF_ISIM_ADF_map['DOMAIN'])
+        if sw == '9000':
+            # 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)
+        else:
+            return (None, sw)
+
+    def update_domain(self, domain=None, mcc=None, mnc=None):
+        hex_str = ""
+        if domain:
+            hex_str = s2h(domain)
+        elif mcc and mnc:
+            # MCC and MNC always has 3 digits in domain form
+            plmn_str = 'mnc' + lpad(mnc, 3, "0") + '.mcc' + lpad(mcc, 3, "0")
+            hex_str = s2h('ims.' + plmn_str + '.3gppnetwork.org')
+
+        # Build TLV
+        tlv = TLV(['80'])
+        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))
+        return sw
+
+    def read_impi(self):
+        (res, sw) = self._scc.read_binary(EF_ISIM_ADF_map['IMPI'])
+        if sw == '9000':
+            # 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)
+        else:
+            return (None, sw)
+
+    def update_impi(self, impi=None):
+        hex_str = ""
+        if impi:
+            hex_str = s2h(impi)
+        # Build TLV
+        tlv = TLV(['80'])
+        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))
+        return sw
+
+    def read_impu(self):
+        rec_cnt = self._scc.record_count(EF_ISIM_ADF_map['IMPU'])
+        impu_recs = ""
+        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 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')
+            else:
+                impu_recs += "IMS public user identity: Can't read, response code = %s\n" % (
+                    sw)
+        return impu_recs
+
+    def update_impu(self, impu=None):
+        hex_str = ""
+        if impu:
+            hex_str = s2h(impu)
+        # Build TLV
+        tlv = TLV(['80'])
+        content = tlv.build({'80': hex_str})
+
+        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)
+        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)
+            if sw == '9000':
+                # 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')
+            else:
+                uiari_recs += "UICC IARI: Can't read, response code = %s\n" % (
+                    sw)
+        return uiari_recs
+
+    def update_ist(self, service, bit=1):
+        (res, sw) = self._scc.read_binary(EF_ISIM_ADF_map['IST'])
+        if sw == '9000':
+            content = enc_st(res, service, bit)
+            (res, sw) = self._scc.update_binary(
+                EF_ISIM_ADF_map['IST'], content)
+        return sw
+
+
+class MagicSimBase(abc.ABC, SimCard):
+    """
+    Theses cards uses several record based EFs to store the provider infos,
+    each possible provider uses a specific record number in each EF. The
+    indexes used are ( where N is the number of providers supported ) :
+     - [2 .. N+1] for the operator name
+     - [1 .. N] for the programmable EFs
+
+    * 3f00/7f4d/8f0c : Operator Name
+
+    bytes 0-15 : provider name, padded with 0xff
+    byte  16   : length of the provider name
+    byte  17   : 01 for valid records, 00 otherwise
+
+    * 3f00/7f4d/8f0d : Programmable Binary EFs
+
+    * 3f00/7f4d/8f0e : Programmable Record EFs
+
+    """
+
+    _files = {}  # type: Dict[str, Tuple[str, int, bool]]
+    _ki_file = None  # type: Optional[str]
+
+    @classmethod
+    def autodetect(kls, scc):
+        try:
+            for p, l, t in kls._files.values():
+                if not t:
+                    continue
+                if scc.record_size(['3f00', '7f4d', p]) != l:
+                    return None
+        except:
+            return None
+
+        return kls(scc)
+
+    def _get_count(self):
+        """
+        Selects the file and returns the total number of entries
+        and entry size
+        """
+        f = self._files['name']
+
+        r = self._scc.select_path(['3f00', '7f4d', f[0]])
+        rec_len = int(r[-1][28:30], 16)
+        tlen = int(r[-1][4:8], 16)
+        rec_cnt = (tlen // rec_len) - 1
+
+        if (rec_cnt < 1) or (rec_len != f[1]):
+            raise RuntimeError('Bad card type')
+
+        return rec_cnt
+
+    def program(self, p):
+        # Go to dir
+        self._scc.select_path(['3f00', '7f4d'])
+
+        # Home PLMN in PLMN_Sel format
+        hplmn = enc_plmn(p['mcc'], p['mnc'])
+
+        # Operator name ( 3f00/7f4d/8f0c )
+        self._scc.update_record(self._files['name'][0], 2,
+                                rpad(b2h(p['name']), 32) + ('%02x' %
+                                                            len(p['name'])) + '01'
+                                )
+
+        # ICCID/IMSI/Ki/HPLMN ( 3f00/7f4d/8f0d )
+        v = ''
+
+        # inline Ki
+        if self._ki_file is None:
+            v += p['ki']
+
+            # ICCID
+        v += '3f00' + '2fe2' + '0a' + enc_iccid(p['iccid'])
+
+        # IMSI
+        v += '7f20' + '6f07' + '09' + enc_imsi(p['imsi'])
+
+        # Ki
+        if self._ki_file:
+            v += self._ki_file + '10' + p['ki']
+
+            # PLMN_Sel
+        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)
+
+        self._scc.update_record(self._files['b_ef'][0], 1,
+                                rpad(v, self._files['b_ef'][1]*2)
+                                )
+
+        # SMSP ( 3f00/7f4d/8f0e )
+        # FIXME
+
+        # Write PLMN_Sel forcefully as well
+        r = self._scc.select_path(['3f00', '7f20', '6f30'])
+        tl = int(r[-1][4:8], 16)
+
+        hplmn = enc_plmn(p['mcc'], p['mnc'])
+        self._scc.update_binary('6f30', hplmn + 'ff' * (tl-3))
+
+    def erase(self):
+        # Dummy
+        df = {}
+        for k, v in self._files.items():
+            ofs = 1
+            fv = v[1] * 'ff'
+            if k == 'name':
+                ofs = 2
+                fv = fv[0:-4] + '0000'
+            df[v[0]] = (fv, ofs)
+
+        # Write
+        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)
+
+
+class SuperSim(MagicSimBase):
+
+    name = 'supersim'
+
+    _files = {
+        'name': ('8f0c', 18, True),
+        'b_ef': ('8f0d', 74, True),
+        'r_ef': ('8f0e', 50, True),
+    }
+
+    _ki_file = None
+
+
+class MagicSim(MagicSimBase):
+
+    name = 'magicsim'
+
+    _files = {
+        'name': ('8f0c', 18, True),
+        'b_ef': ('8f0d', 130, True),
+        'r_ef': ('8f0e', 102, False),
+    }
+
+    _ki_file = '6f1b'
+
+
+class FakeMagicSim(SimCard):
+    """
+    Theses cards have a record based EF 3f00/000c that contains the provider
+    information. See the program method for its format. The records go from
+    1 to N.
+    """
+
+    name = 'fakemagicsim'
+
+    @classmethod
+    def autodetect(kls, scc):
+        try:
+            if scc.record_size(['3f00', '000c']) != 0x5a:
+                return None
+        except:
+            return None
+
+        return kls(scc)
+
+    def _get_infos(self):
+        """
+        Selects the file and returns the total number of entries
+        and entry size
+        """
+
+        r = self._scc.select_path(['3f00', '000c'])
+        rec_len = int(r[-1][28:30], 16)
+        tlen = int(r[-1][4:8], 16)
+        rec_cnt = (tlen // rec_len) - 1
+
+        if (rec_cnt < 1) or (rec_len != 0x5a):
+            raise RuntimeError('Bad card type')
+
+        return rec_cnt, rec_len
+
+    def program(self, p):
+        # Home PLMN
+        r = self._scc.select_path(['3f00', '7f20', '6f30'])
+        tl = int(r[-1][4:8], 16)
+
+        hplmn = enc_plmn(p['mcc'], p['mnc'])
+        self._scc.update_binary('6f30', hplmn + 'ff' * (tl-3))
+
+        # Get total number of entries and entry size
+        rec_cnt, rec_len = self._get_infos()
+
+        # Set first entry
+        entry = (
+            '81' +  # 1b  Status: Valid & Active
+            rpad(s2h(p['name'][0:14]), 28) +  # 14b  Entry Name
+            enc_iccid(p['iccid']) +			# 10b  ICCID
+            enc_imsi(p['imsi']) +  # 9b  IMSI_len + id_type(9) + IMSI
+            p['ki'] +				# 16b  Ki
+            lpad(p['smsp'], 80)			# 40b  SMSP (padded with ff if needed)
+        )
+        self._scc.update_record('000c', 1, entry)
+
+    def erase(self):
+        # Get total number of entries and entry size
+        rec_cnt, rec_len = self._get_infos()
+
+        # Erase all entries
+        entry = 'ff' * rec_len
+        for i in range(0, rec_cnt):
+            self._scc.update_record('000c', 1+i, entry)
+
+
+class GrcardSim(SimCard):
+    """
+    Greencard (grcard.cn) HZCOS GSM SIM
+    These cards have a much more regular ISO 7816-4 / TS 11.11 structure,
+    and use standard UPDATE RECORD / UPDATE BINARY commands except for Ki.
+    """
+
+    name = 'grcardsim'
+
+    @classmethod
+    def autodetect(kls, scc):
+        return None
+
+    def program(self, p):
+        # We don't really know yet what ADM PIN 4 is about
+        #self._scc.verify_chv(4, h2b("4444444444444444"))
+
+        # Authenticate using ADM PIN 5
+        if p['pin_adm']:
+            pin = h2b(p['pin_adm'])
+        else:
+            pin = h2b("4444444444444444")
+        self._scc.verify_chv(5, pin)
+
+        # EF.ICCID
+        r = self._scc.select_path(['3f00', '2fe2'])
+        data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid']))
+
+        # EF.IMSI
+        r = self._scc.select_path(['3f00', '7f20', '6f07'])
+        data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi']))
+
+        # EF.ACC
+        if p.get('acc') is not None:
+            data, sw = self._scc.update_binary('6f78', lpad(p['acc'], 4))
+
+        # EF.SMSP
+        if p.get('smsp'):
+            r = self._scc.select_path(['3f00', '7f10', '6f42'])
+            data, sw = self._scc.update_record('6f42', 1, lpad(p['smsp'], 80))
+
+        # Set the Ki using proprietary command
+        pdu = '80d4020010' + p['ki']
+        data, sw = self._scc._tp.send_apdu(pdu)
+
+        # EF.HPLMN
+        r = self._scc.select_path(['3f00', '7f20', '6f30'])
+        size = int(r[-1][4:8], 16)
+        hplmn = enc_plmn(p['mcc'], p['mnc'])
+        self._scc.update_binary('6f30', hplmn + 'ff' * (size-3))
+
+        # EF.SPN (Service Provider Name)
+        r = self._scc.select_path(['3f00', '7f20', '6f30'])
+        size = int(r[-1][4:8], 16)
+        # FIXME
+
+        # FIXME: EF.MSISDN
+
+
+class SysmoSIMgr1(GrcardSim):
+    """
+    sysmocom sysmoSIM-GR1
+    These cards have a much more regular ISO 7816-4 / TS 11.11 structure,
+    and use standard UPDATE RECORD / UPDATE BINARY commands except for Ki.
+    """
+    name = 'sysmosim-gr1'
+
+    @classmethod
+    def autodetect(kls, scc):
+        try:
+            # Look for ATR
+            if scc.get_atr() == toBytes("3B 99 18 00 11 88 22 33 44 55 66 77 60"):
+                return kls(scc)
+        except:
+            return None
+        return None
+
+
+class SysmoUSIMgr1(UsimCard):
+    """
+    sysmocom sysmoUSIM-GR1
+    """
+    name = 'sysmoUSIM-GR1'
+
+    @classmethod
+    def autodetect(kls, scc):
+        # TODO: Access the ATR
+        return None
+
+    def program(self, p):
+        # 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")
+
+        # TODO: move into SimCardCommands
+        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
+               )
+        data, sw = self._scc._tp.send_apdu_checksw("0099000033" + par)
+
+
+class SysmoSIMgr2(SimCard):
+    """
+    sysmocom sysmoSIM-GR2
+    """
+
+    name = 'sysmoSIM-GR2'
+
+    @classmethod
+    def autodetect(kls, scc):
+        try:
+            # Look for ATR
+            if scc.get_atr() == toBytes("3B 7D 94 00 00 55 55 53 0A 74 86 93 0B 24 7C 4D 54 68"):
+                return kls(scc)
+        except:
+            return None
+        return None
+
+    def program(self, p):
+
+        # select MF
+        r = self._scc.select_path(['3f00'])
+
+        # authenticate as SUPER ADM using default key
+        self._scc.verify_chv(0x0b, h2b("3838383838383838"))
+
+        # set ADM pin using proprietary command
+        # INS: D4
+        # P1: 3A for PIN, 3B for PUK
+        # P2: CHV number, as in VERIFY CHV for PIN, and as in UNBLOCK CHV for PUK
+        # P3: 08, CHV length (curiously the PUK is also 08 length, instead of 10)
+        if p['pin_adm']:
+            pin = h2b(p['pin_adm'])
+        else:
+            pin = h2b("4444444444444444")
+
+        pdu = 'A0D43A0508' + b2h(pin)
+        data, sw = self._scc._tp.send_apdu(pdu)
+
+        # authenticate as ADM (enough to write file, and can set PINs)
+
+        self._scc.verify_chv(0x05, pin)
+
+        # write EF.ICCID
+        data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid']))
+
+        # select DF_GSM
+        r = self._scc.select_path(['7f20'])
+
+        # write EF.IMSI
+        data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi']))
+
+        # write EF.ACC
+        if p.get('acc') is not None:
+            data, sw = self._scc.update_binary('6f78', lpad(p['acc'], 4))
+
+        # get size and write EF.HPLMN
+        r = self._scc.select_path(['6f30'])
+        size = int(r[-1][4:8], 16)
+        hplmn = enc_plmn(p['mcc'], p['mnc'])
+        self._scc.update_binary('6f30', hplmn + 'ff' * (size-3))
+
+        # set COMP128 version 0 in proprietary file
+        data, sw = self._scc.update_binary('0001', '001000')
+
+        # set Ki in proprietary file
+        data, sw = self._scc.update_binary('0001', p['ki'], 3)
+
+        # select DF_TELECOM
+        r = self._scc.select_path(['3f00', '7f10'])
+
+        # write EF.SMSP
+        if p.get('smsp'):
+            data, sw = self._scc.update_record('6f42', 1, lpad(p['smsp'], 80))
+
+
+class SysmoUSIMSJS1(UsimCard):
+    """
+    sysmocom sysmoUSIM-SJS1
+    """
+
+    name = 'sysmoUSIM-SJS1'
+
+    def __init__(self, ssc):
+        super(SysmoUSIMSJS1, self).__init__(ssc)
+        self._scc.cla_byte = "00"
+        self._scc.sel_ctrl = "0004"  # request an FCP
+
+    @classmethod
+    def autodetect(kls, scc):
+        try:
+            # Look for ATR
+            if scc.get_atr() == toBytes("3B 9F 96 80 1F C7 80 31 A0 73 BE 21 13 67 43 20 07 18 00 00 01 A5"):
+                return kls(scc)
+        except:
+            return None
+        return None
+
+    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")
+        (res, sw) = self._scc.verify_chv(0x0A, key)
+        return sw
+
+    def program(self, p):
+        self.verify_adm(h2b(p['pin_adm']))
+
+        # select MF
+        r = self._scc.select_path(['3f00'])
+
+        # write EF.ICCID
+        data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid']))
+
+        # select DF_GSM
+        r = self._scc.select_path(['7f20'])
+
+        # set Ki in proprietary file
+        data, sw = self._scc.update_binary('00FF', p['ki'])
+
+        # set OPc in proprietary file
+        if 'opc' in p:
+            content = "01" + p['opc']
+            data, sw = self._scc.update_binary('00F7', content)
+
+        # set Service Provider Name
+        if p.get('name') is not None:
+            self.update_spn(p['name'], True, True)
+
+        if p.get('acc') is not None:
+            self.update_acc(p['acc'])
+
+        # write EF.IMSI
+        data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi']))
+
+        # 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)
+
+        # 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)
+
+        # 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)
+
+        # 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)
+
+        # EF.AD
+        if (p.get('mcc') and p.get('mnc')) or p.get('opmode'):
+            if p.get('mcc') and p.get('mnc'):
+                mnc = p['mnc']
+            else:
+                mnc = None
+            sw = self.update_ad(mnc=mnc, opmode=p.get('opmode'))
+            if sw != '9000':
+                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)
+
+        # EF.MSISDN
+        # TODO: Alpha Identifier (currently 'ff'O * 20)
+        # TODO: Capability/Configuration1 Record Identifier
+        # TODO: Extension1 Record Identifier
+        if p.get('msisdn') is not None:
+            msisdn = enc_msisdn(p['msisdn'])
+            data = 'ff' * 20 + msisdn
+
+            r = self._scc.select_path(['3f00', '7f10'])
+            data, sw = self._scc.update_record('6F40', 1, data, force_len=True)
+
+
+class FairwavesSIM(UsimCard):
+    """
+    FairwavesSIM
+
+    The SIM card is operating according to the standard.
+    For Ki/OP/OPC programming the following files are additionally open for writing:
+            3F00/7F20/FF01 – OP/OPC:
+            byte 1 = 0x01, bytes 2-17: OPC;
+            byte 1 = 0x00, bytes 2-17: OP;
+            3F00/7F20/FF02: Ki
+    """
+
+    name = 'Fairwaves-SIM'
+    # Propriatary files
+    _EF_num = {
+        'Ki': 'FF02',
+        'OP/OPC': 'FF01',
+    }
+    _EF = {
+        'Ki':     DF['GSM']+[_EF_num['Ki']],
+        'OP/OPC': DF['GSM']+[_EF_num['OP/OPC']],
+    }
+
+    def __init__(self, ssc):
+        super(FairwavesSIM, self).__init__(ssc)
+        self._adm_chv_num = 0x11
+        self._adm2_chv_num = 0x12
+
+    @classmethod
+    def autodetect(kls, scc):
+        try:
+            # Look for ATR
+            if scc.get_atr() == toBytes("3B 9F 96 80 1F C7 80 31 A0 73 BE 21 13 67 44 22 06 10 00 00 01 A9"):
+                return kls(scc)
+        except:
+            return None
+        return None
+
+    def verify_adm2(self, key):
+        '''
+        Authenticate with ADM2 key.
+
+        Fairwaves SIM cards support hierarchical key structure and ADM2 key
+        is a key which has access to proprietary files (Ki and OP/OPC).
+        That said, ADM key inherits permissions of ADM2 key and thus we rarely
+        need ADM2 key per se.
+        '''
+        (res, sw) = self._scc.verify_chv(self._adm2_chv_num, key)
+        return sw
+
+    def read_ki(self):
+        """
+        Read Ki in proprietary file.
+
+        Requires ADM1 access level
+        """
+        return self._scc.read_binary(self._EF['Ki'])
+
+    def update_ki(self, ki):
+        """
+        Set Ki in proprietary file.
+
+        Requires ADM1 access level
+        """
+        data, sw = self._scc.update_binary(self._EF['Ki'], ki)
+        return sw
+
+    def read_op_opc(self):
+        """
+        Read Ki in proprietary file.
+
+        Requires ADM1 access level
+        """
+        (ef, sw) = self._scc.read_binary(self._EF['OP/OPC'])
+        type = 'OP' if ef[0:2] == '00' else 'OPC'
+        return ((type, ef[2:]), sw)
+
+    def update_op(self, op):
+        """
+        Set OP in proprietary file.
+
+        Requires ADM1 access level
+        """
+        content = '00' + op
+        data, sw = self._scc.update_binary(self._EF['OP/OPC'], content)
+        return sw
+
+    def update_opc(self, opc):
+        """
+        Set OPC in proprietary file.
+
+        Requires ADM1 access level
+        """
+        content = '01' + opc
+        data, sw = self._scc.update_binary(self._EF['OP/OPC'], content)
+        return sw
+
+    def program(self, p):
+        # For some reason the card programming only works when the card
+        # is handled as a classic SIM, even though it is an USIM, so we
+        # reconfigure the class byte and the select control field on
+        # the fly. When the programming is done the original values are
+        # restored.
+        cla_byte_orig = self._scc.cla_byte
+        sel_ctrl_orig = self._scc.sel_ctrl
+        self._scc.cla_byte = "a0"
+        self._scc.sel_ctrl = "0000"
+
+        try:
+            self._program(p)
+        finally:
+            # restore original cla byte and sel ctrl
+            self._scc.cla_byte = cla_byte_orig
+            self._scc.sel_ctrl = sel_ctrl_orig
+
+    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")
+        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)
+        # 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)
+        if p.get('imsi') is not None:
+            sw = self.update_imsi(p['imsi'])
+            if sw != '9000':
+                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)
+        if p.get('opc') is not None:
+            sw = self.update_opc(p['opc'])
+            if sw != '9000':
+                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)
+
+
+class OpenCellsSim(SimCard):
+    """
+    OpenCellsSim
+
+    """
+
+    name = 'OpenCells-SIM'
+
+    def __init__(self, ssc):
+        super(OpenCellsSim, self).__init__(ssc)
+        self._adm_chv_num = 0x0A
+
+    @classmethod
+    def autodetect(kls, scc):
+        try:
+            # Look for ATR
+            if scc.get_atr() == toBytes("3B 9F 95 80 1F C3 80 31 E0 73 FE 21 13 57 86 81 02 86 98 44 18 A8"):
+                return kls(scc)
+        except:
+            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")
+        self._scc.verify_chv(0x0A, h2b(p['pin_adm']))
+
+        # select MF
+        r = self._scc.select_path(['3f00'])
+
+        # write EF.ICCID
+        data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid']))
+
+        r = self._scc.select_path(['7ff0'])
+
+        # set Ki in proprietary file
+        data, sw = self._scc.update_binary('FF02', p['ki'])
+
+        # set OPC in proprietary file
+        data, sw = self._scc.update_binary('FF01', p['opc'])
+
+        # select DF_GSM
+        r = self._scc.select_path(['7f20'])
+
+        # write EF.IMSI
+        data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi']))
+
+
+class WavemobileSim(UsimCard):
+    """
+    WavemobileSim
+
+    """
+
+    name = 'Wavemobile-SIM'
+
+    def __init__(self, ssc):
+        super(WavemobileSim, self).__init__(ssc)
+        self._adm_chv_num = 0x0A
+        self._scc.cla_byte = "00"
+        self._scc.sel_ctrl = "0004"  # request an FCP
+
+    @classmethod
+    def autodetect(kls, scc):
+        try:
+            # Look for ATR
+            if scc.get_atr() == toBytes("3B 9F 95 80 1F C7 80 31 E0 73 F6 21 13 67 4D 45 16 00 43 01 00 8F"):
+                return kls(scc)
+        except:
+            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")
+        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.")
+
+        # KI (Presumably a proprietary file)
+        # TODO: Add programming of KI
+        if p.get('ki'):
+            print(
+                "Warning: Programming of the KI is not implemented for this type of card.")
+
+        # OPc (Presumably a proprietary file)
+        # TODO: Add programming of OPc
+        if p.get('opc'):
+            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)
+
+        # EF.IMSI
+        if p.get('imsi'):
+            sw = self.update_imsi(p['imsi'])
+            if sw != '9000':
+                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)
+
+        # 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)
+
+        # 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)
+
+        # 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)
+
+        # EF.AD
+        if (p.get('mcc') and p.get('mnc')) or p.get('opmode'):
+            if p.get('mcc') and p.get('mnc'):
+                mnc = p['mnc']
+            else:
+                mnc = None
+            sw = self.update_ad(mnc=mnc, opmode=p.get('opmode'))
+            if sw != '9000':
+                print("Programming AD failed with code %s" % sw)
+
+        return None
+
+
+class SysmoISIMSJA2(UsimCard, IsimCard):
+    """
+    sysmocom sysmoISIM-SJA2
+    """
+
+    name = 'sysmoISIM-SJA2'
+
+    def __init__(self, ssc):
+        super(SysmoISIMSJA2, self).__init__(ssc)
+        self._scc.cla_byte = "00"
+        self._scc.sel_ctrl = "0004"  # request an FCP
+
+    @classmethod
+    def autodetect(kls, scc):
+        try:
+            # Try card model #1
+            atr = "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 30 34 05 4B A9"
+            if scc.get_atr() == toBytes(atr):
+                return kls(scc)
+
+            # Try card model #2
+            atr = "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 31 33 02 51 B2"
+            if scc.get_atr() == toBytes(atr):
+                return kls(scc)
+
+            # Try card model #3
+            atr = "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 52 75 31 04 51 D5"
+            if scc.get_atr() == toBytes(atr):
+                return kls(scc)
+        except:
+            return None
+        return None
+
+    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")
+        (res, sw) = self._scc.verify_chv(0x0A, key)
+        return sw
+
+    def program(self, p):
+        self.verify_adm(h2b(p['pin_adm']))
+
+        # Populate AIDs
+        self.read_aids()
+
+        # This type of card does not allow to reprogram the ICCID.
+        # Reprogramming the ICCID would mess up the card os software
+        # 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.")
+
+        # select DF_GSM
+        self._scc.select_path(['7f20'])
+
+        # set Service Provider Name
+        if p.get('name') is not None:
+            self.update_spn(p['name'], True, True)
+
+        # write EF.IMSI
+        if p.get('imsi'):
+            self._scc.update_binary('6f07', enc_imsi(p['imsi']))
+
+        # 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)
+
+        # 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)
+
+        # 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)
+
+        # 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)
+
+        # EF.AD
+        if (p.get('mcc') and p.get('mnc')) or p.get('opmode'):
+            if p.get('mcc') and p.get('mnc'):
+                mnc = p['mnc']
+            else:
+                mnc = None
+            sw = self.update_ad(mnc=mnc, opmode=p.get('opmode'))
+            if sw != '9000':
+                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)
+
+        # EF.MSISDN
+        # TODO: Alpha Identifier (currently 'ff'O * 20)
+        # TODO: Capability/Configuration1 Record Identifier
+        # TODO: Extension1 Record Identifier
+        if p.get('msisdn') is not None:
+            msisdn = enc_msisdn(p['msisdn'])
+            content = 'ff' * 20 + msisdn
+
+            r = self._scc.select_path(['3f00', '7f10'])
+            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)
+
+        # update EF-SIM_AUTH_KEY (and EF-USIM_AUTH_KEY_2G, which is
+        # hard linked to EF-USIM_AUTH_KEY)
+        self._scc.select_path(['3f00'])
+        self._scc.select_path(['a515'])
+        if p.get('ki'):
+            self._scc.update_binary('6f20', p['ki'], 1)
+        if p.get('opc'):
+            self._scc.update_binary('6f20', p['opc'], 17)
+
+        # update EF-USIM_AUTH_KEY in ADF.ISIM
+        if self.adf_present("isim"):
+            self.select_adf_by_aid(adf="isim")
+
+            if p.get('ki'):
+                self._scc.update_binary('af20', p['ki'], 1)
+            if p.get('opc'):
+                self._scc.update_binary('af20', p['opc'], 17)
+
+            # update EF.P-CSCF in ADF.ISIM
+            if self.file_exists(EF_ISIM_ADF_map['PCSCF']):
+                if p.get('pcscf'):
+                    sw = self.update_pcscf(p['pcscf'])
+                else:
+                    sw = self.update_pcscf("")
+                if sw != '9000':
+                    print("Programming P-CSCF failed with code %s" % sw)
+
+            # update EF.DOMAIN in ADF.ISIM
+            if self.file_exists(EF_ISIM_ADF_map['DOMAIN']):
+                if p.get('ims_hdomain'):
+                    sw = self.update_domain(domain=p['ims_hdomain'])
+                else:
+                    sw = self.update_domain()
+
+                if sw != '9000':
+                    print(
+                        "Programming Home Network Domain Name failed with code %s" % sw)
+
+            # update EF.IMPI in ADF.ISIM
+            # TODO: Validate IMPI input
+            if self.file_exists(EF_ISIM_ADF_map['IMPI']):
+                if p.get('impi'):
+                    sw = self.update_impi(p['impi'])
+                else:
+                    sw = self.update_impi()
+                if sw != '9000':
+                    print("Programming IMPI failed with code %s" % sw)
+
+            # update EF.IMPU in ADF.ISIM
+            # TODO: Validate IMPU input
+            # Support multiple IMPU if there is enough space
+            if self.file_exists(EF_ISIM_ADF_map['IMPU']):
+                if p.get('impu'):
+                    sw = self.update_impu(p['impu'])
+                else:
+                    sw = self.update_impu()
+                if sw != '9000':
+                    print("Programming IMPU failed with code %s" % sw)
+
+        if self.adf_present("usim"):
+            self.select_adf_by_aid(adf="usim")
+
+            # EF.AD in ADF.USIM
+            if (p.get('mcc') and p.get('mnc')) or p.get('opmode'):
+                 if p.get('mcc') and p.get('mnc'):
+                     mnc = p['mnc']
+                 else:
+                     mnc = None
+            sw = self.update_ad(mnc=mnc, opmode=p.get('opmode'),
+                                path=EF_USIM_ADF_map['AD'])
+            if sw != '9000':
+                print("Programming AD failed with code %s" % sw)
+
+            # update EF-USIM_AUTH_KEY in ADF.USIM
+            if p.get('ki'):
+                self._scc.update_binary('af20', p['ki'], 1)
+            if p.get('opc'):
+                self._scc.update_binary('af20', p['opc'], 17)
+
+            # update EF.EHPLMN in ADF.USIM
+            if self.file_exists(EF_USIM_ADF_map['EHPLMN']):
+                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)
+
+            # update EF.ePDGId in ADF.USIM
+            if self.file_exists(EF_USIM_ADF_map['ePDGId']):
+                if p.get('epdgid'):
+                    sw = self.update_epdgid(p['epdgid'])
+                else:
+                    sw = self.update_epdgid("")
+                if sw != '9000':
+                    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:])
+                else:
+                    sw = self.update_ePDGSelection("", "")
+                if sw != '9000':
+                    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
+            # Disable service 95, 99, 115 if ISIM application is present
+            if self.file_exists(EF_USIM_ADF_map['UST']):
+                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)
+                    sw = self.update_ust(107, 1)
+                    if sw != '9000':
+                        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)
+                sw = self.update_ust(99, 0)
+                if sw != '9000':
+                    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)
+
+        return
+
+class SysmoISIMSJA5(SysmoISIMSJA2):
+    """
+    sysmocom sysmoISIM-SJA5
+    """
+
+    name = 'sysmoISIM-SJA5'
+
+    @classmethod
+    def autodetect(kls, scc):
+        try:
+            # Try card model #1 (9FJ)
+            atr = "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 35 75 30 35 02 51 CC"
+            if scc.get_atr() == toBytes(atr):
+                return kls(scc)
+            # Try card model #2 (SLM17)
+            atr = "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 35 75 30 35 02 65 F8"
+            if scc.get_atr() == toBytes(atr):
+                return kls(scc)
+            # Try card model #3 (9FV)
+            atr = "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 35 75 30 35 02 59 C4"
+            if scc.get_atr() == toBytes(atr):
+                return kls(scc)
+        except:
+            return None
+        return None
+
+
+class GialerSim(UsimCard):
+    """
+    Gialer sim cards (www.gialer.com).
+    """
+    name = 'gialersim'
+
+    def __init__(self, ssc):
+        super().__init__(ssc)
+        self._program_handlers = {
+            'iccid': self.update_iccid,
+            'imsi': self.update_imsi,
+            'acc': self.update_acc,
+            'smsp': self.update_smsp,
+            'ki': self.update_ki,
+            'opc': self.update_opc,
+            'fplmn': self.update_fplmn,
+        }
+
+    @classmethod
+    def autodetect(cls, scc):
+        try:
+            # Look for ATR
+            if scc.get_atr() == toBytes('3B 9F 95 80 1F C7 80 31 A0 73 B6 A1 00 67 CF 32 15 CA 9C D7 09 20'):
+                return cls(scc)
+        except:
+            return None
+        return None
+
+    def program(self, p):
+        self.set_apdu_parameter('00', '0004')
+        # Authenticate
+        self._scc.verify_chv(0xc, h2b('3834373936313533'))
+        for handler in self._program_handlers:
+            if p.get(handler) is not None:
+                self._program_handlers[handler](p[handler])
+
+        mcc = p.get('mcc')
+        mnc = p.get('mnc')
+        has_plmn = mcc is not None and mnc is not None
+        # EF.HPLMN
+        if has_plmn:
+            self.update_hplmn_act(mcc, mnc)
+
+        # EF.AD
+        if has_plmn or (p.get('opmode') is not None):
+            self.update_ad(mnc=mnc, opmode=p.get('opmode'))
+
+    def update_smsp(self, smsp):
+        data, sw = self._scc.update_record(EF['SMSP'], 1, rpad(smsp, 80))
+        return sw
+
+    def update_ki(self, ki):
+        self._scc.select_path(['3f00', '0001'])
+        self._scc.update_binary('0001', ki)
+
+    def update_opc(self, opc):
+        self._scc.select_path(['3f00', '6002'])
+        # No idea why the '01' is required
+        self._scc.update_binary('6002', '01' + opc)
+
+
+# In order for autodetection ...
+_cards_classes = [FakeMagicSim, SuperSim, MagicSim, GrcardSim,
+                  SysmoSIMgr1, SysmoSIMgr2, SysmoUSIMgr1, SysmoUSIMSJS1,
+                  FairwavesSIM, OpenCellsSim, WavemobileSim, SysmoISIMSJA2,
+                  SysmoISIMSJA5, GialerSim]
+
+
+def card_detect(ctype, scc):
+    # Detect type if needed
+    card = None
+    ctypes = dict([(kls.name, kls) for kls in _cards_classes])
+
+    if ctype == "auto":
+        for kls in _cards_classes:
+            card = kls.autodetect(scc)
+            if card:
+                print("Autodetected card type: %s" % card.name)
+                card.reset()
+                break
+
+        if card is None:
+            print("Autodetection failed")
+            return None
+
+    elif ctype in ctypes:
+        card = ctypes[ctype](scc)
+
+    else:
+        raise ValueError("Unknown card type: %s" % ctype)
+
+    return card
diff --git a/pySim/legacy/utils.py b/pySim/legacy/utils.py
new file mode 100644
index 0000000..1b6b5d1
--- /dev/null
+++ b/pySim/legacy/utils.py
@@ -0,0 +1,212 @@
+# -*- coding: utf-8 -*-
+
+""" pySim: various utilities only used by legacy tools (pySim-{prog,read})
+"""
+
+# Copyright (C) 2009-2010  Sylvain Munaut <tnt@246tNt.com>
+# Copyright (C) 2021 Harald Welte <laforge@osmocom.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+from pySim.utils import Hexstr, rpad, enc_plmn
+from pySim.utils import dec_xplmn_w_act, dec_xplmn, dec_mcc_from_plmn, dec_mnc_from_plmn
+
+def hexstr_to_Nbytearr(s, nbytes):
+    return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2))]
+
+def format_xplmn_w_act(hexstr):
+    s = ""
+    for rec_data in hexstr_to_Nbytearr(hexstr, 5):
+        rec_info = dec_xplmn_w_act(rec_data)
+        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']))
+        s += "\t%s # %s\n" % (rec_data, rec_str)
+    return s
+
+
+def format_xplmn(hexstr: Hexstr) -> str:
+    s = ""
+    for rec_data in hexstr_to_Nbytearr(hexstr, 3):
+        rec_info = dec_xplmn(rec_data)
+        if not rec_info['mcc'] and not rec_info['mnc']:
+            rec_str = "unused"
+        else:
+            rec_str = "MCC: %s MNC: %s" % (rec_info['mcc'], rec_info['mnc'])
+        s += "\t%s # %s\n" % (rec_data, rec_str)
+    return s
+
+
+def format_ePDGSelection(hexstr):
+    ePDGSelection_info_tag_chars = 2
+    ePDGSelection_info_tag_str = hexstr[:2]
+    s = ""
+    # Minimum length
+    len_chars = 2
+    # TODO: Need to determine length properly - definite length support only
+    # Inconsistency in spec: 3GPP TS 31.102 version 15.2.0 Release 15, 4.2.104
+    # As per spec, length is 5n, n - number of PLMNs
+    # But, each PLMN entry is made of PLMN (3 Bytes) + ePDG Priority (2 Bytes) + ePDG FQDN format (1 Byte)
+    # Totalling to 6 Bytes, maybe length should be 6n
+    len_str = hexstr[ePDGSelection_info_tag_chars:ePDGSelection_info_tag_chars+len_chars]
+
+    # Not programmed scenario
+    if int(len_str, 16) == 255 or int(ePDGSelection_info_tag_str, 16) == 255:
+        len_chars = 0
+        ePDGSelection_info_tag_chars = 0
+    if len_str[0] == '8':
+        # The bits 7 to 1 denotes the number of length octets if length > 127
+        if int(len_str[1]) > 0:
+            # Update number of length octets
+            len_chars = len_chars * int(len_str[1])
+            len_str = hexstr[ePDGSelection_info_tag_chars:len_chars]
+
+    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)))
+    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'])
+        s += "\t%s # %s\n" % (rec_data, rec_str)
+    return s
+
+def enc_st(st, service, state=1):
+    """
+    Encodes the EF S/U/IST/EST and returns the updated Service Table
+
+    Parameters:
+            st - Current value of SIM/USIM/ISIM Service Table
+            service - Service Number to encode as activated/de-activated
+            state - 1 mean activate, 0 means de-activate
+
+    Returns:
+            s - Modified value of SIM/USIM/ISIM Service Table
+
+    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)]
+
+    s = ""
+    # Check whether the requested service is present in each byte
+    for i in range(0, len(st_bytes)):
+        # Byte i contains info about Services num (8i+1) to num (8i+8)
+        if service in range((8*i) + 1, (8*i) + 9):
+            byte = int(st_bytes[i], 16)
+            # Services in each byte are in order MSB to LSB
+            # MSB - Service (8i+8)
+            # LSB - Service (8i+1)
+            mod_byte = 0x00
+            # Copy bit by bit contents of byte to mod_byte with modified bit
+            # for requested service
+            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
+                else:
+                    mod_byte = byte & 0x01 == 0x01 and mod_byte | 0x80 or mod_byte & 0x7f
+                byte = byte >> 1
+
+            s += ('%02x' % (mod_byte))
+        else:
+            s += st_bytes[i]
+
+    return s
+
+
+def dec_st(st, table="sim") -> str:
+    """
+    Parses the EF S/U/IST and prints the list of available services in EF S/U/IST
+    """
+
+    if table == "isim":
+        from pySim.ts_31_103 import EF_IST_map
+        lookup_map = EF_IST_map
+    elif table == "usim":
+        from pySim.ts_31_102 import EF_UST_map
+        lookup_map = EF_UST_map
+    else:
+        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)]
+
+    avail_st = ""
+    # Get each byte and check for available services
+    for i in range(0, len(st_bytes)):
+        # Byte i contains info about Services num (8i+1) to num (8i+8)
+        byte = int(st_bytes[i], 16)
+        # Services in each byte are in order MSB to LSB
+        # 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):
+                # 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])
+            byte = byte >> 1
+    return avail_st
+
+
+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.
+    See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
+
+    Default values:
+            - epdg_priority: '0001' - 1st Priority
+            - epdg_fqdn_format: '00' - Operator Identifier FQDN
+    """
+
+    plmn1 = enc_plmn(mcc, mnc) + epdg_priority + epdg_fqdn_format
+    # TODO: Handle encoding of Length field for length more than 127 Bytes
+    content = '80' + ('%02x' % (len(plmn1)//2)) + plmn1
+    content = rpad(content, len(hexstr))
+    return content
+
+
+def dec_ePDGSelection(sixhexbytes):
+    """
+    Decode ePDGSelection to get EF.ePDGSelection or EF.ePDGSelectionEm.
+    See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
+    """
+
+    res = {'mcc': 0, 'mnc': 0, 'epdg_priority': 0, 'epdg_fqdn_format': ''}
+    plmn_chars = 6
+    epdg_priority_chars = 4
+    epdg_fqdn_format_chars = 2
+    # 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]
+    # 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]
+    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