blob: 225348228e3231937f660ef21091e3e4662b03cf [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']):
233 sel.append(a['name'])
234 return sel
235
Harald Welte52255572021-04-03 09:56:32 +0200236def dec_xplmn_w_act(fivehexbytes:Hexstr) -> Dict[str,Any]:
Philipp Maierb919f8b2021-04-27 18:28:27 +0200237 res = {'mcc': "0", 'mnc': "0", 'act': []}
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100238 plmn_chars = 6
239 act_chars = 4
240 plmn_str = fivehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
241 act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars] # two bytes after first three bytes
Philipp Maierb919f8b2021-04-27 18:28:27 +0200242 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
243 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100244 res['act'] = dec_act(act_str)
245 return res
246
247def format_xplmn_w_act(hexstr):
248 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200249 for rec_data in hexstr_to_Nbytearr(hexstr, 5):
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100250 rec_info = dec_xplmn_w_act(rec_data)
Philipp Maierb919f8b2021-04-27 18:28:27 +0200251 if rec_info['mcc'] == "" and rec_info['mnc'] == "":
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100252 rec_str = "unused"
253 else:
Philipp Maierb919f8b2021-04-27 18:28:27 +0200254 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 +0100255 s += "\t%s # %s\n" % (rec_data, rec_str)
256 return s
257
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100258def dec_loci(hexstr):
259 res = {'tmsi': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0}
260 res['tmsi'] = hexstr[:8]
261 res['mcc'] = dec_mcc_from_plmn(hexstr[8:14])
262 res['mnc'] = dec_mnc_from_plmn(hexstr[8:14])
263 res['lac'] = hexstr[14:18]
264 res['status'] = h2i(hexstr[20:22])
265 return res
266
267def dec_psloci(hexstr):
268 res = {'p-tmsi': '', 'p-tmsi-sig': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'rac': '', 'status': 0}
269 res['p-tmsi'] = hexstr[:8]
270 res['p-tmsi-sig'] = hexstr[8:14]
271 res['mcc'] = dec_mcc_from_plmn(hexstr[14:20])
272 res['mnc'] = dec_mnc_from_plmn(hexstr[14:20])
273 res['lac'] = hexstr[20:24]
274 res['rac'] = hexstr[24:26]
275 res['status'] = h2i(hexstr[26:28])
276 return res
277
278def dec_epsloci(hexstr):
279 res = {'guti': '', 'mcc': 0, 'mnc': 0, 'tac': '', 'status': 0}
280 res['guti'] = hexstr[:24]
281 res['tai'] = hexstr[24:34]
282 res['mcc'] = dec_mcc_from_plmn(hexstr[24:30])
283 res['mnc'] = dec_mnc_from_plmn(hexstr[24:30])
284 res['tac'] = hexstr[30:34]
285 res['status'] = h2i(hexstr[34:36])
286 return res
287
Harald Welte52255572021-04-03 09:56:32 +0200288def dec_xplmn(threehexbytes:Hexstr) -> dict:
Harald Welteca673942020-06-03 15:19:40 +0200289 res = {'mcc': 0, 'mnc': 0, 'act': []}
290 plmn_chars = 6
291 plmn_str = threehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
292 res['mcc'] = dec_mcc_from_plmn(plmn_str)
293 res['mnc'] = dec_mnc_from_plmn(plmn_str)
294 return res
295
Harald Welte52255572021-04-03 09:56:32 +0200296def format_xplmn(hexstr:Hexstr) -> str:
Harald Welteca673942020-06-03 15:19:40 +0200297 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200298 for rec_data in hexstr_to_Nbytearr(hexstr, 3):
Harald Welteca673942020-06-03 15:19:40 +0200299 rec_info = dec_xplmn(rec_data)
300 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
301 rec_str = "unused"
302 else:
303 rec_str = "MCC: %03d MNC: %03d" % (rec_info['mcc'], rec_info['mnc'])
304 s += "\t%s # %s\n" % (rec_data, rec_str)
305 return s
306
Harald Welte52255572021-04-03 09:56:32 +0200307def derive_milenage_opc(ki_hex:Hexstr, op_hex:Hexstr) -> Hexstr:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900308 """
309 Run the milenage algorithm to calculate OPC from Ki and OP
310 """
311 from Crypto.Cipher import AES
312 from Crypto.Util.strxor import strxor
313 from pySim.utils import b2h
314
315 # We pass in hex string and now need to work on bytes
Harald Welteeab8d2a2021-03-05 18:30:23 +0100316 ki_bytes = bytes(h2b(ki_hex))
317 op_bytes = bytes(h2b(op_hex))
Harald Welteab34fa82021-03-05 18:39:59 +0100318 aes = AES.new(ki_bytes, AES.MODE_ECB)
Harald Welteeab8d2a2021-03-05 18:30:23 +0100319 opc_bytes = aes.encrypt(op_bytes)
320 return b2h(strxor(opc_bytes, op_bytes))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900321
Harald Welte52255572021-04-03 09:56:32 +0200322def calculate_luhn(cc) -> int:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900323 """
324 Calculate Luhn checksum used in e.g. ICCID and IMEI
325 """
Daniel Willmanndd014ea2020-10-19 10:35:11 +0200326 num = list(map(int, str(cc)))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900327 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10
328 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200329
Harald Welte52255572021-04-03 09:56:32 +0200330def mcc_from_imsi(imsi:str) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200331 """
332 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
333 """
334 if imsi == None:
335 return None
336
337 if len(imsi) > 3:
338 return imsi[:3]
339 else:
340 return None
341
Harald Welte52255572021-04-03 09:56:32 +0200342def mnc_from_imsi(imsi:str, long:bool=False) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200343 """
344 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
345 """
346 if imsi == None:
347 return None
348
349 if len(imsi) > 3:
350 if long:
351 return imsi[3:6]
352 else:
353 return imsi[3:5]
354 else:
355 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100356
Harald Welte52255572021-04-03 09:56:32 +0200357def derive_mcc(digit1:int, digit2:int, digit3:int) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100358 """
359 Derive decimal representation of the MCC (Mobile Country Code)
360 from three given digits.
361 """
362
363 mcc = 0
364
365 if digit1 != 0x0f:
366 mcc += digit1 * 100
367 if digit2 != 0x0f:
368 mcc += digit2 * 10
369 if digit3 != 0x0f:
370 mcc += digit3
371
372 return mcc
373
Harald Welte52255572021-04-03 09:56:32 +0200374def derive_mnc(digit1:int, digit2:int, digit3:int=0x0f) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100375 """
376 Derive decimal representation of the MNC (Mobile Network Code)
377 from two or (optionally) three given digits.
378 """
379
380 mnc = 0
381
382 # 3-rd digit is optional for the MNC. If present
383 # the algorythm is the same as for the MCC.
384 if digit3 != 0x0f:
385 return derive_mcc(digit1, digit2, digit3)
386
387 if digit1 != 0x0f:
388 mnc += digit1 * 10
389 if digit2 != 0x0f:
390 mnc += digit2
391
392 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100393
Harald Welte52255572021-04-03 09:56:32 +0200394def dec_msisdn(ef_msisdn:Hexstr) -> Optional[Tuple[int,int,Optional[str]]]:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100395 """
396 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
397 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
398 """
399
400 # Convert from str to (kind of) 'bytes'
401 ef_msisdn = h2b(ef_msisdn)
402
403 # Make sure mandatory fields are present
404 if len(ef_msisdn) < 14:
405 raise ValueError("EF.MSISDN is too short")
406
407 # Skip optional Alpha Identifier
408 xlen = len(ef_msisdn) - 14
409 msisdn_lhv = ef_msisdn[xlen:]
410
411 # Parse the length (in bytes) of the BCD encoded number
Harald Welte4f6ca432021-02-01 17:51:56 +0100412 bcd_len = msisdn_lhv[0]
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100413 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
414 if bcd_len == 0xff:
415 return None
416 elif bcd_len > 11 or bcd_len < 1:
417 raise ValueError("Length of MSISDN (%d bytes) is out of range" % bcd_len)
418
419 # Parse ToN / NPI
Harald Welte4f6ca432021-02-01 17:51:56 +0100420 ton = (msisdn_lhv[1] >> 4) & 0x07
421 npi = msisdn_lhv[1] & 0x0f
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100422 bcd_len -= 1
423
424 # No MSISDN?
425 if not bcd_len:
426 return (npi, ton, None)
427
428 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
429 # International number 10.5.118/3GPP TS 24.008
Vadim Yanitskiy7ba24282020-02-27 00:04:13 +0700430 if ton == 0x01:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100431 msisdn = '+' + msisdn
432
433 return (npi, ton, msisdn)
Supreeth Herle5a541012019-12-22 08:59:16 +0100434
Harald Welte52255572021-04-03 09:56:32 +0200435def enc_msisdn(msisdn:str, npi:int=0x01, ton:int=0x03) -> Hexstr:
Supreeth Herle5a541012019-12-22 08:59:16 +0100436 """
437 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200438 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
439 will not contain the optional Alpha Identifier at the beginning.)
Supreeth Herle5a541012019-12-22 08:59:16 +0100440
441 Default NPI / ToN values:
442 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
443 - ToN: network specific or international number (if starts with '+').
444 """
445
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200446 # If no MSISDN is supplied then encode the file contents as all "ff"
447 if msisdn == "" or msisdn == "+":
448 return "ff" * 14
449
Supreeth Herle5a541012019-12-22 08:59:16 +0100450 # Leading '+' indicates International Number
451 if msisdn[0] == '+':
452 msisdn = msisdn[1:]
453 ton = 0x01
454
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200455 # An MSISDN must not exceed 20 digits
456 if len(msisdn) > 20:
457 raise ValueError("msisdn must not be longer than 20 digits")
458
Supreeth Herle5a541012019-12-22 08:59:16 +0100459 # Append 'f' padding if number of digits is odd
460 if len(msisdn) % 2 > 0:
461 msisdn += 'f'
462
463 # BCD length also includes NPI/ToN header
464 bcd_len = len(msisdn) // 2 + 1
465 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
466 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
467
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200468 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
469
Supreeth Herle441c4a72020-03-24 10:19:15 +0100470
Harald Welte52255572021-04-03 09:56:32 +0200471def dec_st(st, table="sim") -> str:
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200472 """
473 Parses the EF S/U/IST and prints the list of available services in EF S/U/IST
474 """
475
Supreeth Herledf330372020-04-20 14:48:55 +0200476 if table == "isim":
477 from pySim.ts_31_103 import EF_IST_map
478 lookup_map = EF_IST_map
479 elif table == "usim":
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200480 from pySim.ts_31_102 import EF_UST_map
481 lookup_map = EF_UST_map
482 else:
483 from pySim.ts_51_011 import EF_SST_map
484 lookup_map = EF_SST_map
485
486 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
487
488 avail_st = ""
489 # Get each byte and check for available services
490 for i in range(0, len(st_bytes)):
491 # Byte i contains info about Services num (8i+1) to num (8i+8)
492 byte = int(st_bytes[i], 16)
493 # Services in each byte are in order MSB to LSB
494 # MSB - Service (8i+8)
495 # LSB - Service (8i+1)
496 for j in range(1, 9):
497 if byte&0x01 == 0x01 and ((8*i) + j in lookup_map):
498 # Byte X contains info about Services num (8X-7) to num (8X)
499 # bit = 1: service available
500 # bit = 0: service not available
501 avail_st += '\tService %d - %s\n' % ((8*i) + j, lookup_map[(8*i) + j])
502 byte = byte >> 1
503 return avail_st
Supreeth Herle98370552020-05-11 09:04:41 +0200504
505def first_TLV_parser(bytelist):
506 '''
507 first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
508
509 parses first TLV format record in a list of bytelist
510 returns a 3-Tuple: Tag, Length, Value
511 Value is a list of bytes
512 parsing of length is ETSI'style 101.220
513 '''
514 Tag = bytelist[0]
515 if bytelist[1] == 0xFF:
516 Len = bytelist[2]*256 + bytelist[3]
517 Val = bytelist[4:4+Len]
518 else:
519 Len = bytelist[1]
520 Val = bytelist[2:2+Len]
521 return (Tag, Len, Val)
522
523def TLV_parser(bytelist):
524 '''
525 TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
526
527 loops on the input list of bytes with the "first_TLV_parser()" function
528 returns a list of 3-Tuples
529 '''
530 ret = []
531 while len(bytelist) > 0:
532 T, L, V = first_TLV_parser(bytelist)
533 if T == 0xFF:
534 # padding bytes
535 break
536 ret.append( (T, L, V) )
537 # need to manage length of L
538 if L > 0xFE:
539 bytelist = bytelist[ L+4 : ]
540 else:
541 bytelist = bytelist[ L+2 : ]
542 return ret
Supreeth Herled572ede2020-03-22 09:55:04 +0100543
Supreeth Herled84daa12020-03-24 12:20:40 +0100544def enc_st(st, service, state=1):
545 """
546 Encodes the EF S/U/IST/EST and returns the updated Service Table
547
548 Parameters:
549 st - Current value of SIM/USIM/ISIM Service Table
550 service - Service Number to encode as activated/de-activated
551 state - 1 mean activate, 0 means de-activate
552
553 Returns:
554 s - Modified value of SIM/USIM/ISIM Service Table
555
556 Default values:
557 - state: 1 - Sets the particular Service bit to 1
558 """
559 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
560
561 s = ""
562 # Check whether the requested service is present in each byte
563 for i in range(0, len(st_bytes)):
564 # Byte i contains info about Services num (8i+1) to num (8i+8)
565 if service in range((8*i) + 1, (8*i) + 9):
566 byte = int(st_bytes[i], 16)
567 # Services in each byte are in order MSB to LSB
568 # MSB - Service (8i+8)
569 # LSB - Service (8i+1)
570 mod_byte = 0x00
571 # Copy bit by bit contents of byte to mod_byte with modified bit
572 # for requested service
573 for j in range(1, 9):
574 mod_byte = mod_byte >> 1
575 if service == (8*i) + j:
576 mod_byte = state == 1 and mod_byte|0x80 or mod_byte&0x7f
577 else:
578 mod_byte = byte&0x01 == 0x01 and mod_byte|0x80 or mod_byte&0x7f
579 byte = byte >> 1
580
581 s += ('%02x' % (mod_byte))
582 else:
583 s += st_bytes[i]
584
585 return s
586
Supreeth Herle3b342c22020-03-24 16:15:02 +0100587def dec_addr_tlv(hexstr):
Supreeth Herled572ede2020-03-22 09:55:04 +0100588 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100589 Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm.
590 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 +0100591 """
592
593 # Convert from hex str to int bytes list
Supreeth Herle3b342c22020-03-24 16:15:02 +0100594 addr_tlv_bytes = h2i(hexstr)
Supreeth Herled572ede2020-03-22 09:55:04 +0100595
596 s = ""
597
598 # Get list of tuples containing parsed TLVs
Supreeth Herle3b342c22020-03-24 16:15:02 +0100599 tlvs = TLV_parser(addr_tlv_bytes)
Supreeth Herled572ede2020-03-22 09:55:04 +0100600
601 for tlv in tlvs:
602 # tlv = (T, L, [V])
603 # T = Tag
604 # L = Length
605 # [V] = List of value
606
607 # Invalid Tag value scenario
608 if tlv[0] != 0x80:
609 continue
610
Supreeth Herled6a5ec52020-06-01 12:27:51 +0200611 # Empty field - Zero length
612 if tlv[1] == 0:
613 continue
614
Supreeth Herled572ede2020-03-22 09:55:04 +0100615 # First byte in the value has the address type
616 addr_type = tlv[2][0]
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100617 # TODO: Support parsing of IPv6
Supreeth Herle3b342c22020-03-24 16:15:02 +0100618 # Address Type: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), other (Reserved)
Supreeth Herled572ede2020-03-22 09:55:04 +0100619 if addr_type == 0x00: #FQDN
620 # Skip address tye byte i.e. first byte in value list
621 content = tlv[2][1:]
622 s += "\t%s # %s\n" % (i2h(content), i2s(content))
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100623 elif addr_type == 0x01: #IPv4
624 # Skip address tye byte i.e. first byte in value list
625 # Skip the unused byte in Octect 4 after address type byte as per 3GPP TS 31.102
626 ipv4 = tlv[2][2:]
627 content = '.'.join(str(x) for x in ipv4)
628 s += "\t%s # %s\n" % (i2h(ipv4), content)
Supreeth Herled572ede2020-03-22 09:55:04 +0100629
630 return s
Philipp Maierff84c232020-05-12 17:24:18 +0200631
Supreeth Herle3b342c22020-03-24 16:15:02 +0100632def enc_addr_tlv(addr, addr_type='00'):
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100633 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100634 Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm.
635 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 +0100636
637 Default values:
Supreeth Herle3b342c22020-03-24 16:15:02 +0100638 - addr_type: 00 - FQDN format of Address
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100639 """
640
641 s = ""
642
Supreeth Herle654eca72020-03-25 14:25:38 +0100643 # TODO: Encoding of IPv6 address
644 if addr_type == '00': #FQDN
Supreeth Herle3b342c22020-03-24 16:15:02 +0100645 hex_str = s2h(addr)
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100646 s += '80' + ('%02x' % ((len(hex_str)//2)+1)) + '00' + hex_str
Supreeth Herle654eca72020-03-25 14:25:38 +0100647 elif addr_type == '01': #IPv4
648 ipv4_list = addr.split('.')
649 ipv4_str = ""
650 for i in ipv4_list:
651 ipv4_str += ('%02x' % (int(i)))
652
653 # Unused bytes shall be set to 'ff'. i.e 4th Octet after Address Type is not used
654 # IPv4 Address is in octet 5 to octet 8 of the TLV data object
655 s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100656
657 return s
658
Harald Welte52255572021-04-03 09:56:32 +0200659def is_hex(string:str, minlen:int=2, maxlen:Optional[int]=None) -> bool:
Philipp Maier47236502021-03-09 21:28:25 +0100660 """
661 Check if a string is a valid hexstring
662 """
663
664 # Filter obviously bad strings
665 if not string:
666 return False
667 if len(string) < minlen or minlen < 2:
668 return False
669 if len(string) % 2:
670 return False
671 if maxlen and len(string) > maxlen:
672 return False
673
674 # Try actual encoding to be sure
675 try:
676 try_encode = h2b(string)
677 return True
678 except:
679 return False
680
Harald Welte52255572021-04-03 09:56:32 +0200681def sanitize_pin_adm(pin_adm, pin_adm_hex = None) -> Hexstr:
Philipp Maiere8536c02020-05-11 21:35:01 +0200682 """
683 The ADM pin can be supplied either in its hexadecimal form or as
684 ascii string. This function checks the supplied opts parameter and
Harald Welte236a65f2021-04-03 10:20:11 +0200685 returns the pin_adm as hex encoded string, regardless in which form
Philipp Maiere8536c02020-05-11 21:35:01 +0200686 it was originally supplied by the user
687 """
688
Harald Welte79b5ba42021-01-08 21:22:38 +0100689 if pin_adm is not None:
690 if len(pin_adm) <= 8:
691 pin_adm = ''.join(['%02x'%(ord(x)) for x in pin_adm])
Philipp Maiere8536c02020-05-11 21:35:01 +0200692 pin_adm = rpad(pin_adm, 16)
693
694 else:
695 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
696
Harald Welte79b5ba42021-01-08 21:22:38 +0100697 if pin_adm_hex is not None:
698 if len(pin_adm_hex) == 16:
699 pin_adm = pin_adm_hex
Philipp Maiere8536c02020-05-11 21:35:01 +0200700 # Ensure that it's hex-encoded
701 try:
702 try_encode = h2b(pin_adm)
703 except ValueError:
704 raise ValueError("PIN-ADM needs to be hex encoded using this option")
705 else:
706 raise ValueError("PIN-ADM needs to be exactly 16 digits (hex encoded)")
707
708 return pin_adm
709
Supreeth Herle95ec7722020-03-24 13:09:03 +0100710def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='00'):
711 """
712 Encode ePDGSelection so it can be stored at EF.ePDGSelection or EF.ePDGSelectionEm.
713 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
714
715 Default values:
716 - epdg_priority: '0001' - 1st Priority
717 - epdg_fqdn_format: '00' - Operator Identifier FQDN
718 """
719
720 plmn1 = enc_plmn(mcc, mnc) + epdg_priority + epdg_fqdn_format
721 # TODO: Handle encoding of Length field for length more than 127 Bytes
722 content = '80' + ('%02x' % (len(plmn1)//2)) + plmn1
723 content = rpad(content, len(hexstr))
724 return content
725
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100726def dec_ePDGSelection(sixhexbytes):
727 """
728 Decode ePDGSelection to get EF.ePDGSelection or EF.ePDGSelectionEm.
729 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
730 """
731
732 res = {'mcc': 0, 'mnc': 0, 'epdg_priority': 0, 'epdg_fqdn_format': ''}
733 plmn_chars = 6
734 epdg_priority_chars = 4
735 epdg_fqdn_format_chars = 2
736 # first three bytes (six ascii hex chars)
737 plmn_str = sixhexbytes[:plmn_chars]
738 # two bytes after first three bytes
739 epdg_priority_str = sixhexbytes[plmn_chars:plmn_chars + epdg_priority_chars]
740 # one byte after first five bytes
741 epdg_fqdn_format_str = sixhexbytes[plmn_chars + epdg_priority_chars:plmn_chars + epdg_priority_chars + epdg_fqdn_format_chars]
742 res['mcc'] = dec_mcc_from_plmn(plmn_str)
743 res['mnc'] = dec_mnc_from_plmn(plmn_str)
744 res['epdg_priority'] = epdg_priority_str
745 res['epdg_fqdn_format'] = epdg_fqdn_format_str == '00' and 'Operator Identifier FQDN' or 'Location based FQDN'
746 return res
747
748def format_ePDGSelection(hexstr):
749 ePDGSelection_info_tag_chars = 2
750 ePDGSelection_info_tag_str = hexstr[:2]
herlesupreeth3a261d32021-01-05 09:20:11 +0100751 s = ""
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100752 # Minimum length
753 len_chars = 2
754 # TODO: Need to determine length properly - definite length support only
755 # Inconsistency in spec: 3GPP TS 31.102 version 15.2.0 Release 15, 4.2.104
756 # As per spec, length is 5n, n - number of PLMNs
757 # But, each PLMN entry is made of PLMN (3 Bytes) + ePDG Priority (2 Bytes) + ePDG FQDN format (1 Byte)
758 # Totalling to 6 Bytes, maybe length should be 6n
759 len_str = hexstr[ePDGSelection_info_tag_chars:ePDGSelection_info_tag_chars+len_chars]
herlesupreeth3a261d32021-01-05 09:20:11 +0100760
761 # Not programmed scenario
762 if int(len_str, 16) == 255 or int(ePDGSelection_info_tag_str, 16) == 255:
763 len_chars = 0
764 ePDGSelection_info_tag_chars = 0
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100765 if len_str[0] == '8':
766 # The bits 7 to 1 denotes the number of length octets if length > 127
767 if int(len_str[1]) > 0:
768 # Update number of length octets
769 len_chars = len_chars * int(len_str[1])
770 len_str = hexstr[ePDGSelection_info_tag_chars:len_chars]
771
772 content_str = hexstr[ePDGSelection_info_tag_chars+len_chars:]
773 # Right pad to prevent index out of range - multiple of 6 bytes
774 content_str = rpad(content_str, len(content_str) + (12 - (len(content_str) % 12)))
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100775 for rec_data in hexstr_to_Nbytearr(content_str, 6):
776 rec_info = dec_ePDGSelection(rec_data)
777 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
778 rec_str = "unused"
779 else:
780 rec_str = "MCC: %03d MNC: %03d ePDG Priority: %s ePDG FQDN format: %s" % \
781 (rec_info['mcc'], rec_info['mnc'], rec_info['epdg_priority'], rec_info['epdg_fqdn_format'])
782 s += "\t%s # %s\n" % (rec_data, rec_str)
783 return s
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100784
785def get_addr_type(addr):
786 """
787 Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
788 Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given)
789
790 TODO: Handle IPv6
791 """
792
793 # Empty address string
794 if not len(addr):
795 return None
796
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100797 addr_list = addr.split('.')
798
799 # Check for IPv4/IPv6
800 try:
801 import ipaddress
802 # Throws ValueError if addr is not correct
Denis 'GNUtoo' Carikli79f5b602020-02-15 04:02:57 +0700803 ipa = ipaddress.ip_address(addr)
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100804
805 if ipa.version == 4:
806 return 0x01
807 elif ipa.version == 6:
808 return 0x02
809 except Exception as e:
810 invalid_ipv4 = True
811 for i in addr_list:
812 # Invalid IPv4 may qualify for a valid FQDN, so make check here
813 # e.g. 172.24.15.300
814 import re
815 if not re.match('^[0-9_]+$', i):
816 invalid_ipv4 = False
817 break
818
819 if invalid_ipv4:
820 return None
821
822 fqdn_flag = True
823 for i in addr_list:
824 # Only Alpha-numeric characters and hyphen - RFC 1035
825 import re
826 if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
827 fqdn_flag = False
828 break
829
830 # FQDN
831 if fqdn_flag:
832 return 0x00
833
834 return None
Harald Welte67d551a2021-01-21 14:50:01 +0100835
Harald Welte1e456572021-04-02 17:16:30 +0200836def sw_match(sw:str, pattern:str) -> bool:
Harald Welte67d551a2021-01-21 14:50:01 +0100837 """Match given SW against given pattern."""
838 # Create a masked version of the returned status word
839 sw_lower = sw.lower()
840 sw_masked = ""
841 for i in range(0, 4):
Philipp Maier78e32f22021-03-22 20:39:24 +0100842 if pattern[i] == '?':
Harald Welte67d551a2021-01-21 14:50:01 +0100843 sw_masked = sw_masked + '?'
Philipp Maier78e32f22021-03-22 20:39:24 +0100844 elif pattern[i] == 'x':
Harald Welte67d551a2021-01-21 14:50:01 +0100845 sw_masked = sw_masked + 'x'
846 else:
847 sw_masked = sw_masked + sw_lower[i]
Philipp Maier78e32f22021-03-22 20:39:24 +0100848 # Compare the masked version against the pattern
Harald Welte67d551a2021-01-21 14:50:01 +0100849 return sw_masked == pattern
Philipp Maier5d3e2592021-02-22 17:22:16 +0100850
Harald Welteee3501f2021-04-02 13:00:18 +0200851def tabulate_str_list(str_list, width:int = 79, hspace:int = 2, lspace:int = 1,
Harald Welte52255572021-04-03 09:56:32 +0200852 align_left:bool = True) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +0200853 """Pretty print a list of strings into a tabulated form.
854
855 Args:
856 width : total width in characters per line
857 space : horizontal space between cells
858 lspace : number of spaces before row
859 align_lef : Align text to the left side
860 Returns:
861 multi-line string containing formatted table
862 """
Philipp Maier5d3e2592021-02-22 17:22:16 +0100863 if str_list == None:
864 return ""
865 if len(str_list) <= 0:
866 return ""
867 longest_str = max(str_list, key=len)
868 cellwith = len(longest_str) + hspace
869 cols = width // cellwith
870 rows = (len(str_list) - 1) // cols + 1
871 table = []
872 for i in iter(range(rows)):
873 str_list_row = str_list[i::rows]
874 if (align_left):
875 format_str_cell = '%%-%ds'
876 else:
877 format_str_cell = '%%%ds'
878 format_str_row = (format_str_cell % cellwith) * len(str_list_row)
879 format_str_row = (" " * lspace) + format_str_row
880 table.append(format_str_row % tuple(str_list_row))
881 return '\n'.join(table)
Harald Welte5e749a72021-04-10 17:18:17 +0200882
883class JsonEncoder(json.JSONEncoder):
884 """Extend the standard library JSONEncoder with support for more types."""
885 def default(self, o):
886 if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
887 return b2h(o)
888 return json.JSONEncoder.default(self, o)
Philipp Maier80ce71f2021-04-19 21:24:23 +0200889
890def boxed_heading_str(heading, width=80):
891 """Generate a string that contains a boxed heading."""
892 # Auto-enlarge box if heading exceeds length
893 if len(heading) > width - 4:
894 width = len(heading) + 4
895
896 res = "#" * width
897 fstr = "\n# %-" + str(width - 4) + "s #\n"
898 res += fstr % (heading)
899 res += "#" * width
900 return res