Add codecs for EF_SPN and GSM strings via construct

This will replace the hand-crafted codec for EF_SPN
by a struct definition using the construct library.
Old encoders are updated and kept for API compatibility
but are not used internally anymore.

New data structures:
* Rpad(Adapter): Right-padded bytestring (0xff, adjustable)
* GsmStringAdapter(Adapter): Codec for "SMS default 7-bit
	coded alphabet as defined int TS 23.038" using
	the gsm0338 library.
* GsmString(n): Convenient wrapper of both above

Adjustments:
* utils: update+deprecate old dec_spn(), enc_spn()
* remove refs to deprecated functions

Change-Id: Ia1d3a3835933bac0002b7c52511481dd8094b994
This commit is contained in:
Robert Falkenberg 2021-05-07 15:23:20 +02:00
parent c957ce8adc
commit b07a3e9c87
8 changed files with 97 additions and 27 deletions

View File

@ -23,6 +23,7 @@ pip install cmd2
pip install jsonpath-ng
pip install construct
pip install bidict
pip install gsm0338
# Execute automatically discovered unit tests first
python -m unittest discover -v -s tests/

View File

@ -36,7 +36,7 @@ from pySim.commands import SimCardCommands
from pySim.transport import init_reader, argparse_add_reader_args
from pySim.cards import card_detect, Card, UsimCard, IsimCard
from pySim.utils import h2b, swap_nibbles, rpad, dec_imsi, dec_iccid, dec_msisdn
from pySim.utils import format_xplmn_w_act, dec_spn, dec_st, dec_addr_tlv
from pySim.utils import format_xplmn_w_act, dec_st, dec_addr_tlv
from pySim.utils import h2s, format_ePDGSelection
option_parser = argparse.ArgumentParser(prog='pySim-read',

View File

@ -25,7 +25,7 @@
from typing import Optional, Dict, Tuple
import abc
from pySim.ts_51_011 import EF, DF, EF_AD
from pySim.ts_51_011 import EF, DF, EF_AD, EF_SPN
from pySim.ts_31_102 import EF_USIM_ADF_map
from pySim.ts_31_103 import EF_ISIM_ADF_map
from pySim.utils import *
@ -203,15 +203,24 @@ class Card(object):
return sw
def read_spn(self):
(spn, sw) = self._scc.read_binary(EF['SPN'])
(content, sw) = self._scc.read_binary(EF['SPN'])
if sw == '9000':
return (dec_spn(spn), sw)
abstract_data = EF_SPN().decode_hex(content)
show_in_hplmn = abstract_data['show_in_hplmn']
hide_in_oplmn = abstract_data['hide_in_oplmn']
name = abstract_data['spn']
return ((name, show_in_hplmn, hide_in_oplmn), sw)
else:
return (None, sw)
def update_spn(self, name, hplmn_disp=False, oplmn_disp=False):
content = enc_spn(name, hplmn_disp, oplmn_disp)
data, sw = self._scc.update_binary(EF['SPN'], rpad(content, 32))
def update_spn(self, name="", show_in_hplmn=False, hide_in_oplmn=False):
abstract_data = {
'hide_in_oplmn' : hide_in_oplmn,
'show_in_hplmn' : show_in_hplmn,
'spn' : name,
}
content = EF_SPN().encode_hex(abstract_data)
data, sw = self._scc.update_binary(EF['SPN'], content)
return sw
def read_binary(self, ef, length=None, offset=0):
@ -915,8 +924,7 @@ class SysmoUSIMSJS1(UsimCard):
# set Service Provider Name
if p.get('name') is not None:
content = enc_spn(p['name'], True, True)
data, sw = self._scc.update_binary('6F46', rpad(content, 32))
self.update_spn(p['name'], True, True)
if p.get('acc') is not None:
self.update_acc(p['acc'])
@ -1310,8 +1318,7 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
# set Service Provider Name
if p.get('name') is not None:
content = enc_spn(p['name'], True, True)
data, sw = self._scc.update_binary('6F46', rpad(content, 32))
self.update_spn(p['name'], True, True)
# write EF.IMSI
if p.get('imsi'):

View File

@ -1,5 +1,6 @@
from construct import *
from pySim.utils import b2h, h2b, swap_nibbles
import gsm0338
"""Utility code related to the integration of the 'construct' declarative parser."""
@ -33,6 +34,42 @@ class BcdAdapter(Adapter):
def _encode(self, obj, context, path):
return h2b(swap_nibbles(obj))
class Rpad(Adapter):
"""
Encoder appends padding bytes (b'\\xff') up to target size.
Decoder removes trailing padding bytes.
Parameters:
subcon: Subconstruct as defined by construct library
pattern: set padding pattern (default: b'\\xff')
"""
def __init__(self, subcon, pattern=b'\xff'):
super().__init__(subcon)
self.pattern = pattern
def _decode(self, obj, context, path):
return obj.rstrip(self.pattern)
def _encode(self, obj, context, path):
if len(obj) > self.sizeof():
raise SizeofError("Input ({}) exceeds target size ({})".format(len(obj), self.sizeof()))
return obj + self.pattern * (self.sizeof() - len(obj))
class GsmStringAdapter(Adapter):
"""Convert GSM 03.38 encoded bytes to a string."""
def __init__(self, subcon, codec='gsm03.38', err='strict'):
super().__init__(subcon)
self.codec = codec
self.err = err
def _decode(self, obj, context, path):
return obj.decode(self.codec)
def _encode(self, obj, context, path):
return obj.encode(self.codec, self.err)
def filter_dict(d, exclude_prefix='_'):
"""filter the input dict to ensure no keys starting with 'exclude_prefix' remain."""
res = {}
@ -88,3 +125,17 @@ def BytesRFU(n=1):
n (Integer): Number of bytes (default: 1)
'''
return Default(Bytes(n), __RFU_VALUE)
def GsmString(n):
'''
GSM 03.38 encoded byte string of fixed length n.
Encoder appends padding bytes (b'\\xff') to maintain
length. Decoder removes those trailing bytes.
Exceptions are raised for invalid characters
and length excess.
Parameters:
n (Integer): Fixed length of the encoded byte string
'''
return GsmStringAdapter(Rpad(Bytes(n), pattern=b'\xff'), codec='gsm03.38')

View File

@ -323,7 +323,7 @@ from pySim.utils import *
from struct import pack, unpack
from construct import *
from construct import Optional as COptional
from pySim.construct import HexAdapter, BcdAdapter, FlagRFU, ByteRFU, GreedyBytesRFU, BitsRFU, BytesRFU
from pySim.construct import *
import enum
from pySim.filesystem import *
@ -519,11 +519,14 @@ class EF_ServiceTable(TransparentEF):
class EF_SPN(TransparentEF):
def __init__(self, fid='6f46', sfid=None, name='EF.SPN', desc='Service Provider Name', size={17,17}):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
def _decode_hex(self, raw_hex):
return {'spn': dec_spn(raw_hex)}
def _encode_hex(self, abstract):
spn = abstract['spn']
return enc_spn(spn[0], spn[1], spn[2])
self._construct = BitStruct(
# Byte 1
'rfu'/BitsRFU(6),
'hide_in_oplmn'/Flag,
'show_in_hplmn'/Flag,
# Bytes 2..17
'spn'/Bytewise(GsmString(16))
)
# TS 51.011 Section 10.3.13
class EF_CBMI(TransRecEF):

View File

@ -248,17 +248,23 @@ def dec_plmn(threehexbytes:Hexstr) -> dict:
return res
def dec_spn(ef):
byte1 = int(ef[0:2])
hplmn_disp = (byte1&0x01 == 0x01)
oplmn_disp = (byte1&0x02 == 0x02)
name = h2s(ef[2:])
return (name, hplmn_disp, oplmn_disp)
"""Obsolete, kept for API compatibility"""
from ts_51_011 import EF_SPN
abstract_data = EF_SPN().decode_hex(ef)
show_in_hplmn = abstract_data['show_in_hplmn']
hide_in_oplmn = abstract_data['hide_in_oplmn']
name = abstract_data['spn']
return (name, show_in_hplmn, hide_in_oplmn)
def enc_spn(name:str, hplmn_disp=False, oplmn_disp=False):
byte1 = 0x00
if hplmn_disp: byte1 = byte1|0x01
if oplmn_disp: byte1 = byte1|0x02
return i2h([byte1])+s2h(name)
def enc_spn(name:str, show_in_hplmn=False, hide_in_oplmn=False):
"""Obsolete, kept for API compatibility"""
from ts_51_011 import EF_SPN
abstract_data = {
'hide_in_oplmn' : hide_in_oplmn,
'show_in_hplmn' : show_in_hplmn,
'spn' : name,
}
return EF_SPN().encode_hex(abstract_data)
def hexstr_to_Nbytearr(s, nbytes):
return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2)) ]

View File

@ -5,3 +5,4 @@ cmd2
jsonpath-ng
construct
bidict
gsm0338

View File

@ -16,6 +16,7 @@ setup(
"jsonpath-ng",
"construct >= 2.9",
"bidict",
"gsm0338",
],
scripts=[
'pySim-prog.py',