From d16a20ccc3e67ffab1142d4f0541989ccc36a7ec Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Sun, 28 Jan 2024 22:02:07 +0100 Subject: 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 --- pySim/esim/saip/__init__.py | 124 ++++++++++++++++++++++++++++++++++++++++++ pySim/esim/saip/validation.py | 33 +++++++++++ 2 files changed, 157 insertions(+) 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 . 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.""" + -- cgit v1.2.3