mirror of https://gerrit.osmocom.org/pysim
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:
parent
c957ce8adc
commit
b07a3e9c87
|
@ -23,6 +23,7 @@ pip install cmd2
|
||||||
pip install jsonpath-ng
|
pip install jsonpath-ng
|
||||||
pip install construct
|
pip install construct
|
||||||
pip install bidict
|
pip install bidict
|
||||||
|
pip install gsm0338
|
||||||
|
|
||||||
# Execute automatically discovered unit tests first
|
# Execute automatically discovered unit tests first
|
||||||
python -m unittest discover -v -s tests/
|
python -m unittest discover -v -s tests/
|
||||||
|
|
|
@ -36,7 +36,7 @@ from pySim.commands import SimCardCommands
|
||||||
from pySim.transport import init_reader, argparse_add_reader_args
|
from pySim.transport import init_reader, argparse_add_reader_args
|
||||||
from pySim.cards import card_detect, Card, UsimCard, IsimCard
|
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 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
|
from pySim.utils import h2s, format_ePDGSelection
|
||||||
|
|
||||||
option_parser = argparse.ArgumentParser(prog='pySim-read',
|
option_parser = argparse.ArgumentParser(prog='pySim-read',
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
from typing import Optional, Dict, Tuple
|
from typing import Optional, Dict, Tuple
|
||||||
import abc
|
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_102 import EF_USIM_ADF_map
|
||||||
from pySim.ts_31_103 import EF_ISIM_ADF_map
|
from pySim.ts_31_103 import EF_ISIM_ADF_map
|
||||||
from pySim.utils import *
|
from pySim.utils import *
|
||||||
|
@ -203,15 +203,24 @@ class Card(object):
|
||||||
return sw
|
return sw
|
||||||
|
|
||||||
def read_spn(self):
|
def read_spn(self):
|
||||||
(spn, sw) = self._scc.read_binary(EF['SPN'])
|
(content, sw) = self._scc.read_binary(EF['SPN'])
|
||||||
if sw == '9000':
|
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:
|
else:
|
||||||
return (None, sw)
|
return (None, sw)
|
||||||
|
|
||||||
def update_spn(self, name, hplmn_disp=False, oplmn_disp=False):
|
def update_spn(self, name="", show_in_hplmn=False, hide_in_oplmn=False):
|
||||||
content = enc_spn(name, hplmn_disp, oplmn_disp)
|
abstract_data = {
|
||||||
data, sw = self._scc.update_binary(EF['SPN'], rpad(content, 32))
|
'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
|
return sw
|
||||||
|
|
||||||
def read_binary(self, ef, length=None, offset=0):
|
def read_binary(self, ef, length=None, offset=0):
|
||||||
|
@ -915,8 +924,7 @@ class SysmoUSIMSJS1(UsimCard):
|
||||||
|
|
||||||
# set Service Provider Name
|
# set Service Provider Name
|
||||||
if p.get('name') is not None:
|
if p.get('name') is not None:
|
||||||
content = enc_spn(p['name'], True, True)
|
self.update_spn(p['name'], True, True)
|
||||||
data, sw = self._scc.update_binary('6F46', rpad(content, 32))
|
|
||||||
|
|
||||||
if p.get('acc') is not None:
|
if p.get('acc') is not None:
|
||||||
self.update_acc(p['acc'])
|
self.update_acc(p['acc'])
|
||||||
|
@ -1310,8 +1318,7 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
|
||||||
|
|
||||||
# set Service Provider Name
|
# set Service Provider Name
|
||||||
if p.get('name') is not None:
|
if p.get('name') is not None:
|
||||||
content = enc_spn(p['name'], True, True)
|
self.update_spn(p['name'], True, True)
|
||||||
data, sw = self._scc.update_binary('6F46', rpad(content, 32))
|
|
||||||
|
|
||||||
# write EF.IMSI
|
# write EF.IMSI
|
||||||
if p.get('imsi'):
|
if p.get('imsi'):
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from construct import *
|
from construct import *
|
||||||
from pySim.utils import b2h, h2b, swap_nibbles
|
from pySim.utils import b2h, h2b, swap_nibbles
|
||||||
|
import gsm0338
|
||||||
|
|
||||||
"""Utility code related to the integration of the 'construct' declarative parser."""
|
"""Utility code related to the integration of the 'construct' declarative parser."""
|
||||||
|
|
||||||
|
@ -33,6 +34,42 @@ class BcdAdapter(Adapter):
|
||||||
def _encode(self, obj, context, path):
|
def _encode(self, obj, context, path):
|
||||||
return h2b(swap_nibbles(obj))
|
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='_'):
|
def filter_dict(d, exclude_prefix='_'):
|
||||||
"""filter the input dict to ensure no keys starting with 'exclude_prefix' remain."""
|
"""filter the input dict to ensure no keys starting with 'exclude_prefix' remain."""
|
||||||
res = {}
|
res = {}
|
||||||
|
@ -88,3 +125,17 @@ def BytesRFU(n=1):
|
||||||
n (Integer): Number of bytes (default: 1)
|
n (Integer): Number of bytes (default: 1)
|
||||||
'''
|
'''
|
||||||
return Default(Bytes(n), __RFU_VALUE)
|
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')
|
||||||
|
|
|
@ -323,7 +323,7 @@ from pySim.utils import *
|
||||||
from struct import pack, unpack
|
from struct import pack, unpack
|
||||||
from construct import *
|
from construct import *
|
||||||
from construct import Optional as COptional
|
from construct import Optional as COptional
|
||||||
from pySim.construct import HexAdapter, BcdAdapter, FlagRFU, ByteRFU, GreedyBytesRFU, BitsRFU, BytesRFU
|
from pySim.construct import *
|
||||||
import enum
|
import enum
|
||||||
|
|
||||||
from pySim.filesystem import *
|
from pySim.filesystem import *
|
||||||
|
@ -519,11 +519,14 @@ class EF_ServiceTable(TransparentEF):
|
||||||
class EF_SPN(TransparentEF):
|
class EF_SPN(TransparentEF):
|
||||||
def __init__(self, fid='6f46', sfid=None, name='EF.SPN', desc='Service Provider Name', size={17,17}):
|
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)
|
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
|
||||||
def _decode_hex(self, raw_hex):
|
self._construct = BitStruct(
|
||||||
return {'spn': dec_spn(raw_hex)}
|
# Byte 1
|
||||||
def _encode_hex(self, abstract):
|
'rfu'/BitsRFU(6),
|
||||||
spn = abstract['spn']
|
'hide_in_oplmn'/Flag,
|
||||||
return enc_spn(spn[0], spn[1], spn[2])
|
'show_in_hplmn'/Flag,
|
||||||
|
# Bytes 2..17
|
||||||
|
'spn'/Bytewise(GsmString(16))
|
||||||
|
)
|
||||||
|
|
||||||
# TS 51.011 Section 10.3.13
|
# TS 51.011 Section 10.3.13
|
||||||
class EF_CBMI(TransRecEF):
|
class EF_CBMI(TransRecEF):
|
||||||
|
|
|
@ -248,17 +248,23 @@ def dec_plmn(threehexbytes:Hexstr) -> dict:
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def dec_spn(ef):
|
def dec_spn(ef):
|
||||||
byte1 = int(ef[0:2])
|
"""Obsolete, kept for API compatibility"""
|
||||||
hplmn_disp = (byte1&0x01 == 0x01)
|
from ts_51_011 import EF_SPN
|
||||||
oplmn_disp = (byte1&0x02 == 0x02)
|
abstract_data = EF_SPN().decode_hex(ef)
|
||||||
name = h2s(ef[2:])
|
show_in_hplmn = abstract_data['show_in_hplmn']
|
||||||
return (name, hplmn_disp, oplmn_disp)
|
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):
|
def enc_spn(name:str, show_in_hplmn=False, hide_in_oplmn=False):
|
||||||
byte1 = 0x00
|
"""Obsolete, kept for API compatibility"""
|
||||||
if hplmn_disp: byte1 = byte1|0x01
|
from ts_51_011 import EF_SPN
|
||||||
if oplmn_disp: byte1 = byte1|0x02
|
abstract_data = {
|
||||||
return i2h([byte1])+s2h(name)
|
'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):
|
def hexstr_to_Nbytearr(s, nbytes):
|
||||||
return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2)) ]
|
return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2)) ]
|
||||||
|
|
|
@ -5,3 +5,4 @@ cmd2
|
||||||
jsonpath-ng
|
jsonpath-ng
|
||||||
construct
|
construct
|
||||||
bidict
|
bidict
|
||||||
|
gsm0338
|
||||||
|
|
Loading…
Reference in New Issue