laforge has uploaded this change for review. ( 
https://gerrit.osmocom.org/c/pysim/+/37011?usp=email )


Change subject: add contrib/saip-tool.py
......................................................................

add contrib/saip-tool.py

This is a tool to work with eSIM profiles in SAIP format.  It allows
to dump the contents, run constraint checkers as well as splitting
of the PE-Sequence into the individual PEs.

Change-Id: I396bcd594e0628dfc26bd90233317a77e2f91b20
---
A contrib/saip-tool.py
1 file changed, 176 insertions(+), 0 deletions(-)



  git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/11/37011/1

diff --git a/contrib/saip-tool.py b/contrib/saip-tool.py
new file mode 100755
index 0000000..eae906c
--- /dev/null
+++ b/contrib/saip-tool.py
@@ -0,0 +1,163 @@
+#!/usr/bin/env python3
+
+# (C) 2024 by Harald Welte <lafo...@osmocom.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import sys
+import argparse
+import logging
+from pathlib import Path
+from typing import List
+
+from pySim.esim.saip import *
+from pySim.esim.saip.validation import CheckBasicStructure
+from pySim.utils import h2b, b2h, swap_nibbles
+from pySim.pprint import HexBytesPrettyPrinter
+
+pp = HexBytesPrettyPrinter(indent=4,width=500)
+
+logging.basicConfig(level=logging.DEBUG)
+
+parser = argparse.ArgumentParser(description="""
+Utility program to work with eSIM SAIP (SimAlliance Interoperable Profile) 
files.""")
+parser.add_argument('INPUT_UPP', help='Unprotected Profile Package Input file')
+subparsers = parser.add_subparsers(dest='command', help="The command to 
perform", required=True)
+
+parser_split = subparsers.add_parser('split', help='Split PE-Sequence into 
individual PEs')
+parser_split.add_argument('--output-prefix', default='.', help='Prefix 
path/filename for output files')
+
+parser_dump = subparsers.add_parser('dump', help='Dump information on 
PE-Sequence')
+parser_dump.add_argument('mode', choices=['all_pe', 'all_pe_by_type', 
'all_pe_by_naa'])
+parser_dump.add_argument('--dump-decoded', action='store_true', help='Dump 
decoded PEs')
+
+parser_check = subparsers.add_parser('check', help='Run constraint checkers on 
PE-Sequence')
+
+parser_rpe = subparsers.add_parser('remove-pe', help='Remove specified PEs 
from PE-Sequence')
+parser_rpe.add_argument('--output-file', required=True, help='Output file 
name')
+parser_rpe.add_argument('--identification', type=int, action='append', 
help='Remove PEs matching specified identification')
+
+parser_rn = subparsers.add_parser('remove-naa', help='Remove speciifed NAAs 
from PE-Sequence')
+parser_rn.add_argument('--output-file', required=True, help='Output file name')
+parser_rn.add_argument('--naa-type', required=True, choices=NAAs.keys(), 
help='Network Access Application type to remove')
+# TODO: add an --naa-index or the like, so only one given instance can be 
removed
+
+
+def do_split(pes: ProfileElementSequence, opts):
+    i = 0
+    for pe in pes.pe_list:
+        basename = Path(opts.INPUT_UPP).stem
+        if not pe.identification:
+            fname = '%s-%02u-%s.der' % (basename, i, pe.type)
+        else:
+            fname = '%s-%02u-%05u-%s.der' % (basename, i, pe.identification, 
pe.type)
+        print("writing single PE to file '%s'" % fname)
+        with open(os.path.join(opts.output_prefix, fname), 'wb') as outf:
+            outf.write(pe.to_der())
+        i += 1
+
+def do_dump(pes: ProfileElementSequence, opts):
+    def print_all_pe(pes: ProfileElementSequence, dump_decoded:bool = False):
+        # iterate over each pe in the pes (using its __iter__ method)
+        for pe in pes:
+            print("="*70 + " " + pe.type)
+            if dump_decoded:
+                pp.pprint(pe.decoded)
+
+    def print_all_pe_by_type(pes: ProfileElementSequence, dump_decoded:bool = 
False):
+        # sort by PE type and show all PE within that type
+        for pe_type in pes.pe_by_type.keys():
+            print("="*70 + " " + pe_type)
+            for pe in pes.pe_by_type[pe_type]:
+                pp.pprint(pe)
+                if dump_decoded:
+                    pp.pprint(pe.decoded)
+
+    def print_all_pe_by_naa(pes: ProfileElementSequence, dump_decoded:bool = 
False):
+        for naa in pes.pes_by_naa:
+            i = 0
+            for naa_instance in pes.pes_by_naa[naa]:
+                print("="*70 + " " + naa + str(i))
+                i += 1
+                for pe in naa_instance:
+                    pp.pprint(pe.type)
+                    if dump_decoded:
+                        for d in pe.decoded:
+                            print("    %s" % d)
+                            #pp.pprint(pe.decoded[d])
+                        #if pe.type in ['akaParameter', 'pinCodes', 
'pukCodes']:
+                        #    pp.pprint(pe.decoded)
+
+    if opts.mode == 'all_pe':
+        print_all_pe(pes, opts.dump_decoded)
+    elif opts.mode == 'all_pe_by_type':
+        print_all_pe_by_type(pes, opts.dump_decoded)
+    elif opts.mode == 'all_pe_by_naa':
+        print_all_pe_by_naa(pes, opts.dump_decoded)
+
+def do_check(pes: ProfileElementSequence, opts):
+    print("Checking PE-Sequence structure...")
+    checker = CheckBasicStructure()
+    checker.check(pes)
+    print("All good!")
+
+def do_remove_pe(pes: ProfileElementSequence, opts):
+    new_pe_list = []
+    for pe in pes.pe_list:
+        identification = pe.identification
+        if identification:
+            if identification in opts.identification:
+                print("Removing PE %s (id=%u) from Sequence..." % (pe, 
identification))
+                continue
+        new_pe_list.append(pe)
+
+    pes.pe_list = new_pe_list
+    pes._process_pelist()
+    print("Writing %u PEs to file '%s'..." % (len(pes.pe_list), 
opts.output_file))
+    with open(opts.output_file, 'wb') as f:
+        f.write(pes.to_der())
+
+def do_remove_naa(pes: ProfileElementSequence, opts):
+    if not opts.naa_type in NAAs:
+        raise ValueError('unsupported NAA type %s' % opts.naa_type)
+    naa = NAAs[opts.naa_type]
+    print("Removing NAAs of type '%s' from Sequence..." % opts.naa_type)
+    pes.remove_naas_of_type(naa)
+    print("Writing %u PEs to file '%s'..." % (len(pes.pe_list), 
opts.output_file))
+    with open(opts.output_file, 'wb') as f:
+        f.write(pes.to_der())
+
+
+if __name__ == '__main__':
+    opts = parser.parse_args()
+
+    with open(opts.INPUT_UPP, 'rb') as f:
+        pes = ProfileElementSequence.from_der(f.read())
+
+    print("Read %u PEs from file '%s'" % (len(pes.pe_list), opts.INPUT_UPP))
+
+    if opts.command == 'split':
+        do_split(pes, opts)
+    elif opts.command == 'dump':
+        do_dump(pes, opts)
+    elif opts.command == 'check':
+        do_check(pes, opts)
+    elif opts.command == 'remove-pe':
+        do_remove_pe(pes, opts)
+    elif opts.command == 'remove-naa':
+        do_remove_naa(pes, opts)
+    else:
+        parser.print_help(sys.stderr)
+        sys.exit(2)

--
To view, visit https://gerrit.osmocom.org/c/pysim/+/37011?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings

Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I396bcd594e0628dfc26bd90233317a77e2f91b20
Gerrit-Change-Number: 37011
Gerrit-PatchSet: 1
Gerrit-Owner: laforge <lafo...@osmocom.org>
Gerrit-MessageType: newchange

Reply via email to