laforge submitted this change.

View Change

Approvals: Jenkins Builder: Verified laforge: Looks good to me, approved
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(-)

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):

To view, visit change 38118. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: merged
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I19ec8ba14551ec282fc0cc12ae2f6d528bdfc527
Gerrit-Change-Number: 38118
Gerrit-PatchSet: 13
Gerrit-Owner: dexter <pmaier@sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge@osmocom.org>