<p>laforge <strong>submitted</strong> this change.</p><p><a href="https://gerrit.osmocom.org/c/pysim/+/24102">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  Jenkins Builder: Verified
  fixeria: Looks good to me, approved
  dexter: Looks good to me, but someone else must approve

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">BER-TLV EF support (command, filesystem, shell)<br><br>This adds support for a new EF file type: BER-TLV files.  They are<br>different from transparent and linear fixed EFs in that they neither<br>operate on a byte stream nor fixed-sized records, but on BER-TLV encoded<br>objects.  One can specify a tag value, and the card will return the<br>entire TLV for that tag.<br><br>As indicated in the spec, the magic tag value 0x5C (92) will return a<br>list of tags existing in the file.<br><br>Change-Id: Ibfcce757dcd477fd0d6857f64fbb4346d6d62e63<br>---<br>M docs/shell.rst<br>M pySim-shell.py<br>M pySim/commands.py<br>M pySim/filesystem.py<br>M pySim/ts_102_221.py<br>M pySim/utils.py<br>6 files changed, 301 insertions(+), 7 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/docs/shell.rst b/docs/shell.rst</span><br><span>index 4cdf9e0..5170999 100644</span><br><span>--- a/docs/shell.rst</span><br><span>+++ b/docs/shell.rst</span><br><span>@@ -334,6 +334,43 @@</span><br><span> This allows for easy interactive modification of file contents.</span><br><span> </span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+BER-TLV EF commands</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%);">+BER-TLV EFs are files that contain BER-TLV structured data.  Every file can contain any number</span><br><span style="color: hsl(120, 100%, 40%);">+of variable-length IEs (DOs).  The tag within a BER-TLV EF must be unique within the file.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+The commands below become enabled only when your currently selected file is of *BER-TLV EF* type.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+retrieve_tags</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%);">+Retrieve a list of all tags present in the currently selected file.</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%);">+retrieve_data</span><br><span style="color: hsl(120, 100%, 40%);">+~~~~~~~~~~~~~</span><br><span style="color: hsl(120, 100%, 40%);">+.. argparse::</span><br><span style="color: hsl(120, 100%, 40%);">+   :module: pySim.filesystem</span><br><span style="color: hsl(120, 100%, 40%);">+   :func: BerTlvEF.ShellCommands.retrieve_data_parser</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%);">+set_data</span><br><span style="color: hsl(120, 100%, 40%);">+~~~~~~~~</span><br><span style="color: hsl(120, 100%, 40%);">+.. argparse::</span><br><span style="color: hsl(120, 100%, 40%);">+   :module: pySim.filesystem</span><br><span style="color: hsl(120, 100%, 40%);">+   :func: BerTlvEF.ShellCommands.set_data_parser</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%);">+del_data</span><br><span style="color: hsl(120, 100%, 40%);">+~~~~~~~~</span><br><span style="color: hsl(120, 100%, 40%);">+.. argparse::</span><br><span style="color: hsl(120, 100%, 40%);">+   :module: pySim.filesystem</span><br><span style="color: hsl(120, 100%, 40%);">+   :func: BerTlvEF.ShellCommands.del_data_parser</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%);">+</span><br><span> USIM commands</span><br><span> -------------</span><br><span> </span><br><span>diff --git a/pySim-shell.py b/pySim-shell.py</span><br><span>index 659c12c..5b5768a 100755</span><br><span>--- a/pySim-shell.py</span><br><span>+++ b/pySim-shell.py</span><br><span>@@ -38,7 +38,7 @@</span><br><span> from pySim.commands import SimCardCommands</span><br><span> from pySim.transport import init_reader, ApduTracer, argparse_add_reader_args</span><br><span> from pySim.cards import card_detect, Card</span><br><span style="color: hsl(0, 100%, 40%);">-from pySim.utils import h2b, swap_nibbles, rpad, h2s, JsonEncoder</span><br><span style="color: hsl(120, 100%, 40%);">+from pySim.utils import h2b, swap_nibbles, rpad, b2h, h2s, JsonEncoder, bertlv_parse_one</span><br><span> from pySim.utils import dec_st, sanitize_pin_adm, tabulate_str_list, is_hex, boxed_heading_str</span><br><span> from pySim.card_handler import card_handler</span><br><span> </span><br><span>@@ -256,11 +256,19 @@</span><br><span>                       if structure == 'transparent':</span><br><span>                               result = self._cmd.rs.read_binary()</span><br><span>                          self._cmd.poutput("update_binary " + str(result[0]))</span><br><span style="color: hsl(0, 100%, 40%);">-                  if structure == 'cyclic' or structure == 'linear_fixed':</span><br><span style="color: hsl(120, 100%, 40%);">+                      elif structure == 'cyclic' or structure == 'linear_fixed':</span><br><span>                           num_of_rec = fd['num_of_rec']</span><br><span>                                for r in range(1, num_of_rec + 1):</span><br><span>                                   result = self._cmd.rs.read_record(r)</span><br><span>                                         self._cmd.poutput("update_record %d %s" % (r, str(result[0])))</span><br><span style="color: hsl(120, 100%, 40%);">+                      elif structure == 'ber_tlv':</span><br><span style="color: hsl(120, 100%, 40%);">+                          tags = self._cmd.rs.retrieve_tags()</span><br><span style="color: hsl(120, 100%, 40%);">+                           for t in tags:</span><br><span style="color: hsl(120, 100%, 40%);">+                                        result = self._cmd.rs.retrieve_data(t)</span><br><span style="color: hsl(120, 100%, 40%);">+                                        (tag, l, val) = bertlv_parse_one(h2b(result[0]))</span><br><span style="color: hsl(120, 100%, 40%);">+                                      self._cmd.poutput("set_data 0x%02x %s" % (t, b2h(val)))</span><br><span style="color: hsl(120, 100%, 40%);">+                     else:</span><br><span style="color: hsl(120, 100%, 40%);">+                         raise RuntimeError('Unsupported structure "%s" of file "%s"' % (structure, filename))</span><br><span>            except Exception as e:</span><br><span>                       bad_file_str = '/'.join(df_path_list) + "/" + str(filename) + ", " + str(e)</span><br><span>                      self._cmd.poutput("# bad file: %s" % bad_file_str)</span><br><span>diff --git a/pySim/commands.py b/pySim/commands.py</span><br><span>index 33aec12..0b3d9b6 100644</span><br><span>--- a/pySim/commands.py</span><br><span>+++ b/pySim/commands.py</span><br><span>@@ -5,7 +5,7 @@</span><br><span> </span><br><span> #</span><br><span> # Copyright (C) 2009-2010  Sylvain Munaut <tnt@246tNt.com></span><br><span style="color: hsl(0, 100%, 40%);">-# Copyright (C) 2010       Harald Welte <laforge@gnumonks.org></span><br><span style="color: hsl(120, 100%, 40%);">+# Copyright (C) 2010-2021  Harald Welte <laforge@gnumonks.org></span><br><span> #</span><br><span> # This program is free software: you can redistribute it and/or modify</span><br><span> # it under the terms of the GNU General Public License as published by</span><br><span>@@ -23,7 +23,7 @@</span><br><span> </span><br><span> from construct import *</span><br><span> from pySim.construct import LV</span><br><span style="color: hsl(0, 100%, 40%);">-from pySim.utils import rpad, b2h, sw_match</span><br><span style="color: hsl(120, 100%, 40%);">+from pySim.utils import rpad, b2h, h2b, sw_match, bertlv_encode_len</span><br><span> from pySim.exceptions import SwMatchError</span><br><span> </span><br><span> class SimCardCommands(object):</span><br><span>@@ -269,6 +269,76 @@</span><br><span>               r = self.select_path(ef)</span><br><span>             return self.__len(r)</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+      # TS 102 221 Section 11.3.1 low-level helper</span><br><span style="color: hsl(120, 100%, 40%);">+  def _retrieve_data(self, tag:int, first:bool=True):</span><br><span style="color: hsl(120, 100%, 40%);">+           if first:</span><br><span style="color: hsl(120, 100%, 40%);">+                     pdu = '80cb008001%02x' % (tag)</span><br><span style="color: hsl(120, 100%, 40%);">+                else:</span><br><span style="color: hsl(120, 100%, 40%);">+                 pdu = '80cb000000'</span><br><span style="color: hsl(120, 100%, 40%);">+            return self._tp.send_apdu_checksw(pdu)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      # TS 102 221 Section 11.3.1</span><br><span style="color: hsl(120, 100%, 40%);">+   def retrieve_data(self, ef, tag:int):</span><br><span style="color: hsl(120, 100%, 40%);">+         """Execute RETRIEVE DATA.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+            Args</span><br><span style="color: hsl(120, 100%, 40%);">+                  ef : string or list of strings indicating name or path of transparent EF</span><br><span style="color: hsl(120, 100%, 40%);">+                      tag : BER-TLV Tag of value to be retrieved</span><br><span style="color: hsl(120, 100%, 40%);">+            """</span><br><span style="color: hsl(120, 100%, 40%);">+            r = self.select_path(ef)</span><br><span style="color: hsl(120, 100%, 40%);">+              if len(r[-1]) == 0:</span><br><span style="color: hsl(120, 100%, 40%);">+                   return (None, None)</span><br><span style="color: hsl(120, 100%, 40%);">+           total_data = ''</span><br><span style="color: hsl(120, 100%, 40%);">+               # retrieve first block</span><br><span style="color: hsl(120, 100%, 40%);">+                data, sw = self._retrieve_data(tag, first=True)</span><br><span style="color: hsl(120, 100%, 40%);">+               total_data += data</span><br><span style="color: hsl(120, 100%, 40%);">+            while sw == '62f1' or sw == '62f2':</span><br><span style="color: hsl(120, 100%, 40%);">+                   data, sw = self._retrieve_data(tag, first=False)</span><br><span style="color: hsl(120, 100%, 40%);">+                      total_data += data</span><br><span style="color: hsl(120, 100%, 40%);">+            return total_data, sw</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       # TS 102 221 Section 11.3.2 low-level helper</span><br><span style="color: hsl(120, 100%, 40%);">+  def _set_data(self, data:str, first:bool=True):</span><br><span style="color: hsl(120, 100%, 40%);">+               if first:</span><br><span style="color: hsl(120, 100%, 40%);">+                     p1 = 0x80</span><br><span style="color: hsl(120, 100%, 40%);">+             else:</span><br><span style="color: hsl(120, 100%, 40%);">+                 p1 = 0x00</span><br><span style="color: hsl(120, 100%, 40%);">+             if isinstance(data, bytes) or isinstance(data, bytearray):</span><br><span style="color: hsl(120, 100%, 40%);">+                    data = b2h(data)</span><br><span style="color: hsl(120, 100%, 40%);">+              pdu = '80db00%02x%02x%s' % (p1, len(data)//2, data)</span><br><span style="color: hsl(120, 100%, 40%);">+           return self._tp.send_apdu_checksw(pdu)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      def set_data(self, ef, tag:int, value:str, verify:bool=False, conserve:bool=False):</span><br><span style="color: hsl(120, 100%, 40%);">+           """Execute SET DATA.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+         Args</span><br><span style="color: hsl(120, 100%, 40%);">+                  ef : string or list of strings indicating name or path of transparent EF</span><br><span style="color: hsl(120, 100%, 40%);">+                      tag : BER-TLV Tag of value to be stored</span><br><span style="color: hsl(120, 100%, 40%);">+                       value : BER-TLV value to be stored</span><br><span style="color: hsl(120, 100%, 40%);">+            """</span><br><span style="color: hsl(120, 100%, 40%);">+            r = self.select_path(ef)</span><br><span style="color: hsl(120, 100%, 40%);">+              if len(r[-1]) == 0:</span><br><span style="color: hsl(120, 100%, 40%);">+                   return (None, None)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+         # in case of deleting the data, we only have 'tag' but no 'value'</span><br><span style="color: hsl(120, 100%, 40%);">+             if not value:</span><br><span style="color: hsl(120, 100%, 40%);">+                 return self._set_data('%02x' % tag, first=True)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+             # FIXME: proper BER-TLV encode</span><br><span style="color: hsl(120, 100%, 40%);">+                tl = '%02x%s' % (tag, b2h(bertlv_encode_len(len(value)//2)))</span><br><span style="color: hsl(120, 100%, 40%);">+          tlv = tl + value</span><br><span style="color: hsl(120, 100%, 40%);">+              tlv_bin = h2b(tlv)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+          first = True</span><br><span style="color: hsl(120, 100%, 40%);">+          total_len = len(tlv_bin)</span><br><span style="color: hsl(120, 100%, 40%);">+              remaining = tlv_bin</span><br><span style="color: hsl(120, 100%, 40%);">+           while len(remaining) > 0:</span><br><span style="color: hsl(120, 100%, 40%);">+                  fragment = remaining[:255]</span><br><span style="color: hsl(120, 100%, 40%);">+                    rdata, sw = self._set_data(fragment, first=first)</span><br><span style="color: hsl(120, 100%, 40%);">+                     first = False</span><br><span style="color: hsl(120, 100%, 40%);">+                 remaining = remaining[255:]</span><br><span style="color: hsl(120, 100%, 40%);">+           return rdata, sw</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>   def run_gsm(self, rand:str):</span><br><span>                 """Execute RUN GSM ALGORITHM."""</span><br><span>               if len(rand) != 32:</span><br><span>diff --git a/pySim/filesystem.py b/pySim/filesystem.py</span><br><span>index edfe85d..b3e28ef 100644</span><br><span>--- a/pySim/filesystem.py</span><br><span>+++ b/pySim/filesystem.py</span><br><span>@@ -34,7 +34,7 @@</span><br><span> </span><br><span> from typing import cast, Optional, Iterable, List, Any, Dict, Tuple</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-from pySim.utils import sw_match, h2b, b2h, is_hex</span><br><span style="color: hsl(120, 100%, 40%);">+from pySim.utils import sw_match, h2b, b2h, is_hex, auto_int, bertlv_parse_one</span><br><span> from pySim.construct import filter_dict</span><br><span> from pySim.exceptions import *</span><br><span> from pySim.jsonpath import js_path_find, js_path_modify</span><br><span>@@ -914,7 +914,7 @@</span><br><span>         return b''.join(chunks)</span><br><span> </span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-class BerTlvEF(TransparentEF):</span><br><span style="color: hsl(120, 100%, 40%);">+class BerTlvEF(CardEF):</span><br><span>     """BER-TLV EF (Entry File) in the smart card filesystem.</span><br><span>     A BER-TLV EF is a binary file with a BER (Basic Encoding Rules) TLV structure</span><br><span> </span><br><span>@@ -922,6 +922,61 @@</span><br><span>     around TransparentEF as a place-holder, so we can already define EFs of BER-TLV</span><br><span>     type without fully supporting them."""</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+    @with_default_category('BER-TLV EF Commands')</span><br><span style="color: hsl(120, 100%, 40%);">+    class ShellCommands(CommandSet):</span><br><span style="color: hsl(120, 100%, 40%);">+        """Shell commands specific for BER-TLV EFs."""</span><br><span style="color: hsl(120, 100%, 40%);">+        def __init__(self):</span><br><span style="color: hsl(120, 100%, 40%);">+            super().__init__()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        retrieve_data_parser = argparse.ArgumentParser()</span><br><span style="color: hsl(120, 100%, 40%);">+        retrieve_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to retrieve')</span><br><span style="color: hsl(120, 100%, 40%);">+        @cmd2.with_argparser(retrieve_data_parser)</span><br><span style="color: hsl(120, 100%, 40%);">+        def do_retrieve_data(self, opts):</span><br><span style="color: hsl(120, 100%, 40%);">+            """Retrieve (Read) data from a BER-TLV EF"""</span><br><span style="color: hsl(120, 100%, 40%);">+            (data, sw) = self._cmd.rs.retrieve_data(opts.tag)</span><br><span style="color: hsl(120, 100%, 40%);">+            self._cmd.poutput(data)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        def do_retrieve_tags(self, opts):</span><br><span style="color: hsl(120, 100%, 40%);">+            """List tags available in a given BER-TLV EF"""</span><br><span style="color: hsl(120, 100%, 40%);">+            tags = self._cmd.rs.retrieve_tags()</span><br><span style="color: hsl(120, 100%, 40%);">+            self._cmd.poutput(tags)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        set_data_parser = argparse.ArgumentParser()</span><br><span style="color: hsl(120, 100%, 40%);">+        set_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to set')</span><br><span style="color: hsl(120, 100%, 40%);">+        set_data_parser.add_argument('data', help='Data bytes (hex format) to write')</span><br><span style="color: hsl(120, 100%, 40%);">+        @cmd2.with_argparser(set_data_parser)</span><br><span style="color: hsl(120, 100%, 40%);">+        def do_set_data(self, opts):</span><br><span style="color: hsl(120, 100%, 40%);">+            """Set (Write) data for a given tag in a BER-TLV EF"""</span><br><span style="color: hsl(120, 100%, 40%);">+            (data, sw) = self._cmd.rs.set_data(opts.tag, opts.data)</span><br><span style="color: hsl(120, 100%, 40%);">+            if data:</span><br><span style="color: hsl(120, 100%, 40%);">+                self._cmd.poutput(data)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        del_data_parser = argparse.ArgumentParser()</span><br><span style="color: hsl(120, 100%, 40%);">+        del_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to set')</span><br><span style="color: hsl(120, 100%, 40%);">+        @cmd2.with_argparser(del_data_parser)</span><br><span style="color: hsl(120, 100%, 40%);">+        def do_delete_data(self, opts):</span><br><span style="color: hsl(120, 100%, 40%);">+            """Delete  data for a given tag in a BER-TLV EF"""</span><br><span style="color: hsl(120, 100%, 40%);">+            (data, sw) = self._cmd.rs.set_data(opts.tag, None)</span><br><span style="color: hsl(120, 100%, 40%);">+            if data:</span><br><span style="color: hsl(120, 100%, 40%);">+                self._cmd.poutput(data)</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, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None,</span><br><span style="color: hsl(120, 100%, 40%);">+                 size={1,None}):</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        Args:</span><br><span style="color: hsl(120, 100%, 40%);">+            fid : File Identifier (4 hex digits)</span><br><span style="color: hsl(120, 100%, 40%);">+            sfid : Short File Identifier (2 hex digits, optional)</span><br><span style="color: hsl(120, 100%, 40%);">+            name : Brief name of the file, lik EF_ICCID</span><br><span style="color: hsl(120, 100%, 40%);">+            desc : Description of the file</span><br><span style="color: hsl(120, 100%, 40%);">+            parent : Parent CardFile object within filesystem hierarchy</span><br><span style="color: hsl(120, 100%, 40%);">+            size : tuple of (minimum_size, recommended_size)</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)</span><br><span style="color: hsl(120, 100%, 40%);">+        self._construct = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.size = size</span><br><span style="color: hsl(120, 100%, 40%);">+        self.shell_commands = [self.ShellCommands()]</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> </span><br><span> class RuntimeState(object):</span><br><span>     """Represent the runtime state of a session with a card."""</span><br><span>@@ -1172,6 +1227,43 @@</span><br><span>         data_hex = self.selected_file.encode_record_hex(data)</span><br><span>         return self.update_record(rec_nr, data_hex)</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+    def retrieve_data(self, tag:int=0):</span><br><span style="color: hsl(120, 100%, 40%);">+        """Read a DO/TLV as binary data.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        Args:</span><br><span style="color: hsl(120, 100%, 40%);">+            tag : Tag of TLV/DO to read</span><br><span style="color: hsl(120, 100%, 40%);">+        Returns:</span><br><span style="color: hsl(120, 100%, 40%);">+            hex string of full BER-TLV DO including Tag and Length</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        if not isinstance(self.selected_file, BerTlvEF):</span><br><span style="color: hsl(120, 100%, 40%);">+            raise TypeError("Only works with BER-TLV EF")</span><br><span style="color: hsl(120, 100%, 40%);">+        # returns a string of hex nibbles</span><br><span style="color: hsl(120, 100%, 40%);">+        return self.card._scc.retrieve_data(self.selected_file.fid, tag)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def retrieve_tags(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        """Retrieve tags available on BER-TLV EF.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        Returns:</span><br><span style="color: hsl(120, 100%, 40%);">+            list of integer tags contained in EF</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        if not isinstance(self.selected_file, BerTlvEF):</span><br><span style="color: hsl(120, 100%, 40%);">+            raise TypeError("Only works with BER-TLV EF")</span><br><span style="color: hsl(120, 100%, 40%);">+        data, sw = self.card._scc.retrieve_data(self.selected_file.fid, 0x5c)</span><br><span style="color: hsl(120, 100%, 40%);">+        tag, length, value = bertlv_parse_one(h2b(data))</span><br><span style="color: hsl(120, 100%, 40%);">+        return list(value)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def set_data(self, tag:int, data_hex:str):</span><br><span style="color: hsl(120, 100%, 40%);">+        """Update a TLV/DO with given binary data</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        Args:</span><br><span style="color: hsl(120, 100%, 40%);">+            tag : Tag of TLV/DO to be written</span><br><span style="color: hsl(120, 100%, 40%);">+            data_hex : Hex string binary data to be written (value portion)</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        if not isinstance(self.selected_file, BerTlvEF):</span><br><span style="color: hsl(120, 100%, 40%);">+            raise TypeError("Only works with BER-TLV EF")</span><br><span style="color: hsl(120, 100%, 40%);">+        return self.card._scc.set_data(self.selected_file.fid, tag, data_hex, conserve=self.conserve_write)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> </span><br><span> </span><br><span> class FileData(object):</span><br><span>diff --git a/pySim/ts_102_221.py b/pySim/ts_102_221.py</span><br><span>index 2c335a6..bb49fe5 100644</span><br><span>--- a/pySim/ts_102_221.py</span><br><span>+++ b/pySim/ts_102_221.py</span><br><span>@@ -113,10 +113,14 @@</span><br><span>         1: 'transparent',</span><br><span>         2: 'linear_fixed',</span><br><span>         6: 'cyclic',</span><br><span style="color: hsl(120, 100%, 40%);">+     0x39: 'ber_tlv',</span><br><span>     }</span><br><span>     fdb = in_bin[0]</span><br><span>     ftype = (fdb >> 3) & 7</span><br><span style="color: hsl(0, 100%, 40%);">-    fstruct = fdb & 7</span><br><span style="color: hsl(120, 100%, 40%);">+    if fdb & 0xbf == 0x39:</span><br><span style="color: hsl(120, 100%, 40%);">+        fstruct = 0x39</span><br><span style="color: hsl(120, 100%, 40%);">+    else:</span><br><span style="color: hsl(120, 100%, 40%);">+        fstruct = fdb & 7</span><br><span>     out['shareable'] = True if fdb & 0x40 else False</span><br><span>     out['file_type'] = ft_dict[ftype] if ftype in ft_dict else ftype</span><br><span>     out['structure'] = fs_dict[fstruct] if fstruct in fs_dict else fstruct</span><br><span>diff --git a/pySim/utils.py b/pySim/utils.py</span><br><span>index 3e67386..a3cd1b5 100644</span><br><span>--- a/pySim/utils.py</span><br><span>+++ b/pySim/utils.py</span><br><span>@@ -89,6 +89,85 @@</span><br><span> def half_round_up(n:int) -> int:</span><br><span>    return (n + 1)//2</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+#########################################################################</span><br><span style="color: hsl(120, 100%, 40%);">+# poor man's BER-TLV decoder. To be a more sophisticated OO library later</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 bertlv_parse_tag(binary:bytes) -> Tuple[dict, bytes]:</span><br><span style="color: hsl(120, 100%, 40%);">+    """Parse a single Tag value according to ITU-T X.690 8.1.2</span><br><span style="color: hsl(120, 100%, 40%);">+     Args:</span><br><span style="color: hsl(120, 100%, 40%);">+         binary : binary input data of BER-TLV length field</span><br><span style="color: hsl(120, 100%, 40%);">+    Returns:</span><br><span style="color: hsl(120, 100%, 40%);">+              Tuple of ({class:int, constructed:bool, tag:int}, remainder:bytes)</span><br><span style="color: hsl(120, 100%, 40%);">+    """</span><br><span style="color: hsl(120, 100%, 40%);">+    cls = binary[0] >> 6</span><br><span style="color: hsl(120, 100%, 40%);">+    constructed = True if binary[0] & 0x20 else False</span><br><span style="color: hsl(120, 100%, 40%);">+ tag = binary[0] & 0x1f</span><br><span style="color: hsl(120, 100%, 40%);">+    if tag <= 30:</span><br><span style="color: hsl(120, 100%, 40%);">+              return ({'class':cls, 'constructed':constructed, 'tag': tag}, binary[1:])</span><br><span style="color: hsl(120, 100%, 40%);">+     else: # multi-byte tag</span><br><span style="color: hsl(120, 100%, 40%);">+                tag = 0</span><br><span style="color: hsl(120, 100%, 40%);">+               i = 1</span><br><span style="color: hsl(120, 100%, 40%);">+         last = False</span><br><span style="color: hsl(120, 100%, 40%);">+          while not last:</span><br><span style="color: hsl(120, 100%, 40%);">+                       last = False if binary[i] & 0x80 else True</span><br><span style="color: hsl(120, 100%, 40%);">+                        tag <<= 7</span><br><span style="color: hsl(120, 100%, 40%);">+                       tag |= binary[i] & 0x7f</span><br><span style="color: hsl(120, 100%, 40%);">+                   i += 1</span><br><span style="color: hsl(120, 100%, 40%);">+                return ({'class':cls, 'constructed':constructed, 'tag':tag}, binary[i:])</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def bertlv_parse_len(binary:bytes) -> Tuple[int, bytes]:</span><br><span style="color: hsl(120, 100%, 40%);">+       """Parse a single Length value according to ITU-T X.690 8.1.3;</span><br><span style="color: hsl(120, 100%, 40%);">+ only the definite form is supported here.</span><br><span style="color: hsl(120, 100%, 40%);">+     Args:</span><br><span style="color: hsl(120, 100%, 40%);">+         binary : binary input data of BER-TLV length field</span><br><span style="color: hsl(120, 100%, 40%);">+    Returns:</span><br><span style="color: hsl(120, 100%, 40%);">+              Tuple of (length, remainder)</span><br><span style="color: hsl(120, 100%, 40%);">+  """</span><br><span style="color: hsl(120, 100%, 40%);">+    if binary[0] < 0x80:</span><br><span style="color: hsl(120, 100%, 40%);">+               return (binary[0], binary[1:])</span><br><span style="color: hsl(120, 100%, 40%);">+        else:</span><br><span style="color: hsl(120, 100%, 40%);">+         num_len_oct = binary[0] & 0x7f</span><br><span style="color: hsl(120, 100%, 40%);">+            length = 0</span><br><span style="color: hsl(120, 100%, 40%);">+            for i in range(1, 1+num_len_oct):</span><br><span style="color: hsl(120, 100%, 40%);">+                     length <<= 8</span><br><span style="color: hsl(120, 100%, 40%);">+                    length |= binary[i]</span><br><span style="color: hsl(120, 100%, 40%);">+           return (length, binary[num_len_oct:])</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def bertlv_encode_len(length:int) -> bytes:</span><br><span style="color: hsl(120, 100%, 40%);">+       """Encode a single Length value according to ITU-T X.690 8.1.3;</span><br><span style="color: hsl(120, 100%, 40%);">+        only the definite form is supported here.</span><br><span style="color: hsl(120, 100%, 40%);">+     Args:</span><br><span style="color: hsl(120, 100%, 40%);">+         length : length value to be encoded</span><br><span style="color: hsl(120, 100%, 40%);">+   Returns:</span><br><span style="color: hsl(120, 100%, 40%);">+              binary output data of BER-TLV length field</span><br><span style="color: hsl(120, 100%, 40%);">+    """</span><br><span style="color: hsl(120, 100%, 40%);">+    if length < 0x80:</span><br><span style="color: hsl(120, 100%, 40%);">+          return length.to_bytes(1, 'big')</span><br><span style="color: hsl(120, 100%, 40%);">+      elif length <= 0xff:</span><br><span style="color: hsl(120, 100%, 40%);">+               return b'\x81' + length.to_bytes(1, 'big')</span><br><span style="color: hsl(120, 100%, 40%);">+    elif length <= 0xffff:</span><br><span style="color: hsl(120, 100%, 40%);">+             return b'\x82' + length.to_bytes(2, 'big')</span><br><span style="color: hsl(120, 100%, 40%);">+    elif length <= 0xffffff:</span><br><span style="color: hsl(120, 100%, 40%);">+           return b'\x83' + length.to_bytes(3, 'big')</span><br><span style="color: hsl(120, 100%, 40%);">+    elif length <= 0xffffffff:</span><br><span style="color: hsl(120, 100%, 40%);">+         return b'\x84' + length.to_bytes(4, 'big')</span><br><span style="color: hsl(120, 100%, 40%);">+    else:</span><br><span style="color: hsl(120, 100%, 40%);">+         raise ValueError("Length > 32bits not supported")</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def bertlv_parse_one(binary:bytes) -> (dict, int, bytes):</span><br><span style="color: hsl(120, 100%, 40%);">+        """Parse a single TLV IE at the start of the given binary data.</span><br><span style="color: hsl(120, 100%, 40%);">+        Args:</span><br><span style="color: hsl(120, 100%, 40%);">+         binary : binary input data of BER-TLV length field</span><br><span style="color: hsl(120, 100%, 40%);">+    Returns:</span><br><span style="color: hsl(120, 100%, 40%);">+              Tuple of (tag:dict, len:int, remainder:bytes)</span><br><span style="color: hsl(120, 100%, 40%);">+ """</span><br><span style="color: hsl(120, 100%, 40%);">+    (tagdict, remainder) = bertlv_parse_tag(binary)</span><br><span style="color: hsl(120, 100%, 40%);">+       (length, remainder) = bertlv_parse_len(remainder)</span><br><span style="color: hsl(120, 100%, 40%);">+     return (tagdict, length, remainder)</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%);">+</span><br><span> # IMSI encoded format:</span><br><span> # For IMSI 0123456789ABCDE:</span><br><span> #</span><br><span>@@ -894,6 +973,10 @@</span><br><span>               table.append(format_str_row % tuple(str_list_row))</span><br><span>   return '\n'.join(table)</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+def auto_int(x):</span><br><span style="color: hsl(120, 100%, 40%);">+    """Helper function for argparse to accept hexadecimal integers."""</span><br><span style="color: hsl(120, 100%, 40%);">+    return int(x, 0)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> class JsonEncoder(json.JSONEncoder):</span><br><span>     """Extend the standard library JSONEncoder with support for more types."""</span><br><span>     def default(self, o):</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.osmocom.org/c/pysim/+/24102">change 24102</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/+/24102"/><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: Ibfcce757dcd477fd0d6857f64fbb4346d6d62e63 </div>
<div style="display:none"> Gerrit-Change-Number: 24102 </div>
<div style="display:none"> Gerrit-PatchSet: 3 </div>
<div style="display:none"> Gerrit-Owner: laforge <laforge@osmocom.org> </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: fixeria <vyanitskiy@sysmocom.de> </div>
<div style="display:none"> Gerrit-Reviewer: laforge <laforge@osmocom.org> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>