Ben I has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/41913?usp=email )
Change subject: Decode GSM 7-bit packed EF.PNN data ......................................................................
Decode GSM 7-bit packed EF.PNN data
Change-Id: I4558fd011592aeeae2389fe8a1f62f3d7e21d219 --- M pySim/ts_51_011.py M pySim/utils.py 2 files changed, 88 insertions(+), 7 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/13/41913/1
diff --git a/pySim/ts_51_011.py b/pySim/ts_51_011.py index 70c38b1..a71e2c2 100644 --- a/pySim/ts_51_011.py +++ b/pySim/ts_51_011.py @@ -40,7 +40,7 @@ from osmocom.construct import *
from pySim.utils import dec_iccid, enc_iccid, dec_imsi, enc_imsi, dec_plmn, enc_plmn, dec_xplmn_w_act -from pySim.utils import bytes_for_nibbles +from pySim.utils import bytes_for_nibbles, gsm7_unpack, gsm7_pack from pySim.profile import CardProfile, CardProfileAddon from pySim.filesystem import * from pySim.ts_31_102_telecom import DF_PHONEBOOK, DF_MULTIMEDIA, DF_MCS, DF_V2X @@ -884,19 +884,56 @@ 'cap_conf_id'/Int8ub, ext_name/Int8ub)
+ +class NetworkNameAdapter(Adapter): + """Adapter for Network Name IE per TS 24.008 Section 10.5.3.5a.""" + + def _decode(self, obj, context, path): + if len(obj) < 1: + return '' + header = obj[0] + coding_scheme = (header >> 6) & 0x01 # bit 7: 0=GSM7, 1=UCS2 + spare_bits = header & 0x07 # bits 3-1 + data = obj[1:] + if not data: + return '' + if coding_scheme == 0: + # GSM 7-bit packed + unpacked = gsm7_unpack(data, spare_bits) + return unpacked.decode('gsm03.38') + else: + # UCS-2 (UTF-16 BE) + return data.decode('utf_16_be') + + def _encode(self, obj, context, path): + if not obj: + return b'\x80' # Empty with GSM7 coding + # Try GSM 7-bit first + try: + unpacked = obj.encode('gsm03.38') + packed, spare_bits = gsm7_pack(unpacked) + header = 0x80 | spare_bits # ext=1, coding=0, ci=0 + return bytes([header]) + packed + except UnicodeEncodeError: + # Fall back to UCS-2 + encoded = obj.encode('utf_16_be') + header = 0x80 | 0x40 # ext=1, coding=1 + return bytes([header]) + encoded + + # TS 51.011 Section 4.2.58 class EF_PNN(LinFixedEF): - # TODO: 430a82d432bbbc7eb75de432450a82d432bbbc7eb75de432ffffffff - # TODO: 430a82c596b34cbfbfe5eb39ffffffffffffffffffffffffffffffffffff + _test_de_encode = [ + ('430584c330bc0c', [{'full_name_for_network': 'Cape'}]), + ] + class FullNameForNetwork(BER_TLV_IE, tag=0x43): # TS 24.008 10.5.3.5a - # TODO: proper decode - _construct = GreedyBytes + _construct = NetworkNameAdapter(GreedyBytes)
class ShortNameForNetwork(BER_TLV_IE, tag=0x45): # TS 24.008 10.5.3.5a - # TODO: proper decode - _construct = GreedyBytes + _construct = NetworkNameAdapter(GreedyBytes)
class NetworkNameCollection(TLV_IE_Collection, nested=[FullNameForNetwork, ShortNameForNetwork]): pass diff --git a/pySim/utils.py b/pySim/utils.py index 7d8e3fa..2ec5bd0 100644 --- a/pySim/utils.py +++ b/pySim/utils.py @@ -78,6 +78,50 @@ return imsi
+def gsm7_unpack(packed_data: bytes, num_spare_bits: int = 0) -> bytes: + """Unpack GSM 7-bit packed data to unpacked bytes (one septet per byte). + + Args: + packed_data: The packed 7-bit data bytes + num_spare_bits: Number of spare bits in the last octet (0-6) + + Returns: + Unpacked bytes where each byte contains one 7-bit character value + """ + if not packed_data: + return b'' + packed_int = int.from_bytes(packed_data, byteorder='little') + num_bits = len(packed_data) * 8 - num_spare_bits + num_septets = num_bits // 7 + unpacked = bytearray() + for i in range(num_septets): + septet = (packed_int >> (i * 7)) & 0x7f + unpacked.append(septet) + return bytes(unpacked) + + +def gsm7_pack(unpacked_data: bytes) -> Tuple[bytes, int]: + """Pack unpacked GSM 7-bit data into packed format. + + Args: + unpacked_data: Unpacked bytes (one septet per byte, high bit ignored) + + Returns: + Tuple of (packed_data, num_spare_bits) + """ + if not unpacked_data: + return b'', 0 + num_septets = len(unpacked_data) + num_bits = num_septets * 7 + num_bytes = (num_bits + 7) // 8 + num_spare_bits = num_bytes * 8 - num_bits + packed_int = 0 + for i, septet in enumerate(unpacked_data): + packed_int |= (septet & 0x7f) << (i * 7) + packed = packed_int.to_bytes(num_bytes, byteorder='little') + return packed, num_spare_bits + + def dec_iccid(ef: Hexstr) -> str: return swap_nibbles(ef).strip('f')