blob: 11919833c8ee3c1f68b0e2365b1f0d1822b4be2c [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]
Harald Weltede027182021-05-04 20:06:45 +0200135 return (length, binary[1+num_len_oct:])
Harald Welte917d98c2021-04-21 11:51:25 +0200136
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):
Robert Falkenbergb07a3e92021-05-07 15:23:20 +0200251 """Obsolete, kept for API compatibility"""
252 from ts_51_011 import EF_SPN
253 abstract_data = EF_SPN().decode_hex(ef)
254 show_in_hplmn = abstract_data['show_in_hplmn']
255 hide_in_oplmn = abstract_data['hide_in_oplmn']
256 name = abstract_data['spn']
257 return (name, show_in_hplmn, hide_in_oplmn)
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300258
Robert Falkenbergb07a3e92021-05-07 15:23:20 +0200259def enc_spn(name:str, show_in_hplmn=False, hide_in_oplmn=False):
260 """Obsolete, kept for API compatibility"""
261 from ts_51_011 import EF_SPN
262 abstract_data = {
263 'hide_in_oplmn' : hide_in_oplmn,
264 'show_in_hplmn' : show_in_hplmn,
265 'spn' : name,
266 }
267 return EF_SPN().encode_hex(abstract_data)
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900268
Supreeth Herlef3948532020-03-24 12:23:51 +0100269def hexstr_to_Nbytearr(s, nbytes):
270 return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2)) ]
271
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100272# Accepts hex string representing three bytes
Harald Welte52255572021-04-03 09:56:32 +0200273def dec_mcc_from_plmn(plmn:Hexstr) -> int:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100274 ia = h2i(plmn)
275 digit1 = ia[0] & 0x0F # 1st byte, LSB
276 digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB
277 digit3 = ia[1] & 0x0F # 2nd byte, LSB
278 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
279 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100280 return derive_mcc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100281
Philipp Maier6c5cd802021-04-23 21:19:36 +0200282def dec_mcc_from_plmn_str(plmn:Hexstr) -> str:
283 digit1 = plmn[1] # 1st byte, LSB
284 digit2 = plmn[0] # 1st byte, MSB
285 digit3 = plmn[3] # 2nd byte, LSB
286 res = digit1 + digit2 + digit3
287 return res.upper().strip("F")
288
Harald Welte52255572021-04-03 09:56:32 +0200289def dec_mnc_from_plmn(plmn:Hexstr) -> int:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100290 ia = h2i(plmn)
Vadim Yanitskiyb271be32021-03-11 23:56:58 +0100291 digit1 = ia[2] & 0x0F # 3rd byte, LSB
292 digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB
293 digit3 = (ia[1] & 0xF0) >> 4 # 2nd byte, MSB
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100294 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
295 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100296 return derive_mnc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100297
Philipp Maier6c5cd802021-04-23 21:19:36 +0200298def dec_mnc_from_plmn_str(plmn:Hexstr) -> str:
299 digit1 = plmn[5] # 3rd byte, LSB
300 digit2 = plmn[4] # 3rd byte, MSB
301 digit3 = plmn[2] # 2nd byte, MSB
302 res = digit1 + digit2 + digit3
303 return res.upper().strip("F")
304
Harald Welte52255572021-04-03 09:56:32 +0200305def dec_act(twohexbytes:Hexstr) -> List[str]:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100306 act_list = [
307 {'bit': 15, 'name': "UTRAN"},
308 {'bit': 14, 'name': "E-UTRAN"},
309 {'bit': 7, 'name': "GSM"},
310 {'bit': 6, 'name': "GSM COMPACT"},
311 {'bit': 5, 'name': "cdma2000 HRPD"},
312 {'bit': 4, 'name': "cdma2000 1xRTT"},
313 ]
314 ia = h2i(twohexbytes)
315 u16t = (ia[0] << 8)|ia[1]
316 sel = []
317 for a in act_list:
318 if u16t & (1 << a['bit']):
Philipp Maiere7d41792021-04-29 16:20:07 +0200319 if a['name'] == "E-UTRAN":
320 # The Access technology identifier of E-UTRAN
321 # allows a more detailed specification:
322 if u16t & (1 << 13) and u16t & (1 << 12):
323 sel.append("E-UTRAN WB-S1")
324 sel.append("E-UTRAN NB-S1")
325 elif u16t & (1 << 13):
326 sel.append("E-UTRAN WB-S1")
327 elif u16t & (1 << 12):
328 sel.append("E-UTRAN NB-S1")
329 else:
330 sel.append("E-UTRAN")
331 else:
332 sel.append(a['name'])
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100333 return sel
334
Harald Welte52255572021-04-03 09:56:32 +0200335def dec_xplmn_w_act(fivehexbytes:Hexstr) -> Dict[str,Any]:
Philipp Maierb919f8b2021-04-27 18:28:27 +0200336 res = {'mcc': "0", 'mnc': "0", 'act': []}
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100337 plmn_chars = 6
338 act_chars = 4
339 plmn_str = fivehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
340 act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars] # two bytes after first three bytes
Philipp Maierb919f8b2021-04-27 18:28:27 +0200341 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
342 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100343 res['act'] = dec_act(act_str)
344 return res
345
346def format_xplmn_w_act(hexstr):
347 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200348 for rec_data in hexstr_to_Nbytearr(hexstr, 5):
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100349 rec_info = dec_xplmn_w_act(rec_data)
Philipp Maierb919f8b2021-04-27 18:28:27 +0200350 if rec_info['mcc'] == "" and rec_info['mnc'] == "":
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100351 rec_str = "unused"
352 else:
Philipp Maierb919f8b2021-04-27 18:28:27 +0200353 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 +0100354 s += "\t%s # %s\n" % (rec_data, rec_str)
355 return s
356
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100357def dec_loci(hexstr):
358 res = {'tmsi': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0}
359 res['tmsi'] = hexstr[:8]
360 res['mcc'] = dec_mcc_from_plmn(hexstr[8:14])
361 res['mnc'] = dec_mnc_from_plmn(hexstr[8:14])
362 res['lac'] = hexstr[14:18]
363 res['status'] = h2i(hexstr[20:22])
364 return res
365
366def dec_psloci(hexstr):
367 res = {'p-tmsi': '', 'p-tmsi-sig': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'rac': '', 'status': 0}
368 res['p-tmsi'] = hexstr[:8]
369 res['p-tmsi-sig'] = hexstr[8:14]
370 res['mcc'] = dec_mcc_from_plmn(hexstr[14:20])
371 res['mnc'] = dec_mnc_from_plmn(hexstr[14:20])
372 res['lac'] = hexstr[20:24]
373 res['rac'] = hexstr[24:26]
374 res['status'] = h2i(hexstr[26:28])
375 return res
376
377def dec_epsloci(hexstr):
378 res = {'guti': '', 'mcc': 0, 'mnc': 0, 'tac': '', 'status': 0}
379 res['guti'] = hexstr[:24]
380 res['tai'] = hexstr[24:34]
381 res['mcc'] = dec_mcc_from_plmn(hexstr[24:30])
382 res['mnc'] = dec_mnc_from_plmn(hexstr[24:30])
383 res['tac'] = hexstr[30:34]
384 res['status'] = h2i(hexstr[34:36])
385 return res
386
Harald Welte52255572021-04-03 09:56:32 +0200387def dec_xplmn(threehexbytes:Hexstr) -> dict:
Harald Welteca673942020-06-03 15:19:40 +0200388 res = {'mcc': 0, 'mnc': 0, 'act': []}
389 plmn_chars = 6
390 plmn_str = threehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
391 res['mcc'] = dec_mcc_from_plmn(plmn_str)
392 res['mnc'] = dec_mnc_from_plmn(plmn_str)
393 return res
394
Harald Welte52255572021-04-03 09:56:32 +0200395def format_xplmn(hexstr:Hexstr) -> str:
Harald Welteca673942020-06-03 15:19:40 +0200396 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200397 for rec_data in hexstr_to_Nbytearr(hexstr, 3):
Harald Welteca673942020-06-03 15:19:40 +0200398 rec_info = dec_xplmn(rec_data)
399 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
400 rec_str = "unused"
401 else:
402 rec_str = "MCC: %03d MNC: %03d" % (rec_info['mcc'], rec_info['mnc'])
403 s += "\t%s # %s\n" % (rec_data, rec_str)
404 return s
405
Harald Welte52255572021-04-03 09:56:32 +0200406def derive_milenage_opc(ki_hex:Hexstr, op_hex:Hexstr) -> Hexstr:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900407 """
408 Run the milenage algorithm to calculate OPC from Ki and OP
409 """
410 from Crypto.Cipher import AES
411 from Crypto.Util.strxor import strxor
412 from pySim.utils import b2h
413
414 # We pass in hex string and now need to work on bytes
Harald Welteeab8d2a2021-03-05 18:30:23 +0100415 ki_bytes = bytes(h2b(ki_hex))
416 op_bytes = bytes(h2b(op_hex))
Harald Welteab34fa82021-03-05 18:39:59 +0100417 aes = AES.new(ki_bytes, AES.MODE_ECB)
Harald Welteeab8d2a2021-03-05 18:30:23 +0100418 opc_bytes = aes.encrypt(op_bytes)
419 return b2h(strxor(opc_bytes, op_bytes))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900420
Harald Welte52255572021-04-03 09:56:32 +0200421def calculate_luhn(cc) -> int:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900422 """
423 Calculate Luhn checksum used in e.g. ICCID and IMEI
424 """
Daniel Willmanndd014ea2020-10-19 10:35:11 +0200425 num = list(map(int, str(cc)))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900426 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10
427 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200428
Harald Welte52255572021-04-03 09:56:32 +0200429def mcc_from_imsi(imsi:str) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200430 """
431 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
432 """
433 if imsi == None:
434 return None
435
436 if len(imsi) > 3:
437 return imsi[:3]
438 else:
439 return None
440
Harald Welte52255572021-04-03 09:56:32 +0200441def mnc_from_imsi(imsi:str, long:bool=False) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200442 """
443 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
444 """
445 if imsi == None:
446 return None
447
448 if len(imsi) > 3:
449 if long:
450 return imsi[3:6]
451 else:
452 return imsi[3:5]
453 else:
454 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100455
Harald Welte52255572021-04-03 09:56:32 +0200456def derive_mcc(digit1:int, digit2:int, digit3:int) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100457 """
458 Derive decimal representation of the MCC (Mobile Country Code)
459 from three given digits.
460 """
461
462 mcc = 0
463
464 if digit1 != 0x0f:
465 mcc += digit1 * 100
466 if digit2 != 0x0f:
467 mcc += digit2 * 10
468 if digit3 != 0x0f:
469 mcc += digit3
470
471 return mcc
472
Harald Welte52255572021-04-03 09:56:32 +0200473def derive_mnc(digit1:int, digit2:int, digit3:int=0x0f) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100474 """
475 Derive decimal representation of the MNC (Mobile Network Code)
476 from two or (optionally) three given digits.
477 """
478
479 mnc = 0
480
481 # 3-rd digit is optional for the MNC. If present
482 # the algorythm is the same as for the MCC.
483 if digit3 != 0x0f:
484 return derive_mcc(digit1, digit2, digit3)
485
486 if digit1 != 0x0f:
487 mnc += digit1 * 10
488 if digit2 != 0x0f:
489 mnc += digit2
490
491 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100492
Harald Welte52255572021-04-03 09:56:32 +0200493def dec_msisdn(ef_msisdn:Hexstr) -> Optional[Tuple[int,int,Optional[str]]]:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100494 """
495 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
496 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
497 """
498
499 # Convert from str to (kind of) 'bytes'
500 ef_msisdn = h2b(ef_msisdn)
501
502 # Make sure mandatory fields are present
503 if len(ef_msisdn) < 14:
504 raise ValueError("EF.MSISDN is too short")
505
506 # Skip optional Alpha Identifier
507 xlen = len(ef_msisdn) - 14
508 msisdn_lhv = ef_msisdn[xlen:]
509
510 # Parse the length (in bytes) of the BCD encoded number
Harald Welte4f6ca432021-02-01 17:51:56 +0100511 bcd_len = msisdn_lhv[0]
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100512 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
513 if bcd_len == 0xff:
514 return None
515 elif bcd_len > 11 or bcd_len < 1:
516 raise ValueError("Length of MSISDN (%d bytes) is out of range" % bcd_len)
517
518 # Parse ToN / NPI
Harald Welte4f6ca432021-02-01 17:51:56 +0100519 ton = (msisdn_lhv[1] >> 4) & 0x07
520 npi = msisdn_lhv[1] & 0x0f
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100521 bcd_len -= 1
522
523 # No MSISDN?
524 if not bcd_len:
525 return (npi, ton, None)
526
527 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
528 # International number 10.5.118/3GPP TS 24.008
Vadim Yanitskiy7ba24282020-02-27 00:04:13 +0700529 if ton == 0x01:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100530 msisdn = '+' + msisdn
531
532 return (npi, ton, msisdn)
Supreeth Herle5a541012019-12-22 08:59:16 +0100533
Harald Welte52255572021-04-03 09:56:32 +0200534def enc_msisdn(msisdn:str, npi:int=0x01, ton:int=0x03) -> Hexstr:
Supreeth Herle5a541012019-12-22 08:59:16 +0100535 """
536 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200537 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
538 will not contain the optional Alpha Identifier at the beginning.)
Supreeth Herle5a541012019-12-22 08:59:16 +0100539
540 Default NPI / ToN values:
541 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
542 - ToN: network specific or international number (if starts with '+').
543 """
544
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200545 # If no MSISDN is supplied then encode the file contents as all "ff"
546 if msisdn == "" or msisdn == "+":
547 return "ff" * 14
548
Supreeth Herle5a541012019-12-22 08:59:16 +0100549 # Leading '+' indicates International Number
550 if msisdn[0] == '+':
551 msisdn = msisdn[1:]
552 ton = 0x01
553
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200554 # An MSISDN must not exceed 20 digits
555 if len(msisdn) > 20:
556 raise ValueError("msisdn must not be longer than 20 digits")
557
Supreeth Herle5a541012019-12-22 08:59:16 +0100558 # Append 'f' padding if number of digits is odd
559 if len(msisdn) % 2 > 0:
560 msisdn += 'f'
561
562 # BCD length also includes NPI/ToN header
563 bcd_len = len(msisdn) // 2 + 1
564 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
565 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
566
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200567 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
568
Supreeth Herle441c4a72020-03-24 10:19:15 +0100569
Harald Welte52255572021-04-03 09:56:32 +0200570def dec_st(st, table="sim") -> str:
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200571 """
572 Parses the EF S/U/IST and prints the list of available services in EF S/U/IST
573 """
574
Supreeth Herledf330372020-04-20 14:48:55 +0200575 if table == "isim":
576 from pySim.ts_31_103 import EF_IST_map
577 lookup_map = EF_IST_map
578 elif table == "usim":
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200579 from pySim.ts_31_102 import EF_UST_map
580 lookup_map = EF_UST_map
581 else:
582 from pySim.ts_51_011 import EF_SST_map
583 lookup_map = EF_SST_map
584
585 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
586
587 avail_st = ""
588 # Get each byte and check for available services
589 for i in range(0, len(st_bytes)):
590 # Byte i contains info about Services num (8i+1) to num (8i+8)
591 byte = int(st_bytes[i], 16)
592 # Services in each byte are in order MSB to LSB
593 # MSB - Service (8i+8)
594 # LSB - Service (8i+1)
595 for j in range(1, 9):
596 if byte&0x01 == 0x01 and ((8*i) + j in lookup_map):
597 # Byte X contains info about Services num (8X-7) to num (8X)
598 # bit = 1: service available
599 # bit = 0: service not available
600 avail_st += '\tService %d - %s\n' % ((8*i) + j, lookup_map[(8*i) + j])
601 byte = byte >> 1
602 return avail_st
Supreeth Herle98370552020-05-11 09:04:41 +0200603
604def first_TLV_parser(bytelist):
605 '''
606 first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
607
608 parses first TLV format record in a list of bytelist
609 returns a 3-Tuple: Tag, Length, Value
610 Value is a list of bytes
611 parsing of length is ETSI'style 101.220
612 '''
613 Tag = bytelist[0]
614 if bytelist[1] == 0xFF:
615 Len = bytelist[2]*256 + bytelist[3]
616 Val = bytelist[4:4+Len]
617 else:
618 Len = bytelist[1]
619 Val = bytelist[2:2+Len]
620 return (Tag, Len, Val)
621
622def TLV_parser(bytelist):
623 '''
624 TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
625
626 loops on the input list of bytes with the "first_TLV_parser()" function
627 returns a list of 3-Tuples
628 '''
629 ret = []
630 while len(bytelist) > 0:
631 T, L, V = first_TLV_parser(bytelist)
632 if T == 0xFF:
633 # padding bytes
634 break
635 ret.append( (T, L, V) )
636 # need to manage length of L
637 if L > 0xFE:
638 bytelist = bytelist[ L+4 : ]
639 else:
640 bytelist = bytelist[ L+2 : ]
641 return ret
Supreeth Herled572ede2020-03-22 09:55:04 +0100642
Supreeth Herled84daa12020-03-24 12:20:40 +0100643def enc_st(st, service, state=1):
644 """
645 Encodes the EF S/U/IST/EST and returns the updated Service Table
646
647 Parameters:
648 st - Current value of SIM/USIM/ISIM Service Table
649 service - Service Number to encode as activated/de-activated
650 state - 1 mean activate, 0 means de-activate
651
652 Returns:
653 s - Modified value of SIM/USIM/ISIM Service Table
654
655 Default values:
656 - state: 1 - Sets the particular Service bit to 1
657 """
658 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
659
660 s = ""
661 # Check whether the requested service is present in each byte
662 for i in range(0, len(st_bytes)):
663 # Byte i contains info about Services num (8i+1) to num (8i+8)
664 if service in range((8*i) + 1, (8*i) + 9):
665 byte = int(st_bytes[i], 16)
666 # Services in each byte are in order MSB to LSB
667 # MSB - Service (8i+8)
668 # LSB - Service (8i+1)
669 mod_byte = 0x00
670 # Copy bit by bit contents of byte to mod_byte with modified bit
671 # for requested service
672 for j in range(1, 9):
673 mod_byte = mod_byte >> 1
674 if service == (8*i) + j:
675 mod_byte = state == 1 and mod_byte|0x80 or mod_byte&0x7f
676 else:
677 mod_byte = byte&0x01 == 0x01 and mod_byte|0x80 or mod_byte&0x7f
678 byte = byte >> 1
679
680 s += ('%02x' % (mod_byte))
681 else:
682 s += st_bytes[i]
683
684 return s
685
Supreeth Herle3b342c22020-03-24 16:15:02 +0100686def dec_addr_tlv(hexstr):
Supreeth Herled572ede2020-03-22 09:55:04 +0100687 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100688 Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm.
689 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 +0100690 """
691
692 # Convert from hex str to int bytes list
Supreeth Herle3b342c22020-03-24 16:15:02 +0100693 addr_tlv_bytes = h2i(hexstr)
Supreeth Herled572ede2020-03-22 09:55:04 +0100694
Supreeth Herled572ede2020-03-22 09:55:04 +0100695 # Get list of tuples containing parsed TLVs
Supreeth Herle3b342c22020-03-24 16:15:02 +0100696 tlvs = TLV_parser(addr_tlv_bytes)
Supreeth Herled572ede2020-03-22 09:55:04 +0100697
698 for tlv in tlvs:
699 # tlv = (T, L, [V])
700 # T = Tag
701 # L = Length
702 # [V] = List of value
703
704 # Invalid Tag value scenario
705 if tlv[0] != 0x80:
706 continue
707
Supreeth Herled6a5ec52020-06-01 12:27:51 +0200708 # Empty field - Zero length
709 if tlv[1] == 0:
710 continue
711
Supreeth Herled572ede2020-03-22 09:55:04 +0100712 # First byte in the value has the address type
713 addr_type = tlv[2][0]
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100714 # TODO: Support parsing of IPv6
Supreeth Herle3b342c22020-03-24 16:15:02 +0100715 # Address Type: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), other (Reserved)
Supreeth Herled572ede2020-03-22 09:55:04 +0100716 if addr_type == 0x00: #FQDN
717 # Skip address tye byte i.e. first byte in value list
718 content = tlv[2][1:]
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200719 return (i2s(content), '00')
720
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100721 elif addr_type == 0x01: #IPv4
722 # Skip address tye byte i.e. first byte in value list
723 # Skip the unused byte in Octect 4 after address type byte as per 3GPP TS 31.102
724 ipv4 = tlv[2][2:]
725 content = '.'.join(str(x) for x in ipv4)
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200726 return (content, '01')
727 else:
728 raise ValueError("Invalid address type")
Supreeth Herled572ede2020-03-22 09:55:04 +0100729
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200730 return (None, None)
Philipp Maierff84c232020-05-12 17:24:18 +0200731
Supreeth Herle3b342c22020-03-24 16:15:02 +0100732def enc_addr_tlv(addr, addr_type='00'):
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100733 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100734 Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm.
735 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 +0100736
737 Default values:
Supreeth Herle3b342c22020-03-24 16:15:02 +0100738 - addr_type: 00 - FQDN format of Address
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100739 """
740
741 s = ""
742
Supreeth Herle654eca72020-03-25 14:25:38 +0100743 # TODO: Encoding of IPv6 address
744 if addr_type == '00': #FQDN
Supreeth Herle3b342c22020-03-24 16:15:02 +0100745 hex_str = s2h(addr)
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100746 s += '80' + ('%02x' % ((len(hex_str)//2)+1)) + '00' + hex_str
Supreeth Herle654eca72020-03-25 14:25:38 +0100747 elif addr_type == '01': #IPv4
748 ipv4_list = addr.split('.')
749 ipv4_str = ""
750 for i in ipv4_list:
751 ipv4_str += ('%02x' % (int(i)))
752
753 # Unused bytes shall be set to 'ff'. i.e 4th Octet after Address Type is not used
754 # IPv4 Address is in octet 5 to octet 8 of the TLV data object
755 s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100756
757 return s
758
Harald Welte52255572021-04-03 09:56:32 +0200759def is_hex(string:str, minlen:int=2, maxlen:Optional[int]=None) -> bool:
Philipp Maier47236502021-03-09 21:28:25 +0100760 """
761 Check if a string is a valid hexstring
762 """
763
764 # Filter obviously bad strings
765 if not string:
766 return False
767 if len(string) < minlen or minlen < 2:
768 return False
769 if len(string) % 2:
770 return False
771 if maxlen and len(string) > maxlen:
772 return False
773
774 # Try actual encoding to be sure
775 try:
776 try_encode = h2b(string)
777 return True
778 except:
779 return False
780
Harald Welte52255572021-04-03 09:56:32 +0200781def sanitize_pin_adm(pin_adm, pin_adm_hex = None) -> Hexstr:
Philipp Maiere8536c02020-05-11 21:35:01 +0200782 """
783 The ADM pin can be supplied either in its hexadecimal form or as
784 ascii string. This function checks the supplied opts parameter and
Harald Welte236a65f2021-04-03 10:20:11 +0200785 returns the pin_adm as hex encoded string, regardless in which form
Philipp Maiere8536c02020-05-11 21:35:01 +0200786 it was originally supplied by the user
787 """
788
Harald Welte79b5ba42021-01-08 21:22:38 +0100789 if pin_adm is not None:
790 if len(pin_adm) <= 8:
791 pin_adm = ''.join(['%02x'%(ord(x)) for x in pin_adm])
Philipp Maiere8536c02020-05-11 21:35:01 +0200792 pin_adm = rpad(pin_adm, 16)
793
794 else:
795 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
796
Harald Welte79b5ba42021-01-08 21:22:38 +0100797 if pin_adm_hex is not None:
798 if len(pin_adm_hex) == 16:
799 pin_adm = pin_adm_hex
Philipp Maiere8536c02020-05-11 21:35:01 +0200800 # Ensure that it's hex-encoded
801 try:
802 try_encode = h2b(pin_adm)
803 except ValueError:
804 raise ValueError("PIN-ADM needs to be hex encoded using this option")
805 else:
806 raise ValueError("PIN-ADM needs to be exactly 16 digits (hex encoded)")
807
808 return pin_adm
809
Supreeth Herle95ec7722020-03-24 13:09:03 +0100810def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='00'):
811 """
812 Encode ePDGSelection so it can be stored at EF.ePDGSelection or EF.ePDGSelectionEm.
813 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
814
815 Default values:
816 - epdg_priority: '0001' - 1st Priority
817 - epdg_fqdn_format: '00' - Operator Identifier FQDN
818 """
819
820 plmn1 = enc_plmn(mcc, mnc) + epdg_priority + epdg_fqdn_format
821 # TODO: Handle encoding of Length field for length more than 127 Bytes
822 content = '80' + ('%02x' % (len(plmn1)//2)) + plmn1
823 content = rpad(content, len(hexstr))
824 return content
825
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100826def dec_ePDGSelection(sixhexbytes):
827 """
828 Decode ePDGSelection to get EF.ePDGSelection or EF.ePDGSelectionEm.
829 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
830 """
831
832 res = {'mcc': 0, 'mnc': 0, 'epdg_priority': 0, 'epdg_fqdn_format': ''}
833 plmn_chars = 6
834 epdg_priority_chars = 4
835 epdg_fqdn_format_chars = 2
836 # first three bytes (six ascii hex chars)
837 plmn_str = sixhexbytes[:plmn_chars]
838 # two bytes after first three bytes
839 epdg_priority_str = sixhexbytes[plmn_chars:plmn_chars + epdg_priority_chars]
840 # one byte after first five bytes
841 epdg_fqdn_format_str = sixhexbytes[plmn_chars + epdg_priority_chars:plmn_chars + epdg_priority_chars + epdg_fqdn_format_chars]
842 res['mcc'] = dec_mcc_from_plmn(plmn_str)
843 res['mnc'] = dec_mnc_from_plmn(plmn_str)
844 res['epdg_priority'] = epdg_priority_str
845 res['epdg_fqdn_format'] = epdg_fqdn_format_str == '00' and 'Operator Identifier FQDN' or 'Location based FQDN'
846 return res
847
848def format_ePDGSelection(hexstr):
849 ePDGSelection_info_tag_chars = 2
850 ePDGSelection_info_tag_str = hexstr[:2]
herlesupreeth3a261d32021-01-05 09:20:11 +0100851 s = ""
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100852 # Minimum length
853 len_chars = 2
854 # TODO: Need to determine length properly - definite length support only
855 # Inconsistency in spec: 3GPP TS 31.102 version 15.2.0 Release 15, 4.2.104
856 # As per spec, length is 5n, n - number of PLMNs
857 # But, each PLMN entry is made of PLMN (3 Bytes) + ePDG Priority (2 Bytes) + ePDG FQDN format (1 Byte)
858 # Totalling to 6 Bytes, maybe length should be 6n
859 len_str = hexstr[ePDGSelection_info_tag_chars:ePDGSelection_info_tag_chars+len_chars]
herlesupreeth3a261d32021-01-05 09:20:11 +0100860
861 # Not programmed scenario
862 if int(len_str, 16) == 255 or int(ePDGSelection_info_tag_str, 16) == 255:
863 len_chars = 0
864 ePDGSelection_info_tag_chars = 0
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100865 if len_str[0] == '8':
866 # The bits 7 to 1 denotes the number of length octets if length > 127
867 if int(len_str[1]) > 0:
868 # Update number of length octets
869 len_chars = len_chars * int(len_str[1])
870 len_str = hexstr[ePDGSelection_info_tag_chars:len_chars]
871
872 content_str = hexstr[ePDGSelection_info_tag_chars+len_chars:]
873 # Right pad to prevent index out of range - multiple of 6 bytes
874 content_str = rpad(content_str, len(content_str) + (12 - (len(content_str) % 12)))
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100875 for rec_data in hexstr_to_Nbytearr(content_str, 6):
876 rec_info = dec_ePDGSelection(rec_data)
877 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
878 rec_str = "unused"
879 else:
880 rec_str = "MCC: %03d MNC: %03d ePDG Priority: %s ePDG FQDN format: %s" % \
881 (rec_info['mcc'], rec_info['mnc'], rec_info['epdg_priority'], rec_info['epdg_fqdn_format'])
882 s += "\t%s # %s\n" % (rec_data, rec_str)
883 return s
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100884
885def get_addr_type(addr):
886 """
887 Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
888 Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given)
889
890 TODO: Handle IPv6
891 """
892
893 # Empty address string
894 if not len(addr):
895 return None
896
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100897 addr_list = addr.split('.')
898
899 # Check for IPv4/IPv6
900 try:
901 import ipaddress
902 # Throws ValueError if addr is not correct
Denis 'GNUtoo' Carikli79f5b602020-02-15 04:02:57 +0700903 ipa = ipaddress.ip_address(addr)
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100904
905 if ipa.version == 4:
906 return 0x01
907 elif ipa.version == 6:
908 return 0x02
909 except Exception as e:
910 invalid_ipv4 = True
911 for i in addr_list:
912 # Invalid IPv4 may qualify for a valid FQDN, so make check here
913 # e.g. 172.24.15.300
914 import re
915 if not re.match('^[0-9_]+$', i):
916 invalid_ipv4 = False
917 break
918
919 if invalid_ipv4:
920 return None
921
922 fqdn_flag = True
923 for i in addr_list:
924 # Only Alpha-numeric characters and hyphen - RFC 1035
925 import re
926 if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
927 fqdn_flag = False
928 break
929
930 # FQDN
931 if fqdn_flag:
932 return 0x00
933
934 return None
Harald Welte67d551a2021-01-21 14:50:01 +0100935
Harald Welte1e456572021-04-02 17:16:30 +0200936def sw_match(sw:str, pattern:str) -> bool:
Harald Welte67d551a2021-01-21 14:50:01 +0100937 """Match given SW against given pattern."""
938 # Create a masked version of the returned status word
939 sw_lower = sw.lower()
940 sw_masked = ""
941 for i in range(0, 4):
Philipp Maier78e32f22021-03-22 20:39:24 +0100942 if pattern[i] == '?':
Harald Welte67d551a2021-01-21 14:50:01 +0100943 sw_masked = sw_masked + '?'
Philipp Maier78e32f22021-03-22 20:39:24 +0100944 elif pattern[i] == 'x':
Harald Welte67d551a2021-01-21 14:50:01 +0100945 sw_masked = sw_masked + 'x'
946 else:
947 sw_masked = sw_masked + sw_lower[i]
Philipp Maier78e32f22021-03-22 20:39:24 +0100948 # Compare the masked version against the pattern
Harald Welte67d551a2021-01-21 14:50:01 +0100949 return sw_masked == pattern
Philipp Maier5d3e2592021-02-22 17:22:16 +0100950
Harald Welteee3501f2021-04-02 13:00:18 +0200951def tabulate_str_list(str_list, width:int = 79, hspace:int = 2, lspace:int = 1,
Harald Welte52255572021-04-03 09:56:32 +0200952 align_left:bool = True) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +0200953 """Pretty print a list of strings into a tabulated form.
954
955 Args:
956 width : total width in characters per line
957 space : horizontal space between cells
958 lspace : number of spaces before row
959 align_lef : Align text to the left side
960 Returns:
961 multi-line string containing formatted table
962 """
Philipp Maier5d3e2592021-02-22 17:22:16 +0100963 if str_list == None:
964 return ""
965 if len(str_list) <= 0:
966 return ""
967 longest_str = max(str_list, key=len)
968 cellwith = len(longest_str) + hspace
969 cols = width // cellwith
970 rows = (len(str_list) - 1) // cols + 1
971 table = []
972 for i in iter(range(rows)):
973 str_list_row = str_list[i::rows]
974 if (align_left):
975 format_str_cell = '%%-%ds'
976 else:
977 format_str_cell = '%%%ds'
978 format_str_row = (format_str_cell % cellwith) * len(str_list_row)
979 format_str_row = (" " * lspace) + format_str_row
980 table.append(format_str_row % tuple(str_list_row))
981 return '\n'.join(table)
Harald Welte5e749a72021-04-10 17:18:17 +0200982
Harald Welte917d98c2021-04-21 11:51:25 +0200983def auto_int(x):
984 """Helper function for argparse to accept hexadecimal integers."""
985 return int(x, 0)
986
Harald Welte5e749a72021-04-10 17:18:17 +0200987class JsonEncoder(json.JSONEncoder):
988 """Extend the standard library JSONEncoder with support for more types."""
989 def default(self, o):
990 if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
991 return b2h(o)
992 return json.JSONEncoder.default(self, o)
Philipp Maier80ce71f2021-04-19 21:24:23 +0200993
994def boxed_heading_str(heading, width=80):
995 """Generate a string that contains a boxed heading."""
996 # Auto-enlarge box if heading exceeds length
997 if len(heading) > width - 4:
998 width = len(heading) + 4
999
1000 res = "#" * width
1001 fstr = "\n# %-" + str(width - 4) + "s #\n"
1002 res += fstr % (heading)
1003 res += "#" * width
1004 return res
Harald Welte3de6ca22021-05-02 20:05:56 +02001005
1006
1007
1008class DataObject(abc.ABC):
1009 """A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
1010 simply has any number of different TLVs that may occur in any order at any point, ISO 7816
1011 has the habit of specifying TLV data but with very spcific ordering, or specific choices of
1012 tags at specific points in a stream. This class tries to represent this."""
1013 def __init__(self, name, desc = None, tag = None):
1014 """
1015 Args:
1016 name: A brief, all-lowercase, underscore separated string identifier
1017 desc: A human-readable description of what this DO represents
1018 tag : The tag associated with this DO
1019 """
1020 self.name = name
1021 self.desc = desc
1022 self.tag = tag
1023 self.decoded = None
1024 self.encoded = None
1025
1026 def __str__(self):
1027 return self.name
1028
1029 def __repr__(self):
1030 return '%s(%s)' % (self.__class__, self.name)
1031
1032 def __or__(self, other):
1033 """OR-ing DataObjects together renders a DataObjectChoice."""
1034 if isinstance(other, DataObject):
1035 # DataObject | DataObject = DataObjectChoice
1036 return DataObjectChoice(None, members=[self, other])
1037 else:
1038 raise TypeError
1039
1040 def __add__(self, other):
1041 """ADD-ing DataObjects together renders a DataObjectCollection."""
1042 if isinstance(other, DataObject):
1043 # DataObject + DataObject = DataObjectCollectin
1044 return DataObjectCollection(None, members=[self, other])
1045
1046 def _compute_tag(self):
1047 """Compute the tag (sometimes the tag encodes part of the value)."""
1048 return self.tag
1049
1050 def to_dict(self):
1051 """Return a dict in form "name: decoded_value" """
1052 return {self.name: self.decoded}
1053
1054 @abc.abstractmethod
1055 def from_bytes(self, do:bytes):
1056 """Parse the value part of the DO into the internal state of this instance.
1057 Args:
1058 do : binary encoded bytes
1059 """
1060
1061 @abc.abstractmethod
1062 def to_bytes(self):
1063 """Encode the internal state of this instance into the TLV value part.
1064 Returns:
1065 binary bytes encoding the internal state
1066 """
1067
1068 def from_tlv(self, do:bytes):
1069 """Parse binary TLV representation into internal state. The resulting decoded
1070 representation is _not_ returned, but just internalized in the object instance!
1071 Args:
1072 do : input bytes containing TLV-encoded representation
1073 Returns:
1074 bytes remaining at end of 'do' after parsing one TLV/DO.
1075 """
1076 if do[0] != self.tag:
1077 raise ValueError('%s: Can only decode tag 0x%02x' % (self, self.tag))
1078 length = do[1]
1079 val = do[2:2+length]
1080 self.from_bytes(val)
1081 # return remaining bytes
1082 return do[2+length:]
1083
1084 def to_tlv(self):
1085 """Encode internal representation to binary TLV.
1086 Returns:
1087 bytes encoded in TLV format.
1088 """
1089 val = self.to_bytes()
1090 return bytes(self._compute_tag()) + bytes(len(val)) + val
1091
1092 # 'codec' interface
1093 def decode(self, binary:bytes):
1094 """Decode a single DOs from the input data.
1095 Args:
1096 binary : binary bytes of encoded data
1097 Returns:
1098 tuple of (decoded_result, binary_remainder)
1099 """
1100 tag = binary[0]
1101 if tag != self.tag:
1102 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected 0x%02x' %
1103 (self, tag, binary, self.tag))
1104 remainder = self.from_tlv(binary)
1105 return (self.to_dict(), remainder)
1106
1107 # 'codec' interface
1108 def encode(self):
1109 return self.to_tlv()
1110
1111class TL0_DataObject(DataObject):
1112 """Data Object that has Tag, Len=0 and no Value part."""
1113 def __init__(self, name, desc, tag, val=None):
1114 super().__init__(name, desc, tag)
1115 self.val = val
1116
1117 def from_bytes(self, binary:bytes):
1118 if len(binary) != 0:
1119 raise ValueError
1120 self.decoded = self.val
1121
1122 def to_bytes(self):
1123 return b''
1124
1125
1126class DataObjectCollection:
1127 """A DataObjectCollection consits of multiple Data Objects identified by their tags.
1128 A given encoded DO may contain any of them in any order, and may contain multiple instances
1129 of each DO."""
1130 def __init__(self, name, desc = None, members=None):
1131 self.name = name
1132 self.desc = desc
1133 self.members = members or []
1134 self.members_by_tag = {}
1135 self.members_by_name = {}
1136 self.members_by_tag = { m.tag:m for m in members }
1137 self.members_by_name = { m.name:m for m in members }
1138
1139 def __str__(self):
1140 member_strs = [str(x) for x in self.members]
1141 return '%s(%s)' % (self.name, ','.join(member_strs))
1142
1143 def __repr__(self):
1144 member_strs = [repr(x) for x in self.members]
1145 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1146
1147 def __add__(self, other):
1148 """Extending DataCollections with other DataCollections or DataObjects."""
1149 if isinstance(other, DataObjectCollection):
1150 # adding one collection to another
1151 members = self.members + other.members
1152 return DataObjectCollection(self.name, self.desc, members)
1153 elif isinstance(other, DataObject):
1154 # adding a member to a collection
1155 return DataObjectCollection(self.name, self.desc, self.members + [other])
1156 else:
1157 raise TypeError
1158
1159 # 'codec' interface
1160 def decode(self, binary:bytes):
1161 """Decode any number of DOs from the collection until the end of the input data,
1162 or uninitialized memory (0xFF) is found.
1163 Args:
1164 binary : binary bytes of encoded data
1165 Returns:
1166 tuple of (decoded_result, binary_remainder)
1167 """
1168 res = []
1169 remainder = binary
1170 # iterate until no binary trailer is left
1171 while len(remainder):
1172 tag = remainder[0]
1173 if tag == 0xff: # uninitialized memory at the end?
1174 return (res, remainder)
1175 if not tag in self.members_by_tag:
1176 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1177 (self, tag, remainder, self.members_by_tag.keys()))
1178 obj = self.members_by_tag[tag]
1179 # DO from_tlv returns remainder of binary
1180 remainder = obj.from_tlv(remainder)
1181 # collect our results
1182 res.append(obj.to_dict())
1183 return (res, remainder)
1184
1185 # 'codec' interface
1186 def encode(self, decoded):
1187 res = bytearray()
1188 for i in decoded:
1189 obj = self.members_by_name(i[0])
1190 res.append(obj.to_tlv())
1191 return res
1192
1193class DataObjectChoice(DataObjectCollection):
1194 """One Data Object from within a choice, identified by its tag.
1195 This means that exactly one member of the choice must occur, and which one occurs depends
1196 on the tag."""
1197 def __add__(self, other):
1198 """We overload the add operator here to avoid inheriting it from DataObjecCollection."""
1199 raise TypeError
1200
1201 def __or__(self, other):
1202 """OR-ing a Choice to another choice extends the choice, as does OR-ing a DataObject."""
1203 if isinstance(other, DataObjectChoice):
1204 # adding one collection to another
1205 members = self.members + other.members
1206 return DataObjectChoice(self.name, self.desc, members)
1207 elif isinstance(other, DataObject):
1208 # adding a member to a collection
1209 return DataObjectChoice(self.name, self.desc, self.members + [other])
1210 else:
1211 raise TypeError
1212
1213 # 'codec' interface
1214 def decode(self, binary:bytes):
1215 """Decode a single DOs from the choice based on the tag.
1216 Args:
1217 binary : binary bytes of encoded data
1218 Returns:
1219 tuple of (decoded_result, binary_remainder)
1220 """
1221 tag = binary[0]
1222 if tag == 0xff:
1223 return (None, binary)
1224 if not tag in self.members_by_tag:
1225 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1226 (self, tag, binary, self.members_by_tag.keys()))
1227 obj = self.members_by_tag[tag]
1228 remainder = obj.from_tlv(binary)
1229 return (obj.to_dict(), remainder)
1230
1231 # 'codec' interface
1232 def encode(self, decoded):
1233 obj = self.members_by_name(decoded[0])
1234 return obj.to_tlv()
1235
1236class DataObjectSequence:
1237 """A sequence of DataObjects or DataObjectChoices. This allows us to express a certain
1238 ordered sequence of DOs or choices of DOs that have to appear as per the specification.
1239 By wrapping them into this formal DataObjectSequence, we can offer convenience methods
1240 for encoding or decoding an entire sequence."""
1241 def __init__(self, name, desc=None, sequence=None):
1242 self.sequence = sequence or []
1243 self.name = name
1244 self.desc = desc
1245
1246 def __str__(self):
1247 member_strs = [str(x) for x in self.sequence]
1248 return '%s(%s)' % (self.name, ','.join(member_strs))
1249
1250 def __repr__(self):
1251 member_strs = [repr(x) for x in self.sequence]
1252 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1253
1254 def __add__(self, other):
1255 """Add (append) a DataObject or DataObjectChoice to the sequence."""
1256 if isinstance(other, 'DataObject'):
1257 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
1258 elif isinstance(other, 'DataObjectChoice'):
1259 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
1260 elif isinstance(other, 'DataObjectSequence'):
1261 return DataObjectSequence(self.name, self.desc, self.sequence + other.sequence)
1262
1263 # 'codec' interface
1264 def decode(self, binary:bytes):
1265 """Decode a sequence by calling the decoder of each element in the sequence.
1266 Args:
1267 binary : binary bytes of encoded data
1268 Returns:
1269 tuple of (decoded_result, binary_remainder)
1270 """
1271 remainder = binary
1272 res = []
1273 for e in self.sequence:
1274 (r, remainder) = e.decode(remainder)
1275 if r:
1276 res.append(r)
1277 return (res, remainder)
1278
1279 # 'codec' interface
1280 def decode_multi(self, do:bytes):
1281 """Decode multiple occurrences of the sequence from the binary input data.
1282 Args:
1283 do : binary input data to be decoded
1284 Returns:
1285 list of results of the decoder of this sequences
1286 """
1287 remainder = do
1288 res = []
1289 while len(remainder):
1290 (r, remainder2) = self.decode(remainder)
1291 if r:
1292 res.append(r)
1293 if len(remainder2) < len(remainder):
1294 remainder = remainder2
1295 else:
1296 remainder = remainder2
1297 break
1298 return (res, remainder)
1299
1300 # 'codec' interface
1301 def encode(self, decoded):
1302 """Encode a sequence by calling the encoder of each element in the sequence."""
1303 encoded = bytearray()
1304 i = 0
1305 for e in self.sequence:
1306 encoded += e.encode(decoded[i])
1307 i += 1
1308 return encoded
Harald Welte90441432021-05-02 21:28:12 +02001309
1310class CardCommand:
1311 """A single card command / instruction."""
1312 def __init__(self, name, ins, cla_list=None, desc=None):
1313 self.name = name
1314 self.ins = ins
1315 self.cla_list = cla_list or []
1316 self.cla_list = [x.lower() for x in self.cla_list]
1317 self.desc = desc
1318
1319 def __str__(self):
1320 return self.name
1321
1322 def __repr__(self):
1323 return '%s(INS=%02x,CLA=%s)' % (self.name, self.ins, self.cla_list)
1324
1325 def match_cla(self, cla):
1326 """Does the given CLA match the CLA list of the command?."""
1327 if not isinstance(cla, str):
1328 cla = '%02u' % cla
1329 cla = cla.lower()
1330 for cla_match in self.cla_list:
1331 cla_masked = ""
1332 for i in range(0, 2):
1333 if cla_match[i] == 'x':
1334 cla_masked += 'x'
1335 else:
1336 cla_masked += cla[i]
1337 if cla_masked == cla_match:
1338 return True
1339 return False
1340
1341
1342class CardCommandSet:
1343 """A set of card instructions, typically specified within one spec."""
1344 def __init__(self, name, cmds=[]):
1345 self.name = name
1346 self.cmds = { c.ins : c for c in cmds }
1347
1348 def __str__(self):
1349 return self.name
1350
1351 def __getitem__(self, idx):
1352 return self.cmds[idx]
1353
1354 def __add__(self, other):
1355 if isinstance(other, CardCommand):
1356 if other.ins in self.cmds:
1357 raise ValueError('%s: INS 0x%02x already defined: %s' %
1358 (self, other.ins, self.cmds[other.ins]))
1359 self.cmds[other.ins] = other
1360 elif isinstance(other, CardCommandSet):
1361 for c in other.cmds.keys():
1362 self.cmds[c] = other.cmds[c]
1363 else:
1364 raise ValueError('%s: Unsupported type to add operator: %s' % (self, other))
1365
1366 def lookup(self, ins, cla=None):
1367 """look-up the command within the CommandSet."""
1368 ins = int(ins)
1369 if not ins in self.cmds:
1370 return None
1371 cmd = self.cmds[ins]
1372 if cla and not cmd.match_cla(cla):
1373 return None
1374 return cmd