blob: e268e4226e23e6e30ec715f0a5f3491673b40061 [file] [log] [blame]
Sylvain Munaut76504e02010-12-07 00:24:32 +01001# -*- coding: utf-8 -*-
2
3""" pySim: various utilities
4"""
5
Harald Welte5e749a72021-04-10 17:18:17 +02006import json
7from io import BytesIO
Harald Welte52255572021-04-03 09:56:32 +02008from typing import Optional, List, Dict, Any, Tuple
9
Sylvain Munaut76504e02010-12-07 00:24:32 +010010# Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com>
Harald Welte5e749a72021-04-10 17:18:17 +020011# Copyright (C) 2021 Harald Welte <laforge@osmocom.org>
Sylvain Munaut76504e02010-12-07 00:24:32 +010012#
13# This program is free software: you can redistribute it and/or modify
14# it under the terms of the GNU General Public License as published by
15# the Free Software Foundation, either version 2 of the License, or
16# (at your option) any later version.
17#
18# This program is distributed in the hope that it will be useful,
19# but WITHOUT ANY WARRANTY; without even the implied warranty of
20# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21# GNU General Public License for more details.
22#
23# You should have received a copy of the GNU General Public License
24# along with this program. If not, see <http://www.gnu.org/licenses/>.
25#
26
Harald Welte52255572021-04-03 09:56:32 +020027# just to differentiate strings of hex nibbles from everything else
28Hexstr = str
Sylvain Munaut76504e02010-12-07 00:24:32 +010029
Harald Welte52255572021-04-03 09:56:32 +020030def h2b(s:Hexstr) -> bytearray:
Harald Welte4f6ca432021-02-01 17:51:56 +010031 """convert from a string of hex nibbles to a sequence of bytes"""
32 return bytearray.fromhex(s)
Sylvain Munaut76504e02010-12-07 00:24:32 +010033
Harald Welte52255572021-04-03 09:56:32 +020034def b2h(b:bytearray) -> Hexstr:
Harald Welte4f6ca432021-02-01 17:51:56 +010035 """convert from a sequence of bytes to a string of hex nibbles"""
36 return ''.join(['%02x'%(x) for x in b])
Sylvain Munaut76504e02010-12-07 00:24:32 +010037
Harald Welte52255572021-04-03 09:56:32 +020038def h2i(s:Hexstr) -> List[int]:
Harald Welteee3501f2021-04-02 13:00:18 +020039 """convert from a string of hex nibbles to a list of integers"""
Sylvain Munaut76504e02010-12-07 00:24:32 +010040 return [(int(x,16)<<4)+int(y,16) for x,y in zip(s[0::2], s[1::2])]
41
Harald Welte52255572021-04-03 09:56:32 +020042def i2h(s:List[int]) -> Hexstr:
Harald Welteee3501f2021-04-02 13:00:18 +020043 """convert from a list of integers to a string of hex nibbles"""
Sylvain Munaut76504e02010-12-07 00:24:32 +010044 return ''.join(['%02x'%(x) for x in s])
45
Harald Welte52255572021-04-03 09:56:32 +020046def h2s(s:Hexstr) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +020047 """convert from a string of hex nibbles to an ASCII string"""
Vadim Yanitskiyeb06b452020-05-10 02:32:46 +070048 return ''.join([chr((int(x,16)<<4)+int(y,16)) for x,y in zip(s[0::2], s[1::2])
49 if int(x + y, 16) != 0xff])
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +030050
Harald Welte52255572021-04-03 09:56:32 +020051def s2h(s:str) -> Hexstr:
Harald Welteee3501f2021-04-02 13:00:18 +020052 """convert from an ASCII string to a string of hex nibbles"""
Harald Welte4f6ca432021-02-01 17:51:56 +010053 b = bytearray()
54 b.extend(map(ord, s))
55 return b2h(b)
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +030056
Supreeth Herle7d77d2d2020-05-11 09:07:08 +020057# List of bytes to string
Harald Welte52255572021-04-03 09:56:32 +020058def i2s(s:List[int]) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +020059 """convert from a list of integers to an ASCII string"""
Supreeth Herle7d77d2d2020-05-11 09:07:08 +020060 return ''.join([chr(x) for x in s])
61
Harald Welte52255572021-04-03 09:56:32 +020062def swap_nibbles(s:Hexstr) -> Hexstr:
Harald Welteee3501f2021-04-02 13:00:18 +020063 """swap the nibbles in a hex string"""
Sylvain Munaut76504e02010-12-07 00:24:32 +010064 return ''.join([x+y for x,y in zip(s[1::2], s[0::2])])
65
Harald Welteee3501f2021-04-02 13:00:18 +020066def rpad(s:str, l:int, c='f') -> str:
67 """pad string on the right side.
68 Args:
69 s : string to pad
70 l : total length to pad to
71 c : padding character
72 Returns:
73 String 's' padded with as many 'c' as needed to reach total length of 'l'
74 """
Sylvain Munaut76504e02010-12-07 00:24:32 +010075 return s + c * (l - len(s))
76
Harald Welteee3501f2021-04-02 13:00:18 +020077def lpad(s:str, l:int, c='f') -> str:
78 """pad string on the left side.
79 Args:
80 s : string to pad
81 l : total length to pad to
82 c : padding character
83 Returns:
84 String 's' padded with as many 'c' as needed to reach total length of 'l'
85 """
Sylvain Munaut76504e02010-12-07 00:24:32 +010086 return c * (l - len(s)) + s
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +040087
Harald Welteee3501f2021-04-02 13:00:18 +020088def half_round_up(n:int) -> int:
Ben Fox-Moore0ec14752018-09-24 15:47:02 +020089 return (n + 1)//2
90
91# IMSI encoded format:
92# For IMSI 0123456789ABCDE:
93#
94# | byte 1 | 2 upper | 2 lower | 3 upper | 3 lower | ... | 9 upper | 9 lower |
95# | length in bytes | 0 | odd/even | 2 | 1 | ... | E | D |
96#
97# If the IMSI is less than 15 characters, it should be padded with 'f' from the end.
98#
99# The length is the total number of bytes used to encoded the IMSI. This includes the odd/even
100# parity bit. E.g. an IMSI of length 14 is 8 bytes long, not 7, as it uses bytes 2 to 9 to
101# encode itself.
102#
103# Because of this, an odd length IMSI fits exactly into len(imsi) + 1 // 2 bytes, whereas an
104# even length IMSI only uses half of the last byte.
105
Harald Welteee3501f2021-04-02 13:00:18 +0200106def enc_imsi(imsi:str):
107 """Converts a string IMSI into the encoded value of the EF"""
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200108 l = half_round_up(len(imsi) + 1) # Required bytes - include space for odd/even indicator
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400109 oe = len(imsi) & 1 # Odd (1) / Even (0)
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200110 ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe<<3)|1, rpad(imsi, 15)))
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400111 return ei
112
Harald Welte52255572021-04-03 09:56:32 +0200113def dec_imsi(ef:Hexstr) -> Optional[str]:
Harald Weltec9cdce32021-04-11 10:28:28 +0200114 """Converts an EF value to the IMSI string representation"""
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400115 if len(ef) < 4:
116 return None
Pau Espin Pedrol665bd222017-12-29 20:30:35 +0100117 l = int(ef[0:2], 16) * 2 # Length of the IMSI string
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200118 l = l - 1 # Encoded length byte includes oe nibble
119 swapped = swap_nibbles(ef[2:]).rstrip('f')
Philipp Maiercd3d6262020-05-11 21:41:56 +0200120 if len(swapped) < 1:
121 return None
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400122 oe = (int(swapped[0])>>3) & 1 # Odd (1) / Even (0)
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200123 if not oe:
124 # if even, only half of last byte was used
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400125 l = l-1
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200126 if l != len(swapped) - 1:
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400127 return None
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200128 imsi = swapped[1:]
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400129 return imsi
130
Harald Welte52255572021-04-03 09:56:32 +0200131def dec_iccid(ef:Hexstr) -> str:
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400132 return swap_nibbles(ef).strip('f')
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400133
Harald Welte52255572021-04-03 09:56:32 +0200134def enc_iccid(iccid:str) -> Hexstr:
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400135 return swap_nibbles(rpad(iccid, 20))
136
Philipp Maiere6f8d682021-04-23 21:14:41 +0200137def enc_plmn(mcc:Hexstr, mnc:Hexstr) -> Hexstr:
Alexander Chemerisdddbf522017-07-18 16:49:59 +0300138 """Converts integer MCC/MNC into 3 bytes for EF"""
Philipp Maier6c5cd802021-04-23 21:19:36 +0200139
140 # Make sure there are no excess whitespaces in the input
141 # parameters
142 mcc = mcc.strip()
143 mnc = mnc.strip()
144
145 # Make sure that MCC/MNC are correctly padded with leading
146 # zeros or 'F', depending on the length.
147 if len(mnc) == 0:
148 mnc = "FFF"
149 elif len(mnc) == 1:
150 mnc = "F0" + mnc
151 elif len(mnc) == 2:
152 mnc += "F"
153
154 if len(mcc) == 0:
155 mcc = "FFF"
156 elif len(mcc) == 1:
157 mcc = "00" + mcc
158 elif len(mcc) == 2:
159 mcc = "0" + mcc
160
Vadim Yanitskiyc8458e22021-03-12 00:34:10 +0100161 return (mcc[1] + mcc[0]) + (mnc[2] + mcc[2]) + (mnc[1] + mnc[0])
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300162
Harald Welted7a7e172021-04-07 00:30:10 +0200163def dec_plmn(threehexbytes:Hexstr) -> dict:
Philipp Maier6c5cd802021-04-23 21:19:36 +0200164 res = {'mcc': "0", 'mnc': "0" }
165 dec_mcc_from_plmn_str(threehexbytes)
166 res['mcc'] = dec_mcc_from_plmn_str(threehexbytes)
167 res['mnc'] = dec_mnc_from_plmn_str(threehexbytes)
Harald Welted7a7e172021-04-07 00:30:10 +0200168 return res
169
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300170def dec_spn(ef):
171 byte1 = int(ef[0:2])
172 hplmn_disp = (byte1&0x01 == 0x01)
173 oplmn_disp = (byte1&0x02 == 0x02)
174 name = h2s(ef[2:])
175 return (name, hplmn_disp, oplmn_disp)
176
177def enc_spn(name, hplmn_disp=False, oplmn_disp=False):
178 byte1 = 0x00
179 if hplmn_disp: byte1 = byte1|0x01
180 if oplmn_disp: byte1 = byte1|0x02
181 return i2h([byte1])+s2h(name)
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900182
Supreeth Herlef3948532020-03-24 12:23:51 +0100183def hexstr_to_Nbytearr(s, nbytes):
184 return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2)) ]
185
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100186# Accepts hex string representing three bytes
Harald Welte52255572021-04-03 09:56:32 +0200187def dec_mcc_from_plmn(plmn:Hexstr) -> int:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100188 ia = h2i(plmn)
189 digit1 = ia[0] & 0x0F # 1st byte, LSB
190 digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB
191 digit3 = ia[1] & 0x0F # 2nd byte, LSB
192 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
193 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100194 return derive_mcc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100195
Philipp Maier6c5cd802021-04-23 21:19:36 +0200196def dec_mcc_from_plmn_str(plmn:Hexstr) -> str:
197 digit1 = plmn[1] # 1st byte, LSB
198 digit2 = plmn[0] # 1st byte, MSB
199 digit3 = plmn[3] # 2nd byte, LSB
200 res = digit1 + digit2 + digit3
201 return res.upper().strip("F")
202
Harald Welte52255572021-04-03 09:56:32 +0200203def dec_mnc_from_plmn(plmn:Hexstr) -> int:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100204 ia = h2i(plmn)
Vadim Yanitskiyb271be32021-03-11 23:56:58 +0100205 digit1 = ia[2] & 0x0F # 3rd byte, LSB
206 digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB
207 digit3 = (ia[1] & 0xF0) >> 4 # 2nd byte, MSB
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100208 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
209 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100210 return derive_mnc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100211
Philipp Maier6c5cd802021-04-23 21:19:36 +0200212def dec_mnc_from_plmn_str(plmn:Hexstr) -> str:
213 digit1 = plmn[5] # 3rd byte, LSB
214 digit2 = plmn[4] # 3rd byte, MSB
215 digit3 = plmn[2] # 2nd byte, MSB
216 res = digit1 + digit2 + digit3
217 return res.upper().strip("F")
218
Harald Welte52255572021-04-03 09:56:32 +0200219def dec_act(twohexbytes:Hexstr) -> List[str]:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100220 act_list = [
221 {'bit': 15, 'name': "UTRAN"},
222 {'bit': 14, 'name': "E-UTRAN"},
223 {'bit': 7, 'name': "GSM"},
224 {'bit': 6, 'name': "GSM COMPACT"},
225 {'bit': 5, 'name': "cdma2000 HRPD"},
226 {'bit': 4, 'name': "cdma2000 1xRTT"},
227 ]
228 ia = h2i(twohexbytes)
229 u16t = (ia[0] << 8)|ia[1]
230 sel = []
231 for a in act_list:
232 if u16t & (1 << a['bit']):
Philipp Maiere7d41792021-04-29 16:20:07 +0200233 if a['name'] == "E-UTRAN":
234 # The Access technology identifier of E-UTRAN
235 # allows a more detailed specification:
236 if u16t & (1 << 13) and u16t & (1 << 12):
237 sel.append("E-UTRAN WB-S1")
238 sel.append("E-UTRAN NB-S1")
239 elif u16t & (1 << 13):
240 sel.append("E-UTRAN WB-S1")
241 elif u16t & (1 << 12):
242 sel.append("E-UTRAN NB-S1")
243 else:
244 sel.append("E-UTRAN")
245 else:
246 sel.append(a['name'])
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100247 return sel
248
Harald Welte52255572021-04-03 09:56:32 +0200249def dec_xplmn_w_act(fivehexbytes:Hexstr) -> Dict[str,Any]:
Philipp Maierb919f8b2021-04-27 18:28:27 +0200250 res = {'mcc': "0", 'mnc': "0", 'act': []}
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100251 plmn_chars = 6
252 act_chars = 4
253 plmn_str = fivehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
254 act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars] # two bytes after first three bytes
Philipp Maierb919f8b2021-04-27 18:28:27 +0200255 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
256 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100257 res['act'] = dec_act(act_str)
258 return res
259
260def format_xplmn_w_act(hexstr):
261 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200262 for rec_data in hexstr_to_Nbytearr(hexstr, 5):
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100263 rec_info = dec_xplmn_w_act(rec_data)
Philipp Maierb919f8b2021-04-27 18:28:27 +0200264 if rec_info['mcc'] == "" and rec_info['mnc'] == "":
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100265 rec_str = "unused"
266 else:
Philipp Maierb919f8b2021-04-27 18:28:27 +0200267 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 +0100268 s += "\t%s # %s\n" % (rec_data, rec_str)
269 return s
270
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100271def dec_loci(hexstr):
272 res = {'tmsi': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0}
273 res['tmsi'] = hexstr[:8]
274 res['mcc'] = dec_mcc_from_plmn(hexstr[8:14])
275 res['mnc'] = dec_mnc_from_plmn(hexstr[8:14])
276 res['lac'] = hexstr[14:18]
277 res['status'] = h2i(hexstr[20:22])
278 return res
279
280def dec_psloci(hexstr):
281 res = {'p-tmsi': '', 'p-tmsi-sig': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'rac': '', 'status': 0}
282 res['p-tmsi'] = hexstr[:8]
283 res['p-tmsi-sig'] = hexstr[8:14]
284 res['mcc'] = dec_mcc_from_plmn(hexstr[14:20])
285 res['mnc'] = dec_mnc_from_plmn(hexstr[14:20])
286 res['lac'] = hexstr[20:24]
287 res['rac'] = hexstr[24:26]
288 res['status'] = h2i(hexstr[26:28])
289 return res
290
291def dec_epsloci(hexstr):
292 res = {'guti': '', 'mcc': 0, 'mnc': 0, 'tac': '', 'status': 0}
293 res['guti'] = hexstr[:24]
294 res['tai'] = hexstr[24:34]
295 res['mcc'] = dec_mcc_from_plmn(hexstr[24:30])
296 res['mnc'] = dec_mnc_from_plmn(hexstr[24:30])
297 res['tac'] = hexstr[30:34]
298 res['status'] = h2i(hexstr[34:36])
299 return res
300
Harald Welte52255572021-04-03 09:56:32 +0200301def dec_xplmn(threehexbytes:Hexstr) -> dict:
Harald Welteca673942020-06-03 15:19:40 +0200302 res = {'mcc': 0, 'mnc': 0, 'act': []}
303 plmn_chars = 6
304 plmn_str = threehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
305 res['mcc'] = dec_mcc_from_plmn(plmn_str)
306 res['mnc'] = dec_mnc_from_plmn(plmn_str)
307 return res
308
Harald Welte52255572021-04-03 09:56:32 +0200309def format_xplmn(hexstr:Hexstr) -> str:
Harald Welteca673942020-06-03 15:19:40 +0200310 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200311 for rec_data in hexstr_to_Nbytearr(hexstr, 3):
Harald Welteca673942020-06-03 15:19:40 +0200312 rec_info = dec_xplmn(rec_data)
313 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
314 rec_str = "unused"
315 else:
316 rec_str = "MCC: %03d MNC: %03d" % (rec_info['mcc'], rec_info['mnc'])
317 s += "\t%s # %s\n" % (rec_data, rec_str)
318 return s
319
Harald Welte52255572021-04-03 09:56:32 +0200320def derive_milenage_opc(ki_hex:Hexstr, op_hex:Hexstr) -> Hexstr:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900321 """
322 Run the milenage algorithm to calculate OPC from Ki and OP
323 """
324 from Crypto.Cipher import AES
325 from Crypto.Util.strxor import strxor
326 from pySim.utils import b2h
327
328 # We pass in hex string and now need to work on bytes
Harald Welteeab8d2a2021-03-05 18:30:23 +0100329 ki_bytes = bytes(h2b(ki_hex))
330 op_bytes = bytes(h2b(op_hex))
Harald Welteab34fa82021-03-05 18:39:59 +0100331 aes = AES.new(ki_bytes, AES.MODE_ECB)
Harald Welteeab8d2a2021-03-05 18:30:23 +0100332 opc_bytes = aes.encrypt(op_bytes)
333 return b2h(strxor(opc_bytes, op_bytes))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900334
Harald Welte52255572021-04-03 09:56:32 +0200335def calculate_luhn(cc) -> int:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900336 """
337 Calculate Luhn checksum used in e.g. ICCID and IMEI
338 """
Daniel Willmanndd014ea2020-10-19 10:35:11 +0200339 num = list(map(int, str(cc)))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900340 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10
341 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200342
Harald Welte52255572021-04-03 09:56:32 +0200343def mcc_from_imsi(imsi:str) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200344 """
345 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
346 """
347 if imsi == None:
348 return None
349
350 if len(imsi) > 3:
351 return imsi[:3]
352 else:
353 return None
354
Harald Welte52255572021-04-03 09:56:32 +0200355def mnc_from_imsi(imsi:str, long:bool=False) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200356 """
357 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
358 """
359 if imsi == None:
360 return None
361
362 if len(imsi) > 3:
363 if long:
364 return imsi[3:6]
365 else:
366 return imsi[3:5]
367 else:
368 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100369
Harald Welte52255572021-04-03 09:56:32 +0200370def derive_mcc(digit1:int, digit2:int, digit3:int) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100371 """
372 Derive decimal representation of the MCC (Mobile Country Code)
373 from three given digits.
374 """
375
376 mcc = 0
377
378 if digit1 != 0x0f:
379 mcc += digit1 * 100
380 if digit2 != 0x0f:
381 mcc += digit2 * 10
382 if digit3 != 0x0f:
383 mcc += digit3
384
385 return mcc
386
Harald Welte52255572021-04-03 09:56:32 +0200387def derive_mnc(digit1:int, digit2:int, digit3:int=0x0f) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100388 """
389 Derive decimal representation of the MNC (Mobile Network Code)
390 from two or (optionally) three given digits.
391 """
392
393 mnc = 0
394
395 # 3-rd digit is optional for the MNC. If present
396 # the algorythm is the same as for the MCC.
397 if digit3 != 0x0f:
398 return derive_mcc(digit1, digit2, digit3)
399
400 if digit1 != 0x0f:
401 mnc += digit1 * 10
402 if digit2 != 0x0f:
403 mnc += digit2
404
405 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100406
Harald Welte52255572021-04-03 09:56:32 +0200407def dec_msisdn(ef_msisdn:Hexstr) -> Optional[Tuple[int,int,Optional[str]]]:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100408 """
409 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
410 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
411 """
412
413 # Convert from str to (kind of) 'bytes'
414 ef_msisdn = h2b(ef_msisdn)
415
416 # Make sure mandatory fields are present
417 if len(ef_msisdn) < 14:
418 raise ValueError("EF.MSISDN is too short")
419
420 # Skip optional Alpha Identifier
421 xlen = len(ef_msisdn) - 14
422 msisdn_lhv = ef_msisdn[xlen:]
423
424 # Parse the length (in bytes) of the BCD encoded number
Harald Welte4f6ca432021-02-01 17:51:56 +0100425 bcd_len = msisdn_lhv[0]
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100426 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
427 if bcd_len == 0xff:
428 return None
429 elif bcd_len > 11 or bcd_len < 1:
430 raise ValueError("Length of MSISDN (%d bytes) is out of range" % bcd_len)
431
432 # Parse ToN / NPI
Harald Welte4f6ca432021-02-01 17:51:56 +0100433 ton = (msisdn_lhv[1] >> 4) & 0x07
434 npi = msisdn_lhv[1] & 0x0f
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100435 bcd_len -= 1
436
437 # No MSISDN?
438 if not bcd_len:
439 return (npi, ton, None)
440
441 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
442 # International number 10.5.118/3GPP TS 24.008
Vadim Yanitskiy7ba24282020-02-27 00:04:13 +0700443 if ton == 0x01:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100444 msisdn = '+' + msisdn
445
446 return (npi, ton, msisdn)
Supreeth Herle5a541012019-12-22 08:59:16 +0100447
Harald Welte52255572021-04-03 09:56:32 +0200448def enc_msisdn(msisdn:str, npi:int=0x01, ton:int=0x03) -> Hexstr:
Supreeth Herle5a541012019-12-22 08:59:16 +0100449 """
450 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200451 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
452 will not contain the optional Alpha Identifier at the beginning.)
Supreeth Herle5a541012019-12-22 08:59:16 +0100453
454 Default NPI / ToN values:
455 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
456 - ToN: network specific or international number (if starts with '+').
457 """
458
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200459 # If no MSISDN is supplied then encode the file contents as all "ff"
460 if msisdn == "" or msisdn == "+":
461 return "ff" * 14
462
Supreeth Herle5a541012019-12-22 08:59:16 +0100463 # Leading '+' indicates International Number
464 if msisdn[0] == '+':
465 msisdn = msisdn[1:]
466 ton = 0x01
467
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200468 # An MSISDN must not exceed 20 digits
469 if len(msisdn) > 20:
470 raise ValueError("msisdn must not be longer than 20 digits")
471
Supreeth Herle5a541012019-12-22 08:59:16 +0100472 # Append 'f' padding if number of digits is odd
473 if len(msisdn) % 2 > 0:
474 msisdn += 'f'
475
476 # BCD length also includes NPI/ToN header
477 bcd_len = len(msisdn) // 2 + 1
478 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
479 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
480
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200481 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
482
Supreeth Herle441c4a72020-03-24 10:19:15 +0100483
Harald Welte52255572021-04-03 09:56:32 +0200484def dec_st(st, table="sim") -> str:
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200485 """
486 Parses the EF S/U/IST and prints the list of available services in EF S/U/IST
487 """
488
Supreeth Herledf330372020-04-20 14:48:55 +0200489 if table == "isim":
490 from pySim.ts_31_103 import EF_IST_map
491 lookup_map = EF_IST_map
492 elif table == "usim":
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200493 from pySim.ts_31_102 import EF_UST_map
494 lookup_map = EF_UST_map
495 else:
496 from pySim.ts_51_011 import EF_SST_map
497 lookup_map = EF_SST_map
498
499 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
500
501 avail_st = ""
502 # Get each byte and check for available services
503 for i in range(0, len(st_bytes)):
504 # Byte i contains info about Services num (8i+1) to num (8i+8)
505 byte = int(st_bytes[i], 16)
506 # Services in each byte are in order MSB to LSB
507 # MSB - Service (8i+8)
508 # LSB - Service (8i+1)
509 for j in range(1, 9):
510 if byte&0x01 == 0x01 and ((8*i) + j in lookup_map):
511 # Byte X contains info about Services num (8X-7) to num (8X)
512 # bit = 1: service available
513 # bit = 0: service not available
514 avail_st += '\tService %d - %s\n' % ((8*i) + j, lookup_map[(8*i) + j])
515 byte = byte >> 1
516 return avail_st
Supreeth Herle98370552020-05-11 09:04:41 +0200517
518def first_TLV_parser(bytelist):
519 '''
520 first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
521
522 parses first TLV format record in a list of bytelist
523 returns a 3-Tuple: Tag, Length, Value
524 Value is a list of bytes
525 parsing of length is ETSI'style 101.220
526 '''
527 Tag = bytelist[0]
528 if bytelist[1] == 0xFF:
529 Len = bytelist[2]*256 + bytelist[3]
530 Val = bytelist[4:4+Len]
531 else:
532 Len = bytelist[1]
533 Val = bytelist[2:2+Len]
534 return (Tag, Len, Val)
535
536def TLV_parser(bytelist):
537 '''
538 TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
539
540 loops on the input list of bytes with the "first_TLV_parser()" function
541 returns a list of 3-Tuples
542 '''
543 ret = []
544 while len(bytelist) > 0:
545 T, L, V = first_TLV_parser(bytelist)
546 if T == 0xFF:
547 # padding bytes
548 break
549 ret.append( (T, L, V) )
550 # need to manage length of L
551 if L > 0xFE:
552 bytelist = bytelist[ L+4 : ]
553 else:
554 bytelist = bytelist[ L+2 : ]
555 return ret
Supreeth Herled572ede2020-03-22 09:55:04 +0100556
Supreeth Herled84daa12020-03-24 12:20:40 +0100557def enc_st(st, service, state=1):
558 """
559 Encodes the EF S/U/IST/EST and returns the updated Service Table
560
561 Parameters:
562 st - Current value of SIM/USIM/ISIM Service Table
563 service - Service Number to encode as activated/de-activated
564 state - 1 mean activate, 0 means de-activate
565
566 Returns:
567 s - Modified value of SIM/USIM/ISIM Service Table
568
569 Default values:
570 - state: 1 - Sets the particular Service bit to 1
571 """
572 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
573
574 s = ""
575 # Check whether the requested service is present in each byte
576 for i in range(0, len(st_bytes)):
577 # Byte i contains info about Services num (8i+1) to num (8i+8)
578 if service in range((8*i) + 1, (8*i) + 9):
579 byte = int(st_bytes[i], 16)
580 # Services in each byte are in order MSB to LSB
581 # MSB - Service (8i+8)
582 # LSB - Service (8i+1)
583 mod_byte = 0x00
584 # Copy bit by bit contents of byte to mod_byte with modified bit
585 # for requested service
586 for j in range(1, 9):
587 mod_byte = mod_byte >> 1
588 if service == (8*i) + j:
589 mod_byte = state == 1 and mod_byte|0x80 or mod_byte&0x7f
590 else:
591 mod_byte = byte&0x01 == 0x01 and mod_byte|0x80 or mod_byte&0x7f
592 byte = byte >> 1
593
594 s += ('%02x' % (mod_byte))
595 else:
596 s += st_bytes[i]
597
598 return s
599
Supreeth Herle3b342c22020-03-24 16:15:02 +0100600def dec_addr_tlv(hexstr):
Supreeth Herled572ede2020-03-22 09:55:04 +0100601 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100602 Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm.
603 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 +0100604 """
605
606 # Convert from hex str to int bytes list
Supreeth Herle3b342c22020-03-24 16:15:02 +0100607 addr_tlv_bytes = h2i(hexstr)
Supreeth Herled572ede2020-03-22 09:55:04 +0100608
609 s = ""
610
611 # Get list of tuples containing parsed TLVs
Supreeth Herle3b342c22020-03-24 16:15:02 +0100612 tlvs = TLV_parser(addr_tlv_bytes)
Supreeth Herled572ede2020-03-22 09:55:04 +0100613
614 for tlv in tlvs:
615 # tlv = (T, L, [V])
616 # T = Tag
617 # L = Length
618 # [V] = List of value
619
620 # Invalid Tag value scenario
621 if tlv[0] != 0x80:
622 continue
623
Supreeth Herled6a5ec52020-06-01 12:27:51 +0200624 # Empty field - Zero length
625 if tlv[1] == 0:
626 continue
627
Supreeth Herled572ede2020-03-22 09:55:04 +0100628 # First byte in the value has the address type
629 addr_type = tlv[2][0]
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100630 # TODO: Support parsing of IPv6
Supreeth Herle3b342c22020-03-24 16:15:02 +0100631 # Address Type: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), other (Reserved)
Supreeth Herled572ede2020-03-22 09:55:04 +0100632 if addr_type == 0x00: #FQDN
633 # Skip address tye byte i.e. first byte in value list
634 content = tlv[2][1:]
635 s += "\t%s # %s\n" % (i2h(content), i2s(content))
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100636 elif addr_type == 0x01: #IPv4
637 # Skip address tye byte i.e. first byte in value list
638 # Skip the unused byte in Octect 4 after address type byte as per 3GPP TS 31.102
639 ipv4 = tlv[2][2:]
640 content = '.'.join(str(x) for x in ipv4)
641 s += "\t%s # %s\n" % (i2h(ipv4), content)
Supreeth Herled572ede2020-03-22 09:55:04 +0100642
643 return s
Philipp Maierff84c232020-05-12 17:24:18 +0200644
Supreeth Herle3b342c22020-03-24 16:15:02 +0100645def enc_addr_tlv(addr, addr_type='00'):
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100646 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100647 Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm.
648 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 +0100649
650 Default values:
Supreeth Herle3b342c22020-03-24 16:15:02 +0100651 - addr_type: 00 - FQDN format of Address
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100652 """
653
654 s = ""
655
Supreeth Herle654eca72020-03-25 14:25:38 +0100656 # TODO: Encoding of IPv6 address
657 if addr_type == '00': #FQDN
Supreeth Herle3b342c22020-03-24 16:15:02 +0100658 hex_str = s2h(addr)
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100659 s += '80' + ('%02x' % ((len(hex_str)//2)+1)) + '00' + hex_str
Supreeth Herle654eca72020-03-25 14:25:38 +0100660 elif addr_type == '01': #IPv4
661 ipv4_list = addr.split('.')
662 ipv4_str = ""
663 for i in ipv4_list:
664 ipv4_str += ('%02x' % (int(i)))
665
666 # Unused bytes shall be set to 'ff'. i.e 4th Octet after Address Type is not used
667 # IPv4 Address is in octet 5 to octet 8 of the TLV data object
668 s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100669
670 return s
671
Harald Welte52255572021-04-03 09:56:32 +0200672def is_hex(string:str, minlen:int=2, maxlen:Optional[int]=None) -> bool:
Philipp Maier47236502021-03-09 21:28:25 +0100673 """
674 Check if a string is a valid hexstring
675 """
676
677 # Filter obviously bad strings
678 if not string:
679 return False
680 if len(string) < minlen or minlen < 2:
681 return False
682 if len(string) % 2:
683 return False
684 if maxlen and len(string) > maxlen:
685 return False
686
687 # Try actual encoding to be sure
688 try:
689 try_encode = h2b(string)
690 return True
691 except:
692 return False
693
Harald Welte52255572021-04-03 09:56:32 +0200694def sanitize_pin_adm(pin_adm, pin_adm_hex = None) -> Hexstr:
Philipp Maiere8536c02020-05-11 21:35:01 +0200695 """
696 The ADM pin can be supplied either in its hexadecimal form or as
697 ascii string. This function checks the supplied opts parameter and
Harald Welte236a65f2021-04-03 10:20:11 +0200698 returns the pin_adm as hex encoded string, regardless in which form
Philipp Maiere8536c02020-05-11 21:35:01 +0200699 it was originally supplied by the user
700 """
701
Harald Welte79b5ba42021-01-08 21:22:38 +0100702 if pin_adm is not None:
703 if len(pin_adm) <= 8:
704 pin_adm = ''.join(['%02x'%(ord(x)) for x in pin_adm])
Philipp Maiere8536c02020-05-11 21:35:01 +0200705 pin_adm = rpad(pin_adm, 16)
706
707 else:
708 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
709
Harald Welte79b5ba42021-01-08 21:22:38 +0100710 if pin_adm_hex is not None:
711 if len(pin_adm_hex) == 16:
712 pin_adm = pin_adm_hex
Philipp Maiere8536c02020-05-11 21:35:01 +0200713 # Ensure that it's hex-encoded
714 try:
715 try_encode = h2b(pin_adm)
716 except ValueError:
717 raise ValueError("PIN-ADM needs to be hex encoded using this option")
718 else:
719 raise ValueError("PIN-ADM needs to be exactly 16 digits (hex encoded)")
720
721 return pin_adm
722
Supreeth Herle95ec7722020-03-24 13:09:03 +0100723def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='00'):
724 """
725 Encode ePDGSelection so it can be stored at EF.ePDGSelection or EF.ePDGSelectionEm.
726 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
727
728 Default values:
729 - epdg_priority: '0001' - 1st Priority
730 - epdg_fqdn_format: '00' - Operator Identifier FQDN
731 """
732
733 plmn1 = enc_plmn(mcc, mnc) + epdg_priority + epdg_fqdn_format
734 # TODO: Handle encoding of Length field for length more than 127 Bytes
735 content = '80' + ('%02x' % (len(plmn1)//2)) + plmn1
736 content = rpad(content, len(hexstr))
737 return content
738
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100739def dec_ePDGSelection(sixhexbytes):
740 """
741 Decode ePDGSelection to get EF.ePDGSelection or EF.ePDGSelectionEm.
742 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
743 """
744
745 res = {'mcc': 0, 'mnc': 0, 'epdg_priority': 0, 'epdg_fqdn_format': ''}
746 plmn_chars = 6
747 epdg_priority_chars = 4
748 epdg_fqdn_format_chars = 2
749 # first three bytes (six ascii hex chars)
750 plmn_str = sixhexbytes[:plmn_chars]
751 # two bytes after first three bytes
752 epdg_priority_str = sixhexbytes[plmn_chars:plmn_chars + epdg_priority_chars]
753 # one byte after first five bytes
754 epdg_fqdn_format_str = sixhexbytes[plmn_chars + epdg_priority_chars:plmn_chars + epdg_priority_chars + epdg_fqdn_format_chars]
755 res['mcc'] = dec_mcc_from_plmn(plmn_str)
756 res['mnc'] = dec_mnc_from_plmn(plmn_str)
757 res['epdg_priority'] = epdg_priority_str
758 res['epdg_fqdn_format'] = epdg_fqdn_format_str == '00' and 'Operator Identifier FQDN' or 'Location based FQDN'
759 return res
760
761def format_ePDGSelection(hexstr):
762 ePDGSelection_info_tag_chars = 2
763 ePDGSelection_info_tag_str = hexstr[:2]
herlesupreeth3a261d32021-01-05 09:20:11 +0100764 s = ""
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100765 # Minimum length
766 len_chars = 2
767 # TODO: Need to determine length properly - definite length support only
768 # Inconsistency in spec: 3GPP TS 31.102 version 15.2.0 Release 15, 4.2.104
769 # As per spec, length is 5n, n - number of PLMNs
770 # But, each PLMN entry is made of PLMN (3 Bytes) + ePDG Priority (2 Bytes) + ePDG FQDN format (1 Byte)
771 # Totalling to 6 Bytes, maybe length should be 6n
772 len_str = hexstr[ePDGSelection_info_tag_chars:ePDGSelection_info_tag_chars+len_chars]
herlesupreeth3a261d32021-01-05 09:20:11 +0100773
774 # Not programmed scenario
775 if int(len_str, 16) == 255 or int(ePDGSelection_info_tag_str, 16) == 255:
776 len_chars = 0
777 ePDGSelection_info_tag_chars = 0
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100778 if len_str[0] == '8':
779 # The bits 7 to 1 denotes the number of length octets if length > 127
780 if int(len_str[1]) > 0:
781 # Update number of length octets
782 len_chars = len_chars * int(len_str[1])
783 len_str = hexstr[ePDGSelection_info_tag_chars:len_chars]
784
785 content_str = hexstr[ePDGSelection_info_tag_chars+len_chars:]
786 # Right pad to prevent index out of range - multiple of 6 bytes
787 content_str = rpad(content_str, len(content_str) + (12 - (len(content_str) % 12)))
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100788 for rec_data in hexstr_to_Nbytearr(content_str, 6):
789 rec_info = dec_ePDGSelection(rec_data)
790 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
791 rec_str = "unused"
792 else:
793 rec_str = "MCC: %03d MNC: %03d ePDG Priority: %s ePDG FQDN format: %s" % \
794 (rec_info['mcc'], rec_info['mnc'], rec_info['epdg_priority'], rec_info['epdg_fqdn_format'])
795 s += "\t%s # %s\n" % (rec_data, rec_str)
796 return s
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100797
798def get_addr_type(addr):
799 """
800 Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
801 Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given)
802
803 TODO: Handle IPv6
804 """
805
806 # Empty address string
807 if not len(addr):
808 return None
809
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100810 addr_list = addr.split('.')
811
812 # Check for IPv4/IPv6
813 try:
814 import ipaddress
815 # Throws ValueError if addr is not correct
Denis 'GNUtoo' Carikli79f5b602020-02-15 04:02:57 +0700816 ipa = ipaddress.ip_address(addr)
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100817
818 if ipa.version == 4:
819 return 0x01
820 elif ipa.version == 6:
821 return 0x02
822 except Exception as e:
823 invalid_ipv4 = True
824 for i in addr_list:
825 # Invalid IPv4 may qualify for a valid FQDN, so make check here
826 # e.g. 172.24.15.300
827 import re
828 if not re.match('^[0-9_]+$', i):
829 invalid_ipv4 = False
830 break
831
832 if invalid_ipv4:
833 return None
834
835 fqdn_flag = True
836 for i in addr_list:
837 # Only Alpha-numeric characters and hyphen - RFC 1035
838 import re
839 if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
840 fqdn_flag = False
841 break
842
843 # FQDN
844 if fqdn_flag:
845 return 0x00
846
847 return None
Harald Welte67d551a2021-01-21 14:50:01 +0100848
Harald Welte1e456572021-04-02 17:16:30 +0200849def sw_match(sw:str, pattern:str) -> bool:
Harald Welte67d551a2021-01-21 14:50:01 +0100850 """Match given SW against given pattern."""
851 # Create a masked version of the returned status word
852 sw_lower = sw.lower()
853 sw_masked = ""
854 for i in range(0, 4):
Philipp Maier78e32f22021-03-22 20:39:24 +0100855 if pattern[i] == '?':
Harald Welte67d551a2021-01-21 14:50:01 +0100856 sw_masked = sw_masked + '?'
Philipp Maier78e32f22021-03-22 20:39:24 +0100857 elif pattern[i] == 'x':
Harald Welte67d551a2021-01-21 14:50:01 +0100858 sw_masked = sw_masked + 'x'
859 else:
860 sw_masked = sw_masked + sw_lower[i]
Philipp Maier78e32f22021-03-22 20:39:24 +0100861 # Compare the masked version against the pattern
Harald Welte67d551a2021-01-21 14:50:01 +0100862 return sw_masked == pattern
Philipp Maier5d3e2592021-02-22 17:22:16 +0100863
Harald Welteee3501f2021-04-02 13:00:18 +0200864def tabulate_str_list(str_list, width:int = 79, hspace:int = 2, lspace:int = 1,
Harald Welte52255572021-04-03 09:56:32 +0200865 align_left:bool = True) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +0200866 """Pretty print a list of strings into a tabulated form.
867
868 Args:
869 width : total width in characters per line
870 space : horizontal space between cells
871 lspace : number of spaces before row
872 align_lef : Align text to the left side
873 Returns:
874 multi-line string containing formatted table
875 """
Philipp Maier5d3e2592021-02-22 17:22:16 +0100876 if str_list == None:
877 return ""
878 if len(str_list) <= 0:
879 return ""
880 longest_str = max(str_list, key=len)
881 cellwith = len(longest_str) + hspace
882 cols = width // cellwith
883 rows = (len(str_list) - 1) // cols + 1
884 table = []
885 for i in iter(range(rows)):
886 str_list_row = str_list[i::rows]
887 if (align_left):
888 format_str_cell = '%%-%ds'
889 else:
890 format_str_cell = '%%%ds'
891 format_str_row = (format_str_cell % cellwith) * len(str_list_row)
892 format_str_row = (" " * lspace) + format_str_row
893 table.append(format_str_row % tuple(str_list_row))
894 return '\n'.join(table)
Harald Welte5e749a72021-04-10 17:18:17 +0200895
896class JsonEncoder(json.JSONEncoder):
897 """Extend the standard library JSONEncoder with support for more types."""
898 def default(self, o):
899 if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
900 return b2h(o)
901 return json.JSONEncoder.default(self, o)
Philipp Maier80ce71f2021-04-19 21:24:23 +0200902
903def boxed_heading_str(heading, width=80):
904 """Generate a string that contains a boxed heading."""
905 # Auto-enlarge box if heading exceeds length
906 if len(heading) > width - 4:
907 width = len(heading) + 4
908
909 res = "#" * width
910 fstr = "\n# %-" + str(width - 4) + "s #\n"
911 res += fstr % (heading)
912 res += "#" * width
913 return res