Ben I has uploaded this change for review.

View Change

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


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

Gerrit-MessageType: newchange
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I4558fd011592aeeae2389fe8a1f62f3d7e21d219
Gerrit-Change-Number: 41913
Gerrit-PatchSet: 1
Gerrit-Owner: Ben I <biofel@cape.co>