blob: a3cd1b54f25f95e3d82f953501af4d1623268864 [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
Harald Welte917d98c2021-04-21 11:51:25 +020092#########################################################################
93# poor man's BER-TLV decoder. To be a more sophisticated OO library later
94#########################################################################
95
96def bertlv_parse_tag(binary:bytes) -> Tuple[dict, bytes]:
97 """Parse a single Tag value according to ITU-T X.690 8.1.2
98 Args:
99 binary : binary input data of BER-TLV length field
100 Returns:
101 Tuple of ({class:int, constructed:bool, tag:int}, remainder:bytes)
102 """
103 cls = binary[0] >> 6
104 constructed = True if binary[0] & 0x20 else False
105 tag = binary[0] & 0x1f
106 if tag <= 30:
107 return ({'class':cls, 'constructed':constructed, 'tag': tag}, binary[1:])
108 else: # multi-byte tag
109 tag = 0
110 i = 1
111 last = False
112 while not last:
113 last = False if binary[i] & 0x80 else True
114 tag <<= 7
115 tag |= binary[i] & 0x7f
116 i += 1
117 return ({'class':cls, 'constructed':constructed, 'tag':tag}, binary[i:])
118
119def bertlv_parse_len(binary:bytes) -> Tuple[int, bytes]:
120 """Parse a single Length value according to ITU-T X.690 8.1.3;
121 only the definite form is supported here.
122 Args:
123 binary : binary input data of BER-TLV length field
124 Returns:
125 Tuple of (length, remainder)
126 """
127 if binary[0] < 0x80:
128 return (binary[0], binary[1:])
129 else:
130 num_len_oct = binary[0] & 0x7f
131 length = 0
132 for i in range(1, 1+num_len_oct):
133 length <<= 8
134 length |= binary[i]
135 return (length, binary[num_len_oct:])
136
137def bertlv_encode_len(length:int) -> bytes:
138 """Encode a single Length value according to ITU-T X.690 8.1.3;
139 only the definite form is supported here.
140 Args:
141 length : length value to be encoded
142 Returns:
143 binary output data of BER-TLV length field
144 """
145 if length < 0x80:
146 return length.to_bytes(1, 'big')
147 elif length <= 0xff:
148 return b'\x81' + length.to_bytes(1, 'big')
149 elif length <= 0xffff:
150 return b'\x82' + length.to_bytes(2, 'big')
151 elif length <= 0xffffff:
152 return b'\x83' + length.to_bytes(3, 'big')
153 elif length <= 0xffffffff:
154 return b'\x84' + length.to_bytes(4, 'big')
155 else:
156 raise ValueError("Length > 32bits not supported")
157
158def bertlv_parse_one(binary:bytes) -> (dict, int, bytes):
159 """Parse a single TLV IE at the start of the given binary data.
160 Args:
161 binary : binary input data of BER-TLV length field
162 Returns:
163 Tuple of (tag:dict, len:int, remainder:bytes)
164 """
165 (tagdict, remainder) = bertlv_parse_tag(binary)
166 (length, remainder) = bertlv_parse_len(remainder)
167 return (tagdict, length, remainder)
168
169
170
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200171# IMSI encoded format:
172# For IMSI 0123456789ABCDE:
173#
174# | byte 1 | 2 upper | 2 lower | 3 upper | 3 lower | ... | 9 upper | 9 lower |
175# | length in bytes | 0 | odd/even | 2 | 1 | ... | E | D |
176#
177# If the IMSI is less than 15 characters, it should be padded with 'f' from the end.
178#
179# The length is the total number of bytes used to encoded the IMSI. This includes the odd/even
180# parity bit. E.g. an IMSI of length 14 is 8 bytes long, not 7, as it uses bytes 2 to 9 to
181# encode itself.
182#
183# Because of this, an odd length IMSI fits exactly into len(imsi) + 1 // 2 bytes, whereas an
184# even length IMSI only uses half of the last byte.
185
Harald Welteee3501f2021-04-02 13:00:18 +0200186def enc_imsi(imsi:str):
187 """Converts a string IMSI into the encoded value of the EF"""
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200188 l = half_round_up(len(imsi) + 1) # Required bytes - include space for odd/even indicator
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400189 oe = len(imsi) & 1 # Odd (1) / Even (0)
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200190 ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe<<3)|1, rpad(imsi, 15)))
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400191 return ei
192
Harald Welte52255572021-04-03 09:56:32 +0200193def dec_imsi(ef:Hexstr) -> Optional[str]:
Harald Weltec9cdce32021-04-11 10:28:28 +0200194 """Converts an EF value to the IMSI string representation"""
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400195 if len(ef) < 4:
196 return None
Pau Espin Pedrol665bd222017-12-29 20:30:35 +0100197 l = int(ef[0:2], 16) * 2 # Length of the IMSI string
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200198 l = l - 1 # Encoded length byte includes oe nibble
199 swapped = swap_nibbles(ef[2:]).rstrip('f')
Philipp Maiercd3d6262020-05-11 21:41:56 +0200200 if len(swapped) < 1:
201 return None
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400202 oe = (int(swapped[0])>>3) & 1 # Odd (1) / Even (0)
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200203 if not oe:
204 # if even, only half of last byte was used
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400205 l = l-1
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200206 if l != len(swapped) - 1:
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400207 return None
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200208 imsi = swapped[1:]
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400209 return imsi
210
Harald Welte52255572021-04-03 09:56:32 +0200211def dec_iccid(ef:Hexstr) -> str:
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400212 return swap_nibbles(ef).strip('f')
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400213
Harald Welte52255572021-04-03 09:56:32 +0200214def enc_iccid(iccid:str) -> Hexstr:
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400215 return swap_nibbles(rpad(iccid, 20))
216
Philipp Maiere6f8d682021-04-23 21:14:41 +0200217def enc_plmn(mcc:Hexstr, mnc:Hexstr) -> Hexstr:
Alexander Chemerisdddbf522017-07-18 16:49:59 +0300218 """Converts integer MCC/MNC into 3 bytes for EF"""
Philipp Maier6c5cd802021-04-23 21:19:36 +0200219
220 # Make sure there are no excess whitespaces in the input
221 # parameters
222 mcc = mcc.strip()
223 mnc = mnc.strip()
224
225 # Make sure that MCC/MNC are correctly padded with leading
226 # zeros or 'F', depending on the length.
227 if len(mnc) == 0:
228 mnc = "FFF"
229 elif len(mnc) == 1:
230 mnc = "F0" + mnc
231 elif len(mnc) == 2:
232 mnc += "F"
233
234 if len(mcc) == 0:
235 mcc = "FFF"
236 elif len(mcc) == 1:
237 mcc = "00" + mcc
238 elif len(mcc) == 2:
239 mcc = "0" + mcc
240
Vadim Yanitskiyc8458e22021-03-12 00:34:10 +0100241 return (mcc[1] + mcc[0]) + (mnc[2] + mcc[2]) + (mnc[1] + mnc[0])
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300242
Harald Welted7a7e172021-04-07 00:30:10 +0200243def dec_plmn(threehexbytes:Hexstr) -> dict:
Philipp Maier6c5cd802021-04-23 21:19:36 +0200244 res = {'mcc': "0", 'mnc': "0" }
245 dec_mcc_from_plmn_str(threehexbytes)
246 res['mcc'] = dec_mcc_from_plmn_str(threehexbytes)
247 res['mnc'] = dec_mnc_from_plmn_str(threehexbytes)
Harald Welted7a7e172021-04-07 00:30:10 +0200248 return res
249
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300250def dec_spn(ef):
251 byte1 = int(ef[0:2])
252 hplmn_disp = (byte1&0x01 == 0x01)
253 oplmn_disp = (byte1&0x02 == 0x02)
254 name = h2s(ef[2:])
255 return (name, hplmn_disp, oplmn_disp)
256
Philipp Maierf39a4cb2021-04-29 17:14:43 +0200257def enc_spn(name:str, hplmn_disp=False, oplmn_disp=False):
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300258 byte1 = 0x00
259 if hplmn_disp: byte1 = byte1|0x01
260 if oplmn_disp: byte1 = byte1|0x02
261 return i2h([byte1])+s2h(name)
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900262
Supreeth Herlef3948532020-03-24 12:23:51 +0100263def hexstr_to_Nbytearr(s, nbytes):
264 return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2)) ]
265
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100266# Accepts hex string representing three bytes
Harald Welte52255572021-04-03 09:56:32 +0200267def dec_mcc_from_plmn(plmn:Hexstr) -> int:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100268 ia = h2i(plmn)
269 digit1 = ia[0] & 0x0F # 1st byte, LSB
270 digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB
271 digit3 = ia[1] & 0x0F # 2nd byte, LSB
272 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
273 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100274 return derive_mcc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100275
Philipp Maier6c5cd802021-04-23 21:19:36 +0200276def dec_mcc_from_plmn_str(plmn:Hexstr) -> str:
277 digit1 = plmn[1] # 1st byte, LSB
278 digit2 = plmn[0] # 1st byte, MSB
279 digit3 = plmn[3] # 2nd byte, LSB
280 res = digit1 + digit2 + digit3
281 return res.upper().strip("F")
282
Harald Welte52255572021-04-03 09:56:32 +0200283def dec_mnc_from_plmn(plmn:Hexstr) -> int:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100284 ia = h2i(plmn)
Vadim Yanitskiyb271be32021-03-11 23:56:58 +0100285 digit1 = ia[2] & 0x0F # 3rd byte, LSB
286 digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB
287 digit3 = (ia[1] & 0xF0) >> 4 # 2nd byte, MSB
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100288 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
289 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100290 return derive_mnc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100291
Philipp Maier6c5cd802021-04-23 21:19:36 +0200292def dec_mnc_from_plmn_str(plmn:Hexstr) -> str:
293 digit1 = plmn[5] # 3rd byte, LSB
294 digit2 = plmn[4] # 3rd byte, MSB
295 digit3 = plmn[2] # 2nd byte, MSB
296 res = digit1 + digit2 + digit3
297 return res.upper().strip("F")
298
Harald Welte52255572021-04-03 09:56:32 +0200299def dec_act(twohexbytes:Hexstr) -> List[str]:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100300 act_list = [
301 {'bit': 15, 'name': "UTRAN"},
302 {'bit': 14, 'name': "E-UTRAN"},
303 {'bit': 7, 'name': "GSM"},
304 {'bit': 6, 'name': "GSM COMPACT"},
305 {'bit': 5, 'name': "cdma2000 HRPD"},
306 {'bit': 4, 'name': "cdma2000 1xRTT"},
307 ]
308 ia = h2i(twohexbytes)
309 u16t = (ia[0] << 8)|ia[1]
310 sel = []
311 for a in act_list:
312 if u16t & (1 << a['bit']):
Philipp Maiere7d41792021-04-29 16:20:07 +0200313 if a['name'] == "E-UTRAN":
314 # The Access technology identifier of E-UTRAN
315 # allows a more detailed specification:
316 if u16t & (1 << 13) and u16t & (1 << 12):
317 sel.append("E-UTRAN WB-S1")
318 sel.append("E-UTRAN NB-S1")
319 elif u16t & (1 << 13):
320 sel.append("E-UTRAN WB-S1")
321 elif u16t & (1 << 12):
322 sel.append("E-UTRAN NB-S1")
323 else:
324 sel.append("E-UTRAN")
325 else:
326 sel.append(a['name'])
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100327 return sel
328
Harald Welte52255572021-04-03 09:56:32 +0200329def dec_xplmn_w_act(fivehexbytes:Hexstr) -> Dict[str,Any]:
Philipp Maierb919f8b2021-04-27 18:28:27 +0200330 res = {'mcc': "0", 'mnc': "0", 'act': []}
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100331 plmn_chars = 6
332 act_chars = 4
333 plmn_str = fivehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
334 act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars] # two bytes after first three bytes
Philipp Maierb919f8b2021-04-27 18:28:27 +0200335 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
336 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100337 res['act'] = dec_act(act_str)
338 return res
339
340def format_xplmn_w_act(hexstr):
341 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200342 for rec_data in hexstr_to_Nbytearr(hexstr, 5):
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100343 rec_info = dec_xplmn_w_act(rec_data)
Philipp Maierb919f8b2021-04-27 18:28:27 +0200344 if rec_info['mcc'] == "" and rec_info['mnc'] == "":
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100345 rec_str = "unused"
346 else:
Philipp Maierb919f8b2021-04-27 18:28:27 +0200347 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 +0100348 s += "\t%s # %s\n" % (rec_data, rec_str)
349 return s
350
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100351def dec_loci(hexstr):
352 res = {'tmsi': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0}
353 res['tmsi'] = hexstr[:8]
354 res['mcc'] = dec_mcc_from_plmn(hexstr[8:14])
355 res['mnc'] = dec_mnc_from_plmn(hexstr[8:14])
356 res['lac'] = hexstr[14:18]
357 res['status'] = h2i(hexstr[20:22])
358 return res
359
360def dec_psloci(hexstr):
361 res = {'p-tmsi': '', 'p-tmsi-sig': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'rac': '', 'status': 0}
362 res['p-tmsi'] = hexstr[:8]
363 res['p-tmsi-sig'] = hexstr[8:14]
364 res['mcc'] = dec_mcc_from_plmn(hexstr[14:20])
365 res['mnc'] = dec_mnc_from_plmn(hexstr[14:20])
366 res['lac'] = hexstr[20:24]
367 res['rac'] = hexstr[24:26]
368 res['status'] = h2i(hexstr[26:28])
369 return res
370
371def dec_epsloci(hexstr):
372 res = {'guti': '', 'mcc': 0, 'mnc': 0, 'tac': '', 'status': 0}
373 res['guti'] = hexstr[:24]
374 res['tai'] = hexstr[24:34]
375 res['mcc'] = dec_mcc_from_plmn(hexstr[24:30])
376 res['mnc'] = dec_mnc_from_plmn(hexstr[24:30])
377 res['tac'] = hexstr[30:34]
378 res['status'] = h2i(hexstr[34:36])
379 return res
380
Harald Welte52255572021-04-03 09:56:32 +0200381def dec_xplmn(threehexbytes:Hexstr) -> dict:
Harald Welteca673942020-06-03 15:19:40 +0200382 res = {'mcc': 0, 'mnc': 0, 'act': []}
383 plmn_chars = 6
384 plmn_str = threehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
385 res['mcc'] = dec_mcc_from_plmn(plmn_str)
386 res['mnc'] = dec_mnc_from_plmn(plmn_str)
387 return res
388
Harald Welte52255572021-04-03 09:56:32 +0200389def format_xplmn(hexstr:Hexstr) -> str:
Harald Welteca673942020-06-03 15:19:40 +0200390 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200391 for rec_data in hexstr_to_Nbytearr(hexstr, 3):
Harald Welteca673942020-06-03 15:19:40 +0200392 rec_info = dec_xplmn(rec_data)
393 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
394 rec_str = "unused"
395 else:
396 rec_str = "MCC: %03d MNC: %03d" % (rec_info['mcc'], rec_info['mnc'])
397 s += "\t%s # %s\n" % (rec_data, rec_str)
398 return s
399
Harald Welte52255572021-04-03 09:56:32 +0200400def derive_milenage_opc(ki_hex:Hexstr, op_hex:Hexstr) -> Hexstr:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900401 """
402 Run the milenage algorithm to calculate OPC from Ki and OP
403 """
404 from Crypto.Cipher import AES
405 from Crypto.Util.strxor import strxor
406 from pySim.utils import b2h
407
408 # We pass in hex string and now need to work on bytes
Harald Welteeab8d2a2021-03-05 18:30:23 +0100409 ki_bytes = bytes(h2b(ki_hex))
410 op_bytes = bytes(h2b(op_hex))
Harald Welteab34fa82021-03-05 18:39:59 +0100411 aes = AES.new(ki_bytes, AES.MODE_ECB)
Harald Welteeab8d2a2021-03-05 18:30:23 +0100412 opc_bytes = aes.encrypt(op_bytes)
413 return b2h(strxor(opc_bytes, op_bytes))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900414
Harald Welte52255572021-04-03 09:56:32 +0200415def calculate_luhn(cc) -> int:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900416 """
417 Calculate Luhn checksum used in e.g. ICCID and IMEI
418 """
Daniel Willmanndd014ea2020-10-19 10:35:11 +0200419 num = list(map(int, str(cc)))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900420 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10
421 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200422
Harald Welte52255572021-04-03 09:56:32 +0200423def mcc_from_imsi(imsi:str) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200424 """
425 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
426 """
427 if imsi == None:
428 return None
429
430 if len(imsi) > 3:
431 return imsi[:3]
432 else:
433 return None
434
Harald Welte52255572021-04-03 09:56:32 +0200435def mnc_from_imsi(imsi:str, long:bool=False) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200436 """
437 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
438 """
439 if imsi == None:
440 return None
441
442 if len(imsi) > 3:
443 if long:
444 return imsi[3:6]
445 else:
446 return imsi[3:5]
447 else:
448 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100449
Harald Welte52255572021-04-03 09:56:32 +0200450def derive_mcc(digit1:int, digit2:int, digit3:int) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100451 """
452 Derive decimal representation of the MCC (Mobile Country Code)
453 from three given digits.
454 """
455
456 mcc = 0
457
458 if digit1 != 0x0f:
459 mcc += digit1 * 100
460 if digit2 != 0x0f:
461 mcc += digit2 * 10
462 if digit3 != 0x0f:
463 mcc += digit3
464
465 return mcc
466
Harald Welte52255572021-04-03 09:56:32 +0200467def derive_mnc(digit1:int, digit2:int, digit3:int=0x0f) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100468 """
469 Derive decimal representation of the MNC (Mobile Network Code)
470 from two or (optionally) three given digits.
471 """
472
473 mnc = 0
474
475 # 3-rd digit is optional for the MNC. If present
476 # the algorythm is the same as for the MCC.
477 if digit3 != 0x0f:
478 return derive_mcc(digit1, digit2, digit3)
479
480 if digit1 != 0x0f:
481 mnc += digit1 * 10
482 if digit2 != 0x0f:
483 mnc += digit2
484
485 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100486
Harald Welte52255572021-04-03 09:56:32 +0200487def dec_msisdn(ef_msisdn:Hexstr) -> Optional[Tuple[int,int,Optional[str]]]:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100488 """
489 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
490 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
491 """
492
493 # Convert from str to (kind of) 'bytes'
494 ef_msisdn = h2b(ef_msisdn)
495
496 # Make sure mandatory fields are present
497 if len(ef_msisdn) < 14:
498 raise ValueError("EF.MSISDN is too short")
499
500 # Skip optional Alpha Identifier
501 xlen = len(ef_msisdn) - 14
502 msisdn_lhv = ef_msisdn[xlen:]
503
504 # Parse the length (in bytes) of the BCD encoded number
Harald Welte4f6ca432021-02-01 17:51:56 +0100505 bcd_len = msisdn_lhv[0]
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100506 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
507 if bcd_len == 0xff:
508 return None
509 elif bcd_len > 11 or bcd_len < 1:
510 raise ValueError("Length of MSISDN (%d bytes) is out of range" % bcd_len)
511
512 # Parse ToN / NPI
Harald Welte4f6ca432021-02-01 17:51:56 +0100513 ton = (msisdn_lhv[1] >> 4) & 0x07
514 npi = msisdn_lhv[1] & 0x0f
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100515 bcd_len -= 1
516
517 # No MSISDN?
518 if not bcd_len:
519 return (npi, ton, None)
520
521 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
522 # International number 10.5.118/3GPP TS 24.008
Vadim Yanitskiy7ba24282020-02-27 00:04:13 +0700523 if ton == 0x01:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100524 msisdn = '+' + msisdn
525
526 return (npi, ton, msisdn)
Supreeth Herle5a541012019-12-22 08:59:16 +0100527
Harald Welte52255572021-04-03 09:56:32 +0200528def enc_msisdn(msisdn:str, npi:int=0x01, ton:int=0x03) -> Hexstr:
Supreeth Herle5a541012019-12-22 08:59:16 +0100529 """
530 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200531 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
532 will not contain the optional Alpha Identifier at the beginning.)
Supreeth Herle5a541012019-12-22 08:59:16 +0100533
534 Default NPI / ToN values:
535 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
536 - ToN: network specific or international number (if starts with '+').
537 """
538
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200539 # If no MSISDN is supplied then encode the file contents as all "ff"
540 if msisdn == "" or msisdn == "+":
541 return "ff" * 14
542
Supreeth Herle5a541012019-12-22 08:59:16 +0100543 # Leading '+' indicates International Number
544 if msisdn[0] == '+':
545 msisdn = msisdn[1:]
546 ton = 0x01
547
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200548 # An MSISDN must not exceed 20 digits
549 if len(msisdn) > 20:
550 raise ValueError("msisdn must not be longer than 20 digits")
551
Supreeth Herle5a541012019-12-22 08:59:16 +0100552 # Append 'f' padding if number of digits is odd
553 if len(msisdn) % 2 > 0:
554 msisdn += 'f'
555
556 # BCD length also includes NPI/ToN header
557 bcd_len = len(msisdn) // 2 + 1
558 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
559 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
560
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200561 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
562
Supreeth Herle441c4a72020-03-24 10:19:15 +0100563
Harald Welte52255572021-04-03 09:56:32 +0200564def dec_st(st, table="sim") -> str:
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200565 """
566 Parses the EF S/U/IST and prints the list of available services in EF S/U/IST
567 """
568
Supreeth Herledf330372020-04-20 14:48:55 +0200569 if table == "isim":
570 from pySim.ts_31_103 import EF_IST_map
571 lookup_map = EF_IST_map
572 elif table == "usim":
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200573 from pySim.ts_31_102 import EF_UST_map
574 lookup_map = EF_UST_map
575 else:
576 from pySim.ts_51_011 import EF_SST_map
577 lookup_map = EF_SST_map
578
579 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
580
581 avail_st = ""
582 # Get each byte and check for available services
583 for i in range(0, len(st_bytes)):
584 # Byte i contains info about Services num (8i+1) to num (8i+8)
585 byte = int(st_bytes[i], 16)
586 # Services in each byte are in order MSB to LSB
587 # MSB - Service (8i+8)
588 # LSB - Service (8i+1)
589 for j in range(1, 9):
590 if byte&0x01 == 0x01 and ((8*i) + j in lookup_map):
591 # Byte X contains info about Services num (8X-7) to num (8X)
592 # bit = 1: service available
593 # bit = 0: service not available
594 avail_st += '\tService %d - %s\n' % ((8*i) + j, lookup_map[(8*i) + j])
595 byte = byte >> 1
596 return avail_st
Supreeth Herle98370552020-05-11 09:04:41 +0200597
598def first_TLV_parser(bytelist):
599 '''
600 first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
601
602 parses first TLV format record in a list of bytelist
603 returns a 3-Tuple: Tag, Length, Value
604 Value is a list of bytes
605 parsing of length is ETSI'style 101.220
606 '''
607 Tag = bytelist[0]
608 if bytelist[1] == 0xFF:
609 Len = bytelist[2]*256 + bytelist[3]
610 Val = bytelist[4:4+Len]
611 else:
612 Len = bytelist[1]
613 Val = bytelist[2:2+Len]
614 return (Tag, Len, Val)
615
616def TLV_parser(bytelist):
617 '''
618 TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
619
620 loops on the input list of bytes with the "first_TLV_parser()" function
621 returns a list of 3-Tuples
622 '''
623 ret = []
624 while len(bytelist) > 0:
625 T, L, V = first_TLV_parser(bytelist)
626 if T == 0xFF:
627 # padding bytes
628 break
629 ret.append( (T, L, V) )
630 # need to manage length of L
631 if L > 0xFE:
632 bytelist = bytelist[ L+4 : ]
633 else:
634 bytelist = bytelist[ L+2 : ]
635 return ret
Supreeth Herled572ede2020-03-22 09:55:04 +0100636
Supreeth Herled84daa12020-03-24 12:20:40 +0100637def enc_st(st, service, state=1):
638 """
639 Encodes the EF S/U/IST/EST and returns the updated Service Table
640
641 Parameters:
642 st - Current value of SIM/USIM/ISIM Service Table
643 service - Service Number to encode as activated/de-activated
644 state - 1 mean activate, 0 means de-activate
645
646 Returns:
647 s - Modified value of SIM/USIM/ISIM Service Table
648
649 Default values:
650 - state: 1 - Sets the particular Service bit to 1
651 """
652 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
653
654 s = ""
655 # Check whether the requested service is present in each byte
656 for i in range(0, len(st_bytes)):
657 # Byte i contains info about Services num (8i+1) to num (8i+8)
658 if service in range((8*i) + 1, (8*i) + 9):
659 byte = int(st_bytes[i], 16)
660 # Services in each byte are in order MSB to LSB
661 # MSB - Service (8i+8)
662 # LSB - Service (8i+1)
663 mod_byte = 0x00
664 # Copy bit by bit contents of byte to mod_byte with modified bit
665 # for requested service
666 for j in range(1, 9):
667 mod_byte = mod_byte >> 1
668 if service == (8*i) + j:
669 mod_byte = state == 1 and mod_byte|0x80 or mod_byte&0x7f
670 else:
671 mod_byte = byte&0x01 == 0x01 and mod_byte|0x80 or mod_byte&0x7f
672 byte = byte >> 1
673
674 s += ('%02x' % (mod_byte))
675 else:
676 s += st_bytes[i]
677
678 return s
679
Supreeth Herle3b342c22020-03-24 16:15:02 +0100680def dec_addr_tlv(hexstr):
Supreeth Herled572ede2020-03-22 09:55:04 +0100681 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100682 Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm.
683 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 +0100684 """
685
686 # Convert from hex str to int bytes list
Supreeth Herle3b342c22020-03-24 16:15:02 +0100687 addr_tlv_bytes = h2i(hexstr)
Supreeth Herled572ede2020-03-22 09:55:04 +0100688
689 s = ""
690
691 # Get list of tuples containing parsed TLVs
Supreeth Herle3b342c22020-03-24 16:15:02 +0100692 tlvs = TLV_parser(addr_tlv_bytes)
Supreeth Herled572ede2020-03-22 09:55:04 +0100693
694 for tlv in tlvs:
695 # tlv = (T, L, [V])
696 # T = Tag
697 # L = Length
698 # [V] = List of value
699
700 # Invalid Tag value scenario
701 if tlv[0] != 0x80:
702 continue
703
Supreeth Herled6a5ec52020-06-01 12:27:51 +0200704 # Empty field - Zero length
705 if tlv[1] == 0:
706 continue
707
Supreeth Herled572ede2020-03-22 09:55:04 +0100708 # First byte in the value has the address type
709 addr_type = tlv[2][0]
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100710 # TODO: Support parsing of IPv6
Supreeth Herle3b342c22020-03-24 16:15:02 +0100711 # Address Type: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), other (Reserved)
Supreeth Herled572ede2020-03-22 09:55:04 +0100712 if addr_type == 0x00: #FQDN
713 # Skip address tye byte i.e. first byte in value list
714 content = tlv[2][1:]
715 s += "\t%s # %s\n" % (i2h(content), i2s(content))
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100716 elif addr_type == 0x01: #IPv4
717 # Skip address tye byte i.e. first byte in value list
718 # Skip the unused byte in Octect 4 after address type byte as per 3GPP TS 31.102
719 ipv4 = tlv[2][2:]
720 content = '.'.join(str(x) for x in ipv4)
721 s += "\t%s # %s\n" % (i2h(ipv4), content)
Supreeth Herled572ede2020-03-22 09:55:04 +0100722
723 return s
Philipp Maierff84c232020-05-12 17:24:18 +0200724
Supreeth Herle3b342c22020-03-24 16:15:02 +0100725def enc_addr_tlv(addr, addr_type='00'):
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100726 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100727 Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm.
728 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 +0100729
730 Default values:
Supreeth Herle3b342c22020-03-24 16:15:02 +0100731 - addr_type: 00 - FQDN format of Address
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100732 """
733
734 s = ""
735
Supreeth Herle654eca72020-03-25 14:25:38 +0100736 # TODO: Encoding of IPv6 address
737 if addr_type == '00': #FQDN
Supreeth Herle3b342c22020-03-24 16:15:02 +0100738 hex_str = s2h(addr)
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100739 s += '80' + ('%02x' % ((len(hex_str)//2)+1)) + '00' + hex_str
Supreeth Herle654eca72020-03-25 14:25:38 +0100740 elif addr_type == '01': #IPv4
741 ipv4_list = addr.split('.')
742 ipv4_str = ""
743 for i in ipv4_list:
744 ipv4_str += ('%02x' % (int(i)))
745
746 # Unused bytes shall be set to 'ff'. i.e 4th Octet after Address Type is not used
747 # IPv4 Address is in octet 5 to octet 8 of the TLV data object
748 s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100749
750 return s
751
Harald Welte52255572021-04-03 09:56:32 +0200752def is_hex(string:str, minlen:int=2, maxlen:Optional[int]=None) -> bool:
Philipp Maier47236502021-03-09 21:28:25 +0100753 """
754 Check if a string is a valid hexstring
755 """
756
757 # Filter obviously bad strings
758 if not string:
759 return False
760 if len(string) < minlen or minlen < 2:
761 return False
762 if len(string) % 2:
763 return False
764 if maxlen and len(string) > maxlen:
765 return False
766
767 # Try actual encoding to be sure
768 try:
769 try_encode = h2b(string)
770 return True
771 except:
772 return False
773
Harald Welte52255572021-04-03 09:56:32 +0200774def sanitize_pin_adm(pin_adm, pin_adm_hex = None) -> Hexstr:
Philipp Maiere8536c02020-05-11 21:35:01 +0200775 """
776 The ADM pin can be supplied either in its hexadecimal form or as
777 ascii string. This function checks the supplied opts parameter and
Harald Welte236a65f2021-04-03 10:20:11 +0200778 returns the pin_adm as hex encoded string, regardless in which form
Philipp Maiere8536c02020-05-11 21:35:01 +0200779 it was originally supplied by the user
780 """
781
Harald Welte79b5ba42021-01-08 21:22:38 +0100782 if pin_adm is not None:
783 if len(pin_adm) <= 8:
784 pin_adm = ''.join(['%02x'%(ord(x)) for x in pin_adm])
Philipp Maiere8536c02020-05-11 21:35:01 +0200785 pin_adm = rpad(pin_adm, 16)
786
787 else:
788 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
789
Harald Welte79b5ba42021-01-08 21:22:38 +0100790 if pin_adm_hex is not None:
791 if len(pin_adm_hex) == 16:
792 pin_adm = pin_adm_hex
Philipp Maiere8536c02020-05-11 21:35:01 +0200793 # Ensure that it's hex-encoded
794 try:
795 try_encode = h2b(pin_adm)
796 except ValueError:
797 raise ValueError("PIN-ADM needs to be hex encoded using this option")
798 else:
799 raise ValueError("PIN-ADM needs to be exactly 16 digits (hex encoded)")
800
801 return pin_adm
802
Supreeth Herle95ec7722020-03-24 13:09:03 +0100803def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='00'):
804 """
805 Encode ePDGSelection so it can be stored at EF.ePDGSelection or EF.ePDGSelectionEm.
806 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
807
808 Default values:
809 - epdg_priority: '0001' - 1st Priority
810 - epdg_fqdn_format: '00' - Operator Identifier FQDN
811 """
812
813 plmn1 = enc_plmn(mcc, mnc) + epdg_priority + epdg_fqdn_format
814 # TODO: Handle encoding of Length field for length more than 127 Bytes
815 content = '80' + ('%02x' % (len(plmn1)//2)) + plmn1
816 content = rpad(content, len(hexstr))
817 return content
818
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100819def dec_ePDGSelection(sixhexbytes):
820 """
821 Decode ePDGSelection to get EF.ePDGSelection or EF.ePDGSelectionEm.
822 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
823 """
824
825 res = {'mcc': 0, 'mnc': 0, 'epdg_priority': 0, 'epdg_fqdn_format': ''}
826 plmn_chars = 6
827 epdg_priority_chars = 4
828 epdg_fqdn_format_chars = 2
829 # first three bytes (six ascii hex chars)
830 plmn_str = sixhexbytes[:plmn_chars]
831 # two bytes after first three bytes
832 epdg_priority_str = sixhexbytes[plmn_chars:plmn_chars + epdg_priority_chars]
833 # one byte after first five bytes
834 epdg_fqdn_format_str = sixhexbytes[plmn_chars + epdg_priority_chars:plmn_chars + epdg_priority_chars + epdg_fqdn_format_chars]
835 res['mcc'] = dec_mcc_from_plmn(plmn_str)
836 res['mnc'] = dec_mnc_from_plmn(plmn_str)
837 res['epdg_priority'] = epdg_priority_str
838 res['epdg_fqdn_format'] = epdg_fqdn_format_str == '00' and 'Operator Identifier FQDN' or 'Location based FQDN'
839 return res
840
841def format_ePDGSelection(hexstr):
842 ePDGSelection_info_tag_chars = 2
843 ePDGSelection_info_tag_str = hexstr[:2]
herlesupreeth3a261d32021-01-05 09:20:11 +0100844 s = ""
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100845 # Minimum length
846 len_chars = 2
847 # TODO: Need to determine length properly - definite length support only
848 # Inconsistency in spec: 3GPP TS 31.102 version 15.2.0 Release 15, 4.2.104
849 # As per spec, length is 5n, n - number of PLMNs
850 # But, each PLMN entry is made of PLMN (3 Bytes) + ePDG Priority (2 Bytes) + ePDG FQDN format (1 Byte)
851 # Totalling to 6 Bytes, maybe length should be 6n
852 len_str = hexstr[ePDGSelection_info_tag_chars:ePDGSelection_info_tag_chars+len_chars]
herlesupreeth3a261d32021-01-05 09:20:11 +0100853
854 # Not programmed scenario
855 if int(len_str, 16) == 255 or int(ePDGSelection_info_tag_str, 16) == 255:
856 len_chars = 0
857 ePDGSelection_info_tag_chars = 0
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100858 if len_str[0] == '8':
859 # The bits 7 to 1 denotes the number of length octets if length > 127
860 if int(len_str[1]) > 0:
861 # Update number of length octets
862 len_chars = len_chars * int(len_str[1])
863 len_str = hexstr[ePDGSelection_info_tag_chars:len_chars]
864
865 content_str = hexstr[ePDGSelection_info_tag_chars+len_chars:]
866 # Right pad to prevent index out of range - multiple of 6 bytes
867 content_str = rpad(content_str, len(content_str) + (12 - (len(content_str) % 12)))
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100868 for rec_data in hexstr_to_Nbytearr(content_str, 6):
869 rec_info = dec_ePDGSelection(rec_data)
870 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
871 rec_str = "unused"
872 else:
873 rec_str = "MCC: %03d MNC: %03d ePDG Priority: %s ePDG FQDN format: %s" % \
874 (rec_info['mcc'], rec_info['mnc'], rec_info['epdg_priority'], rec_info['epdg_fqdn_format'])
875 s += "\t%s # %s\n" % (rec_data, rec_str)
876 return s
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100877
878def get_addr_type(addr):
879 """
880 Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
881 Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given)
882
883 TODO: Handle IPv6
884 """
885
886 # Empty address string
887 if not len(addr):
888 return None
889
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100890 addr_list = addr.split('.')
891
892 # Check for IPv4/IPv6
893 try:
894 import ipaddress
895 # Throws ValueError if addr is not correct
Denis 'GNUtoo' Carikli79f5b602020-02-15 04:02:57 +0700896 ipa = ipaddress.ip_address(addr)
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100897
898 if ipa.version == 4:
899 return 0x01
900 elif ipa.version == 6:
901 return 0x02
902 except Exception as e:
903 invalid_ipv4 = True
904 for i in addr_list:
905 # Invalid IPv4 may qualify for a valid FQDN, so make check here
906 # e.g. 172.24.15.300
907 import re
908 if not re.match('^[0-9_]+$', i):
909 invalid_ipv4 = False
910 break
911
912 if invalid_ipv4:
913 return None
914
915 fqdn_flag = True
916 for i in addr_list:
917 # Only Alpha-numeric characters and hyphen - RFC 1035
918 import re
919 if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
920 fqdn_flag = False
921 break
922
923 # FQDN
924 if fqdn_flag:
925 return 0x00
926
927 return None
Harald Welte67d551a2021-01-21 14:50:01 +0100928
Harald Welte1e456572021-04-02 17:16:30 +0200929def sw_match(sw:str, pattern:str) -> bool:
Harald Welte67d551a2021-01-21 14:50:01 +0100930 """Match given SW against given pattern."""
931 # Create a masked version of the returned status word
932 sw_lower = sw.lower()
933 sw_masked = ""
934 for i in range(0, 4):
Philipp Maier78e32f22021-03-22 20:39:24 +0100935 if pattern[i] == '?':
Harald Welte67d551a2021-01-21 14:50:01 +0100936 sw_masked = sw_masked + '?'
Philipp Maier78e32f22021-03-22 20:39:24 +0100937 elif pattern[i] == 'x':
Harald Welte67d551a2021-01-21 14:50:01 +0100938 sw_masked = sw_masked + 'x'
939 else:
940 sw_masked = sw_masked + sw_lower[i]
Philipp Maier78e32f22021-03-22 20:39:24 +0100941 # Compare the masked version against the pattern
Harald Welte67d551a2021-01-21 14:50:01 +0100942 return sw_masked == pattern
Philipp Maier5d3e2592021-02-22 17:22:16 +0100943
Harald Welteee3501f2021-04-02 13:00:18 +0200944def tabulate_str_list(str_list, width:int = 79, hspace:int = 2, lspace:int = 1,
Harald Welte52255572021-04-03 09:56:32 +0200945 align_left:bool = True) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +0200946 """Pretty print a list of strings into a tabulated form.
947
948 Args:
949 width : total width in characters per line
950 space : horizontal space between cells
951 lspace : number of spaces before row
952 align_lef : Align text to the left side
953 Returns:
954 multi-line string containing formatted table
955 """
Philipp Maier5d3e2592021-02-22 17:22:16 +0100956 if str_list == None:
957 return ""
958 if len(str_list) <= 0:
959 return ""
960 longest_str = max(str_list, key=len)
961 cellwith = len(longest_str) + hspace
962 cols = width // cellwith
963 rows = (len(str_list) - 1) // cols + 1
964 table = []
965 for i in iter(range(rows)):
966 str_list_row = str_list[i::rows]
967 if (align_left):
968 format_str_cell = '%%-%ds'
969 else:
970 format_str_cell = '%%%ds'
971 format_str_row = (format_str_cell % cellwith) * len(str_list_row)
972 format_str_row = (" " * lspace) + format_str_row
973 table.append(format_str_row % tuple(str_list_row))
974 return '\n'.join(table)
Harald Welte5e749a72021-04-10 17:18:17 +0200975
Harald Welte917d98c2021-04-21 11:51:25 +0200976def auto_int(x):
977 """Helper function for argparse to accept hexadecimal integers."""
978 return int(x, 0)
979
Harald Welte5e749a72021-04-10 17:18:17 +0200980class JsonEncoder(json.JSONEncoder):
981 """Extend the standard library JSONEncoder with support for more types."""
982 def default(self, o):
983 if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
984 return b2h(o)
985 return json.JSONEncoder.default(self, o)
Philipp Maier80ce71f2021-04-19 21:24:23 +0200986
987def boxed_heading_str(heading, width=80):
988 """Generate a string that contains a boxed heading."""
989 # Auto-enlarge box if heading exceeds length
990 if len(heading) > width - 4:
991 width = len(heading) + 4
992
993 res = "#" * width
994 fstr = "\n# %-" + str(width - 4) + "s #\n"
995 res += fstr % (heading)
996 res += "#" * width
997 return res
Harald Welte3de6ca22021-05-02 20:05:56 +0200998
999
1000
1001class DataObject(abc.ABC):
1002 """A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
1003 simply has any number of different TLVs that may occur in any order at any point, ISO 7816
1004 has the habit of specifying TLV data but with very spcific ordering, or specific choices of
1005 tags at specific points in a stream. This class tries to represent this."""
1006 def __init__(self, name, desc = None, tag = None):
1007 """
1008 Args:
1009 name: A brief, all-lowercase, underscore separated string identifier
1010 desc: A human-readable description of what this DO represents
1011 tag : The tag associated with this DO
1012 """
1013 self.name = name
1014 self.desc = desc
1015 self.tag = tag
1016 self.decoded = None
1017 self.encoded = None
1018
1019 def __str__(self):
1020 return self.name
1021
1022 def __repr__(self):
1023 return '%s(%s)' % (self.__class__, self.name)
1024
1025 def __or__(self, other):
1026 """OR-ing DataObjects together renders a DataObjectChoice."""
1027 if isinstance(other, DataObject):
1028 # DataObject | DataObject = DataObjectChoice
1029 return DataObjectChoice(None, members=[self, other])
1030 else:
1031 raise TypeError
1032
1033 def __add__(self, other):
1034 """ADD-ing DataObjects together renders a DataObjectCollection."""
1035 if isinstance(other, DataObject):
1036 # DataObject + DataObject = DataObjectCollectin
1037 return DataObjectCollection(None, members=[self, other])
1038
1039 def _compute_tag(self):
1040 """Compute the tag (sometimes the tag encodes part of the value)."""
1041 return self.tag
1042
1043 def to_dict(self):
1044 """Return a dict in form "name: decoded_value" """
1045 return {self.name: self.decoded}
1046
1047 @abc.abstractmethod
1048 def from_bytes(self, do:bytes):
1049 """Parse the value part of the DO into the internal state of this instance.
1050 Args:
1051 do : binary encoded bytes
1052 """
1053
1054 @abc.abstractmethod
1055 def to_bytes(self):
1056 """Encode the internal state of this instance into the TLV value part.
1057 Returns:
1058 binary bytes encoding the internal state
1059 """
1060
1061 def from_tlv(self, do:bytes):
1062 """Parse binary TLV representation into internal state. The resulting decoded
1063 representation is _not_ returned, but just internalized in the object instance!
1064 Args:
1065 do : input bytes containing TLV-encoded representation
1066 Returns:
1067 bytes remaining at end of 'do' after parsing one TLV/DO.
1068 """
1069 if do[0] != self.tag:
1070 raise ValueError('%s: Can only decode tag 0x%02x' % (self, self.tag))
1071 length = do[1]
1072 val = do[2:2+length]
1073 self.from_bytes(val)
1074 # return remaining bytes
1075 return do[2+length:]
1076
1077 def to_tlv(self):
1078 """Encode internal representation to binary TLV.
1079 Returns:
1080 bytes encoded in TLV format.
1081 """
1082 val = self.to_bytes()
1083 return bytes(self._compute_tag()) + bytes(len(val)) + val
1084
1085 # 'codec' interface
1086 def decode(self, binary:bytes):
1087 """Decode a single DOs from the input data.
1088 Args:
1089 binary : binary bytes of encoded data
1090 Returns:
1091 tuple of (decoded_result, binary_remainder)
1092 """
1093 tag = binary[0]
1094 if tag != self.tag:
1095 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected 0x%02x' %
1096 (self, tag, binary, self.tag))
1097 remainder = self.from_tlv(binary)
1098 return (self.to_dict(), remainder)
1099
1100 # 'codec' interface
1101 def encode(self):
1102 return self.to_tlv()
1103
1104class TL0_DataObject(DataObject):
1105 """Data Object that has Tag, Len=0 and no Value part."""
1106 def __init__(self, name, desc, tag, val=None):
1107 super().__init__(name, desc, tag)
1108 self.val = val
1109
1110 def from_bytes(self, binary:bytes):
1111 if len(binary) != 0:
1112 raise ValueError
1113 self.decoded = self.val
1114
1115 def to_bytes(self):
1116 return b''
1117
1118
1119class DataObjectCollection:
1120 """A DataObjectCollection consits of multiple Data Objects identified by their tags.
1121 A given encoded DO may contain any of them in any order, and may contain multiple instances
1122 of each DO."""
1123 def __init__(self, name, desc = None, members=None):
1124 self.name = name
1125 self.desc = desc
1126 self.members = members or []
1127 self.members_by_tag = {}
1128 self.members_by_name = {}
1129 self.members_by_tag = { m.tag:m for m in members }
1130 self.members_by_name = { m.name:m for m in members }
1131
1132 def __str__(self):
1133 member_strs = [str(x) for x in self.members]
1134 return '%s(%s)' % (self.name, ','.join(member_strs))
1135
1136 def __repr__(self):
1137 member_strs = [repr(x) for x in self.members]
1138 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1139
1140 def __add__(self, other):
1141 """Extending DataCollections with other DataCollections or DataObjects."""
1142 if isinstance(other, DataObjectCollection):
1143 # adding one collection to another
1144 members = self.members + other.members
1145 return DataObjectCollection(self.name, self.desc, members)
1146 elif isinstance(other, DataObject):
1147 # adding a member to a collection
1148 return DataObjectCollection(self.name, self.desc, self.members + [other])
1149 else:
1150 raise TypeError
1151
1152 # 'codec' interface
1153 def decode(self, binary:bytes):
1154 """Decode any number of DOs from the collection until the end of the input data,
1155 or uninitialized memory (0xFF) is found.
1156 Args:
1157 binary : binary bytes of encoded data
1158 Returns:
1159 tuple of (decoded_result, binary_remainder)
1160 """
1161 res = []
1162 remainder = binary
1163 # iterate until no binary trailer is left
1164 while len(remainder):
1165 tag = remainder[0]
1166 if tag == 0xff: # uninitialized memory at the end?
1167 return (res, remainder)
1168 if not tag in self.members_by_tag:
1169 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1170 (self, tag, remainder, self.members_by_tag.keys()))
1171 obj = self.members_by_tag[tag]
1172 # DO from_tlv returns remainder of binary
1173 remainder = obj.from_tlv(remainder)
1174 # collect our results
1175 res.append(obj.to_dict())
1176 return (res, remainder)
1177
1178 # 'codec' interface
1179 def encode(self, decoded):
1180 res = bytearray()
1181 for i in decoded:
1182 obj = self.members_by_name(i[0])
1183 res.append(obj.to_tlv())
1184 return res
1185
1186class DataObjectChoice(DataObjectCollection):
1187 """One Data Object from within a choice, identified by its tag.
1188 This means that exactly one member of the choice must occur, and which one occurs depends
1189 on the tag."""
1190 def __add__(self, other):
1191 """We overload the add operator here to avoid inheriting it from DataObjecCollection."""
1192 raise TypeError
1193
1194 def __or__(self, other):
1195 """OR-ing a Choice to another choice extends the choice, as does OR-ing a DataObject."""
1196 if isinstance(other, DataObjectChoice):
1197 # adding one collection to another
1198 members = self.members + other.members
1199 return DataObjectChoice(self.name, self.desc, members)
1200 elif isinstance(other, DataObject):
1201 # adding a member to a collection
1202 return DataObjectChoice(self.name, self.desc, self.members + [other])
1203 else:
1204 raise TypeError
1205
1206 # 'codec' interface
1207 def decode(self, binary:bytes):
1208 """Decode a single DOs from the choice based on the tag.
1209 Args:
1210 binary : binary bytes of encoded data
1211 Returns:
1212 tuple of (decoded_result, binary_remainder)
1213 """
1214 tag = binary[0]
1215 if tag == 0xff:
1216 return (None, binary)
1217 if not tag in self.members_by_tag:
1218 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1219 (self, tag, binary, self.members_by_tag.keys()))
1220 obj = self.members_by_tag[tag]
1221 remainder = obj.from_tlv(binary)
1222 return (obj.to_dict(), remainder)
1223
1224 # 'codec' interface
1225 def encode(self, decoded):
1226 obj = self.members_by_name(decoded[0])
1227 return obj.to_tlv()
1228
1229class DataObjectSequence:
1230 """A sequence of DataObjects or DataObjectChoices. This allows us to express a certain
1231 ordered sequence of DOs or choices of DOs that have to appear as per the specification.
1232 By wrapping them into this formal DataObjectSequence, we can offer convenience methods
1233 for encoding or decoding an entire sequence."""
1234 def __init__(self, name, desc=None, sequence=None):
1235 self.sequence = sequence or []
1236 self.name = name
1237 self.desc = desc
1238
1239 def __str__(self):
1240 member_strs = [str(x) for x in self.sequence]
1241 return '%s(%s)' % (self.name, ','.join(member_strs))
1242
1243 def __repr__(self):
1244 member_strs = [repr(x) for x in self.sequence]
1245 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1246
1247 def __add__(self, other):
1248 """Add (append) a DataObject or DataObjectChoice to the sequence."""
1249 if isinstance(other, 'DataObject'):
1250 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
1251 elif isinstance(other, 'DataObjectChoice'):
1252 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
1253 elif isinstance(other, 'DataObjectSequence'):
1254 return DataObjectSequence(self.name, self.desc, self.sequence + other.sequence)
1255
1256 # 'codec' interface
1257 def decode(self, binary:bytes):
1258 """Decode a sequence by calling the decoder of each element in the sequence.
1259 Args:
1260 binary : binary bytes of encoded data
1261 Returns:
1262 tuple of (decoded_result, binary_remainder)
1263 """
1264 remainder = binary
1265 res = []
1266 for e in self.sequence:
1267 (r, remainder) = e.decode(remainder)
1268 if r:
1269 res.append(r)
1270 return (res, remainder)
1271
1272 # 'codec' interface
1273 def decode_multi(self, do:bytes):
1274 """Decode multiple occurrences of the sequence from the binary input data.
1275 Args:
1276 do : binary input data to be decoded
1277 Returns:
1278 list of results of the decoder of this sequences
1279 """
1280 remainder = do
1281 res = []
1282 while len(remainder):
1283 (r, remainder2) = self.decode(remainder)
1284 if r:
1285 res.append(r)
1286 if len(remainder2) < len(remainder):
1287 remainder = remainder2
1288 else:
1289 remainder = remainder2
1290 break
1291 return (res, remainder)
1292
1293 # 'codec' interface
1294 def encode(self, decoded):
1295 """Encode a sequence by calling the encoder of each element in the sequence."""
1296 encoded = bytearray()
1297 i = 0
1298 for e in self.sequence:
1299 encoded += e.encode(decoded[i])
1300 i += 1
1301 return encoded
Harald Welte90441432021-05-02 21:28:12 +02001302
1303class CardCommand:
1304 """A single card command / instruction."""
1305 def __init__(self, name, ins, cla_list=None, desc=None):
1306 self.name = name
1307 self.ins = ins
1308 self.cla_list = cla_list or []
1309 self.cla_list = [x.lower() for x in self.cla_list]
1310 self.desc = desc
1311
1312 def __str__(self):
1313 return self.name
1314
1315 def __repr__(self):
1316 return '%s(INS=%02x,CLA=%s)' % (self.name, self.ins, self.cla_list)
1317
1318 def match_cla(self, cla):
1319 """Does the given CLA match the CLA list of the command?."""
1320 if not isinstance(cla, str):
1321 cla = '%02u' % cla
1322 cla = cla.lower()
1323 for cla_match in self.cla_list:
1324 cla_masked = ""
1325 for i in range(0, 2):
1326 if cla_match[i] == 'x':
1327 cla_masked += 'x'
1328 else:
1329 cla_masked += cla[i]
1330 if cla_masked == cla_match:
1331 return True
1332 return False
1333
1334
1335class CardCommandSet:
1336 """A set of card instructions, typically specified within one spec."""
1337 def __init__(self, name, cmds=[]):
1338 self.name = name
1339 self.cmds = { c.ins : c for c in cmds }
1340
1341 def __str__(self):
1342 return self.name
1343
1344 def __getitem__(self, idx):
1345 return self.cmds[idx]
1346
1347 def __add__(self, other):
1348 if isinstance(other, CardCommand):
1349 if other.ins in self.cmds:
1350 raise ValueError('%s: INS 0x%02x already defined: %s' %
1351 (self, other.ins, self.cmds[other.ins]))
1352 self.cmds[other.ins] = other
1353 elif isinstance(other, CardCommandSet):
1354 for c in other.cmds.keys():
1355 self.cmds[c] = other.cmds[c]
1356 else:
1357 raise ValueError('%s: Unsupported type to add operator: %s' % (self, other))
1358
1359 def lookup(self, ins, cla=None):
1360 """look-up the command within the CommandSet."""
1361 ins = int(ins)
1362 if not ins in self.cmds:
1363 return None
1364 cmd = self.cmds[ins]
1365 if cla and not cmd.match_cla(cla):
1366 return None
1367 return cmd