Change in pysim[master]: pySim-shell: add bulk provisioning support

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

dexter gerrit-no-reply at lists.osmocom.org
Thu Sep 23 09:38:10 UTC 2021


dexter has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/25552 )


Change subject: pySim-shell: add bulk provisioning support
......................................................................

pySim-shell: add bulk provisioning support

There are scenarios where multiple cards need to get the same change.
Lets add a new command that takes a script as parameter and executes the
secript in a loop on multiple cards while prompting the user to change
the card before starting the next cycle.

Change-Id: I9e4926675c5a497a22fc6a4fefdd388fe18a2b2d
Related: SYS#5617
---
M pySim-shell.py
1 file changed, 182 insertions(+), 4 deletions(-)



  git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/52/25552/1

diff --git a/pySim-shell.py b/pySim-shell.py
index 6a49080..1ca5cb2 100755
--- a/pySim-shell.py
+++ b/pySim-shell.py
@@ -30,6 +30,7 @@
 import os
 import sys
 from pathlib import Path
+from io import StringIO
 
 from pySim.ts_51_011 import EF, DF, EF_SST_map
 from pySim.ts_31_102 import EF_UST_map, EF_USIM_ADF_map
@@ -41,7 +42,7 @@
 from pySim.cards import card_detect, SimCard
 from pySim.utils import h2b, swap_nibbles, rpad, b2h, h2s, JsonEncoder, bertlv_parse_one
 from pySim.utils import dec_st, sanitize_pin_adm, tabulate_str_list, is_hex, boxed_heading_str
-from pySim.card_handler import CardHandler
+from pySim.card_handler import CardHandler, CardHandlerAuto
 
 from pySim.filesystem import CardMF, RuntimeState, CardDF, CardADF
 from pySim.ts_51_011 import CardProfileSIM, DF_TELECOM, DF_GSM
@@ -92,7 +93,7 @@
 
 class PysimApp(cmd2.Cmd):
 	CUSTOM_CATEGORY = 'pySim Commands'
-	def __init__(self, card, rs, script = None):
+	def __init__(self, card, rs, sl, ch, script = None):
 		basic_commands = []
 		super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False,
 				 use_ipython=True, auto_load_commands=False, startup_script=script)
@@ -102,6 +103,8 @@
 		self.conserve_write = True
 		self.json_pretty_print = True
 		self.apdu_trace = False
+		self.sl = sl
+		self.ch = ch
 
 		self.add_settable(cmd2.Settable('numeric_path', bool, 'Print File IDs instead of names',
 						onchange_cb=self._onchange_numeric_path))
