Hoernchen has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/43561?usp=email )
Change subject: cat: properly decode IMEI/IMEISV IE ......................................................................
cat: properly decode IMEI/IMEISV IE
Currently BcdAdapter(GreedyBytes), a plain swapped nibble digit string, which is wrong, it is not a bcd string. See TS 124.008 10.5.1.4 figure 10.5.4 + table 10.5.4
It currently - decodes framing nibble as digit - encodes a 15 digit IMEI as 7 bytes, wrong len, dropped last digit
Fix this, while at it add the 8.74 IMEISV as well + tests for the framing because the current round trip missed all of this.
Change-Id: Iad7aea77513d413c338edc18ef1bcc37488ae849 --- M pySim/cat.py M tests/unittests/test_cat.py M tests/unittests/test_tlvs.py 3 files changed, 115 insertions(+), 4 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/61/43561/1
diff --git a/pySim/cat.py b/pySim/cat.py index 991dd40..8e072d8 100644 --- a/pySim/cat.py +++ b/pySim/cat.py @@ -22,7 +22,7 @@ from bidict import bidict from construct import Int8ub, Int16ub, Byte, BitsInteger from construct import Struct, Enum, BitStruct, this -from construct import Switch, GreedyRange, FlagsEnum +from construct import Switch, GreedyRange, FlagsEnum, Adapter from osmocom.tlv import TLV_IE, COMPR_TLV_IE, BER_TLV_IE, TLV_IE_Collection from osmocom.construct import PlmnAdapter, BcdAdapter, GsmStringAdapter, TonNpi, GsmString, Bytes, GreedyBytes from osmocom.utils import b2h, h2b @@ -320,9 +320,52 @@ class LocationInformation(COMPR_TLV_IE, tag=0x93): pass
-# TS 102 223 Section 8.20 +class MobileIdentityAdapter(Adapter): + """TS 124.008 section 10.5.1.4 figure 10.5.4 + table 10.5.4 + + NOT a plain BCD string: + - bits 1-3 type of identity + odd/even bit 4 + - digit 1 in bits 5-8, following octets contain 2 digits, low nibble first + - if even length: high nibble of last octet 1111 + So IMEI IE of 8 bytes is 15 digits + framing nibble.""" + + # Table 10.5.4 bits 321 + TYPE_IMSI = 1 + TYPE_IMEI = 2 + TYPE_IMEISV = 3 + + def __init__(self, subcon, type_of_identity: int): + super().__init__(subcon) + self.type_of_identity = type_of_identity + + def _decode(self, obj, context, path): + data = bytes(obj) + if not data: + return '' + odd = bool(data[0] & 0x08) # bit 4: 1 = odd number of digits + digits = '%x' % (data[0] >> 4) # bits 5-8: digit 1 + for octet in data[1:]: + digits += '%x%x' % (octet & 0x0f, octet >> 4) + if not odd: + digits = digits[:-1] # drop the 1111 end mark + return digits + + def _encode(self, obj, context, path): + digits = str(obj) + odd = len(digits) % 2 + first = (int(digits[0], 16) << 4) | (0x08 if odd else 0x00) | self.type_of_identity + rest = digits[1:] if odd else digits[1:] + 'f' + return bytes([first]) + bytes((int(rest[i+1], 16) << 4) | int(rest[i], 16) + for i in range(0, len(rest), 2)) + +# TS 102 223 Section 8.20, len is fixed at 8: "The IMEI is coded [..] as the +# value part of the Mobile Identity IE as specified in TS 124 008", and the +# IMEI itself is the 15 digits of TS 123 003. class IMEI(COMPR_TLV_IE, tag=0x94): - _construct = BcdAdapter(GreedyBytes) + _test_de_encode = [ + ( '94081a32547698103254', '123456789012345' ), + ] + _construct = MobileIdentityAdapter(GreedyBytes, MobileIdentityAdapter.TYPE_IMEI)
# TS 102 223 Section 8.21 class HelpRequest(COMPR_TLV_IE, tag=0x95): @@ -596,6 +639,14 @@ eutran_inter_rat_utran=0x08, eutran_inter_rat_nr=0x09)
+# TS 102 223 Section 8.74, length is not fixed, because IMEISV is 16 digits per TS 123.003 +# -> even count needs the '1111' end mark and is 9 bytes long +class IMEISV(COMPR_TLV_IE, tag=0xE2): + _test_de_encode = [ + ( 'e2091332547698103254f6', '1234567890123456' ), + ] + _construct = MobileIdentityAdapter(GreedyBytes, MobileIdentityAdapter.TYPE_IMEISV) + # TS 102 223 Section 8.75 class NetworkSearchMode(COMPR_TLV_IE, tag=0xE5): _construct = Enum(Int8ub, manual=0, automatic=1) diff --git a/tests/unittests/test_cat.py b/tests/unittests/test_cat.py index 4e0450c..c6324c2 100644 --- a/tests/unittests/test_cat.py +++ b/tests/unittests/test_cat.py @@ -25,7 +25,66 @@
from osmocom.utils import b2h, h2b
-from pySim.cat import SupportedRadioAccessTechnologies +from pySim.cat import IMEI, IMEISV, AccessTechnology, SupportedRadioAccessTechnologies + + +class IMEI_Test(unittest.TestCase): + """TS 102 223 8.20: the IMEI IE is 8 bytes, coded as valie part of Mobile Identity IE from 124 008""" + + IMEI_15 = '123456789012345' + ENCODED = '94081a32547698103254' + + def test_encode_is_eight_bytes(self): + """15 digits in 8 byte: 16 nibbles, one is type/parity framing.""" + tlv = IMEI(decoded=self.IMEI_15).to_tlv() + self.assertEqual(b2h(tlv), self.ENCODED) + self.assertEqual(tlv[1], 0x08) # spec len 8 + self.assertEqual(len(tlv) - 2, 8) + + def test_first_octet_framing(self): + """TS 24.008 table 10.5.4""" + octet1 = IMEI(decoded=self.IMEI_15).to_tlv()[2] + self.assertEqual(octet1 & 0x07, 2) # IMEI + self.assertEqual((octet1 >> 3) & 0x01, 1) # odd + self.assertEqual(octet1 >> 4, 1) # digit 1 + + def test_decodes_to_the_raw_imei(self): + """strip framing nibble""" + ie = IMEI() + ie.from_tlv(h2b(self.ENCODED)) + self.assertEqual(ie.decoded, self.IMEI_15) + + def test_even_digit_count_uses_the_end_mark(self): + """"end marker, IMEISV case""" + ie = IMEI(decoded='1234567890123456') + tlv = ie.to_tlv() + self.assertEqual(tlv[2] >> 3 & 0x01, 0) # even + self.assertEqual(tlv[-1] >> 4, 0x0f) # end mark + back = IMEI() + back.from_tlv(tlv) + self.assertEqual(back.decoded, '1234567890123456') + + +class IMEISV_Test(unittest.TestCase): + """TS 102 223 8.74, no fixed len, end marker""" + + IMEISV_16 = '1234567890123456' + ENCODED = 'e2091332547698103254f6' + + def test_encode(self): + self.assertEqual(b2h(IMEISV(decoded=self.IMEISV_16).to_tlv()), self.ENCODED) + + def test_type_of_identity_and_end_mark(self): + value = IMEISV(decoded=self.IMEISV_16).to_tlv()[2:] + self.assertEqual(value[0] & 0x07, 3) # IMEISV + self.assertEqual((value[0] >> 3) & 0x01, 0) # even + self.assertEqual(value[-1] >> 4, 0x0f) # end mark + self.assertEqual(len(value), 9) + + def test_decode(self): + ie = IMEISV() + ie.from_tlv(h2b(self.ENCODED)) + self.assertEqual(ie.decoded, self.IMEISV_16)
class SupportedRadioAccessTechnologies_Test(unittest.TestCase): diff --git a/tests/unittests/test_tlvs.py b/tests/unittests/test_tlvs.py index 7f1b72b..71eaa05 100755 --- a/tests/unittests/test_tlvs.py +++ b/tests/unittests/test_tlvs.py @@ -21,6 +21,7 @@ from osmocom.utils import b2h, h2b, all_subclasses from osmocom.tlv import *
+import pySim.cat import pySim.iso7816_4 import pySim.ts_102_221 import pySim.ts_102_222