blob: 6cacdaa6d903dcb29eb783d409d10a6425672f2b [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
541 # We pass in hex string and now need to work on bytes
542 ki_bytes = bytes(h2b(ki_hex))
543 op_bytes = bytes(h2b(op_hex))
544 aes = AES.new(ki_bytes, AES.MODE_ECB)
545 opc_bytes = aes.encrypt(op_bytes)
546 return b2h(strxor(opc_bytes, op_bytes))
547
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900548
Harald Welte52255572021-04-03 09:56:32 +0200549def calculate_luhn(cc) -> int:
Harald Weltec91085e2022-02-10 18:05:45 +0100550 """
551 Calculate Luhn checksum used in e.g. ICCID and IMEI
552 """
553 num = list(map(int, str(cc)))
554 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10))
555 for d in num[::-2]]) % 10
556 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200557
Philipp Maier7592eee2019-09-12 13:03:23 +0200558
Harald Weltec91085e2022-02-10 18:05:45 +0100559def mcc_from_imsi(imsi: str) -> Optional[str]:
560 """
561 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
562 """
563 if imsi == None:
564 return None
Philipp Maier7592eee2019-09-12 13:03:23 +0200565
Harald Weltec91085e2022-02-10 18:05:45 +0100566 if len(imsi) > 3:
567 return imsi[:3]
568 else:
569 return None
Philipp Maier7592eee2019-09-12 13:03:23 +0200570
Supreeth Herled24f1632019-11-30 10:37:09 +0100571
Harald Weltec91085e2022-02-10 18:05:45 +0100572def mnc_from_imsi(imsi: str, long: bool = False) -> Optional[str]:
573 """
574 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
575 """
576 if imsi == None:
577 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100578
Harald Weltec91085e2022-02-10 18:05:45 +0100579 if len(imsi) > 3:
580 if long:
581 return imsi[3:6]
582 else:
583 return imsi[3:5]
584 else:
585 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100586
Supreeth Herled24f1632019-11-30 10:37:09 +0100587
Harald Weltec91085e2022-02-10 18:05:45 +0100588def derive_mcc(digit1: int, digit2: int, digit3: int) -> int:
589 """
590 Derive decimal representation of the MCC (Mobile Country Code)
591 from three given digits.
592 """
Supreeth Herled24f1632019-11-30 10:37:09 +0100593
Harald Weltec91085e2022-02-10 18:05:45 +0100594 mcc = 0
Supreeth Herled24f1632019-11-30 10:37:09 +0100595
Harald Weltec91085e2022-02-10 18:05:45 +0100596 if digit1 != 0x0f:
597 mcc += digit1 * 100
598 if digit2 != 0x0f:
599 mcc += digit2 * 10
600 if digit3 != 0x0f:
601 mcc += digit3
Supreeth Herled24f1632019-11-30 10:37:09 +0100602
Harald Weltec91085e2022-02-10 18:05:45 +0100603 return mcc
Supreeth Herled24f1632019-11-30 10:37:09 +0100604
Supreeth Herled24f1632019-11-30 10:37:09 +0100605
Harald Weltec91085e2022-02-10 18:05:45 +0100606def derive_mnc(digit1: int, digit2: int, digit3: int = 0x0f) -> int:
607 """
608 Derive decimal representation of the MNC (Mobile Network Code)
609 from two or (optionally) three given digits.
610 """
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100611
Harald Weltec91085e2022-02-10 18:05:45 +0100612 mnc = 0
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100613
Harald Weltec91085e2022-02-10 18:05:45 +0100614 # 3-rd digit is optional for the MNC. If present
615 # the algorythm is the same as for the MCC.
616 if digit3 != 0x0f:
617 return derive_mcc(digit1, digit2, digit3)
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100618
Harald Weltec91085e2022-02-10 18:05:45 +0100619 if digit1 != 0x0f:
620 mnc += digit1 * 10
621 if digit2 != 0x0f:
622 mnc += digit2
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100623
Harald Weltec91085e2022-02-10 18:05:45 +0100624 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100625
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100626
Harald Weltec91085e2022-02-10 18:05:45 +0100627def dec_msisdn(ef_msisdn: Hexstr) -> Optional[Tuple[int, int, Optional[str]]]:
628 """
629 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
630 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
631 """
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100632
Harald Weltec91085e2022-02-10 18:05:45 +0100633 # Convert from str to (kind of) 'bytes'
634 ef_msisdn = h2b(ef_msisdn)
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100635
Harald Weltec91085e2022-02-10 18:05:45 +0100636 # Make sure mandatory fields are present
637 if len(ef_msisdn) < 14:
638 raise ValueError("EF.MSISDN is too short")
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100639
Harald Weltec91085e2022-02-10 18:05:45 +0100640 # Skip optional Alpha Identifier
641 xlen = len(ef_msisdn) - 14
642 msisdn_lhv = ef_msisdn[xlen:]
Supreeth Herle5a541012019-12-22 08:59:16 +0100643
Harald Weltec91085e2022-02-10 18:05:45 +0100644 # Parse the length (in bytes) of the BCD encoded number
645 bcd_len = msisdn_lhv[0]
646 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
647 if bcd_len == 0xff:
648 return None
649 elif bcd_len > 11 or bcd_len < 1:
650 raise ValueError(
651 "Length of MSISDN (%d bytes) is out of range" % bcd_len)
Supreeth Herle5a541012019-12-22 08:59:16 +0100652
Harald Weltec91085e2022-02-10 18:05:45 +0100653 # Parse ToN / NPI
654 ton = (msisdn_lhv[1] >> 4) & 0x07
655 npi = msisdn_lhv[1] & 0x0f
656 bcd_len -= 1
Supreeth Herle5a541012019-12-22 08:59:16 +0100657
Harald Weltec91085e2022-02-10 18:05:45 +0100658 # No MSISDN?
659 if not bcd_len:
660 return (npi, ton, None)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200661
Harald Weltec91085e2022-02-10 18:05:45 +0100662 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
663 # International number 10.5.118/3GPP TS 24.008
664 if ton == 0x01:
665 msisdn = '+' + msisdn
Supreeth Herle5a541012019-12-22 08:59:16 +0100666
Harald Weltec91085e2022-02-10 18:05:45 +0100667 return (npi, ton, msisdn)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200668
Supreeth Herle5a541012019-12-22 08:59:16 +0100669
Harald Weltec91085e2022-02-10 18:05:45 +0100670def enc_msisdn(msisdn: str, npi: int = 0x01, ton: int = 0x03) -> Hexstr:
671 """
672 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
673 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
674 will not contain the optional Alpha Identifier at the beginning.)
Supreeth Herle5a541012019-12-22 08:59:16 +0100675
Harald Weltec91085e2022-02-10 18:05:45 +0100676 Default NPI / ToN values:
677 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
678 - ToN: network specific or international number (if starts with '+').
679 """
680
681 # If no MSISDN is supplied then encode the file contents as all "ff"
682 if msisdn == "" or msisdn == "+":
683 return "ff" * 14
684
685 # Leading '+' indicates International Number
686 if msisdn[0] == '+':
687 msisdn = msisdn[1:]
688 ton = 0x01
689
690 # An MSISDN must not exceed 20 digits
691 if len(msisdn) > 20:
692 raise ValueError("msisdn must not be longer than 20 digits")
693
694 # Append 'f' padding if number of digits is odd
695 if len(msisdn) % 2 > 0:
696 msisdn += 'f'
697
698 # BCD length also includes NPI/ToN header
699 bcd_len = len(msisdn) // 2 + 1
700 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
701 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
702
703 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200704
Supreeth Herle441c4a72020-03-24 10:19:15 +0100705
Supreeth Herle98370552020-05-11 09:04:41 +0200706def first_TLV_parser(bytelist):
Harald Weltec91085e2022-02-10 18:05:45 +0100707 '''
708 first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
Supreeth Herle98370552020-05-11 09:04:41 +0200709
Harald Weltec91085e2022-02-10 18:05:45 +0100710 parses first TLV format record in a list of bytelist
711 returns a 3-Tuple: Tag, Length, Value
712 Value is a list of bytes
713 parsing of length is ETSI'style 101.220
714 '''
715 Tag = bytelist[0]
716 if bytelist[1] == 0xFF:
717 Len = bytelist[2]*256 + bytelist[3]
718 Val = bytelist[4:4+Len]
719 else:
720 Len = bytelist[1]
721 Val = bytelist[2:2+Len]
722 return (Tag, Len, Val)
723
Supreeth Herle98370552020-05-11 09:04:41 +0200724
725def TLV_parser(bytelist):
Harald Weltec91085e2022-02-10 18:05:45 +0100726 '''
727 TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
Supreeth Herle98370552020-05-11 09:04:41 +0200728
Harald Weltec91085e2022-02-10 18:05:45 +0100729 loops on the input list of bytes with the "first_TLV_parser()" function
730 returns a list of 3-Tuples
731 '''
732 ret = []
733 while len(bytelist) > 0:
734 T, L, V = first_TLV_parser(bytelist)
735 if T == 0xFF:
736 # padding bytes
737 break
738 ret.append((T, L, V))
739 # need to manage length of L
740 if L > 0xFE:
741 bytelist = bytelist[L+4:]
742 else:
743 bytelist = bytelist[L+2:]
744 return ret
745
Supreeth Herled572ede2020-03-22 09:55:04 +0100746
Supreeth Herle3b342c22020-03-24 16:15:02 +0100747def dec_addr_tlv(hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100748 """
749 Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm.
750 See 3GPP TS 31.102 version 13.4.0 Release 13, section 4.2.8, 4.2.102 and 4.2.104.
751 """
Supreeth Herled572ede2020-03-22 09:55:04 +0100752
Harald Weltec91085e2022-02-10 18:05:45 +0100753 # Convert from hex str to int bytes list
754 addr_tlv_bytes = h2i(hexstr)
Supreeth Herled572ede2020-03-22 09:55:04 +0100755
Harald Weltec91085e2022-02-10 18:05:45 +0100756 # Get list of tuples containing parsed TLVs
757 tlvs = TLV_parser(addr_tlv_bytes)
Supreeth Herled572ede2020-03-22 09:55:04 +0100758
Harald Weltec91085e2022-02-10 18:05:45 +0100759 for tlv in tlvs:
760 # tlv = (T, L, [V])
761 # T = Tag
762 # L = Length
763 # [V] = List of value
Supreeth Herled572ede2020-03-22 09:55:04 +0100764
Harald Weltec91085e2022-02-10 18:05:45 +0100765 # Invalid Tag value scenario
766 if tlv[0] != 0x80:
767 continue
Supreeth Herled572ede2020-03-22 09:55:04 +0100768
Harald Weltec91085e2022-02-10 18:05:45 +0100769 # Empty field - Zero length
770 if tlv[1] == 0:
771 continue
Supreeth Herled6a5ec52020-06-01 12:27:51 +0200772
Philipp Maier1f46f072023-07-27 10:38:38 +0200773 # Uninitialized field
774 if all([v == 0xff for v in tlv[2]]):
775 continue
776
Harald Weltec91085e2022-02-10 18:05:45 +0100777 # First byte in the value has the address type
778 addr_type = tlv[2][0]
779 # TODO: Support parsing of IPv6
780 # Address Type: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), other (Reserved)
781 if addr_type == 0x00: # FQDN
782 # Skip address tye byte i.e. first byte in value list
783 content = tlv[2][1:]
784 return (i2s(content), '00')
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200785
Harald Weltec91085e2022-02-10 18:05:45 +0100786 elif addr_type == 0x01: # IPv4
787 # Skip address tye byte i.e. first byte in value list
788 # Skip the unused byte in Octect 4 after address type byte as per 3GPP TS 31.102
789 ipv4 = tlv[2][2:]
790 content = '.'.join(str(x) for x in ipv4)
791 return (content, '01')
792 else:
793 raise ValueError("Invalid address type")
Supreeth Herled572ede2020-03-22 09:55:04 +0100794
Harald Weltec91085e2022-02-10 18:05:45 +0100795 return (None, None)
796
Philipp Maierff84c232020-05-12 17:24:18 +0200797
Supreeth Herle3b342c22020-03-24 16:15:02 +0100798def enc_addr_tlv(addr, addr_type='00'):
Harald Weltec91085e2022-02-10 18:05:45 +0100799 """
800 Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm.
801 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 +0100802
Harald Weltec91085e2022-02-10 18:05:45 +0100803 Default values:
804 - addr_type: 00 - FQDN format of Address
805 """
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100806
Harald Weltec91085e2022-02-10 18:05:45 +0100807 s = ""
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100808
Harald Weltec91085e2022-02-10 18:05:45 +0100809 # TODO: Encoding of IPv6 address
810 if addr_type == '00': # FQDN
811 hex_str = s2h(addr)
812 s += '80' + ('%02x' % ((len(hex_str)//2)+1)) + '00' + hex_str
813 elif addr_type == '01': # IPv4
814 ipv4_list = addr.split('.')
815 ipv4_str = ""
816 for i in ipv4_list:
817 ipv4_str += ('%02x' % (int(i)))
Supreeth Herle654eca72020-03-25 14:25:38 +0100818
Harald Weltec91085e2022-02-10 18:05:45 +0100819 # Unused bytes shall be set to 'ff'. i.e 4th Octet after Address Type is not used
820 # IPv4 Address is in octet 5 to octet 8 of the TLV data object
821 s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100822
Harald Weltec91085e2022-02-10 18:05:45 +0100823 return s
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100824
Philipp Maier47236502021-03-09 21:28:25 +0100825
Harald Weltec91085e2022-02-10 18:05:45 +0100826def is_hex(string: str, minlen: int = 2, maxlen: Optional[int] = None) -> bool:
827 """
828 Check if a string is a valid hexstring
829 """
Philipp Maier47236502021-03-09 21:28:25 +0100830
Harald Weltec91085e2022-02-10 18:05:45 +0100831 # Filter obviously bad strings
832 if not string:
833 return False
834 if len(string) < minlen or minlen < 2:
835 return False
836 if len(string) % 2:
837 return False
838 if maxlen and len(string) > maxlen:
839 return False
Philipp Maier47236502021-03-09 21:28:25 +0100840
Harald Weltec91085e2022-02-10 18:05:45 +0100841 # Try actual encoding to be sure
842 try:
843 try_encode = h2b(string)
844 return True
845 except:
846 return False
Philipp Maiere8536c02020-05-11 21:35:01 +0200847
Philipp Maiere8536c02020-05-11 21:35:01 +0200848
Harald Weltec91085e2022-02-10 18:05:45 +0100849def sanitize_pin_adm(pin_adm, pin_adm_hex=None) -> Hexstr:
850 """
851 The ADM pin can be supplied either in its hexadecimal form or as
852 ascii string. This function checks the supplied opts parameter and
853 returns the pin_adm as hex encoded string, regardless in which form
854 it was originally supplied by the user
855 """
Philipp Maiere8536c02020-05-11 21:35:01 +0200856
Harald Weltec91085e2022-02-10 18:05:45 +0100857 if pin_adm is not None:
858 if len(pin_adm) <= 8:
859 pin_adm = ''.join(['%02x' % (ord(x)) for x in pin_adm])
860 pin_adm = rpad(pin_adm, 16)
Philipp Maiere8536c02020-05-11 21:35:01 +0200861
Harald Weltec91085e2022-02-10 18:05:45 +0100862 else:
863 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
864
865 if pin_adm_hex is not None:
866 if len(pin_adm_hex) == 16:
867 pin_adm = pin_adm_hex
868 # Ensure that it's hex-encoded
869 try:
870 try_encode = h2b(pin_adm)
871 except ValueError:
872 raise ValueError(
873 "PIN-ADM needs to be hex encoded using this option")
874 else:
875 raise ValueError(
876 "PIN-ADM needs to be exactly 16 digits (hex encoded)")
877
878 return pin_adm
879
Philipp Maiere8536c02020-05-11 21:35:01 +0200880
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100881def get_addr_type(addr):
Harald Weltec91085e2022-02-10 18:05:45 +0100882 """
883 Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
884 Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given)
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100885
Harald Weltec91085e2022-02-10 18:05:45 +0100886 TODO: Handle IPv6
887 """
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100888
Harald Weltec91085e2022-02-10 18:05:45 +0100889 # Empty address string
890 if not len(addr):
891 return None
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100892
Harald Weltec91085e2022-02-10 18:05:45 +0100893 addr_list = addr.split('.')
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100894
Harald Weltec91085e2022-02-10 18:05:45 +0100895 # Check for IPv4/IPv6
896 try:
897 import ipaddress
898 # Throws ValueError if addr is not correct
899 ipa = ipaddress.ip_address(addr)
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100900
Harald Weltec91085e2022-02-10 18:05:45 +0100901 if ipa.version == 4:
902 return 0x01
903 elif ipa.version == 6:
904 return 0x02
905 except Exception as e:
906 invalid_ipv4 = True
907 for i in addr_list:
908 # Invalid IPv4 may qualify for a valid FQDN, so make check here
909 # e.g. 172.24.15.300
910 import re
911 if not re.match('^[0-9_]+$', i):
912 invalid_ipv4 = False
913 break
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100914
Harald Weltec91085e2022-02-10 18:05:45 +0100915 if invalid_ipv4:
916 return None
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100917
Harald Weltec91085e2022-02-10 18:05:45 +0100918 fqdn_flag = True
919 for i in addr_list:
920 # Only Alpha-numeric characters and hyphen - RFC 1035
921 import re
922 if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
923 fqdn_flag = False
924 break
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100925
Harald Weltec91085e2022-02-10 18:05:45 +0100926 # FQDN
927 if fqdn_flag:
928 return 0x00
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100929
Harald Weltec91085e2022-02-10 18:05:45 +0100930 return None
Harald Welte67d551a2021-01-21 14:50:01 +0100931
Philipp Maier5d3e2592021-02-22 17:22:16 +0100932
Harald Weltec91085e2022-02-10 18:05:45 +0100933def sw_match(sw: str, pattern: str) -> bool:
934 """Match given SW against given pattern."""
935 # Create a masked version of the returned status word
936 sw_lower = sw.lower()
937 sw_masked = ""
938 for i in range(0, 4):
939 if pattern[i] == '?':
940 sw_masked = sw_masked + '?'
941 elif pattern[i] == 'x':
942 sw_masked = sw_masked + 'x'
943 else:
944 sw_masked = sw_masked + sw_lower[i]
945 # Compare the masked version against the pattern
946 return sw_masked == pattern
Harald Welteee3501f2021-04-02 13:00:18 +0200947
Harald Weltec91085e2022-02-10 18:05:45 +0100948
949def tabulate_str_list(str_list, width: int = 79, hspace: int = 2, lspace: int = 1,
950 align_left: bool = True) -> str:
951 """Pretty print a list of strings into a tabulated form.
952
953 Args:
954 width : total width in characters per line
955 space : horizontal space between cells
956 lspace : number of spaces before row
957 align_lef : Align text to the left side
958 Returns:
959 multi-line string containing formatted table
960 """
961 if str_list == None:
962 return ""
963 if len(str_list) <= 0:
964 return ""
965 longest_str = max(str_list, key=len)
966 cellwith = len(longest_str) + hspace
967 cols = width // cellwith
968 rows = (len(str_list) - 1) // cols + 1
969 table = []
970 for i in iter(range(rows)):
971 str_list_row = str_list[i::rows]
972 if (align_left):
973 format_str_cell = '%%-%ds'
974 else:
975 format_str_cell = '%%%ds'
976 format_str_row = (format_str_cell % cellwith) * len(str_list_row)
977 format_str_row = (" " * lspace) + format_str_row
978 table.append(format_str_row % tuple(str_list_row))
979 return '\n'.join(table)
980
Harald Welte5e749a72021-04-10 17:18:17 +0200981
Harald Welte917d98c2021-04-21 11:51:25 +0200982def auto_int(x):
983 """Helper function for argparse to accept hexadecimal integers."""
984 return int(x, 0)
985
Harald Weltec91085e2022-02-10 18:05:45 +0100986
Philipp Maier40ea4a42022-06-02 14:45:41 +0200987def expand_hex(hexstring, length):
988 """Expand a given hexstring to a specified length by replacing "." or ".."
989 with a filler that is derived from the neighboring nibbles respective
990 bytes. Usually this will be the nibble respective byte before "." or
991 "..", execpt when the string begins with "." or "..", then the nibble
992 respective byte after "." or ".." is used.". In case the string cannot
993 be expanded for some reason, the input string is returned unmodified.
994
995 Args:
996 hexstring : hexstring to expand
997 length : desired length of the resulting hexstring.
998 Returns:
999 expanded hexstring
1000 """
1001
1002 # expand digit aligned
1003 if hexstring.count(".") == 1:
1004 pos = hexstring.index(".")
1005 if pos > 0:
1006 filler = hexstring[pos - 1]
1007 else:
1008 filler = hexstring[pos + 1]
1009
1010 missing = length * 2 - (len(hexstring) - 1)
1011 if missing <= 0:
1012 return hexstring
1013
1014 return hexstring.replace(".", filler * missing)
1015
1016 # expand byte aligned
1017 elif hexstring.count("..") == 1:
1018 if len(hexstring) % 2:
1019 return hexstring
1020
1021 pos = hexstring.index("..")
1022
1023 if pos % 2:
1024 return hexstring
1025
1026 if pos > 1:
1027 filler = hexstring[pos - 2:pos]
1028 else:
1029 filler = hexstring[pos + 2:pos+4]
1030
1031 missing = length * 2 - (len(hexstring) - 2)
1032 if missing <= 0:
1033 return hexstring
1034
1035 return hexstring.replace("..", filler * (missing // 2))
1036
1037 # no change
1038 return hexstring
1039
1040
Harald Welte5e749a72021-04-10 17:18:17 +02001041class JsonEncoder(json.JSONEncoder):
1042 """Extend the standard library JSONEncoder with support for more types."""
Harald Weltec91085e2022-02-10 18:05:45 +01001043
Harald Welte5e749a72021-04-10 17:18:17 +02001044 def default(self, o):
1045 if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
1046 return b2h(o)
1047 return json.JSONEncoder.default(self, o)
Philipp Maier80ce71f2021-04-19 21:24:23 +02001048
Harald Weltec91085e2022-02-10 18:05:45 +01001049
Philipp Maier80ce71f2021-04-19 21:24:23 +02001050def boxed_heading_str(heading, width=80):
Harald Weltec91085e2022-02-10 18:05:45 +01001051 """Generate a string that contains a boxed heading."""
1052 # Auto-enlarge box if heading exceeds length
1053 if len(heading) > width - 4:
1054 width = len(heading) + 4
Philipp Maier80ce71f2021-04-19 21:24:23 +02001055
Harald Weltec91085e2022-02-10 18:05:45 +01001056 res = "#" * width
1057 fstr = "\n# %-" + str(width - 4) + "s #\n"
1058 res += fstr % (heading)
1059 res += "#" * width
1060 return res
Harald Welte3de6ca22021-05-02 20:05:56 +02001061
1062
1063class DataObject(abc.ABC):
1064 """A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
1065 simply has any number of different TLVs that may occur in any order at any point, ISO 7816
1066 has the habit of specifying TLV data but with very spcific ordering, or specific choices of
1067 tags at specific points in a stream. This class tries to represent this."""
Harald Weltec91085e2022-02-10 18:05:45 +01001068
Harald Welte425038f2022-02-10 19:32:04 +01001069 def __init__(self, name: str, desc: Optional[str] = None, tag: Optional[int] = None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001070 """
1071 Args:
1072 name: A brief, all-lowercase, underscore separated string identifier
1073 desc: A human-readable description of what this DO represents
1074 tag : The tag associated with this DO
1075 """
1076 self.name = name
1077 self.desc = desc
1078 self.tag = tag
1079 self.decoded = None
1080 self.encoded = None
1081
1082 def __str__(self):
1083 return self.name
1084
Harald Welte50368772022-02-10 15:22:22 +01001085 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001086 return '%s(%s)' % (self.__class__, self.name)
1087
Harald Welte50368772022-02-10 15:22:22 +01001088 def __or__(self, other) -> 'DataObjectChoice':
Harald Welte3de6ca22021-05-02 20:05:56 +02001089 """OR-ing DataObjects together renders a DataObjectChoice."""
1090 if isinstance(other, DataObject):
1091 # DataObject | DataObject = DataObjectChoice
1092 return DataObjectChoice(None, members=[self, other])
1093 else:
1094 raise TypeError
1095
Harald Welte50368772022-02-10 15:22:22 +01001096 def __add__(self, other) -> 'DataObjectCollection':
Harald Welte3de6ca22021-05-02 20:05:56 +02001097 """ADD-ing DataObjects together renders a DataObjectCollection."""
1098 if isinstance(other, DataObject):
1099 # DataObject + DataObject = DataObjectCollectin
1100 return DataObjectCollection(None, members=[self, other])
Harald Welte50368772022-02-10 15:22:22 +01001101 else:
1102 raise TypeError
Harald Welte3de6ca22021-05-02 20:05:56 +02001103
Harald Welte50368772022-02-10 15:22:22 +01001104 def _compute_tag(self) -> int:
Harald Welte3de6ca22021-05-02 20:05:56 +02001105 """Compute the tag (sometimes the tag encodes part of the value)."""
1106 return self.tag
1107
Harald Welte50368772022-02-10 15:22:22 +01001108 def to_dict(self) -> dict:
Harald Welte3de6ca22021-05-02 20:05:56 +02001109 """Return a dict in form "name: decoded_value" """
1110 return {self.name: self.decoded}
1111
1112 @abc.abstractmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001113 def from_bytes(self, do: bytes):
Harald Welte3de6ca22021-05-02 20:05:56 +02001114 """Parse the value part of the DO into the internal state of this instance.
1115 Args:
1116 do : binary encoded bytes
1117 """
1118
1119 @abc.abstractmethod
Harald Welte50368772022-02-10 15:22:22 +01001120 def to_bytes(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001121 """Encode the internal state of this instance into the TLV value part.
1122 Returns:
1123 binary bytes encoding the internal state
1124 """
1125
Harald Weltec91085e2022-02-10 18:05:45 +01001126 def from_tlv(self, do: bytes) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001127 """Parse binary TLV representation into internal state. The resulting decoded
1128 representation is _not_ returned, but just internalized in the object instance!
1129 Args:
1130 do : input bytes containing TLV-encoded representation
1131 Returns:
1132 bytes remaining at end of 'do' after parsing one TLV/DO.
1133 """
1134 if do[0] != self.tag:
Harald Weltec91085e2022-02-10 18:05:45 +01001135 raise ValueError('%s: Can only decode tag 0x%02x' %
1136 (self, self.tag))
Harald Welte3de6ca22021-05-02 20:05:56 +02001137 length = do[1]
1138 val = do[2:2+length]
1139 self.from_bytes(val)
1140 # return remaining bytes
1141 return do[2+length:]
1142
Harald Welte50368772022-02-10 15:22:22 +01001143 def to_tlv(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001144 """Encode internal representation to binary TLV.
1145 Returns:
1146 bytes encoded in TLV format.
1147 """
1148 val = self.to_bytes()
Harald Welte785d4842022-04-05 14:24:22 +02001149 return bertlv_encode_tag(self._compute_tag()) + bertlv_encode_len(len(val)) + val
Harald Welte3de6ca22021-05-02 20:05:56 +02001150
1151 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001152 def decode(self, binary: bytes) -> Tuple[dict, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001153 """Decode a single DOs from the input data.
1154 Args:
1155 binary : binary bytes of encoded data
1156 Returns:
1157 tuple of (decoded_result, binary_remainder)
1158 """
1159 tag = binary[0]
1160 if tag != self.tag:
1161 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected 0x%02x' %
1162 (self, tag, binary, self.tag))
1163 remainder = self.from_tlv(binary)
1164 return (self.to_dict(), remainder)
1165
1166 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001167 def encode(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001168 return self.to_tlv()
1169
Harald Weltec91085e2022-02-10 18:05:45 +01001170
Harald Welte3de6ca22021-05-02 20:05:56 +02001171class TL0_DataObject(DataObject):
1172 """Data Object that has Tag, Len=0 and no Value part."""
Harald Weltec91085e2022-02-10 18:05:45 +01001173
1174 def __init__(self, name: str, desc: str, tag: int, val=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001175 super().__init__(name, desc, tag)
1176 self.val = val
1177
Harald Weltec91085e2022-02-10 18:05:45 +01001178 def from_bytes(self, binary: bytes):
Harald Welte3de6ca22021-05-02 20:05:56 +02001179 if len(binary) != 0:
1180 raise ValueError
1181 self.decoded = self.val
1182
Harald Welte50368772022-02-10 15:22:22 +01001183 def to_bytes(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001184 return b''
1185
1186
1187class DataObjectCollection:
1188 """A DataObjectCollection consits of multiple Data Objects identified by their tags.
1189 A given encoded DO may contain any of them in any order, and may contain multiple instances
1190 of each DO."""
Harald Weltec91085e2022-02-10 18:05:45 +01001191
Harald Welte425038f2022-02-10 19:32:04 +01001192 def __init__(self, name: str, desc: Optional[str] = None, members=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001193 self.name = name
1194 self.desc = desc
1195 self.members = members or []
1196 self.members_by_tag = {}
1197 self.members_by_name = {}
Harald Weltec91085e2022-02-10 18:05:45 +01001198 self.members_by_tag = {m.tag: m for m in members}
1199 self.members_by_name = {m.name: m for m in members}
Harald Welte3de6ca22021-05-02 20:05:56 +02001200
Harald Welte50368772022-02-10 15:22:22 +01001201 def __str__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001202 member_strs = [str(x) for x in self.members]
1203 return '%s(%s)' % (self.name, ','.join(member_strs))
1204
Harald Welte50368772022-02-10 15:22:22 +01001205 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001206 member_strs = [repr(x) for x in self.members]
1207 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1208
Harald Welte50368772022-02-10 15:22:22 +01001209 def __add__(self, other) -> 'DataObjectCollection':
Harald Welte3de6ca22021-05-02 20:05:56 +02001210 """Extending DataCollections with other DataCollections or DataObjects."""
1211 if isinstance(other, DataObjectCollection):
1212 # adding one collection to another
1213 members = self.members + other.members
1214 return DataObjectCollection(self.name, self.desc, members)
1215 elif isinstance(other, DataObject):
1216 # adding a member to a collection
1217 return DataObjectCollection(self.name, self.desc, self.members + [other])
1218 else:
1219 raise TypeError
1220
1221 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001222 def decode(self, binary: bytes) -> Tuple[List, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001223 """Decode any number of DOs from the collection until the end of the input data,
1224 or uninitialized memory (0xFF) is found.
1225 Args:
1226 binary : binary bytes of encoded data
1227 Returns:
1228 tuple of (decoded_result, binary_remainder)
1229 """
1230 res = []
1231 remainder = binary
1232 # iterate until no binary trailer is left
1233 while len(remainder):
1234 tag = remainder[0]
Harald Weltec91085e2022-02-10 18:05:45 +01001235 if tag == 0xff: # uninitialized memory at the end?
Harald Welte3de6ca22021-05-02 20:05:56 +02001236 return (res, remainder)
1237 if not tag in self.members_by_tag:
1238 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1239 (self, tag, remainder, self.members_by_tag.keys()))
1240 obj = self.members_by_tag[tag]
1241 # DO from_tlv returns remainder of binary
1242 remainder = obj.from_tlv(remainder)
1243 # collect our results
1244 res.append(obj.to_dict())
1245 return (res, remainder)
1246
1247 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001248 def encode(self, decoded) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001249 res = bytearray()
1250 for i in decoded:
1251 obj = self.members_by_name(i[0])
1252 res.append(obj.to_tlv())
1253 return res
1254
Harald Weltec91085e2022-02-10 18:05:45 +01001255
Harald Welte3de6ca22021-05-02 20:05:56 +02001256class DataObjectChoice(DataObjectCollection):
1257 """One Data Object from within a choice, identified by its tag.
1258 This means that exactly one member of the choice must occur, and which one occurs depends
1259 on the tag."""
Harald Weltec91085e2022-02-10 18:05:45 +01001260
Harald Welte3de6ca22021-05-02 20:05:56 +02001261 def __add__(self, other):
1262 """We overload the add operator here to avoid inheriting it from DataObjecCollection."""
1263 raise TypeError
1264
Harald Welte50368772022-02-10 15:22:22 +01001265 def __or__(self, other) -> 'DataObjectChoice':
Harald Welte3de6ca22021-05-02 20:05:56 +02001266 """OR-ing a Choice to another choice extends the choice, as does OR-ing a DataObject."""
1267 if isinstance(other, DataObjectChoice):
1268 # adding one collection to another
1269 members = self.members + other.members
1270 return DataObjectChoice(self.name, self.desc, members)
1271 elif isinstance(other, DataObject):
1272 # adding a member to a collection
1273 return DataObjectChoice(self.name, self.desc, self.members + [other])
1274 else:
1275 raise TypeError
1276
1277 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001278 def decode(self, binary: bytes) -> Tuple[dict, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001279 """Decode a single DOs from the choice based on the tag.
1280 Args:
1281 binary : binary bytes of encoded data
1282 Returns:
1283 tuple of (decoded_result, binary_remainder)
1284 """
1285 tag = binary[0]
1286 if tag == 0xff:
1287 return (None, binary)
1288 if not tag in self.members_by_tag:
1289 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1290 (self, tag, binary, self.members_by_tag.keys()))
1291 obj = self.members_by_tag[tag]
1292 remainder = obj.from_tlv(binary)
1293 return (obj.to_dict(), remainder)
1294
1295 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001296 def encode(self, decoded) -> bytes:
Harald Welte785d4842022-04-05 14:24:22 +02001297 obj = self.members_by_name[list(decoded)[0]]
1298 obj.decoded = list(decoded.values())[0]
Harald Welte3de6ca22021-05-02 20:05:56 +02001299 return obj.to_tlv()
1300
Harald Weltec91085e2022-02-10 18:05:45 +01001301
Harald Welte3de6ca22021-05-02 20:05:56 +02001302class DataObjectSequence:
1303 """A sequence of DataObjects or DataObjectChoices. This allows us to express a certain
1304 ordered sequence of DOs or choices of DOs that have to appear as per the specification.
1305 By wrapping them into this formal DataObjectSequence, we can offer convenience methods
1306 for encoding or decoding an entire sequence."""
Harald Weltec91085e2022-02-10 18:05:45 +01001307
Harald Welte425038f2022-02-10 19:32:04 +01001308 def __init__(self, name: str, desc: Optional[str] = None, sequence=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001309 self.sequence = sequence or []
1310 self.name = name
1311 self.desc = desc
1312
Harald Welte50368772022-02-10 15:22:22 +01001313 def __str__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001314 member_strs = [str(x) for x in self.sequence]
1315 return '%s(%s)' % (self.name, ','.join(member_strs))
1316
Harald Welte50368772022-02-10 15:22:22 +01001317 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001318 member_strs = [repr(x) for x in self.sequence]
1319 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1320
Harald Welte50368772022-02-10 15:22:22 +01001321 def __add__(self, other) -> 'DataObjectSequence':
Harald Welte3de6ca22021-05-02 20:05:56 +02001322 """Add (append) a DataObject or DataObjectChoice to the sequence."""
1323 if isinstance(other, 'DataObject'):
Harald Weltec91085e2022-02-10 18:05:45 +01001324 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
Harald Welte3de6ca22021-05-02 20:05:56 +02001325 elif isinstance(other, 'DataObjectChoice'):
Harald Weltec91085e2022-02-10 18:05:45 +01001326 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
Harald Welte3de6ca22021-05-02 20:05:56 +02001327 elif isinstance(other, 'DataObjectSequence'):
Harald Weltec91085e2022-02-10 18:05:45 +01001328 return DataObjectSequence(self.name, self.desc, self.sequence + other.sequence)
Harald Welte3de6ca22021-05-02 20:05:56 +02001329
1330 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001331 def decode(self, binary: bytes) -> Tuple[list, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001332 """Decode a sequence by calling the decoder of each element in the sequence.
1333 Args:
1334 binary : binary bytes of encoded data
1335 Returns:
1336 tuple of (decoded_result, binary_remainder)
1337 """
1338 remainder = binary
1339 res = []
1340 for e in self.sequence:
1341 (r, remainder) = e.decode(remainder)
1342 if r:
1343 res.append(r)
1344 return (res, remainder)
1345
1346 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001347 def decode_multi(self, do: bytes) -> Tuple[list, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001348 """Decode multiple occurrences of the sequence from the binary input data.
1349 Args:
1350 do : binary input data to be decoded
1351 Returns:
1352 list of results of the decoder of this sequences
1353 """
1354 remainder = do
1355 res = []
1356 while len(remainder):
1357 (r, remainder2) = self.decode(remainder)
1358 if r:
1359 res.append(r)
1360 if len(remainder2) < len(remainder):
1361 remainder = remainder2
1362 else:
1363 remainder = remainder2
1364 break
1365 return (res, remainder)
1366
1367 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001368 def encode(self, decoded) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001369 """Encode a sequence by calling the encoder of each element in the sequence."""
1370 encoded = bytearray()
1371 i = 0
1372 for e in self.sequence:
1373 encoded += e.encode(decoded[i])
1374 i += 1
1375 return encoded
Harald Welte90441432021-05-02 21:28:12 +02001376
Harald Welte0dcdfbf2022-04-05 14:42:48 +02001377 def encode_multi(self, decoded) -> bytes:
1378 """Encode multiple occurrences of the sequence from the decoded input data.
1379 Args:
1380 decoded : list of json-serializable input data; one sequence per list item
1381 Returns:
1382 binary encoded output data
1383 """
1384 encoded = bytearray()
1385 for d in decoded:
1386 encoded += self.encode(d)
1387 return encoded
1388
Harald Weltec91085e2022-02-10 18:05:45 +01001389
Harald Welte90441432021-05-02 21:28:12 +02001390class CardCommand:
1391 """A single card command / instruction."""
Harald Weltec91085e2022-02-10 18:05:45 +01001392
Harald Welte90441432021-05-02 21:28:12 +02001393 def __init__(self, name, ins, cla_list=None, desc=None):
1394 self.name = name
1395 self.ins = ins
1396 self.cla_list = cla_list or []
1397 self.cla_list = [x.lower() for x in self.cla_list]
1398 self.desc = desc
1399
1400 def __str__(self):
1401 return self.name
1402
1403 def __repr__(self):
1404 return '%s(INS=%02x,CLA=%s)' % (self.name, self.ins, self.cla_list)
1405
1406 def match_cla(self, cla):
1407 """Does the given CLA match the CLA list of the command?."""
1408 if not isinstance(cla, str):
1409 cla = '%02u' % cla
1410 cla = cla.lower()
1411 for cla_match in self.cla_list:
1412 cla_masked = ""
1413 for i in range(0, 2):
1414 if cla_match[i] == 'x':
1415 cla_masked += 'x'
1416 else:
1417 cla_masked += cla[i]
1418 if cla_masked == cla_match:
1419 return True
1420 return False
1421
1422
1423class CardCommandSet:
1424 """A set of card instructions, typically specified within one spec."""
Harald Weltec91085e2022-02-10 18:05:45 +01001425
Harald Welte90441432021-05-02 21:28:12 +02001426 def __init__(self, name, cmds=[]):
1427 self.name = name
Harald Weltec91085e2022-02-10 18:05:45 +01001428 self.cmds = {c.ins: c for c in cmds}
Harald Welte90441432021-05-02 21:28:12 +02001429
1430 def __str__(self):
1431 return self.name
1432
1433 def __getitem__(self, idx):
1434 return self.cmds[idx]
1435
1436 def __add__(self, other):
1437 if isinstance(other, CardCommand):
1438 if other.ins in self.cmds:
1439 raise ValueError('%s: INS 0x%02x already defined: %s' %
1440 (self, other.ins, self.cmds[other.ins]))
1441 self.cmds[other.ins] = other
1442 elif isinstance(other, CardCommandSet):
1443 for c in other.cmds.keys():
1444 self.cmds[c] = other.cmds[c]
1445 else:
Harald Weltec91085e2022-02-10 18:05:45 +01001446 raise ValueError(
1447 '%s: Unsupported type to add operator: %s' % (self, other))
Harald Welte90441432021-05-02 21:28:12 +02001448
1449 def lookup(self, ins, cla=None):
1450 """look-up the command within the CommandSet."""
1451 ins = int(ins)
1452 if not ins in self.cmds:
1453 return None
1454 cmd = self.cmds[ins]
1455 if cla and not cmd.match_cla(cla):
1456 return None
1457 return cmd
Philipp Maiera028c7d2021-11-08 16:12:03 +01001458
Harald Weltec91085e2022-02-10 18:05:45 +01001459
Philipp Maiera028c7d2021-11-08 16:12:03 +01001460def all_subclasses(cls) -> set:
Harald Weltec91085e2022-02-10 18:05:45 +01001461 """Recursively get all subclasses of a specified class"""
1462 return set(cls.__subclasses__()).union([s for c in cls.__subclasses__() for s in all_subclasses(c)])