Files
pysim-local/pySim/esim/__init__.py
Harald Welte 3ad3da8995 contrib/es9p_client: Add support for reporting notifications to SM-DP+
The ES9+ interface is not only used for downloading eSIM profiles, but
it is also used to report back the installation result as well as
profile management operations like enable/disable/delete.

Change-Id: Iefba7fa0471b34eae30700ed43531a515af0eb93
2024-07-17 18:05:57 +02:00

138 lines
4.7 KiB
Python

import sys
from typing import Optional, Tuple
from importlib import resources
class PMO:
"""Convenience conversion class for ProfileManagementOperation as used in ES9+ notifications."""
pmo4operation = {
'install': 0x80,
'enable': 0x40,
'disable': 0x20,
'delete': 0x10,
}
def __init__(self, op: str):
if not op in self.pmo4operation:
raise ValueError('Unknown operation "%s"' % op)
self.op = op
def to_int(self):
return self.pmo4operation[self.op]
@staticmethod
def _num_bits(data: int)-> int:
for i in range(0, 8):
if data & (1 << i):
return 8-i
return 0
def to_bitstring(self) -> Tuple[bytes, int]:
"""return value in a format as used by asn1tools for BITSTRING."""
val = self.to_int()
return (bytes([val]), self._num_bits(val))
@classmethod
def from_int(cls, i: int) -> 'PMO':
"""Parse an integer representation."""
for k, v in cls.pmo4operation.items():
if v == i:
return cls(k)
raise ValueError('Unknown PMO 0x%02x' % i)
@classmethod
def from_bitstring(cls, bstr: Tuple[bytes, int]) -> 'PMO':
"""Parse a asn1tools BITSTRING representation."""
return cls.from_int(bstr[0][0])
def __str__(self):
return self.op
def compile_asn1_subdir(subdir_name:str, codec='der'):
"""Helper function that compiles ASN.1 syntax from all files within given subdir"""
import asn1tools
asn_txt = ''
__ver = sys.version_info
if (__ver.major, __ver.minor) >= (3, 9):
for i in resources.files('pySim.esim').joinpath('asn1').joinpath(subdir_name).iterdir():
asn_txt += i.read_text()
asn_txt += "\n"
#else:
#print(resources.read_text(__name__, 'asn1/rsp.asn'))
return asn1tools.compile_string(asn_txt, codec=codec)
# SGP.22 section 4.1 Activation Code
class ActivationCode:
def __init__(self, hostname:str, token:str, oid: Optional[str] = None, cc_required: Optional[bool] = False):
if '$' in hostname:
raise ValueError('$ sign not permitted in hostname')
self.hostname = hostname
if '$' in token:
raise ValueError('$ sign not permitted in token')
self.token = token
# TODO: validate OID
self.oid = oid
self.cc_required = cc_required
# only format 1 is specified and supported here
self.format = 1
@staticmethod
def decode_str(ac: str) -> dict:
if ac[0] != '1':
raise ValueError("Unsupported AC_Format '%s'!" % ac[0])
ac_elements = ac.split('$')
d = {
'oid': None,
'cc_required': False,
}
d['format'] = ac_elements.pop(0)
d['hostname'] = ac_elements.pop(0)
d['token'] = ac_elements.pop(0)
if len(ac_elements):
oid = ac_elements.pop(0)
if oid != '':
d['oid'] = oid
if len(ac_elements):
ccr = ac_elements.pop(0)
if ccr == '1':
d['cc_required'] = True
return d
@classmethod
def from_string(cls, ac: str) -> 'ActivationCode':
"""Create new instance from SGP.22 section 4.1 string representation."""
d = cls.decode_str(ac)
return cls(d['hostname'], d['token'], d['oid'], d['cc_required'])
def to_string(self, for_qrcode:bool = False) -> str:
"""Convert from internal representation to SGP.22 section 4.1 string representation."""
if for_qrcode:
ret = 'LPA:'
else:
ret = ''
ret += '%d$%s$%s' % (self.format, self.hostname, self.token)
if self.oid:
ret += '$%s' % (self.oid)
elif self.cc_required:
ret += '$'
if self.cc_required:
ret += '$1'
return ret
def __str__(self):
return self.to_string()
def to_qrcode(self):
"""Encode internal representation to QR code."""
import qrcode
qr = qrcode.QRCode()
qr.add_data(self.to_string(for_qrcode=True))
return qr.make_image()
def __repr__(self):
return "ActivationCode(format=%u, hostname='%s', token='%s', oid=%s, cc_required=%s)" % (self.format,
self.hostname,
self.token,
self.oid,
self.cc_required)