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-prog.py b/pySim-prog.py
index 028db55..2e9d5d7 100755
--- a/pySim-prog.py
+++ b/pySim-prog.py
@@ -36,7 +36,7 @@
 
 from pySim.commands import SimCardCommands
 from pySim.transport import init_reader
-from pySim.cards import _cards_classes, card_detect
+from pySim.legacy.cards import _cards_classes, card_detect
 from pySim.utils import h2b, swap_nibbles, rpad, derive_milenage_opc, calculate_luhn, dec_iccid
 from pySim.ts_51_011 import EF, EF_AD
 from pySim.card_handler import *
diff --git a/pySim-read.py b/pySim-read.py
index f80509c..52c60be 100755
--- a/pySim-read.py
+++ b/pySim-read.py
@@ -35,10 +35,9 @@
 from pySim.commands import SimCardCommands
 from pySim.transport import init_reader, argparse_add_reader_args
 from pySim.exceptions import SwMatchError
-from pySim.cards import card_detect, SimCard, UsimCard, IsimCard
-from pySim.utils import h2b, swap_nibbles, rpad, dec_imsi, dec_iccid, dec_msisdn
-from pySim.utils import format_xplmn_w_act, dec_st
-from pySim.utils import h2s, format_ePDGSelection
+from pySim.legacy.cards import card_detect, SimCard, UsimCard, IsimCard
+from pySim.utils import h2b, h2s, swap_nibbles, rpad, dec_imsi, dec_iccid, dec_msisdn
+from pySim.legacy.utils import format_xplmn_w_act, dec_st
 
 option_parser = argparse.ArgumentParser(description='Legacy tool for reading some parts of a SIM card',
                                         formatter_class=argparse.ArgumentDefaultsHelpFormatter)
diff --git a/pySim-shell.py b/pySim-shell.py
index a65e42a..4d4f2e2 100755
--- a/pySim-shell.py
+++ b/pySim-shell.py
@@ -49,7 +49,7 @@
 from pySim.exceptions import *
 from pySim.commands import SimCardCommands
 from pySim.transport import init_reader, ApduTracer, argparse_add_reader_args, ProactiveHandler
-from pySim.cards import card_detect, SimCard, UsimCard
+from pySim.cards import card_detect, SimCardBase, UiccCardBase
 from pySim.utils import h2b, b2h, i2h, swap_nibbles, rpad, JsonEncoder, bertlv_parse_one, sw_match
 from pySim.utils import sanitize_pin_adm, tabulate_str_list, boxed_heading_str, Hexstr
 from pySim.card_handler import CardHandler, CardHandlerAuto
@@ -95,10 +95,10 @@
         return None, None
 
     generic_card = False
-    card = card_detect("auto", scc)
+    card = card_detect(scc)
     if card is None:
         print("Warning: Could not detect card type - assuming a generic card type...")
-        card = SimCard(scc)
+        card = SimCardBase(scc)
         generic_card = True
 
     profile = CardProfile.pick(scc)
@@ -132,7 +132,7 @@
         # IF we don't do this, we will have a SimCard but try USIM specific commands like
         # the update_ust method (see https://osmocom.org/issues/6055)
         if generic_card:
-            card = UsimCard(scc)
+            card = UiccCardBase(scc)
 
     # Create runtime state with card profile
     rs = RuntimeState(card, profile)
diff --git a/pySim-trace.py b/pySim-trace.py
index bb1805c..4c8696d 100755
--- a/pySim-trace.py
+++ b/pySim-trace.py
@@ -8,7 +8,7 @@
 from pySim.apdu import *
 from pySim.filesystem import RuntimeState
 
-from pySim.cards import UsimCard
+from pySim.cards import UiccCardBase
 from pySim.commands import SimCardCommands
 from pySim.profile import CardProfile
 from pySim.ts_102_221 import CardProfileUICCSIM
