forked from public/pysim
cat: properly decode IMEI/IMEISV IE
Currently BcdAdapter(GreedyBytes), i.e. a plain swapped nibble digit string, which is wrong, the IMEI IE 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
This commit is contained in:
+56
-3
@@ -22,7 +22,7 @@ from typing import List
|
|||||||
from bidict import bidict
|
from bidict import bidict
|
||||||
from construct import Int8ub, Int16ub, Byte, BitsInteger
|
from construct import Int8ub, Int16ub, Byte, BitsInteger
|
||||||
from construct import Struct, Enum, BitStruct, this
|
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.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.construct import PlmnAdapter, BcdAdapter, GsmStringAdapter, TonNpi, GsmString, Bytes, GreedyBytes
|
||||||
from osmocom.utils import b2h, h2b
|
from osmocom.utils import b2h, h2b
|
||||||
@@ -320,9 +320,54 @@ class FileList(COMPR_TLV_IE, tag=0x92):
|
|||||||
class LocationInformation(COMPR_TLV_IE, tag=0x93):
|
class LocationInformation(COMPR_TLV_IE, tag=0x93):
|
||||||
pass
|
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 ''
|
||||||
|
# TS 24.008 figure 10.5.4: octet 3 holds type of identity (b1-3), odd/even (b4) and
|
||||||
|
# digit 1 in its high nibble, the remaining digits follow BCD swapped from octet 4
|
||||||
|
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):
|
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
|
# TS 102 223 Section 8.21
|
||||||
class HelpRequest(COMPR_TLV_IE, tag=0x95):
|
class HelpRequest(COMPR_TLV_IE, tag=0x95):
|
||||||
@@ -596,6 +641,14 @@ class UtranEutranMeasurementQualifier(COMPR_TLV_IE, tag=0xE9):
|
|||||||
eutran_inter_rat_utran=0x08,
|
eutran_inter_rat_utran=0x08,
|
||||||
eutran_inter_rat_nr=0x09)
|
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
|
# TS 102 223 Section 8.75
|
||||||
class NetworkSearchMode(COMPR_TLV_IE, tag=0xE5):
|
class NetworkSearchMode(COMPR_TLV_IE, tag=0xE5):
|
||||||
_construct = Enum(Int8ub, manual=0, automatic=1)
|
_construct = Enum(Int8ub, manual=0, automatic=1)
|
||||||
|
|||||||
@@ -26,7 +26,66 @@ import unittest
|
|||||||
|
|
||||||
from osmocom.utils import b2h, h2b
|
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):
|
class SupportedRadioAccessTechnologies_Test(unittest.TestCase):
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import logging
|
|||||||
from osmocom.utils import b2h, h2b, all_subclasses
|
from osmocom.utils import b2h, h2b, all_subclasses
|
||||||
from osmocom.tlv import *
|
from osmocom.tlv import *
|
||||||
|
|
||||||
|
import pySim.cat
|
||||||
import pySim.iso7816_4
|
import pySim.iso7816_4
|
||||||
import pySim.ts_102_221
|
import pySim.ts_102_221
|
||||||
import pySim.ts_102_222
|
import pySim.ts_102_222
|
||||||
|
|||||||
Reference in New Issue
Block a user