utils.py: Add more type annotations

Change-Id: I50a0a07132890af0817f4ff0ce9fec53b7512522
This commit is contained in:
Harald Welte 2021-04-03 09:56:32 +02:00
parent 6e0458dda6
commit 522555710b
1 changed files with 33 additions and 30 deletions

View File

@ -3,7 +3,8 @@
""" pySim: various utilities """ pySim: various utilities
""" """
# from typing import Optional, List, Dict, Any, Tuple
# Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com> # Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
@ -20,40 +21,42 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
# just to differentiate strings of hex nibbles from everything else
Hexstr = str
def h2b(s: str) -> bytearray: def h2b(s:Hexstr) -> bytearray:
"""convert from a string of hex nibbles to a sequence of bytes""" """convert from a string of hex nibbles to a sequence of bytes"""
return bytearray.fromhex(s) return bytearray.fromhex(s)
def b2h(b: bytearray) -> str: def b2h(b:bytearray) -> Hexstr:
"""convert from a sequence of bytes to a string of hex nibbles""" """convert from a sequence of bytes to a string of hex nibbles"""
return ''.join(['%02x'%(x) for x in b]) return ''.join(['%02x'%(x) for x in b])
def h2i(s:str): def h2i(s:Hexstr) -> List[int]:
"""convert from a string of hex nibbles to a list of integers""" """convert from a string of hex nibbles to a list of integers"""
return [(int(x,16)<<4)+int(y,16) for x,y in zip(s[0::2], s[1::2])] return [(int(x,16)<<4)+int(y,16) for x,y in zip(s[0::2], s[1::2])]
def i2h(s) -> str: def i2h(s:List[int]) -> Hexstr:
"""convert from a list of integers to a string of hex nibbles""" """convert from a list of integers to a string of hex nibbles"""
return ''.join(['%02x'%(x) for x in s]) return ''.join(['%02x'%(x) for x in s])
def h2s(s:str) -> str: def h2s(s:Hexstr) -> str:
"""convert from a string of hex nibbles to an ASCII string""" """convert from a string of hex nibbles to an ASCII string"""
return ''.join([chr((int(x,16)<<4)+int(y,16)) for x,y in zip(s[0::2], s[1::2]) return ''.join([chr((int(x,16)<<4)+int(y,16)) for x,y in zip(s[0::2], s[1::2])
if int(x + y, 16) != 0xff]) if int(x + y, 16) != 0xff])
def s2h(s:str) -> str: def s2h(s:str) -> Hexstr:
"""convert from an ASCII string to a string of hex nibbles""" """convert from an ASCII string to a string of hex nibbles"""
b = bytearray() b = bytearray()
b.extend(map(ord, s)) b.extend(map(ord, s))
return b2h(b) return b2h(b)
# List of bytes to string # List of bytes to string
def i2s(s) -> str: def i2s(s:List[int]) -> str:
"""convert from a list of integers to an ASCII string""" """convert from a list of integers to an ASCII string"""
return ''.join([chr(x) for x in s]) return ''.join([chr(x) for x in s])
def swap_nibbles(s:str) -> str: def swap_nibbles(s:Hexstr) -> Hexstr:
"""swap the nibbles in a hex string""" """swap the nibbles in a hex string"""
return ''.join([x+y for x,y in zip(s[1::2], s[0::2])]) return ''.join([x+y for x,y in zip(s[1::2], s[0::2])])
@ -104,7 +107,7 @@ def enc_imsi(imsi:str):
ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe<<3)|1, rpad(imsi, 15))) ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe<<3)|1, rpad(imsi, 15)))
return ei return ei
def dec_imsi(ef): def dec_imsi(ef:Hexstr) -> Optional[str]:
"""Converts an EF value to the imsi string representation""" """Converts an EF value to the imsi string representation"""
if len(ef) < 4: if len(ef) < 4:
return None return None
@ -122,10 +125,10 @@ def dec_imsi(ef):
imsi = swapped[1:] imsi = swapped[1:]
return imsi return imsi
def dec_iccid(ef): def dec_iccid(ef:Hexstr) -> str:
return swap_nibbles(ef).strip('f') return swap_nibbles(ef).strip('f')
def enc_iccid(iccid): def enc_iccid(iccid:str) -> Hexstr:
return swap_nibbles(rpad(iccid, 20)) return swap_nibbles(rpad(iccid, 20))
def enc_plmn(mcc, mnc): def enc_plmn(mcc, mnc):
@ -151,7 +154,7 @@ def hexstr_to_Nbytearr(s, nbytes):
return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2)) ] return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2)) ]
# Accepts hex string representing three bytes # Accepts hex string representing three bytes
def dec_mcc_from_plmn(plmn): def dec_mcc_from_plmn(plmn:Hexstr) -> int:
ia = h2i(plmn) ia = h2i(plmn)
digit1 = ia[0] & 0x0F # 1st byte, LSB digit1 = ia[0] & 0x0F # 1st byte, LSB
digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB
@ -160,7 +163,7 @@ def dec_mcc_from_plmn(plmn):
return 0xFFF # 4095 return 0xFFF # 4095
return derive_mcc(digit1, digit2, digit3) return derive_mcc(digit1, digit2, digit3)
def dec_mnc_from_plmn(plmn): def dec_mnc_from_plmn(plmn:Hexstr) -> int:
ia = h2i(plmn) ia = h2i(plmn)
digit1 = ia[2] & 0x0F # 3rd byte, LSB digit1 = ia[2] & 0x0F # 3rd byte, LSB
digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB
@ -169,7 +172,7 @@ def dec_mnc_from_plmn(plmn):
return 0xFFF # 4095 return 0xFFF # 4095
return derive_mnc(digit1, digit2, digit3) return derive_mnc(digit1, digit2, digit3)
def dec_act(twohexbytes): def dec_act(twohexbytes:Hexstr) -> List[str]:
act_list = [ act_list = [
{'bit': 15, 'name': "UTRAN"}, {'bit': 15, 'name': "UTRAN"},
{'bit': 14, 'name': "E-UTRAN"}, {'bit': 14, 'name': "E-UTRAN"},
@ -186,7 +189,7 @@ def dec_act(twohexbytes):
sel.append(a['name']) sel.append(a['name'])
return sel return sel
def dec_xplmn_w_act(fivehexbytes): def dec_xplmn_w_act(fivehexbytes:Hexstr) -> Dict[str,Any]:
res = {'mcc': 0, 'mnc': 0, 'act': []} res = {'mcc': 0, 'mnc': 0, 'act': []}
plmn_chars = 6 plmn_chars = 6
act_chars = 4 act_chars = 4
@ -238,7 +241,7 @@ def dec_epsloci(hexstr):
res['status'] = h2i(hexstr[34:36]) res['status'] = h2i(hexstr[34:36])
return res return res
def dec_xplmn(threehexbytes): def dec_xplmn(threehexbytes:Hexstr) -> dict:
res = {'mcc': 0, 'mnc': 0, 'act': []} res = {'mcc': 0, 'mnc': 0, 'act': []}
plmn_chars = 6 plmn_chars = 6
plmn_str = threehexbytes[:plmn_chars] # first three bytes (six ascii hex chars) plmn_str = threehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
@ -246,7 +249,7 @@ def dec_xplmn(threehexbytes):
res['mnc'] = dec_mnc_from_plmn(plmn_str) res['mnc'] = dec_mnc_from_plmn(plmn_str)
return res return res
def format_xplmn(hexstr): def format_xplmn(hexstr:Hexstr) -> str:
s = "" s = ""
for rec_data in hexstr_to_Nbytearr(hexstr, 3): for rec_data in hexstr_to_Nbytearr(hexstr, 3):
rec_info = dec_xplmn(rec_data) rec_info = dec_xplmn(rec_data)
@ -257,7 +260,7 @@ def format_xplmn(hexstr):
s += "\t%s # %s\n" % (rec_data, rec_str) s += "\t%s # %s\n" % (rec_data, rec_str)
return s return s
def derive_milenage_opc(ki_hex, op_hex): def derive_milenage_opc(ki_hex:Hexstr, op_hex:Hexstr) -> Hexstr:
""" """
Run the milenage algorithm to calculate OPC from Ki and OP Run the milenage algorithm to calculate OPC from Ki and OP
""" """
@ -272,7 +275,7 @@ def derive_milenage_opc(ki_hex, op_hex):
opc_bytes = aes.encrypt(op_bytes) opc_bytes = aes.encrypt(op_bytes)
return b2h(strxor(opc_bytes, op_bytes)) return b2h(strxor(opc_bytes, op_bytes))
def calculate_luhn(cc): def calculate_luhn(cc) -> int:
""" """
Calculate Luhn checksum used in e.g. ICCID and IMEI Calculate Luhn checksum used in e.g. ICCID and IMEI
""" """
@ -280,7 +283,7 @@ def calculate_luhn(cc):
check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10
return 0 if check_digit == 10 else check_digit return 0 if check_digit == 10 else check_digit
def mcc_from_imsi(imsi): def mcc_from_imsi(imsi:str) -> Optional[str]:
""" """
Derive the MCC (Mobile Country Code) from the first three digits of an IMSI Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
""" """
@ -292,7 +295,7 @@ def mcc_from_imsi(imsi):
else: else:
return None return None
def mnc_from_imsi(imsi, long=False): def mnc_from_imsi(imsi:str, long:bool=False) -> Optional[str]:
""" """
Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
""" """
@ -307,7 +310,7 @@ def mnc_from_imsi(imsi, long=False):
else: else:
return None return None
def derive_mcc(digit1, digit2, digit3): def derive_mcc(digit1:int, digit2:int, digit3:int) -> int:
""" """
Derive decimal representation of the MCC (Mobile Country Code) Derive decimal representation of the MCC (Mobile Country Code)
from three given digits. from three given digits.
@ -324,7 +327,7 @@ def derive_mcc(digit1, digit2, digit3):
return mcc return mcc
def derive_mnc(digit1, digit2, digit3=0x0f): def derive_mnc(digit1:int, digit2:int, digit3:int=0x0f) -> int:
""" """
Derive decimal representation of the MNC (Mobile Network Code) Derive decimal representation of the MNC (Mobile Network Code)
from two or (optionally) three given digits. from two or (optionally) three given digits.
@ -344,7 +347,7 @@ def derive_mnc(digit1, digit2, digit3=0x0f):
return mnc return mnc
def dec_msisdn(ef_msisdn): def dec_msisdn(ef_msisdn:Hexstr) -> Optional[Tuple[int,int,Optional[str]]]:
""" """
Decode MSISDN from EF.MSISDN or EF.ADN (same structure). Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
@ -385,7 +388,7 @@ def dec_msisdn(ef_msisdn):
return (npi, ton, msisdn) return (npi, ton, msisdn)
def enc_msisdn(msisdn, npi=0x01, ton=0x03): def enc_msisdn(msisdn:str, npi:int=0x01, ton:int=0x03) -> Hexstr:
""" """
Encode MSISDN as LHV so it can be stored to EF.MSISDN. Encode MSISDN as LHV so it can be stored to EF.MSISDN.
See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
@ -411,7 +414,7 @@ def enc_msisdn(msisdn, npi=0x01, ton=0x03):
return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd
def dec_st(st, table="sim"): def dec_st(st, table="sim") -> str:
""" """
Parses the EF S/U/IST and prints the list of available services in EF S/U/IST Parses the EF S/U/IST and prints the list of available services in EF S/U/IST
""" """
@ -599,7 +602,7 @@ def enc_addr_tlv(addr, addr_type='00'):
return s return s
def is_hex(string, minlen=2, maxlen=None) -> bool: def is_hex(string:str, minlen:int=2, maxlen:Optional[int]=None) -> bool:
""" """
Check if a string is a valid hexstring Check if a string is a valid hexstring
""" """
@ -621,7 +624,7 @@ def is_hex(string, minlen=2, maxlen=None) -> bool:
except: except:
return False return False
def sanitize_pin_adm(pin_adm, pin_adm_hex = None): def sanitize_pin_adm(pin_adm, pin_adm_hex = None) -> Hexstr:
""" """
The ADM pin can be supplied either in its hexadecimal form or as The ADM pin can be supplied either in its hexadecimal form or as
ascii string. This function checks the supplied opts parameter and ascii string. This function checks the supplied opts parameter and
@ -792,7 +795,7 @@ def sw_match(sw:str, pattern:str) -> bool:
return sw_masked == pattern return sw_masked == pattern
def tabulate_str_list(str_list, width:int = 79, hspace:int = 2, lspace:int = 1, def tabulate_str_list(str_list, width:int = 79, hspace:int = 2, lspace:int = 1,
align_left:bool = True): align_left:bool = True) -> str:
"""Pretty print a list of strings into a tabulated form. """Pretty print a list of strings into a tabulated form.
Args: Args: