Migrate over to using pyosmocom

We're creating a 'pyosmocom' pypi module which contains a number of core
Osmocom libraries / interfaces that are not specific to SIM card stuff
contained here.

The main modules moved in this initial step are pySim.tlv, pySim.utils
and pySim.construct. utils is split, not all of the contents is
unrelated to SIM Cards.  The other two are moved completely.

Change-Id: I4b63e45bcb0c9ba2424dacf85e0222aee735f411
This commit is contained in:
Harald Welte
2024-08-30 12:07:08 +02:00
parent a437d11135
commit a3962b2076
71 changed files with 157 additions and 2057 deletions

View File

@@ -77,7 +77,7 @@ Please install the following dependencies:
- cmd2 >= 1.5.0
- colorlog
- construct >= 2.9.51
- gsm0338
- pyosmocom
- jsonpath-ng
- packaging
- pycryptodomex

View File

@@ -22,8 +22,8 @@ import sys
import csv
import argparse
from Cryptodome.Cipher import AES
from osmocom.utils import h2b, b2h, Hexstr
from pySim.utils import h2b, b2h, Hexstr
from pySim.card_key_provider import CardKeyProviderCsv
def dict_keys_to_upper(d: dict) -> dict:

View File

@@ -28,10 +28,11 @@ from cryptography import x509
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
from cryptography.hazmat.primitives.asymmetric import ec
from osmocom.utils import h2b, b2h, swap_nibbles, is_hexstr
from osmocom.tlv import bertlv_parse_one_rawtag, bertlv_return_one_rawtlv
import pySim.esim.rsp as rsp
from pySim.esim import es9p, PMO
from pySim.utils import h2b, b2h, swap_nibbles, is_hexstr
from pySim.utils import bertlv_parse_one_rawtag, bertlv_return_one_rawtlv
from pySim.esim.x509_cert import CertAndPrivkey
from pySim.esim.es8p import BoundProfilePackage

View File

@@ -22,11 +22,11 @@ import logging
import zipfile
from pathlib import Path as PlPath
from typing import List
from osmocom.utils import h2b, b2h, swap_nibbles
from pySim.esim.saip import *
from pySim.esim.saip.validation import CheckBasicStructure
from pySim import javacard
from pySim.utils import h2b, b2h, swap_nibbles
from pySim.pprint import HexBytesPrettyPrinter
pp = HexBytesPrettyPrinter(indent=4,width=500)

View File

@@ -6,7 +6,8 @@
import sys
import argparse
from pySim.utils import bertlv_parse_one, bertlv_encode_tag, b2h, h2b
from osmocom.utils import b2h, h2b
from osmocom.tlv import bertlv_parse_one, bertlv_encode_tag
def process_one_level(content: bytes, indent: int):
remainder = content

View File

@@ -32,7 +32,7 @@ from klein import Klein
from twisted.web.iweb import IRequest
import asn1tools
from pySim.utils import h2b, b2h, swap_nibbles
from osmocom.utils import h2b, b2h, swap_nibbles
import pySim.esim.rsp as rsp
from pySim.esim import saip, PMO

View File

@@ -33,11 +33,12 @@ import sys
import traceback
import json
import csv
from osmocom.utils import h2b, swap_nibbles, rpad
from pySim.commands import SimCardCommands
from pySim.transport import init_reader, argparse_add_reader_args
from pySim.legacy.cards import _cards_classes, card_detect
from pySim.utils import h2b, swap_nibbles, rpad, derive_milenage_opc, calculate_luhn, dec_iccid
from pySim.utils import derive_milenage_opc, calculate_luhn, dec_iccid
from pySim.ts_51_011 import EF_AD
from pySim.legacy.ts_51_011 import EF
from pySim.card_handler import *

View File

