filesystem: Maintain a 'service' attribute for all files on a card

This can be populated by card profiles with the SST/IST/UST service
that is associated with the file.

Change-Id: I3b3f74b691368fa09967ecb377a9f7a6d8af7869
diff --git a/pySim/filesystem.py b/pySim/filesystem.py
index b8b6152..d52a16e 100644
--- a/pySim/filesystem.py
+++ b/pySim/filesystem.py
@@ -34,7 +34,7 @@
 from cmd2 import CommandSet, with_default_category, with_argparser
 import argparse
 
-from typing import cast, Optional, Iterable, List, Dict, Tuple
+from typing import cast, Optional, Iterable, List, Dict, Tuple, Union
 
 from smartcard.util import toBytes
 
@@ -44,6 +44,10 @@
 from pySim.jsonpath import js_path_find, js_path_modify
 from pySim.commands import SimCardCommands
 
+# int: a single service is associated with this file
+# list: any of the listed services requires this file
+# tuple: logical-and of the listed services requires this file
+CardFileService = Union[int, List[int], Tuple[int, ...]]
 
 class CardFile(object):
     """Base class for all objects in the smart card filesystem.
@@ -53,7 +57,8 @@
     RESERVED_FIDS = ['3f00']
 
     def __init__(self, fid: str = None, sfid: str = None, name: str = None, desc: str = None,
-                 parent: Optional['CardDF'] = None, profile: Optional['CardProfile'] = None):
+                 parent: Optional['CardDF'] = None, profile: Optional['CardProfile'] = None,
+                 service: Optional[CardFileService] = None):
         """
         Args:
             fid : File Identifier (4 hex digits)
@@ -62,6 +67,7 @@
             desc : Description of the file
             parent : Parent CardFile object within filesystem hierarchy
             profile : Card profile that this file should be part of
+            service : Service (SST/UST/IST) associated with the file
         """
         if not isinstance(self, CardADF) and fid == None:
             raise ValueError("fid is mandatory")
@@ -75,6 +81,7 @@
         if self.parent and self.parent != self and self.fid:
             self.parent.add_file(self)
         self.profile = profile
+        self.service = service
         self.shell_commands = []  # type: List[CommandSet]
 
         # Note: the basic properties (fid, name, ect.) are verified when
@@ -207,6 +214,28 @@
             return self.parent.get_profile()
         return None
 
+    def should_exist_for_services(self, services: List[int]):
+        """Assuming the provided list of activated services, should this file exist and be activated?."""
+        if self.service is None:
+            return None
+        elif isinstance(self.service, int):
+            # a single service determines the result
+            return self.service in services
+        elif isinstance(self.service, list):
+            # any of the services active -> true
+            for s in self.service:
+                if s in services:
+                    return True
+            return False
+        elif isinstance(self.service, tuple):
+            # all of the services active -> true
+            for s in self.service:
+                if not s in services:
+                    return False
+            return True
+        else:
+            raise ValueError("self.service must be either int or list or tuple")
+
 
 class CardDF(CardFile):
     """DF (Dedicated File) in the smart card filesystem.  Those are basically sub-directories."""
@@ -223,10 +252,27 @@
         super().__init__(**kwargs)
         self.children = dict()
         self.shell_commands = [self.ShellCommands()]
+        # dict of CardFile affected by service(int), indexed by service
+        self.files_by_service = {}
 
     def __str__(self):
         return "DF(%s)" % (super().__str__())
 
+    def _add_file_services(self, child):
+        """Add a child (DF/EF) to the files_by_services of the parent."""
+        if not child.service:
+            return
+        if isinstance(child.service, int):
+            self.files_by_service.setdefault(child.service, []).append(child)
+        elif isinstance(child.service, list):
+            for service in child.service:
+                self.files_by_service.setdefault(service, []).append(child)
+        elif isinstance(child.service, tuple):
+            for service in child.service:
+                self.files_by_service.setdefault(service, []).append(child)
+        else:
+            raise ValueError
+
     def add_file(self, child: CardFile, ignore_existing: bool = False):
         """Add a child (DF/EF) to this DF.
         Args:
@@ -256,6 +302,7 @@
                 "File with given name %s already exists in %s" % (child.name, self))
         self.children[child.fid] = child
         child.parent = self
+        self._add_file_services(child)
 
     def add_files(self, children: Iterable[CardFile], ignore_existing: bool = False):
         """Add a list of child (DF/EF) to this DF
@@ -519,7 +566,7 @@
                         self._cmd.poutput_json(data)
 
     def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
-                 size={1, None}):
+                 size={1, None}, **kwargs):
         """
         Args:
             fid : File Identifier (4 hex digits)
@@ -529,7 +576,7 @@
             parent : Parent CardFile object within filesystem hierarchy
             size : tuple of (minimum_size, recommended_size)
         """
-        super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
+        super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
         self._construct = None
         self._tlv = None
         self.size = size
@@ -654,8 +701,8 @@
     class ShellCommands(CommandSet):
         """Shell commands specific for Linear Fixed EFs."""
 
-        def __init__(self):
-            super().__init__()
+        def __init__(self, **kwargs):
+            super().__init__(**kwargs)
 
         read_rec_parser = argparse.ArgumentParser()
         read_rec_parser.add_argument(
@@ -777,7 +824,7 @@
                         self._cmd.poutput_json(data)
 
     def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None,
-                 parent: Optional[CardDF] = None, rec_len={1, None}):
+                 parent: Optional[CardDF] = None, rec_len={1, None}, **kwargs):
         """
         Args:
             fid : File Identifier (4 hex digits)
@@ -787,7 +834,7 @@
             parent : Parent CardFile object within filesystem hierarchy
             rec_len : set of {minimum_length, recommended_length}
         """
-        super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
+        super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
         self.rec_len = rec_len
         self.shell_commands = [self.ShellCommands()]
         self._construct = None
@@ -908,9 +955,8 @@
     # we don't really have any special support for those; just recycling LinFixedEF here
 
     def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
-                 rec_len={1, None}):
-        super().__init__(fid=fid, sfid=sfid, name=name,
-                         desc=desc, parent=parent, rec_len=rec_len)
+                 rec_len={1, None}, **kwargs):
+        super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, rec_len=rec_len, **kwargs)
 
 
 class TransRecEF(TransparentEF):
@@ -924,7 +970,7 @@
     """
 
     def __init__(self, fid: str, rec_len: int, sfid: str = None, name: str = None, desc: str = None,
-                 parent: Optional[CardDF] = None, size={1, None}):
+                 parent: Optional[CardDF] = None, size={1, None}, **kwargs):
         """
         Args:
             fid : File Identifier (4 hex digits)
@@ -935,7 +981,7 @@
             rec_len : Length of the fixed-length records within transparent EF
             size : tuple of (minimum_size, recommended_size)
         """
-        super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size)
+        super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size, **kwargs)
         self.rec_len = rec_len
 
     def decode_record_hex(self, raw_hex_data: str) -> dict:
@@ -1112,7 +1158,7 @@
                 self._cmd.poutput(data)
 
     def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
-                 size={1, None}):
+                 size={1, None}, **kwargs):
         """
         Args:
             fid : File Identifier (4 hex digits)
@@ -1122,7 +1168,7 @@
             parent : Parent CardFile object within filesystem hierarchy
             size : tuple of (minimum_size, recommended_size)
         """
-        super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
+        super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
         self._construct = None
         self.size = size
         self.shell_commands = [self.ShellCommands()]