blob: 1541e2e6d64d450ea16340f4dc285480c180db5c [file] [log] [blame]
Sylvain Munaut76504e02010-12-07 00:24:32 +01001# -*- coding: utf-8 -*-
2
3""" pySim: various utilities
4"""
5
Harald Welte5e749a72021-04-10 17:18:17 +02006import json
Harald Welte3de6ca22021-05-02 20:05:56 +02007import abc
Philipp Maier796ca3d2021-11-01 17:13:57 +01008import string
Harald Welte5e749a72021-04-10 17:18:17 +02009from io import BytesIO
Harald Weltef5e26ae2023-07-09 16:11:46 +020010from typing import Optional, List, Dict, Any, Tuple, NewType
Harald Welte52255572021-04-03 09:56:32 +020011
Sylvain Munaut76504e02010-12-07 00:24:32 +010012# Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com>
Harald Welte5e749a72021-04-10 17:18:17 +020013# Copyright (C) 2021 Harald Welte <laforge@osmocom.org>
Sylvain Munaut76504e02010-12-07 00:24:32 +010014#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 2 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
27#
28
Harald Welte52255572021-04-03 09:56:32 +020029# just to differentiate strings of hex nibbles from everything else
Harald Weltef5e26ae2023-07-09 16:11:46 +020030Hexstr = NewType('Hexstr', str)
Harald Welteab6897c2023-07-09 16:21:23 +020031SwHexstr = NewType('SwHexstr', str)
32SwMatchstr = NewType('SwMatchstr', str)
Harald Weltefdb187d2023-07-09 17:03:17 +020033ResTuple = Tuple[Hexstr, SwHexstr]
Sylvain Munaut76504e02010-12-07 00:24:32 +010034
Harald Weltec91085e2022-02-10 18:05:45 +010035def h2b(s: Hexstr) -> bytearray:
36 """convert from a string of hex nibbles to a sequence of bytes"""
37 return bytearray.fromhex(s)
Sylvain Munaut76504e02010-12-07 00:24:32 +010038
Sylvain Munaut76504e02010-12-07 00:24:32 +010039
Harald Weltec91085e2022-02-10 18:05:45 +010040def b2h(b: bytearray) -> Hexstr:
41 """convert from a sequence of bytes to a string of hex nibbles"""
42 return ''.join(['%02x' % (x) for x in b])
Sylvain Munaut76504e02010-12-07 00:24:32 +010043
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +030044
Harald Weltec91085e2022-02-10 18:05:45 +010045def h2i(s: Hexstr) -> List[int]:
46 """convert from a string of hex nibbles to a list of integers"""
47 return [(int(x, 16) << 4)+int(y, 16) for x, y in zip(s[0::2], s[1::2])]
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +030048
Supreeth Herle7d77d2d2020-05-11 09:07:08 +020049
Harald Weltec91085e2022-02-10 18:05:45 +010050def i2h(s: List[int]) -> Hexstr:
51 """convert from a list of integers to a string of hex nibbles"""
52 return ''.join(['%02x' % (x) for x in s])
Sylvain Munaut76504e02010-12-07 00:24:32 +010053
Sylvain Munaut76504e02010-12-07 00:24:32 +010054
Harald Weltec91085e2022-02-10 18:05:45 +010055def h2s(s: Hexstr) -> str:
56 """convert from a string of hex nibbles to an ASCII string"""
57 return ''.join([chr((int(x, 16) << 4)+int(y, 16)) for x, y in zip(s[0::2], s[1::2])
58 if int(x + y, 16) != 0xff])
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +040059
Ben Fox-Moore0ec14752018-09-24 15:47:02 +020060
Harald Weltec91085e2022-02-10 18:05:45 +010061def s2h(s: str) -> Hexstr:
62 """convert from an ASCII string to a string of hex nibbles"""
63 b = bytearray()
64 b.extend(map(ord, s))
65 return b2h(b)
Philipp Maier796ca3d2021-11-01 17:13:57 +010066
Philipp Maier796ca3d2021-11-01 17:13:57 +010067
Harald Weltec91085e2022-02-10 18:05:45 +010068def i2s(s: List[int]) -> str:
69 """convert from a list of integers to an ASCII string"""
70 return ''.join([chr(x) for x in s])
71
72
73def swap_nibbles(s: Hexstr) -> Hexstr:
74 """swap the nibbles in a hex string"""
75 return ''.join([x+y for x, y in zip(s[1::2], s[0::2])])
76
77
78def rpad(s: str, l: int, c='f') -> str:
79 """pad string on the right side.
80 Args:
81 s : string to pad
82 l : total length to pad to
83 c : padding character
84 Returns:
85 String 's' padded with as many 'c' as needed to reach total length of 'l'
86 """
87 return s + c * (l - len(s))
88
89
90def lpad(s: str, l: int, c='f') -> str:
91 """pad string on the left side.
92 Args:
93 s : string to pad
94 l : total length to pad to
95 c : padding character
96 Returns:
97 String 's' padded with as many 'c' as needed to reach total length of 'l'
98 """
99 return c * (l - len(s)) + s
100
101
102def half_round_up(n: int) -> int:
103 return (n + 1)//2
104
105
106def str_sanitize(s: str) -> str:
107 """replace all non printable chars, line breaks and whitespaces, with ' ', make sure that
108 there are no whitespaces at the end and at the beginning of the string.
109
110 Args:
111 s : string to sanitize
112 Returns:
113 filtered result of string 's'
114 """
115
116 chars_to_keep = string.digits + string.ascii_letters + string.punctuation
117 res = ''.join([c if c in chars_to_keep else ' ' for c in s])
118 return res.strip()
Philipp Maier796ca3d2021-11-01 17:13:57 +0100119
Harald Welte917d98c2021-04-21 11:51:25 +0200120#########################################################################
Harald Welte9f3b44d2021-05-04 18:18:09 +0200121# poor man's COMPREHENSION-TLV decoder.
122#########################################################################
123
Harald Weltec91085e2022-02-10 18:05:45 +0100124
125def comprehensiontlv_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]:
Harald Welte6912b1b2021-05-24 23:16:44 +0200126 """Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
127 if binary[0] in [0x00, 0x80, 0xff]:
Harald Weltec91085e2022-02-10 18:05:45 +0100128 raise ValueError("Found illegal value 0x%02x in %s" %
129 (binary[0], binary))
Harald Welte6912b1b2021-05-24 23:16:44 +0200130 if binary[0] == 0x7f:
131 # three-byte tag
132 tag = binary[0] << 16 | binary[1] << 8 | binary[2]
133 return (tag, binary[3:])
134 elif binary[0] == 0xff:
135 return None, binary
136 else:
137 # single byte tag
138 tag = binary[0]
139 return (tag, binary[1:])
140
Harald Weltec91085e2022-02-10 18:05:45 +0100141
142def comprehensiontlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]:
Harald Welte9f3b44d2021-05-04 18:18:09 +0200143 """Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
144 if binary[0] in [0x00, 0x80, 0xff]:
Harald Weltec91085e2022-02-10 18:05:45 +0100145 raise ValueError("Found illegal value 0x%02x in %s" %
146 (binary[0], binary))
Harald Welte9f3b44d2021-05-04 18:18:09 +0200147 if binary[0] == 0x7f:
148 # three-byte tag
149 tag = (binary[1] & 0x7f) << 8
150 tag |= binary[2]
151 compr = True if binary[1] & 0x80 else False
152 return ({'comprehension': compr, 'tag': tag}, binary[3:])
153 else:
154 # single byte tag
155 tag = binary[0] & 0x7f
156 compr = True if binary[0] & 0x80 else False
157 return ({'comprehension': compr, 'tag': tag}, binary[1:])
158
Harald Weltec91085e2022-02-10 18:05:45 +0100159
Harald Welte9f3b44d2021-05-04 18:18:09 +0200160def comprehensiontlv_encode_tag(tag) -> bytes:
161 """Encode a single Tag according to ETSI TS 101 220 Section 7.1.1"""
162 # permit caller to specify tag also as integer value
163 if isinstance(tag, int):
164 compr = True if tag < 0xff and tag & 0x80 else False
165 tag = {'tag': tag, 'comprehension': compr}
166 compr = tag.get('comprehension', False)
167 if tag['tag'] in [0x00, 0x80, 0xff] or tag['tag'] > 0xff:
168 # 3-byte format
Vadim Yanitskiydbd5ed62021-11-05 16:20:52 +0300169 byte3 = tag['tag'] & 0xff
Harald Welte9f3b44d2021-05-04 18:18:09 +0200170 byte2 = (tag['tag'] >> 8) & 0x7f
171 if compr:
172 byte2 |= 0x80
173 return b'\x7f' + byte2.to_bytes(1, 'big') + byte3.to_bytes(1, 'big')
174 else:
175 # 1-byte format
176 ret = tag['tag']
177 if compr:
178 ret |= 0x80
179 return ret.to_bytes(1, 'big')
180
181# length value coding is equal to BER-TLV
182
Harald Welte6912b1b2021-05-24 23:16:44 +0200183
Harald Weltec91085e2022-02-10 18:05:45 +0100184def comprehensiontlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]:
185 """Parse a single TLV IE at the start of the given binary data.
186 Args:
187 binary : binary input data of BER-TLV length field
188 Returns:
189 Tuple of (tag:dict, len:int, remainder:bytes)
190 """
191 (tagdict, remainder) = comprehensiontlv_parse_tag(binary)
192 (length, remainder) = bertlv_parse_len(remainder)
193 value = remainder[:length]
194 remainder = remainder[length:]
195 return (tagdict, length, value, remainder)
Harald Welte6912b1b2021-05-24 23:16:44 +0200196
Harald Welte9f3b44d2021-05-04 18:18:09 +0200197
198#########################################################################
Harald Welte917d98c2021-04-21 11:51:25 +0200199# poor man's BER-TLV decoder. To be a more sophisticated OO library later
200#########################################################################
201
Harald Weltec91085e2022-02-10 18:05:45 +0100202def bertlv_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]:
203 """Get a single raw Tag from start of input according to ITU-T X.690 8.1.2
204 Args:
205 binary : binary input data of BER-TLV length field
206 Returns:
207 Tuple of (tag:int, remainder:bytes)
208 """
209 # check for FF padding at the end, as customary in SIM card files
210 if binary[0] == 0xff and len(binary) == 1 or binary[0] == 0xff and binary[1] == 0xff:
211 return None, binary
212 tag = binary[0] & 0x1f
213 if tag <= 30:
214 return binary[0], binary[1:]
215 else: # multi-byte tag
216 tag = binary[0]
217 i = 1
218 last = False
219 while not last:
220 last = False if binary[i] & 0x80 else True
221 tag <<= 8
222 tag |= binary[i]
223 i += 1
224 return tag, binary[i:]
Harald Welte6912b1b2021-05-24 23:16:44 +0200225
Harald Weltec91085e2022-02-10 18:05:45 +0100226
227def bertlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]:
228 """Parse a single Tag value according to ITU-T X.690 8.1.2
229 Args:
230 binary : binary input data of BER-TLV length field
231 Returns:
232 Tuple of ({class:int, constructed:bool, tag:int}, remainder:bytes)
233 """
234 cls = binary[0] >> 6
235 constructed = True if binary[0] & 0x20 else False
236 tag = binary[0] & 0x1f
237 if tag <= 30:
238 return ({'class': cls, 'constructed': constructed, 'tag': tag}, binary[1:])
239 else: # multi-byte tag
240 tag = 0
241 i = 1
242 last = False
243 while not last:
244 last = False if binary[i] & 0x80 else True
245 tag <<= 7
246 tag |= binary[i] & 0x7f
247 i += 1
248 return ({'class': cls, 'constructed': constructed, 'tag': tag}, binary[i:])
249
Harald Welte917d98c2021-04-21 11:51:25 +0200250
Harald Weltef0885b12021-05-24 23:18:59 +0200251def bertlv_encode_tag(t) -> bytes:
252 """Encode a single Tag value according to ITU-T X.690 8.1.2
253 """
Harald Weltec91085e2022-02-10 18:05:45 +0100254 def get_top7_bits(inp: int) -> Tuple[int, int]:
Harald Weltef0885b12021-05-24 23:18:59 +0200255 """Get top 7 bits of integer. Returns those 7 bits as integer and the remaining LSBs."""
256 remain_bits = inp.bit_length()
257 if remain_bits >= 7:
258 bitcnt = 7
259 else:
260 bitcnt = remain_bits
261 outp = inp >> (remain_bits - bitcnt)
262 remainder = inp & ~ (inp << (remain_bits - bitcnt))
263 return outp, remainder
264
265 if isinstance(t, int):
266 # FIXME: multiple byte tags
267 tag = t & 0x1f
268 constructed = True if t & 0x20 else False
269 cls = t >> 6
270 else:
271 tag = t['tag']
272 constructed = t['constructed']
273 cls = t['class']
274 if tag <= 30:
275 t = tag & 0x1f
276 if constructed:
277 t |= 0x20
278 t |= (cls & 3) << 6
279 return bytes([t])
Harald Weltec91085e2022-02-10 18:05:45 +0100280 else: # multi-byte tag
Vadim Yanitskiydbd5ed62021-11-05 16:20:52 +0300281 t = 0x1f
Harald Weltef0885b12021-05-24 23:18:59 +0200282 if constructed:
283 t |= 0x20
284 t |= (cls & 3) << 6
285 tag_bytes = bytes([t])
286 remain = tag
287 while True:
288 t, remain = get_top7_bits(remain)
289 if remain:
290 t |= 0x80
291 tag_bytes += bytes([t])
292 if not remain:
293 break
294 return tag_bytes
295
Harald Welte917d98c2021-04-21 11:51:25 +0200296
Harald Weltec91085e2022-02-10 18:05:45 +0100297def bertlv_parse_len(binary: bytes) -> Tuple[int, bytes]:
298 """Parse a single Length value according to ITU-T X.690 8.1.3;
299 only the definite form is supported here.
300 Args:
301 binary : binary input data of BER-TLV length field
302 Returns:
303 Tuple of (length, remainder)
304 """
305 if binary[0] < 0x80:
306 return (binary[0], binary[1:])
307 else:
308 num_len_oct = binary[0] & 0x7f
309 length = 0
310 for i in range(1, 1+num_len_oct):
311 length <<= 8
312 length |= binary[i]
313 return (length, binary[1+num_len_oct:])
Harald Welte917d98c2021-04-21 11:51:25 +0200314
Harald Welte917d98c2021-04-21 11:51:25 +0200315
Harald Weltec91085e2022-02-10 18:05:45 +0100316def bertlv_encode_len(length: int) -> bytes:
317 """Encode a single Length value according to ITU-T X.690 8.1.3;
318 only the definite form is supported here.
319 Args:
320 length : length value to be encoded
321 Returns:
322 binary output data of BER-TLV length field
323 """
324 if length < 0x80:
325 return length.to_bytes(1, 'big')
326 elif length <= 0xff:
327 return b'\x81' + length.to_bytes(1, 'big')
328 elif length <= 0xffff:
329 return b'\x82' + length.to_bytes(2, 'big')
330 elif length <= 0xffffff:
331 return b'\x83' + length.to_bytes(3, 'big')
332 elif length <= 0xffffffff:
333 return b'\x84' + length.to_bytes(4, 'big')
334 else:
335 raise ValueError("Length > 32bits not supported")
336
337
338def bertlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]:
339 """Parse a single TLV IE at the start of the given binary data.
340 Args:
341 binary : binary input data of BER-TLV length field
342 Returns:
343 Tuple of (tag:dict, len:int, remainder:bytes)
344 """
345 (tagdict, remainder) = bertlv_parse_tag(binary)
346 (length, remainder) = bertlv_parse_len(remainder)
347 value = remainder[:length]
348 remainder = remainder[length:]
349 return (tagdict, length, value, remainder)
Harald Welte917d98c2021-04-21 11:51:25 +0200350
351
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200352# IMSI encoded format:
353# For IMSI 0123456789ABCDE:
354#
355# | byte 1 | 2 upper | 2 lower | 3 upper | 3 lower | ... | 9 upper | 9 lower |
356# | length in bytes | 0 | odd/even | 2 | 1 | ... | E | D |
357#
358# If the IMSI is less than 15 characters, it should be padded with 'f' from the end.
359#
360# The length is the total number of bytes used to encoded the IMSI. This includes the odd/even
361# parity bit. E.g. an IMSI of length 14 is 8 bytes long, not 7, as it uses bytes 2 to 9 to
362# encode itself.
363#
364# Because of this, an odd length IMSI fits exactly into len(imsi) + 1 // 2 bytes, whereas an
365# even length IMSI only uses half of the last byte.
366
Harald Weltec91085e2022-02-10 18:05:45 +0100367def enc_imsi(imsi: str):
368 """Converts a string IMSI into the encoded value of the EF"""
369 l = half_round_up(
370 len(imsi) + 1) # Required bytes - include space for odd/even indicator
371 oe = len(imsi) & 1 # Odd (1) / Even (0)
372 ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe << 3) | 1, rpad(imsi, 15)))
373 return ei
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400374
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400375
Harald Weltec91085e2022-02-10 18:05:45 +0100376def dec_imsi(ef: Hexstr) -> Optional[str]:
377 """Converts an EF value to the IMSI string representation"""
378 if len(ef) < 4:
379 return None
380 l = int(ef[0:2], 16) * 2 # Length of the IMSI string
381 l = l - 1 # Encoded length byte includes oe nibble
382 swapped = swap_nibbles(ef[2:]).rstrip('f')
383 if len(swapped) < 1:
384 return None
385 oe = (int(swapped[0]) >> 3) & 1 # Odd (1) / Even (0)
386 if not oe:
387 # if even, only half of last byte was used
388 l = l-1
389 if l != len(swapped) - 1:
390 return None
391 imsi = swapped[1:]
392 return imsi
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400393
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400394
Harald Weltec91085e2022-02-10 18:05:45 +0100395def dec_iccid(ef: Hexstr) -> str:
396 return swap_nibbles(ef).strip('f')
Philipp Maier6c5cd802021-04-23 21:19:36 +0200397
Philipp Maier6c5cd802021-04-23 21:19:36 +0200398
Harald Weltec91085e2022-02-10 18:05:45 +0100399def enc_iccid(iccid: str) -> Hexstr:
400 return swap_nibbles(rpad(iccid, 20))
Philipp Maier6c5cd802021-04-23 21:19:36 +0200401
Philipp Maier6c5cd802021-04-23 21:19:36 +0200402
Harald Weltec91085e2022-02-10 18:05:45 +0100403def enc_plmn(mcc: Hexstr, mnc: Hexstr) -> Hexstr:
404 """Converts integer MCC/MNC into 3 bytes for EF"""
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300405
Harald Weltec91085e2022-02-10 18:05:45 +0100406 # Make sure there are no excess whitespaces in the input
407 # parameters
408 mcc = mcc.strip()
409 mnc = mnc.strip()
410
411 # Make sure that MCC/MNC are correctly padded with leading
412 # zeros or 'F', depending on the length.
413 if len(mnc) == 0:
414 mnc = "FFF"
415 elif len(mnc) == 1:
farhadhfec721f2023-07-19 15:43:13 +0200416 mnc = "0" + mnc + "F"
Harald Weltec91085e2022-02-10 18:05:45 +0100417 elif len(mnc) == 2:
418 mnc += "F"
419
420 if len(mcc) == 0:
421 mcc = "FFF"
422 elif len(mcc) == 1:
423 mcc = "00" + mcc
424 elif len(mcc) == 2:
425 mcc = "0" + mcc
426
427 return (mcc[1] + mcc[0]) + (mnc[2] + mcc[2]) + (mnc[1] + mnc[0])
428
429
430def dec_plmn(threehexbytes: Hexstr) -> dict:
431 res = {'mcc': "0", 'mnc': "0"}
432 dec_mcc_from_plmn_str(threehexbytes)
433 res['mcc'] = dec_mcc_from_plmn_str(threehexbytes)
434 res['mnc'] = dec_mnc_from_plmn_str(threehexbytes)
435 return res
436
Harald Welted7a7e172021-04-07 00:30:10 +0200437
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100438# Accepts hex string representing three bytes
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100439
Philipp Maier6c5cd802021-04-23 21:19:36 +0200440
Harald Weltec91085e2022-02-10 18:05:45 +0100441def dec_mcc_from_plmn(plmn: Hexstr) -> int:
442 ia = h2i(plmn)
443 digit1 = ia[0] & 0x0F # 1st byte, LSB
444 digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB
445 digit3 = ia[1] & 0x0F # 2nd byte, LSB
446 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
447 return 0xFFF # 4095
448 return derive_mcc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100449
Philipp Maier6c5cd802021-04-23 21:19:36 +0200450
Harald Weltec91085e2022-02-10 18:05:45 +0100451def dec_mcc_from_plmn_str(plmn: Hexstr) -> str:
452 digit1 = plmn[1] # 1st byte, LSB
453 digit2 = plmn[0] # 1st byte, MSB
454 digit3 = plmn[3] # 2nd byte, LSB
455 res = digit1 + digit2 + digit3
456 return res.upper().strip("F")
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100457
Harald Weltec91085e2022-02-10 18:05:45 +0100458
459def dec_mnc_from_plmn(plmn: Hexstr) -> int:
460 ia = h2i(plmn)
461 digit1 = ia[2] & 0x0F # 3rd byte, LSB
462 digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB
463 digit3 = (ia[1] & 0xF0) >> 4 # 2nd byte, MSB
464 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
465 return 0xFFF # 4095
466 return derive_mnc(digit1, digit2, digit3)
467
468
469def dec_mnc_from_plmn_str(plmn: Hexstr) -> str:
470 digit1 = plmn[5] # 3rd byte, LSB
471 digit2 = plmn[4] # 3rd byte, MSB
472 digit3 = plmn[2] # 2nd byte, MSB
473 res = digit1 + digit2 + digit3
474 return res.upper().strip("F")
475
476
477def dec_act(twohexbytes: Hexstr) -> List[str]:
478 act_list = [
479 {'bit': 15, 'name': "UTRAN"},
480 {'bit': 14, 'name': "E-UTRAN"},
481 {'bit': 11, 'name': "NG-RAN"},
482 {'bit': 7, 'name': "GSM"},
483 {'bit': 6, 'name': "GSM COMPACT"},
484 {'bit': 5, 'name': "cdma2000 HRPD"},
485 {'bit': 4, 'name': "cdma2000 1xRTT"},
486 ]
487 ia = h2i(twohexbytes)
488 u16t = (ia[0] << 8) | ia[1]
489 sel = []
490 for a in act_list:
491 if u16t & (1 << a['bit']):
492 if a['name'] == "E-UTRAN":
493 # The Access technology identifier of E-UTRAN
494 # allows a more detailed specification:
495 if u16t & (1 << 13) and u16t & (1 << 12):
496 sel.append("E-UTRAN WB-S1")
497 sel.append("E-UTRAN NB-S1")
498 elif u16t & (1 << 13):
499 sel.append("E-UTRAN WB-S1")
500 elif u16t & (1 << 12):
501 sel.append("E-UTRAN NB-S1")
502 else:
503 sel.append("E-UTRAN")
504 else:
505 sel.append(a['name'])
506 return sel
507
508
509def dec_xplmn_w_act(fivehexbytes: Hexstr) -> Dict[str, Any]:
510 res = {'mcc': "0", 'mnc': "0", 'act': []}
511 plmn_chars = 6
512 act_chars = 4
513 # first three bytes (six ascii hex chars)
514 plmn_str = fivehexbytes[:plmn_chars]
515 # two bytes after first three bytes
516 act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars]
517 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
518 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
519 res['act'] = dec_act(act_str)
520 return res
521
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100522
Harald Weltec91085e2022-02-10 18:05:45 +0100523def dec_xplmn(threehexbytes: Hexstr) -> dict:
524 res = {'mcc': 0, 'mnc': 0, 'act': []}
525 plmn_chars = 6
526 # first three bytes (six ascii hex chars)
527 plmn_str = threehexbytes[:plmn_chars]
Matan Perelman60951b02023-06-01 17:39:04 +0300528 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
529 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
Harald Weltec91085e2022-02-10 18:05:45 +0100530 return res
Harald Welteca673942020-06-03 15:19:40 +0200531
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900532
Harald Weltec91085e2022-02-10 18:05:45 +0100533def derive_milenage_opc(ki_hex: Hexstr, op_hex: Hexstr) -> Hexstr:
534 """
535 Run the milenage algorithm to calculate OPC from Ki and OP
536 """
Harald Welted75fa3f2023-05-31 20:47:55 +0200537 from Cryptodome.Cipher import AES
Harald Weltec91085e2022-02-10 18:05:45 +0100538 # pylint: disable=no-name-in-module
Harald Welted75fa3f2023-05-31 20:47:55 +0200539 from Cryptodome.Util.strxor import strxor
Harald Weltec91085e2022-02-10 18:05:45 +0100540 from pySim.utils import b2h
541
542 # We pass in hex string and now need to work on bytes
543 ki_bytes = bytes(h2b(ki_hex))
544 op_bytes = bytes(h2b(op_hex))
545 aes = AES.new(ki_bytes, AES.MODE_ECB)
546 opc_bytes = aes.encrypt(op_bytes)
547 return b2h(strxor(opc_bytes, op_bytes))
548
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900549
Harald Welte52255572021-04-03 09:56:32 +0200550def calculate_luhn(cc) -> int:
Harald Weltec91085e2022-02-10 18:05:45 +0100551 """
552 Calculate Luhn checksum used in e.g. ICCID and IMEI
553 """
554 num = list(map(int, str(cc)))
555 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10))
556 for d in num[::-2]]) % 10
557 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200558
Philipp Maier7592eee2019-09-12 13:03:23 +0200559
Harald Weltec91085e2022-02-10 18:05:45 +0100560def mcc_from_imsi(imsi: str) -> Optional[str]:
561 """
562 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
563 """
564 if imsi == None:
565 return None
Philipp Maier7592eee2019-09-12 13:03:23 +0200566
Harald Weltec91085e2022-02-10 18:05:45 +0100567 if len(imsi) > 3:
568 return imsi[:3]
569 else:
570 return None
Philipp Maier7592eee2019-09-12 13:03:23 +0200571
Supreeth Herled24f1632019-11-30 10:37:09 +0100572
Harald Weltec91085e2022-02-10 18:05:45 +0100573def mnc_from_imsi(imsi: str, long: bool = False) -> Optional[str]:
574 """
575 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
576 """
577 if imsi == None:
578 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100579
Harald Weltec91085e2022-02-10 18:05:45 +0100580 if len(imsi) > 3:
581 if long:
582 return imsi[3:6]
583 else:
584 return imsi[3:5]
585 else:
586 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100587
Supreeth Herled24f1632019-11-30 10:37:09 +0100588
Harald Weltec91085e2022-02-10 18:05:45 +0100589def derive_mcc(digit1: int, digit2: int, digit3: int) -> int:
590 """
591 Derive decimal representation of the MCC (Mobile Country Code)
592 from three given digits.
593 """
Supreeth Herled24f1632019-11-30 10:37:09 +0100594
Harald Weltec91085e2022-02-10 18:05:45 +0100595 mcc = 0
Supreeth Herled24f1632019-11-30 10:37:09 +0100596
Harald Weltec91085e2022-02-10 18:05:45 +0100597 if digit1 != 0x0f:
598 mcc += digit1 * 100
599 if digit2 != 0x0f:
600 mcc += digit2 * 10
601 if digit3 != 0x0f:
602 mcc += digit3
Supreeth Herled24f1632019-11-30 10:37:09 +0100603
Harald Weltec91085e2022-02-10 18:05:45 +0100604 return mcc
Supreeth Herled24f1632019-11-30 10:37:09 +0100605
Supreeth Herled24f1632019-11-30 10:37:09 +0100606
Harald Weltec91085e2022-02-10 18:05:45 +0100607def derive_mnc(digit1: int, digit2: int, digit3: int = 0x0f) -> int:
608 """
609 Derive decimal representation of the MNC (Mobile Network Code)
610 from two or (optionally) three given digits.
611 """
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100612
Harald Weltec91085e2022-02-10 18:05:45 +0100613 mnc = 0
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100614
Harald Weltec91085e2022-02-10 18:05:45 +0100615 # 3-rd digit is optional for the MNC. If present
616 # the algorythm is the same as for the MCC.
617 if digit3 != 0x0f:
618 return derive_mcc(digit1, digit2, digit3)
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100619
Harald Weltec91085e2022-02-10 18:05:45 +0100620 if digit1 != 0x0f:
621 mnc += digit1 * 10
622 if digit2 != 0x0f:
623 mnc += digit2
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100624
Harald Weltec91085e2022-02-10 18:05:45 +0100625 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100626
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100627
Harald Weltec91085e2022-02-10 18:05:45 +0100628def dec_msisdn(ef_msisdn: Hexstr) -> Optional[Tuple[int, int, Optional[str]]]:
629 """
630 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
631 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
632 """
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100633
Harald Weltec91085e2022-02-10 18:05:45 +0100634 # Convert from str to (kind of) 'bytes'
635 ef_msisdn = h2b(ef_msisdn)
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100636
Harald Weltec91085e2022-02-10 18:05:45 +0100637 # Make sure mandatory fields are present
638 if len(ef_msisdn) < 14:
639 raise ValueError("EF.MSISDN is too short")
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100640
Harald Weltec91085e2022-02-10 18:05:45 +0100641 # Skip optional Alpha Identifier
642 xlen = len(ef_msisdn) - 14
643 msisdn_lhv = ef_msisdn[xlen:]
Supreeth Herle5a541012019-12-22 08:59:16 +0100644
Harald Weltec91085e2022-02-10 18:05:45 +0100645 # Parse the length (in bytes) of the BCD encoded number
646 bcd_len = msisdn_lhv[0]
647 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
648 if bcd_len == 0xff:
649 return None
650 elif bcd_len > 11 or bcd_len < 1:
651 raise ValueError(
652 "Length of MSISDN (%d bytes) is out of range" % bcd_len)
Supreeth Herle5a541012019-12-22 08:59:16 +0100653
Harald Weltec91085e2022-02-10 18:05:45 +0100654 # Parse ToN / NPI
655 ton = (msisdn_lhv[1] >> 4) & 0x07
656 npi = msisdn_lhv[1] & 0x0f
657 bcd_len -= 1
Supreeth Herle5a541012019-12-22 08:59:16 +0100658
Harald Weltec91085e2022-02-10 18:05:45 +0100659 # No MSISDN?
660 if not bcd_len:
661 return (npi, ton, None)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200662
Harald Weltec91085e2022-02-10 18:05:45 +0100663 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
664 # International number 10.5.118/3GPP TS 24.008
665 if ton == 0x01:
666 msisdn = '+' + msisdn
Supreeth Herle5a541012019-12-22 08:59:16 +0100667
Harald Weltec91085e2022-02-10 18:05:45 +0100668 return (npi, ton, msisdn)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200669
Supreeth Herle5a541012019-12-22 08:59:16 +0100670
Harald Weltec91085e2022-02-10 18:05:45 +0100671def enc_msisdn(msisdn: str, npi: int = 0x01, ton: int = 0x03) -> Hexstr:
672 """
673 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
674 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
675 will not contain the optional Alpha Identifier at the beginning.)
Supreeth Herle5a541012019-12-22 08:59:16 +0100676
Harald Weltec91085e2022-02-10 18:05:45 +0100677 Default NPI / ToN values:
678 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
679 - ToN: network specific or international number (if starts with '+').
680 """
681
682 # If no MSISDN is supplied then encode the file contents as all "ff"
683 if msisdn == "" or msisdn == "+":
684 return "ff" * 14
685
686 # Leading '+' indicates International Number
687 if msisdn[0] == '+':
688 msisdn = msisdn[1:]
689 ton = 0x01
690
691 # An MSISDN must not exceed 20 digits
692 if len(msisdn) > 20:
693 raise ValueError("msisdn must not be longer than 20 digits")
694
695 # Append 'f' padding if number of digits is odd
696 if len(msisdn) % 2 > 0:
697 msisdn += 'f'
698
699 # BCD length also includes NPI/ToN header
700 bcd_len = len(msisdn) // 2 + 1
701 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
702 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
703
704 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200705
Supreeth Herle441c4a72020-03-24 10:19:15 +0100706
Supreeth Herle98370552020-05-11 09:04:41 +0200707def first_TLV_parser(bytelist):
Harald Weltec91085e2022-02-10 18:05:45 +0100708 '''
709 first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
Supreeth Herle98370552020-05-11 09:04:41 +0200710
Harald Weltec91085e2022-02-10 18:05:45 +0100711 parses first TLV format record in a list of bytelist
712 returns a 3-Tuple: Tag, Length, Value
713 Value is a list of bytes
714 parsing of length is ETSI'style 101.220
715 '''
716 Tag = bytelist[0]
717 if bytelist[1] == 0xFF:
718 Len = bytelist[2]*256 + bytelist[3]
719 Val = bytelist[4:4+Len]
720 else:
721 Len = bytelist[1]
722 Val = bytelist[2:2+Len]
723 return (Tag, Len, Val)
724
Supreeth Herle98370552020-05-11 09:04:41 +0200725
726def TLV_parser(bytelist):
Harald Weltec91085e2022-02-10 18:05:45 +0100727 '''
728 TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
Supreeth Herle98370552020-05-11 09:04:41 +0200729
Harald Weltec91085e2022-02-10 18:05:45 +0100730 loops on the input list of bytes with the "first_TLV_parser()" function
731 returns a list of 3-Tuples
732 '''
733 ret = []
734 while len(bytelist) > 0:
735 T, L, V = first_TLV_parser(bytelist)
736 if T == 0xFF:
737 # padding bytes
738 break
739 ret.append((T, L, V))
740 # need to manage length of L
741 if L > 0xFE:
742 bytelist = bytelist[L+4:]
743 else:
744 bytelist = bytelist[L+2:]
745 return ret
746
Supreeth Herled572ede2020-03-22 09:55:04 +0100747
Supreeth Herle3b342c22020-03-24 16:15:02 +0100748def dec_addr_tlv(hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100749 """
750 Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm.
751 See 3GPP TS 31.102 version 13.4.0 Release 13, section 4.2.8, 4.2.102 and 4.2.104.
752 """
Supreeth Herled572ede2020-03-22 09:55:04 +0100753
Harald Weltec91085e2022-02-10 18:05:45 +0100754 # Convert from hex str to int bytes list
755 addr_tlv_bytes = h2i(hexstr)
Supreeth Herled572ede2020-03-22 09:55:04 +0100756
Harald Weltec91085e2022-02-10 18:05:45 +0100757 # Get list of tuples containing parsed TLVs
758 tlvs = TLV_parser(addr_tlv_bytes)
Supreeth Herled572ede2020-03-22 09:55:04 +0100759
Harald Weltec91085e2022-02-10 18:05:45 +0100760 for tlv in tlvs:
761 # tlv = (T, L, [V])
762 # T = Tag
763 # L = Length
764 # [V] = List of value
Supreeth Herled572ede2020-03-22 09:55:04 +0100765
Harald Weltec91085e2022-02-10 18:05:45 +0100766 # Invalid Tag value scenario
767 if tlv[0] != 0x80:
768 continue
Supreeth Herled572ede2020-03-22 09:55:04 +0100769
Harald Weltec91085e2022-02-10 18:05:45 +0100770 # Empty field - Zero length
771 if tlv[1] == 0:
772 continue
Supreeth Herled6a5ec52020-06-01 12:27:51 +0200773
Philipp Maier1f46f072023-07-27 10:38:38 +0200774 # Uninitialized field
775 if all([v == 0xff for v in tlv[2]]):
776 continue
777
Harald Weltec91085e2022-02-10 18:05:45 +0100778 # First byte in the value has the address type
779 addr_type = tlv[2][0]
780 # TODO: Support parsing of IPv6
781 # Address Type: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), other (Reserved)
782 if addr_type == 0x00: # FQDN
783 # Skip address tye byte i.e. first byte in value list
784 content = tlv[2][1:]
785 return (i2s(content), '00')
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200786
Harald Weltec91085e2022-02-10 18:05:45 +0100787 elif addr_type == 0x01: # IPv4
788 # Skip address tye byte i.e. first byte in value list
789 # Skip the unused byte in Octect 4 after address type byte as per 3GPP TS 31.102
790 ipv4 = tlv[2][2:]
791 content = '.'.join(str(x) for x in ipv4)
792 return (content, '01')
793 else:
794 raise ValueError("Invalid address type")
Supreeth Herled572ede2020-03-22 09:55:04 +0100795
Harald Weltec91085e2022-02-10 18:05:45 +0100796 return (None, None)
797
Philipp Maierff84c232020-05-12 17:24:18 +0200798
Supreeth Herle3b342c22020-03-24 16:15:02 +0100799def enc_addr_tlv(addr, addr_type='00'):
Harald Weltec91085e2022-02-10 18:05:45 +0100800 """
801 Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm.
802 See 3GPP TS 31.102 version 13.4.0 Release 13, section 4.2.8, 4.2.102 and 4.2.104.
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100803
Harald Weltec91085e2022-02-10 18:05:45 +0100804 Default values:
805 - addr_type: 00 - FQDN format of Address
806 """
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100807
Harald Weltec91085e2022-02-10 18:05:45 +0100808 s = ""
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100809
Harald Weltec91085e2022-02-10 18:05:45 +0100810 # TODO: Encoding of IPv6 address
811 if addr_type == '00': # FQDN
812 hex_str = s2h(addr)
813 s += '80' + ('%02x' % ((len(hex_str)//2)+1)) + '00' + hex_str
814 elif addr_type == '01': # IPv4
815 ipv4_list = addr.split('.')
816 ipv4_str = ""
817 for i in ipv4_list:
818 ipv4_str += ('%02x' % (int(i)))
Supreeth Herle654eca72020-03-25 14:25:38 +0100819
Harald Weltec91085e2022-02-10 18:05:45 +0100820 # Unused bytes shall be set to 'ff'. i.e 4th Octet after Address Type is not used
821 # IPv4 Address is in octet 5 to octet 8 of the TLV data object
822 s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100823
Harald Weltec91085e2022-02-10 18:05:45 +0100824 return s
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100825
Philipp Maier47236502021-03-09 21:28:25 +0100826
Harald Weltec91085e2022-02-10 18:05:45 +0100827def is_hex(string: str, minlen: int = 2, maxlen: Optional[int] = None) -> bool:
828 """
829 Check if a string is a valid hexstring
830 """
Philipp Maier47236502021-03-09 21:28:25 +0100831
Harald Weltec91085e2022-02-10 18:05:45 +0100832 # Filter obviously bad strings
833 if not string:
834 return False
835 if len(string) < minlen or minlen < 2:
836 return False
837 if len(string) % 2:
838 return False
839 if maxlen and len(string) > maxlen:
840 return False
Philipp Maier47236502021-03-09 21:28:25 +0100841
Harald Weltec91085e2022-02-10 18:05:45 +0100842 # Try actual encoding to be sure
843 try:
844 try_encode = h2b(string)
845 return True
846 except:
847 return False
Philipp Maiere8536c02020-05-11 21:35:01 +0200848
Philipp Maiere8536c02020-05-11 21:35:01 +0200849
Harald Weltec91085e2022-02-10 18:05:45 +0100850def sanitize_pin_adm(pin_adm, pin_adm_hex=None) -> Hexstr:
851 """
852 The ADM pin can be supplied either in its hexadecimal form or as
853 ascii string. This function checks the supplied opts parameter and
854 returns the pin_adm as hex encoded string, regardless in which form
855 it was originally supplied by the user
856 """
Philipp Maiere8536c02020-05-11 21:35:01 +0200857
Harald Weltec91085e2022-02-10 18:05:45 +0100858 if pin_adm is not None:
859 if len(pin_adm) <= 8:
860 pin_adm = ''.join(['%02x' % (ord(x)) for x in pin_adm])
861 pin_adm = rpad(pin_adm, 16)
Philipp Maiere8536c02020-05-11 21:35:01 +0200862
Harald Weltec91085e2022-02-10 18:05:45 +0100863 else:
864 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
865
866 if pin_adm_hex is not None:
867 if len(pin_adm_hex) == 16:
868 pin_adm = pin_adm_hex
869 # Ensure that it's hex-encoded
870 try:
871 try_encode = h2b(pin_adm)
872 except ValueError:
873 raise ValueError(
874 "PIN-ADM needs to be hex encoded using this option")
875 else:
876 raise ValueError(
877 "PIN-ADM needs to be exactly 16 digits (hex encoded)")
878
879 return pin_adm
880
Philipp Maiere8536c02020-05-11 21:35:01 +0200881
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100882def get_addr_type(addr):
Harald Weltec91085e2022-02-10 18:05:45 +0100883 """
884 Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
885 Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given)
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100886
Harald Weltec91085e2022-02-10 18:05:45 +0100887 TODO: Handle IPv6
888 """
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100889
Harald Weltec91085e2022-02-10 18:05:45 +0100890 # Empty address string
891 if not len(addr):
892 return None
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100893
Harald Weltec91085e2022-02-10 18:05:45 +0100894 addr_list = addr.split('.')
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100895
Harald Weltec91085e2022-02-10 18:05:45 +0100896 # Check for IPv4/IPv6
897 try:
898 import ipaddress
899 # Throws ValueError if addr is not correct
900 ipa = ipaddress.ip_address(addr)
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100901
Harald Weltec91085e2022-02-10 18:05:45 +0100902 if ipa.version == 4:
903 return 0x01
904 elif ipa.version == 6:
905 return 0x02
906 except Exception as e:
907 invalid_ipv4 = True
908 for i in addr_list:
909 # Invalid IPv4 may qualify for a valid FQDN, so make check here
910 # e.g. 172.24.15.300
911 import re
912 if not re.match('^[0-9_]+$', i):
913 invalid_ipv4 = False
914 break
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100915
Harald Weltec91085e2022-02-10 18:05:45 +0100916 if invalid_ipv4:
917 return None
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100918
Harald Weltec91085e2022-02-10 18:05:45 +0100919 fqdn_flag = True
920 for i in addr_list:
921 # Only Alpha-numeric characters and hyphen - RFC 1035
922 import re
923 if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
924 fqdn_flag = False
925 break
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100926
Harald Weltec91085e2022-02-10 18:05:45 +0100927 # FQDN
928 if fqdn_flag:
929 return 0x00
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100930
Harald Weltec91085e2022-02-10 18:05:45 +0100931 return None
Harald Welte67d551a2021-01-21 14:50:01 +0100932
Philipp Maier5d3e2592021-02-22 17:22:16 +0100933
Harald Weltec91085e2022-02-10 18:05:45 +0100934def sw_match(sw: str, pattern: str) -> bool:
935 """Match given SW against given pattern."""
936 # Create a masked version of the returned status word
937 sw_lower = sw.lower()
938 sw_masked = ""
939 for i in range(0, 4):
940 if pattern[i] == '?':
941 sw_masked = sw_masked + '?'
942 elif pattern[i] == 'x':
943 sw_masked = sw_masked + 'x'
944 else:
945 sw_masked = sw_masked + sw_lower[i]
946 # Compare the masked version against the pattern
947 return sw_masked == pattern
Harald Welteee3501f2021-04-02 13:00:18 +0200948
Harald Weltec91085e2022-02-10 18:05:45 +0100949
950def tabulate_str_list(str_list, width: int = 79, hspace: int = 2, lspace: int = 1,
951 align_left: bool = True) -> str:
952 """Pretty print a list of strings into a tabulated form.
953
954 Args:
955 width : total width in characters per line
956 space : horizontal space between cells
957 lspace : number of spaces before row
958 align_lef : Align text to the left side
959 Returns:
960 multi-line string containing formatted table
961 """
962 if str_list == None:
963 return ""
964 if len(str_list) <= 0:
965 return ""
966 longest_str = max(str_list, key=len)
967 cellwith = len(longest_str) + hspace
968 cols = width // cellwith
969 rows = (len(str_list) - 1) // cols + 1
970 table = []
971 for i in iter(range(rows)):
972 str_list_row = str_list[i::rows]
973 if (align_left):
974 format_str_cell = '%%-%ds'
975 else:
976 format_str_cell = '%%%ds'
977 format_str_row = (format_str_cell % cellwith) * len(str_list_row)
978 format_str_row = (" " * lspace) + format_str_row
979 table.append(format_str_row % tuple(str_list_row))
980 return '\n'.join(table)
981
Harald Welte5e749a72021-04-10 17:18:17 +0200982
Harald Welte917d98c2021-04-21 11:51:25 +0200983def auto_int(x):
984 """Helper function for argparse to accept hexadecimal integers."""
985 return int(x, 0)
986
Harald Weltec91085e2022-02-10 18:05:45 +0100987
Philipp Maier40ea4a42022-06-02 14:45:41 +0200988def expand_hex(hexstring, length):
989 """Expand a given hexstring to a specified length by replacing "." or ".."
990 with a filler that is derived from the neighboring nibbles respective
991 bytes. Usually this will be the nibble respective byte before "." or
992 "..", execpt when the string begins with "." or "..", then the nibble
993 respective byte after "." or ".." is used.". In case the string cannot
994 be expanded for some reason, the input string is returned unmodified.
995
996 Args:
997 hexstring : hexstring to expand
998 length : desired length of the resulting hexstring.
999 Returns:
1000 expanded hexstring
1001 """
1002
1003 # expand digit aligned
1004 if hexstring.count(".") == 1:
1005 pos = hexstring.index(".")
1006 if pos > 0:
1007 filler = hexstring[pos - 1]
1008 else:
1009 filler = hexstring[pos + 1]
1010
1011 missing = length * 2 - (len(hexstring) - 1)
1012 if missing <= 0:
1013 return hexstring
1014
1015 return hexstring.replace(".", filler * missing)
1016
1017 # expand byte aligned
1018 elif hexstring.count("..") == 1:
1019 if len(hexstring) % 2:
1020 return hexstring
1021
1022 pos = hexstring.index("..")
1023
1024 if pos % 2:
1025 return hexstring
1026
1027 if pos > 1:
1028 filler = hexstring[pos - 2:pos]
1029 else:
1030 filler = hexstring[pos + 2:pos+4]
1031
1032 missing = length * 2 - (len(hexstring) - 2)
1033 if missing <= 0:
1034 return hexstring
1035
1036 return hexstring.replace("..", filler * (missing // 2))
1037
1038 # no change
1039 return hexstring
1040
1041
Harald Welte5e749a72021-04-10 17:18:17 +02001042class JsonEncoder(json.JSONEncoder):
1043 """Extend the standard library JSONEncoder with support for more types."""
Harald Weltec91085e2022-02-10 18:05:45 +01001044
Harald Welte5e749a72021-04-10 17:18:17 +02001045 def default(self, o):
1046 if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
1047 return b2h(o)
1048 return json.JSONEncoder.default(self, o)
Philipp Maier80ce71f2021-04-19 21:24:23 +02001049
Harald Weltec91085e2022-02-10 18:05:45 +01001050
Philipp Maier80ce71f2021-04-19 21:24:23 +02001051def boxed_heading_str(heading, width=80):
Harald Weltec91085e2022-02-10 18:05:45 +01001052 """Generate a string that contains a boxed heading."""
1053 # Auto-enlarge box if heading exceeds length
1054 if len(heading) > width - 4:
1055 width = len(heading) + 4
Philipp Maier80ce71f2021-04-19 21:24:23 +02001056
Harald Weltec91085e2022-02-10 18:05:45 +01001057 res = "#" * width
1058 fstr = "\n# %-" + str(width - 4) + "s #\n"
1059 res += fstr % (heading)
1060 res += "#" * width
1061 return res
Harald Welte3de6ca22021-05-02 20:05:56 +02001062
1063
1064class DataObject(abc.ABC):
1065 """A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
1066 simply has any number of different TLVs that may occur in any order at any point, ISO 7816
1067 has the habit of specifying TLV data but with very spcific ordering, or specific choices of
1068 tags at specific points in a stream. This class tries to represent this."""
Harald Weltec91085e2022-02-10 18:05:45 +01001069
Harald Welte425038f2022-02-10 19:32:04 +01001070 def __init__(self, name: str, desc: Optional[str] = None, tag: Optional[int] = None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001071 """
1072 Args:
1073 name: A brief, all-lowercase, underscore separated string identifier
1074 desc: A human-readable description of what this DO represents
1075 tag : The tag associated with this DO
1076 """
1077 self.name = name
1078 self.desc = desc
1079 self.tag = tag
1080 self.decoded = None
1081 self.encoded = None
1082
1083 def __str__(self):
1084 return self.name
1085
Harald Welte50368772022-02-10 15:22:22 +01001086 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001087 return '%s(%s)' % (self.__class__, self.name)
1088
Harald Welte50368772022-02-10 15:22:22 +01001089 def __or__(self, other) -> 'DataObjectChoice':
Harald Welte3de6ca22021-05-02 20:05:56 +02001090 """OR-ing DataObjects together renders a DataObjectChoice."""
1091 if isinstance(other, DataObject):
1092 # DataObject | DataObject = DataObjectChoice
1093 return DataObjectChoice(None, members=[self, other])
1094 else:
1095 raise TypeError
1096
Harald Welte50368772022-02-10 15:22:22 +01001097 def __add__(self, other) -> 'DataObjectCollection':
Harald Welte3de6ca22021-05-02 20:05:56 +02001098 """ADD-ing DataObjects together renders a DataObjectCollection."""
1099 if isinstance(other, DataObject):
1100 # DataObject + DataObject = DataObjectCollectin
1101 return DataObjectCollection(None, members=[self, other])
Harald Welte50368772022-02-10 15:22:22 +01001102 else:
1103 raise TypeError
Harald Welte3de6ca22021-05-02 20:05:56 +02001104
Harald Welte50368772022-02-10 15:22:22 +01001105 def _compute_tag(self) -> int:
Harald Welte3de6ca22021-05-02 20:05:56 +02001106 """Compute the tag (sometimes the tag encodes part of the value)."""
1107 return self.tag
1108
Harald Welte50368772022-02-10 15:22:22 +01001109 def to_dict(self) -> dict:
Harald Welte3de6ca22021-05-02 20:05:56 +02001110 """Return a dict in form "name: decoded_value" """
1111 return {self.name: self.decoded}
1112
1113 @abc.abstractmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001114 def from_bytes(self, do: bytes):
Harald Welte3de6ca22021-05-02 20:05:56 +02001115 """Parse the value part of the DO into the internal state of this instance.
1116 Args:
1117 do : binary encoded bytes
1118 """
1119
1120 @abc.abstractmethod
Harald Welte50368772022-02-10 15:22:22 +01001121 def to_bytes(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001122 """Encode the internal state of this instance into the TLV value part.
1123 Returns:
1124 binary bytes encoding the internal state
1125 """
1126
Harald Weltec91085e2022-02-10 18:05:45 +01001127 def from_tlv(self, do: bytes) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001128 """Parse binary TLV representation into internal state. The resulting decoded
1129 representation is _not_ returned, but just internalized in the object instance!
1130 Args:
1131 do : input bytes containing TLV-encoded representation
1132 Returns:
1133 bytes remaining at end of 'do' after parsing one TLV/DO.
1134 """
1135 if do[0] != self.tag:
Harald Weltec91085e2022-02-10 18:05:45 +01001136 raise ValueError('%s: Can only decode tag 0x%02x' %
1137 (self, self.tag))
Harald Welte3de6ca22021-05-02 20:05:56 +02001138 length = do[1]
1139 val = do[2:2+length]
1140 self.from_bytes(val)
1141 # return remaining bytes
1142 return do[2+length:]
1143
Harald Welte50368772022-02-10 15:22:22 +01001144 def to_tlv(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001145 """Encode internal representation to binary TLV.
1146 Returns:
1147 bytes encoded in TLV format.
1148 """
1149 val = self.to_bytes()
Harald Welte785d4842022-04-05 14:24:22 +02001150 return bertlv_encode_tag(self._compute_tag()) + bertlv_encode_len(len(val)) + val
Harald Welte3de6ca22021-05-02 20:05:56 +02001151
1152 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001153 def decode(self, binary: bytes) -> Tuple[dict, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001154 """Decode a single DOs from the input data.
1155 Args:
1156 binary : binary bytes of encoded data
1157 Returns:
1158 tuple of (decoded_result, binary_remainder)
1159 """
1160 tag = binary[0]
1161 if tag != self.tag:
1162 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected 0x%02x' %
1163 (self, tag, binary, self.tag))
1164 remainder = self.from_tlv(binary)
1165 return (self.to_dict(), remainder)
1166
1167 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001168 def encode(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001169 return self.to_tlv()
1170
Harald Weltec91085e2022-02-10 18:05:45 +01001171
Harald Welte3de6ca22021-05-02 20:05:56 +02001172class TL0_DataObject(DataObject):
1173 """Data Object that has Tag, Len=0 and no Value part."""
Harald Weltec91085e2022-02-10 18:05:45 +01001174
1175 def __init__(self, name: str, desc: str, tag: int, val=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001176 super().__init__(name, desc, tag)
1177 self.val = val
1178
Harald Weltec91085e2022-02-10 18:05:45 +01001179 def from_bytes(self, binary: bytes):
Harald Welte3de6ca22021-05-02 20:05:56 +02001180 if len(binary) != 0:
1181 raise ValueError
1182 self.decoded = self.val
1183
Harald Welte50368772022-02-10 15:22:22 +01001184 def to_bytes(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001185 return b''
1186
1187
1188class DataObjectCollection:
1189 """A DataObjectCollection consits of multiple Data Objects identified by their tags.
1190 A given encoded DO may contain any of them in any order, and may contain multiple instances
1191 of each DO."""
Harald Weltec91085e2022-02-10 18:05:45 +01001192
Harald Welte425038f2022-02-10 19:32:04 +01001193 def __init__(self, name: str, desc: Optional[str] = None, members=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001194 self.name = name
1195 self.desc = desc
1196 self.members = members or []
1197 self.members_by_tag = {}
1198 self.members_by_name = {}
Harald Weltec91085e2022-02-10 18:05:45 +01001199 self.members_by_tag = {m.tag: m for m in members}
1200 self.members_by_name = {m.name: m for m in members}
Harald Welte3de6ca22021-05-02 20:05:56 +02001201
Harald Welte50368772022-02-10 15:22:22 +01001202 def __str__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001203 member_strs = [str(x) for x in self.members]
1204 return '%s(%s)' % (self.name, ','.join(member_strs))
1205
Harald Welte50368772022-02-10 15:22:22 +01001206 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001207 member_strs = [repr(x) for x in self.members]
1208 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1209
Harald Welte50368772022-02-10 15:22:22 +01001210 def __add__(self, other) -> 'DataObjectCollection':
Harald Welte3de6ca22021-05-02 20:05:56 +02001211 """Extending DataCollections with other DataCollections or DataObjects."""
1212 if isinstance(other, DataObjectCollection):
1213 # adding one collection to another
1214 members = self.members + other.members
1215 return DataObjectCollection(self.name, self.desc, members)
1216 elif isinstance(other, DataObject):
1217 # adding a member to a collection
1218 return DataObjectCollection(self.name, self.desc, self.members + [other])
1219 else:
1220 raise TypeError
1221
1222 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001223 def decode(self, binary: bytes) -> Tuple[List, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001224 """Decode any number of DOs from the collection until the end of the input data,
1225 or uninitialized memory (0xFF) is found.
1226 Args:
1227 binary : binary bytes of encoded data
1228 Returns:
1229 tuple of (decoded_result, binary_remainder)
1230 """
1231 res = []
1232 remainder = binary
1233 # iterate until no binary trailer is left
1234 while len(remainder):
1235 tag = remainder[0]
Harald Weltec91085e2022-02-10 18:05:45 +01001236 if tag == 0xff: # uninitialized memory at the end?
Harald Welte3de6ca22021-05-02 20:05:56 +02001237 return (res, remainder)
1238 if not tag in self.members_by_tag:
1239 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1240 (self, tag, remainder, self.members_by_tag.keys()))
1241 obj = self.members_by_tag[tag]
1242 # DO from_tlv returns remainder of binary
1243 remainder = obj.from_tlv(remainder)
1244 # collect our results
1245 res.append(obj.to_dict())
1246 return (res, remainder)
1247
1248 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001249 def encode(self, decoded) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001250 res = bytearray()
1251 for i in decoded:
1252 obj = self.members_by_name(i[0])
1253 res.append(obj.to_tlv())
1254 return res
1255
Harald Weltec91085e2022-02-10 18:05:45 +01001256
Harald Welte3de6ca22021-05-02 20:05:56 +02001257class DataObjectChoice(DataObjectCollection):
1258 """One Data Object from within a choice, identified by its tag.
1259 This means that exactly one member of the choice must occur, and which one occurs depends
1260 on the tag."""
Harald Weltec91085e2022-02-10 18:05:45 +01001261
Harald Welte3de6ca22021-05-02 20:05:56 +02001262 def __add__(self, other):
1263 """We overload the add operator here to avoid inheriting it from DataObjecCollection."""
1264 raise TypeError
1265
Harald Welte50368772022-02-10 15:22:22 +01001266 def __or__(self, other) -> 'DataObjectChoice':
Harald Welte3de6ca22021-05-02 20:05:56 +02001267 """OR-ing a Choice to another choice extends the choice, as does OR-ing a DataObject."""
1268 if isinstance(other, DataObjectChoice):
1269 # adding one collection to another
1270 members = self.members + other.members
1271 return DataObjectChoice(self.name, self.desc, members)
1272 elif isinstance(other, DataObject):
1273 # adding a member to a collection
1274 return DataObjectChoice(self.name, self.desc, self.members + [other])
1275 else:
1276 raise TypeError
1277
1278 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001279 def decode(self, binary: bytes) -> Tuple[dict, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001280 """Decode a single DOs from the choice based on the tag.
1281 Args:
1282 binary : binary bytes of encoded data
1283 Returns:
1284 tuple of (decoded_result, binary_remainder)
1285 """
1286 tag = binary[0]
1287 if tag == 0xff:
1288 return (None, binary)
1289 if not tag in self.members_by_tag:
1290 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1291 (self, tag, binary, self.members_by_tag.keys()))
1292 obj = self.members_by_tag[tag]
1293 remainder = obj.from_tlv(binary)
1294 return (obj.to_dict(), remainder)
1295
1296 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001297 def encode(self, decoded) -> bytes:
Harald Welte785d4842022-04-05 14:24:22 +02001298 obj = self.members_by_name[list(decoded)[0]]
1299 obj.decoded = list(decoded.values())[0]
Harald Welte3de6ca22021-05-02 20:05:56 +02001300 return obj.to_tlv()
1301
Harald Weltec91085e2022-02-10 18:05:45 +01001302
Harald Welte3de6ca22021-05-02 20:05:56 +02001303class DataObjectSequence:
1304 """A sequence of DataObjects or DataObjectChoices. This allows us to express a certain
1305 ordered sequence of DOs or choices of DOs that have to appear as per the specification.
1306 By wrapping them into this formal DataObjectSequence, we can offer convenience methods
1307 for encoding or decoding an entire sequence."""
Harald Weltec91085e2022-02-10 18:05:45 +01001308
Harald Welte425038f2022-02-10 19:32:04 +01001309 def __init__(self, name: str, desc: Optional[str] = None, sequence=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001310 self.sequence = sequence or []
1311 self.name = name
1312 self.desc = desc
1313
Harald Welte50368772022-02-10 15:22:22 +01001314 def __str__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001315 member_strs = [str(x) for x in self.sequence]
1316 return '%s(%s)' % (self.name, ','.join(member_strs))
1317
Harald Welte50368772022-02-10 15:22:22 +01001318 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001319 member_strs = [repr(x) for x in self.sequence]
1320 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1321
Harald Welte50368772022-02-10 15:22:22 +01001322 def __add__(self, other) -> 'DataObjectSequence':
Harald Welte3de6ca22021-05-02 20:05:56 +02001323 """Add (append) a DataObject or DataObjectChoice to the sequence."""
1324 if isinstance(other, 'DataObject'):
Harald Weltec91085e2022-02-10 18:05:45 +01001325 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
Harald Welte3de6ca22021-05-02 20:05:56 +02001326 elif isinstance(other, 'DataObjectChoice'):
Harald Weltec91085e2022-02-10 18:05:45 +01001327 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
Harald Welte3de6ca22021-05-02 20:05:56 +02001328 elif isinstance(other, 'DataObjectSequence'):
Harald Weltec91085e2022-02-10 18:05:45 +01001329 return DataObjectSequence(self.name, self.desc, self.sequence + other.sequence)
Harald Welte3de6ca22021-05-02 20:05:56 +02001330
1331 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001332 def decode(self, binary: bytes) -> Tuple[list, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001333 """Decode a sequence by calling the decoder of each element in the sequence.
1334 Args:
1335 binary : binary bytes of encoded data
1336 Returns:
1337 tuple of (decoded_result, binary_remainder)
1338 """
1339 remainder = binary
1340 res = []
1341 for e in self.sequence:
1342 (r, remainder) = e.decode(remainder)
1343 if r:
1344 res.append(r)
1345 return (res, remainder)
1346
1347 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001348 def decode_multi(self, do: bytes) -> Tuple[list, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001349 """Decode multiple occurrences of the sequence from the binary input data.
1350 Args:
1351 do : binary input data to be decoded
1352 Returns:
1353 list of results of the decoder of this sequences
1354 """
1355 remainder = do
1356 res = []
1357 while len(remainder):
1358 (r, remainder2) = self.decode(remainder)
1359 if r:
1360 res.append(r)
1361 if len(remainder2) < len(remainder):
1362 remainder = remainder2
1363 else:
1364 remainder = remainder2
1365 break
1366 return (res, remainder)
1367
1368 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001369 def encode(self, decoded) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001370 """Encode a sequence by calling the encoder of each element in the sequence."""
1371 encoded = bytearray()
1372 i = 0
1373 for e in self.sequence:
1374 encoded += e.encode(decoded[i])
1375 i += 1
1376 return encoded
Harald Welte90441432021-05-02 21:28:12 +02001377
Harald Welte0dcdfbf2022-04-05 14:42:48 +02001378 def encode_multi(self, decoded) -> bytes:
1379 """Encode multiple occurrences of the sequence from the decoded input data.
1380 Args:
1381 decoded : list of json-serializable input data; one sequence per list item
1382 Returns:
1383 binary encoded output data
1384 """
1385 encoded = bytearray()
1386 for d in decoded:
1387 encoded += self.encode(d)
1388 return encoded
1389
Harald Weltec91085e2022-02-10 18:05:45 +01001390
Harald Welte90441432021-05-02 21:28:12 +02001391class CardCommand:
1392 """A single card command / instruction."""
Harald Weltec91085e2022-02-10 18:05:45 +01001393
Harald Welte90441432021-05-02 21:28:12 +02001394 def __init__(self, name, ins, cla_list=None, desc=None):
1395 self.name = name
1396 self.ins = ins
1397 self.cla_list = cla_list or []
1398 self.cla_list = [x.lower() for x in self.cla_list]
1399 self.desc = desc
1400
1401 def __str__(self):
1402 return self.name
1403
1404 def __repr__(self):
1405 return '%s(INS=%02x,CLA=%s)' % (self.name, self.ins, self.cla_list)
1406
1407 def match_cla(self, cla):
1408 """Does the given CLA match the CLA list of the command?."""
1409 if not isinstance(cla, str):
1410 cla = '%02u' % cla
1411 cla = cla.lower()
1412 for cla_match in self.cla_list:
1413 cla_masked = ""
1414 for i in range(0, 2):
1415 if cla_match[i] == 'x':
1416 cla_masked += 'x'
1417 else:
1418 cla_masked += cla[i]
1419 if cla_masked == cla_match:
1420 return True
1421 return False
1422
1423
1424class CardCommandSet:
1425 """A set of card instructions, typically specified within one spec."""
Harald Weltec91085e2022-02-10 18:05:45 +01001426
Harald Welte90441432021-05-02 21:28:12 +02001427 def __init__(self, name, cmds=[]):
1428 self.name = name
Harald Weltec91085e2022-02-10 18:05:45 +01001429 self.cmds = {c.ins: c for c in cmds}
Harald Welte90441432021-05-02 21:28:12 +02001430
1431 def __str__(self):
1432 return self.name
1433
1434 def __getitem__(self, idx):
1435 return self.cmds[idx]
1436
1437 def __add__(self, other):
1438 if isinstance(other, CardCommand):
1439 if other.ins in self.cmds:
1440 raise ValueError('%s: INS 0x%02x already defined: %s' %
1441 (self, other.ins, self.cmds[other.ins]))
1442 self.cmds[other.ins] = other
1443 elif isinstance(other, CardCommandSet):
1444 for c in other.cmds.keys():
1445 self.cmds[c] = other.cmds[c]
1446 else:
Harald Weltec91085e2022-02-10 18:05:45 +01001447 raise ValueError(
1448 '%s: Unsupported type to add operator: %s' % (self, other))
Harald Welte90441432021-05-02 21:28:12 +02001449
1450 def lookup(self, ins, cla=None):
1451 """look-up the command within the CommandSet."""
1452 ins = int(ins)
1453 if not ins in self.cmds:
1454 return None
1455 cmd = self.cmds[ins]
1456 if cla and not cmd.match_cla(cla):
1457 return None
1458 return cmd
Philipp Maiera028c7d2021-11-08 16:12:03 +01001459
Harald Weltec91085e2022-02-10 18:05:45 +01001460
Philipp Maiera028c7d2021-11-08 16:12:03 +01001461def all_subclasses(cls) -> set:
Harald Weltec91085e2022-02-10 18:05:45 +01001462 """Recursively get all subclasses of a specified class"""
1463 return set(cls.__subclasses__()).union([s for c in cls.__subclasses__() for s in all_subclasses(c)])