@@ -122,6 +125,8 @@
 		and commands to enable card operations.
 		"""
 
+		rc = False
+
 		# Unequip everything from pySim-shell that would not work in unequipped state
 		if self.rs:
 			self.rs.unregister_cmds(self)
@@ -146,10 +151,12 @@
 							onchange_cb=self._onchange_conserve_write))
 			self.iccid, sw = self.card.read_iccid()
 			rs.select('MF', self)
+			rc = True
 		else:
 			self.poutput("pySim-shell not equipped!")
 
 		self.update_prompt()
+		return rc
 
 	def poutput_json(self, data, force_no_pretty = False):
 		"""like cmd2.poutput() but for a JSON serializable dict."""
@@ -197,6 +204,170 @@
 		rs, card = init_card(sl);
 		self.equip(card, rs)
 
+	class InterceptStderr(list):
+		def __init__(self):
+			self._stderr_backup = sys.stderr
+		def __enter__(self):
+			self._stringio_stderr = StringIO()
+			sys.stderr = self._stringio_stderr
+			return self
+		def __exit__(self, *args):
+			self.stderr = self._stringio_stderr.getvalue().strip()
+			del self._stringio_stderr
+			sys.stderr = self._stderr_backup
+
+	def _show_failure_sign(self):
+		print("  +-------------+")
+		print("  +   ##   ##   +")
+		print("  +    ## ##    +")
+		print("  +     ###     +")
+		print("  +    ## ##    +")
+		print("  +   ##   ##   +")
+		print("  +-------------+")
+		print("")
+
+	def _show_success_sign(self):
+		print("  +-------------+")
+		print("  +          ## +")
+		print("  +         ##  +")
+		print("  +  #    ##    +")
+		print("  +   ## #      +")
+		print("  +    ##       +")
+		print("  +-------------+")
+		print("")
+
+	def _process_card(self, first, script_path):
+
+		# Eearly phase of card initialzation (this part may fail with an exception)
+		try:
+			rs, card = init_card(self.sl)
+			rc = self.equip(card, rs)
+		except:
+			self.poutput("")
+			self.poutput("Card initialization failed with an execption:")
+			self.poutput("---------------------8<---------------------")
+			traceback.print_exc()
+			self.poutput("---------------------8<---------------------")
+			self.poutput("")
+			return -1
+
+		# Actual card processing step. This part should never fail with an exception since the cmd2
+		# do_run_script method will catch any exception that might occur during script execution.
+		if rc:
+			self.poutput("")
+			self.poutput("Transcript stdout:")
+			self.poutput("---------------------8<---------------------")
+			with self.InterceptStderr() as logged:
+				self.do_run_script(script_path)
+			self.poutput("---------------------8<---------------------")
+
+			self.poutput("")
+			self.poutput("Transcript stderr:")
+			if logged.stderr:
+				self.poutput("---------------------8<---------------------")
+				self.poutput(logged.stderr)
+				self.poutput("---------------------8<---------------------")
+			else:
+				self.poutput("(none)")
+
+			# Check for exceptions
+			self.poutput("")
+			if "EXCEPTION of type" not in logged.stderr:
+				return 0
+
+		return -1
+
+	bulk_script_parser = argparse.ArgumentParser()
+	bulk_script_parser.add_argument('script_path', help="path to the script file")
+	bulk_script_parser.add_argument('--halt_on_error', help='stop card handling if an exeption occurs',
+					action='store_true')
+	bulk_script_parser.add_argument('--tries', type=int, default=2,
+					help='how many tries before trying the next card')
+	bulk_script_parser.add_argument('--on_stop_action', type=str, default=None,
+					help='commandline to execute when card handling has stopped')
+	bulk_script_parser.add_argument('--pre_card_action', type=str, default=None,
+					help='commandline to execute before actually talking to the card')
+
+	@cmd2.with_argparser(bulk_script_parser)
+	@cmd2.with_category(CUSTOM_CATEGORY)
+	def do_bulk_script(self, opts):
+		"""Run script on multiple cards (bulk provisioning)"""
+
+		# Make sure that the script file exists and that it is readable.
+		if not os.access(opts.script_path, os.R_OK):
+			self.poutput("Invalid script file!")
+			return
+
+		success_count = 0
+		fail_count = 0
+
+		first = True
+		while 1:
+			# TODO: Count consecutive failures, if more than N consecutive failures occur, then stop.
+			# The ratinale is: There may be a problem with the device, we do want to prevent that
+			# all remaining cards are fired to the error bin. This is only relevant for situations
+			# with large stacks, probably we do not need this feature right now.
+
+			try:
+				# In case of failure, try multiple times.
+				for i in range(opts.tries):
+					# fetch card into reader bay
+					ch.get(first)
+
+					# if necessary execute an action before we start processing the card
+					if(opts.pre_card_action):
+						os.system(opts.pre_card_action)
+
+					# process the card
+					rc = self._process_card(first, opts.script_path)
+					if rc == 0:
+						success_count = success_count + 1
+						self._show_success_sign()
+						print("Statistics: success :%i, failure: %i" % (success_count, fail_count))
+						break
+					else:
+						fail_count = fail_count + 1
+						self._show_failure_sign()
+						print("Statistics: success :%i, failure: %i" % (success_count, fail_count))
+
+
+				# Depending on success or failure, the card goes either in the "error" bin or in the
+				# "done" bin.
+				if rc < 0:
+					ch.error()
+				else:
+					ch.done()
+
+				# In most cases it is possible to proceed with the next card, but the
+				# user may decide to halt immediately when an error occurs
+				if opts.halt_on_error and rc < 0:
+					return
+
+			except (KeyboardInterrupt):
+				self.poutput("")
+				self.poutput("Terminated by user!")
+				return;
+			except (SystemExit):
+				# When all cards are processed the card handler device will throw a SystemExit
+				# execption. Also Errors that are not recoverable (cards stuck etc.) will end up here.
+				# The user has the option to execute some action to make aware that the card handler
+				# needs service.
+				if(opts.on_stop_action):
+					os.system(opts.on_stop_action)
+				return
+			except:
+				self.poutput("")
+				self.poutput("Card handling failed with an execption:")
+				self.poutput("---------------------8<---------------------")
+				traceback.print_exc()
+				self.poutput("---------------------8<---------------------")
+				self.poutput("")
+				fail_count = fail_count + 1
+				self._show_failure_sign()
+				print("Statistics: success :%i, failure: %i" % (success_count, fail_count))
+
+			first = False
+
 	echo_parser = argparse.ArgumentParser()
 	echo_parser.add_argument('string', help="string to echo on the shell")
 
@@ -548,6 +719,8 @@
 global_group.add_argument('--script', metavar='PATH', default=None,
                           help='script with pySim-shell commands to be executed automatically at start-up')
 global_group.add_argument('--csv', metavar='FILE', default=None, help='Read card data from CSV file')
+global_group.add_argument("--card_handler", dest="card_handler_config", metavar="FILE",
+			  help="Use automatic card handling machine")
 
 adm_group = global_group.add_mutually_exclusive_group()
 adm_group.add_argument('-a', '--pin-adm', metavar='PIN_ADM1', dest='pin_adm', default=None,
@@ -583,13 +756,18 @@
 	# Create command layer
 	scc = SimCardCommands(transport=sl)
 
+	# Create a card handler (for bulk provisioning)
+	if opts.card_handler_config:
+		ch = CardHandlerAuto(None, opts.card_handler_config)
+	else:
+		ch = CardHandler(sl)
 
 	# Detect and initalize the card in the reader. This may fail when there
 	# is no card in the reader or the card is unresponsive. PysimApp is
 	# able to tolerate and recover from that.
 	try:
 		rs, card = init_card(sl)
-		app = PysimApp(card, rs, opts.script)
+		app = PysimApp(card, rs, sl, ch, opts.script)
 	except:
 		print("Card initalization failed with an execption:")
 		print("---------------------8<---------------------")
@@ -599,7 +777,7 @@
 		print(" it should also be noted that some readers may behave strangely when no card")
 		print(" is inserted.)")
 		print("")
-		app = PysimApp(None, None, opts.script)
+		app = PysimApp(None, None, sl, ch, opts.script)
 
 	# If the user supplies an ADM PIN at via commandline args authenticate
 	# immediately so that the user does not have to use the shell commands

-- 
To view, visit https://gerrit.osmocom.org/c/pysim/+/25552
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I9e4926675c5a497a22fc6a4fefdd388fe18a2b2d
Gerrit-Change-Number: 25552
Gerrit-PatchSet: 1
Gerrit-Owner: dexter <pmaier at sysmocom.de>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20210923/cf506c91/attachment.htm>


More information about the gerrit-log mailing list