laforge has submitted this change. ( https://gerrit.osmocom.org/c/pysim/+/38118?usp=email )
Change subject: utils: move enc_msisdn and dec_msisdn to legacy/utils.py ......................................................................
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 --- M pySim-read.py M pySim/legacy/cards.py M pySim/legacy/utils.py M pySim/utils.py M tests/unittests/test_utils.py 5 files changed, 97 insertions(+), 96 deletions(-)
Approvals: Jenkins Builder: Verified laforge: Looks good to me, approved
diff --git a/pySim-read.py b/pySim-read.py index aae5ad4..4ede6dd 100755 --- a/pySim-read.py +++ b/pySim-read.py @@ -42,8 +42,8 @@ from pySim.transport import init_reader, argparse_add_reader_args from pySim.exceptions import SwMatchError from pySim.legacy.cards import card_detect, SimCard, UsimCard, IsimCard -from pySim.utils import dec_imsi, dec_iccid, dec_msisdn -from pySim.legacy.utils import format_xplmn_w_act, dec_st +from pySim.utils import dec_imsi, dec_iccid +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', formatter_class=argparse.ArgumentDefaultsHelpFormatter) diff --git a/pySim/legacy/cards.py b/pySim/legacy/cards.py index 5e64e95..1f5db8a 100644 --- a/pySim/legacy/cards.py +++ b/pySim/legacy/cards.py @@ -7,11 +7,11 @@ from pytlv.TLV import *
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 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 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_31_102 import EF_USIM_ADF_map from pySim.legacy.ts_31_103 import EF_ISIM_ADF_map diff --git a/pySim/legacy/utils.py b/pySim/legacy/utils.py index cf2416e..4bfd061 100644 --- a/pySim/legacy/utils.py +++ b/pySim/legacy/utils.py @@ -20,8 +20,10 @@ # 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 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): return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2))] @@ -330,3 +332,82 @@ s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str
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) diff --git a/pySim/utils.py b/pySim/utils.py index 05ad962..c706b34 100644 --- a/pySim/utils.py +++ b/pySim/utils.py @@ -341,86 +341,6 @@
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: """ The ADM pin can be supplied either in its hexadecimal form or as diff --git a/tests/unittests/test_utils.py b/tests/unittests/test_utils.py index 0e61a61..30a01d2 100755 --- a/tests/unittests/test_utils.py +++ b/tests/unittests/test_utils.py @@ -145,31 +145,31 @@ self.assertEqual(encoded.lower(), self.testfile_suci_calc_info.lower())
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") - 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") - 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") - 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") - msisdn_encoded = utils.enc_msisdn("", npi=0x01, ton=0x03) + msisdn_encoded = legacy_utils.enc_msisdn("", npi=0x01, ton=0x03) 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")
def testDec_msisdn(self): - msisdn_decoded = utils.dec_msisdn("0891946110325476f8ffffffffff") + msisdn_decoded = legacy_utils.dec_msisdn("0891946110325476f8ffffffffff") 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")) - msisdn_decoded = utils.dec_msisdn("0bb121436587092143658709ffff") + msisdn_decoded = legacy_utils.dec_msisdn("0bb121436587092143658709ffff") 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) - msisdn_decoded = utils.dec_msisdn("00112233445566778899AABBCCDDEEFF001122330bb121436587092143658709ffff") + msisdn_decoded = legacy_utils.dec_msisdn("00112233445566778899AABBCCDDEEFF001122330bb121436587092143658709ffff") 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"))
class TestLuhn(unittest.TestCase):