laforge has uploaded this change for review.

View Change

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)

To view, visit change 35733. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: Ib1674920e488ade9597cb039e4e2047dcbc7864e
Gerrit-Change-Number: 35733
Gerrit-PatchSet: 1
Gerrit-Owner: laforge <laforge@osmocom.org>
Gerrit-MessageType: newchange