blob: 3d96580bc7970f7a2dd8e4deb861127479eb1e94 [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
Harald Weltec1475302021-05-21 21:47:55 +0200158def bertlv_parse_one(binary:bytes) -> (dict, int, bytes, bytes):
Harald Welte917d98c2021-04-21 11:51:25 +0200159 """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)
Harald Weltec1475302021-05-21 21:47:55 +0200167 value = remainder[:length]
168 remainder = remainder[length:]
169 return (tagdict, length, value, remainder)
Harald Welte917d98c2021-04-21 11:51:25 +0200170
171
172
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200173# IMSI encoded format:
174# For IMSI 0123456789ABCDE:
175#
176# | byte 1 | 2 upper | 2 lower | 3 upper | 3 lower | ... | 9 upper | 9 lower |
177# | length in bytes | 0 | odd/even | 2 | 1 | ... | E | D |
178#
179# If the IMSI is less than 15 characters, it should be padded with 'f' from the end.
180#
181# The length is the total number of bytes used to encoded the IMSI. This includes the odd/even
182# parity bit. E.g. an IMSI of length 14 is 8 bytes long, not 7, as it uses bytes 2 to 9 to
183# encode itself.
184#
185# Because of this, an odd length IMSI fits exactly into len(imsi) + 1 // 2 bytes, whereas an
186# even length IMSI only uses half of the last byte.
187
Harald Welteee3501f2021-04-02 13:00:18 +0200188def enc_imsi(imsi:str):
189 """Converts a string IMSI into the encoded value of the EF"""
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200190 l = half_round_up(len(imsi) + 1) # Required bytes - include space for odd/even indicator
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400191 oe = len(imsi) & 1 # Odd (1) / Even (0)
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200192 ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe<<3)|1, rpad(imsi, 15)))
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400193 return ei
194
Harald Welte52255572021-04-03 09:56:32 +0200195def dec_imsi(ef:Hexstr) -> Optional[str]:
Harald Weltec9cdce32021-04-11 10:28:28 +0200196 """Converts an EF value to the IMSI string representation"""
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400197 if len(ef) < 4:
198 return None
Pau Espin Pedrol665bd222017-12-29 20:30:35 +0100199 l = int(ef[0:2], 16) * 2 # Length of the IMSI string
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200200 l = l - 1 # Encoded length byte includes oe nibble
201 swapped = swap_nibbles(ef[2:]).rstrip('f')
Philipp Maiercd3d6262020-05-11 21:41:56 +0200202 if len(swapped) < 1:
203 return None
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400204 oe = (int(swapped[0])>>3) & 1 # Odd (1) / Even (0)
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200205 if not oe:
206 # if even, only half of last byte was used
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400207 l = l-1
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200208 if l != len(swapped) - 1:
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400209 return None
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200210 imsi = swapped[1:]
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400211 return imsi
212
Harald Welte52255572021-04-03 09:56:32 +0200213def dec_iccid(ef:Hexstr) -> str:
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400214 return swap_nibbles(ef).strip('f')
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400215
Harald Welte52255572021-04-03 09:56:32 +0200216def enc_iccid(iccid:str) -> Hexstr:
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400217 return swap_nibbles(rpad(iccid, 20))
218
Philipp Maiere6f8d682021-04-23 21:14:41 +0200219def enc_plmn(mcc:Hexstr, mnc:Hexstr) -> Hexstr:
Alexander Chemerisdddbf522017-07-18 16:49:59 +0300220 """Converts integer MCC/MNC into 3 bytes for EF"""
Philipp Maier6c5cd802021-04-23 21:19:36 +0200221
222 # Make sure there are no excess whitespaces in the input
223 # parameters
224 mcc = mcc.strip()
225 mnc = mnc.strip()
226
227 # Make sure that MCC/MNC are correctly padded with leading
228 # zeros or 'F', depending on the length.
229 if len(mnc) == 0:
230 mnc = "FFF"
231 elif len(mnc) == 1:
232 mnc = "F0" + mnc
233 elif len(mnc) == 2:
234 mnc += "F"
235
236 if len(mcc) == 0:
237 mcc = "FFF"
238 elif len(mcc) == 1:
239 mcc = "00" + mcc
240 elif len(mcc) == 2:
241 mcc = "0" + mcc
242
Vadim Yanitskiyc8458e22021-03-12 00:34:10 +0100243 return (mcc[1] + mcc[0]) + (mnc[2] + mcc[2]) + (mnc[1] + mnc[0])
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300244
Harald Welted7a7e172021-04-07 00:30:10 +0200245def dec_plmn(threehexbytes:Hexstr) -> dict:
Philipp Maier6c5cd802021-04-23 21:19:36 +0200246 res = {'mcc': "0", 'mnc': "0" }
247 dec_mcc_from_plmn_str(threehexbytes)
248 res['mcc'] = dec_mcc_from_plmn_str(threehexbytes)
249 res['mnc'] = dec_mnc_from_plmn_str(threehexbytes)
Harald Welted7a7e172021-04-07 00:30:10 +0200250 return res
251
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300252def dec_spn(ef):
Robert Falkenbergb07a3e92021-05-07 15:23:20 +0200253 """Obsolete, kept for API compatibility"""
254 from ts_51_011 import EF_SPN
255 abstract_data = EF_SPN().decode_hex(ef)
256 show_in_hplmn = abstract_data['show_in_hplmn']
257 hide_in_oplmn = abstract_data['hide_in_oplmn']
258 name = abstract_data['spn']
259 return (name, show_in_hplmn, hide_in_oplmn)
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300260
Robert Falkenbergb07a3e92021-05-07 15:23:20 +0200261def enc_spn(name:str, show_in_hplmn=False, hide_in_oplmn=False):
262 """Obsolete, kept for API compatibility"""
263 from ts_51_011 import EF_SPN
264 abstract_data = {
265 'hide_in_oplmn' : hide_in_oplmn,
266 'show_in_hplmn' : show_in_hplmn,
267 'spn' : name,
268 }
269 return EF_SPN().encode_hex(abstract_data)
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900270
Supreeth Herlef3948532020-03-24 12:23:51 +0100271def hexstr_to_Nbytearr(s, nbytes):
272 return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2)) ]
273
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100274# Accepts hex string representing three bytes
Harald Welte52255572021-04-03 09:56:32 +0200275def dec_mcc_from_plmn(plmn:Hexstr) -> int:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100276 ia = h2i(plmn)
277 digit1 = ia[0] & 0x0F # 1st byte, LSB
278 digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB
279 digit3 = ia[1] & 0x0F # 2nd byte, LSB
280 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
281 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100282 return derive_mcc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100283
Philipp Maier6c5cd802021-04-23 21:19:36 +0200284def dec_mcc_from_plmn_str(plmn:Hexstr) -> str:
285 digit1 = plmn[1] # 1st byte, LSB
286 digit2 = plmn[0] # 1st byte, MSB
287 digit3 = plmn[3] # 2nd byte, LSB
288 res = digit1 + digit2 + digit3
289 return res.upper().strip("F")
290
Harald Welte52255572021-04-03 09:56:32 +0200291def dec_mnc_from_plmn(plmn:Hexstr) -> int:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100292 ia = h2i(plmn)
Vadim Yanitskiyb271be32021-03-11 23:56:58 +0100293 digit1 = ia[2] & 0x0F # 3rd byte, LSB
294 digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB
295 digit3 = (ia[1] & 0xF0) >> 4 # 2nd byte, MSB
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100296 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
297 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100298 return derive_mnc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100299
Philipp Maier6c5cd802021-04-23 21:19:36 +0200300def dec_mnc_from_plmn_str(plmn:Hexstr) -> str:
301 digit1 = plmn[5] # 3rd byte, LSB
302 digit2 = plmn[4] # 3rd byte, MSB
303 digit3 = plmn[2] # 2nd byte, MSB
304 res = digit1 + digit2 + digit3
305 return res.upper().strip("F")
306
Harald Welte52255572021-04-03 09:56:32 +0200307def dec_act(twohexbytes:Hexstr) -> List[str]:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100308 act_list = [
309 {'bit': 15, 'name': "UTRAN"},
310 {'bit': 14, 'name': "E-UTRAN"},
311 {'bit': 7, 'name': "GSM"},
312 {'bit': 6, 'name': "GSM COMPACT"},
313 {'bit': 5, 'name': "cdma2000 HRPD"},
314 {'bit': 4, 'name': "cdma2000 1xRTT"},
315 ]
316 ia = h2i(twohexbytes)
317 u16t = (ia[0] << 8)|ia[1]
318 sel = []
319 for a in act_list:
320 if u16t & (1 << a['bit']):
Philipp Maiere7d41792021-04-29 16:20:07 +0200321 if a['name'] == "E-UTRAN":
322 # The Access technology identifier of E-UTRAN
323 # allows a more detailed specification:
324 if u16t & (1 << 13) and u16t & (1 << 12):
325 sel.append("E-UTRAN WB-S1")
326 sel.append("E-UTRAN NB-S1")
327 elif u16t & (1 << 13):
328 sel.append("E-UTRAN WB-S1")
329 elif u16t & (1 << 12):
330 sel.append("E-UTRAN NB-S1")
331 else:
332 sel.append("E-UTRAN")
333 else:
334 sel.append(a['name'])
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100335 return sel
336
Harald Welte52255572021-04-03 09:56:32 +0200337def dec_xplmn_w_act(fivehexbytes:Hexstr) -> Dict[str,Any]:
Philipp Maierb919f8b2021-04-27 18:28:27 +0200338 res = {'mcc': "0", 'mnc': "0", 'act': []}
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100339 plmn_chars = 6
340 act_chars = 4
341 plmn_str = fivehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
342 act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars] # two bytes after first three bytes
Philipp Maierb919f8b2021-04-27 18:28:27 +0200343 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
344 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100345 res['act'] = dec_act(act_str)
346 return res
347
348def format_xplmn_w_act(hexstr):
349 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200350 for rec_data in hexstr_to_Nbytearr(hexstr, 5):
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100351 rec_info = dec_xplmn_w_act(rec_data)
Philipp Maierb919f8b2021-04-27 18:28:27 +0200352 if rec_info['mcc'] == "" and rec_info['mnc'] == "":
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100353 rec_str = "unused"
354 else:
Philipp Maierb919f8b2021-04-27 18:28:27 +0200355 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 +0100356 s += "\t%s # %s\n" % (rec_data, rec_str)
357 return s
358
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100359def dec_loci(hexstr):
360 res = {'tmsi': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0}
361 res['tmsi'] = hexstr[:8]
362 res['mcc'] = dec_mcc_from_plmn(hexstr[8:14])
363 res['mnc'] = dec_mnc_from_plmn(hexstr[8:14])
364 res['lac'] = hexstr[14:18]
365 res['status'] = h2i(hexstr[20:22])
366 return res
367
368def dec_psloci(hexstr):
369 res = {'p-tmsi': '', 'p-tmsi-sig': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'rac': '', 'status': 0}
370 res['p-tmsi'] = hexstr[:8]
371 res['p-tmsi-sig'] = hexstr[8:14]
372 res['mcc'] = dec_mcc_from_plmn(hexstr[14:20])
373 res['mnc'] = dec_mnc_from_plmn(hexstr[14:20])
374 res['lac'] = hexstr[20:24]
375 res['rac'] = hexstr[24:26]
376 res['status'] = h2i(hexstr[26:28])
377 return res
378
379def dec_epsloci(hexstr):
380 res = {'guti': '', 'mcc': 0, 'mnc': 0, 'tac': '', 'status': 0}
381 res['guti'] = hexstr[:24]
382 res['tai'] = hexstr[24:34]
383 res['mcc'] = dec_mcc_from_plmn(hexstr[24:30])
384 res['mnc'] = dec_mnc_from_plmn(hexstr[24:30])
385 res['tac'] = hexstr[30:34]
386 res['status'] = h2i(hexstr[34:36])
387 return res
388
Harald Welte52255572021-04-03 09:56:32 +0200389def dec_xplmn(threehexbytes:Hexstr) -> dict:
Harald Welteca673942020-06-03 15:19:40 +0200390 res = {'mcc': 0, 'mnc': 0, 'act': []}
391 plmn_chars = 6
392 plmn_str = threehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
393 res['mcc'] = dec_mcc_from_plmn(plmn_str)
394 res['mnc'] = dec_mnc_from_plmn(plmn_str)
395 return res
396
Harald Welte52255572021-04-03 09:56:32 +0200397def format_xplmn(hexstr:Hexstr) -> str:
Harald Welteca673942020-06-03 15:19:40 +0200398 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200399 for rec_data in hexstr_to_Nbytearr(hexstr, 3):
Harald Welteca673942020-06-03 15:19:40 +0200400 rec_info = dec_xplmn(rec_data)
401 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
402 rec_str = "unused"
403 else:
404 rec_str = "MCC: %03d MNC: %03d" % (rec_info['mcc'], rec_info['mnc'])
405 s += "\t%s # %s\n" % (rec_data, rec_str)
406 return s
407
Harald Welte52255572021-04-03 09:56:32 +0200408def derive_milenage_opc(ki_hex:Hexstr, op_hex:Hexstr) -> Hexstr:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900409 """
410 Run the milenage algorithm to calculate OPC from Ki and OP
411 """
412 from Crypto.Cipher import AES
413 from Crypto.Util.strxor import strxor
414 from pySim.utils import b2h
415
416 # We pass in hex string and now need to work on bytes
Harald Welteeab8d2a2021-03-05 18:30:23 +0100417 ki_bytes = bytes(h2b(ki_hex))
418 op_bytes = bytes(h2b(op_hex))
Harald Welteab34fa82021-03-05 18:39:59 +0100419 aes = AES.new(ki_bytes, AES.MODE_ECB)
Harald Welteeab8d2a2021-03-05 18:30:23 +0100420 opc_bytes = aes.encrypt(op_bytes)
421 return b2h(strxor(opc_bytes, op_bytes))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900422
Harald Welte52255572021-04-03 09:56:32 +0200423def calculate_luhn(cc) -> int:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900424 """
425 Calculate Luhn checksum used in e.g. ICCID and IMEI
426 """
Daniel Willmanndd014ea2020-10-19 10:35:11 +0200427 num = list(map(int, str(cc)))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900428 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10
429 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200430
Harald Welte52255572021-04-03 09:56:32 +0200431def mcc_from_imsi(imsi:str) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200432 """
433 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
434 """
435 if imsi == None:
436 return None
437
438 if len(imsi) > 3:
439 return imsi[:3]
440 else:
441 return None
442
Harald Welte52255572021-04-03 09:56:32 +0200443def mnc_from_imsi(imsi:str, long:bool=False) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200444 """
445 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
446 """
447 if imsi == None:
448 return None
449
450 if len(imsi) > 3:
451 if long:
452 return imsi[3:6]
453 else:
454 return imsi[3:5]
455 else:
456 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100457
Harald Welte52255572021-04-03 09:56:32 +0200458def derive_mcc(digit1:int, digit2:int, digit3:int) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100459 """
460 Derive decimal representation of the MCC (Mobile Country Code)
461 from three given digits.
462 """
463
464 mcc = 0
465
466 if digit1 != 0x0f:
467 mcc += digit1 * 100
468 if digit2 != 0x0f:
469 mcc += digit2 * 10
470 if digit3 != 0x0f:
471 mcc += digit3
472
473 return mcc
474
Harald Welte52255572021-04-03 09:56:32 +0200475def derive_mnc(digit1:int, digit2:int, digit3:int=0x0f) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100476 """
477 Derive decimal representation of the MNC (Mobile Network Code)
478 from two or (optionally) three given digits.
479 """
480
481 mnc = 0
482
483 # 3-rd digit is optional for the MNC. If present
484 # the algorythm is the same as for the MCC.
485 if digit3 != 0x0f:
486 return derive_mcc(digit1, digit2, digit3)
487
488 if digit1 != 0x0f:
489 mnc += digit1 * 10
490 if digit2 != 0x0f:
491 mnc += digit2
492
493 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100494
Harald Welte52255572021-04-03 09:56:32 +0200495def dec_msisdn(ef_msisdn:Hexstr) -> Optional[Tuple[int,int,Optional[str]]]:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100496 """
497 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
498 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
499 """
500
501 # Convert from str to (kind of) 'bytes'
502 ef_msisdn = h2b(ef_msisdn)
503
504 # Make sure mandatory fields are present
505 if len(ef_msisdn) < 14:
506 raise ValueError("EF.MSISDN is too short")
507
508 # Skip optional Alpha Identifier
509 xlen = len(ef_msisdn) - 14
510 msisdn_lhv = ef_msisdn[xlen:]
511
512 # Parse the length (in bytes) of the BCD encoded number
Harald Welte4f6ca432021-02-01 17:51:56 +0100513 bcd_len = msisdn_lhv[0]
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100514 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
515 if bcd_len == 0xff:
516 return None
517 elif bcd_len > 11 or bcd_len < 1:
518 raise ValueError("Length of MSISDN (%d bytes) is out of range" % bcd_len)
519
520 # Parse ToN / NPI
Harald Welte4f6ca432021-02-01 17:51:56 +0100521 ton = (msisdn_lhv[1] >> 4) & 0x07
522 npi = msisdn_lhv[1] & 0x0f
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100523 bcd_len -= 1
524
525 # No MSISDN?
526 if not bcd_len:
527 return (npi, ton, None)
528
529 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
530 # International number 10.5.118/3GPP TS 24.008
Vadim Yanitskiy7ba24282020-02-27 00:04:13 +0700531 if ton == 0x01:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100532 msisdn = '+' + msisdn
533
534 return (npi, ton, msisdn)
Supreeth Herle5a541012019-12-22 08:59:16 +0100535
Harald Welte52255572021-04-03 09:56:32 +0200536def enc_msisdn(msisdn:str, npi:int=0x01, ton:int=0x03) -> Hexstr:
Supreeth Herle5a541012019-12-22 08:59:16 +0100537 """
538 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200539 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
540 will not contain the optional Alpha Identifier at the beginning.)
Supreeth Herle5a541012019-12-22 08:59:16 +0100541
542 Default NPI / ToN values:
543 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
544 - ToN: network specific or international number (if starts with '+').
545 """
546
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200547 # If no MSISDN is supplied then encode the file contents as all "ff"
548 if msisdn == "" or msisdn == "+":
549 return "ff" * 14
550
Supreeth Herle5a541012019-12-22 08:59:16 +0100551 # Leading '+' indicates International Number
552 if msisdn[0] == '+':
553 msisdn = msisdn[1:]
554 ton = 0x01
555
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200556 # An MSISDN must not exceed 20 digits
557 if len(msisdn) > 20:
558 raise ValueError("msisdn must not be longer than 20 digits")
559
Supreeth Herle5a541012019-12-22 08:59:16 +0100560 # Append 'f' padding if number of digits is odd
561 if len(msisdn) % 2 > 0:
562 msisdn += 'f'
563
564 # BCD length also includes NPI/ToN header
565 bcd_len = len(msisdn) // 2 + 1
566 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
567 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
568
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200569 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
570
Supreeth Herle441c4a72020-03-24 10:19:15 +0100571
Harald Welte52255572021-04-03 09:56:32 +0200572def dec_st(st, table="sim") -> str:
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200573 """
574 Parses the EF S/U/IST and prints the list of available services in EF S/U/IST
575 """
576
Supreeth Herledf330372020-04-20 14:48:55 +0200577 if table == "isim":
578 from pySim.ts_31_103 import EF_IST_map
579 lookup_map = EF_IST_map
580 elif table == "usim":
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200581 from pySim.ts_31_102 import EF_UST_map
582 lookup_map = EF_UST_map
583 else:
584 from pySim.ts_51_011 import EF_SST_map
585 lookup_map = EF_SST_map
586
587 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
588
589 avail_st = ""
590 # Get each byte and check for available services
591 for i in range(0, len(st_bytes)):
592 # Byte i contains info about Services num (8i+1) to num (8i+8)
593 byte = int(st_bytes[i], 16)
594 # Services in each byte are in order MSB to LSB
595 # MSB - Service (8i+8)
596 # LSB - Service (8i+1)
597 for j in range(1, 9):
598 if byte&0x01 == 0x01 and ((8*i) + j in lookup_map):
599 # Byte X contains info about Services num (8X-7) to num (8X)
600 # bit = 1: service available
601 # bit = 0: service not available
602 avail_st += '\tService %d - %s\n' % ((8*i) + j, lookup_map[(8*i) + j])
603 byte = byte >> 1
604 return avail_st
Supreeth Herle98370552020-05-11 09:04:41 +0200605
606def first_TLV_parser(bytelist):
607 '''
608 first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
609
610 parses first TLV format record in a list of bytelist
611 returns a 3-Tuple: Tag, Length, Value
612 Value is a list of bytes
613 parsing of length is ETSI'style 101.220
614 '''
615 Tag = bytelist[0]
616 if bytelist[1] == 0xFF:
617 Len = bytelist[2]*256 + bytelist[3]
618 Val = bytelist[4:4+Len]
619 else:
620 Len = bytelist[1]
621 Val = bytelist[2:2+Len]
622 return (Tag, Len, Val)
623
624def TLV_parser(bytelist):
625 '''
626 TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
627
628 loops on the input list of bytes with the "first_TLV_parser()" function
629 returns a list of 3-Tuples
630 '''
631 ret = []
632 while len(bytelist) > 0:
633 T, L, V = first_TLV_parser(bytelist)
634 if T == 0xFF:
635 # padding bytes
636 break
637 ret.append( (T, L, V) )
638 # need to manage length of L
639 if L > 0xFE:
640 bytelist = bytelist[ L+4 : ]
641 else:
642 bytelist = bytelist[ L+2 : ]
643 return ret
Supreeth Herled572ede2020-03-22 09:55:04 +0100644
Supreeth Herled84daa12020-03-24 12:20:40 +0100645def enc_st(st, service, state=1):
646 """
647 Encodes the EF S/U/IST/EST and returns the updated Service Table
648
649 Parameters:
650 st - Current value of SIM/USIM/ISIM Service Table
651 service - Service Number to encode as activated/de-activated
652 state - 1 mean activate, 0 means de-activate
653
654 Returns:
655 s - Modified value of SIM/USIM/ISIM Service Table
656
657 Default values:
658 - state: 1 - Sets the particular Service bit to 1
659 """
660 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
661
662 s = ""
663 # Check whether the requested service is present in each byte
664 for i in range(0, len(st_bytes)):
665 # Byte i contains info about Services num (8i+1) to num (8i+8)
666 if service in range((8*i) + 1, (8*i) + 9):
667 byte = int(st_bytes[i], 16)
668 # Services in each byte are in order MSB to LSB
669 # MSB - Service (8i+8)
670 # LSB - Service (8i+1)
671 mod_byte = 0x00
672 # Copy bit by bit contents of byte to mod_byte with modified bit
673 # for requested service
674 for j in range(1, 9):
675 mod_byte = mod_byte >> 1
676 if service == (8*i) + j:
677 mod_byte = state == 1 and mod_byte|0x80 or mod_byte&0x7f
678 else:
679 mod_byte = byte&0x01 == 0x01 and mod_byte|0x80 or mod_byte&0x7f
680 byte = byte >> 1
681
682 s += ('%02x' % (mod_byte))
683 else:
684 s += st_bytes[i]
685
686 return s
687
Supreeth Herle3b342c22020-03-24 16:15:02 +0100688def dec_addr_tlv(hexstr):
Supreeth Herled572ede2020-03-22 09:55:04 +0100689 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100690 Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm.
691 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 +0100692 """
693
694 # Convert from hex str to int bytes list
Supreeth Herle3b342c22020-03-24 16:15:02 +0100695 addr_tlv_bytes = h2i(hexstr)
Supreeth Herled572ede2020-03-22 09:55:04 +0100696
Supreeth Herled572ede2020-03-22 09:55:04 +0100697 # Get list of tuples containing parsed TLVs
Supreeth Herle3b342c22020-03-24 16:15:02 +0100698 tlvs = TLV_parser(addr_tlv_bytes)
Supreeth Herled572ede2020-03-22 09:55:04 +0100699
700 for tlv in tlvs:
701 # tlv = (T, L, [V])
702 # T = Tag
703 # L = Length
704 # [V] = List of value
705
706 # Invalid Tag value scenario
707 if tlv[0] != 0x80:
708 continue
709
Supreeth Herled6a5ec52020-06-01 12:27:51 +0200710 # Empty field - Zero length
711 if tlv[1] == 0:
712 continue
713
Supreeth Herled572ede2020-03-22 09:55:04 +0100714 # First byte in the value has the address type
715 addr_type = tlv[2][0]
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100716 # TODO: Support parsing of IPv6
Supreeth Herle3b342c22020-03-24 16:15:02 +0100717 # Address Type: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), other (Reserved)
Supreeth Herled572ede2020-03-22 09:55:04 +0100718 if addr_type == 0x00: #FQDN
719 # Skip address tye byte i.e. first byte in value list
720 content = tlv[2][1:]
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200721 return (i2s(content), '00')
722
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100723 elif addr_type == 0x01: #IPv4
724 # Skip address tye byte i.e. first byte in value list
725 # Skip the unused byte in Octect 4 after address type byte as per 3GPP TS 31.102
726 ipv4 = tlv[2][2:]
727 content = '.'.join(str(x) for x in ipv4)
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200728 return (content, '01')
729 else:
730 raise ValueError("Invalid address type")
Supreeth Herled572ede2020-03-22 09:55:04 +0100731
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200732 return (None, None)
Philipp Maierff84c232020-05-12 17:24:18 +0200733
Supreeth Herle3b342c22020-03-24 16:15:02 +0100734def enc_addr_tlv(addr, addr_type='00'):
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100735 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100736 Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm.
737 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 +0100738
739 Default values:
Supreeth Herle3b342c22020-03-24 16:15:02 +0100740 - addr_type: 00 - FQDN format of Address
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100741 """
742
743 s = ""
744
Supreeth Herle654eca72020-03-25 14:25:38 +0100745 # TODO: Encoding of IPv6 address
746 if addr_type == '00': #FQDN
Supreeth Herle3b342c22020-03-24 16:15:02 +0100747 hex_str = s2h(addr)
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100748 s += '80' + ('%02x' % ((len(hex_str)//2)+1)) + '00' + hex_str
Supreeth Herle654eca72020-03-25 14:25:38 +0100749 elif addr_type == '01': #IPv4
750 ipv4_list = addr.split('.')
751 ipv4_str = ""
752 for i in ipv4_list:
753 ipv4_str += ('%02x' % (int(i)))
754
755 # Unused bytes shall be set to 'ff'. i.e 4th Octet after Address Type is not used
756 # IPv4 Address is in octet 5 to octet 8 of the TLV data object
757 s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100758
759 return s
760
Harald Welte52255572021-04-03 09:56:32 +0200761def is_hex(string:str, minlen:int=2, maxlen:Optional[int]=None) -> bool:
Philipp Maier47236502021-03-09 21:28:25 +0100762 """
763 Check if a string is a valid hexstring
764 """
765
766 # Filter obviously bad strings
767 if not string:
768 return False
769 if len(string) < minlen or minlen < 2:
770 return False
771 if len(string) % 2:
772 return False
773 if maxlen and len(string) > maxlen:
774 return False
775
776 # Try actual encoding to be sure
777 try:
778 try_encode = h2b(string)
779 return True
780 except:
781 return False
782
Harald Welte52255572021-04-03 09:56:32 +0200783def sanitize_pin_adm(pin_adm, pin_adm_hex = None) -> Hexstr:
Philipp Maiere8536c02020-05-11 21:35:01 +0200784 """
785 The ADM pin can be supplied either in its hexadecimal form or as
786 ascii string. This function checks the supplied opts parameter and
Harald Welte236a65f2021-04-03 10:20:11 +0200787 returns the pin_adm as hex encoded string, regardless in which form
Philipp Maiere8536c02020-05-11 21:35:01 +0200788 it was originally supplied by the user
789 """
790
Harald Welte79b5ba42021-01-08 21:22:38 +0100791 if pin_adm is not None:
792 if len(pin_adm) <= 8:
793 pin_adm = ''.join(['%02x'%(ord(x)) for x in pin_adm])
Philipp Maiere8536c02020-05-11 21:35:01 +0200794 pin_adm = rpad(pin_adm, 16)
795
796 else:
797 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
798
Harald Welte79b5ba42021-01-08 21:22:38 +0100799 if pin_adm_hex is not None:
800 if len(pin_adm_hex) == 16:
801 pin_adm = pin_adm_hex
Philipp Maiere8536c02020-05-11 21:35:01 +0200802 # Ensure that it's hex-encoded
803 try:
804 try_encode = h2b(pin_adm)
805 except ValueError:
806 raise ValueError("PIN-ADM needs to be hex encoded using this option")
807 else:
808 raise ValueError("PIN-ADM needs to be exactly 16 digits (hex encoded)")
809
810 return pin_adm
811
Supreeth Herle95ec7722020-03-24 13:09:03 +0100812def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='00'):
813 """
814 Encode ePDGSelection so it can be stored at EF.ePDGSelection or EF.ePDGSelectionEm.
815 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
816
817 Default values:
818 - epdg_priority: '0001' - 1st Priority
819 - epdg_fqdn_format: '00' - Operator Identifier FQDN
820 """
821
822 plmn1 = enc_plmn(mcc, mnc) + epdg_priority + epdg_fqdn_format
823 # TODO: Handle encoding of Length field for length more than 127 Bytes
824 content = '80' + ('%02x' % (len(plmn1)//2)) + plmn1
825 content = rpad(content, len(hexstr))
826 return content
827
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100828def dec_ePDGSelection(sixhexbytes):
829 """
830 Decode ePDGSelection to get EF.ePDGSelection or EF.ePDGSelectionEm.
831 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
832 """
833
834 res = {'mcc': 0, 'mnc': 0, 'epdg_priority': 0, 'epdg_fqdn_format': ''}
835 plmn_chars = 6
836 epdg_priority_chars = 4
837 epdg_fqdn_format_chars = 2
838 # first three bytes (six ascii hex chars)
839 plmn_str = sixhexbytes[:plmn_chars]
840 # two bytes after first three bytes
841 epdg_priority_str = sixhexbytes[plmn_chars:plmn_chars + epdg_priority_chars]
842 # one byte after first five bytes
843 epdg_fqdn_format_str = sixhexbytes[plmn_chars + epdg_priority_chars:plmn_chars + epdg_priority_chars + epdg_fqdn_format_chars]
844 res['mcc'] = dec_mcc_from_plmn(plmn_str)
845 res['mnc'] = dec_mnc_from_plmn(plmn_str)
846 res['epdg_priority'] = epdg_priority_str
847 res['epdg_fqdn_format'] = epdg_fqdn_format_str == '00' and 'Operator Identifier FQDN' or 'Location based FQDN'
848 return res
849
850def format_ePDGSelection(hexstr):
851 ePDGSelection_info_tag_chars = 2
852 ePDGSelection_info_tag_str = hexstr[:2]
herlesupreeth3a261d32021-01-05 09:20:11 +0100853 s = ""
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100854 # Minimum length
855 len_chars = 2
856 # TODO: Need to determine length properly - definite length support only
857 # Inconsistency in spec: 3GPP TS 31.102 version 15.2.0 Release 15, 4.2.104
858 # As per spec, length is 5n, n - number of PLMNs
859 # But, each PLMN entry is made of PLMN (3 Bytes) + ePDG Priority (2 Bytes) + ePDG FQDN format (1 Byte)
860 # Totalling to 6 Bytes, maybe length should be 6n
861 len_str = hexstr[ePDGSelection_info_tag_chars:ePDGSelection_info_tag_chars+len_chars]
herlesupreeth3a261d32021-01-05 09:20:11 +0100862
863 # Not programmed scenario
864 if int(len_str, 16) == 255 or int(ePDGSelection_info_tag_str, 16) == 255:
865 len_chars = 0
866 ePDGSelection_info_tag_chars = 0
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100867 if len_str[0] == '8':
868 # The bits 7 to 1 denotes the number of length octets if length > 127
869 if int(len_str[1]) > 0:
870 # Update number of length octets
871 len_chars = len_chars * int(len_str[1])
872 len_str = hexstr[ePDGSelection_info_tag_chars:len_chars]
873
874 content_str = hexstr[ePDGSelection_info_tag_chars+len_chars:]
875 # Right pad to prevent index out of range - multiple of 6 bytes
876 content_str = rpad(content_str, len(content_str) + (12 - (len(content_str) % 12)))
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100877 for rec_data in hexstr_to_Nbytearr(content_str, 6):
878 rec_info = dec_ePDGSelection(rec_data)
879 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
880 rec_str = "unused"
881 else:
882 rec_str = "MCC: %03d MNC: %03d ePDG Priority: %s ePDG FQDN format: %s" % \
883 (rec_info['mcc'], rec_info['mnc'], rec_info['epdg_priority'], rec_info['epdg_fqdn_format'])
884 s += "\t%s # %s\n" % (rec_data, rec_str)
885 return s
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100886
887def get_addr_type(addr):
888 """
889 Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
890 Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given)
891
892 TODO: Handle IPv6
893 """
894
895 # Empty address string
896 if not len(addr):
897 return None
898
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100899 addr_list = addr.split('.')
900
901 # Check for IPv4/IPv6
902 try:
903 import ipaddress
904 # Throws ValueError if addr is not correct
Denis 'GNUtoo' Carikli79f5b602020-02-15 04:02:57 +0700905 ipa = ipaddress.ip_address(addr)
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100906
907 if ipa.version == 4:
908 return 0x01
909 elif ipa.version == 6:
910 return 0x02
911 except Exception as e:
912 invalid_ipv4 = True
913 for i in addr_list:
914 # Invalid IPv4 may qualify for a valid FQDN, so make check here
915 # e.g. 172.24.15.300
916 import re
917 if not re.match('^[0-9_]+$', i):
918 invalid_ipv4 = False
919 break
920
921 if invalid_ipv4:
922 return None
923
924 fqdn_flag = True
925 for i in addr_list:
926 # Only Alpha-numeric characters and hyphen - RFC 1035
927 import re
928 if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
929 fqdn_flag = False
930 break
931
932 # FQDN
933 if fqdn_flag:
934 return 0x00
935
936 return None
Harald Welte67d551a2021-01-21 14:50:01 +0100937
Harald Welte1e456572021-04-02 17:16:30 +0200938def sw_match(sw:str, pattern:str) -> bool:
Harald Welte67d551a2021-01-21 14:50:01 +0100939 """Match given SW against given pattern."""
940 # Create a masked version of the returned status word
941 sw_lower = sw.lower()
942 sw_masked = ""
943 for i in range(0, 4):
Philipp Maier78e32f22021-03-22 20:39:24 +0100944 if pattern[i] == '?':
Harald Welte67d551a2021-01-21 14:50:01 +0100945 sw_masked = sw_masked + '?'
Philipp Maier78e32f22021-03-22 20:39:24 +0100946 elif pattern[i] == 'x':
Harald Welte67d551a2021-01-21 14:50:01 +0100947 sw_masked = sw_masked + 'x'
948 else:
949 sw_masked = sw_masked + sw_lower[i]
Philipp Maier78e32f22021-03-22 20:39:24 +0100950 # Compare the masked version against the pattern
Harald Welte67d551a2021-01-21 14:50:01 +0100951 return sw_masked == pattern
Philipp Maier5d3e2592021-02-22 17:22:16 +0100952
Harald Welteee3501f2021-04-02 13:00:18 +0200953def tabulate_str_list(str_list, width:int = 79, hspace:int = 2, lspace:int = 1,
Harald Welte52255572021-04-03 09:56:32 +0200954 align_left:bool = True) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +0200955 """Pretty print a list of strings into a tabulated form.
956
957 Args:
958 width : total width in characters per line
959 space : horizontal space between cells
960 lspace : number of spaces before row
961 align_lef : Align text to the left side
962 Returns:
963 multi-line string containing formatted table
964 """
Philipp Maier5d3e2592021-02-22 17:22:16 +0100965 if str_list == None:
966 return ""
967 if len(str_list) <= 0:
968 return ""
969 longest_str = max(str_list, key=len)
970 cellwith = len(longest_str) + hspace
971 cols = width // cellwith
972 rows = (len(str_list) - 1) // cols + 1
973 table = []
974 for i in iter(range(rows)):
975 str_list_row = str_list[i::rows]
976 if (align_left):
977 format_str_cell = '%%-%ds'
978 else:
979 format_str_cell = '%%%ds'
980 format_str_row = (format_str_cell % cellwith) * len(str_list_row)
981 format_str_row = (" " * lspace) + format_str_row
982 table.append(format_str_row % tuple(str_list_row))
983 return '\n'.join(table)
Harald Welte5e749a72021-04-10 17:18:17 +0200984
Harald Welte917d98c2021-04-21 11:51:25 +0200985def auto_int(x):
986 """Helper function for argparse to accept hexadecimal integers."""
987 return int(x, 0)
988
Harald Welte5e749a72021-04-10 17:18:17 +0200989class JsonEncoder(json.JSONEncoder):
990 """Extend the standard library JSONEncoder with support for more types."""
991 def default(self, o):
992 if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
993 return b2h(o)
994 return json.JSONEncoder.default(self, o)
Philipp Maier80ce71f2021-04-19 21:24:23 +0200995
996def boxed_heading_str(heading, width=80):
997 """Generate a string that contains a boxed heading."""
998 # Auto-enlarge box if heading exceeds length
999 if len(heading) > width - 4:
1000 width = len(heading) + 4
1001
1002 res = "#" * width
1003 fstr = "\n# %-" + str(width - 4) + "s #\n"
1004 res += fstr % (heading)
1005 res += "#" * width
1006 return res
Harald Welte3de6ca22021-05-02 20:05:56 +02001007
1008
1009
1010class DataObject(abc.ABC):
1011 """A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
1012 simply has any number of different TLVs that may occur in any order at any point, ISO 7816
1013 has the habit of specifying TLV data but with very spcific ordering, or specific choices of
1014 tags at specific points in a stream. This class tries to represent this."""
1015 def __init__(self, name, desc = None, tag = None):
1016 """
1017 Args:
1018 name: A brief, all-lowercase, underscore separated string identifier
1019 desc: A human-readable description of what this DO represents
1020 tag : The tag associated with this DO
1021 """
1022 self.name = name
1023 self.desc = desc
1024 self.tag = tag
1025 self.decoded = None
1026 self.encoded = None
1027
1028 def __str__(self):
1029 return self.name
1030
1031 def __repr__(self):
1032 return '%s(%s)' % (self.__class__, self.name)
1033
1034 def __or__(self, other):
1035 """OR-ing DataObjects together renders a DataObjectChoice."""
1036 if isinstance(other, DataObject):
1037 # DataObject | DataObject = DataObjectChoice
1038 return DataObjectChoice(None, members=[self, other])
1039 else:
1040 raise TypeError
1041
1042 def __add__(self, other):
1043 """ADD-ing DataObjects together renders a DataObjectCollection."""
1044 if isinstance(other, DataObject):
1045 # DataObject + DataObject = DataObjectCollectin
1046 return DataObjectCollection(None, members=[self, other])
1047
1048 def _compute_tag(self):
1049 """Compute the tag (sometimes the tag encodes part of the value)."""
1050 return self.tag
1051
1052 def to_dict(self):
1053 """Return a dict in form "name: decoded_value" """
1054 return {self.name: self.decoded}
1055
1056 @abc.abstractmethod
1057 def from_bytes(self, do:bytes):
1058 """Parse the value part of the DO into the internal state of this instance.
1059 Args:
1060 do : binary encoded bytes
1061 """
1062
1063 @abc.abstractmethod
1064 def to_bytes(self):
1065 """Encode the internal state of this instance into the TLV value part.
1066 Returns:
1067 binary bytes encoding the internal state
1068 """
1069
1070 def from_tlv(self, do:bytes):
1071 """Parse binary TLV representation into internal state. The resulting decoded
1072 representation is _not_ returned, but just internalized in the object instance!
1073 Args:
1074 do : input bytes containing TLV-encoded representation
1075 Returns:
1076 bytes remaining at end of 'do' after parsing one TLV/DO.
1077 """
1078 if do[0] != self.tag:
1079 raise ValueError('%s: Can only decode tag 0x%02x' % (self, self.tag))
1080 length = do[1]
1081 val = do[2:2+length]
1082 self.from_bytes(val)
1083 # return remaining bytes
1084 return do[2+length:]
1085
1086 def to_tlv(self):
1087 """Encode internal representation to binary TLV.
1088 Returns:
1089 bytes encoded in TLV format.
1090 """
1091 val = self.to_bytes()
1092 return bytes(self._compute_tag()) + bytes(len(val)) + val
1093
1094 # 'codec' interface
1095 def decode(self, binary:bytes):
1096 """Decode a single DOs from the input data.
1097 Args:
1098 binary : binary bytes of encoded data
1099 Returns:
1100 tuple of (decoded_result, binary_remainder)
1101 """
1102 tag = binary[0]
1103 if tag != self.tag:
1104 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected 0x%02x' %
1105 (self, tag, binary, self.tag))
1106 remainder = self.from_tlv(binary)
1107 return (self.to_dict(), remainder)
1108
1109 # 'codec' interface
1110 def encode(self):
1111 return self.to_tlv()
1112
1113class TL0_DataObject(DataObject):
1114 """Data Object that has Tag, Len=0 and no Value part."""
1115 def __init__(self, name, desc, tag, val=None):
1116 super().__init__(name, desc, tag)
1117 self.val = val
1118
1119 def from_bytes(self, binary:bytes):
1120 if len(binary) != 0:
1121 raise ValueError
1122 self.decoded = self.val
1123
1124 def to_bytes(self):
1125 return b''
1126
1127
1128class DataObjectCollection:
1129 """A DataObjectCollection consits of multiple Data Objects identified by their tags.
1130 A given encoded DO may contain any of them in any order, and may contain multiple instances
1131 of each DO."""
1132 def __init__(self, name, desc = None, members=None):
1133 self.name = name
1134 self.desc = desc
1135 self.members = members or []
1136 self.members_by_tag = {}
1137 self.members_by_name = {}
1138 self.members_by_tag = { m.tag:m for m in members }
1139 self.members_by_name = { m.name:m for m in members }
1140
1141 def __str__(self):
1142 member_strs = [str(x) for x in self.members]
1143 return '%s(%s)' % (self.name, ','.join(member_strs))
1144
1145 def __repr__(self):
1146 member_strs = [repr(x) for x in self.members]
1147 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1148
1149 def __add__(self, other):
1150 """Extending DataCollections with other DataCollections or DataObjects."""
1151 if isinstance(other, DataObjectCollection):
1152 # adding one collection to another
1153 members = self.members + other.members
1154 return DataObjectCollection(self.name, self.desc, members)
1155 elif isinstance(other, DataObject):
1156 # adding a member to a collection
1157 return DataObjectCollection(self.name, self.desc, self.members + [other])
1158 else:
1159 raise TypeError
1160
1161 # 'codec' interface
1162 def decode(self, binary:bytes):
1163 """Decode any number of DOs from the collection until the end of the input data,
1164 or uninitialized memory (0xFF) is found.
1165 Args:
1166 binary : binary bytes of encoded data
1167 Returns:
1168 tuple of (decoded_result, binary_remainder)
1169 """
1170 res = []
1171 remainder = binary
1172 # iterate until no binary trailer is left
1173 while len(remainder):
1174 tag = remainder[0]
1175 if tag == 0xff: # uninitialized memory at the end?
1176 return (res, remainder)
1177 if not tag in self.members_by_tag:
1178 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1179 (self, tag, remainder, self.members_by_tag.keys()))
1180 obj = self.members_by_tag[tag]
1181 # DO from_tlv returns remainder of binary
1182 remainder = obj.from_tlv(remainder)
1183 # collect our results
1184 res.append(obj.to_dict())
1185 return (res, remainder)
1186
1187 # 'codec' interface
1188 def encode(self, decoded):
1189 res = bytearray()
1190 for i in decoded:
1191 obj = self.members_by_name(i[0])
1192 res.append(obj.to_tlv())
1193 return res
1194
1195class DataObjectChoice(DataObjectCollection):
1196 """One Data Object from within a choice, identified by its tag.
1197 This means that exactly one member of the choice must occur, and which one occurs depends
1198 on the tag."""
1199 def __add__(self, other):
1200 """We overload the add operator here to avoid inheriting it from DataObjecCollection."""
1201 raise TypeError
1202
1203 def __or__(self, other):
1204 """OR-ing a Choice to another choice extends the choice, as does OR-ing a DataObject."""
1205 if isinstance(other, DataObjectChoice):
1206 # adding one collection to another
1207 members = self.members + other.members
1208 return DataObjectChoice(self.name, self.desc, members)
1209 elif isinstance(other, DataObject):
1210 # adding a member to a collection
1211 return DataObjectChoice(self.name, self.desc, self.members + [other])
1212 else:
1213 raise TypeError
1214
1215 # 'codec' interface
1216 def decode(self, binary:bytes):
1217 """Decode a single DOs from the choice based on the tag.
1218 Args:
1219 binary : binary bytes of encoded data
1220 Returns:
1221 tuple of (decoded_result, binary_remainder)
1222 """
1223 tag = binary[0]
1224 if tag == 0xff:
1225 return (None, binary)
1226 if not tag in self.members_by_tag:
1227 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1228 (self, tag, binary, self.members_by_tag.keys()))
1229 obj = self.members_by_tag[tag]
1230 remainder = obj.from_tlv(binary)
1231 return (obj.to_dict(), remainder)
1232
1233 # 'codec' interface
1234 def encode(self, decoded):
1235 obj = self.members_by_name(decoded[0])
1236 return obj.to_tlv()
1237
1238class DataObjectSequence:
1239 """A sequence of DataObjects or DataObjectChoices. This allows us to express a certain
1240 ordered sequence of DOs or choices of DOs that have to appear as per the specification.
1241 By wrapping them into this formal DataObjectSequence, we can offer convenience methods
1242 for encoding or decoding an entire sequence."""
1243 def __init__(self, name, desc=None, sequence=None):
1244 self.sequence = sequence or []
1245 self.name = name
1246 self.desc = desc
1247
1248 def __str__(self):
1249 member_strs = [str(x) for x in self.sequence]
1250 return '%s(%s)' % (self.name, ','.join(member_strs))
1251
1252 def __repr__(self):
1253 member_strs = [repr(x) for x in self.sequence]
1254 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1255
1256 def __add__(self, other):
1257 """Add (append) a DataObject or DataObjectChoice to the sequence."""
1258 if isinstance(other, 'DataObject'):
1259 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
1260 elif isinstance(other, 'DataObjectChoice'):
1261 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
1262 elif isinstance(other, 'DataObjectSequence'):
1263 return DataObjectSequence(self.name, self.desc, self.sequence + other.sequence)
1264
1265 # 'codec' interface
1266 def decode(self, binary:bytes):
1267 """Decode a sequence by calling the decoder of each element in the sequence.
1268 Args:
1269 binary : binary bytes of encoded data
1270 Returns:
1271 tuple of (decoded_result, binary_remainder)
1272 """
1273 remainder = binary
1274 res = []
1275 for e in self.sequence:
1276 (r, remainder) = e.decode(remainder)
1277 if r:
1278 res.append(r)
1279 return (res, remainder)
1280
1281 # 'codec' interface
1282 def decode_multi(self, do:bytes):
1283 """Decode multiple occurrences of the sequence from the binary input data.
1284 Args:
1285 do : binary input data to be decoded
1286 Returns:
1287 list of results of the decoder of this sequences
1288 """
1289 remainder = do
1290 res = []
1291 while len(remainder):
1292 (r, remainder2) = self.decode(remainder)
1293 if r:
1294 res.append(r)
1295 if len(remainder2) < len(remainder):
1296 remainder = remainder2
1297 else:
1298 remainder = remainder2
1299 break
1300 return (res, remainder)
1301
1302 # 'codec' interface
1303 def encode(self, decoded):
1304 """Encode a sequence by calling the encoder of each element in the sequence."""
1305 encoded = bytearray()
1306 i = 0
1307 for e in self.sequence:
1308 encoded += e.encode(decoded[i])
1309 i += 1
1310 return encoded
Harald Welte90441432021-05-02 21:28:12 +02001311
1312class CardCommand:
1313 """A single card command / instruction."""
1314 def __init__(self, name, ins, cla_list=None, desc=None):
1315 self.name = name
1316 self.ins = ins
1317 self.cla_list = cla_list or []
1318 self.cla_list = [x.lower() for x in self.cla_list]
1319 self.desc = desc
1320
1321 def __str__(self):
1322 return self.name
1323
1324 def __repr__(self):
1325 return '%s(INS=%02x,CLA=%s)' % (self.name, self.ins, self.cla_list)
1326
1327 def match_cla(self, cla):
1328 """Does the given CLA match the CLA list of the command?."""
1329 if not isinstance(cla, str):
1330 cla = '%02u' % cla
1331 cla = cla.lower()
1332 for cla_match in self.cla_list:
1333 cla_masked = ""
1334 for i in range(0, 2):
1335 if cla_match[i] == 'x':
1336 cla_masked += 'x'
1337 else:
1338 cla_masked += cla[i]
1339 if cla_masked == cla_match:
1340 return True
1341 return False
1342
1343
1344class CardCommandSet:
1345 """A set of card instructions, typically specified within one spec."""
1346 def __init__(self, name, cmds=[]):
1347 self.name = name
1348 self.cmds = { c.ins : c for c in cmds }
1349
1350 def __str__(self):
1351 return self.name
1352
1353 def __getitem__(self, idx):
1354 return self.cmds[idx]
1355
1356 def __add__(self, other):
1357 if isinstance(other, CardCommand):
1358 if other.ins in self.cmds:
1359 raise ValueError('%s: INS 0x%02x already defined: %s' %
1360 (self, other.ins, self.cmds[other.ins]))
1361 self.cmds[other.ins] = other
1362 elif isinstance(other, CardCommandSet):
1363 for c in other.cmds.keys():
1364 self.cmds[c] = other.cmds[c]
1365 else:
1366 raise ValueError('%s: Unsupported type to add operator: %s' % (self, other))
1367
1368 def lookup(self, ins, cla=None):
1369 """look-up the command within the CommandSet."""
1370 ins = int(ins)
1371 if not ins in self.cmds:
1372 return None
1373 cmd = self.cmds[ins]
1374 if cla and not cmd.match_cla(cla):
1375 return None
1376 return cmd