utils: move enc_msisdn and dec_msisdn to legacy/utils.py

We now have a construct based encoder/decoder for the record content
of EF.MSISDN. This means that we do not need the functions enc_msisdn
and dec_msisdn in the non-legacy code anymore. We can now move both
to legacy/utils.py.

Related: OS#5714
Change-Id: I19ec8ba14551ec282fc0cc12ae2f6d528bdfc527
This commit is contained in:
Philipp Maier
2024-09-12 10:27:56 +02:00
parent 1f45799188
commit 93c89856c8
5 changed files with 97 additions and 96 deletions

View File

@@ -42,8 +42,8 @@ from pySim.commands import SimCardCommands
from pySim.transport import init_reader, argparse_add_reader_args from pySim.transport import init_reader, argparse_add_reader_args
from pySim.exceptions import SwMatchError from pySim.exceptions import SwMatchError
from pySim.legacy.cards import card_detect, SimCard, UsimCard, IsimCard from pySim.legacy.cards import card_detect, SimCard, UsimCard, IsimCard
from pySim.utils import dec_imsi, dec_iccid, dec_msisdn from pySim.utils import dec_imsi, dec_iccid
from pySim.legacy.utils import format_xplmn_w_act, dec_st from pySim.legacy.utils import format_xplmn_w_act, dec_st, dec_msisdn
option_parser = argparse.ArgumentParser(description='Legacy tool for reading some parts of a SIM card', option_parser = argparse.ArgumentParser(description='Legacy tool for reading some parts of a SIM card',
formatter_class=argparse.ArgumentDefaultsHelpFormatter) formatter_class=argparse.ArgumentDefaultsHelpFormatter)

View File

@@ -7,11 +7,11 @@ from smartcard.util import toBytes
from pytlv.TLV import * from pytlv.TLV import *
from pySim.cards import SimCardBase, UiccCardBase from pySim.cards import SimCardBase, UiccCardBase
from pySim.utils import dec_iccid, enc_iccid, dec_imsi, enc_imsi, dec_msisdn, enc_msisdn from pySim.utils import dec_iccid, enc_iccid, dec_imsi, enc_imsi
from pySim.utils import enc_plmn, get_addr_type from pySim.utils import enc_plmn, get_addr_type
from pySim.utils import is_hex, h2b, b2h, h2s, s2h, lpad, rpad from pySim.utils import is_hex, h2b, b2h, h2s, s2h, lpad, rpad
from pySim.legacy.utils import enc_ePDGSelection, format_xplmn_w_act, format_xplmn, dec_st, enc_st from pySim.legacy.utils import enc_ePDGSelection, format_xplmn_w_act, format_xplmn, dec_st, enc_st
from pySim.legacy.utils import format_ePDGSelection, dec_addr_tlv, enc_addr_tlv from pySim.legacy.utils import format_ePDGSelection, dec_addr_tlv, enc_addr_tlv, dec_msisdn, enc_msisdn
from pySim.legacy.ts_51_011 import EF, DF from pySim.legacy.ts_51_011 import EF, DF
from pySim.legacy.ts_31_102 import EF_USIM_ADF_map from pySim.legacy.ts_31_102 import EF_USIM_ADF_map
from pySim.legacy.ts_31_103 import EF_ISIM_ADF_map from pySim.legacy.ts_31_103 import EF_ISIM_ADF_map

View File

