aboutsummaryrefslogtreecommitdiffstats
path: root/pySim/utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'pySim/utils.py')
-rw-r--r--pySim/utils.py460
1 files changed, 6 insertions, 454 deletions
diff --git a/pySim/utils.py b/pySim/utils.py
index afa476b..05ad962 100644
--- a/pySim/utils.py
+++ b/pySim/utils.py
@@ -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,374 +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 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:
#
@@ -411,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(
@@ -783,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
@@ -937,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
@@ -1011,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
@@ -1430,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