<p>laforge <strong>submitted</strong> this change.</p><p><a href="https://gerrit.osmocom.org/c/pysim/+/24180">View Change</a></p><div style="white-space:pre-wrap">Approvals:
Jenkins Builder: Verified
dexter: Looks good to me, but someone else must approve
laforge: Looks good to me, approved
</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">Add codecs for EF_SPN and GSM strings via construct<br><br>This will replace the hand-crafted codec for EF_SPN<br>by a struct definition using the construct library.<br>Old encoders are updated and kept for API compatibility<br>but are not used internally anymore.<br><br>New data structures:<br>* Rpad(Adapter): Right-padded bytestring (0xff, adjustable)<br>* GsmStringAdapter(Adapter): Codec for "SMS default 7-bit<br> coded alphabet as defined int TS 23.038" using<br> the gsm0338 library.<br>* GsmString(n): Convenient wrapper of both above<br><br>Adjustments:<br>* utils: update+deprecate old dec_spn(), enc_spn()<br>* remove refs to deprecated functions<br><br>Change-Id: Ia1d3a3835933bac0002b7c52511481dd8094b994<br>---<br>M contrib/jenkins.sh<br>M pySim-read.py<br>M pySim/cards.py<br>M pySim/construct.py<br>M pySim/ts_51_011.py<br>M pySim/utils.py<br>M requirements.txt<br>M setup.py<br>8 files changed, 97 insertions(+), 27 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/contrib/jenkins.sh b/contrib/jenkins.sh</span><br><span>index ed5ba25..27f0245 100755</span><br><span>--- a/contrib/jenkins.sh</span><br><span>+++ b/contrib/jenkins.sh</span><br><span>@@ -23,6 +23,7 @@</span><br><span> pip install jsonpath-ng</span><br><span> pip install construct</span><br><span> pip install bidict</span><br><span style="color: hsl(120, 100%, 40%);">+pip install gsm0338</span><br><span> </span><br><span> # Execute automatically discovered unit tests first</span><br><span> python -m unittest discover -v -s tests/</span><br><span>diff --git a/pySim-read.py b/pySim-read.py</span><br><span>index ebe0e29..9feef9d 100755</span><br><span>--- a/pySim-read.py</span><br><span>+++ b/pySim-read.py</span><br><span>@@ -36,7 +36,7 @@</span><br><span> from pySim.transport import init_reader, argparse_add_reader_args</span><br><span> from pySim.cards import card_detect, Card, UsimCard, IsimCard</span><br><span> from pySim.utils import h2b, swap_nibbles, rpad, dec_imsi, dec_iccid, dec_msisdn</span><br><span style="color: hsl(0, 100%, 40%);">-from pySim.utils import format_xplmn_w_act, dec_spn, dec_st, dec_addr_tlv</span><br><span style="color: hsl(120, 100%, 40%);">+from pySim.utils import format_xplmn_w_act, dec_st, dec_addr_tlv</span><br><span> from pySim.utils import h2s, format_ePDGSelection</span><br><span> </span><br><span> option_parser = argparse.ArgumentParser(prog='pySim-read',</span><br><span>diff --git a/pySim/cards.py b/pySim/cards.py</span><br><span>index 30857b3..dcba26c 100644</span><br><span>--- a/pySim/cards.py</span><br><span>+++ b/pySim/cards.py</span><br><span>@@ -25,7 +25,7 @@</span><br><span> from typing import Optional, Dict, Tuple</span><br><span> import abc</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-from pySim.ts_51_011 import EF, DF, EF_AD</span><br><span style="color: hsl(120, 100%, 40%);">+from pySim.ts_51_011 import EF, DF, EF_AD, EF_SPN</span><br><span> from pySim.ts_31_102 import EF_USIM_ADF_map</span><br><span> from pySim.ts_31_103 import EF_ISIM_ADF_map</span><br><span> from pySim.utils import *</span><br><span>@@ -203,15 +203,24 @@</span><br><span> return sw</span><br><span> </span><br><span> def read_spn(self):</span><br><span style="color: hsl(0, 100%, 40%);">- (spn, sw) = self._scc.read_binary(EF['SPN'])</span><br><span style="color: hsl(120, 100%, 40%);">+ (content, sw) = self._scc.read_binary(EF['SPN'])</span><br><span> if sw == '9000':</span><br><span style="color: hsl(0, 100%, 40%);">- return (dec_spn(spn), sw)</span><br><span style="color: hsl(120, 100%, 40%);">+ abstract_data = EF_SPN().decode_hex(content)</span><br><span style="color: hsl(120, 100%, 40%);">+ show_in_hplmn = abstract_data['show_in_hplmn']</span><br><span style="color: hsl(120, 100%, 40%);">+ hide_in_oplmn = abstract_data['hide_in_oplmn']</span><br><span style="color: hsl(120, 100%, 40%);">+ name = abstract_data['spn']</span><br><span style="color: hsl(120, 100%, 40%);">+ return ((name, show_in_hplmn, hide_in_oplmn), sw)</span><br><span> else:</span><br><span> return (None, sw)</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- def update_spn(self, name, hplmn_disp=False, oplmn_disp=False):</span><br><span style="color: hsl(0, 100%, 40%);">- content = enc_spn(name, hplmn_disp, oplmn_disp)</span><br><span style="color: hsl(0, 100%, 40%);">- data, sw = self._scc.update_binary(EF['SPN'], rpad(content, 32))</span><br><span style="color: hsl(120, 100%, 40%);">+ def update_spn(self, name="", show_in_hplmn=False, hide_in_oplmn=False):</span><br><span style="color: hsl(120, 100%, 40%);">+ abstract_data = {</span><br><span style="color: hsl(120, 100%, 40%);">+ 'hide_in_oplmn' : hide_in_oplmn,</span><br><span style="color: hsl(120, 100%, 40%);">+ 'show_in_hplmn' : show_in_hplmn,</span><br><span style="color: hsl(120, 100%, 40%);">+ 'spn' : name,</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ content = EF_SPN().encode_hex(abstract_data)</span><br><span style="color: hsl(120, 100%, 40%);">+ data, sw = self._scc.update_binary(EF['SPN'], content)</span><br><span> return sw</span><br><span> </span><br><span> def read_binary(self, ef, length=None, offset=0):</span><br><span>@@ -915,8 +924,7 @@</span><br><span> </span><br><span> # set Service Provider Name</span><br><span> if p.get('name') is not None:</span><br><span style="color: hsl(0, 100%, 40%);">- content = enc_spn(p['name'], True, True)</span><br><span style="color: hsl(0, 100%, 40%);">- data, sw = self._scc.update_binary('6F46', rpad(content, 32))</span><br><span style="color: hsl(120, 100%, 40%);">+ self.update_spn(p['name'], True, True)</span><br><span> </span><br><span> if p.get('acc') is not None:</span><br><span> self.update_acc(p['acc'])</span><br><span>@@ -1310,8 +1318,7 @@</span><br><span> </span><br><span> # set Service Provider Name</span><br><span> if p.get('name') is not None:</span><br><span style="color: hsl(0, 100%, 40%);">- content = enc_spn(p['name'], True, True)</span><br><span style="color: hsl(0, 100%, 40%);">- data, sw = self._scc.update_binary('6F46', rpad(content, 32))</span><br><span style="color: hsl(120, 100%, 40%);">+ self.update_spn(p['name'], True, True)</span><br><span> </span><br><span> # write EF.IMSI</span><br><span> if p.get('imsi'):</span><br><span>diff --git a/pySim/construct.py b/pySim/construct.py</span><br><span>index b0f03b7..a903305 100644</span><br><span>--- a/pySim/construct.py</span><br><span>+++ b/pySim/construct.py</span><br><span>@@ -1,5 +1,6 @@</span><br><span> from construct import *</span><br><span> from pySim.utils import b2h, h2b, swap_nibbles</span><br><span style="color: hsl(120, 100%, 40%);">+import gsm0338</span><br><span> </span><br><span> """Utility code related to the integration of the 'construct' declarative parser."""</span><br><span> </span><br><span>@@ -33,6 +34,42 @@</span><br><span> def _encode(self, obj, context, path):</span><br><span> return h2b(swap_nibbles(obj))</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+class Rpad(Adapter):</span><br><span style="color: hsl(120, 100%, 40%);">+ """</span><br><span style="color: hsl(120, 100%, 40%);">+ Encoder appends padding bytes (b'\\xff') up to target size.</span><br><span style="color: hsl(120, 100%, 40%);">+ Decoder removes trailing padding bytes.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ Parameters:</span><br><span style="color: hsl(120, 100%, 40%);">+ subcon: Subconstruct as defined by construct library</span><br><span style="color: hsl(120, 100%, 40%);">+ pattern: set padding pattern (default: b'\\xff')</span><br><span style="color: hsl(120, 100%, 40%);">+ """</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def __init__(self, subcon, pattern=b'\xff'):</span><br><span style="color: hsl(120, 100%, 40%);">+ super().__init__(subcon)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.pattern = pattern</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def _decode(self, obj, context, path):</span><br><span style="color: hsl(120, 100%, 40%);">+ return obj.rstrip(self.pattern)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def _encode(self, obj, context, path):</span><br><span style="color: hsl(120, 100%, 40%);">+ if len(obj) > self.sizeof():</span><br><span style="color: hsl(120, 100%, 40%);">+ raise SizeofError("Input ({}) exceeds target size ({})".format(len(obj), self.sizeof()))</span><br><span style="color: hsl(120, 100%, 40%);">+ return obj + self.pattern * (self.sizeof() - len(obj))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class GsmStringAdapter(Adapter):</span><br><span style="color: hsl(120, 100%, 40%);">+ """Convert GSM 03.38 encoded bytes to a string."""</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def __init__(self, subcon, codec='gsm03.38', err='strict'):</span><br><span style="color: hsl(120, 100%, 40%);">+ super().__init__(subcon)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.codec = codec</span><br><span style="color: hsl(120, 100%, 40%);">+ self.err = err</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def _decode(self, obj, context, path):</span><br><span style="color: hsl(120, 100%, 40%);">+ return obj.decode(self.codec)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def _encode(self, obj, context, path):</span><br><span style="color: hsl(120, 100%, 40%);">+ return obj.encode(self.codec, self.err)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> def filter_dict(d, exclude_prefix='_'):</span><br><span> """filter the input dict to ensure no keys starting with 'exclude_prefix' remain."""</span><br><span> res = {}</span><br><span>@@ -88,3 +125,17 @@</span><br><span> n (Integer): Number of bytes (default: 1)</span><br><span> '''</span><br><span> return Default(Bytes(n), __RFU_VALUE)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def GsmString(n):</span><br><span style="color: hsl(120, 100%, 40%);">+ '''</span><br><span style="color: hsl(120, 100%, 40%);">+ GSM 03.38 encoded byte string of fixed length n.</span><br><span style="color: hsl(120, 100%, 40%);">+ Encoder appends padding bytes (b'\\xff') to maintain</span><br><span style="color: hsl(120, 100%, 40%);">+ length. Decoder removes those trailing bytes.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ Exceptions are raised for invalid characters</span><br><span style="color: hsl(120, 100%, 40%);">+ and length excess.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ Parameters:</span><br><span style="color: hsl(120, 100%, 40%);">+ n (Integer): Fixed length of the encoded byte string</span><br><span style="color: hsl(120, 100%, 40%);">+ '''</span><br><span style="color: hsl(120, 100%, 40%);">+ return GsmStringAdapter(Rpad(Bytes(n), pattern=b'\xff'), codec='gsm03.38')</span><br><span>diff --git a/pySim/ts_51_011.py b/pySim/ts_51_011.py</span><br><span>index dba0369..6ab07f0 100644</span><br><span>--- a/pySim/ts_51_011.py</span><br><span>+++ b/pySim/ts_51_011.py</span><br><span>@@ -323,7 +323,7 @@</span><br><span> from struct import pack, unpack</span><br><span> from construct import *</span><br><span> from construct import Optional as COptional</span><br><span style="color: hsl(0, 100%, 40%);">-from pySim.construct import HexAdapter, BcdAdapter, FlagRFU, ByteRFU, GreedyBytesRFU, BitsRFU, BytesRFU</span><br><span style="color: hsl(120, 100%, 40%);">+from pySim.construct import *</span><br><span> import enum</span><br><span> </span><br><span> from pySim.filesystem import *</span><br><span>@@ -519,11 +519,14 @@</span><br><span> class EF_SPN(TransparentEF):</span><br><span> def __init__(self, fid='6f46', sfid=None, name='EF.SPN', desc='Service Provider Name', size={17,17}):</span><br><span> super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)</span><br><span style="color: hsl(0, 100%, 40%);">- def _decode_hex(self, raw_hex):</span><br><span style="color: hsl(0, 100%, 40%);">- return {'spn': dec_spn(raw_hex)}</span><br><span style="color: hsl(0, 100%, 40%);">- def _encode_hex(self, abstract):</span><br><span style="color: hsl(0, 100%, 40%);">- spn = abstract['spn']</span><br><span style="color: hsl(0, 100%, 40%);">- return enc_spn(spn[0], spn[1], spn[2])</span><br><span style="color: hsl(120, 100%, 40%);">+ self._construct = BitStruct(</span><br><span style="color: hsl(120, 100%, 40%);">+ # Byte 1</span><br><span style="color: hsl(120, 100%, 40%);">+ 'rfu'/BitsRFU(6),</span><br><span style="color: hsl(120, 100%, 40%);">+ 'hide_in_oplmn'/Flag,</span><br><span style="color: hsl(120, 100%, 40%);">+ 'show_in_hplmn'/Flag,</span><br><span style="color: hsl(120, 100%, 40%);">+ # Bytes 2..17</span><br><span style="color: hsl(120, 100%, 40%);">+ 'spn'/Bytewise(GsmString(16))</span><br><span style="color: hsl(120, 100%, 40%);">+ )</span><br><span> </span><br><span> # TS 51.011 Section 10.3.13</span><br><span> class EF_CBMI(TransRecEF):</span><br><span>diff --git a/pySim/utils.py b/pySim/utils.py</span><br><span>index a3cd1b5..3a8ddac 100644</span><br><span>--- a/pySim/utils.py</span><br><span>+++ b/pySim/utils.py</span><br><span>@@ -248,17 +248,23 @@</span><br><span> return res</span><br><span> </span><br><span> def dec_spn(ef):</span><br><span style="color: hsl(0, 100%, 40%);">- byte1 = int(ef[0:2])</span><br><span style="color: hsl(0, 100%, 40%);">- hplmn_disp = (byte1&0x01 == 0x01)</span><br><span style="color: hsl(0, 100%, 40%);">- oplmn_disp = (byte1&0x02 == 0x02)</span><br><span style="color: hsl(0, 100%, 40%);">- name = h2s(ef[2:])</span><br><span style="color: hsl(0, 100%, 40%);">- return (name, hplmn_disp, oplmn_disp)</span><br><span style="color: hsl(120, 100%, 40%);">+ """Obsolete, kept for API compatibility"""</span><br><span style="color: hsl(120, 100%, 40%);">+ from ts_51_011 import EF_SPN</span><br><span style="color: hsl(120, 100%, 40%);">+ abstract_data = EF_SPN().decode_hex(ef)</span><br><span style="color: hsl(120, 100%, 40%);">+ show_in_hplmn = abstract_data['show_in_hplmn']</span><br><span style="color: hsl(120, 100%, 40%);">+ hide_in_oplmn = abstract_data['hide_in_oplmn']</span><br><span style="color: hsl(120, 100%, 40%);">+ name = abstract_data['spn']</span><br><span style="color: hsl(120, 100%, 40%);">+ return (name, show_in_hplmn, hide_in_oplmn)</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-def enc_spn(name:str, hplmn_disp=False, oplmn_disp=False):</span><br><span style="color: hsl(0, 100%, 40%);">- byte1 = 0x00</span><br><span style="color: hsl(0, 100%, 40%);">- if hplmn_disp: byte1 = byte1|0x01</span><br><span style="color: hsl(0, 100%, 40%);">- if oplmn_disp: byte1 = byte1|0x02</span><br><span style="color: hsl(0, 100%, 40%);">- return i2h([byte1])+s2h(name)</span><br><span style="color: hsl(120, 100%, 40%);">+def enc_spn(name:str, show_in_hplmn=False, hide_in_oplmn=False):</span><br><span style="color: hsl(120, 100%, 40%);">+ """Obsolete, kept for API compatibility"""</span><br><span style="color: hsl(120, 100%, 40%);">+ from ts_51_011 import EF_SPN</span><br><span style="color: hsl(120, 100%, 40%);">+ abstract_data = {</span><br><span style="color: hsl(120, 100%, 40%);">+ 'hide_in_oplmn' : hide_in_oplmn,</span><br><span style="color: hsl(120, 100%, 40%);">+ 'show_in_hplmn' : show_in_hplmn,</span><br><span style="color: hsl(120, 100%, 40%);">+ 'spn' : name,</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ return EF_SPN().encode_hex(abstract_data)</span><br><span> </span><br><span> def hexstr_to_Nbytearr(s, nbytes):</span><br><span> return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2)) ]</span><br><span>diff --git a/requirements.txt b/requirements.txt</span><br><span>index 6e8336c..10e9667 100644</span><br><span>--- a/requirements.txt</span><br><span>+++ b/requirements.txt</span><br><span>@@ -5,3 +5,4 @@</span><br><span> jsonpath-ng</span><br><span> construct</span><br><span> bidict</span><br><span style="color: hsl(120, 100%, 40%);">+gsm0338</span><br><span>diff --git a/setup.py b/setup.py</span><br><span>index 08608dc..2ef498f 100644</span><br><span>--- a/setup.py</span><br><span>+++ b/setup.py</span><br><span>@@ -16,6 +16,7 @@</span><br><span> "jsonpath-ng",</span><br><span> "construct >= 2.9",</span><br><span> "bidict",</span><br><span style="color: hsl(120, 100%, 40%);">+ "gsm0338",</span><br><span> ],</span><br><span> scripts=[</span><br><span> 'pySim-prog.py',</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.osmocom.org/c/pysim/+/24180">change 24180</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.osmocom.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.osmocom.org/c/pysim/+/24180"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: pysim </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-Change-Id: Ia1d3a3835933bac0002b7c52511481dd8094b994 </div>
<div style="display:none"> Gerrit-Change-Number: 24180 </div>
<div style="display:none"> Gerrit-PatchSet: 2 </div>
<div style="display:none"> Gerrit-Owner: Falkenber9 <robert.falkenberg@tu-dortmund.de> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins Builder </div>
<div style="display:none"> Gerrit-Reviewer: dexter <pmaier@sysmocom.de> </div>
<div style="display:none"> Gerrit-Reviewer: laforge <laforge@osmocom.org> </div>
<div style="display:none"> Gerrit-CC: fixeria <vyanitskiy@sysmocom.de> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>