blob: 3e673865d599f69dda6479ad13680250cb4e483a [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
Harald Welte3de6ca22021-05-02 20:05:56 +02007import abc
Harald Welte5e749a72021-04-10 17:18:17 +02008from io import BytesIO
Harald Welte52255572021-04-03 09:56:32 +02009from typing import Optional, List, Dict, Any, Tuple
10
Sylvain Munaut76504e02010-12-07 00:24:32 +010011# Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com>
Harald Welte5e749a72021-04-10 17:18:17 +020012# Copyright (C) 2021 Harald Welte <laforge@osmocom.org>
Sylvain Munaut76504e02010-12-07 00:24:32 +010013#
14# This program is free software: you can redistribute it and/or modify
15# it under the terms of the GNU General Public License as published by
16# the Free Software Foundation, either version 2 of the License, or
17# (at your option) any later version.
18#
19# This program is distributed in the hope that it will be useful,
20# but WITHOUT ANY WARRANTY; without even the implied warranty of
21# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22# GNU General Public License for more details.
23#
24# You should have received a copy of the GNU General Public License
25# along with this program. If not, see <http://www.gnu.org/licenses/>.
26#
27
Harald Welte52255572021-04-03 09:56:32 +020028# just to differentiate strings of hex nibbles from everything else
29Hexstr = str
Sylvain Munaut76504e02010-12-07 00:24:32 +010030
Harald Welte52255572021-04-03 09:56:32 +020031def h2b(s:Hexstr) -> bytearray:
Harald Welte4f6ca432021-02-01 17:51:56 +010032 """convert from a string of hex nibbles to a sequence of bytes"""
33 return bytearray.fromhex(s)
Sylvain Munaut76504e02010-12-07 00:24:32 +010034
Harald Welte52255572021-04-03 09:56:32 +020035def b2h(b:bytearray) -> Hexstr:
Harald Welte4f6ca432021-02-01 17:51:56 +010036 """convert from a sequence of bytes to a string of hex nibbles"""
37 return ''.join(['%02x'%(x) for x in b])
Sylvain Munaut76504e02010-12-07 00:24:32 +010038
Harald Welte52255572021-04-03 09:56:32 +020039def h2i(s:Hexstr) -> List[int]:
Harald Welteee3501f2021-04-02 13:00:18 +020040 """convert from a string of hex nibbles to a list of integers"""
Sylvain Munaut76504e02010-12-07 00:24:32 +010041 return [(int(x,16)<<4)+int(y,16) for x,y in zip(s[0::2], s[1::2])]
42
Harald Welte52255572021-04-03 09:56:32 +020043def i2h(s:List[int]) -> Hexstr:
Harald Welteee3501f2021-04-02 13:00:18 +020044 """convert from a list of integers to a string of hex nibbles"""
Sylvain Munaut76504e02010-12-07 00:24:32 +010045 return ''.join(['%02x'%(x) for x in s])
46
Harald Welte52255572021-04-03 09:56:32 +020047def h2s(s:Hexstr) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +020048 """convert from a string of hex nibbles to an ASCII string"""
Vadim Yanitskiyeb06b452020-05-10 02:32:46 +070049 return ''.join([chr((int(x,16)<<4)+int(y,16)) for x,y in zip(s[0::2], s[1::2])
50 if int(x + y, 16) != 0xff])
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +030051
Harald Welte52255572021-04-03 09:56:32 +020052def s2h(s:str) -> Hexstr:
Harald Welteee3501f2021-04-02 13:00:18 +020053 """convert from an ASCII string to a string of hex nibbles"""
Harald Welte4f6ca432021-02-01 17:51:56 +010054 b = bytearray()
55 b.extend(map(ord, s))
56 return b2h(b)
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +030057
Supreeth Herle7d77d2d2020-05-11 09:07:08 +020058# List of bytes to string
Harald Welte52255572021-04-03 09:56:32 +020059def i2s(s:List[int]) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +020060 """convert from a list of integers to an ASCII string"""
Supreeth Herle7d77d2d2020-05-11 09:07:08 +020061 return ''.join([chr(x) for x in s])
62
Harald Welte52255572021-04-03 09:56:32 +020063def swap_nibbles(s:Hexstr) -> Hexstr:
Harald Welteee3501f2021-04-02 13:00:18 +020064 """swap the nibbles in a hex string"""
Sylvain Munaut76504e02010-12-07 00:24:32 +010065 return ''.join([x+y for x,y in zip(s[1::2], s[0::2])])
66
Harald Welteee3501f2021-04-02 13:00:18 +020067def rpad(s:str, l:int, c='f') -> str:
68 """pad string on the right side.
69 Args:
70 s : string to pad
71 l : total length to pad to
72 c : padding character
73 Returns:
74 String 's' padded with as many 'c' as needed to reach total length of 'l'
75 """
Sylvain Munaut76504e02010-12-07 00:24:32 +010076 return s + c * (l - len(s))
77
Harald Welteee3501f2021-04-02 13:00:18 +020078def lpad(s:str, l:int, c='f') -> str:
79 """pad string on the left side.
80 Args:
81 s : string to pad
82 l : total length to pad to
83 c : padding character
84 Returns:
85 String 's' padded with as many 'c' as needed to reach total length of 'l'
86 """
Sylvain Munaut76504e02010-12-07 00:24:32 +010087 return c * (l - len(s)) + s
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +040088
Harald Welteee3501f2021-04-02 13:00:18 +020089def half_round_up(n:int) -> int:
Ben Fox-Moore0ec14752018-09-24 15:47:02 +020090 return (n + 1)//2
91
92# IMSI encoded format:
93# For IMSI 0123456789ABCDE:
94#
95# | byte 1 | 2 upper | 2 lower | 3 upper | 3 lower | ... | 9 upper | 9 lower |
96# | length in bytes | 0 | odd/even | 2 | 1 | ... | E | D |
97#
98# If the IMSI is less than 15 characters, it should be padded with 'f' from the end.
99#
100# The length is the total number of bytes used to encoded the IMSI. This includes the odd/even
101# parity bit. E.g. an IMSI of length 14 is 8 bytes long, not 7, as it uses bytes 2 to 9 to
102# encode itself.
103#
104# Because of this, an odd length IMSI fits exactly into len(imsi) + 1 // 2 bytes, whereas an
105# even length IMSI only uses half of the last byte.
106
Harald Welteee3501f2021-04-02 13:00:18 +0200107def enc_imsi(imsi:str):
108 """Converts a string IMSI into the encoded value of the EF"""
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200109 l = half_round_up(len(imsi) + 1) # Required bytes - include space for odd/even indicator
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400110 oe = len(imsi) & 1 # Odd (1) / Even (0)
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200111 ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe<<3)|1, rpad(imsi, 15)))
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400112 return ei
113
Harald Welte52255572021-04-03 09:56:32 +0200114def dec_imsi(ef:Hexstr) -> Optional[str]:
Harald Weltec9cdce32021-04-11 10:28:28 +0200115 """Converts an EF value to the IMSI string representation"""
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400116 if len(ef) < 4:
117 return None
Pau Espin Pedrol665bd222017-12-29 20:30:35 +0100118 l = int(ef[0:2], 16) * 2 # Length of the IMSI string
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200119 l = l - 1 # Encoded length byte includes oe nibble
120 swapped = swap_nibbles(ef[2:]).rstrip('f')
Philipp Maiercd3d6262020-05-11 21:41:56 +0200121 if len(swapped) < 1:
122 return None
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400123 oe = (int(swapped[0])>>3) & 1 # Odd (1) / Even (0)
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200124 if not oe:
125 # if even, only half of last byte was used
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400126 l = l-1
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200127 if l != len(swapped) - 1:
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400128 return None
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200129 imsi = swapped[1:]
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400130 return imsi
131
Harald Welte52255572021-04-03 09:56:32 +0200132def dec_iccid(ef:Hexstr) -> str:
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400133 return swap_nibbles(ef).strip('f')
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400134
Harald Welte52255572021-04-03 09:56:32 +0200135def enc_iccid(iccid:str) -> Hexstr:
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400136 return swap_nibbles(rpad(iccid, 20))
137
Philipp Maiere6f8d682021-04-23 21:14:41 +0200138def enc_plmn(mcc:Hexstr, mnc:Hexstr) -> Hexstr:
Alexander Chemerisdddbf522017-07-18 16:49:59 +0300139 """Converts integer MCC/MNC into 3 bytes for EF"""
Philipp Maier6c5cd802021-04-23 21:19:36 +0200140
141 # Make sure there are no excess whitespaces in the input
142 # parameters
143 mcc = mcc.strip()
144 mnc = mnc.strip()
145
146 # Make sure that MCC/MNC are correctly padded with leading
147 # zeros or 'F', depending on the length.
148 if len(mnc) == 0:
149 mnc = "FFF"
150 elif len(mnc) == 1:
151 mnc = "F0" + mnc
152 elif len(mnc) == 2:
153 mnc += "F"
154
155 if len(mcc) == 0:
156 mcc = "FFF"
157 elif len(mcc) == 1:
158 mcc = "00" + mcc
159 elif len(mcc) == 2:
160 mcc = "0" + mcc
161
Vadim Yanitskiyc8458e22021-03-12 00:34:10 +0100162 return (mcc[1] + mcc[0]) + (mnc[2] + mcc[2]) + (mnc[1] + mnc[0])
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300163
Harald Welted7a7e172021-04-07 00:30:10 +0200164def dec_plmn(threehexbytes:Hexstr) -> dict:
Philipp Maier6c5cd802021-04-23 21:19:36 +0200165 res = {'mcc': "0", 'mnc': "0" }
166 dec_mcc_from_plmn_str(threehexbytes)
167 res['mcc'] = dec_mcc_from_plmn_str(threehexbytes)
168 res['mnc'] = dec_mnc_from_plmn_str(threehexbytes)
Harald Welted7a7e172021-04-07 00:30:10 +0200169 return res
170
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300171def dec_spn(ef):
172 byte1 = int(ef[0:2])
173 hplmn_disp = (byte1&0x01 == 0x01)
174 oplmn_disp = (byte1&0x02 == 0x02)
175 name = h2s(ef[2:])
176 return (name, hplmn_disp, oplmn_disp)
177
Philipp Maierf39a4cb2021-04-29 17:14:43 +0200178def enc_spn(name:str, hplmn_disp=False, oplmn_disp=False):
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300179 byte1 = 0x00
180 if hplmn_disp: byte1 = byte1|0x01
181 if oplmn_disp: byte1 = byte1|0x02
182 return i2h([byte1])+s2h(name)
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900183
Supreeth Herlef3948532020-03-24 12:23:51 +0100184def hexstr_to_Nbytearr(s, nbytes):
185 return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2)) ]
186
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100187# Accepts hex string representing three bytes
Harald Welte52255572021-04-03 09:56:32 +0200188def dec_mcc_from_plmn(plmn:Hexstr) -> int:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100189 ia = h2i(plmn)
190 digit1 = ia[0] & 0x0F # 1st byte, LSB
191 digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB
192 digit3 = ia[1] & 0x0F # 2nd byte, LSB
193 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
194 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100195 return derive_mcc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100196
Philipp Maier6c5cd802021-04-23 21:19:36 +0200197def dec_mcc_from_plmn_str(plmn:Hexstr) -> str:
198 digit1 = plmn[1] # 1st byte, LSB
199 digit2 = plmn[0] # 1st byte, MSB
200 digit3 = plmn[3] # 2nd byte, LSB
201 res = digit1 + digit2 + digit3
202 return res.upper().strip("F")
203
Harald Welte52255572021-04-03 09:56:32 +0200204def dec_mnc_from_plmn(plmn:Hexstr) -> int:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100205 ia = h2i(plmn)
Vadim Yanitskiyb271be32021-03-11 23:56:58 +0100206 digit1 = ia[2] & 0x0F # 3rd byte, LSB
207 digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB
208 digit3 = (ia[1] & 0xF0) >> 4 # 2nd byte, MSB
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100209 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
210 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100211 return derive_mnc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100212
Philipp Maier6c5cd802021-04-23 21:19:36 +0200213def dec_mnc_from_plmn_str(plmn:Hexstr) -> str:
214 digit1 = plmn[5] # 3rd byte, LSB
215 digit2 = plmn[4] # 3rd byte, MSB
216 digit3 = plmn[2] # 2nd byte, MSB
217 res = digit1 + digit2 + digit3
218 return res.upper().strip("F")
219
Harald Welte52255572021-04-03 09:56:32 +0200220def dec_act(twohexbytes:Hexstr) -> List[str]:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100221 act_list = [
222 {'bit': 15, 'name': "UTRAN"},
223 {'bit': 14, 'name': "E-UTRAN"},
224 {'bit': 7, 'name': "GSM"},
225 {'bit': 6, 'name': "GSM COMPACT"},
226 {'bit': 5, 'name': "cdma2000 HRPD"},
227 {'bit': 4, 'name': "cdma2000 1xRTT"},
228 ]
229 ia = h2i(twohexbytes)
230 u16t = (ia[0] << 8)|ia[1]
231 sel = []
232 for a in act_list:
233 if u16t & (1 << a['bit']):
Philipp Maiere7d41792021-04-29 16:20:07 +0200234 if a['name'] == "E-UTRAN":
235 # The Access technology identifier of E-UTRAN
236 # allows a more detailed specification:
237 if u16t & (1 << 13) and u16t & (1 << 12):
238 sel.append("E-UTRAN WB-S1")
239 sel.append("E-UTRAN NB-S1")
240 elif u16t & (1 << 13):
241 sel.append("E-UTRAN WB-S1")
242 elif u16t & (1 << 12):
243 sel.append("E-UTRAN NB-S1")
244 else:
245 sel.append("E-UTRAN")
246 else:
247 sel.append(a['name'])
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100248 return sel
249
Harald Welte52255572021-04-03 09:56:32 +0200250def dec_xplmn_w_act(fivehexbytes:Hexstr) -> Dict[str,Any]:
Philipp Maierb919f8b2021-04-27 18:28:27 +0200251 res = {'mcc': "0", 'mnc': "0", 'act': []}
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100252 plmn_chars = 6
253 act_chars = 4
254 plmn_str = fivehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
255 act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars] # two bytes after first three bytes
Philipp Maierb919f8b2021-04-27 18:28:27 +0200256 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
257 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100258 res['act'] = dec_act(act_str)
259 return res
260
261def format_xplmn_w_act(hexstr):
262 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200263 for rec_data in hexstr_to_Nbytearr(hexstr, 5):
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100264 rec_info = dec_xplmn_w_act(rec_data)
Philipp Maierb919f8b2021-04-27 18:28:27 +0200265 if rec_info['mcc'] == "" and rec_info['mnc'] == "":
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100266 rec_str = "unused"
267 else:
Philipp Maierb919f8b2021-04-27 18:28:27 +0200268 rec_str = "MCC: %s MNC: %s AcT: %s" % (rec_info['mcc'], rec_info['mnc'], ", ".join(rec_info['act']))
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100269 s += "\t%s # %s\n" % (rec_data, rec_str)
270 return s
271
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100272def dec_loci(hexstr):
273 res = {'tmsi': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0}
274 res['tmsi'] = hexstr[:8]
275 res['mcc'] = dec_mcc_from_plmn(hexstr[8:14])
276 res['mnc'] = dec_mnc_from_plmn(hexstr[8:14])
277 res['lac'] = hexstr[14:18]
278 res['status'] = h2i(hexstr[20:22])
279 return res
280
281def dec_psloci(hexstr):
282 res = {'p-tmsi': '', 'p-tmsi-sig': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'rac': '', 'status': 0}
283 res['p-tmsi'] = hexstr[:8]
284 res['p-tmsi-sig'] = hexstr[8:14]
285 res['mcc'] = dec_mcc_from_plmn(hexstr[14:20])
286 res['mnc'] = dec_mnc_from_plmn(hexstr[14:20])
287 res['lac'] = hexstr[20:24]
288 res['rac'] = hexstr[24:26]
289 res['status'] = h2i(hexstr[26:28])
290 return res
291
292def dec_epsloci(hexstr):
293 res = {'guti': '', 'mcc': 0, 'mnc': 0, 'tac': '', 'status': 0}
294 res['guti'] = hexstr[:24]
295 res['tai'] = hexstr[24:34]
296 res['mcc'] = dec_mcc_from_plmn(hexstr[24:30])
297 res['mnc'] = dec_mnc_from_plmn(hexstr[24:30])
298 res['tac'] = hexstr[30:34]
299 res['status'] = h2i(hexstr[34:36])
300 return res
301
Harald Welte52255572021-04-03 09:56:32 +0200302def dec_xplmn(threehexbytes:Hexstr) -> dict:
Harald Welteca673942020-06-03 15:19:40 +0200303 res = {'mcc': 0, 'mnc': 0, 'act': []}
304 plmn_chars = 6
305 plmn_str = threehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
306 res['mcc'] = dec_mcc_from_plmn(plmn_str)
307 res['mnc'] = dec_mnc_from_plmn(plmn_str)
308 return res
309
Harald Welte52255572021-04-03 09:56:32 +0200310def format_xplmn(hexstr:Hexstr) -> str:
Harald Welteca673942020-06-03 15:19:40 +0200311 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200312 for rec_data in hexstr_to_Nbytearr(hexstr, 3):
Harald Welteca673942020-06-03 15:19:40 +0200313 rec_info = dec_xplmn(rec_data)
314 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
315 rec_str = "unused"
316 else:
317 rec_str = "MCC: %03d MNC: %03d" % (rec_info['mcc'], rec_info['mnc'])
318 s += "\t%s # %s\n" % (rec_data, rec_str)
319 return s
320
Harald Welte52255572021-04-03 09:56:32 +0200321def derive_milenage_opc(ki_hex:Hexstr, op_hex:Hexstr) -> Hexstr:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900322 """
323 Run the milenage algorithm to calculate OPC from Ki and OP
324 """
325 from Crypto.Cipher import AES
326 from Crypto.Util.strxor import strxor
327 from pySim.utils import b2h
328
329 # We pass in hex string and now need to work on bytes
Harald Welteeab8d2a2021-03-05 18:30:23 +0100330 ki_bytes = bytes(h2b(ki_hex))
331 op_bytes = bytes(h2b(op_hex))
Harald Welteab34fa82021-03-05 18:39:59 +0100332 aes = AES.new(ki_bytes, AES.MODE_ECB)
Harald Welteeab8d2a2021-03-05 18:30:23 +0100333 opc_bytes = aes.encrypt(op_bytes)
334 return b2h(strxor(opc_bytes, op_bytes))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900335
Harald Welte52255572021-04-03 09:56:32 +0200336def calculate_luhn(cc) -> int:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900337 """
338 Calculate Luhn checksum used in e.g. ICCID and IMEI
339 """
Daniel Willmanndd014ea2020-10-19 10:35:11 +0200340 num = list(map(int, str(cc)))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900341 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10
342 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200343
Harald Welte52255572021-04-03 09:56:32 +0200344def mcc_from_imsi(imsi:str) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200345 """
346 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
347 """
348 if imsi == None:
349 return None
350
351 if len(imsi) > 3:
352 return imsi[:3]
353 else:
354 return None
355
Harald Welte52255572021-04-03 09:56:32 +0200356def mnc_from_imsi(imsi:str, long:bool=False) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200357 """
358 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
359 """
360 if imsi == None:
361 return None
362
363 if len(imsi) > 3:
364 if long:
365 return imsi[3:6]
366 else:
367 return imsi[3:5]
368 else:
369 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100370
Harald Welte52255572021-04-03 09:56:32 +0200371def derive_mcc(digit1:int, digit2:int, digit3:int) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100372 """
373 Derive decimal representation of the MCC (Mobile Country Code)
374 from three given digits.
375 """
376
377 mcc = 0
378
379 if digit1 != 0x0f:
380 mcc += digit1 * 100
381 if digit2 != 0x0f:
382 mcc += digit2 * 10
383 if digit3 != 0x0f:
384 mcc += digit3
385
386 return mcc
387
Harald Welte52255572021-04-03 09:56:32 +0200388def derive_mnc(digit1:int, digit2:int, digit3:int=0x0f) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100389 """
390 Derive decimal representation of the MNC (Mobile Network Code)
391 from two or (optionally) three given digits.
392 """
393
394 mnc = 0
395
396 # 3-rd digit is optional for the MNC. If present
397 # the algorythm is the same as for the MCC.
398 if digit3 != 0x0f:
399 return derive_mcc(digit1, digit2, digit3)
400
401 if digit1 != 0x0f:
402 mnc += digit1 * 10
403 if digit2 != 0x0f:
404 mnc += digit2
405
406 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100407
Harald Welte52255572021-04-03 09:56:32 +0200408def dec_msisdn(ef_msisdn:Hexstr) -> Optional[Tuple[int,int,Optional[str]]]:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100409 """
410 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
411 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
412 """
413
414 # Convert from str to (kind of) 'bytes'
415 ef_msisdn = h2b(ef_msisdn)
416
417 # Make sure mandatory fields are present
418 if len(ef_msisdn) < 14:
419 raise ValueError("EF.MSISDN is too short")
420
421 # Skip optional Alpha Identifier
422 xlen = len(ef_msisdn) - 14
423 msisdn_lhv = ef_msisdn[xlen:]
424
425 # Parse the length (in bytes) of the BCD encoded number
Harald Welte4f6ca432021-02-01 17:51:56 +0100426 bcd_len = msisdn_lhv[0]
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100427 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
428 if bcd_len == 0xff:
429 return None
430 elif bcd_len > 11 or bcd_len < 1:
431 raise ValueError("Length of MSISDN (%d bytes) is out of range" % bcd_len)
432
433 # Parse ToN / NPI
Harald Welte4f6ca432021-02-01 17:51:56 +0100434 ton = (msisdn_lhv[1] >> 4) & 0x07
435 npi = msisdn_lhv[1] & 0x0f
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100436 bcd_len -= 1
437
438 # No MSISDN?
439 if not bcd_len:
440 return (npi, ton, None)
441
442 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
443 # International number 10.5.118/3GPP TS 24.008
Vadim Yanitskiy7ba24282020-02-27 00:04:13 +0700444 if ton == 0x01:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100445 msisdn = '+' + msisdn
446
447 return (npi, ton, msisdn)
Supreeth Herle5a541012019-12-22 08:59:16 +0100448
Harald Welte52255572021-04-03 09:56:32 +0200449def enc_msisdn(msisdn:str, npi:int=0x01, ton:int=0x03) -> Hexstr:
Supreeth Herle5a541012019-12-22 08:59:16 +0100450 """
451 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200452 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
453 will not contain the optional Alpha Identifier at the beginning.)
Supreeth Herle5a541012019-12-22 08:59:16 +0100454
455 Default NPI / ToN values:
456 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
457 - ToN: network specific or international number (if starts with '+').
458 """
459
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200460 # If no MSISDN is supplied then encode the file contents as all "ff"
461 if msisdn == "" or msisdn == "+":
462 return "ff" * 14
463
Supreeth Herle5a541012019-12-22 08:59:16 +0100464 # Leading '+' indicates International Number
465 if msisdn[0] == '+':
466 msisdn = msisdn[1:]
467 ton = 0x01
468
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200469 # An MSISDN must not exceed 20 digits
470 if len(msisdn) > 20:
471 raise ValueError("msisdn must not be longer than 20 digits")
472
Supreeth Herle5a541012019-12-22 08:59:16 +0100473 # Append 'f' padding if number of digits is odd
474 if len(msisdn) % 2 > 0:
475 msisdn += 'f'
476
477 # BCD length also includes NPI/ToN header
478 bcd_len = len(msisdn) // 2 + 1
479 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
480 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
481
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200482 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
483
Supreeth Herle441c4a72020-03-24 10:19:15 +0100484
Harald Welte52255572021-04-03 09:56:32 +0200485def dec_st(st, table="sim") -> str:
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200486 """
487 Parses the EF S/U/IST and prints the list of available services in EF S/U/IST
488 """
489
Supreeth Herledf330372020-04-20 14:48:55 +0200490 if table == "isim":
491 from pySim.ts_31_103 import EF_IST_map
492 lookup_map = EF_IST_map
493 elif table == "usim":
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200494 from pySim.ts_31_102 import EF_UST_map
495 lookup_map = EF_UST_map
496 else:
497 from pySim.ts_51_011 import EF_SST_map
498 lookup_map = EF_SST_map
499
500 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
501
502 avail_st = ""
503 # Get each byte and check for available services
504 for i in range(0, len(st_bytes)):
505 # Byte i contains info about Services num (8i+1) to num (8i+8)
506 byte = int(st_bytes[i], 16)
507 # Services in each byte are in order MSB to LSB
508 # MSB - Service (8i+8)
509 # LSB - Service (8i+1)
510 for j in range(1, 9):
511 if byte&0x01 == 0x01 and ((8*i) + j in lookup_map):
512 # Byte X contains info about Services num (8X-7) to num (8X)
513 # bit = 1: service available
514 # bit = 0: service not available
515 avail_st += '\tService %d - %s\n' % ((8*i) + j, lookup_map[(8*i) + j])
516 byte = byte >> 1
517 return avail_st
Supreeth Herle98370552020-05-11 09:04:41 +0200518
519def first_TLV_parser(bytelist):
520 '''
521 first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
522
523 parses first TLV format record in a list of bytelist
524 returns a 3-Tuple: Tag, Length, Value
525 Value is a list of bytes
526 parsing of length is ETSI'style 101.220
527 '''
528 Tag = bytelist[0]
529 if bytelist[1] == 0xFF:
530 Len = bytelist[2]*256 + bytelist[3]
531 Val = bytelist[4:4+Len]
532 else:
533 Len = bytelist[1]
534 Val = bytelist[2:2+Len]
535 return (Tag, Len, Val)
536
537def TLV_parser(bytelist):
538 '''
539 TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
540
541 loops on the input list of bytes with the "first_TLV_parser()" function
542 returns a list of 3-Tuples
543 '''
544 ret = []
545 while len(bytelist) > 0:
546 T, L, V = first_TLV_parser(bytelist)
547 if T == 0xFF:
548 # padding bytes
549 break
550 ret.append( (T, L, V) )
551 # need to manage length of L
552 if L > 0xFE:
553 bytelist = bytelist[ L+4 : ]
554 else:
555 bytelist = bytelist[ L+2 : ]
556 return ret
Supreeth Herled572ede2020-03-22 09:55:04 +0100557
Supreeth Herled84daa12020-03-24 12:20:40 +0100558def enc_st(st, service, state=1):
559 """
560 Encodes the EF S/U/IST/EST and returns the updated Service Table
561
562 Parameters:
563 st - Current value of SIM/USIM/ISIM Service Table
564 service - Service Number to encode as activated/de-activated
565 state - 1 mean activate, 0 means de-activate
566
567 Returns:
568 s - Modified value of SIM/USIM/ISIM Service Table
569
570 Default values:
571 - state: 1 - Sets the particular Service bit to 1
572 """
573 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
574
575 s = ""
576 # Check whether the requested service is present in each byte
577 for i in range(0, len(st_bytes)):
578 # Byte i contains info about Services num (8i+1) to num (8i+8)
579 if service in range((8*i) + 1, (8*i) + 9):
580 byte = int(st_bytes[i], 16)
581 # Services in each byte are in order MSB to LSB
582 # MSB - Service (8i+8)
583 # LSB - Service (8i+1)
584 mod_byte = 0x00
585 # Copy bit by bit contents of byte to mod_byte with modified bit
586 # for requested service
587 for j in range(1, 9):
588 mod_byte = mod_byte >> 1
589 if service == (8*i) + j:
590 mod_byte = state == 1 and mod_byte|0x80 or mod_byte&0x7f
591 else:
592 mod_byte = byte&0x01 == 0x01 and mod_byte|0x80 or mod_byte&0x7f
593 byte = byte >> 1
594
595 s += ('%02x' % (mod_byte))
596 else:
597 s += st_bytes[i]
598
599 return s
600
Supreeth Herle3b342c22020-03-24 16:15:02 +0100601def dec_addr_tlv(hexstr):
Supreeth Herled572ede2020-03-22 09:55:04 +0100602 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100603 Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm.
604 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 +0100605 """
606
607 # Convert from hex str to int bytes list
Supreeth Herle3b342c22020-03-24 16:15:02 +0100608 addr_tlv_bytes = h2i(hexstr)
Supreeth Herled572ede2020-03-22 09:55:04 +0100609
610 s = ""
611
612 # Get list of tuples containing parsed TLVs
Supreeth Herle3b342c22020-03-24 16:15:02 +0100613 tlvs = TLV_parser(addr_tlv_bytes)
Supreeth Herled572ede2020-03-22 09:55:04 +0100614
615 for tlv in tlvs:
616 # tlv = (T, L, [V])
617 # T = Tag
618 # L = Length
619 # [V] = List of value
620
621 # Invalid Tag value scenario
622 if tlv[0] != 0x80:
623 continue
624
Supreeth Herled6a5ec52020-06-01 12:27:51 +0200625 # Empty field - Zero length
626 if tlv[1] == 0:
627 continue
628
Supreeth Herled572ede2020-03-22 09:55:04 +0100629 # First byte in the value has the address type
630 addr_type = tlv[2][0]
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100631 # TODO: Support parsing of IPv6
Supreeth Herle3b342c22020-03-24 16:15:02 +0100632 # Address Type: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), other (Reserved)
Supreeth Herled572ede2020-03-22 09:55:04 +0100633 if addr_type == 0x00: #FQDN
634 # Skip address tye byte i.e. first byte in value list
635 content = tlv[2][1:]
636 s += "\t%s # %s\n" % (i2h(content), i2s(content))
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100637 elif addr_type == 0x01: #IPv4
638 # Skip address tye byte i.e. first byte in value list
639 # Skip the unused byte in Octect 4 after address type byte as per 3GPP TS 31.102
640 ipv4 = tlv[2][2:]
641 content = '.'.join(str(x) for x in ipv4)
642 s += "\t%s # %s\n" % (i2h(ipv4), content)
Supreeth Herled572ede2020-03-22 09:55:04 +0100643
644 return s
Philipp Maierff84c232020-05-12 17:24:18 +0200645
Supreeth Herle3b342c22020-03-24 16:15:02 +0100646def enc_addr_tlv(addr, addr_type='00'):
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100647 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100648 Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm.
649 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 +0100650
651 Default values:
Supreeth Herle3b342c22020-03-24 16:15:02 +0100652 - addr_type: 00 - FQDN format of Address
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100653 """
654
655 s = ""
656
Supreeth Herle654eca72020-03-25 14:25:38 +0100657 # TODO: Encoding of IPv6 address
658 if addr_type == '00': #FQDN
Supreeth Herle3b342c22020-03-24 16:15:02 +0100659 hex_str = s2h(addr)
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100660 s += '80' + ('%02x' % ((len(hex_str)//2)+1)) + '00' + hex_str
Supreeth Herle654eca72020-03-25 14:25:38 +0100661 elif addr_type == '01': #IPv4
662 ipv4_list = addr.split('.')
663 ipv4_str = ""
664 for i in ipv4_list:
665 ipv4_str += ('%02x' % (int(i)))
666
667 # Unused bytes shall be set to 'ff'. i.e 4th Octet after Address Type is not used
668 # IPv4 Address is in octet 5 to octet 8 of the TLV data object
669 s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100670
671 return s
672
Harald Welte52255572021-04-03 09:56:32 +0200673def is_hex(string:str, minlen:int=2, maxlen:Optional[int]=None) -> bool:
Philipp Maier47236502021-03-09 21:28:25 +0100674 """
675 Check if a string is a valid hexstring
676 """
677
678 # Filter obviously bad strings
679 if not string:
680 return False
681 if len(string) < minlen or minlen < 2:
682 return False
683 if len(string) % 2:
684 return False
685 if maxlen and len(string) > maxlen:
686 return False
687
688 # Try actual encoding to be sure
689 try:
690 try_encode = h2b(string)
691 return True
692 except:
693 return False
694
Harald Welte52255572021-04-03 09:56:32 +0200695def sanitize_pin_adm(pin_adm, pin_adm_hex = None) -> Hexstr:
Philipp Maiere8536c02020-05-11 21:35:01 +0200696 """
697 The ADM pin can be supplied either in its hexadecimal form or as
698 ascii string. This function checks the supplied opts parameter and
Harald Welte236a65f2021-04-03 10:20:11 +0200699 returns the pin_adm as hex encoded string, regardless in which form
Philipp Maiere8536c02020-05-11 21:35:01 +0200700 it was originally supplied by the user
701 """
702
Harald Welte79b5ba42021-01-08 21:22:38 +0100703 if pin_adm is not None:
704 if len(pin_adm) <= 8:
705 pin_adm = ''.join(['%02x'%(ord(x)) for x in pin_adm])
Philipp Maiere8536c02020-05-11 21:35:01 +0200706 pin_adm = rpad(pin_adm, 16)
707
708 else:
709 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
710
Harald Welte79b5ba42021-01-08 21:22:38 +0100711 if pin_adm_hex is not None:
712 if len(pin_adm_hex) == 16:
713 pin_adm = pin_adm_hex
Philipp Maiere8536c02020-05-11 21:35:01 +0200714 # Ensure that it's hex-encoded
715 try:
716 try_encode = h2b(pin_adm)
717 except ValueError:
718 raise ValueError("PIN-ADM needs to be hex encoded using this option")
719 else:
720 raise ValueError("PIN-ADM needs to be exactly 16 digits (hex encoded)")
721
722 return pin_adm
723
Supreeth Herle95ec7722020-03-24 13:09:03 +0100724def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='00'):
725 """
726 Encode ePDGSelection so it can be stored at EF.ePDGSelection or EF.ePDGSelectionEm.
727 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
728
729 Default values:
730 - epdg_priority: '0001' - 1st Priority
731 - epdg_fqdn_format: '00' - Operator Identifier FQDN
732 """
733
734 plmn1 = enc_plmn(mcc, mnc) + epdg_priority + epdg_fqdn_format
735 # TODO: Handle encoding of Length field for length more than 127 Bytes
736 content = '80' + ('%02x' % (len(plmn1)//2)) + plmn1
737 content = rpad(content, len(hexstr))
738 return content
739
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100740def dec_ePDGSelection(sixhexbytes):
741 """
742 Decode ePDGSelection to get EF.ePDGSelection or EF.ePDGSelectionEm.
743 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
744 """
745
746 res = {'mcc': 0, 'mnc': 0, 'epdg_priority': 0, 'epdg_fqdn_format': ''}
747 plmn_chars = 6
748 epdg_priority_chars = 4
749 epdg_fqdn_format_chars = 2
750 # first three bytes (six ascii hex chars)
751 plmn_str = sixhexbytes[:plmn_chars]
752 # two bytes after first three bytes
753 epdg_priority_str = sixhexbytes[plmn_chars:plmn_chars + epdg_priority_chars]
754 # one byte after first five bytes
755 epdg_fqdn_format_str = sixhexbytes[plmn_chars + epdg_priority_chars:plmn_chars + epdg_priority_chars + epdg_fqdn_format_chars]
756 res['mcc'] = dec_mcc_from_plmn(plmn_str)
757 res['mnc'] = dec_mnc_from_plmn(plmn_str)
758 res['epdg_priority'] = epdg_priority_str
759 res['epdg_fqdn_format'] = epdg_fqdn_format_str == '00' and 'Operator Identifier FQDN' or 'Location based FQDN'
760 return res
761
762def format_ePDGSelection(hexstr):
763 ePDGSelection_info_tag_chars = 2
764 ePDGSelection_info_tag_str = hexstr[:2]
herlesupreeth3a261d32021-01-05 09:20:11 +0100765 s = ""
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100766 # Minimum length
767 len_chars = 2
768 # TODO: Need to determine length properly - definite length support only
769 # Inconsistency in spec: 3GPP TS 31.102 version 15.2.0 Release 15, 4.2.104
770 # As per spec, length is 5n, n - number of PLMNs
771 # But, each PLMN entry is made of PLMN (3 Bytes) + ePDG Priority (2 Bytes) + ePDG FQDN format (1 Byte)
772 # Totalling to 6 Bytes, maybe length should be 6n
773 len_str = hexstr[ePDGSelection_info_tag_chars:ePDGSelection_info_tag_chars+len_chars]
herlesupreeth3a261d32021-01-05 09:20:11 +0100774
775 # Not programmed scenario
776 if int(len_str, 16) == 255 or int(ePDGSelection_info_tag_str, 16) == 255:
777 len_chars = 0
778 ePDGSelection_info_tag_chars = 0
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100779 if len_str[0] == '8':
780 # The bits 7 to 1 denotes the number of length octets if length > 127
781 if int(len_str[1]) > 0:
782 # Update number of length octets
783 len_chars = len_chars * int(len_str[1])
784 len_str = hexstr[ePDGSelection_info_tag_chars:len_chars]
785
786 content_str = hexstr[ePDGSelection_info_tag_chars+len_chars:]
787 # Right pad to prevent index out of range - multiple of 6 bytes
788 content_str = rpad(content_str, len(content_str) + (12 - (len(content_str) % 12)))
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100789 for rec_data in hexstr_to_Nbytearr(content_str, 6):
790 rec_info = dec_ePDGSelection(rec_data)
791 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
792 rec_str = "unused"
793 else:
794 rec_str = "MCC: %03d MNC: %03d ePDG Priority: %s ePDG FQDN format: %s" % \
795 (rec_info['mcc'], rec_info['mnc'], rec_info['epdg_priority'], rec_info['epdg_fqdn_format'])
796 s += "\t%s # %s\n" % (rec_data, rec_str)
797 return s
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100798
799def get_addr_type(addr):
800 """
801 Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
802 Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given)
803
804 TODO: Handle IPv6
805 """
806
807 # Empty address string
808 if not len(addr):
809 return None
810
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100811 addr_list = addr.split('.')
812
813 # Check for IPv4/IPv6
814 try:
815 import ipaddress
816 # Throws ValueError if addr is not correct
Denis 'GNUtoo' Carikli79f5b602020-02-15 04:02:57 +0700817 ipa = ipaddress.ip_address(addr)
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100818
819 if ipa.version == 4:
820 return 0x01
821 elif ipa.version == 6:
822 return 0x02
823 except Exception as e:
824 invalid_ipv4 = True
825 for i in addr_list:
826 # Invalid IPv4 may qualify for a valid FQDN, so make check here
827 # e.g. 172.24.15.300
828 import re
829 if not re.match('^[0-9_]+$', i):
830 invalid_ipv4 = False
831 break
832
833 if invalid_ipv4:
834 return None
835
836 fqdn_flag = True
837 for i in addr_list:
838 # Only Alpha-numeric characters and hyphen - RFC 1035
839 import re
840 if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
841 fqdn_flag = False
842 break
843
844 # FQDN
845 if fqdn_flag:
846 return 0x00
847
848 return None
Harald Welte67d551a2021-01-21 14:50:01 +0100849
Harald Welte1e456572021-04-02 17:16:30 +0200850def sw_match(sw:str, pattern:str) -> bool:
Harald Welte67d551a2021-01-21 14:50:01 +0100851 """Match given SW against given pattern."""
852 # Create a masked version of the returned status word
853 sw_lower = sw.lower()
854 sw_masked = ""
855 for i in range(0, 4):
Philipp Maier78e32f22021-03-22 20:39:24 +0100856 if pattern[i] == '?':
Harald Welte67d551a2021-01-21 14:50:01 +0100857 sw_masked = sw_masked + '?'
Philipp Maier78e32f22021-03-22 20:39:24 +0100858 elif pattern[i] == 'x':
Harald Welte67d551a2021-01-21 14:50:01 +0100859 sw_masked = sw_masked + 'x'
860 else:
861 sw_masked = sw_masked + sw_lower[i]
Philipp Maier78e32f22021-03-22 20:39:24 +0100862 # Compare the masked version against the pattern
Harald Welte67d551a2021-01-21 14:50:01 +0100863 return sw_masked == pattern
Philipp Maier5d3e2592021-02-22 17:22:16 +0100864
Harald Welteee3501f2021-04-02 13:00:18 +0200865def tabulate_str_list(str_list, width:int = 79, hspace:int = 2, lspace:int = 1,
Harald Welte52255572021-04-03 09:56:32 +0200866 align_left:bool = True) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +0200867 """Pretty print a list of strings into a tabulated form.
868
869 Args:
870 width : total width in characters per line
871 space : horizontal space between cells
872 lspace : number of spaces before row
873 align_lef : Align text to the left side
874 Returns:
875 multi-line string containing formatted table
876 """
Philipp Maier5d3e2592021-02-22 17:22:16 +0100877 if str_list == None:
878 return ""
879 if len(str_list) <= 0:
880 return ""
881 longest_str = max(str_list, key=len)
882 cellwith = len(longest_str) + hspace
883 cols = width // cellwith
884 rows = (len(str_list) - 1) // cols + 1
885 table = []
886 for i in iter(range(rows)):
887 str_list_row = str_list[i::rows]
888 if (align_left):
889 format_str_cell = '%%-%ds'
890 else:
891 format_str_cell = '%%%ds'
892 format_str_row = (format_str_cell % cellwith) * len(str_list_row)
893 format_str_row = (" " * lspace) + format_str_row
894 table.append(format_str_row % tuple(str_list_row))
895 return '\n'.join(table)
Harald Welte5e749a72021-04-10 17:18:17 +0200896
897class JsonEncoder(json.JSONEncoder):
898 """Extend the standard library JSONEncoder with support for more types."""
899 def default(self, o):
900 if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
901 return b2h(o)
902 return json.JSONEncoder.default(self, o)
Philipp Maier80ce71f2021-04-19 21:24:23 +0200903
904def boxed_heading_str(heading, width=80):
905 """Generate a string that contains a boxed heading."""
906 # Auto-enlarge box if heading exceeds length
907 if len(heading) > width - 4:
908 width = len(heading) + 4
909
910 res = "#" * width
911 fstr = "\n# %-" + str(width - 4) + "s #\n"
912 res += fstr % (heading)
913 res += "#" * width
914 return res
Harald Welte3de6ca22021-05-02 20:05:56 +0200915
916
917
918class DataObject(abc.ABC):
919 """A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
920 simply has any number of different TLVs that may occur in any order at any point, ISO 7816
921 has the habit of specifying TLV data but with very spcific ordering, or specific choices of
922 tags at specific points in a stream. This class tries to represent this."""
923 def __init__(self, name, desc = None, tag = None):
924 """
925 Args:
926 name: A brief, all-lowercase, underscore separated string identifier
927 desc: A human-readable description of what this DO represents
928 tag : The tag associated with this DO
929 """
930 self.name = name
931 self.desc = desc
932 self.tag = tag
933 self.decoded = None
934 self.encoded = None
935
936 def __str__(self):
937 return self.name
938
939 def __repr__(self):
940 return '%s(%s)' % (self.__class__, self.name)
941
942 def __or__(self, other):
943 """OR-ing DataObjects together renders a DataObjectChoice."""
944 if isinstance(other, DataObject):
945 # DataObject | DataObject = DataObjectChoice
946 return DataObjectChoice(None, members=[self, other])
947 else:
948 raise TypeError
949
950 def __add__(self, other):
951 """ADD-ing DataObjects together renders a DataObjectCollection."""
952 if isinstance(other, DataObject):
953 # DataObject + DataObject = DataObjectCollectin
954 return DataObjectCollection(None, members=[self, other])
955
956 def _compute_tag(self):
957 """Compute the tag (sometimes the tag encodes part of the value)."""
958 return self.tag
959
960 def to_dict(self):
961 """Return a dict in form "name: decoded_value" """
962 return {self.name: self.decoded}
963
964 @abc.abstractmethod
965 def from_bytes(self, do:bytes):
966 """Parse the value part of the DO into the internal state of this instance.
967 Args:
968 do : binary encoded bytes
969 """
970
971 @abc.abstractmethod
972 def to_bytes(self):
973 """Encode the internal state of this instance into the TLV value part.
974 Returns:
975 binary bytes encoding the internal state
976 """
977
978 def from_tlv(self, do:bytes):
979 """Parse binary TLV representation into internal state. The resulting decoded
980 representation is _not_ returned, but just internalized in the object instance!
981 Args:
982 do : input bytes containing TLV-encoded representation
983 Returns:
984 bytes remaining at end of 'do' after parsing one TLV/DO.
985 """
986 if do[0] != self.tag:
987 raise ValueError('%s: Can only decode tag 0x%02x' % (self, self.tag))
988 length = do[1]
989 val = do[2:2+length]
990 self.from_bytes(val)
991 # return remaining bytes
992 return do[2+length:]
993
994 def to_tlv(self):
995 """Encode internal representation to binary TLV.
996 Returns:
997 bytes encoded in TLV format.
998 """
999 val = self.to_bytes()
1000 return bytes(self._compute_tag()) + bytes(len(val)) + val
1001
1002 # 'codec' interface
1003 def decode(self, binary:bytes):
1004 """Decode a single DOs from the input data.
1005 Args:
1006 binary : binary bytes of encoded data
1007 Returns:
1008 tuple of (decoded_result, binary_remainder)
1009 """
1010 tag = binary[0]
1011 if tag != self.tag:
1012 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected 0x%02x' %
1013 (self, tag, binary, self.tag))
1014 remainder = self.from_tlv(binary)
1015 return (self.to_dict(), remainder)
1016
1017 # 'codec' interface
1018 def encode(self):
1019 return self.to_tlv()
1020
1021class TL0_DataObject(DataObject):
1022 """Data Object that has Tag, Len=0 and no Value part."""
1023 def __init__(self, name, desc, tag, val=None):
1024 super().__init__(name, desc, tag)
1025 self.val = val
1026
1027 def from_bytes(self, binary:bytes):
1028 if len(binary) != 0:
1029 raise ValueError
1030 self.decoded = self.val
1031
1032 def to_bytes(self):
1033 return b''
1034
1035
1036class DataObjectCollection:
1037 """A DataObjectCollection consits of multiple Data Objects identified by their tags.
1038 A given encoded DO may contain any of them in any order, and may contain multiple instances
1039 of each DO."""
1040 def __init__(self, name, desc = None, members=None):
1041 self.name = name
1042 self.desc = desc
1043 self.members = members or []
1044 self.members_by_tag = {}
1045 self.members_by_name = {}
1046 self.members_by_tag = { m.tag:m for m in members }
1047 self.members_by_name = { m.name:m for m in members }
1048
1049 def __str__(self):
1050 member_strs = [str(x) for x in self.members]
1051 return '%s(%s)' % (self.name, ','.join(member_strs))
1052
1053 def __repr__(self):
1054 member_strs = [repr(x) for x in self.members]
1055 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1056
1057 def __add__(self, other):
1058 """Extending DataCollections with other DataCollections or DataObjects."""
1059 if isinstance(other, DataObjectCollection):
1060 # adding one collection to another
1061 members = self.members + other.members
1062 return DataObjectCollection(self.name, self.desc, members)
1063 elif isinstance(other, DataObject):
1064 # adding a member to a collection
1065 return DataObjectCollection(self.name, self.desc, self.members + [other])
1066 else:
1067 raise TypeError
1068
1069 # 'codec' interface
1070 def decode(self, binary:bytes):
1071 """Decode any number of DOs from the collection until the end of the input data,
1072 or uninitialized memory (0xFF) is found.
1073 Args:
1074 binary : binary bytes of encoded data
1075 Returns:
1076 tuple of (decoded_result, binary_remainder)
1077 """
1078 res = []
1079 remainder = binary
1080 # iterate until no binary trailer is left
1081 while len(remainder):
1082 tag = remainder[0]
1083 if tag == 0xff: # uninitialized memory at the end?
1084 return (res, remainder)
1085 if not tag in self.members_by_tag:
1086 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1087 (self, tag, remainder, self.members_by_tag.keys()))
1088 obj = self.members_by_tag[tag]
1089 # DO from_tlv returns remainder of binary
1090 remainder = obj.from_tlv(remainder)
1091 # collect our results
1092 res.append(obj.to_dict())
1093 return (res, remainder)
1094
1095 # 'codec' interface
1096 def encode(self, decoded):
1097 res = bytearray()
1098 for i in decoded:
1099 obj = self.members_by_name(i[0])
1100 res.append(obj.to_tlv())
1101 return res
1102
1103class DataObjectChoice(DataObjectCollection):
1104 """One Data Object from within a choice, identified by its tag.
1105 This means that exactly one member of the choice must occur, and which one occurs depends
1106 on the tag."""
1107 def __add__(self, other):
1108 """We overload the add operator here to avoid inheriting it from DataObjecCollection."""
1109 raise TypeError
1110
1111 def __or__(self, other):
1112 """OR-ing a Choice to another choice extends the choice, as does OR-ing a DataObject."""
1113 if isinstance(other, DataObjectChoice):
1114 # adding one collection to another
1115 members = self.members + other.members
1116 return DataObjectChoice(self.name, self.desc, members)
1117 elif isinstance(other, DataObject):
1118 # adding a member to a collection
1119 return DataObjectChoice(self.name, self.desc, self.members + [other])
1120 else:
1121 raise TypeError
1122
1123 # 'codec' interface
1124 def decode(self, binary:bytes):
1125 """Decode a single DOs from the choice based on the tag.
1126 Args:
1127 binary : binary bytes of encoded data
1128 Returns:
1129 tuple of (decoded_result, binary_remainder)
1130 """
1131 tag = binary[0]
1132 if tag == 0xff:
1133 return (None, binary)
1134 if not tag in self.members_by_tag:
1135 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1136 (self, tag, binary, self.members_by_tag.keys()))
1137 obj = self.members_by_tag[tag]
1138 remainder = obj.from_tlv(binary)
1139 return (obj.to_dict(), remainder)
1140
1141 # 'codec' interface
1142 def encode(self, decoded):
1143 obj = self.members_by_name(decoded[0])
1144 return obj.to_tlv()
1145
1146class DataObjectSequence:
1147 """A sequence of DataObjects or DataObjectChoices. This allows us to express a certain
1148 ordered sequence of DOs or choices of DOs that have to appear as per the specification.
1149 By wrapping them into this formal DataObjectSequence, we can offer convenience methods
1150 for encoding or decoding an entire sequence."""
1151 def __init__(self, name, desc=None, sequence=None):
1152 self.sequence = sequence or []
1153 self.name = name
1154 self.desc = desc
1155
1156 def __str__(self):
1157 member_strs = [str(x) for x in self.sequence]
1158 return '%s(%s)' % (self.name, ','.join(member_strs))
1159
1160 def __repr__(self):
1161 member_strs = [repr(x) for x in self.sequence]
1162 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1163
1164 def __add__(self, other):
1165 """Add (append) a DataObject or DataObjectChoice to the sequence."""
1166 if isinstance(other, 'DataObject'):
1167 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
1168 elif isinstance(other, 'DataObjectChoice'):
1169 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
1170 elif isinstance(other, 'DataObjectSequence'):
1171 return DataObjectSequence(self.name, self.desc, self.sequence + other.sequence)
1172
1173 # 'codec' interface
1174 def decode(self, binary:bytes):
1175 """Decode a sequence by calling the decoder of each element in the sequence.
1176 Args:
1177 binary : binary bytes of encoded data
1178 Returns:
1179 tuple of (decoded_result, binary_remainder)
1180 """
1181 remainder = binary
1182 res = []
1183 for e in self.sequence:
1184 (r, remainder) = e.decode(remainder)
1185 if r:
1186 res.append(r)
1187 return (res, remainder)
1188
1189 # 'codec' interface
1190 def decode_multi(self, do:bytes):
1191 """Decode multiple occurrences of the sequence from the binary input data.
1192 Args:
1193 do : binary input data to be decoded
1194 Returns:
1195 list of results of the decoder of this sequences
1196 """
1197 remainder = do
1198 res = []
1199 while len(remainder):
1200 (r, remainder2) = self.decode(remainder)
1201 if r:
1202 res.append(r)
1203 if len(remainder2) < len(remainder):
1204 remainder = remainder2
1205 else:
1206 remainder = remainder2
1207 break
1208 return (res, remainder)
1209
1210 # 'codec' interface
1211 def encode(self, decoded):
1212 """Encode a sequence by calling the encoder of each element in the sequence."""
1213 encoded = bytearray()
1214 i = 0
1215 for e in self.sequence:
1216 encoded += e.encode(decoded[i])
1217 i += 1
1218 return encoded
Harald Welte90441432021-05-02 21:28:12 +02001219
1220class CardCommand:
1221 """A single card command / instruction."""
1222 def __init__(self, name, ins, cla_list=None, desc=None):
1223 self.name = name
1224 self.ins = ins
1225 self.cla_list = cla_list or []
1226 self.cla_list = [x.lower() for x in self.cla_list]
1227 self.desc = desc
1228
1229 def __str__(self):
1230 return self.name
1231
1232 def __repr__(self):
1233 return '%s(INS=%02x,CLA=%s)' % (self.name, self.ins, self.cla_list)
1234
1235 def match_cla(self, cla):
1236 """Does the given CLA match the CLA list of the command?."""
1237 if not isinstance(cla, str):
1238 cla = '%02u' % cla
1239 cla = cla.lower()
1240 for cla_match in self.cla_list:
1241 cla_masked = ""
1242 for i in range(0, 2):
1243 if cla_match[i] == 'x':
1244 cla_masked += 'x'
1245 else:
1246 cla_masked += cla[i]
1247 if cla_masked == cla_match:
1248 return True
1249 return False
1250
1251
1252class CardCommandSet:
1253 """A set of card instructions, typically specified within one spec."""
1254 def __init__(self, name, cmds=[]):
1255 self.name = name
1256 self.cmds = { c.ins : c for c in cmds }
1257
1258 def __str__(self):
1259 return self.name
1260
1261 def __getitem__(self, idx):
1262 return self.cmds[idx]
1263
1264 def __add__(self, other):
1265 if isinstance(other, CardCommand):
1266 if other.ins in self.cmds:
1267 raise ValueError('%s: INS 0x%02x already defined: %s' %
1268 (self, other.ins, self.cmds[other.ins]))
1269 self.cmds[other.ins] = other
1270 elif isinstance(other, CardCommandSet):
1271 for c in other.cmds.keys():
1272 self.cmds[c] = other.cmds[c]
1273 else:
1274 raise ValueError('%s: Unsupported type to add operator: %s' % (self, other))
1275
1276 def lookup(self, ins, cla=None):
1277 """look-up the command within the CommandSet."""
1278 ins = int(ins)
1279 if not ins in self.cmds:
1280 return None
1281 cmd = self.cmds[ins]
1282 if cla and not cmd.match_cla(cla):
1283 return None
1284 return cmd