blob: a177c56912152dc06f542a76be316abb6436e88a [file] [log] [blame]
Sylvain Munaut76504e02010-12-07 00:24:32 +01001# -*- coding: utf-8 -*-
2
3""" pySim: various utilities
4"""
5
Harald Welte5e749a72021-04-10 17:18:17 +02006import json
7from io import BytesIO
Harald Welte52255572021-04-03 09:56:32 +02008from typing import Optional, List, Dict, Any, Tuple
9
Sylvain Munaut76504e02010-12-07 00:24:32 +010010# Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com>
Harald Welte5e749a72021-04-10 17:18:17 +020011# Copyright (C) 2021 Harald Welte <laforge@osmocom.org>
Sylvain Munaut76504e02010-12-07 00:24:32 +010012#
13# This program is free software: you can redistribute it and/or modify
14# it under the terms of the GNU General Public License as published by
15# the Free Software Foundation, either version 2 of the License, or
16# (at your option) any later version.
17#
18# This program is distributed in the hope that it will be useful,
19# but WITHOUT ANY WARRANTY; without even the implied warranty of
20# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21# GNU General Public License for more details.
22#
23# You should have received a copy of the GNU General Public License
24# along with this program. If not, see <http://www.gnu.org/licenses/>.
25#
26
Harald Welte52255572021-04-03 09:56:32 +020027# just to differentiate strings of hex nibbles from everything else
28Hexstr = str
Sylvain Munaut76504e02010-12-07 00:24:32 +010029
Harald Welte52255572021-04-03 09:56:32 +020030def h2b(s:Hexstr) -> bytearray:
Harald Welte4f6ca432021-02-01 17:51:56 +010031 """convert from a string of hex nibbles to a sequence of bytes"""
32 return bytearray.fromhex(s)
Sylvain Munaut76504e02010-12-07 00:24:32 +010033
Harald Welte52255572021-04-03 09:56:32 +020034def b2h(b:bytearray) -> Hexstr:
Harald Welte4f6ca432021-02-01 17:51:56 +010035 """convert from a sequence of bytes to a string of hex nibbles"""
36 return ''.join(['%02x'%(x) for x in b])
Sylvain Munaut76504e02010-12-07 00:24:32 +010037
Harald Welte52255572021-04-03 09:56:32 +020038def h2i(s:Hexstr) -> List[int]:
Harald Welteee3501f2021-04-02 13:00:18 +020039 """convert from a string of hex nibbles to a list of integers"""
Sylvain Munaut76504e02010-12-07 00:24:32 +010040 return [(int(x,16)<<4)+int(y,16) for x,y in zip(s[0::2], s[1::2])]
41
Harald Welte52255572021-04-03 09:56:32 +020042def i2h(s:List[int]) -> Hexstr:
Harald Welteee3501f2021-04-02 13:00:18 +020043 """convert from a list of integers to a string of hex nibbles"""
Sylvain Munaut76504e02010-12-07 00:24:32 +010044 return ''.join(['%02x'%(x) for x in s])
45
Harald Welte52255572021-04-03 09:56:32 +020046def h2s(s:Hexstr) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +020047 """convert from a string of hex nibbles to an ASCII string"""
Vadim Yanitskiyeb06b452020-05-10 02:32:46 +070048 return ''.join([chr((int(x,16)<<4)+int(y,16)) for x,y in zip(s[0::2], s[1::2])
49 if int(x + y, 16) != 0xff])
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +030050
Harald Welte52255572021-04-03 09:56:32 +020051def s2h(s:str) -> Hexstr:
Harald Welteee3501f2021-04-02 13:00:18 +020052 """convert from an ASCII string to a string of hex nibbles"""
Harald Welte4f6ca432021-02-01 17:51:56 +010053 b = bytearray()
54 b.extend(map(ord, s))
55 return b2h(b)
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +030056
Supreeth Herle7d77d2d2020-05-11 09:07:08 +020057# List of bytes to string
Harald Welte52255572021-04-03 09:56:32 +020058def i2s(s:List[int]) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +020059 """convert from a list of integers to an ASCII string"""
Supreeth Herle7d77d2d2020-05-11 09:07:08 +020060 return ''.join([chr(x) for x in s])
61
Harald Welte52255572021-04-03 09:56:32 +020062def swap_nibbles(s:Hexstr) -> Hexstr:
Harald Welteee3501f2021-04-02 13:00:18 +020063 """swap the nibbles in a hex string"""
Sylvain Munaut76504e02010-12-07 00:24:32 +010064 return ''.join([x+y for x,y in zip(s[1::2], s[0::2])])
65
Harald Welteee3501f2021-04-02 13:00:18 +020066def rpad(s:str, l:int, c='f') -> str:
67 """pad string on the right side.
68 Args:
69 s : string to pad
70 l : total length to pad to
71 c : padding character
72 Returns:
73 String 's' padded with as many 'c' as needed to reach total length of 'l'
74 """
Sylvain Munaut76504e02010-12-07 00:24:32 +010075 return s + c * (l - len(s))
76
Harald Welteee3501f2021-04-02 13:00:18 +020077def lpad(s:str, l:int, c='f') -> str:
78 """pad string on the left side.
79 Args:
80 s : string to pad
81 l : total length to pad to
82 c : padding character
83 Returns:
84 String 's' padded with as many 'c' as needed to reach total length of 'l'
85 """
Sylvain Munaut76504e02010-12-07 00:24:32 +010086 return c * (l - len(s)) + s
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +040087
Harald Welteee3501f2021-04-02 13:00:18 +020088def half_round_up(n:int) -> int:
Ben Fox-Moore0ec14752018-09-24 15:47:02 +020089 return (n + 1)//2
90
91# IMSI encoded format:
92# For IMSI 0123456789ABCDE:
93#
94# | byte 1 | 2 upper | 2 lower | 3 upper | 3 lower | ... | 9 upper | 9 lower |
95# | length in bytes | 0 | odd/even | 2 | 1 | ... | E | D |
96#
97# If the IMSI is less than 15 characters, it should be padded with 'f' from the end.
98#
99# The length is the total number of bytes used to encoded the IMSI. This includes the odd/even
100# parity bit. E.g. an IMSI of length 14 is 8 bytes long, not 7, as it uses bytes 2 to 9 to
101# encode itself.
102#
103# Because of this, an odd length IMSI fits exactly into len(imsi) + 1 // 2 bytes, whereas an
104# even length IMSI only uses half of the last byte.
105
Harald Welteee3501f2021-04-02 13:00:18 +0200106def enc_imsi(imsi:str):
107 """Converts a string IMSI into the encoded value of the EF"""
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200108 l = half_round_up(len(imsi) + 1) # Required bytes - include space for odd/even indicator
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400109 oe = len(imsi) & 1 # Odd (1) / Even (0)
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200110 ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe<<3)|1, rpad(imsi, 15)))
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400111 return ei
112
Harald Welte52255572021-04-03 09:56:32 +0200113def dec_imsi(ef:Hexstr) -> Optional[str]:
Harald Weltec9cdce32021-04-11 10:28:28 +0200114 """Converts an EF value to the IMSI string representation"""
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400115 if len(ef) < 4:
116 return None
Pau Espin Pedrol665bd222017-12-29 20:30:35 +0100117 l = int(ef[0:2], 16) * 2 # Length of the IMSI string
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200118 l = l - 1 # Encoded length byte includes oe nibble
119 swapped = swap_nibbles(ef[2:]).rstrip('f')
Philipp Maiercd3d6262020-05-11 21:41:56 +0200120 if len(swapped) < 1:
121 return None
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400122 oe = (int(swapped[0])>>3) & 1 # Odd (1) / Even (0)
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200123 if not oe:
124 # if even, only half of last byte was used
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400125 l = l-1
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200126 if l != len(swapped) - 1:
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400127 return None
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200128 imsi = swapped[1:]
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400129 return imsi
130
Harald Welte52255572021-04-03 09:56:32 +0200131def dec_iccid(ef:Hexstr) -> str:
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400132 return swap_nibbles(ef).strip('f')
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400133
Harald Welte52255572021-04-03 09:56:32 +0200134def enc_iccid(iccid:str) -> Hexstr:
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400135 return swap_nibbles(rpad(iccid, 20))
136
137def enc_plmn(mcc, mnc):
Alexander Chemerisdddbf522017-07-18 16:49:59 +0300138 """Converts integer MCC/MNC into 3 bytes for EF"""
Harald Welte7f1d3c42020-05-12 21:12:44 +0200139 if len(mnc) == 2:
Vadim Yanitskiyc8458e22021-03-12 00:34:10 +0100140 mnc += "F" # pad to 3 digits if needed
141 return (mcc[1] + mcc[0]) + (mnc[2] + mcc[2]) + (mnc[1] + mnc[0])
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300142
Harald Welted7a7e172021-04-07 00:30:10 +0200143def dec_plmn(threehexbytes:Hexstr) -> dict:
144 res = {'mcc': 0, 'mnc': 0 }
145 res['mcc'] = dec_mcc_from_plmn(threehexbytes)
146 res['mnc'] = dec_mnc_from_plmn(threehexbytes)
147 return res
148
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300149def dec_spn(ef):
150 byte1 = int(ef[0:2])
151 hplmn_disp = (byte1&0x01 == 0x01)
152 oplmn_disp = (byte1&0x02 == 0x02)
153 name = h2s(ef[2:])
154 return (name, hplmn_disp, oplmn_disp)
155
156def enc_spn(name, hplmn_disp=False, oplmn_disp=False):
157 byte1 = 0x00
158 if hplmn_disp: byte1 = byte1|0x01
159 if oplmn_disp: byte1 = byte1|0x02
160 return i2h([byte1])+s2h(name)
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900161
Supreeth Herlef3948532020-03-24 12:23:51 +0100162def hexstr_to_Nbytearr(s, nbytes):
163 return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2)) ]
164
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100165# Accepts hex string representing three bytes
Harald Welte52255572021-04-03 09:56:32 +0200166def dec_mcc_from_plmn(plmn:Hexstr) -> int:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100167 ia = h2i(plmn)
168 digit1 = ia[0] & 0x0F # 1st byte, LSB
169 digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB
170 digit3 = ia[1] & 0x0F # 2nd byte, LSB
171 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
172 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100173 return derive_mcc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100174
Harald Welte52255572021-04-03 09:56:32 +0200175def dec_mnc_from_plmn(plmn:Hexstr) -> int:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100176 ia = h2i(plmn)
Vadim Yanitskiyb271be32021-03-11 23:56:58 +0100177 digit1 = ia[2] & 0x0F # 3rd byte, LSB
178 digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB
179 digit3 = (ia[1] & 0xF0) >> 4 # 2nd byte, MSB
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100180 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
181 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100182 return derive_mnc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100183
Harald Welte52255572021-04-03 09:56:32 +0200184def dec_act(twohexbytes:Hexstr) -> List[str]:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100185 act_list = [
186 {'bit': 15, 'name': "UTRAN"},
187 {'bit': 14, 'name': "E-UTRAN"},
188 {'bit': 7, 'name': "GSM"},
189 {'bit': 6, 'name': "GSM COMPACT"},
190 {'bit': 5, 'name': "cdma2000 HRPD"},
191 {'bit': 4, 'name': "cdma2000 1xRTT"},
192 ]
193 ia = h2i(twohexbytes)
194 u16t = (ia[0] << 8)|ia[1]
195 sel = []
196 for a in act_list:
197 if u16t & (1 << a['bit']):
198 sel.append(a['name'])
199 return sel
200
Harald Welte52255572021-04-03 09:56:32 +0200201def dec_xplmn_w_act(fivehexbytes:Hexstr) -> Dict[str,Any]:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100202 res = {'mcc': 0, 'mnc': 0, 'act': []}
203 plmn_chars = 6
204 act_chars = 4
205 plmn_str = fivehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
206 act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars] # two bytes after first three bytes
207 res['mcc'] = dec_mcc_from_plmn(plmn_str)
208 res['mnc'] = dec_mnc_from_plmn(plmn_str)
209 res['act'] = dec_act(act_str)
210 return res
211
212def format_xplmn_w_act(hexstr):
213 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200214 for rec_data in hexstr_to_Nbytearr(hexstr, 5):
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100215 rec_info = dec_xplmn_w_act(rec_data)
216 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
217 rec_str = "unused"
218 else:
Supreeth Herled24f1632019-11-30 10:37:09 +0100219 rec_str = "MCC: %03d MNC: %03d AcT: %s" % (rec_info['mcc'], rec_info['mnc'], ", ".join(rec_info['act']))
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100220 s += "\t%s # %s\n" % (rec_data, rec_str)
221 return s
222
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100223def dec_loci(hexstr):
224 res = {'tmsi': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0}
225 res['tmsi'] = hexstr[:8]
226 res['mcc'] = dec_mcc_from_plmn(hexstr[8:14])
227 res['mnc'] = dec_mnc_from_plmn(hexstr[8:14])
228 res['lac'] = hexstr[14:18]
229 res['status'] = h2i(hexstr[20:22])
230 return res
231
232def dec_psloci(hexstr):
233 res = {'p-tmsi': '', 'p-tmsi-sig': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'rac': '', 'status': 0}
234 res['p-tmsi'] = hexstr[:8]
235 res['p-tmsi-sig'] = hexstr[8:14]
236 res['mcc'] = dec_mcc_from_plmn(hexstr[14:20])
237 res['mnc'] = dec_mnc_from_plmn(hexstr[14:20])
238 res['lac'] = hexstr[20:24]
239 res['rac'] = hexstr[24:26]
240 res['status'] = h2i(hexstr[26:28])
241 return res
242
243def dec_epsloci(hexstr):
244 res = {'guti': '', 'mcc': 0, 'mnc': 0, 'tac': '', 'status': 0}
245 res['guti'] = hexstr[:24]
246 res['tai'] = hexstr[24:34]
247 res['mcc'] = dec_mcc_from_plmn(hexstr[24:30])
248 res['mnc'] = dec_mnc_from_plmn(hexstr[24:30])
249 res['tac'] = hexstr[30:34]
250 res['status'] = h2i(hexstr[34:36])
251 return res
252
Harald Welte52255572021-04-03 09:56:32 +0200253def dec_xplmn(threehexbytes:Hexstr) -> dict:
Harald Welteca673942020-06-03 15:19:40 +0200254 res = {'mcc': 0, 'mnc': 0, 'act': []}
255 plmn_chars = 6
256 plmn_str = threehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
257 res['mcc'] = dec_mcc_from_plmn(plmn_str)
258 res['mnc'] = dec_mnc_from_plmn(plmn_str)
259 return res
260
Harald Welte52255572021-04-03 09:56:32 +0200261def format_xplmn(hexstr:Hexstr) -> str:
Harald Welteca673942020-06-03 15:19:40 +0200262 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200263 for rec_data in hexstr_to_Nbytearr(hexstr, 3):
Harald Welteca673942020-06-03 15:19:40 +0200264 rec_info = dec_xplmn(rec_data)
265 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
266 rec_str = "unused"
267 else:
268 rec_str = "MCC: %03d MNC: %03d" % (rec_info['mcc'], rec_info['mnc'])
269 s += "\t%s # %s\n" % (rec_data, rec_str)
270 return s
271
Harald Welte52255572021-04-03 09:56:32 +0200272def derive_milenage_opc(ki_hex:Hexstr, op_hex:Hexstr) -> Hexstr:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900273 """
274 Run the milenage algorithm to calculate OPC from Ki and OP
275 """
276 from Crypto.Cipher import AES
277 from Crypto.Util.strxor import strxor
278 from pySim.utils import b2h
279
280 # We pass in hex string and now need to work on bytes
Harald Welteeab8d2a2021-03-05 18:30:23 +0100281 ki_bytes = bytes(h2b(ki_hex))
282 op_bytes = bytes(h2b(op_hex))
Harald Welteab34fa82021-03-05 18:39:59 +0100283 aes = AES.new(ki_bytes, AES.MODE_ECB)
Harald Welteeab8d2a2021-03-05 18:30:23 +0100284 opc_bytes = aes.encrypt(op_bytes)
285 return b2h(strxor(opc_bytes, op_bytes))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900286
Harald Welte52255572021-04-03 09:56:32 +0200287def calculate_luhn(cc) -> int:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900288 """
289 Calculate Luhn checksum used in e.g. ICCID and IMEI
290 """
Daniel Willmanndd014ea2020-10-19 10:35:11 +0200291 num = list(map(int, str(cc)))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900292 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10
293 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200294
Harald Welte52255572021-04-03 09:56:32 +0200295def mcc_from_imsi(imsi:str) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200296 """
297 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
298 """
299 if imsi == None:
300 return None
301
302 if len(imsi) > 3:
303 return imsi[:3]
304 else:
305 return None
306
Harald Welte52255572021-04-03 09:56:32 +0200307def mnc_from_imsi(imsi:str, long:bool=False) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200308 """
309 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
310 """
311 if imsi == None:
312 return None
313
314 if len(imsi) > 3:
315 if long:
316 return imsi[3:6]
317 else:
318 return imsi[3:5]
319 else:
320 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100321
Harald Welte52255572021-04-03 09:56:32 +0200322def derive_mcc(digit1:int, digit2:int, digit3:int) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100323 """
324 Derive decimal representation of the MCC (Mobile Country Code)
325 from three given digits.
326 """
327
328 mcc = 0
329
330 if digit1 != 0x0f:
331 mcc += digit1 * 100
332 if digit2 != 0x0f:
333 mcc += digit2 * 10
334 if digit3 != 0x0f:
335 mcc += digit3
336
337 return mcc
338
Harald Welte52255572021-04-03 09:56:32 +0200339def derive_mnc(digit1:int, digit2:int, digit3:int=0x0f) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100340 """
341 Derive decimal representation of the MNC (Mobile Network Code)
342 from two or (optionally) three given digits.
343 """
344
345 mnc = 0
346
347 # 3-rd digit is optional for the MNC. If present
348 # the algorythm is the same as for the MCC.
349 if digit3 != 0x0f:
350 return derive_mcc(digit1, digit2, digit3)
351
352 if digit1 != 0x0f:
353 mnc += digit1 * 10
354 if digit2 != 0x0f:
355 mnc += digit2
356
357 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100358
Harald Welte52255572021-04-03 09:56:32 +0200359def dec_msisdn(ef_msisdn:Hexstr) -> Optional[Tuple[int,int,Optional[str]]]:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100360 """
361 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
362 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
363 """
364
365 # Convert from str to (kind of) 'bytes'
366 ef_msisdn = h2b(ef_msisdn)
367
368 # Make sure mandatory fields are present
369 if len(ef_msisdn) < 14:
370 raise ValueError("EF.MSISDN is too short")
371
372 # Skip optional Alpha Identifier
373 xlen = len(ef_msisdn) - 14
374 msisdn_lhv = ef_msisdn[xlen:]
375
376 # Parse the length (in bytes) of the BCD encoded number
Harald Welte4f6ca432021-02-01 17:51:56 +0100377 bcd_len = msisdn_lhv[0]
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100378 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
379 if bcd_len == 0xff:
380 return None
381 elif bcd_len > 11 or bcd_len < 1:
382 raise ValueError("Length of MSISDN (%d bytes) is out of range" % bcd_len)
383
384 # Parse ToN / NPI
Harald Welte4f6ca432021-02-01 17:51:56 +0100385 ton = (msisdn_lhv[1] >> 4) & 0x07
386 npi = msisdn_lhv[1] & 0x0f
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100387 bcd_len -= 1
388
389 # No MSISDN?
390 if not bcd_len:
391 return (npi, ton, None)
392
393 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
394 # International number 10.5.118/3GPP TS 24.008
Vadim Yanitskiy7ba24282020-02-27 00:04:13 +0700395 if ton == 0x01:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100396 msisdn = '+' + msisdn
397
398 return (npi, ton, msisdn)
Supreeth Herle5a541012019-12-22 08:59:16 +0100399
Harald Welte52255572021-04-03 09:56:32 +0200400def enc_msisdn(msisdn:str, npi:int=0x01, ton:int=0x03) -> Hexstr:
Supreeth Herle5a541012019-12-22 08:59:16 +0100401 """
402 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200403 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
404 will not contain the optional Alpha Identifier at the beginning.)
Supreeth Herle5a541012019-12-22 08:59:16 +0100405
406 Default NPI / ToN values:
407 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
408 - ToN: network specific or international number (if starts with '+').
409 """
410
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200411 # If no MSISDN is supplied then encode the file contents as all "ff"
412 if msisdn == "" or msisdn == "+":
413 return "ff" * 14
414
Supreeth Herle5a541012019-12-22 08:59:16 +0100415 # Leading '+' indicates International Number
416 if msisdn[0] == '+':
417 msisdn = msisdn[1:]
418 ton = 0x01
419
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200420 # An MSISDN must not exceed 20 digits
421 if len(msisdn) > 20:
422 raise ValueError("msisdn must not be longer than 20 digits")
423
Supreeth Herle5a541012019-12-22 08:59:16 +0100424 # Append 'f' padding if number of digits is odd
425 if len(msisdn) % 2 > 0:
426 msisdn += 'f'
427
428 # BCD length also includes NPI/ToN header
429 bcd_len = len(msisdn) // 2 + 1
430 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
431 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
432
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200433 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
434
Supreeth Herle441c4a72020-03-24 10:19:15 +0100435
Harald Welte52255572021-04-03 09:56:32 +0200436def dec_st(st, table="sim") -> str:
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200437 """
438 Parses the EF S/U/IST and prints the list of available services in EF S/U/IST
439 """
440
Supreeth Herledf330372020-04-20 14:48:55 +0200441 if table == "isim":
442 from pySim.ts_31_103 import EF_IST_map
443 lookup_map = EF_IST_map
444 elif table == "usim":
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200445 from pySim.ts_31_102 import EF_UST_map
446 lookup_map = EF_UST_map
447 else:
448 from pySim.ts_51_011 import EF_SST_map
449 lookup_map = EF_SST_map
450
451 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
452
453 avail_st = ""
454 # Get each byte and check for available services
455 for i in range(0, len(st_bytes)):
456 # Byte i contains info about Services num (8i+1) to num (8i+8)
457 byte = int(st_bytes[i], 16)
458 # Services in each byte are in order MSB to LSB
459 # MSB - Service (8i+8)
460 # LSB - Service (8i+1)
461 for j in range(1, 9):
462 if byte&0x01 == 0x01 and ((8*i) + j in lookup_map):
463 # Byte X contains info about Services num (8X-7) to num (8X)
464 # bit = 1: service available
465 # bit = 0: service not available
466 avail_st += '\tService %d - %s\n' % ((8*i) + j, lookup_map[(8*i) + j])
467 byte = byte >> 1
468 return avail_st
Supreeth Herle98370552020-05-11 09:04:41 +0200469
470def first_TLV_parser(bytelist):
471 '''
472 first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
473
474 parses first TLV format record in a list of bytelist
475 returns a 3-Tuple: Tag, Length, Value
476 Value is a list of bytes
477 parsing of length is ETSI'style 101.220
478 '''
479 Tag = bytelist[0]
480 if bytelist[1] == 0xFF:
481 Len = bytelist[2]*256 + bytelist[3]
482 Val = bytelist[4:4+Len]
483 else:
484 Len = bytelist[1]
485 Val = bytelist[2:2+Len]
486 return (Tag, Len, Val)
487
488def TLV_parser(bytelist):
489 '''
490 TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
491
492 loops on the input list of bytes with the "first_TLV_parser()" function
493 returns a list of 3-Tuples
494 '''
495 ret = []
496 while len(bytelist) > 0:
497 T, L, V = first_TLV_parser(bytelist)
498 if T == 0xFF:
499 # padding bytes
500 break
501 ret.append( (T, L, V) )
502 # need to manage length of L
503 if L > 0xFE:
504 bytelist = bytelist[ L+4 : ]
505 else:
506 bytelist = bytelist[ L+2 : ]
507 return ret
Supreeth Herled572ede2020-03-22 09:55:04 +0100508
Supreeth Herled84daa12020-03-24 12:20:40 +0100509def enc_st(st, service, state=1):
510 """
511 Encodes the EF S/U/IST/EST and returns the updated Service Table
512
513 Parameters:
514 st - Current value of SIM/USIM/ISIM Service Table
515 service - Service Number to encode as activated/de-activated
516 state - 1 mean activate, 0 means de-activate
517
518 Returns:
519 s - Modified value of SIM/USIM/ISIM Service Table
520
521 Default values:
522 - state: 1 - Sets the particular Service bit to 1
523 """
524 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
525
526 s = ""
527 # Check whether the requested service is present in each byte
528 for i in range(0, len(st_bytes)):
529 # Byte i contains info about Services num (8i+1) to num (8i+8)
530 if service in range((8*i) + 1, (8*i) + 9):
531 byte = int(st_bytes[i], 16)
532 # Services in each byte are in order MSB to LSB
533 # MSB - Service (8i+8)
534 # LSB - Service (8i+1)
535 mod_byte = 0x00
536 # Copy bit by bit contents of byte to mod_byte with modified bit
537 # for requested service
538 for j in range(1, 9):
539 mod_byte = mod_byte >> 1
540 if service == (8*i) + j:
541 mod_byte = state == 1 and mod_byte|0x80 or mod_byte&0x7f
542 else:
543 mod_byte = byte&0x01 == 0x01 and mod_byte|0x80 or mod_byte&0x7f
544 byte = byte >> 1
545
546 s += ('%02x' % (mod_byte))
547 else:
548 s += st_bytes[i]
549
550 return s
551
Supreeth Herle3b342c22020-03-24 16:15:02 +0100552def dec_addr_tlv(hexstr):
Supreeth Herled572ede2020-03-22 09:55:04 +0100553 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100554 Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm.
555 See 3GPP TS 31.102 version 13.4.0 Release 13, section 4.2.8, 4.2.102 and 4.2.104.
Supreeth Herled572ede2020-03-22 09:55:04 +0100556 """
557
558 # Convert from hex str to int bytes list
Supreeth Herle3b342c22020-03-24 16:15:02 +0100559 addr_tlv_bytes = h2i(hexstr)
Supreeth Herled572ede2020-03-22 09:55:04 +0100560
561 s = ""
562
563 # Get list of tuples containing parsed TLVs
Supreeth Herle3b342c22020-03-24 16:15:02 +0100564 tlvs = TLV_parser(addr_tlv_bytes)
Supreeth Herled572ede2020-03-22 09:55:04 +0100565
566 for tlv in tlvs:
567 # tlv = (T, L, [V])
568 # T = Tag
569 # L = Length
570 # [V] = List of value
571
572 # Invalid Tag value scenario
573 if tlv[0] != 0x80:
574 continue
575
Supreeth Herled6a5ec52020-06-01 12:27:51 +0200576 # Empty field - Zero length
577 if tlv[1] == 0:
578 continue
579
Supreeth Herled572ede2020-03-22 09:55:04 +0100580 # First byte in the value has the address type
581 addr_type = tlv[2][0]
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100582 # TODO: Support parsing of IPv6
Supreeth Herle3b342c22020-03-24 16:15:02 +0100583 # Address Type: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), other (Reserved)
Supreeth Herled572ede2020-03-22 09:55:04 +0100584 if addr_type == 0x00: #FQDN
585 # Skip address tye byte i.e. first byte in value list
586 content = tlv[2][1:]
587 s += "\t%s # %s\n" % (i2h(content), i2s(content))
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100588 elif addr_type == 0x01: #IPv4
589 # Skip address tye byte i.e. first byte in value list
590 # Skip the unused byte in Octect 4 after address type byte as per 3GPP TS 31.102
591 ipv4 = tlv[2][2:]
592 content = '.'.join(str(x) for x in ipv4)
593 s += "\t%s # %s\n" % (i2h(ipv4), content)
Supreeth Herled572ede2020-03-22 09:55:04 +0100594
595 return s
Philipp Maierff84c232020-05-12 17:24:18 +0200596
Supreeth Herle3b342c22020-03-24 16:15:02 +0100597def enc_addr_tlv(addr, addr_type='00'):
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100598 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100599 Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm.
600 See 3GPP TS 31.102 version 13.4.0 Release 13, section 4.2.8, 4.2.102 and 4.2.104.
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100601
602 Default values:
Supreeth Herle3b342c22020-03-24 16:15:02 +0100603 - addr_type: 00 - FQDN format of Address
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100604 """
605
606 s = ""
607
Supreeth Herle654eca72020-03-25 14:25:38 +0100608 # TODO: Encoding of IPv6 address
609 if addr_type == '00': #FQDN
Supreeth Herle3b342c22020-03-24 16:15:02 +0100610 hex_str = s2h(addr)
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100611 s += '80' + ('%02x' % ((len(hex_str)//2)+1)) + '00' + hex_str
Supreeth Herle654eca72020-03-25 14:25:38 +0100612 elif addr_type == '01': #IPv4
613 ipv4_list = addr.split('.')
614 ipv4_str = ""
615 for i in ipv4_list:
616 ipv4_str += ('%02x' % (int(i)))
617
618 # Unused bytes shall be set to 'ff'. i.e 4th Octet after Address Type is not used
619 # IPv4 Address is in octet 5 to octet 8 of the TLV data object
620 s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100621
622 return s
623
Harald Welte52255572021-04-03 09:56:32 +0200624def is_hex(string:str, minlen:int=2, maxlen:Optional[int]=None) -> bool:
Philipp Maier47236502021-03-09 21:28:25 +0100625 """
626 Check if a string is a valid hexstring
627 """
628
629 # Filter obviously bad strings
630 if not string:
631 return False
632 if len(string) < minlen or minlen < 2:
633 return False
634 if len(string) % 2:
635 return False
636 if maxlen and len(string) > maxlen:
637 return False
638
639 # Try actual encoding to be sure
640 try:
641 try_encode = h2b(string)
642 return True
643 except:
644 return False
645
Harald Welte52255572021-04-03 09:56:32 +0200646def sanitize_pin_adm(pin_adm, pin_adm_hex = None) -> Hexstr:
Philipp Maiere8536c02020-05-11 21:35:01 +0200647 """
648 The ADM pin can be supplied either in its hexadecimal form or as
649 ascii string. This function checks the supplied opts parameter and
Harald Welte236a65f2021-04-03 10:20:11 +0200650 returns the pin_adm as hex encoded string, regardless in which form
Philipp Maiere8536c02020-05-11 21:35:01 +0200651 it was originally supplied by the user
652 """
653
Harald Welte79b5ba42021-01-08 21:22:38 +0100654 if pin_adm is not None:
655 if len(pin_adm) <= 8:
656 pin_adm = ''.join(['%02x'%(ord(x)) for x in pin_adm])
Philipp Maiere8536c02020-05-11 21:35:01 +0200657 pin_adm = rpad(pin_adm, 16)
658
659 else:
660 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
661
Harald Welte79b5ba42021-01-08 21:22:38 +0100662 if pin_adm_hex is not None:
663 if len(pin_adm_hex) == 16:
664 pin_adm = pin_adm_hex
Philipp Maiere8536c02020-05-11 21:35:01 +0200665 # Ensure that it's hex-encoded
666 try:
667 try_encode = h2b(pin_adm)
668 except ValueError:
669 raise ValueError("PIN-ADM needs to be hex encoded using this option")
670 else:
671 raise ValueError("PIN-ADM needs to be exactly 16 digits (hex encoded)")
672
673 return pin_adm
674
Supreeth Herle95ec7722020-03-24 13:09:03 +0100675def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='00'):
676 """
677 Encode ePDGSelection so it can be stored at EF.ePDGSelection or EF.ePDGSelectionEm.
678 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
679
680 Default values:
681 - epdg_priority: '0001' - 1st Priority
682 - epdg_fqdn_format: '00' - Operator Identifier FQDN
683 """
684
685 plmn1 = enc_plmn(mcc, mnc) + epdg_priority + epdg_fqdn_format
686 # TODO: Handle encoding of Length field for length more than 127 Bytes
687 content = '80' + ('%02x' % (len(plmn1)//2)) + plmn1
688 content = rpad(content, len(hexstr))
689 return content
690
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100691def dec_ePDGSelection(sixhexbytes):
692 """
693 Decode ePDGSelection to get EF.ePDGSelection or EF.ePDGSelectionEm.
694 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
695 """
696
697 res = {'mcc': 0, 'mnc': 0, 'epdg_priority': 0, 'epdg_fqdn_format': ''}
698 plmn_chars = 6
699 epdg_priority_chars = 4
700 epdg_fqdn_format_chars = 2
701 # first three bytes (six ascii hex chars)
702 plmn_str = sixhexbytes[:plmn_chars]
703 # two bytes after first three bytes
704 epdg_priority_str = sixhexbytes[plmn_chars:plmn_chars + epdg_priority_chars]
705 # one byte after first five bytes
706 epdg_fqdn_format_str = sixhexbytes[plmn_chars + epdg_priority_chars:plmn_chars + epdg_priority_chars + epdg_fqdn_format_chars]
707 res['mcc'] = dec_mcc_from_plmn(plmn_str)
708 res['mnc'] = dec_mnc_from_plmn(plmn_str)
709 res['epdg_priority'] = epdg_priority_str
710 res['epdg_fqdn_format'] = epdg_fqdn_format_str == '00' and 'Operator Identifier FQDN' or 'Location based FQDN'
711 return res
712
713def format_ePDGSelection(hexstr):
714 ePDGSelection_info_tag_chars = 2
715 ePDGSelection_info_tag_str = hexstr[:2]
herlesupreeth3a261d32021-01-05 09:20:11 +0100716 s = ""
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100717 # Minimum length
718 len_chars = 2
719 # TODO: Need to determine length properly - definite length support only
720 # Inconsistency in spec: 3GPP TS 31.102 version 15.2.0 Release 15, 4.2.104
721 # As per spec, length is 5n, n - number of PLMNs
722 # But, each PLMN entry is made of PLMN (3 Bytes) + ePDG Priority (2 Bytes) + ePDG FQDN format (1 Byte)
723 # Totalling to 6 Bytes, maybe length should be 6n
724 len_str = hexstr[ePDGSelection_info_tag_chars:ePDGSelection_info_tag_chars+len_chars]
herlesupreeth3a261d32021-01-05 09:20:11 +0100725
726 # Not programmed scenario
727 if int(len_str, 16) == 255 or int(ePDGSelection_info_tag_str, 16) == 255:
728 len_chars = 0
729 ePDGSelection_info_tag_chars = 0
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100730 if len_str[0] == '8':
731 # The bits 7 to 1 denotes the number of length octets if length > 127
732 if int(len_str[1]) > 0:
733 # Update number of length octets
734 len_chars = len_chars * int(len_str[1])
735 len_str = hexstr[ePDGSelection_info_tag_chars:len_chars]
736
737 content_str = hexstr[ePDGSelection_info_tag_chars+len_chars:]
738 # Right pad to prevent index out of range - multiple of 6 bytes
739 content_str = rpad(content_str, len(content_str) + (12 - (len(content_str) % 12)))
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100740 for rec_data in hexstr_to_Nbytearr(content_str, 6):
741 rec_info = dec_ePDGSelection(rec_data)
742 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
743 rec_str = "unused"
744 else:
745 rec_str = "MCC: %03d MNC: %03d ePDG Priority: %s ePDG FQDN format: %s" % \
746 (rec_info['mcc'], rec_info['mnc'], rec_info['epdg_priority'], rec_info['epdg_fqdn_format'])
747 s += "\t%s # %s\n" % (rec_data, rec_str)
748 return s
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100749
750def get_addr_type(addr):
751 """
752 Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
753 Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given)
754
755 TODO: Handle IPv6
756 """
757
758 # Empty address string
759 if not len(addr):
760 return None
761
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100762 addr_list = addr.split('.')
763
764 # Check for IPv4/IPv6
765 try:
766 import ipaddress
767 # Throws ValueError if addr is not correct
Denis 'GNUtoo' Carikli79f5b602020-02-15 04:02:57 +0700768 ipa = ipaddress.ip_address(addr)
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100769
770 if ipa.version == 4:
771 return 0x01
772 elif ipa.version == 6:
773 return 0x02
774 except Exception as e:
775 invalid_ipv4 = True
776 for i in addr_list:
777 # Invalid IPv4 may qualify for a valid FQDN, so make check here
778 # e.g. 172.24.15.300
779 import re
780 if not re.match('^[0-9_]+$', i):
781 invalid_ipv4 = False
782 break
783
784 if invalid_ipv4:
785 return None
786
787 fqdn_flag = True
788 for i in addr_list:
789 # Only Alpha-numeric characters and hyphen - RFC 1035
790 import re
791 if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
792 fqdn_flag = False
793 break
794
795 # FQDN
796 if fqdn_flag:
797 return 0x00
798
799 return None
Harald Welte67d551a2021-01-21 14:50:01 +0100800
Harald Welte1e456572021-04-02 17:16:30 +0200801def sw_match(sw:str, pattern:str) -> bool:
Harald Welte67d551a2021-01-21 14:50:01 +0100802 """Match given SW against given pattern."""
803 # Create a masked version of the returned status word
804 sw_lower = sw.lower()
805 sw_masked = ""
806 for i in range(0, 4):
Philipp Maier78e32f22021-03-22 20:39:24 +0100807 if pattern[i] == '?':
Harald Welte67d551a2021-01-21 14:50:01 +0100808 sw_masked = sw_masked + '?'
Philipp Maier78e32f22021-03-22 20:39:24 +0100809 elif pattern[i] == 'x':
Harald Welte67d551a2021-01-21 14:50:01 +0100810 sw_masked = sw_masked + 'x'
811 else:
812 sw_masked = sw_masked + sw_lower[i]
Philipp Maier78e32f22021-03-22 20:39:24 +0100813 # Compare the masked version against the pattern
Harald Welte67d551a2021-01-21 14:50:01 +0100814 return sw_masked == pattern
Philipp Maier5d3e2592021-02-22 17:22:16 +0100815
Harald Welteee3501f2021-04-02 13:00:18 +0200816def tabulate_str_list(str_list, width:int = 79, hspace:int = 2, lspace:int = 1,
Harald Welte52255572021-04-03 09:56:32 +0200817 align_left:bool = True) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +0200818 """Pretty print a list of strings into a tabulated form.
819
820 Args:
821 width : total width in characters per line
822 space : horizontal space between cells
823 lspace : number of spaces before row
824 align_lef : Align text to the left side
825 Returns:
826 multi-line string containing formatted table
827 """
Philipp Maier5d3e2592021-02-22 17:22:16 +0100828 if str_list == None:
829 return ""
830 if len(str_list) <= 0:
831 return ""
832 longest_str = max(str_list, key=len)
833 cellwith = len(longest_str) + hspace
834 cols = width // cellwith
835 rows = (len(str_list) - 1) // cols + 1
836 table = []
837 for i in iter(range(rows)):
838 str_list_row = str_list[i::rows]
839 if (align_left):
840 format_str_cell = '%%-%ds'
841 else:
842 format_str_cell = '%%%ds'
843 format_str_row = (format_str_cell % cellwith) * len(str_list_row)
844 format_str_row = (" " * lspace) + format_str_row
845 table.append(format_str_row % tuple(str_list_row))
846 return '\n'.join(table)
Harald Welte5e749a72021-04-10 17:18:17 +0200847
848class JsonEncoder(json.JSONEncoder):
849 """Extend the standard library JSONEncoder with support for more types."""
850 def default(self, o):
851 if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
852 return b2h(o)
853 return json.JSONEncoder.default(self, o)
Philipp Maier80ce71f2021-04-19 21:24:23 +0200854
855def boxed_heading_str(heading, width=80):
856 """Generate a string that contains a boxed heading."""
857 # Auto-enlarge box if heading exceeds length
858 if len(heading) > width - 4:
859 width = len(heading) + 4
860
861 res = "#" * width
862 fstr = "\n# %-" + str(width - 4) + "s #\n"
863 res += fstr % (heading)
864 res += "#" * width
865 return res