@@ -29,6 +29,8 @@ import random
import re
import sys
from osmocom.utils import h2b, h2s, swap_nibbles, rpad
from pySim.ts_51_011 import EF_SST_map, EF_AD
from pySim.legacy.ts_51_011 import EF, DF
from pySim.ts_31_102 import EF_UST_map
@@ -40,7 +42,7 @@ from pySim.commands import SimCardCommands
from pySim.transport import init_reader, argparse_add_reader_args
from pySim.exceptions import SwMatchError
from pySim.legacy.cards import card_detect, SimCard, UsimCard, IsimCard
from pySim.utils import h2b, h2s, swap_nibbles, rpad, dec_imsi, dec_iccid, dec_msisdn
from pySim.utils import dec_imsi, dec_iccid, dec_msisdn
from pySim.legacy.utils import format_xplmn_w_act, dec_st
option_parser = argparse.ArgumentParser(description='Legacy tool for reading some parts of a SIM card',

View File

@@ -47,11 +47,13 @@ from io import StringIO
from pprint import pprint as pp
from osmocom.utils import h2b, b2h, i2h, swap_nibbles, rpad, JsonEncoder, is_hexstr, is_decimal
from osmocom.utils import is_hexstr_or_decimal, Hexstr
from osmocom.tlv import bertlv_parse_one
from pySim.exceptions import *
from pySim.transport import init_reader, ApduTracer, argparse_add_reader_args, ProactiveHandler
from pySim.utils import h2b, b2h, i2h, swap_nibbles, rpad, JsonEncoder, bertlv_parse_one, sw_match
from pySim.utils import sanitize_pin_adm, tabulate_str_list, boxed_heading_str, Hexstr, dec_iccid
from pySim.utils import is_hexstr_or_decimal, is_hexstr, is_decimal
from pySim.utils import sanitize_pin_adm, tabulate_str_list, boxed_heading_str, dec_iccid, sw_match
from pySim.card_handler import CardHandler, CardHandlerAuto
from pySim.filesystem import CardMF, CardEF, CardDF, CardADF

View File

@@ -8,7 +8,8 @@ from pprint import pprint as pp
from pySim.apdu import *
from pySim.runtime import RuntimeState
from pySim.utils import JsonEncoder
from osmocom.utils import JsonEncoder
from pySim.cards import UiccCardBase
from pySim.commands import SimCardCommands
from pySim.profile import CardProfile

View File

@@ -29,12 +29,11 @@ import abc
import typing
from typing import List, Dict, Optional
from termcolor import colored
from construct import Byte, GreedyBytes
from construct import Optional as COptional
from osmocom.construct import *
from osmocom.utils import *
from pySim.construct import *
from pySim.utils import *
from pySim.runtime import RuntimeLchan, RuntimeState, lchan_nr_from_cla
from pySim.filesystem import CardADF, CardFile, TransparentEF, LinFixedEF

View File

@@ -18,9 +18,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from construct import FlagsEnum, Struct
from pySim.tlv import flatten_dict_lists
from osmocom.tlv import flatten_dict_lists
from osmocom.construct import *
from pySim.apdu import ApduCommand, ApduCommandSet
from pySim.construct import *
from pySim.global_platform import InstallParameters
class GpDelete(ApduCommand, n='DELETE', ins=0xE4, cla=['8X', 'CX', 'EX']):

View File

@@ -22,11 +22,12 @@ import logging
from construct import GreedyRange, Struct
from pySim.construct import *
from osmocom.utils import i2h
from osmocom.construct import *
from pySim.filesystem import *
from pySim.runtime import RuntimeLchan
from pySim.apdu import ApduCommand, ApduCommandSet
from pySim.utils import i2h
from pySim import cat
logger = logging.getLogger(__name__)

View File

@@ -20,8 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
from construct import Struct
from osmocom.construct import *
from pySim.construct import *
from pySim.apdu import ApduCommand, ApduCommandSet
from pySim.ts_102_221 import FcpTemplate

View File

@@ -11,9 +11,9 @@ from typing import Dict
from construct import BitStruct, Enum, BitsInteger, Int8ub, Bytes, this, Struct, If, Switch, Const
from construct import Optional as COptional
from osmocom.construct import *
from pySim.filesystem import *
from pySim.construct import *
from pySim.ts_31_102 import SUCI_TlvDataObject
from pySim.apdu import ApduCommand, ApduCommandSet

View File

@@ -28,10 +28,10 @@ Support for the Secure Element Access Control, specifically the ARA-M inside an
from construct import GreedyBytes, GreedyString, Struct, Enum, Int8ub, Int16ub
from construct import Optional as COptional
from pySim.construct import *
from osmocom.construct import *
from osmocom.tlv import *
from osmocom.utils import Hexstr
from pySim.filesystem import *
from pySim.tlv import *
from pySim.utils import Hexstr
import pySim.global_platform
# various BER-TLV encoded Data Objects (DOs)

View File

@@ -30,7 +30,7 @@ operation with pySim-shell.
from typing import List, Dict, Optional
from Cryptodome.Cipher import AES
from pySim.utils import h2b, b2h
from osmocom.utils import h2b, b2h
import abc
import csv

View File

@@ -23,10 +23,11 @@
#
from typing import Optional, Tuple
from osmocom.utils import *
from pySim.ts_102_221 import EF_DIR, CardProfileUICC
from pySim.ts_51_011 import DF_GSM
from pySim.utils import *
from pySim.utils import SwHexstr
from pySim.commands import Path, SimCardCommands
class CardBase:

View File

@@ -23,9 +23,10 @@ from bidict import bidict
from construct import Int8ub, Int16ub, Byte, Bytes, BitsInteger
from construct import Struct, Enum, BitStruct, this
from construct import GreedyBytes, Switch, GreedyRange, FlagsEnum
from pySim.tlv import TLV_IE, COMPR_TLV_IE, BER_TLV_IE, TLV_IE_Collection
from pySim.construct import PlmnAdapter, BcdAdapter, HexAdapter, GsmStringAdapter, TonNpi, GsmString
from pySim.utils import b2h, dec_xplmn_w_act
from osmocom.tlv import TLV_IE, COMPR_TLV_IE, BER_TLV_IE, TLV_IE_Collection
from osmocom.construct import PlmnAdapter, BcdAdapter, HexAdapter, GsmStringAdapter, TonNpi, GsmString
from osmocom.utils import b2h
from pySim.utils import dec_xplmn_w_act
# Tag values as per TS 101 220 Table 7.23

View File

@@ -20,15 +20,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import enum
from construct import Bytewise, BitStruct, BitsInteger, Struct, FlagsEnum
from osmocom.utils import *
from osmocom.construct import *
from pySim.utils import *
from pySim.filesystem import *
from pySim.profile import match_ruim
from pySim.profile import CardProfile, CardProfileAddon
from pySim.ts_51_011 import CardProfileSIM
from pySim.ts_51_011 import DF_TELECOM, DF_GSM
from pySim.ts_51_011 import EF_ServiceTable
from pySim.construct import *
# Mapping between CDMA Service Number and its description

View File

@@ -23,12 +23,13 @@
from typing import List, Tuple
import typing # construct also has a Union, so we do typing.Union below
from construct import Construct, Struct, Const, Select
from construct import Optional as COptional
from pySim.construct import LV, filter_dict
from pySim.utils import rpad, lpad, b2h, h2b, sw_match, bertlv_encode_len, h2i, i2h, str_sanitize, expand_hex, SwMatchstr
from pySim.utils import Hexstr, SwHexstr, ResTuple
from osmocom.construct import LV, filter_dict
from osmocom.utils import rpad, lpad, b2h, h2b, h2i, i2h, str_sanitize, Hexstr
from osmocom.tlv import bertlv_encode_len
from pySim.utils import sw_match, expand_hex, SwHexstr, ResTuple, SwMatchstr
from pySim.exceptions import SwMatchError
from pySim.transport import LinkBase

View File

@@ -1,587 +0,0 @@
"""Utility code related to the integration of the 'construct' declarative parser."""
import typing
import codecs
import ipaddress
import gsm0338
from construct.lib.containers import Container, ListContainer
from construct.core import EnumIntegerString
from construct import Adapter, Prefixed, Int8ub, GreedyBytes, Default, Flag, Byte, Construct, Enum
from construct import BitsInteger, BitStruct, Bytes, StreamError, stream_read_entire, stream_write
from construct import SizeofError, IntegerError, swapbytes
from construct.core import evaluate
from construct.lib import integertypes
from pySim.utils import b2h, h2b, swap_nibbles
# (C) 2021-2022 by Harald Welte <laforge@osmocom.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
class HexAdapter(Adapter):
"""convert a bytes() type to a string of hex nibbles."""
def _decode(self, obj, context, path):
return b2h(obj)
def _encode(self, obj, context, path):
return h2b(obj)
class Utf8Adapter(Adapter):
"""convert a bytes() type that contains utf8 encoded text to human readable text."""
def _decode(self, obj, context, path):
# In case the string contains only 0xff bytes we interpret it as an empty string
if obj == b'\xff' * len(obj):
return ""
return codecs.decode(obj, "utf-8")
def _encode(self, obj, context, path):
return codecs.encode(obj, "utf-8")
class GsmOrUcs2Adapter(Adapter):
"""Try to encode into a GSM 03.38 string; if that fails, fall back to UCS-2 as described
in TS 102 221 Annex A."""
def _decode(self, obj, context, path):
# In case the string contains only 0xff bytes we interpret it as an empty string
if obj == b'\xff' * len(obj):
return ""
# one of the magic bytes of TS 102 221 Annex A
if obj[0] in [0x80, 0x81, 0x82]:
ad = Ucs2Adapter(GreedyBytes)
else:
ad = GsmString(GreedyBytes)
return ad._decode(obj, context, path)
def _encode(self, obj, context, path):
# first try GSM 03.38; then fall back to TS 102 221 Annex A UCS-2
try:
ad = GsmString(GreedyBytes)
return ad._encode(obj, context, path)
except:
ad = Ucs2Adapter(GreedyBytes)
return ad._encode(obj, context, path)
class Ucs2Adapter(Adapter):
"""convert a bytes() type that contains UCS2 encoded characters encoded as defined in TS 102 221
Annex A to normal python string representation (and back)."""
def _decode(self, obj, context, path):
# In case the string contains only 0xff bytes we interpret it as an empty string
if obj == b'\xff' * len(obj):
return ""
if obj[0] == 0x80:
# TS 102 221 Annex A Variant 1
return codecs.decode(obj[1:], 'utf_16_be')
elif obj[0] == 0x81:
# TS 102 221 Annex A Variant 2
out = ""
# second byte contains a value indicating the number of characters
num_of_chars = obj[1]
# the third byte contains an 8 bit number which defines bits 15 to 8 of a 16 bit base
# pointer, where bit 16 is set to zero, and bits 7 to 1 are also set to zero. These
# sixteen bits constitute a base pointer to a "half-page" in the UCS2 code space
base_ptr = obj[2] << 7
for ch in obj[3:3+num_of_chars]:
# if bit 8 of the byte is set to zero, the remaining 7 bits of the byte contain a
# GSM Default Alphabet character, whereas if bit 8 of the byte is set to one, then
# the remaining seven bits are an offset value added to the 16 bit base pointer
# defined earlier, and the resultant 16 bit value is a UCS2 code point
if ch & 0x80:
codepoint = (ch & 0x7f) + base_ptr
out += codecs.decode(codepoint.to_bytes(2, byteorder='big'), 'utf_16_be')
else:
out += codecs.decode(bytes([ch]), 'gsm03.38')
return out
elif obj[0] == 0x82:
# TS 102 221 Annex A Variant 3
out = ""
# second byte contains a value indicating the number of characters
num_of_chars = obj[1]
# third and fourth bytes contain a 16 bit number which defines the complete 16 bit base
# pointer to a half-page in the UCS2 code space, for use with some or all of the
# remaining bytes in the string
base_ptr = obj[2] << 8 | obj[3]
for ch in obj[4:4+num_of_chars]:
# if bit 8 of the byte is set to zero, the remaining 7 bits of the byte contain a
# GSM Default Alphabet character, whereas if bit 8 of the byte is set to one, the
# remaining seven bits are an offset value added to the base pointer defined in
# bytes three and four, and the resultant 16 bit value is a UCS2 code point, else: #
# GSM default alphabet
if ch & 0x80:
codepoint = (ch & 0x7f) + base_ptr
out += codecs.decode(codepoint.to_bytes(2, byteorder='big'), 'utf_16_be')
else:
out += codecs.decode(bytes([ch]), 'gsm03.38')
return out
else:
raise ValueError('First byte of TS 102 221 UCS-2 must be 0x80, 0x81 or 0x82')
def _encode(self, obj, context, path):
def encodable_in_gsm338(instr: str) -> bool:
"""Determine if given input string is encode-ale in gsm03.38."""
try:
# TODO: figure out if/how we can constrain to default alphabet. The gsm0338
# library seems to include the spanish lock/shift table
codecs.encode(instr, 'gsm03.38')
except ValueError:
return False
return True
def codepoints_not_in_gsm338(instr: str) -> typing.List[int]:
"""Return an integer list of UCS2 codepoints for all characters of 'inster'
which are not representable in the GSM 03.38 default alphabet."""
codepoint_list = []
for c in instr:
if encodable_in_gsm338(c):
continue
c_codepoint = int.from_bytes(codecs.encode(c, 'utf_16_be'), byteorder='big')
codepoint_list.append(c_codepoint)
return codepoint_list
def diff_between_min_and_max_of_list(inlst: typing.List) -> int:
return max(inlst) - min(inlst)
def encodable_in_variant2(instr: str) -> bool:
codepoint_prefix = None
for c in instr:
if encodable_in_gsm338(c):
continue
c_codepoint = int.from_bytes(codecs.encode(c, 'utf_16_be'), byteorder='big')
if c_codepoint >= 0x8000:
return False
c_prefix = c_codepoint >> 7
if codepoint_prefix is None:
codepoint_prefix = c_prefix
else:
if c_prefix != codepoint_prefix:
return False
return True
def encodable_in_variant3(instr: str) -> bool:
codepoint_list = codepoints_not_in_gsm338(instr)
# compute delta between max and min; check if it's encodable in 7 bits
if diff_between_min_and_max_of_list(codepoint_list) >= 0x80:
return False
return True
def _encode_variant1(instr: str) -> bytes:
"""Encode according to TS 102 221 Annex A Variant 1"""
return b'\x80' + codecs.encode(instr, 'utf_16_be')
def _encode_variant2(instr: str) -> bytes:
"""Encode according to TS 102 221 Annex A Variant 2"""
codepoint_prefix = None
# second byte contains a value indicating the number of characters
hdr = b'\x81' + len(instr).to_bytes(1, byteorder='big')
chars = b''
for c in instr:
try:
enc = codecs.encode(c, 'gsm03.38')
except ValueError:
c_codepoint = int.from_bytes(codecs.encode(c, 'utf_16_be'), byteorder='big')
c_prefix = c_codepoint >> 7
if codepoint_prefix is None:
codepoint_prefix = c_prefix
assert codepoint_prefix == c_prefix
enc = (0x80 + (c_codepoint & 0x7f)).to_bytes(1, byteorder='big')
chars += enc
if codepoint_prefix is None:
codepoint_prefix = 0
return hdr + codepoint_prefix.to_bytes(1, byteorder='big') + chars
def _encode_variant3(instr: str) -> bytes:
"""Encode according to TS 102 221 Annex A Variant 3"""
# second byte contains a value indicating the number of characters
hdr = b'\x82' + len(instr).to_bytes(1, byteorder='big')
chars = b''
codepoint_list = codepoints_not_in_gsm338(instr)
codepoint_base = min(codepoint_list)
for c in instr:
try:
# if bit 8 of the byte is set to zero, the remaining 7 bits of the byte contain a GSM
# Default # Alphabet character
enc = codecs.encode(c, 'gsm03.38')
except ValueError:
# if bit 8 of the byte is set to one, the remaining seven bits are an offset
# value added to the base pointer defined in bytes three and four, and the
# resultant 16 bit value is a UCS2 code point
c_codepoint = int.from_bytes(codecs.encode(c, 'utf_16_be'), byteorder='big')
c_codepoint_delta = c_codepoint - codepoint_base
assert c_codepoint_delta < 0x80
enc = (0x80 + c_codepoint_delta).to_bytes(1, byteorder='big')
chars += enc
# third and fourth bytes contain a 16 bit number which defines the complete 16 bit base
# pointer to a half-page in the UCS2 code space
return hdr + codepoint_base.to_bytes(2, byteorder='big') + chars
if encodable_in_variant2(obj):
return _encode_variant2(obj)
elif encodable_in_variant3(obj):
return _encode_variant3(obj)
else:
return _encode_variant1(obj)
class BcdAdapter(Adapter):
"""convert a bytes() type to a string of BCD nibbles."""
def _decode(self, obj, context, path):
return swap_nibbles(b2h(obj))
def _encode(self, obj, context, path):
return h2b(swap_nibbles(obj))
class PlmnAdapter(BcdAdapter):
"""convert a bytes(3) type to BCD string like 262-02 or 262-002."""
def _decode(self, obj, context, path):
bcd = super()._decode(obj, context, path)
if bcd[3] == 'f':
return '-'.join([bcd[:3], bcd[4:]])
else:
return '-'.join([bcd[:3], bcd[3:]])
def _encode(self, obj, context, path):
l = obj.split('-')
if len(l[1]) == 2:
bcd = l[0] + 'f' + l[1]
else:
bcd = l[0] + l[1]
return super()._encode(bcd, context, path)
class InvertAdapter(Adapter):
"""inverse logic (false->true, true->false)."""
@staticmethod
def _invert_bool_in_obj(obj):
for k,v in obj.items():
# skip all private entries
if k.startswith('_'):
continue
if v is False:
obj[k] = True
elif v is True:
obj[k] = False
return obj
def _decode(self, obj, context, path):
return self._invert_bool_in_obj(obj)
def _encode(self, obj, context, path):
return self._invert_bool_in_obj(obj)
class Rpad(Adapter):
"""
Encoder appends padding bytes (b'\\xff') or characters up to target size.
Decoder removes trailing padding bytes/characters.
Parameters:
subcon: Subconstruct as defined by construct library
pattern: set padding pattern (default: b'\\xff')
num_per_byte: number of 'elements' per byte. E.g. for hex nibbles: 2
"""
def __init__(self, subcon, pattern=b'\xff', num_per_byte=1):
super().__init__(subcon)
self.pattern = pattern
self.num_per_byte = num_per_byte
def _decode(self, obj, context, path):
return obj.rstrip(self.pattern)
def _encode(self, obj, context, path):
target_size = self.sizeof() * self.num_per_byte
if len(obj) > target_size:
raise SizeofError("Input ({}) exceeds target size ({})".format(
len(obj), target_size))
return obj + self.pattern * (target_size - len(obj))
class MultiplyAdapter(Adapter):
"""
Decoder multiplies by multiplicator
Encoder divides by multiplicator
Parameters:
subcon: Subconstruct as defined by construct library
multiplier: Multiplier to apply to raw encoded value
"""
def __init__(self, subcon, multiplicator):
super().__init__(subcon)
self.multiplicator = multiplicator
def _decode(self, obj, context, path):
return obj * 8
def _encode(self, obj, context, path):
return obj // 8
class GsmStringAdapter(Adapter):
"""Convert GSM 03.38 encoded bytes to a string."""
def __init__(self, subcon, codec='gsm03.38', err='strict'):
super().__init__(subcon)
self.codec = codec
self.err = err
def _decode(self, obj, context, path):
return obj.decode(self.codec)
def _encode(self, obj, context, path):
return obj.encode(self.codec, self.err)
class Ipv4Adapter(Adapter):
"""
Encoder converts from 4 bytes to string representation (A.B.C.D).
Decoder converts from string representation (A.B.C.D) to four bytes.
"""
def _decode(self, obj, context, path):
ia = ipaddress.IPv4Address(obj)
return ia.compressed
def _encode(self, obj, context, path):
ia = ipaddress.IPv4Address(obj)
return ia.packed
class Ipv6Adapter(Adapter):
"""
Encoder converts from 16 bytes to string representation.
Decoder converts from string representation to 16 bytes.
"""
def _decode(self, obj, context, path):
ia = ipaddress.IPv6Address(obj)
return ia.compressed
def _encode(self, obj, context, path):
ia = ipaddress.IPv6Address(obj)
return ia.packed
class StripTrailerAdapter(Adapter):
"""
Encoder removes all trailing bytes matching the default_value
Decoder pads input data up to total_length with default_value
This is used in constellations like "FlagsEnum(StripTrailerAdapter(GreedyBytes, 3), ..."
where you have a bit-mask that may have 1, 2 or 3 bytes, depending on whether or not any
of the LSBs are actually set.
"""
def __init__(self, subcon, total_length:int, default_value=b'\x00', min_len=1):
super().__init__(subcon)
assert len(default_value) == 1
self.total_length = total_length
self.default_value = default_value
self.min_len = min_len
def _decode(self, obj, context, path):
assert isinstance(obj, bytes)
# pad with suppressed/missing bytes
if len(obj) < self.total_length:
obj += self.default_value * (self.total_length - len(obj))
return int.from_bytes(obj, 'big')
def _encode(self, obj, context, path):
assert isinstance(obj, int)
obj = obj.to_bytes(self.total_length, 'big')
# remove trailing bytes if they are zero
while len(obj) > self.min_len and obj[-1] == self.default_value[0]:
obj = obj[:-1]
return obj
def filter_dict(d, exclude_prefix='_'):
"""filter the input dict to ensure no keys starting with 'exclude_prefix' remain."""
if not isinstance(d, dict):
return d
res = {}
for (key, value) in d.items():
if key.startswith(exclude_prefix):
continue
if isinstance(value, dict):
res[key] = filter_dict(value)
else:
res[key] = value
return res
def normalize_construct(c, exclude_prefix: str = '_'):
"""Convert a construct specific type to a related base type, mostly useful
so we can serialize it."""
# we need to include the filter_dict as we otherwise get elements like this
# in the dict: '_io': <_io.BytesIO object at 0x7fdb64e05860> which we cannot json-serialize
c = filter_dict(c, exclude_prefix)
if isinstance(c, (Container, dict)):
r = {k: normalize_construct(v) for (k, v) in c.items()}
elif isinstance(c, ListContainer):
r = [normalize_construct(x) for x in c]
elif isinstance(c, list):
r = [normalize_construct(x) for x in c]
elif isinstance(c, EnumIntegerString):
r = str(c)
else:
r = c
return r
def parse_construct(c, raw_bin_data: bytes, length: typing.Optional[int] = None, exclude_prefix: str = '_', context: dict = {}):
"""Helper function to wrap around normalize_construct() and filter_dict()."""
if not length:
length = len(raw_bin_data)
try:
parsed = c.parse(raw_bin_data, total_len=length, **context)
except StreamError as e:
# if the input is all-ff, this means the content is undefined. Let's avoid passing StreamError
# exceptions in those situations (which might occur if a length field 0xff is 255 but then there's
# actually less bytes in the remainder of the file.
if all(v == 0xff for v in raw_bin_data):
return None
else:
raise e
return normalize_construct(parsed, exclude_prefix)
def build_construct(c, decoded_data, context: dict = {}):
"""Helper function to handle total_len."""
return c.build(decoded_data, total_len=None, **context)
# here we collect some shared / common definitions of data types
LV = Prefixed(Int8ub, HexAdapter(GreedyBytes))
# Default value for Reserved for Future Use (RFU) bits/bytes
# See TS 31.101 Sec. "3.4 Coding Conventions"
__RFU_VALUE = 0
# Field that packs Reserved for Future Use (RFU) bit
FlagRFU = Default(Flag, __RFU_VALUE)
# Field that packs Reserved for Future Use (RFU) byte
ByteRFU = Default(Byte, __RFU_VALUE)
# Field that packs all remaining Reserved for Future Use (RFU) bytes
GreedyBytesRFU = Default(GreedyBytes, b'')
def BitsRFU(n=1):
'''
Field that packs Reserved for Future Use (RFU) bit(s)
as defined in TS 31.101 Sec. "3.4 Coding Conventions"
Use this for (currently) unused/reserved bits whose contents
should be initialized automatically but should not be cleared
in the future or when restoring read data (unlike padding).
Parameters:
n (Integer): Number of bits (default: 1)
'''
return Default(BitsInteger(n), __RFU_VALUE)
def BytesRFU(n=1):
'''
Field that packs Reserved for Future Use (RFU) byte(s)
as defined in TS 31.101 Sec. "3.4 Coding Conventions"
Use this for (currently) unused/reserved bytes whose contents
should be initialized automatically but should not be cleared
in the future or when restoring read data (unlike padding).
Parameters:
n (Integer): Number of bytes (default: 1)
'''
return Default(Bytes(n), __RFU_VALUE)
def GsmString(n):
'''
GSM 03.38 encoded byte string of fixed length n.
Encoder appends padding bytes (b'\\xff') to maintain
length. Decoder removes those trailing bytes.
Exceptions are raised for invalid characters
and length excess.
Parameters:
n (Integer): Fixed length of the encoded byte string
'''
return GsmStringAdapter(Rpad(Bytes(n), pattern=b'\xff'), codec='gsm03.38')
def GsmOrUcs2String(n):
'''
GSM 03.38 or UCS-2 (TS 102 221 Annex A) encoded byte string of fixed length n.
Encoder appends padding bytes (b'\\xff') to maintain
length. Decoder removes those trailing bytes.
Exceptions are raised for invalid characters
and length excess.
Parameters:
n (Integer): Fixed length of the encoded byte string
'''
return GsmOrUcs2Adapter(Rpad(Bytes(n), pattern=b'\xff'))
class GreedyInteger(Construct):
"""A variable-length integer implementation, think of combining GrredyBytes with BytesInteger."""
def __init__(self, signed=False, swapped=False, minlen=0):
super().__init__()
self.signed = signed
self.swapped = swapped
self.minlen = minlen
def _parse(self, stream, context, path):
data = stream_read_entire(stream, path)
if evaluate(self.swapped, context):
data = swapbytes(data)
try:
return int.from_bytes(data, byteorder='big', signed=self.signed)
except ValueError as e:
raise IntegerError(str(e), path=path)
def __bytes_required(self, i, minlen=0):
if self.signed:
raise NotImplementedError("FIXME: Implement support for encoding signed integer")
# compute how many bytes we need
nbytes = 1
while True:
i = i >> 8
if i == 0:
break
else:
nbytes = nbytes + 1
# round up to the minimum number
# of bytes we anticipate
nbytes = max(nbytes, minlen)
return nbytes
def _build(self, obj, stream, context, path):
if not isinstance(obj, integertypes):
raise IntegerError(f"value {obj} is not an integer", path=path)
length = self.__bytes_required(obj, self.minlen)
try:
data = obj.to_bytes(length, byteorder='big', signed=self.signed)
except ValueError as e:
raise IntegerError(str(e), path=path) from e
if evaluate(self.swapped, context):
data = swapbytes(data)
stream_write(stream, data, length, path)
return obj
# merged definitions of 24.008 + 23.040
TypeOfNumber = Enum(BitsInteger(3), unknown=0, international=1, national=2, network_specific=3,
short_code=4, alphanumeric=5, abbreviated=6, reserved_for_extension=7)
NumberingPlan = Enum(BitsInteger(4), unknown=0, isdn_e164=1, data_x121=3, telex_f69=4,
sc_specific_5=5, sc_specific_6=6, national=8, private=9,
ermes=10, reserved_cts=11, reserved_for_extension=15)
TonNpi = BitStruct('ext'/Flag, 'type_of_number'/TypeOfNumber, 'numbering_plan_id'/NumberingPlan)

View File

@@ -35,7 +35,8 @@ from cryptography.hazmat.primitives.kdf.x963kdf import X963KDF
from Cryptodome.Cipher import AES
from Cryptodome.Hash import CMAC
from pySim.utils import bertlv_encode_len, bertlv_parse_one, b2h
from osmocom.utils import b2h
from osmocom.tlv import bertlv_encode_len, bertlv_parse_one
# don't log by default
logger = logging.getLogger(__name__)

View File

@@ -17,11 +17,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from typing import Dict, List, Optional
from cryptography.hazmat.primitives.asymmetric import ec
from pySim.utils import b2h, h2b, bertlv_encode_tag, bertlv_encode_len, bertlv_parse_one_rawtag
from pySim.utils import bertlv_return_one_rawtlv
from osmocom.utils import b2h, h2b
from osmocom.tlv import bertlv_encode_tag, bertlv_encode_len, bertlv_parse_one_rawtag
from osmocom.tlv import bertlv_return_one_rawtlv
import pySim.esim.rsp as rsp
from pySim.esim.bsp import BspInstance

View File

@@ -23,8 +23,9 @@ import shelve
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import Encoding
from cryptography import x509
from osmocom.utils import b2h
from osmocom.tlv import bertlv_parse_one_rawtag, bertlv_return_one_rawtlv
from pySim.utils import bertlv_parse_one_rawtag, bertlv_return_one_rawtlv, b2h
from pySim.esim import compile_asn1_subdir
asn1 = compile_asn1_subdir('rsp')

View File

@@ -20,19 +20,19 @@ import abc
import io
from typing import Tuple, List, Optional, Dict, Union
from collections import OrderedDict
import asn1tools
from osmocom.utils import b2h, h2b, Hexstr
from osmocom.tlv import BER_TLV_IE, bertlv_parse_tag, bertlv_parse_len
from osmocom.construct import build_construct, parse_construct, GreedyInteger
from pySim.utils import bertlv_parse_tag, bertlv_parse_len, b2h, h2b, dec_imsi, Hexstr
from pySim.utils import dec_imsi
from pySim.ts_102_221 import FileDescriptor
from pySim.filesystem import CardADF, Path
from pySim.ts_31_102 import ADF_USIM
from pySim.ts_31_103 import ADF_ISIM
from pySim.construct import build_construct, parse_construct, GreedyInteger
from pySim.esim import compile_asn1_subdir
from pySim.esim.saip import templates
from pySim.esim.saip import oid
from pySim.tlv import BER_TLV_IE
from pySim.global_platform import KeyType, KeyUsageQualifier
from pySim.global_platform.uicc import UiccSdInstallParams

View File

@@ -19,7 +19,7 @@ import abc
import io
from typing import List, Tuple
from pySim.tlv import camel_to_snake
from osmocom.tlv import camel_to_snake
from pySim.utils import enc_iccid, enc_imsi, h2b, rpad, sanitize_iccid
from pySim.esim.saip import ProfileElement, ProfileElementSequence

View File

@@ -27,11 +27,12 @@ import argparse
from construct import Array, Struct, FlagsEnum, GreedyRange
from cmd2 import cmd2, CommandSet, with_default_category
from osmocom.utils import Hexstr
from osmocom.tlv import *
from osmocom.construct import *
from pySim.tlv import *
from pySim.construct import *
from pySim.commands import SimCardCommands
from pySim.utils import Hexstr, SwHexstr, SwMatchstr
from pySim.commands import SimCardCommands
import pySim.global_platform
def compute_eid_checksum(eid) -> str:

View File

@@ -35,10 +35,11 @@ import cmd2
from cmd2 import CommandSet, with_default_category
from smartcard.util import toBytes
from pySim.utils import sw_match, h2b, b2h, is_hex, auto_int, auto_uint8, auto_uint16, is_hexstr, JsonEncoder
from pySim.utils import bertlv_parse_one
from osmocom.utils import h2b, b2h, is_hex, auto_int, auto_uint8, auto_uint16, is_hexstr, JsonEncoder
from osmocom.tlv import bertlv_parse_one
from osmocom.construct import filter_dict, parse_construct, build_construct
from pySim.construct import filter_dict, parse_construct, build_construct
from pySim.utils import sw_match
from pySim.jsonpath import js_path_modify
from pySim.commands import SimCardCommands
from pySim.exceptions import SwMatchError

View File

@@ -23,12 +23,13 @@ from construct import Optional as COptional
from construct import Struct, GreedyRange, FlagsEnum, Int16ub, Int24ub, Padding, Bit, Const
from Cryptodome.Random import get_random_bytes
from Cryptodome.Cipher import DES, DES3, AES
from osmocom.utils import *
from osmocom.tlv import *
from osmocom.construct import *
from pySim.utils import ResTuple
from pySim.card_key_provider import card_key_provider_get_field
from pySim.global_platform.scp import SCP02, SCP03
from pySim.construct import *
from pySim.utils import *
from pySim.filesystem import *
from pySim.tlv import *
from pySim.profile import CardProfile
from pySim.ota import SimFileAccessAndToolkitAppSpecParams

View File

@@ -20,8 +20,8 @@ Also known as SCP81 for SIM/USIM/UICC/eUICC/eSIM OTA.
from construct import Struct, Int8ub, Int16ub, Bytes, GreedyBytes, GreedyString, BytesInteger
from construct import this, len_, Rebuild, Const
from construct import Optional as COptional
from osmocom.tlv import BER_TLV_IE
from pySim.tlv import BER_TLV_IE
from pySim import cat

View File

@@ -22,7 +22,9 @@ from Cryptodome.Cipher import DES3, DES
from Cryptodome.Util.strxor import strxor
from construct import Struct, Bytes, Int8ub, Int16ub, Const
from construct import Optional as COptional
from pySim.utils import b2h, bertlv_parse_len, bertlv_encode_len
from osmocom.utils import b2h
from osmocom.tlv import bertlv_parse_len, bertlv_encode_len
from pySim.secure_channel import SecureChannel
logger = logging.getLogger(__name__)

View File

@@ -19,9 +19,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
from construct import Optional as COptional
from construct import Struct, GreedyRange, FlagsEnum, Int16ub, Int24ub, Padding, Bit, Const
from pySim.construct import *
from pySim.utils import *
from pySim.tlv import *
from osmocom.construct import *
from osmocom.utils import *
from osmocom.tlv import *
# Section 11.6.2.3 / Table 11-58
class SecurityDomainAid(BER_TLV_IE, tag=0x4f):

View File

@@ -28,8 +28,8 @@ from pySim.utils import *
from struct import pack, unpack
from construct import Struct, Bytes, Int8ub, Int16ub, Int24ub, Int32ub, FlagsEnum
from construct import Optional as COptional
from osmocom.construct import *
from pySim.construct import *
from pySim.profile import CardProfileAddon
from pySim.filesystem import *

View File

@@ -25,8 +25,8 @@ telecom-related protocol traces over UDP.
import socket
from construct import Optional as COptional
from construct import Int8ub, Int8sb, Int32ub, BitStruct, Enum, GreedyBytes, Struct, Switch
from construct import this, PaddedString
from pySim.construct import *
from construct import this, PaddedString, Flag, BitsInteger, Bytes
from osmocom.construct import *
# The root definition of GSMTAP can be found at
# https://cgit.osmocom.org/cgit/libosmocore/tree/include/osmocom/core/gsmtap.h

View File

@@ -18,10 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from construct import GreedyBytes, GreedyString
from pySim.construct import *
from pySim.utils import *
from pySim.filesystem import *
from pySim.tlv import *
from osmocom.tlv import *
from osmocom.construct import *
# Table 91 + Section 8.2.1.2
class ApplicationId(BER_TLV_IE, tag=0x4f):

View File

@@ -21,9 +21,9 @@ import struct
from typing import Optional, Tuple
from construct import Enum, Int8ub, Int16ub, Struct, Bytes, GreedyBytes, BitsInteger, BitStruct
from construct import Flag, Padding, Switch, this, PrefixedArray, GreedyRange
from osmocom.construct import *
from osmocom.utils import b2h
from pySim.construct import *
from pySim.utils import b2h
from pySim.sms import UserDataHeader
# ETS TS 102 225 gives the general command structure and the dialects for CAT_TP, TCP/IP and HTTPS

View File

@@ -18,8 +18,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from typing import Optional, Tuple
from osmocom.utils import h2b, i2h, is_hex, Hexstr
from osmocom.tlv import bertlv_parse_one
from pySim.utils import h2b, i2h, is_hex, bertlv_parse_one, Hexstr
from pySim.exceptions import *
from pySim.filesystem import *

View File

@@ -16,7 +16,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import abc
from pySim.utils import b2h, h2b, ResTuple, Hexstr
from osmocom.utils import b2h, h2b, Hexstr
from pySim.utils import ResTuple
class SecureChannel(abc.ABC):
@abc.abstractmethod

View File

@@ -23,9 +23,8 @@ from bidict import bidict
from construct import Int8ub, Byte, Bytes, Bit, Flag, BitsInteger
from construct import Struct, Enum, Tell, BitStruct, this, Padding
from construct import Prefixed, GreedyRange, GreedyBytes
from pySim.construct import HexAdapter, BcdAdapter, TonNpi
from pySim.utils import Hexstr, h2b, b2h
from osmocom.construct import HexAdapter, BcdAdapter, TonNpi
from osmocom.utils import Hexstr, h2b, b2h
from smpp.pdu import pdu_types, operations

View File

@@ -21,11 +21,11 @@ from struct import unpack
from construct import FlagsEnum, Byte, Struct, Int8ub, Bytes, Mapping, Enum, Padding, BitsInteger
from construct import Bit, this, Int32ub, Int16ub, Nibble, BytesInteger, GreedyRange, Const
from construct import Optional as COptional
from osmocom.utils import *
from osmocom.construct import *
from pySim.utils import *
from pySim.filesystem import *
from pySim.runtime import RuntimeState
from pySim.construct import *
import pySim
key_type2str = {

View File

@@ -1,553 +0,0 @@
"""object-oriented TLV parser/encoder library."""
# (C) 2021 by Harald Welte <laforge@osmocom.org>
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import inspect
import abc
import re
from typing import List, Tuple, Optional
from pySim.utils import bertlv_encode_len, bertlv_parse_len, bertlv_encode_tag, bertlv_parse_tag
from pySim.utils import comprehensiontlv_encode_tag, comprehensiontlv_parse_tag
from pySim.utils import bertlv_parse_tag_raw, comprehensiontlv_parse_tag_raw
from pySim.utils import dgi_parse_tag_raw, dgi_parse_len, dgi_encode_tag, dgi_encode_len
from pySim.construct import build_construct, parse_construct
def camel_to_snake(name):
name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', name).lower()
class TlvMeta(abc.ABCMeta):
"""Metaclass which we use to set some class variables at the time of defining a subclass.
This allows us to create subclasses for each TLV/IE type, where the class represents fixed
parameters like the tag/type and instances of it represent the actual TLV data."""
def __new__(mcs, name, bases, namespace, **kwargs):
#print("TlvMeta_new_(mcs=%s, name=%s, bases=%s, namespace=%s, kwargs=%s)" % (mcs, name, bases, namespace, kwargs))
x = super().__new__(mcs, name, bases, namespace)
# this becomes a _class_ variable, not an instance variable
x.tag = namespace.get('tag', kwargs.get('tag', None))
x.desc = namespace.get('desc', kwargs.get('desc', None))
nested = namespace.get('nested', kwargs.get('nested', None))
if nested is None or inspect.isclass(nested) and issubclass(nested, TLV_IE_Collection):
# caller has specified TLV_IE_Collection sub-class, we can directly reference it
x.nested_collection_cls = nested
else:
# caller passed list of other TLV classes that might possibly appear within us,
# build a dynamically-created TLV_IE_Collection sub-class and reference it
name = 'auto_collection_%s' % (name)
cls = type(name, (TLV_IE_Collection,), {'nested': nested})
x.nested_collection_cls = cls
return x
class TlvCollectionMeta(abc.ABCMeta):
"""Metaclass which we use to set some class variables at the time of defining a subclass.
This allows us to create subclasses for each Collection type, where the class represents fixed
parameters like the nested IE classes and instances of it represent the actual TLV data."""
def __new__(mcs, name, bases, namespace, **kwargs):
#print("TlvCollectionMeta_new_(mcs=%s, name=%s, bases=%s, namespace=%s, kwargs=%s)" % (mcs, name, bases, namespace, kwargs))
x = super().__new__(mcs, name, bases, namespace)
# this becomes a _class_ variable, not an instance variable
x.possible_nested = namespace.get('nested', kwargs.get('nested', None))
return x
class Transcodable(abc.ABC):
_construct = None
"""Base class for something that can be encoded + encoded. Decoding and Encoding happens either
* via a 'construct' object stored in a derived class' _construct variable, or
* via a 'construct' object stored in an instance _construct variable, or
* via a derived class' _{to,from}_bytes() methods."""
def __init__(self):
self.encoded = None
self.decoded = None
self._construct = None
def to_bytes(self, context: dict = {}) -> bytes:
"""Convert from internal representation to binary bytes. Store the binary result
in the internal state and return it."""
if self.decoded is None:
do = b''
elif self._construct:
do = build_construct(self._construct, self.decoded, context)
elif self.__class__._construct:
do = build_construct(self.__class__._construct, self.decoded, context)
else:
do = self._to_bytes()
self.encoded = do
return do
# not an abstractmethod, as it is only required if no _construct exists
def _to_bytes(self):
raise NotImplementedError('%s._to_bytes' % type(self).__name__)
def from_bytes(self, do: bytes, context: dict = {}):
"""Convert from binary bytes to internal representation. Store the decoded result
in the internal state and return it."""
self.encoded = do
if self.encoded == b'':
self.decoded = None
elif self._construct:
self.decoded = parse_construct(self._construct, do, context=context)
elif self.__class__._construct:
self.decoded = parse_construct(self.__class__._construct, do, context=context)
else:
self.decoded = self._from_bytes(do)
return self.decoded
# not an abstractmethod, as it is only required if no _construct exists
def _from_bytes(self, do: bytes):
raise NotImplementedError('%s._from_bytes' % type(self).__name__)
class IE(Transcodable, metaclass=TlvMeta):
# we specify the metaclass so any downstream subclasses will automatically use it
"""Base class for various Information Elements. We understand the notion of a hierarchy
of IEs on top of the Transcodable class."""
# this is overridden by the TlvMeta metaclass, if it is used to create subclasses
nested_collection_cls = None
tag = None
def __init__(self, **kwargs):
super().__init__()
self.nested_collection = None
if self.nested_collection_cls:
self.nested_collection = self.nested_collection_cls()
# if we are a constructed IE, [ordered] list of actual child-IE instances
self.children = kwargs.get('children', [])
self.decoded = kwargs.get('decoded', None)
def __repr__(self):
"""Return a string representing the [nested] IE data (for print)."""
if len(self.children):
member_strs = [repr(x) for x in self.children]
return '%s(%s)' % (type(self).__name__, ','.join(member_strs))
else:
return '%s(%s)' % (type(self).__name__, self.decoded)
def to_val_dict(self):
"""Return a JSON-serializable dict representing just the [nested] value portion of the IE
data. This does not include any indication of the type of 'self', so the resulting dict alone
will be insufficient ot recreate an object from it without additional type information."""
if len(self.children):
return [x.to_dict() for x in self.children]
else:
return self.decoded
def from_val_dict(self, decoded):
"""Set the IE internal decoded representation to data from the argument.
If this is a nested IE, the child IE instance list is re-created.
This method is symmetrical to to_val_dict() aboe, i.e. there is no outer dict
containig the snake-reformatted type name of 'self'."""
if self.nested_collection:
self.children = self.nested_collection.from_dict(decoded)
else:
self.children = []
self.decoded = decoded
def to_dict(self):
"""Return a JSON-serializable dict representing the [nested] IE data. The returned
data contains an outer dict with the snake-reformatted type of 'self' and is hence
sufficient to re-create an object from it."""
return {camel_to_snake(type(self).__name__): self.to_val_dict()}
def from_dict(self, decoded: dict):
"""Set the IE internal decoded representation to data from the argument.
If this is a nested IE, the child IE instance list is re-created.
This method is symmetrical to to_dict() above, i.e. the outer dict must contain just a single
key-value pair, where the key is the snake-reformatted type name of 'self'"""
expected_key_name = camel_to_snake(type(self).__name__)
if not expected_key_name in decoded:
raise ValueError("Dict %s doesn't contain expected key %s" % (decoded, expected_key_name))
self.from_val_dict(decoded[expected_key_name])
def is_constructed(self):
"""Is this IE constructed by further nested IEs?"""
return bool(len(self.children) > 0)
@abc.abstractmethod
def to_ie(self, context: dict = {}) -> bytes:
"""Convert the internal representation to entire IE including IE header."""
def to_bytes(self, context: dict = {}) -> bytes:
"""Convert the internal representation *of the value part* to binary bytes."""
if self.is_constructed():
# concatenate the encoded IE of all children to form the value part
out = b''
for c in self.children:
out += c.to_ie(context=context)
return out
else:
return super().to_bytes(context=context)
def from_bytes(self, do: bytes, context: dict = {}):
"""Parse *the value part* from binary bytes to internal representation."""
if self.nested_collection:
self.children = self.nested_collection.from_bytes(do, context=context)
else:
self.children = []
return super().from_bytes(do, context=context)
def child_by_name(self, name: str) -> Optional['IE']:
"""Return a child IE instance of given snake-case/json type name. This only works in case
there is no more than one child IE of the given type."""
children = list(filter(lambda c: camel_to_snake(type(c).__name__) == name, self.children))
if len(children) > 1:
raise KeyError('There are multiple children of class %s' % name)
elif len(children) == 1:
return children[0]
def child_by_type(self, cls) -> Optional['IE']:
"""Return a child IE instance of given type (class). This only works in case
there is no more than one child IE of the given type."""
children = list(filter(lambda c: isinstance(c, cls), self.children))
if len(children) > 1:
raise KeyError('There are multiple children of class %s' % cls)
elif len(children) == 1:
return children[0]
class TLV_IE(IE):
"""Abstract base class for various TLV type Information Elements."""
def _compute_tag(self) -> int:
"""Compute the tag (sometimes the tag encodes part of the value)."""
return self.tag
@classmethod
@abc.abstractmethod
def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
"""Obtain the raw TAG at the start of the bytes provided by the user."""
@classmethod
@abc.abstractmethod
def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
"""Obtain the length encoded at the start of the bytes provided by the user."""
@abc.abstractmethod
def _encode_tag(self) -> bytes:
"""Encode the tag part. Must be provided by derived (TLV format specific) class."""
@abc.abstractmethod
def _encode_len(self, val: bytes) -> bytes:
"""Encode the length part assuming a certain binary value. Must be provided by
derived (TLV format specific) class."""
def to_ie(self, context: dict = {}):
return self.to_tlv(context=context)
def to_tlv(self, context: dict = {}):
"""Convert the internal representation to binary TLV bytes."""
val = self.to_bytes(context=context)
return self._encode_tag() + self._encode_len(val) + val
def is_tag_compatible(self, rawtag) -> bool:
"""Is the given rawtag compatible with this class?"""
return rawtag == self._compute_tag()
def from_tlv(self, do: bytes, context: dict = {}):
if len(do) == 0:
return {}, b''
(rawtag, remainder) = self.__class__._parse_tag_raw(do)
if rawtag:
if not self.is_tag_compatible(rawtag):
raise ValueError("%s: Encountered tag %s doesn't match our supported tag %s" %
(self, rawtag, self.tag))
(length, remainder) = self.__class__._parse_len(remainder)
value = remainder[:length]
remainder = remainder[length:]
else:
value = do
remainder = b''
dec = self.from_bytes(value, context=context)
return dec, remainder
class COMPACT_TLV_IE(TLV_IE):
"""TLV_IE formatted as COMPACT-TLV described in ISO 7816"""
@classmethod
def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
return do[0] >> 4, do
@classmethod
def _decode_tag(cls, do: bytes) -> Tuple[dict, bytes]:
rawtag, remainder = cls._parse_tag_raw(do)
return {'tag': rawtag}, remainder
@classmethod
def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
return do[0] & 0xf, do[1:]
def _encode_tag(self) -> bytes:
"""Not needed as we override the to_tlv() method to encode tag+length into one byte."""
raise NotImplementedError
def _encode_len(self):
"""Not needed as we override the to_tlv() method to encode tag+length into one byte."""
raise NotImplementedError
def to_tlv(self, context: dict = {}):
val = self.to_bytes(context=context)
return bytes([(self.tag << 4) | (len(val) & 0xF)]) + val
class BER_TLV_IE(TLV_IE):
"""TLV_IE formatted as ASN.1 BER described in ITU-T X.690 8.1.2."""
@classmethod
def _decode_tag(cls, do: bytes) -> Tuple[dict, bytes]:
return bertlv_parse_tag(do)
@classmethod
def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
return bertlv_parse_tag_raw(do)
@classmethod
def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
return bertlv_parse_len(do)
def _encode_tag(self) -> bytes:
return bertlv_encode_tag(self._compute_tag())
def _encode_len(self, val: bytes) -> bytes:
return bertlv_encode_len(len(val))
class ComprTlvMeta(TlvMeta):
def __new__(mcs, name, bases, namespace, **kwargs):
x = super().__new__(mcs, name, bases, namespace, **kwargs)
if x.tag:
# we currently assume that the tag values always have the comprehension bit set;
# let's fix it up if a derived class has forgotten about that
if x.tag > 0xff and x.tag & 0x8000 == 0:
print("Fixing up COMPR_TLV_IE class %s: tag=0x%x has no comprehension bit" % (name, x.tag))
x.tag = x.tag | 0x8000
elif x.tag & 0x80 == 0:
print("Fixing up COMPR_TLV_IE class %s: tag=0x%x has no comprehension bit" % (name, x.tag))
x.tag = x.tag | 0x80
return x
class COMPR_TLV_IE(TLV_IE, metaclass=ComprTlvMeta):
"""TLV_IE formated as COMPREHENSION-TLV as described in ETSI TS 101 220."""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.comprehension = False
@classmethod
def _decode_tag(cls, do: bytes) -> Tuple[dict, bytes]:
return comprehensiontlv_parse_tag(do)
@classmethod
def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
return comprehensiontlv_parse_tag_raw(do)
@classmethod
def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
return bertlv_parse_len(do)
def is_tag_compatible(self, rawtag: int) -> bool:
"""Override is_tag_compatible as we need to mask out the
comprehension bit when doing compares."""
ctag = self._compute_tag()
if ctag > 0xff:
return ctag & 0x7fff == rawtag & 0x7fff
else:
return ctag & 0x7f == rawtag & 0x7f
def _encode_tag(self) -> bytes:
return comprehensiontlv_encode_tag(self._compute_tag())
def _encode_len(self, val: bytes) -> bytes:
return bertlv_encode_len(len(val))
class DGI_TLV_IE(TLV_IE):
"""TLV_IE formated as GlobalPlatform Systems Scripting Language Specification v1.1.0 Annex B."""
@classmethod
def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
return dgi_parse_tag_raw(do)
@classmethod
def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
return dgi_parse_len(do)
def _encode_tag(self) -> bytes:
return dgi_encode_tag(self._compute_tag())
def _encode_len(self, val: bytes) -> bytes:
return dgi_encode_len(len(val))
class TLV_IE_Collection(metaclass=TlvCollectionMeta):
# we specify the metaclass so any downstream subclasses will automatically use it
"""A TLV_IE_Collection consists of multiple TLV_IE classes identified by their tags.
A given encoded DO may contain any of them in any order, and may contain multiple instances
of each DO."""
# this is overridden by the TlvCollectionMeta metaclass, if it is used to create subclasses
possible_nested = []
def __init__(self, desc=None, **kwargs):
self.desc = desc
#print("possible_nested: ", self.possible_nested)
self.members = kwargs.get('nested', self.possible_nested)
self.members_by_tag = {}
self.members_by_name = {}
self.members_by_tag = {m.tag: m for m in self.members}
self.members_by_name = {camel_to_snake(m.__name__): m for m in self.members}
# if we are a constructed IE, [ordered] list of actual child-IE instances
self.children = kwargs.get('children', [])
self.encoded = None
def __str__(self):
member_strs = [str(x) for x in self.members]
return '%s(%s)' % (type(self).__name__, ','.join(member_strs))
def __repr__(self):
member_strs = [repr(x) for x in self.members]
return '%s(%s)' % (self.__class__, ','.join(member_strs))
def __add__(self, other):
"""Extending TLV_IE_Collections with other TLV_IE_Collections or TLV_IEs."""
if isinstance(other, TLV_IE_Collection):
# adding one collection to another
members = self.members + other.members
return TLV_IE_Collection(self.desc, nested=members)
elif inspect.isclass(other) and issubclass(other, TLV_IE):
# adding a member to a collection
return TLV_IE_Collection(self.desc, nested=self.members + [other])
else:
raise TypeError
def from_bytes(self, binary: bytes, context: dict = {}) -> List[TLV_IE]:
"""Create a list of TLV_IEs from the collection based on binary input data.
Args:
binary : binary bytes of encoded data
Returns:
list of instances of TLV_IE sub-classes containing parsed data
"""
self.encoded = binary
# list of instances of TLV_IE collection member classes appearing in the data
res = []
remainder = binary
first = next(iter(self.members_by_tag.values()))
# iterate until no binary trailer is left
while len(remainder):
context['siblings'] = res
# obtain the tag at the start of the remainder
tag, _r = first._parse_tag_raw(remainder)
if tag is None:
break
if issubclass(first, COMPR_TLV_IE):
tag = tag | 0x80 # HACK: always assume comprehension
if tag in self.members_by_tag:
cls = self.members_by_tag[tag]
# create an instance and parse accordingly
inst = cls()
_dec, remainder = inst.from_tlv(remainder, context=context)
res.append(inst)
else:
# unknown tag; create the related class on-the-fly using the same base class
name = 'unknown_%s_%X' % (first.__base__.__name__, tag)
cls = type(name, (first.__base__,), {'tag': tag, 'possible_nested': [],
'nested_collection_cls': None})
cls._from_bytes = lambda s, a: {'raw': a.hex()}
cls._to_bytes = lambda s: bytes.fromhex(s.decoded['raw'])
# create an instance and parse accordingly
inst = cls()
_dec, remainder = inst.from_tlv(remainder, context=context)
res.append(inst)
self.children = res
return res
def from_dict(self, decoded: List[dict]) -> List[TLV_IE]:
"""Create a list of TLV_IE instances from the collection based on an array
of dicts, where they key indicates the name of the TLV_IE subclass to use."""
# list of instances of TLV_IE collection member classes appearing in the data
res = []
# iterate over members of the list passed into "decoded"
for i in decoded:
# iterate over all the keys (typically one!) within the current list item dict
for k in i.keys():
# check if we have a member identified by the dict key
if k in self.members_by_name:
# resolve the class for that name; create an instance of it
cls = self.members_by_name[k]
inst = cls()
inst.from_dict({k: i[k]})
res.append(inst)
else:
raise ValueError('%s: Unknown TLV Class %s in %s; expected %s' %
(self, k, decoded, self.members_by_name.keys()))
self.children = res
return res
def to_dict(self):
# we intentionally return not a dict, but a list of dicts. We could prefix by
# self.__class__.__name__, but that is usually some meaningless auto-generated collection name.
return [x.to_dict() for x in self.children]
def to_bytes(self, context: dict = {}):
out = b''
context['siblings'] = self.children
for c in self.children:
out += c.to_tlv(context=context)
return out
def from_tlv(self, do, context: dict = {}):
return self.from_bytes(do, context=context)
def to_tlv(self, context: dict = {}):
return self.to_bytes(context=context)
def flatten_dict_lists(inp):
"""hierarchically flatten each list-of-dicts into a single dict. This is useful to
make the output of hierarchical TLV decoder structures flatter and more easy to read."""
def are_all_elements_dict(l):
for e in l:
if not isinstance(e, dict):
return False
return True
def are_elements_unique(lod):
set_of_keys = {list(x.keys())[0] for x in lod}
return len(lod) == len(set_of_keys)
if isinstance(inp, list):
if are_all_elements_dict(inp) and are_elements_unique(inp):
# flatten into one shared dict
newdict = {}
for e in inp:
key = list(e.keys())[0]
newdict[key] = e[key]
inp = newdict
# process result as any native dict
return {k:flatten_dict_lists(v) for k,v in inp.items()}
else:
return [flatten_dict_lists(x) for x in inp]
elif isinstance(inp, dict):
return {k:flatten_dict_lists(v) for k,v in inp.items()}
else:
return inp

View File

@@ -8,9 +8,10 @@ import abc
import argparse
from typing import Optional, Tuple
from construct import Construct
from osmocom.utils import b2h, h2b, i2h, Hexstr
from pySim.exceptions import *
from pySim.utils import sw_match, b2h, h2b, i2h, Hexstr, SwHexstr, SwMatchstr, ResTuple
from pySim.utils import SwHexstr, SwMatchstr, ResTuple, sw_match
from pySim.cat import ProactiveCommand, CommandDetails, DeviceIdentities, Result
#

View File

@@ -22,10 +22,11 @@ import socket
import os
import argparse
from typing import Optional
from osmocom.utils import h2b, b2h, Hexstr
from pySim.transport import LinkBase
from pySim.exceptions import ReaderError, ProtocolError
from pySim.utils import h2b, b2h, Hexstr, ResTuple
from pySim.utils import ResTuple
class L1CTLMessage:

View File

@@ -22,8 +22,9 @@ import re
import argparse
from typing import Optional
import serial
from osmocom.utils import Hexstr
from pySim.utils import Hexstr, ResTuple
from pySim.utils import ResTuple
from pySim.transport import LinkBase
from pySim.exceptions import ReaderError, ProtocolError

View File

@@ -27,9 +27,11 @@ from smartcard.Exceptions import NoCardException, CardRequestTimeoutException, C
from smartcard.System import readers
from smartcard.ExclusiveConnectCardConnection import ExclusiveConnectCardConnection
from osmocom.utils import h2i, i2h, Hexstr
from pySim.exceptions import NoCardError, ProtocolError, ReaderError
from pySim.transport import LinkBase
from pySim.utils import h2i, i2h, Hexstr, ResTuple
from pySim.utils import ResTuple
class PcscSimLink(LinkBase):

View File

@@ -21,10 +21,11 @@ import os
import argparse
from typing import Optional
import serial
from osmocom.utils import h2b, b2h, Hexstr
from pySim.exceptions import NoCardError, ProtocolError
from pySim.transport import LinkBase
from pySim.utils import h2b, b2h, Hexstr, ResTuple
from pySim.utils import ResTuple
class SerialSimLink(LinkBase):

View File

@@ -21,10 +21,11 @@ from bidict import bidict
from construct import Select, Const, Bit, Struct, Int16ub, FlagsEnum, GreedyString, ValidationError
from construct import Optional as COptional, Computed
from pySim.construct import *
from osmocom.construct import *
from osmocom.utils import *
from osmocom.tlv import *
from pySim.utils import *
from pySim.filesystem import *
from pySim.tlv import *
from pySim.profile import CardProfile
from pySim.profile import match_uicc
from pySim import iso7816_4

View File

@@ -19,11 +19,9 @@
from typing import List
import argparse
import cmd2
from cmd2 import CommandSet, with_default_category
from pySim.utils import b2h, auto_uint8, auto_uint16, is_hexstr
from osmocom.utils import b2h, auto_uint8, auto_uint16, is_hexstr
from pySim.ts_102_221 import *

View File

@@ -17,13 +17,12 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from pySim.construct import *
from construct import *
from construct import Optional as COptional
from osmocom.construct import *
#from pySim.utils import *
from osmocom.tlv import BER_TLV_IE, TLV_IE_Collection
from pySim.filesystem import CardDF, TransparentEF
from pySim.tlv import BER_TLV_IE, TLV_IE_Collection
# TS102 310 Section 7.1
class EF_EAPKEYS(TransparentEF):

View File

@@ -32,6 +32,10 @@ from construct import Optional as COptional
from construct import Int32ub, Nibble, GreedyRange, Struct, FlagsEnum, Switch, this, Int16ub, Padding
from construct import Bytewise, Int24ub, PaddedString, PrefixedArray, If
from osmocom.utils import is_hexstr
from osmocom.tlv import *
from osmocom.construct import *
import pySim.ts_102_221
from pySim.ts_51_011 import EF_ACMmax, EF_AAeM, EF_eMLPP, EF_CMI, EF_PNN
from pySim.ts_51_011 import EF_MMSN, EF_MMSICP, EF_MMSUP, EF_MMSUCP, EF_VGCS, EF_VGCSS, EF_NIA
@@ -40,13 +44,10 @@ from pySim.ts_51_011 import EF_CBMID, EF_CBMIR, EF_ADN, EF_CFIS, EF_SMS, EF_MSIS
from pySim.ts_51_011 import EF_IMSI, EF_xPLMNwAcT, EF_SPN, EF_CBMI, EF_ACC, EF_PLMNsel
from pySim.ts_51_011 import EF_Kc, EF_CPBCCH, EF_InvScan
from pySim.ts_102_221 import EF_ARR
from pySim.tlv import *
from pySim.filesystem import *
from pySim.ts_31_102_telecom import DF_PHONEBOOK, EF_UServiceTable
from pySim.ts_31_103_shared import EF_IMSConfigData, EF_XCAPConfigData, EF_MuDMiDConfigData
from pySim.ts_31_103_shared import EF_AC_GBAUAPI, EF_IMSDCI
from pySim.construct import *
from pySim.utils import is_hexstr
from pySim.cat import SMS_TPDU, DeviceIdentities, SMSPPDownload
# Mapping between USIM Service Number and its description

View File

@@ -28,10 +28,10 @@ Needs to be a separate python module to avoid cyclic imports
from construct import Optional as COptional
from construct import Struct, Int16ub, Int32ub
from osmocom.tlv import *
from osmocom.construct import *
from pySim.tlv import *
from pySim.filesystem import *
from pySim.construct import *
# TS 31.102 Section 4.2.8
class EF_UServiceTable(TransparentEF):

View File

@@ -23,16 +23,16 @@ Various constants from 3GPP TS 31.103 V18.1.0
#
from construct import Struct, Switch, this, Bytes, GreedyString
from osmocom.utils import *
from osmocom.tlv import *
from osmocom.construct import *
from pySim.filesystem import *
from pySim.utils import *
from pySim.tlv import *
from pySim.ts_51_011 import EF_AD, EF_SMS, EF_SMSS, EF_SMSR, EF_SMSP
from pySim.ts_31_102 import ADF_USIM, EF_FromPreferred
from pySim.ts_31_102_telecom import EF_UServiceTable
from pySim.ts_31_103_shared import *
import pySim.ts_102_221
from pySim.ts_102_221 import EF_ARR
from pySim.construct import *
# Mapping between ISIM Service Number and its description
EF_IST_map = {

View File

@@ -20,9 +20,9 @@ hence need to be in a separate python module to avoid circular dependencies.
#
from construct import Struct, Switch, Bytes, GreedyString, GreedyBytes, Int8ub, Prefixed, Enum, Byte
from pySim.construct import HexAdapter, Utf8Adapter
from osmocom.tlv import BER_TLV_IE, TLV_IE_Collection
from osmocom.construct import HexAdapter, Utf8Adapter
from pySim.filesystem import *
from pySim.tlv import BER_TLV_IE, TLV_IE_Collection
# TS 31.103 Section 4.2.16
class EF_UICCIARI(LinFixedEF):

View File

@@ -20,9 +20,9 @@ Support for 3GPP TS 31.104 V17.0.0
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from osmocom.utils import *
from osmocom.tlv import *
from pySim.filesystem import *
from pySim.utils import *
from pySim.tlv import *
from pySim.ts_31_102 import ADF_USIM
from pySim.ts_51_011 import EF_IMSI, EF_AD
import pySim.ts_102_221

View File

@@ -29,19 +29,23 @@ order to describe the files specified in the relevant ETSI + 3GPP specifications
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import enum
from struct import pack, unpack
from typing import Tuple
from construct import Optional as COptional
from construct import *
from osmocom.tlv import *
from osmocom.utils import *
from osmocom.construct import *
from pySim.utils import dec_iccid, enc_iccid, dec_imsi, enc_imsi, dec_plmn, enc_plmn, dec_xplmn_w_act
from pySim.utils import dec_msisdn, enc_msisdn
from pySim.profile import match_sim
from pySim.profile import CardProfile, CardProfileAddon
from pySim.filesystem import *
from pySim.ts_31_102_telecom import DF_PHONEBOOK, DF_MULTIMEDIA, DF_MCS, DF_V2X
from pySim.gsm_r import AddonGSMR
import enum
from pySim.construct import *
from construct import Optional as COptional
from construct import *
from struct import pack, unpack
from typing import Tuple
from pySim.tlv import *
from pySim.utils import *
# Mapping between SIM Service Number and its description
EF_SST_map = {

View File

@@ -10,6 +10,8 @@ import datetime
import argparse
from io import BytesIO
from typing import Optional, List, Dict, Any, Tuple, NewType, Union
from osmocom.utils import *
from osmocom.tlv import bertlv_encode_tag, bertlv_encode_len
# Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com>
# Copyright (C) 2021 Harald Welte <laforge@osmocom.org>
@@ -28,400 +30,6 @@ from typing import Optional, List, Dict, Any, Tuple, NewType, Union
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# just to differentiate strings of hex nibbles from everything else
Hexstr = NewType('Hexstr', str)
SwHexstr = NewType('SwHexstr', str)
SwMatchstr = NewType('SwMatchstr', str)
ResTuple = Tuple[Hexstr, SwHexstr]
def h2b(s: Hexstr) -> bytearray:
"""convert from a string of hex nibbles to a sequence of bytes"""
return bytearray.fromhex(s)
def b2h(b: bytearray) -> Hexstr:
"""convert from a sequence of bytes to a string of hex nibbles"""
return ''.join(['%02x' % (x) for x in b])
def h2i(s: Hexstr) -> List[int]:
"""convert from a string of hex nibbles to a list of integers"""
return [(int(x, 16) << 4)+int(y, 16) for x, y in zip(s[0::2], s[1::2])]
def i2h(s: List[int]) -> Hexstr:
"""convert from a list of integers to a string of hex nibbles"""
return ''.join(['%02x' % (x) for x in s])
def h2s(s: Hexstr) -> str:
"""convert from a string of hex nibbles to an ASCII string"""
return ''.join([chr((int(x, 16) << 4)+int(y, 16)) for x, y in zip(s[0::2], s[1::2])
if int(x + y, 16) != 0xff])
def s2h(s: str) -> Hexstr:
"""convert from an ASCII string to a string of hex nibbles"""
b = bytearray()
b.extend(map(ord, s))
return b2h(b)
def i2s(s: List[int]) -> str:
"""convert from a list of integers to an ASCII string"""
return ''.join([chr(x) for x in s])
def swap_nibbles(s: Hexstr) -> Hexstr:
"""swap the nibbles in a hex string"""
return ''.join([x+y for x, y in zip(s[1::2], s[0::2])])
def rpad(s: str, l: int, c='f') -> str:
"""pad string on the right side.
Args:
s : string to pad
l : total length to pad to
c : padding character
Returns:
String 's' padded with as many 'c' as needed to reach total length of 'l'
"""
return s + c * (l - len(s))
def lpad(s: str, l: int, c='f') -> str:
"""pad string on the left side.
Args:
s : string to pad
l : total length to pad to
c : padding character
Returns:
String 's' padded with as many 'c' as needed to reach total length of 'l'
"""
return c * (l - len(s)) + s
def half_round_up(n: int) -> int:
return (n + 1)//2
def str_sanitize(s: str) -> str:
"""replace all non printable chars, line breaks and whitespaces, with ' ', make sure that
there are no whitespaces at the end and at the beginning of the string.
Args:
s : string to sanitize
Returns:
filtered result of string 's'
"""
chars_to_keep = string.digits + string.ascii_letters + string.punctuation
res = ''.join([c if c in chars_to_keep else ' ' for c in s])
return res.strip()
#########################################################################
# poor man's COMPREHENSION-TLV decoder.
#########################################################################
def comprehensiontlv_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]:
"""Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
if binary[0] in [0x00, 0x80, 0xff]:
raise ValueError("Found illegal value 0x%02x in %s" %
(binary[0], binary))
if binary[0] == 0x7f:
# three-byte tag
tag = binary[0] << 16 | binary[1] << 8 | binary[2]
return (tag, binary[3:])
elif binary[0] == 0xff:
return None, binary
else:
# single byte tag
tag = binary[0]
return (tag, binary[1:])
def comprehensiontlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]:
"""Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
if binary[0] in [0x00, 0x80, 0xff]:
raise ValueError("Found illegal value 0x%02x in %s" %
(binary[0], binary))
if binary[0] == 0x7f:
# three-byte tag
tag = (binary[1] & 0x7f) << 8
tag |= binary[2]
compr = bool(binary[1] & 0x80)
return ({'comprehension': compr, 'tag': tag}, binary[3:])
else:
# single byte tag
tag = binary[0] & 0x7f
compr = bool(binary[0] & 0x80)
return ({'comprehension': compr, 'tag': tag}, binary[1:])
def comprehensiontlv_encode_tag(tag) -> bytes:
"""Encode a single Tag according to ETSI TS 101 220 Section 7.1.1"""
# permit caller to specify tag also as integer value
if isinstance(tag, int):
compr = bool(tag < 0xff and tag & 0x80)
tag = {'tag': tag, 'comprehension': compr}
compr = tag.get('comprehension', False)
if tag['tag'] in [0x00, 0x80, 0xff] or tag['tag'] > 0xff:
# 3-byte format
byte3 = tag['tag'] & 0xff
byte2 = (tag['tag'] >> 8) & 0x7f
if compr:
byte2 |= 0x80
return b'\x7f' + byte2.to_bytes(1, 'big') + byte3.to_bytes(1, 'big')
else:
# 1-byte format
ret = tag['tag']
if compr:
ret |= 0x80
return ret.to_bytes(1, 'big')
# length value coding is equal to BER-TLV
def comprehensiontlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]:
"""Parse a single TLV IE at the start of the given binary data.
Args:
binary : binary input data of BER-TLV length field
Returns:
Tuple of (tag:dict, len:int, remainder:bytes)
"""
(tagdict, remainder) = comprehensiontlv_parse_tag(binary)
(length, remainder) = bertlv_parse_len(remainder)
value = remainder[:length]
remainder = remainder[length:]
return (tagdict, length, value, remainder)
#########################################################################
# poor man's BER-TLV decoder. To be a more sophisticated OO library later
#########################################################################
def bertlv_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]:
"""Get a single raw Tag from start of input according to ITU-T X.690 8.1.2
Args:
binary : binary input data of BER-TLV length field
Returns:
Tuple of (tag:int, remainder:bytes)
"""
# check for FF padding at the end, as customary in SIM card files
if binary[0] == 0xff and len(binary) == 1 or binary[0] == 0xff and binary[1] == 0xff:
return None, binary
tag = binary[0] & 0x1f
if tag <= 30:
return binary[0], binary[1:]
else: # multi-byte tag
tag = binary[0]
i = 1
last = False
while not last:
last = not bool(binary[i] & 0x80)
tag <<= 8
tag |= binary[i]
i += 1
return tag, binary[i:]
def bertlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]:
"""Parse a single Tag value according to ITU-T X.690 8.1.2
Args:
binary : binary input data of BER-TLV length field
Returns:
Tuple of ({class:int, constructed:bool, tag:int}, remainder:bytes)
"""
cls = binary[0] >> 6
constructed = bool(binary[0] & 0x20)
tag = binary[0] & 0x1f
if tag <= 30:
return ({'class': cls, 'constructed': constructed, 'tag': tag}, binary[1:])
else: # multi-byte tag
tag = 0
i = 1
last = False
while not last:
last = not bool(binary[i] & 0x80)
tag <<= 7
tag |= binary[i] & 0x7f
i += 1
return ({'class': cls, 'constructed': constructed, 'tag': tag}, binary[i:])
def bertlv_encode_tag(t) -> bytes:
"""Encode a single Tag value according to ITU-T X.690 8.1.2
"""
def get_top7_bits(inp: int) -> Tuple[int, int]:
"""Get top 7 bits of integer. Returns those 7 bits as integer and the remaining LSBs."""
remain_bits = inp.bit_length()
if remain_bits >= 7:
bitcnt = 7
else:
bitcnt = remain_bits
outp = inp >> (remain_bits - bitcnt)
remainder = inp & ~ (inp << (remain_bits - bitcnt))
return outp, remainder
def count_int_bytes(inp: int) -> int:
"""count the number of bytes require to represent the given integer."""
i = 1
inp = inp >> 8
while inp:
i += 1
inp = inp >> 8
return i
if isinstance(t, int):
# first convert to a dict representation
tag_size = count_int_bytes(t)
t, _remainder = bertlv_parse_tag(t.to_bytes(tag_size, 'big'))
tag = t['tag']
constructed = t['constructed']
cls = t['class']
if tag <= 30:
t = tag & 0x1f
if constructed:
t |= 0x20
t |= (cls & 3) << 6
return bytes([t])
else: # multi-byte tag
t = 0x1f
if constructed:
t |= 0x20
t |= (cls & 3) << 6
tag_bytes = bytes([t])
remain = tag
while True:
t, remain = get_top7_bits(remain)
if remain:
t |= 0x80
tag_bytes += bytes([t])
if not remain:
break
return tag_bytes
def bertlv_parse_len(binary: bytes) -> Tuple[int, bytes]:
"""Parse a single Length value according to ITU-T X.690 8.1.3;
only the definite form is supported here.
Args:
binary : binary input data of BER-TLV length field
Returns:
Tuple of (length, remainder)
"""
if binary[0] < 0x80:
return (binary[0], binary[1:])
else:
num_len_oct = binary[0] & 0x7f
length = 0
if len(binary) < num_len_oct + 1:
return (0, b'')
for i in range(1, 1+num_len_oct):
length <<= 8
length |= binary[i]
return (length, binary[1+num_len_oct:])
def bertlv_encode_len(length: int) -> bytes:
"""Encode a single Length value according to ITU-T X.690 8.1.3;
only the definite form is supported here.
Args:
length : length value to be encoded
Returns:
binary output data of BER-TLV length field
"""
if length < 0x80:
return length.to_bytes(1, 'big')
elif length <= 0xff:
return b'\x81' + length.to_bytes(1, 'big')
elif length <= 0xffff:
return b'\x82' + length.to_bytes(2, 'big')
elif length <= 0xffffff:
return b'\x83' + length.to_bytes(3, 'big')
elif length <= 0xffffffff:
return b'\x84' + length.to_bytes(4, 'big')
else:
raise ValueError("Length > 32bits not supported")
def bertlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]:
"""Parse a single TLV IE at the start of the given binary data.
Args:
binary : binary input data of BER-TLV length field
Returns:
Tuple of (tag:dict, len:int, remainder:bytes)
"""
(tagdict, remainder) = bertlv_parse_tag(binary)
(length, remainder) = bertlv_parse_len(remainder)
value = remainder[:length]
remainder = remainder[length:]
return (tagdict, length, value, remainder)
def bertlv_parse_one_rawtag(binary: bytes) -> Tuple[int, int, bytes, bytes]:
"""Parse a single TLV IE at the start of the given binary data; return tag as raw integer.
Args:
binary : binary input data of BER-TLV length field
Returns:
Tuple of (tag:int, len:int, remainder:bytes)
"""
(tag, remainder) = bertlv_parse_tag_raw(binary)
(length, remainder) = bertlv_parse_len(remainder)
value = remainder[:length]
remainder = remainder[length:]
return (tag, length, value, remainder)
def bertlv_return_one_rawtlv(binary: bytes) -> Tuple[int, int, bytes, bytes]:
"""Return one single [encoded] TLV IE at the start of the given binary data.
Args:
binary : binary input data of BER-TLV length field
Returns:
Tuple of (tag:int, len:int, tlv:bytes, remainder:bytes)
"""
(tag, remainder) = bertlv_parse_tag_raw(binary)
(length, remainder) = bertlv_parse_len(remainder)
tl_length = len(binary) - len(remainder)
value = binary[:tl_length] + remainder[:length]
remainder = remainder[length:]
return (tag, length, value, remainder)
def dgi_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]:
# In absence of any clear spec guidance we assume it's always 16 bit
return int.from_bytes(binary[:2], 'big'), binary[2:]
def dgi_encode_tag(t: int) -> bytes:
return t.to_bytes(2, 'big')
def dgi_encode_len(length: int) -> bytes:
"""Encode a single Length value according to GlobalPlatform Systems Scripting Language
Specification v1.1.0 Annex B.
Args:
length : length value to be encoded
Returns:
binary output data of encoded length field
"""
if length < 255:
return length.to_bytes(1, 'big')
elif length <= 0xffff:
return b'\xff' + length.to_bytes(2, 'big')
else:
raise ValueError("Length > 32bits not supported")
def dgi_parse_len(binary: bytes) -> Tuple[int, bytes]:
"""Parse a single Length value according to GlobalPlatform Systems Scripting Language
Specification v1.1.0 Annex B.
Args:
binary : binary input data of BER-TLV length field
Returns:
Tuple of (length, remainder)
"""
if binary[0] == 255:
assert len(binary) >= 3
return ((binary[1] << 8) | binary[2]), binary[3:]
else:
return binary[0], binary[1:]
# IMSI encoded format:
# For IMSI 0123456789ABCDE:
#
@@ -437,6 +45,10 @@ def dgi_parse_len(binary: bytes) -> Tuple[int, bytes]:
# Because of this, an odd length IMSI fits exactly into len(imsi) + 1 // 2 bytes, whereas an
# even length IMSI only uses half of the last byte.
SwHexstr = NewType('SwHexstr', str)
SwMatchstr = NewType('SwMatchstr', str)
ResTuple = Tuple[Hexstr, SwHexstr]
def enc_imsi(imsi: str):
"""Converts a string IMSI into the encoded value of the EF"""
l = half_round_up(
@@ -809,29 +421,6 @@ def enc_msisdn(msisdn: str, npi: int = 0x01, ton: int = 0x03) -> Hexstr:
return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
def is_hex(string: str, minlen: int = 2, maxlen: Optional[int] = None) -> bool:
"""
Check if a string is a valid hexstring
"""
# Filter obviously bad strings
if not string:
return False
if len(string) < minlen or minlen < 2:
return False
if len(string) % 2:
return False
if maxlen and len(string) > maxlen:
return False
# Try actual encoding to be sure
try:
_try_encode = h2b(string)
return True
except:
return False
def sanitize_pin_adm(pin_adm, pin_adm_hex=None) -> Hexstr:
"""
The ADM pin can be supplied either in its hexadecimal form or as
@@ -963,26 +552,6 @@ def tabulate_str_list(str_list, width: int = 79, hspace: int = 2, lspace: int =
return '\n'.join(table)
def auto_int(x):
"""Helper function for argparse to accept hexadecimal integers."""
return int(x, 0)
def _auto_uint(x, max_val: int):
"""Helper function for argparse to accept hexadecimal or decimal integers."""
ret = int(x, 0)
if ret < 0 or ret > max_val:
raise argparse.ArgumentTypeError('Number exceeds permited value range (0, %u)' % max_val)
return ret
def auto_uint7(x):
return _auto_uint(x, 127)
def auto_uint8(x):
return _auto_uint(x, 255)
def auto_uint16(x):
return _auto_uint(x, 65535)
def expand_hex(hexstring, length):
"""Expand a given hexstring to a specified length by replacing "." or ".."
with a filler that is derived from the neighboring nibbles respective
@@ -1037,17 +606,6 @@ def expand_hex(hexstring, length):
return hexstring
class JsonEncoder(json.JSONEncoder):
"""Extend the standard library JSONEncoder with support for more types."""
def default(self, o):
if isinstance(o, (BytesIO, bytes, bytearray)):
return b2h(o)
elif isinstance(o, datetime.datetime):
return o.isoformat()
return json.JSONEncoder.default(self, o)
def boxed_heading_str(heading, width=80):
"""Generate a string that contains a boxed heading."""
# Auto-enlarge box if heading exceeds length
@@ -1456,35 +1014,3 @@ class CardCommandSet:
if cla and not cmd.match_cla(cla):
return None
return cmd
def all_subclasses(cls) -> set:
"""Recursively get all subclasses of a specified class"""
return set(cls.__subclasses__()).union([s for c in cls.__subclasses__() for s in all_subclasses(c)])
def is_hexstr_or_decimal(instr: str) -> str:
"""Method that can be used as 'type' in argparse.add_argument() to validate the value consists of
[hexa]decimal digits only."""
if instr.isdecimal():
return instr
if not all(c in string.hexdigits for c in instr):
raise ValueError('Input must be [hexa]decimal')
if len(instr) & 1:
raise ValueError('Input has un-even number of hex digits')
return instr
def is_hexstr(instr: str) -> str:
"""Method that can be used as 'type' in argparse.add_argument() to validate the value consists of
an even sequence of hexadecimal digits only."""
if not all(c in string.hexdigits for c in instr):
raise ValueError('Input must be hexadecimal')
if len(instr) & 1:
raise ValueError('Input has un-even number of hex digits')
return instr
def is_decimal(instr: str) -> str:
"""Method that can be used as 'type' in argparse.add_argument() to validate the value consists of
an even sequence of decimal digits only."""
if not instr.isdecimal():
raise ValueError('Input must decimal')
return instr

View File

@@ -5,7 +5,7 @@ cmd2>=1.5
jsonpath-ng
construct>=2.9.51
bidict
gsm0338
pyosmocom
pyyaml>=5.1
termcolor
colorlog

View File

@@ -24,7 +24,7 @@ setup(
"jsonpath-ng",
"construct >= 2.9.51",
"bidict",
"gsm0338",
"pyosmocom",
"pyyaml >= 5.1",
"termcolor",
"colorlog",

View File

@@ -1,8 +1,9 @@
#!/usr/bin/env python3
import unittest
from pySim.utils import h2b, b2h
from pySim.construct import filter_dict
from osmocom.utils import h2b, b2h
from osmocom.construct import filter_dict
from pySim.apdu import Apdu
from pySim.apdu.ts_31_102 import UsimAuthenticateEven

View File

@@ -1,103 +0,0 @@
#!/usr/bin/env python3
import unittest
from pySim.utils import b2h, h2b
from pySim.construct import *
from construct import FlagsEnum
tests = [
( b'\x80', 0x80 ),
( b'\x80\x01', 0x8001 ),
( b'\x80\x00\x01', 0x800001 ),
( b'\x80\x23\x42\x01', 0x80234201 ),
]
class TestGreedyInt(unittest.TestCase):
def test_GreedyInt_decoder(self):
gi = GreedyInteger()
for t in tests:
self.assertEqual(gi.parse(t[0]), t[1])
def test_GreedyInt_encoder(self):
gi = GreedyInteger()
for t in tests:
self.assertEqual(t[0], gi.build(t[1]))
pass
class TestUtils(unittest.TestCase):
def test_filter_dict(self):
inp = {'foo': 0xf00, '_bar' : 0xba5, 'baz': 0xba2 }
out = {'foo': 0xf00, 'baz': 0xba2 }
self.assertEqual(filter_dict(inp), out)
def test_filter_dict_nested(self):
inp = {'foo': 0xf00, 'nest': {'_bar' : 0xba5}, 'baz': 0xba2 }
out = {'foo': 0xf00, 'nest': {}, 'baz': 0xba2 }
self.assertEqual(filter_dict(inp), out)
class TestUcs2Adapter(unittest.TestCase):
# the three examples from TS 102 221 Annex A
EXAMPLE1 = b'\x80\x00\x30\x00\x31\x00\x32\x00\x33'
EXAMPLE2 = b'\x81\x05\x13\x53\x95\xa6\xa6\xff\xff'
EXAMPLE3 = b'\x82\x05\x05\x30\x2d\x82\xd3\x2d\x31'
ad = Ucs2Adapter(GreedyBytes)
def test_example1_decode(self):
dec = self.ad._decode(self.EXAMPLE1, None, None)
self.assertEqual(dec, "0123")
def test_example2_decode(self):
dec = self.ad._decode(self.EXAMPLE2, None, None)
self.assertEqual(dec, "S\u0995\u09a6\u09a6\u09ff")
def test_example3_decode(self):
dec = self.ad._decode(self.EXAMPLE3, None, None)
self.assertEqual(dec, "-\u0532\u0583-1")
testdata = [
# variant 2 with only GSM alphabet characters
( "mahlzeit", '8108006d61686c7a656974' ),
# variant 2 with mixed GSM alphabet + UCS2
( "mahlzeit\u099523", '810b136d61686c7a656974953233' ),
# variant 3 due to codepoint exceeding 8 bit
( "mahl\u8023zeit", '820980236d61686c807a656974' ),
# variant 1 as there is no common codepoint pointer / prefix
( "\u3000\u2000\u1000", '80300020001000' ),
]
def test_data_decode(self):
for string, encoded_hex in self.testdata:
encoded = h2b(encoded_hex)
dec = self.ad._decode(encoded, None, None)
self.assertEqual(dec, string)
def test_data_encode(self):
for string, encoded_hex in self.testdata:
encoded = h2b(encoded_hex)
re_enc = self.ad._encode(string, None, None)
self.assertEqual(encoded, re_enc)
class TestTrailerAdapter(unittest.TestCase):
Privileges = FlagsEnum(StripTrailerAdapter(GreedyBytes, 3), security_domain=0x800000,
dap_verification=0x400000,
delegated_management=0x200000, card_lock=0x100000,
card_terminate=0x080000, card_reset=0x040000,
cvm_management=0x020000, mandated_dap_verification=0x010000,
trusted_path=0x8000, authorized_management=0x4000,
token_management=0x2000, global_delete=0x1000,
global_lock=0x0800, global_registry=0x0400,
final_application=0x0200, global_service=0x0100,
receipt_generation=0x80, ciphered_load_file_data_block=0x40,
contactless_activation=0x20, contactless_self_activation=0x10)
examples = ['00', '80', '8040', '400010']
def test_examples(self):
for e in self.examples:
dec = self.Privileges.parse(h2b(e))
reenc = self.Privileges.build(dec)
self.assertEqual(e, b2h(reenc))
if __name__ == "__main__":
unittest.main()

View File

@@ -18,8 +18,8 @@
import unittest
import logging
import base64
from osmocom.utils import b2h, h2b
from pySim.utils import b2h, h2b
from pySim.esim.bsp import *
import pySim.esim.rsp as rsp
from pySim.esim import ActivationCode

View File

@@ -18,8 +18,8 @@
import unittest
import logging
import base64
from osmocom.utils import b2h, h2b
from pySim.utils import b2h, h2b
from pySim.esim.bsp import *
import pySim.esim.rsp as rsp

View File

@@ -18,8 +18,8 @@
import unittest
import logging
import copy
from osmocom.utils import h2b, b2h
from pySim.utils import h2b, b2h
from pySim.esim.saip import *
from pySim.esim.saip.personalization import *
from pprint import pprint as pp

View File

@@ -17,10 +17,9 @@
import unittest
import logging
from osmocom.utils import *
from pySim.utils import *
from pySim.filesystem import *
import pySim.iso7816_4
import pySim.ts_102_221
import pySim.ts_102_222

View File

@@ -17,10 +17,10 @@
import unittest
import logging
from osmocom.utils import b2h, h2b
from pySim.global_platform import *
from pySim.global_platform.scp import *
from pySim.utils import b2h, h2b
KIC = h2b('100102030405060708090a0b0c0d0e0f') # enc
KID = h2b('101102030405060708090a0b0c0d0e0f') # MAC

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python3
import unittest
from pySim.utils import h2b, b2h
from osmocom.utils import h2b, b2h
from pySim.sms import SMS_SUBMIT, SMS_DELIVER, AddressField
from pySim.ota import *

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python3
import unittest
from pySim.utils import h2b, b2h
from osmocom.utils import h2b, b2h
from pySim.sms import *
class Test_SMS_UDH(unittest.TestCase):

View File

@@ -1,145 +0,0 @@
#!/usr/bin/env python3
# (C) 2022 by Harald Welte <laforge@osmocom.org>
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from construct import Int8ub, GreedyBytes
from pySim.tlv import *
from pySim.utils import h2b
class TestUtils(unittest.TestCase):
def test_camel_to_snake(self):
cases = [
('CamelCase', 'camel_case'),
('CamelCaseUPPER', 'camel_case_upper'),
('Camel_CASE_underSCORE', 'camel_case_under_score'),
]
for c in cases:
self.assertEqual(camel_to_snake(c[0]), c[1])
def test_flatten_dict_lists(self):
inp = [
{ 'first': 1 },
{ 'second': 2 },
{ 'third': 3 },
]
out = { 'first': 1, 'second':2, 'third': 3}
self.assertEqual(flatten_dict_lists(inp), out)
def test_flatten_dict_lists_nodict(self):
inp = [
{ 'first': 1 },
{ 'second': 2 },
{ 'third': 3 },
4,
]
self.assertEqual(flatten_dict_lists(inp), inp)
def test_flatten_dict_lists_nested(self):
inp = {'top': [
{ 'first': 1 },
{ 'second': 2 },
{ 'third': 3 },
] }
out = {'top': { 'first': 1, 'second':2, 'third': 3 } }
self.assertEqual(flatten_dict_lists(inp), out)
class TestTranscodable(unittest.TestCase):
class XC_constr_class(Transcodable):
_construct = Int8ub
def __init__(self):
super().__init__();
def test_XC_constr_class(self):
"""Transcodable derived class with _construct class variable"""
xc = TestTranscodable.XC_constr_class()
self.assertEqual(xc.from_bytes(b'\x23'), 35)
self.assertEqual(xc.to_bytes(), b'\x23')
class XC_constr_instance(Transcodable):
def __init__(self):
super().__init__();
self._construct = Int8ub
def test_XC_constr_instance(self):
"""Transcodable derived class with _construct instance variable"""
xc = TestTranscodable.XC_constr_instance()
self.assertEqual(xc.from_bytes(b'\x23'), 35)
self.assertEqual(xc.to_bytes(), b'\x23')
class XC_method_instance(Transcodable):
def __init__(self):
super().__init__();
def _from_bytes(self, do):
return ('decoded', do)
def _to_bytes(self):
return self.decoded[1]
def test_XC_method_instance(self):
"""Transcodable derived class with _{from,to}_bytes() methods"""
xc = TestTranscodable.XC_method_instance()
self.assertEqual(xc.to_bytes(), b'')
self.assertEqual(xc.from_bytes(b''), None)
self.assertEqual(xc.from_bytes(b'\x23'), ('decoded', b'\x23'))
self.assertEqual(xc.to_bytes(), b'\x23')
class TestIE(unittest.TestCase):
class MyIE(IE, tag=0x23, desc='My IE description'):
_construct = Int8ub
def to_ie(self):
return self.to_bytes()
def test_IE_empty(self):
ie = TestIE.MyIE()
self.assertEqual(ie.to_dict(), {'my_ie': None})
self.assertEqual(repr(ie), 'MyIE(None)')
self.assertEqual(ie.is_constructed(), False)
def test_IE_from_bytes(self):
ie = TestIE.MyIE()
ie.from_bytes(b'\x42')
self.assertEqual(ie.to_dict(), {'my_ie': 66})
self.assertEqual(repr(ie), 'MyIE(66)')
self.assertEqual(ie.is_constructed(), False)
self.assertEqual(ie.to_bytes(), b'\x42')
self.assertEqual(ie.to_ie(), b'\x42')
class TestCompact(unittest.TestCase):
class IE_3(COMPACT_TLV_IE, tag=0x3):
_construct = GreedyBytes
class IE_7(COMPACT_TLV_IE, tag=0x7):
_construct = GreedyBytes
class IE_5(COMPACT_TLV_IE, tag=0x5):
_construct = GreedyBytes
# pylint: disable=undefined-variable
class IE_Coll(TLV_IE_Collection, nested=[IE_3, IE_7, IE_5]):
_construct = GreedyBytes
def test_ATR(self):
atr = h2b("31E073FE211F5745437531301365")
c = self.IE_Coll()
c.from_tlv(atr)
self.assertEqual(c.children[0].tag, 3)
self.assertEqual(c.children[0].to_bytes(), b'\xe0')
self.assertEqual(c.children[1].tag, 7)
self.assertEqual(c.children[1].to_bytes(), b'\xfe\x21\x1f')
self.assertEqual(c.children[2].tag, 5)
self.assertEqual(c.children[2].to_bytes(), b'\x45\x43\x75\x31\x30\x13\x65')
self.assertEqual(c.to_tlv(), atr)
if __name__ == "__main__":
unittest.main()

View File

@@ -18,8 +18,8 @@
import unittest
import logging
from pySim.utils import b2h, h2b, all_subclasses
from pySim.tlv import *
from osmocom.utils import b2h, h2b, all_subclasses
from osmocom.tlv import *
import pySim.iso7816_4
import pySim.ts_102_221

View File

@@ -172,70 +172,6 @@ class DecTestCase(unittest.TestCase):
msisdn_decoded = utils.dec_msisdn("ffffffffffffffffffffffffffffffffffffffff0bb121436587092143658709ffff")
self.assertEqual(msisdn_decoded, (1, 3, "12345678901234567890"))
class TestBerTlv(unittest.TestCase):
def test_BerTlvTagDec(self):
res = utils.bertlv_parse_tag(b'\x01')
self.assertEqual(res, ({'tag':1, 'constructed':False, 'class': 0}, b''))
res = utils.bertlv_parse_tag(b'\x21')
self.assertEqual(res, ({'tag':1, 'constructed':True, 'class': 0}, b''))
res = utils.bertlv_parse_tag(b'\x81\x23')
self.assertEqual(res, ({'tag':1, 'constructed':False, 'class': 2}, b'\x23'))
res = utils.bertlv_parse_tag(b'\x1f\x8f\x00\x23')
self.assertEqual(res, ({'tag':0xf<<7, 'constructed':False, 'class': 0}, b'\x23'))
def test_BerTlvLenDec(self):
self.assertEqual(utils.bertlv_encode_len(1), b'\x01')
self.assertEqual(utils.bertlv_encode_len(127), b'\x7f')
self.assertEqual(utils.bertlv_encode_len(128), b'\x81\x80')
self.assertEqual(utils.bertlv_encode_len(0x123456), b'\x83\x12\x34\x56')
def test_BerTlvLenEnc(self):
self.assertEqual(utils.bertlv_parse_len(b'\x01\x23'), (1, b'\x23'))
self.assertEqual(utils.bertlv_parse_len(b'\x7f'), (127, b''))
self.assertEqual(utils.bertlv_parse_len(b'\x81\x80'), (128, b''))
self.assertEqual(utils.bertlv_parse_len(b'\x83\x12\x34\x56\x78'), (0x123456, b'\x78'))
def test_BerTlvParseOne(self):
res = utils.bertlv_parse_one(b'\x81\x01\x01');
self.assertEqual(res, ({'tag':1, 'constructed':False, 'class':2}, 1, b'\x01', b''))
class TestComprTlv(unittest.TestCase):
def test_ComprTlvTagDec(self):
res = utils.comprehensiontlv_parse_tag(b'\x12\x23')
self.assertEqual(res, ({'tag': 0x12, 'comprehension': False}, b'\x23'))
res = utils.comprehensiontlv_parse_tag(b'\x92')
self.assertEqual(res, ({'tag': 0x12, 'comprehension': True}, b''))
res = utils.comprehensiontlv_parse_tag(b'\x7f\x12\x34')
self.assertEqual(res, ({'tag': 0x1234, 'comprehension': False}, b''))
res = utils.comprehensiontlv_parse_tag(b'\x7f\x82\x34\x56')
self.assertEqual(res, ({'tag': 0x234, 'comprehension': True}, b'\x56'))
def test_ComprTlvTagEnc(self):
res = utils.comprehensiontlv_encode_tag(0x12)
self.assertEqual(res, b'\x12')
res = utils.comprehensiontlv_encode_tag({'tag': 0x12})
self.assertEqual(res, b'\x12')
res = utils.comprehensiontlv_encode_tag({'tag': 0x12, 'comprehension':True})
self.assertEqual(res, b'\x92')
res = utils.comprehensiontlv_encode_tag(0x1234)
self.assertEqual(res, b'\x7f\x12\x34')
res = utils.comprehensiontlv_encode_tag({'tag': 0x1234, 'comprehension':True})
self.assertEqual(res, b'\x7f\x92\x34')
class TestDgiTlv(unittest.TestCase):
def test_DgiTlvLenEnc(self):
self.assertEqual(utils.dgi_encode_len(10), b'\x0a')
self.assertEqual(utils.dgi_encode_len(254), b'\xfe')
self.assertEqual(utils.dgi_encode_len(255), b'\xff\x00\xff')
self.assertEqual(utils.dgi_encode_len(65535), b'\xff\xff\xff')
with self.assertRaises(ValueError):
utils.dgi_encode_len(65536)
def testDgiTlvLenDec(self):
self.assertEqual(utils.dgi_parse_len(b'\x0a\x0b'), (10, b'\x0b'))
self.assertEqual(utils.dgi_parse_len(b'\xfe\x0b'), (254, b'\x0b'))
self.assertEqual(utils.dgi_parse_len(b'\xff\x00\xff\x0b'), (255, b'\x0b'))
class TestLuhn(unittest.TestCase):
def test_verify(self):
utils.verify_luhn('8988211000000530082')