@@ -20,8 +20,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from typing import Optional, Tuple
from pySim.utils import Hexstr, rpad, enc_plmn, h2i, i2s, s2h from pySim.utils import Hexstr, rpad, enc_plmn, h2i, i2s, s2h
from pySim.utils import dec_xplmn_w_act, dec_xplmn, dec_mcc_from_plmn, dec_mnc_from_plmn from pySim.utils import dec_xplmn_w_act, dec_xplmn, dec_mcc_from_plmn, dec_mnc_from_plmn
from osmocom.utils import swap_nibbles, h2b, b2h
def hexstr_to_Nbytearr(s, nbytes): 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))]
@@ -330,3 +332,82 @@ def enc_addr_tlv(addr, addr_type='00'):
s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str
return s return s
def dec_msisdn(ef_msisdn: Hexstr) -> Optional[Tuple[int, int, Optional[str]]]:
"""
Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
"""
# Convert from str to (kind of) 'bytes'
ef_msisdn = h2b(ef_msisdn)
# Make sure mandatory fields are present
if len(ef_msisdn) < 14:
raise ValueError("EF.MSISDN is too short")
# Skip optional Alpha Identifier
xlen = len(ef_msisdn) - 14
msisdn_lhv = ef_msisdn[xlen:]
# Parse the length (in bytes) of the BCD encoded number
bcd_len = msisdn_lhv[0]
# BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
if bcd_len == 0xff:
return None
elif bcd_len > 11 or bcd_len < 1:
raise ValueError(
"Length of MSISDN (%d bytes) is out of range" % bcd_len)
# Parse ToN / NPI
ton = (msisdn_lhv[1] >> 4) & 0x07
npi = msisdn_lhv[1] & 0x0f
bcd_len -= 1
# No MSISDN?
if not bcd_len:
return (npi, ton, None)
msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
# International number 10.5.118/3GPP TS 24.008
if ton == 0x01:
msisdn = '+' + msisdn
return (npi, ton, msisdn)
def enc_msisdn(msisdn: str, npi: int = 0x01, ton: int = 0x03) -> Hexstr:
"""
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. (The result
will not contain the optional Alpha Identifier at the beginning.)
Default NPI / ToN values:
- NPI: ISDN / telephony numbering plan (E.164 / E.163),
- ToN: network specific or international number (if starts with '+').
"""
# If no MSISDN is supplied then encode the file contents as all "ff"
if msisdn in ["", "+"]:
return "ff" * 14
# Leading '+' indicates International Number
if msisdn[0] == '+':
msisdn = msisdn[1:]
ton = 0x01
# An MSISDN must not exceed 20 digits
if len(msisdn) > 20:
raise ValueError("msisdn must not be longer than 20 digits")
# Append 'f' padding if number of digits is odd
if len(msisdn) % 2 > 0:
msisdn += 'f'
# BCD length also includes NPI/ToN header
bcd_len = len(msisdn) // 2 + 1
npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)

View File

@@ -341,86 +341,6 @@ def derive_mnc(digit1: int, digit2: int, digit3: int = 0x0f) -> int:
return mnc return mnc
def dec_msisdn(ef_msisdn: Hexstr) -> Optional[Tuple[int, int, Optional[str]]]:
"""
Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
"""
# Convert from str to (kind of) 'bytes'
ef_msisdn = h2b(ef_msisdn)
# Make sure mandatory fields are present
if len(ef_msisdn) < 14:
raise ValueError("EF.MSISDN is too short")
# Skip optional Alpha Identifier
xlen = len(ef_msisdn) - 14
msisdn_lhv = ef_msisdn[xlen:]
# Parse the length (in bytes) of the BCD encoded number
bcd_len = msisdn_lhv[0]
# BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
if bcd_len == 0xff:
return None
elif bcd_len > 11 or bcd_len < 1:
raise ValueError(
"Length of MSISDN (%d bytes) is out of range" % bcd_len)
# Parse ToN / NPI
ton = (msisdn_lhv[1] >> 4) & 0x07
npi = msisdn_lhv[1] & 0x0f
bcd_len -= 1
# No MSISDN?
if not bcd_len:
return (npi, ton, None)
msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
# International number 10.5.118/3GPP TS 24.008
if ton == 0x01:
msisdn = '+' + msisdn
return (npi, ton, msisdn)
def enc_msisdn(msisdn: str, npi: int = 0x01, ton: int = 0x03) -> Hexstr:
"""
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. (The result
will not contain the optional Alpha Identifier at the beginning.)
Default NPI / ToN values:
- NPI: ISDN / telephony numbering plan (E.164 / E.163),
- ToN: network specific or international number (if starts with '+').
"""
# If no MSISDN is supplied then encode the file contents as all "ff"
if msisdn in ["", "+"]:
return "ff" * 14
# Leading '+' indicates International Number
if msisdn[0] == '+':
msisdn = msisdn[1:]
ton = 0x01
# An MSISDN must not exceed 20 digits
if len(msisdn) > 20:
raise ValueError("msisdn must not be longer than 20 digits")
# Append 'f' padding if number of digits is odd
if len(msisdn) % 2 > 0:
msisdn += 'f'
# BCD length also includes NPI/ToN header
bcd_len = len(msisdn) // 2 + 1
npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
def sanitize_pin_adm(pin_adm, pin_adm_hex=None) -> Hexstr: 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

View File

