From c4923d41ea78f96ce8c35d38f8022661a8d1d08b Mon Sep 17 00:00:00 2001
From: Laszlo Sitzer <laszlo.sitzer@sumup.com>
Date: Mon, 3 Dec 2018 17:40:20 +0100
Subject: [PATCH] WIP: Decode xPLMNwAcT.

---
 pySim-read.py  |  8 +++---
 pySim/utils.py | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 77 insertions(+), 4 deletions(-)

diff --git a/pySim-read.py b/pySim-read.py
index 4356453..6341fd0 100755
--- a/pySim-read.py
+++ b/pySim-read.py
@@ -37,7 +37,7 @@ except ImportError:
 	import simplejson as json
 
 from pySim.commands import SimCardCommands
-from pySim.utils import h2b, swap_nibbles, rpad, dec_imsi, dec_iccid
+from pySim.utils import h2b, swap_nibbles, rpad, dec_imsi, dec_iccid, format_xplmn_w_act
 
 
 def parse_options():
@@ -129,7 +129,7 @@ if __name__ == '__main__':
         try:
 	        (res, sw) = scc.read_binary(EF['PLMNwAcT'])
 	        if sw == '9000':
-		        print("PLMNwAcT: %s" % (res))
+		        print("PLMNwAcT:\n%s" % (format_xplmn_w_act(res)))
 	        else:
 		        print("PLMNwAcT: Can't read, response code = %s" % (sw,))
 	except Exception as e:
@@ -139,7 +139,7 @@ if __name__ == '__main__':
         try:
 	        (res, sw) = scc.read_binary(EF['OPLMNwAcT'])
 	        if sw == '9000':
-		        print("OPLMNwAcT: %s" % (res))
+		        print("OPLMNwAcT:\n%s" % (format_xplmn_w_act(res)))
 	        else:
 		        print("OPLMNwAcT: Can't read, response code = %s" % (sw,))
 	except Exception as e:
@@ -149,7 +149,7 @@ if __name__ == '__main__':
         try:
 	        (res, sw) = scc.read_binary(EF['HPLMNAcT'])
 	        if sw == '9000':
-		        print("HPLMNAcT: %s" % (res))
+		        print("HPLMNAcT:\n%s" % (format_xplmn_w_act(res)))
 	        else:
 		        print("HPLMNAcT: Can't read, response code = %s" % (sw,))
 	except Exception as e:
diff --git a/pySim/utils.py b/pySim/utils.py
index ba94702..3ea543f 100644
--- a/pySim/utils.py
+++ b/pySim/utils.py
@@ -113,6 +113,79 @@ def enc_spn(name, hplmn_disp=False, oplmn_disp=False):
 	if oplmn_disp: byte1 = byte1|0x02
 	return i2h([byte1])+s2h(name)
 
+def hexstr_to_fivebytearr(s):
+	return [s[i:i+10] for i in range(0, len(s), 10) ]
+
+# Accepts hex string representing three bytes
+def dec_mcc_from_plmn(plmn):
+	ia = h2i(plmn)
+	digit1 = ia[0] & 0x0F		# 1st byte, LSB
+	digit2 = (ia[0] & 0xF0) >> 4	# 1st byte, MSB
+	digit3 = ia[1] & 0x0F		# 2nd byte, LSB
+	if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
+		return 0xFFF # 4095
+	mcc = digit1 * 100
+	mcc += digit2 * 10
+	mcc += digit3
+	return mcc
+
+def dec_mnc_from_plmn(plmn):
+	ia = h2i(plmn)
+	digit1 = ia[2] & 0x0F		# 3rd byte, LSB
+	digit2 = (ia[2] & 0xF0) >> 4	# 3rd byte, MSB
+	digit3 = (ia[1] & 0xF0) >> 4	# 2nd byte, MSB
+	if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
+		return 0xFFF # 4095
+	mnc = 0
+	# signifies two digit MNC
+	if digit3 == 0xF:
+		mnc += digit1 * 10
+		mnc += digit2
+	else:
+		mnc += digit1 * 100
+		mnc += digit2 * 10
+		mnc += digit3
+	return mnc
+
+def dec_act(twohexbytes):
+	act_list = [
+		{'bit': 15, 'name': "UTRAN"},
+		{'bit': 14, 'name': "E-UTRAN"},
+		{'bit':  7, 'name': "GSM"},
+		{'bit':  6, 'name': "GSM COMPACT"},
+		{'bit':  5, 'name': "cdma2000 HRPD"},
+		{'bit':  4, 'name': "cdma2000 1xRTT"},
+	]
+	ia = h2i(twohexbytes)
+	u16t = (ia[0] << 8)|ia[1]
+	sel = []
+	for a in act_list:
+		if u16t & (1 << a['bit']):
+			sel.append(a['name'])
+	return sel
+
+def dec_xplmn_w_act(fivehexbytes):
+	res = {'mcc': 0, 'mnc': 0, 'act': []}
+	plmn_chars = 6
+	act_chars = 4
+	plmn_str = fivehexbytes[:plmn_chars]				# first three bytes (six ascii hex chars)
+	act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars]	# two bytes after first three bytes
+	res['mcc'] = dec_mcc_from_plmn(plmn_str)
+	res['mnc'] = dec_mnc_from_plmn(plmn_str)
+	res['act'] = dec_act(act_str)
+	return res
+
+def format_xplmn_w_act(hexstr):
+	s = ""
+	for rec_data in hexstr_to_fivebytearr(hexstr):
+		rec_info = dec_xplmn_w_act(rec_data)
+		if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
+			rec_str = "unused"
+		else:
+			rec_str = "MCC: %3s MNC: %3s AcT: %s" % (rec_info['mcc'], rec_info['mnc'], ", ".join(rec_info['act']))
+		s += "%s # %s\n" % (rec_data, rec_str)
+	return s
+
 def derive_milenage_opc(ki_hex, op_hex):
 	"""
 	Run the milenage algorithm to calculate OPC from Ki and OP
-- 
2.19.2

