# -*- coding: utf-8 -*-

""" pySim: Card programmation logic
"""

#
# Copyright (C) 2009-2010  Sylvain Munaut <tnt@246tNt.com>
# Copyright (C) 2011  Harald Welte <laforge@gnumonks.org>
# Copyright (C) 2017 Alexander.Chemeris <Alexander.Chemeris@gmail.com>
#
# 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 typing import Optional, Dict, Tuple
import abc

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
from pySim.utils import *
from smartcard.util import toBytes
from pytlv.TLV import *

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(object):

	name = 'SIM'

	def __init__(self, scc):
		self._scc = scc
		self._adm_chv_num = 4
		self._aids = []

	def reset(self):
		self._scc.reset_card()

	def erase(self):
		print("warning: erasing is not supported for specified card type!")
		return

	def file_exists(self, fid):
		res_arr = self._scc.try_select_path(fid)
		for res in res_arr:
			if res[1] != '9000':
				return False
		return True

	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 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'):
		"""
		See note in update_hplmn_act()
		"""
		# get size and write EF.OPLMNwAcT
		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'):
		"""
		See note in update_hplmn_act()
		"""
		# get size and write EF.PLMNwAcT
		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):
		"""
		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

		Returns:
			str: Return code of write operation
		"""

		ad = EF_AD()

		# read from card
		raw_hex_data, sw = self._scc.read_binary(EF['AD'], length=None, offset=0)
		abstract_data = ad.decode_hex(raw_hex_data)

		# perform updates
		if mnc and abstract_data['extensions']:
			mnclen = len(str(mnc))
			if mnclen == 1:
				mnclen = 2
			if mnclen > 3:
				raise RuntimeError('invalid length of mnc "{}"'.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(EF['AD'], 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_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 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)

	# Fetch all the AIDs present on UICC
	def read_aids(self):
		self._aids = []
		try:
			# 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'])
			for i in range(0, rec_cnt):
				rec = self._scc.read_record(EF['DIR'], 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])
		except Exception as e:
			print("Can't read AIDs from SIM -- %s" % (str(e),))
			self._aids = []
		return self._aids

	# Select ADF.U/ISIM in the Card using its full AID
	def select_adf_by_aid(self, adf="usim"):
		# Find full AID by partial AID:
		if is_hex(adf):
			for aid in self._aids:
				if len(aid) >= len(adf) and adf == aid[0:len(adf)]:
					return self._scc.select_adf(aid)
		# Find full AID by application name:
		elif adf in ["usim", "isim"]:
			# First (known) halves of the U/ISIM AID
			aid_map = {}
			aid_map["usim"] = "a0000000871002"
			aid_map["isim"] = "a0000000871004"
			for aid in self._aids:
				if aid_map[adf] in aid:
					return self._scc.select_adf(aid)
		return (None, None)

	# Erase the contents of a file
	def erase_binary(self, ef):
		len = self._scc.binary_size(ef)
		self._scc.update_binary(ef, "ff" * len, offset=0, verify=True)

	# Erase the contents of a single record
	def erase_record(self, ef, rec_no):
		len = self._scc.record_size(ef)
		self._scc.update_record(ef, rec_no, "ff" * len, force_len=False, verify=True)

class UsimCard(SimCard):

	name = 'USIM'

	def __init__(self, ssc):
		super(UsimCard, self).__init__(ssc)

	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_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

class IsimCard(SimCard):

	name = 'ISIM'

	def __init__(self, ssc):
		super(IsimCard, self).__init__(ssc)

	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 inital 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 inital 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 inital 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 inital 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

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):
		# 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 propritary file)
		# TODO: Add programming of KI
		if p.get('ki'):
			print("Warning: Programming of the KI is not implemented for this type of card.")

		# OPc (Presumably a propritary file)
		# TODO: Add programming of OPc
		if p.get('opc'):
			print("Warning: Programming of the OPc is not implemented for this type of card.")

		# 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']))

		# 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)

		# Populate AIDs
		self.read_aids()

		# 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
		data, sw = self.select_adf_by_aid(adf="isim")
		if sw == '9000':
			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)

		data, sw = self.select_adf_by_aid(adf="usim")
		if sw == '9000':
			# 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


# In order for autodetection ...
_cards_classes = [ FakeMagicSim, SuperSim, MagicSim, GrcardSim,
		   SysmoSIMgr1, SysmoSIMgr2, SysmoUSIMgr1, SysmoUSIMSJS1,
		   FairwavesSIM, OpenCellsSim, WavemobileSim, SysmoISIMSJA2 ]

def card_autodetect(scc):
	for kls in _cards_classes:
		card = kls.autodetect(scc)
		if card is not None:
			card.reset()
			return card
	return None

def card_detect(ctype, scc):
	# Detect type if needed
	card = None
	ctypes = dict([(kls.name, kls) for kls in _cards_classes])

	if ctype in ("auto", "auto_once"):
		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

		if ctype == "auto_once":
			ctype = card.name

	elif ctype in ctypes:
		card = ctypes[ctype](scc)

	else:
		raise ValueError("Unknown card type: %s" % ctype)

	return card