@@ -36,13 +36,13 @@
 
 class DummySimLink(LinkBase):
     """A dummy implementation of the LinkBase abstract base class.  Currently required
-    as the UsimCard doesn't work without SimCardCommands, which in turn require
+    as the UiccCardBase doesn't work without SimCardCommands, which in turn require
     a LinkBase implementation talking to a card.
 
     In the tracer, we don't actually talk to any card, so we simply drop everything
     and claim it is successful.
 
-    The UsimCard / SimCardCommands should be refactored to make this obsolete later."""
+    The UiccCardBase / SimCardCommands should be refactored to make this obsolete later."""
     def __init__(self, debug: bool = False, **kwargs):
         super().__init__(**kwargs)
         self._debug = debug
@@ -75,7 +75,7 @@
         profile.add_application(CardApplicationUSIM())
         profile.add_application(CardApplicationISIM())
         scc = SimCardCommands(transport=DummySimLink())
-        card = UsimCard(scc)
+        card = UiccCardBase(scc)
         self.rs = RuntimeState(card, profile)
         # APDU Decoder
         self.ad = ApduDecoder(ApduCommands)
diff --git a/pySim/cards.py b/pySim/cards.py
index 4481117..f44c54f 100644
--- a/pySim/cards.py
+++ b/pySim/cards.py
@@ -23,6 +23,8 @@
 #
 
 from typing import Optional, Dict, Tuple
+from pySim.ts_102_221 import EF_DIR, EF_ICCID
+from pySim.ts_51_011 import DF_GSM
 import abc
 
 from pySim.utils import *
@@ -65,21 +67,23 @@
         # callers having to do hasattr('read_aids') ahead of every call.
         return []
 
+    def read_iccid(self):
+        ef_iccid = EF_ICCID()
+        (res, sw) = self._scc.read_binary(ef_iccid.fid)
+        if sw == '9000':
+            return (dec_iccid(res), sw)
+        else:
+            return (None, sw)
+
 
 class SimCardBase(CardBase):
-
     """Here we only add methods for commands specified in TS 51.011, without
     any higher-layer processing."""
-
     name = 'SIM'
 
-    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 probe(self):
+        df_gsm = DF_GSM()
+        return self.file_exists(df_gsm.fid)
 
 
 class UiccCardBase(SimCardBase):
@@ -90,244 +94,21 @@
 	    # See also: ETSI TS 102 221, Table 9.3
         self._adm_chv_num = 0xA0
 
-################################################################################
-# LEGACY
-################################################################################
-
-from smartcard.util import toBytes
-from pytlv.TLV import *
-
-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 verify_adm(self, key):
-        """Authenticate with ADM key"""
-        (res, sw) = self._scc.verify_chv(self._adm_chv_num, key)
-        return sw
-
-    def read_iccid(self):
-        (res, sw) = self._scc.read_binary(EF['ICCID'])
-        if sw == '9000':
-            return (dec_iccid(res), sw)
-        else:
-            return (None, 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 probe(self):
+        # EF.DIR is a mandatory EF on all ICCIDs; however it *may* also exist on a TS 51.011 SIM
+        ef_dir = EF_DIR()
+        return self.file_exists(ef_dir.fid)
 
     def read_aids(self):
         """Fetch all the AIDs present on UICC"""
         self._aids = []
         try:
+            ef_dir = EF_DIR()
             # Find out how many records the EF.DIR has
             # and store all the AIDs in the UICC
-            rec_cnt = self._scc.record_count(EF['DIR'])
+            rec_cnt = self._scc.record_count(ef_dir.fid)
             for i in range(0, rec_cnt):
-                rec = self._scc.read_record(EF['DIR'], i + 1)
+                rec = self._scc.read_record(ef_dir.fid, i + 1)
                 if (rec[0][0:2], rec[0][4:6]) == ('61', '4f') and len(rec[0]) > 12 \
                         and rec[0][8:8 + int(rec[0][6:8], 16) * 2] not in self._aids:
                     self._aids.append(rec[0][8:8 + int(rec[0][6:8], 16) * 2])
@@ -380,1431 +161,15 @@
                 return self._scc.select_adf(aid)
         return (None, None)
 
+def card_detect(scc):
+    # UICC always has higher preference, as a UICC might also contain a SIM application
+    uicc = UiccCardBase(scc)
+    if uicc.probe():
+        return uicc
 
-    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)
+    # this is for detecting a real, classic TS 11.11 SIM card without any UICC support
+    sim = SimCardBase(scc)
+    if sim.probe():
+        return sim
 
-    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
+    return None
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
diff --git a/pySim/utils.py b/pySim/utils.py
index 9defaf3..c25560c 100644
--- a/pySim/utils.py
+++ b/pySim/utils.py
@@ -435,30 +435,6 @@
     return res
 
 
-def dec_spn(ef):
-    """Obsolete, kept for API compatibility"""
-    from ts_51_011 import EF_SPN
-    abstract_data = EF_SPN().decode_hex(ef)
-    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)
-
-
-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,
-    }
-    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))]
-
 # Accepts hex string representing three bytes
 
 
@@ -544,53 +520,6 @@
     return res
 
 
-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 dec_loci(hexstr):
-    res = {'tmsi': '',  'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0}
-    res['tmsi'] = hexstr[:8]
-    res['mcc'] = dec_mcc_from_plmn(hexstr[8:14])
-    res['mnc'] = dec_mnc_from_plmn(hexstr[8:14])
-    res['lac'] = hexstr[14:18]
-    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'] = hexstr[:8]
-    res['p-tmsi-sig'] = hexstr[8:14]
-    res['mcc'] = dec_mcc_from_plmn(hexstr[14:20])
-    res['mnc'] = dec_mnc_from_plmn(hexstr[14:20])
-    res['lac'] = hexstr[20:24]
-    res['rac'] = hexstr[24:26]
-    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]
-    res['tai'] = hexstr[24:34]
-    res['mcc'] = dec_mcc_from_plmn(hexstr[24:30])
-    res['mnc'] = dec_mnc_from_plmn(hexstr[24:30])
-    res['tac'] = hexstr[30:34]
-    res['status'] = h2i(hexstr[34:36])
-    return res
-
-
 def dec_xplmn(threehexbytes: Hexstr) -> dict:
     res = {'mcc': 0, 'mnc': 0, 'act': []}
     plmn_chars = 6
@@ -601,18 +530,6 @@
     return res
 
 
-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 derive_milenage_opc(ki_hex: Hexstr, op_hex: Hexstr) -> Hexstr:
     """
     Run the milenage algorithm to calculate OPC from Ki and OP
