aboutsummaryrefslogtreecommitdiffstats
path: root/pySim
diff options
context:
space:
mode:
authorHarald Welte <laforge@osmocom.org>2024-01-28 22:02:07 +0100
committerHarald Welte <laforge@osmocom.org>2024-02-18 22:30:08 +0100
commitd16a20ccc3e67ffab1142d4f0541989ccc36a7ec (patch)
tree99d43910073ba913040de498cd2d805fde55c710 /pySim
parent54b4f0ccbd65832d4ff4ec2439642c6c0f08089c (diff)
saip: profile processing; merging with templates
Introduce code that makes use of the information from pySim.esim.saip.templates to build a complete representation of a file by merging the template with the ProfileElement decribing the file. This happens within the class pySim.esim.saip.File, whose instances are created from ProfileElement + Template. Change-Id: Ib1674920e488ade9597cb039e4e2047dcbc7864e
Diffstat (limited to 'pySim')
-rw-r--r--pySim/esim/saip/__init__.py124
-rw-r--r--pySim/esim/saip/validation.py33
2 files changed, 157 insertions, 0 deletions
diff --git a/pySim/esim/saip/__init__.py b/pySim/esim/saip/__init__.py
index 7bb2172..fb064a1 100644
--- a/pySim/esim/saip/__init__.py
+++ b/pySim/esim/saip/__init__.py
@@ -16,16 +16,117 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import abc
+import io
from typing import Tuple, List, Optional, Dict, Union
import asn1tools
from pySim.utils import bertlv_parse_tag, bertlv_parse_len
+from pySim.ts_102_221 import FileDescriptor
+from pySim.construct import build_construct
from pySim.esim import compile_asn1_subdir
+import pySim.esim.saip.templates as templates
asn1 = compile_asn1_subdir('saip')
+class File:
+ """Internal representation of a file in a profile filesystem."""
+ def __init__(self, pename: str, l: Optional[List[Tuple]] = None, template: Optional[templates.FileTemplate] = None):
+ self.pe_name = pename
+ self.template = template
+ self.fileDescriptor = {}
+ self.stream = None
+ # apply some defaults from profile
+ if self.template:
+ self.from_template(self.template)
+ print("after template: %s" % repr(self))
+ if l:
+ self.from_tuples(l)
+
+ def from_template(self, template: templates.FileTemplate):
+ """Determine defaults for file based on given FileTemplate."""
+ fdb_dec = {}
+ self.rec_len = None
+ if template.fid:
+ self.fileDescriptor['fileID'] = template.fid.to_bytes(2, 'big')
+ if template.sfi:
+ self.fileDescriptor['shortEFID'] = bytes([template.sfi])
+ if template.arr:
+ self.fileDescriptor['securityAttributesReferenced'] = bytes([template.arr])
+ # All the files defined in the templates shall have, by default, shareable/not-shareable bit in the file descriptor set to "shareable".
+ fdb_dec['shareable'] = True
+ if template.file_type in ['LF', 'CY']:
+ fdb_dec['file_type'] = 'working_ef'
+ if template.rec_len:
+ self.record_len = template.rec_len
+ if template.nb_rec and template.rec_len:
+ self.fileDescriptor['efFileSize'] = (template.nb_rec * template.rec_len).to_bytes(2, 'big') # FIXME
+ if template.file_type == 'LF':
+ fdb_dec['structure'] = 'linear_fixed'
+ elif template.file_type == 'CY':
+ fdb_dec['structure'] = 'cyclic'
+ elif template.file_type in ['TR', 'BT']:
+ fdb_dec['file_type'] = 'working_ef'
+ if template.file_size:
+ self.fileDescriptor['efFileSize'] = template.file_size.to_bytes(2, 'big') # FIXME
+ if template.file_type == 'BT':
+ fdb_dec['structure'] = 'ber_tlv'
+ elif template.file_type == 'TR':
+ fdb_dec['structure'] = 'transparent'
+ elif template.file_type in ['MF', 'DF', 'ADF']:
+ fdb_dec['file_type'] = 'df'
+ fdb_dec['structure'] = 'no_info_given'
+ # build file descriptor based on above input data
+ fd_dict = {'file_descriptor_byte': fdb_dec}
+ if self.rec_len:
+ fd_dict['record_len'] = self.rec_len
+ self.fileDescriptor['fileDescriptor'] = build_construct(FileDescriptor._construct, fd_dict)
+ # FIXME: default_val
+ # FIXME: high_update
+ # FIXME: params?
+
+ def from_tuples(self, l:List[Tuple]):
+ """Parse a list of fileDescriptor, fillFileContent, fillFileOffset tuples into this instance."""
+ def get_fileDescriptor(l:List[Tuple]):
+ for k, v in l:
+ if k == 'fileDescriptor':
+ return v
+ fd = get_fileDescriptor(l)
+ if not fd:
+ raise ValueError("No fileDescriptor found")
+ self.fileDescriptor.update(dict(fd))
+ self.stream = self.linearize_file_content(l)
+
+ def to_tuples(self) -> List[Tuple]:
+ """Generate a list of fileDescriptor, fillFileContent, fillFileOffset tuples into this instance."""
+ raise NotImplementedError
+
+ @staticmethod
+ def linearize_file_content(l: List[Tuple]) -> Optional[io.BytesIO]:
+ """linearize a list of fillFileContent + fillFileOffset tuples."""
+ stream = io.BytesIO()
+ for k, v in l:
+ if k == 'doNotCreate':
+ return None
+ if k == 'fileDescriptor':
+ pass
+ elif k == 'fillFileOffset':
+ stream.write(b'\xff' * v)
+ elif k == 'fillFileContent':
+ stream.write(v)
+ else:
+ return ValueError("Unknown key '%s' in tuple list" % k)
+ return stream
+
+ def __str__(self) -> str:
+ return "File(%s)" % self.pe_name
+
+ def __repr__(self) -> str:
+ return "File(%s): %s" % (self.pe_name, self.fileDescriptor)
+
class ProfileElement:
+ FILE_BEARING = ['mf', 'cd', 'telecom', 'usim', 'opt-usim', 'isim', 'opt-isim', 'phonebook', 'gsm-access',
+ 'csim', 'opt-csim', 'eap', 'df-5gs', 'df-saip', 'df-snpn', 'df-5gprose', 'iot', 'opt-iot']
def _fixup_sqnInit_dec(self) -> None:
"""asn1tools has a bug when working with SEQUENCE OF that have DEFAULT values. Let's work around
this."""
@@ -59,6 +160,29 @@ class ProfileElement:
# work around asn1tools bug regarding DEFAULT for a SEQUENCE OF
self._fixup_sqnInit_dec()
+ @property
+ def header_name(self) -> str:
+ # unneccessarry compliaction by inconsistent naming :(
+ if self.type.startswith('opt-'):
+ return self.type.replace('-','') + '-header'
+ else:
+ return self.type + '-header'
+
+ @property
+ def header(self):
+ return self.decoded.get(self.header_name, None)
+
+ @property
+ def templateID(self):
+ return self.decoded.get('templateID', None)
+
+ @property
+ def files(self):
+ """Return dict of decoded 'File' ASN.1 items."""
+ if not self.type in self.FILE_BEARING:
+ return {}
+ return {k:v for (k,v) in self.decoded.items() if k not in ['templateID', self.header_name]}
+
@classmethod
def from_der(cls, der: bytes) -> 'ProfileElement':
"""Construct an instance from given raw, DER encoded bytes."""
diff --git a/pySim/esim/saip/validation.py b/pySim/esim/saip/validation.py
index 2acc413..acf1864 100644
--- a/pySim/esim/saip/validation.py
+++ b/pySim/esim/saip/validation.py
@@ -94,3 +94,36 @@ class CheckBasicStructure(ProfileConstraintChecker):
raise ProfileError('profile-a-x25519 mandatory, but no usim or isim')
if 'profile-a-p256' in m_svcs and not not ('usim' in m_svcs or 'isim' in m_svcs):
raise ProfileError('profile-a-p256 mandatory, but no usim or isim')
+
+FileChoiceList = List[Tuple]
+
+class FileError(ProfileError):
+ pass
+
+class FileConstraintChecker:
+ def check(self, l: FileChoiceList):
+ for name in dir(self):
+ if name.startswith('check_'):
+ method = getattr(self, name)
+ method(l)
+
+class FileCheckBasicStructure(FileConstraintChecker):
+ def check_seqence(self, l: FileChoiceList):
+ by_type = {}
+ for k, v in l:
+ if k in by_type:
+ by_type[k].append(v)
+ else:
+ by_type[k] = [v]
+ if 'doNotCreate' in by_type:
+ if len(l) != 1:
+ raise FileError("doNotCreate must be the only element")
+ if 'fileDescriptor' in by_type:
+ if len(by_type['fileDescriptor']) != 1:
+ raise FileError("fileDescriptor must be the only element")
+ if l[0][0] != 'fileDescriptor':
+ raise FileError("fileDescriptor must be the first element")
+
+ def check_forbidden(self, l: FileChoiceList):
+ """Perform checks for forbidden parameters as described in Section 8.3.3."""
+