laforge has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/35733?usp=email )
Change subject: WIP: profile processing; merging with templates ......................................................................
WIP: profile processing; merging with templates
Change-Id: Ib1674920e488ade9597cb039e4e2047dcbc7864e --- M pySim/esim/saip/__init__.py M pySim/esim/saip/validation.py A saip-test.py 3 files changed, 261 insertions(+), 0 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/33/35733/1
diff --git a/pySim/esim/saip/__init__.py b/pySim/esim/saip/__init__.py index 9b182e5..d8bc4d7 100644 --- a/pySim/esim/saip/__init__.py +++ b/pySim/esim/saip/__init__.py @@ -16,12 +16,16 @@ # along with this program. If not, see http://www.gnu.org/licenses/.
import abc +import io from typing import Tuple, List, Optional, Dict
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')
@@ -81,8 +85,104 @@ IoT_default = eOID("2.17") IoT_default = eOID("2.18")
+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() -> 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): """asn1tools has a bug when working with SEQUENCE OF that have DEFAULT values. Let's work around this.""" @@ -116,6 +216,29 @@ # 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 @@ 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.""" + diff --git a/saip-test.py b/saip-test.py new file mode 100755 index 0000000..223bad7 --- /dev/null +++ b/saip-test.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 + +from pySim.utils import b2h, h2b +from pySim.esim.saip import * +from pySim.esim.saip.validation import * + +from pySim.pprint import HexBytesPrettyPrinter + +pp = HexBytesPrettyPrinter(indent=4,width=500) + +import abc + + + + +with open('smdpp-data/upp/TS48 V2 eSIM_GTP_SAIP2.3_NoBERTLV.rename2der', 'rb') as f: + pes = ProfileElementSequence.from_der(f.read()) + +if False: + for pe in pes.pe_list: + print("="*70 + " " + pe.type) + pp.pprint(pe.decoded) + +if False: + for pe in pes: + print("="*70 + " " + pe.type) + pp.pprint(pe.decoded) + +if False: + 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) + pp.pprint(pe.decoded) + +checker = CheckBasicStructure() +checker.check(pes) + +if 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) + for d in pe.decoded: + print(" %s" % d) + #pp.pprint(pe.decoded[d]) + #if pe.type in ['akaParameter', 'pinCodes', 'pukCodes']: + # pp.pprint(pe.decoded) + + +from pySim.esim.saip.personalization import * + +params = [Iccid(h2b('98494400000000000001')), + Puk1(value=b'01234567'), Puk2(value=b'98765432'), Pin1(b'1111'), Pin2(b'2222'), Adm1(b'11111111'), + K(h2b('000102030405060708090a0b0c0d0e0f')), Opc(h2b('101112131415161718191a1b1c1d1e1f'))] + +from pySim.esim.saip.templates import * +import pySim.esim.saip.templates.v31 + +for p in params: + p.apply(pes) + +if False: + for pe in pes: + pp.pprint(pe.decoded) + pass + +naas = pes.pes_by_naa.keys() +for naa in naas: + for pe in pes.pes_by_naa[naa][0]: + print(pe) + #pp.pprint(pe.decoded) + #print(pe.header) + tpl_id = pe.templateID + if tpl_id: + prof = ProfileTemplateRegistry.get_by_oid(tpl_id) + print(prof) + #pp.pprint(pe.decoded) + for fname, fdata in pe.files.items(): + print() + print("============== %s" % fname) + ftempl = None + if prof: + ftempl = prof.files_by_pename[fname] + print("Template: %s" % repr(ftempl)) + print("Data: %s" % fdata) + file = File(fname, fdata, ftempl) + print(repr(file)) + #pp.pprint(pe.files) + + + +#print(ProfileTemplateRegistry.by_oid)