@@ -787,42 +704,6 @@
     return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
 
 
-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 first_TLV_parser(bytelist):
     '''
     first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
@@ -864,50 +745,6 @@
     return ret
 
 
-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_addr_tlv(hexstr):
     """
     Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm.
@@ -1038,88 +875,6 @@
     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.
-    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
-
-
-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 get_addr_type(addr):
     """
     Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
diff --git a/setup.py b/setup.py
index 5678c55..f776443 100644
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@
 setup(
     name='pySim',
     version='1.0',
-    packages=['pySim', 'pySim.transport', 'pySim.apdu', 'pySim.apdu_source'],
+    packages=['pySim', 'pySim.legacy', 'pySim.transport', 'pySim.apdu', 'pySim.apdu_source'],
     url='https://osmocom.org/projects/pysim/wiki',
     license='GPLv2',
     author_email='simtrace@lists.osmocom.org',
diff --git a/tests/test_utils.py b/tests/test_utils.py
index b7f790d..764cf71 100755
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -2,6 +2,7 @@
 
 import unittest
 from pySim import utils
+from pySim.legacy import utils as legacy_utils
 from pySim.ts_31_102 import EF_SUCI_Calc_Info
 
 # we don't really want to thest TS 102 221, but the underlying DataObject codebase
@@ -45,7 +46,7 @@
 			"ffffff0002",
 			"ffffff0001",
 		]
-		self.assertEqual(utils.hexstr_to_Nbytearr(input_str, 5), expected)
+		self.assertEqual(legacy_utils.hexstr_to_Nbytearr(input_str, 5), expected)
 
 	def testDecMCCfromPLMN(self):
 		self.assertEqual(utils.dec_mcc_from_plmn("92f501"), 295)
@@ -130,7 +131,7 @@
 		expected += "\tffffff0000 # unused\n"
 		expected += "\tffffff0000 # unused\n"
 		expected += "\tffffff0000 # unused\n"
-		self.assertEqual(utils.format_xplmn_w_act(input_str), expected)
+		self.assertEqual(legacy_utils.format_xplmn_w_act(input_str), expected)
 
 
 	def testDecodeSuciCalcInfo(self):