@@ -145,31 +145,31 @@ class DecTestCase(unittest.TestCase):
self.assertEqual(encoded.lower(), self.testfile_suci_calc_info.lower()) self.assertEqual(encoded.lower(), self.testfile_suci_calc_info.lower())
def testEnc_msisdn(self): def testEnc_msisdn(self):
msisdn_encoded = utils.enc_msisdn("+4916012345678", npi=0x01, ton=0x03) msisdn_encoded = legacy_utils.enc_msisdn("+4916012345678", npi=0x01, ton=0x03)
self.assertEqual(msisdn_encoded, "0891946110325476f8ffffffffff") self.assertEqual(msisdn_encoded, "0891946110325476f8ffffffffff")
msisdn_encoded = utils.enc_msisdn("123456", npi=0x01, ton=0x03) msisdn_encoded = legacy_utils.enc_msisdn("123456", npi=0x01, ton=0x03)
self.assertEqual(msisdn_encoded, "04b1214365ffffffffffffffffff") self.assertEqual(msisdn_encoded, "04b1214365ffffffffffffffffff")
msisdn_encoded = utils.enc_msisdn("12345678901234567890", npi=0x01, ton=0x03) msisdn_encoded = legacy_utils.enc_msisdn("12345678901234567890", npi=0x01, ton=0x03)
self.assertEqual(msisdn_encoded, "0bb121436587092143658709ffff") self.assertEqual(msisdn_encoded, "0bb121436587092143658709ffff")
msisdn_encoded = utils.enc_msisdn("+12345678901234567890", npi=0x01, ton=0x03) msisdn_encoded = legacy_utils.enc_msisdn("+12345678901234567890", npi=0x01, ton=0x03)
self.assertEqual(msisdn_encoded, "0b9121436587092143658709ffff") self.assertEqual(msisdn_encoded, "0b9121436587092143658709ffff")
msisdn_encoded = utils.enc_msisdn("", npi=0x01, ton=0x03) msisdn_encoded = legacy_utils.enc_msisdn("", npi=0x01, ton=0x03)
self.assertEqual(msisdn_encoded, "ffffffffffffffffffffffffffff") self.assertEqual(msisdn_encoded, "ffffffffffffffffffffffffffff")
msisdn_encoded = utils.enc_msisdn("+", npi=0x01, ton=0x03) msisdn_encoded = legacy_utils.enc_msisdn("+", npi=0x01, ton=0x03)
self.assertEqual(msisdn_encoded, "ffffffffffffffffffffffffffff") self.assertEqual(msisdn_encoded, "ffffffffffffffffffffffffffff")
def testDec_msisdn(self): def testDec_msisdn(self):
msisdn_decoded = utils.dec_msisdn("0891946110325476f8ffffffffff") msisdn_decoded = legacy_utils.dec_msisdn("0891946110325476f8ffffffffff")
self.assertEqual(msisdn_decoded, (1, 1, "+4916012345678")) self.assertEqual(msisdn_decoded, (1, 1, "+4916012345678"))
msisdn_decoded = utils.dec_msisdn("04b1214365ffffffffffffffffff") msisdn_decoded = legacy_utils.dec_msisdn("04b1214365ffffffffffffffffff")
self.assertEqual(msisdn_decoded, (1, 3, "123456")) self.assertEqual(msisdn_decoded, (1, 3, "123456"))
msisdn_decoded = utils.dec_msisdn("0bb121436587092143658709ffff") msisdn_decoded = legacy_utils.dec_msisdn("0bb121436587092143658709ffff")
self.assertEqual(msisdn_decoded, (1, 3, "12345678901234567890")) self.assertEqual(msisdn_decoded, (1, 3, "12345678901234567890"))
msisdn_decoded = utils.dec_msisdn("ffffffffffffffffffffffffffff") msisdn_decoded = legacy_utils.dec_msisdn("ffffffffffffffffffffffffffff")
self.assertEqual(msisdn_decoded, None) self.assertEqual(msisdn_decoded, None)
msisdn_decoded = utils.dec_msisdn("00112233445566778899AABBCCDDEEFF001122330bb121436587092143658709ffff") msisdn_decoded = legacy_utils.dec_msisdn("00112233445566778899AABBCCDDEEFF001122330bb121436587092143658709ffff")
self.assertEqual(msisdn_decoded, (1, 3, "12345678901234567890")) self.assertEqual(msisdn_decoded, (1, 3, "12345678901234567890"))
msisdn_decoded = utils.dec_msisdn("ffffffffffffffffffffffffffffffffffffffff0bb121436587092143658709ffff") msisdn_decoded = legacy_utils.dec_msisdn("ffffffffffffffffffffffffffffffffffffffff0bb121436587092143658709ffff")
self.assertEqual(msisdn_decoded, (1, 3, "12345678901234567890")) self.assertEqual(msisdn_decoded, (1, 3, "12345678901234567890"))
class TestLuhn(unittest.TestCase): class TestLuhn(unittest.TestCase):