blob: 7459a3f43f618038dae27fdd6930da4b6051dcca [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
Harald Welte10669f22023-10-23 01:54:36 +0200265 def count_int_bytes(inp: int) -> int:
266 """count the number of bytes require to represent the given integer."""
267 i = 1
268 inp = inp >> 8
269 while inp:
270 i += 1
271 inp = inp >> 8
272 return i
273
Harald Weltef0885b12021-05-24 23:18:59 +0200274 if isinstance(t, int):
Harald Welte10669f22023-10-23 01:54:36 +0200275 # first convert to a dict representation
276 tag_size = count_int_bytes(t)
277 t, remainder = bertlv_parse_tag(t.to_bytes(tag_size, 'big'))
278 tag = t['tag']
279 constructed = t['constructed']
280 cls = t['class']
Harald Weltef0885b12021-05-24 23:18:59 +0200281 if tag <= 30:
282 t = tag & 0x1f
283 if constructed:
284 t |= 0x20
285 t |= (cls & 3) << 6
286 return bytes([t])
Harald Weltec91085e2022-02-10 18:05:45 +0100287 else: # multi-byte tag
Vadim Yanitskiydbd5ed62021-11-05 16:20:52 +0300288 t = 0x1f
Harald Weltef0885b12021-05-24 23:18:59 +0200289 if constructed:
290 t |= 0x20
291 t |= (cls & 3) << 6
292 tag_bytes = bytes([t])
293 remain = tag
294 while True:
295 t, remain = get_top7_bits(remain)
296 if remain:
297 t |= 0x80
298 tag_bytes += bytes([t])
299 if not remain:
300 break
301 return tag_bytes
302
Harald Welte917d98c2021-04-21 11:51:25 +0200303
Harald Weltec91085e2022-02-10 18:05:45 +0100304def bertlv_parse_len(binary: bytes) -> Tuple[int, bytes]:
305 """Parse a single Length value according to ITU-T X.690 8.1.3;
306 only the definite form is supported here.
307 Args:
308 binary : binary input data of BER-TLV length field
309 Returns:
310 Tuple of (length, remainder)
311 """
312 if binary[0] < 0x80:
313 return (binary[0], binary[1:])
314 else:
315 num_len_oct = binary[0] & 0x7f
316 length = 0
317 for i in range(1, 1+num_len_oct):
318 length <<= 8
319 length |= binary[i]
320 return (length, binary[1+num_len_oct:])
Harald Welte917d98c2021-04-21 11:51:25 +0200321
Harald Welte917d98c2021-04-21 11:51:25 +0200322
Harald Weltec91085e2022-02-10 18:05:45 +0100323def bertlv_encode_len(length: int) -> bytes:
324 """Encode a single Length value according to ITU-T X.690 8.1.3;
325 only the definite form is supported here.
326 Args:
327 length : length value to be encoded
328 Returns:
329 binary output data of BER-TLV length field
330 """
331 if length < 0x80:
332 return length.to_bytes(1, 'big')
333 elif length <= 0xff:
334 return b'\x81' + length.to_bytes(1, 'big')
335 elif length <= 0xffff:
336 return b'\x82' + length.to_bytes(2, 'big')
337 elif length <= 0xffffff:
338 return b'\x83' + length.to_bytes(3, 'big')
339 elif length <= 0xffffffff:
340 return b'\x84' + length.to_bytes(4, 'big')
341 else:
342 raise ValueError("Length > 32bits not supported")
343
344
345def bertlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]:
346 """Parse a single TLV IE at the start of the given binary data.
347 Args:
348 binary : binary input data of BER-TLV length field
349 Returns:
350 Tuple of (tag:dict, len:int, remainder:bytes)
351 """
352 (tagdict, remainder) = bertlv_parse_tag(binary)
353 (length, remainder) = bertlv_parse_len(remainder)
354 value = remainder[:length]
355 remainder = remainder[length:]
356 return (tagdict, length, value, remainder)
Harald Welte917d98c2021-04-21 11:51:25 +0200357
358
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200359# IMSI encoded format:
360# For IMSI 0123456789ABCDE:
361#
362# | byte 1 | 2 upper | 2 lower | 3 upper | 3 lower | ... | 9 upper | 9 lower |
363# | length in bytes | 0 | odd/even | 2 | 1 | ... | E | D |
364#
365# If the IMSI is less than 15 characters, it should be padded with 'f' from the end.
366#
367# The length is the total number of bytes used to encoded the IMSI. This includes the odd/even
368# parity bit. E.g. an IMSI of length 14 is 8 bytes long, not 7, as it uses bytes 2 to 9 to
369# encode itself.
370#
371# Because of this, an odd length IMSI fits exactly into len(imsi) + 1 // 2 bytes, whereas an
372# even length IMSI only uses half of the last byte.
373
Harald Weltec91085e2022-02-10 18:05:45 +0100374def enc_imsi(imsi: str):
375 """Converts a string IMSI into the encoded value of the EF"""
376 l = half_round_up(
377 len(imsi) + 1) # Required bytes - include space for odd/even indicator
378 oe = len(imsi) & 1 # Odd (1) / Even (0)
379 ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe << 3) | 1, rpad(imsi, 15)))
380 return ei
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400381
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400382
Harald Weltec91085e2022-02-10 18:05:45 +0100383def dec_imsi(ef: Hexstr) -> Optional[str]:
384 """Converts an EF value to the IMSI string representation"""
385 if len(ef) < 4:
386 return None
387 l = int(ef[0:2], 16) * 2 # Length of the IMSI string
388 l = l - 1 # Encoded length byte includes oe nibble
389 swapped = swap_nibbles(ef[2:]).rstrip('f')
390 if len(swapped) < 1:
391 return None
392 oe = (int(swapped[0]) >> 3) & 1 # Odd (1) / Even (0)
393 if not oe:
394 # if even, only half of last byte was used
395 l = l-1
396 if l != len(swapped) - 1:
397 return None
398 imsi = swapped[1:]
399 return imsi
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400400
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400401
Harald Weltec91085e2022-02-10 18:05:45 +0100402def dec_iccid(ef: Hexstr) -> str:
403 return swap_nibbles(ef).strip('f')
Philipp Maier6c5cd802021-04-23 21:19:36 +0200404
Philipp Maier6c5cd802021-04-23 21:19:36 +0200405
Harald Weltec91085e2022-02-10 18:05:45 +0100406def enc_iccid(iccid: str) -> Hexstr:
407 return swap_nibbles(rpad(iccid, 20))
Philipp Maier6c5cd802021-04-23 21:19:36 +0200408
Philipp Maier6c5cd802021-04-23 21:19:36 +0200409
Harald Weltec91085e2022-02-10 18:05:45 +0100410def enc_plmn(mcc: Hexstr, mnc: Hexstr) -> Hexstr:
411 """Converts integer MCC/MNC into 3 bytes for EF"""
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300412
Harald Weltec91085e2022-02-10 18:05:45 +0100413 # Make sure there are no excess whitespaces in the input
414 # parameters
415 mcc = mcc.strip()
416 mnc = mnc.strip()
417
418 # Make sure that MCC/MNC are correctly padded with leading
419 # zeros or 'F', depending on the length.
420 if len(mnc) == 0:
421 mnc = "FFF"
422 elif len(mnc) == 1:
farhadhfec721f2023-07-19 15:43:13 +0200423 mnc = "0" + mnc + "F"
Harald Weltec91085e2022-02-10 18:05:45 +0100424 elif len(mnc) == 2:
425 mnc += "F"
426
427 if len(mcc) == 0:
428 mcc = "FFF"
429 elif len(mcc) == 1:
430 mcc = "00" + mcc
431 elif len(mcc) == 2:
432 mcc = "0" + mcc
433
434 return (mcc[1] + mcc[0]) + (mnc[2] + mcc[2]) + (mnc[1] + mnc[0])
435
436
437def dec_plmn(threehexbytes: Hexstr) -> dict:
438 res = {'mcc': "0", 'mnc': "0"}
439 dec_mcc_from_plmn_str(threehexbytes)
440 res['mcc'] = dec_mcc_from_plmn_str(threehexbytes)
441 res['mnc'] = dec_mnc_from_plmn_str(threehexbytes)
442 return res
443
Harald Welted7a7e172021-04-07 00:30:10 +0200444
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100445# Accepts hex string representing three bytes
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100446
Philipp Maier6c5cd802021-04-23 21:19:36 +0200447
Harald Weltec91085e2022-02-10 18:05:45 +0100448def dec_mcc_from_plmn(plmn: Hexstr) -> int:
449 ia = h2i(plmn)
450 digit1 = ia[0] & 0x0F # 1st byte, LSB
451 digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB
452 digit3 = ia[1] & 0x0F # 2nd byte, LSB
453 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
454 return 0xFFF # 4095
455 return derive_mcc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100456
Philipp Maier6c5cd802021-04-23 21:19:36 +0200457
Harald Weltec91085e2022-02-10 18:05:45 +0100458def dec_mcc_from_plmn_str(plmn: Hexstr) -> str:
459 digit1 = plmn[1] # 1st byte, LSB
460 digit2 = plmn[0] # 1st byte, MSB
461 digit3 = plmn[3] # 2nd byte, LSB
462 res = digit1 + digit2 + digit3
463 return res.upper().strip("F")
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100464
Harald Weltec91085e2022-02-10 18:05:45 +0100465
466def dec_mnc_from_plmn(plmn: Hexstr) -> int:
467 ia = h2i(plmn)
468 digit1 = ia[2] & 0x0F # 3rd byte, LSB
469 digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB
470 digit3 = (ia[1] & 0xF0) >> 4 # 2nd byte, MSB
471 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
472 return 0xFFF # 4095
473 return derive_mnc(digit1, digit2, digit3)
474
475
476def dec_mnc_from_plmn_str(plmn: Hexstr) -> str:
477 digit1 = plmn[5] # 3rd byte, LSB
478 digit2 = plmn[4] # 3rd byte, MSB
479 digit3 = plmn[2] # 2nd byte, MSB
480 res = digit1 + digit2 + digit3
481 return res.upper().strip("F")
482
483
484def dec_act(twohexbytes: Hexstr) -> List[str]:
485 act_list = [
486 {'bit': 15, 'name': "UTRAN"},
487 {'bit': 14, 'name': "E-UTRAN"},
488 {'bit': 11, 'name': "NG-RAN"},
489 {'bit': 7, 'name': "GSM"},
490 {'bit': 6, 'name': "GSM COMPACT"},
491 {'bit': 5, 'name': "cdma2000 HRPD"},
492 {'bit': 4, 'name': "cdma2000 1xRTT"},
493 ]
494 ia = h2i(twohexbytes)
495 u16t = (ia[0] << 8) | ia[1]
496 sel = []
497 for a in act_list:
498 if u16t & (1 << a['bit']):
499 if a['name'] == "E-UTRAN":
500 # The Access technology identifier of E-UTRAN
501 # allows a more detailed specification:
502 if u16t & (1 << 13) and u16t & (1 << 12):
503 sel.append("E-UTRAN WB-S1")
504 sel.append("E-UTRAN NB-S1")
505 elif u16t & (1 << 13):
506 sel.append("E-UTRAN WB-S1")
507 elif u16t & (1 << 12):
508 sel.append("E-UTRAN NB-S1")
509 else:
510 sel.append("E-UTRAN")
511 else:
512 sel.append(a['name'])
513 return sel
514
515
516def dec_xplmn_w_act(fivehexbytes: Hexstr) -> Dict[str, Any]:
517 res = {'mcc': "0", 'mnc': "0", 'act': []}
518 plmn_chars = 6
519 act_chars = 4
520 # first three bytes (six ascii hex chars)
521 plmn_str = fivehexbytes[:plmn_chars]
522 # two bytes after first three bytes
523 act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars]
524 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
525 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
526 res['act'] = dec_act(act_str)
527 return res
528
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100529
Harald Weltec91085e2022-02-10 18:05:45 +0100530def dec_xplmn(threehexbytes: Hexstr) -> dict:
531 res = {'mcc': 0, 'mnc': 0, 'act': []}
532 plmn_chars = 6
533 # first three bytes (six ascii hex chars)
534 plmn_str = threehexbytes[:plmn_chars]
Matan Perelman60951b02023-06-01 17:39:04 +0300535 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
536 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
Harald Weltec91085e2022-02-10 18:05:45 +0100537 return res
Harald Welteca673942020-06-03 15:19:40 +0200538
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900539
Harald Weltec91085e2022-02-10 18:05:45 +0100540def derive_milenage_opc(ki_hex: Hexstr, op_hex: Hexstr) -> Hexstr:
541 """
542 Run the milenage algorithm to calculate OPC from Ki and OP
543 """
Harald Welted75fa3f2023-05-31 20:47:55 +0200544 from Cryptodome.Cipher import AES
Harald Weltec91085e2022-02-10 18:05:45 +0100545 # pylint: disable=no-name-in-module
Harald Welted75fa3f2023-05-31 20:47:55 +0200546 from Cryptodome.Util.strxor import strxor
Harald Weltec91085e2022-02-10 18:05:45 +0100547
548 # We pass in hex string and now need to work on bytes
549 ki_bytes = bytes(h2b(ki_hex))
550 op_bytes = bytes(h2b(op_hex))
551 aes = AES.new(ki_bytes, AES.MODE_ECB)
552 opc_bytes = aes.encrypt(op_bytes)
553 return b2h(strxor(opc_bytes, op_bytes))
554
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900555
Harald Welte52255572021-04-03 09:56:32 +0200556def calculate_luhn(cc) -> int:
Harald Weltec91085e2022-02-10 18:05:45 +0100557 """
558 Calculate Luhn checksum used in e.g. ICCID and IMEI
559 """
560 num = list(map(int, str(cc)))
561 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10))
562 for d in num[::-2]]) % 10
563 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200564
Philipp Maier7592eee2019-09-12 13:03:23 +0200565
Harald Weltec91085e2022-02-10 18:05:45 +0100566def mcc_from_imsi(imsi: str) -> Optional[str]:
567 """
568 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
569 """
570 if imsi == None:
571 return None
Philipp Maier7592eee2019-09-12 13:03:23 +0200572
Harald Weltec91085e2022-02-10 18:05:45 +0100573 if len(imsi) > 3:
574 return imsi[:3]
575 else:
576 return None
Philipp Maier7592eee2019-09-12 13:03:23 +0200577
Supreeth Herled24f1632019-11-30 10:37:09 +0100578
Harald Weltec91085e2022-02-10 18:05:45 +0100579def mnc_from_imsi(imsi: str, long: bool = False) -> Optional[str]:
580 """
581 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
582 """
583 if imsi == None:
584 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100585
Harald Weltec91085e2022-02-10 18:05:45 +0100586 if len(imsi) > 3:
587 if long:
588 return imsi[3:6]
589 else:
590 return imsi[3:5]
591 else:
592 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100593
Supreeth Herled24f1632019-11-30 10:37:09 +0100594
Harald Weltec91085e2022-02-10 18:05:45 +0100595def derive_mcc(digit1: int, digit2: int, digit3: int) -> int:
596 """
597 Derive decimal representation of the MCC (Mobile Country Code)
598 from three given digits.
599 """
Supreeth Herled24f1632019-11-30 10:37:09 +0100600
Harald Weltec91085e2022-02-10 18:05:45 +0100601 mcc = 0
Supreeth Herled24f1632019-11-30 10:37:09 +0100602
Harald Weltec91085e2022-02-10 18:05:45 +0100603 if digit1 != 0x0f:
604 mcc += digit1 * 100
605 if digit2 != 0x0f:
606 mcc += digit2 * 10
607 if digit3 != 0x0f:
608 mcc += digit3
Supreeth Herled24f1632019-11-30 10:37:09 +0100609
Harald Weltec91085e2022-02-10 18:05:45 +0100610 return mcc
Supreeth Herled24f1632019-11-30 10:37:09 +0100611
Supreeth Herled24f1632019-11-30 10:37:09 +0100612
Harald Weltec91085e2022-02-10 18:05:45 +0100613def derive_mnc(digit1: int, digit2: int, digit3: int = 0x0f) -> int:
614 """
615 Derive decimal representation of the MNC (Mobile Network Code)
616 from two or (optionally) three given digits.
617 """
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100618
Harald Weltec91085e2022-02-10 18:05:45 +0100619 mnc = 0
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100620
Harald Weltec91085e2022-02-10 18:05:45 +0100621 # 3-rd digit is optional for the MNC. If present
622 # the algorythm is the same as for the MCC.
623 if digit3 != 0x0f:
624 return derive_mcc(digit1, digit2, digit3)
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100625
Harald Weltec91085e2022-02-10 18:05:45 +0100626 if digit1 != 0x0f:
627 mnc += digit1 * 10
628 if digit2 != 0x0f:
629 mnc += digit2
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100630
Harald Weltec91085e2022-02-10 18:05:45 +0100631 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100632
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100633
Harald Weltec91085e2022-02-10 18:05:45 +0100634def dec_msisdn(ef_msisdn: Hexstr) -> Optional[Tuple[int, int, Optional[str]]]:
635 """
636 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
637 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
638 """
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100639
Harald Weltec91085e2022-02-10 18:05:45 +0100640 # Convert from str to (kind of) 'bytes'
641 ef_msisdn = h2b(ef_msisdn)
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100642
Harald Weltec91085e2022-02-10 18:05:45 +0100643 # Make sure mandatory fields are present
644 if len(ef_msisdn) < 14:
645 raise ValueError("EF.MSISDN is too short")
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100646
Harald Weltec91085e2022-02-10 18:05:45 +0100647 # Skip optional Alpha Identifier
648 xlen = len(ef_msisdn) - 14
649 msisdn_lhv = ef_msisdn[xlen:]
Supreeth Herle5a541012019-12-22 08:59:16 +0100650
Harald Weltec91085e2022-02-10 18:05:45 +0100651 # Parse the length (in bytes) of the BCD encoded number
652 bcd_len = msisdn_lhv[0]
653 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
654 if bcd_len == 0xff:
655 return None
656 elif bcd_len > 11 or bcd_len < 1:
657 raise ValueError(
658 "Length of MSISDN (%d bytes) is out of range" % bcd_len)
Supreeth Herle5a541012019-12-22 08:59:16 +0100659
Harald Weltec91085e2022-02-10 18:05:45 +0100660 # Parse ToN / NPI
661 ton = (msisdn_lhv[1] >> 4) & 0x07
662 npi = msisdn_lhv[1] & 0x0f
663 bcd_len -= 1
Supreeth Herle5a541012019-12-22 08:59:16 +0100664
Harald Weltec91085e2022-02-10 18:05:45 +0100665 # No MSISDN?
666 if not bcd_len:
667 return (npi, ton, None)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200668
Harald Weltec91085e2022-02-10 18:05:45 +0100669 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
670 # International number 10.5.118/3GPP TS 24.008
671 if ton == 0x01:
672 msisdn = '+' + msisdn
Supreeth Herle5a541012019-12-22 08:59:16 +0100673
Harald Weltec91085e2022-02-10 18:05:45 +0100674 return (npi, ton, msisdn)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200675
Supreeth Herle5a541012019-12-22 08:59:16 +0100676
Harald Weltec91085e2022-02-10 18:05:45 +0100677def enc_msisdn(msisdn: str, npi: int = 0x01, ton: int = 0x03) -> Hexstr:
678 """
679 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
680 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
681 will not contain the optional Alpha Identifier at the beginning.)
Supreeth Herle5a541012019-12-22 08:59:16 +0100682
Harald Weltec91085e2022-02-10 18:05:45 +0100683 Default NPI / ToN values:
684 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
685 - ToN: network specific or international number (if starts with '+').
686 """
687
688 # If no MSISDN is supplied then encode the file contents as all "ff"
689 if msisdn == "" or msisdn == "+":
690 return "ff" * 14
691
692 # Leading '+' indicates International Number
693 if msisdn[0] == '+':
694 msisdn = msisdn[1:]
695 ton = 0x01
696
697 # An MSISDN must not exceed 20 digits
698 if len(msisdn) > 20:
699 raise ValueError("msisdn must not be longer than 20 digits")
700
701 # Append 'f' padding if number of digits is odd
702 if len(msisdn) % 2 > 0:
703 msisdn += 'f'
704
705 # BCD length also includes NPI/ToN header
706 bcd_len = len(msisdn) // 2 + 1
707 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
708 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
709
710 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200711
Supreeth Herle441c4a72020-03-24 10:19:15 +0100712
Supreeth Herle98370552020-05-11 09:04:41 +0200713def first_TLV_parser(bytelist):
Harald Weltec91085e2022-02-10 18:05:45 +0100714 '''
715 first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
Supreeth Herle98370552020-05-11 09:04:41 +0200716
Harald Weltec91085e2022-02-10 18:05:45 +0100717 parses first TLV format record in a list of bytelist
718 returns a 3-Tuple: Tag, Length, Value
719 Value is a list of bytes
720 parsing of length is ETSI'style 101.220
721 '''
722 Tag = bytelist[0]
723 if bytelist[1] == 0xFF:
724 Len = bytelist[2]*256 + bytelist[3]
725 Val = bytelist[4:4+Len]
726 else:
727 Len = bytelist[1]
728 Val = bytelist[2:2+Len]
729 return (Tag, Len, Val)
730
Supreeth Herle98370552020-05-11 09:04:41 +0200731
732def TLV_parser(bytelist):
Harald Weltec91085e2022-02-10 18:05:45 +0100733 '''
734 TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
Supreeth Herle98370552020-05-11 09:04:41 +0200735
Harald Weltec91085e2022-02-10 18:05:45 +0100736 loops on the input list of bytes with the "first_TLV_parser()" function
737 returns a list of 3-Tuples
738 '''
739 ret = []
740 while len(bytelist) > 0:
741 T, L, V = first_TLV_parser(bytelist)
742 if T == 0xFF:
743 # padding bytes
744 break
745 ret.append((T, L, V))
746 # need to manage length of L
747 if L > 0xFE:
748 bytelist = bytelist[L+4:]
749 else:
750 bytelist = bytelist[L+2:]
751 return ret
752
Supreeth Herled572ede2020-03-22 09:55:04 +0100753
Supreeth Herle3b342c22020-03-24 16:15:02 +0100754def dec_addr_tlv(hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100755 """
756 Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm.
757 See 3GPP TS 31.102 version 13.4.0 Release 13, section 4.2.8, 4.2.102 and 4.2.104.
758 """
Supreeth Herled572ede2020-03-22 09:55:04 +0100759
Harald Weltec91085e2022-02-10 18:05:45 +0100760 # Convert from hex str to int bytes list
761 addr_tlv_bytes = h2i(hexstr)
Supreeth Herled572ede2020-03-22 09:55:04 +0100762
Harald Weltec91085e2022-02-10 18:05:45 +0100763 # Get list of tuples containing parsed TLVs
764 tlvs = TLV_parser(addr_tlv_bytes)
Supreeth Herled572ede2020-03-22 09:55:04 +0100765
Harald Weltec91085e2022-02-10 18:05:45 +0100766 for tlv in tlvs:
767 # tlv = (T, L, [V])
768 # T = Tag
769 # L = Length
770 # [V] = List of value
Supreeth Herled572ede2020-03-22 09:55:04 +0100771
Harald Weltec91085e2022-02-10 18:05:45 +0100772 # Invalid Tag value scenario
773 if tlv[0] != 0x80:
774 continue
Supreeth Herled572ede2020-03-22 09:55:04 +0100775
Harald Weltec91085e2022-02-10 18:05:45 +0100776 # Empty field - Zero length
777 if tlv[1] == 0:
778 continue
Supreeth Herled6a5ec52020-06-01 12:27:51 +0200779
Philipp Maier1f46f072023-07-27 10:38:38 +0200780 # Uninitialized field
781 if all([v == 0xff for v in tlv[2]]):
782 continue
783
Harald Weltec91085e2022-02-10 18:05:45 +0100784 # First byte in the value has the address type
785 addr_type = tlv[2][0]
786 # TODO: Support parsing of IPv6
787 # Address Type: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), other (Reserved)
788 if addr_type == 0x00: # FQDN
789 # Skip address tye byte i.e. first byte in value list
790 content = tlv[2][1:]
791 return (i2s(content), '00')
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200792
Harald Weltec91085e2022-02-10 18:05:45 +0100793 elif addr_type == 0x01: # IPv4
794 # Skip address tye byte i.e. first byte in value list
795 # Skip the unused byte in Octect 4 after address type byte as per 3GPP TS 31.102
796 ipv4 = tlv[2][2:]
797 content = '.'.join(str(x) for x in ipv4)
798 return (content, '01')
799 else:
800 raise ValueError("Invalid address type")
Supreeth Herled572ede2020-03-22 09:55:04 +0100801
Harald Weltec91085e2022-02-10 18:05:45 +0100802 return (None, None)
803
Philipp Maierff84c232020-05-12 17:24:18 +0200804
Supreeth Herle3b342c22020-03-24 16:15:02 +0100805def enc_addr_tlv(addr, addr_type='00'):
Harald Weltec91085e2022-02-10 18:05:45 +0100806 """
807 Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm.
808 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 +0100809
Harald Weltec91085e2022-02-10 18:05:45 +0100810 Default values:
811 - addr_type: 00 - FQDN format of Address
812 """
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100813
Harald Weltec91085e2022-02-10 18:05:45 +0100814 s = ""
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100815
Harald Weltec91085e2022-02-10 18:05:45 +0100816 # TODO: Encoding of IPv6 address
817 if addr_type == '00': # FQDN
818 hex_str = s2h(addr)
819 s += '80' + ('%02x' % ((len(hex_str)//2)+1)) + '00' + hex_str
820 elif addr_type == '01': # IPv4
821 ipv4_list = addr.split('.')
822 ipv4_str = ""
823 for i in ipv4_list:
824 ipv4_str += ('%02x' % (int(i)))
Supreeth Herle654eca72020-03-25 14:25:38 +0100825
Harald Weltec91085e2022-02-10 18:05:45 +0100826 # Unused bytes shall be set to 'ff'. i.e 4th Octet after Address Type is not used
827 # IPv4 Address is in octet 5 to octet 8 of the TLV data object
828 s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100829
Harald Weltec91085e2022-02-10 18:05:45 +0100830 return s
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100831
Philipp Maier47236502021-03-09 21:28:25 +0100832
Harald Weltec91085e2022-02-10 18:05:45 +0100833def is_hex(string: str, minlen: int = 2, maxlen: Optional[int] = None) -> bool:
834 """
835 Check if a string is a valid hexstring
836 """
Philipp Maier47236502021-03-09 21:28:25 +0100837
Harald Weltec91085e2022-02-10 18:05:45 +0100838 # Filter obviously bad strings
839 if not string:
840 return False
841 if len(string) < minlen or minlen < 2:
842 return False
843 if len(string) % 2:
844 return False
845 if maxlen and len(string) > maxlen:
846 return False
Philipp Maier47236502021-03-09 21:28:25 +0100847
Harald Weltec91085e2022-02-10 18:05:45 +0100848 # Try actual encoding to be sure
849 try:
850 try_encode = h2b(string)
851 return True
852 except:
853 return False
Philipp Maiere8536c02020-05-11 21:35:01 +0200854
Philipp Maiere8536c02020-05-11 21:35:01 +0200855
Harald Weltec91085e2022-02-10 18:05:45 +0100856def sanitize_pin_adm(pin_adm, pin_adm_hex=None) -> Hexstr:
857 """
858 The ADM pin can be supplied either in its hexadecimal form or as
859 ascii string. This function checks the supplied opts parameter and
860 returns the pin_adm as hex encoded string, regardless in which form
861 it was originally supplied by the user
862 """
Philipp Maiere8536c02020-05-11 21:35:01 +0200863
Harald Weltec91085e2022-02-10 18:05:45 +0100864 if pin_adm is not None:
865 if len(pin_adm) <= 8:
866 pin_adm = ''.join(['%02x' % (ord(x)) for x in pin_adm])
867 pin_adm = rpad(pin_adm, 16)
Philipp Maiere8536c02020-05-11 21:35:01 +0200868
Harald Weltec91085e2022-02-10 18:05:45 +0100869 else:
870 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
871
872 if pin_adm_hex is not None:
873 if len(pin_adm_hex) == 16:
874 pin_adm = pin_adm_hex
875 # Ensure that it's hex-encoded
876 try:
877 try_encode = h2b(pin_adm)
878 except ValueError:
879 raise ValueError(
880 "PIN-ADM needs to be hex encoded using this option")
881 else:
882 raise ValueError(
883 "PIN-ADM needs to be exactly 16 digits (hex encoded)")
884
885 return pin_adm
886
Philipp Maiere8536c02020-05-11 21:35:01 +0200887
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100888def get_addr_type(addr):
Harald Weltec91085e2022-02-10 18:05:45 +0100889 """
890 Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
891 Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given)
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100892
Harald Weltec91085e2022-02-10 18:05:45 +0100893 TODO: Handle IPv6
894 """
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100895
Harald Weltec91085e2022-02-10 18:05:45 +0100896 # Empty address string
897 if not len(addr):
898 return None
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100899
Harald Weltec91085e2022-02-10 18:05:45 +0100900 addr_list = addr.split('.')
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100901
Harald Weltec91085e2022-02-10 18:05:45 +0100902 # Check for IPv4/IPv6
903 try:
904 import ipaddress
905 # Throws ValueError if addr is not correct
906 ipa = ipaddress.ip_address(addr)
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100907
Harald Weltec91085e2022-02-10 18:05:45 +0100908 if ipa.version == 4:
909 return 0x01
910 elif ipa.version == 6:
911 return 0x02
912 except Exception as e:
913 invalid_ipv4 = True
914 for i in addr_list:
915 # Invalid IPv4 may qualify for a valid FQDN, so make check here
916 # e.g. 172.24.15.300
917 import re
918 if not re.match('^[0-9_]+$', i):
919 invalid_ipv4 = False
920 break
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100921
Harald Weltec91085e2022-02-10 18:05:45 +0100922 if invalid_ipv4:
923 return None
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100924
Harald Weltec91085e2022-02-10 18:05:45 +0100925 fqdn_flag = True
926 for i in addr_list:
927 # Only Alpha-numeric characters and hyphen - RFC 1035
928 import re
929 if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
930 fqdn_flag = False
931 break
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100932
Harald Weltec91085e2022-02-10 18:05:45 +0100933 # FQDN
934 if fqdn_flag:
935 return 0x00
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100936
Harald Weltec91085e2022-02-10 18:05:45 +0100937 return None
Harald Welte67d551a2021-01-21 14:50:01 +0100938
Philipp Maier5d3e2592021-02-22 17:22:16 +0100939
Harald Weltec91085e2022-02-10 18:05:45 +0100940def sw_match(sw: str, pattern: str) -> bool:
941 """Match given SW against given pattern."""
942 # Create a masked version of the returned status word
943 sw_lower = sw.lower()
944 sw_masked = ""
945 for i in range(0, 4):
946 if pattern[i] == '?':
947 sw_masked = sw_masked + '?'
948 elif pattern[i] == 'x':
949 sw_masked = sw_masked + 'x'
950 else:
951 sw_masked = sw_masked + sw_lower[i]
952 # Compare the masked version against the pattern
953 return sw_masked == pattern
Harald Welteee3501f2021-04-02 13:00:18 +0200954
Harald Weltec91085e2022-02-10 18:05:45 +0100955
956def tabulate_str_list(str_list, width: int = 79, hspace: int = 2, lspace: int = 1,
957 align_left: bool = True) -> str:
958 """Pretty print a list of strings into a tabulated form.
959
960 Args:
961 width : total width in characters per line
962 space : horizontal space between cells
963 lspace : number of spaces before row
964 align_lef : Align text to the left side
965 Returns:
966 multi-line string containing formatted table
967 """
968 if str_list == None:
969 return ""
970 if len(str_list) <= 0:
971 return ""
972 longest_str = max(str_list, key=len)
973 cellwith = len(longest_str) + hspace
974 cols = width // cellwith
975 rows = (len(str_list) - 1) // cols + 1
976 table = []
977 for i in iter(range(rows)):
978 str_list_row = str_list[i::rows]
979 if (align_left):
980 format_str_cell = '%%-%ds'
981 else:
982 format_str_cell = '%%%ds'
983 format_str_row = (format_str_cell % cellwith) * len(str_list_row)
984 format_str_row = (" " * lspace) + format_str_row
985 table.append(format_str_row % tuple(str_list_row))
986 return '\n'.join(table)
987
Harald Welte5e749a72021-04-10 17:18:17 +0200988
Harald Welte917d98c2021-04-21 11:51:25 +0200989def auto_int(x):
990 """Helper function for argparse to accept hexadecimal integers."""
991 return int(x, 0)
992
Harald Weltec91085e2022-02-10 18:05:45 +0100993
Philipp Maier40ea4a42022-06-02 14:45:41 +0200994def expand_hex(hexstring, length):
995 """Expand a given hexstring to a specified length by replacing "." or ".."
996 with a filler that is derived from the neighboring nibbles respective
997 bytes. Usually this will be the nibble respective byte before "." or
998 "..", execpt when the string begins with "." or "..", then the nibble
999 respective byte after "." or ".." is used.". In case the string cannot
1000 be expanded for some reason, the input string is returned unmodified.
1001
1002 Args:
1003 hexstring : hexstring to expand
1004 length : desired length of the resulting hexstring.
1005 Returns:
1006 expanded hexstring
1007 """
1008
1009 # expand digit aligned
1010 if hexstring.count(".") == 1:
1011 pos = hexstring.index(".")
1012 if pos > 0:
1013 filler = hexstring[pos - 1]
1014 else:
1015 filler = hexstring[pos + 1]
1016
1017 missing = length * 2 - (len(hexstring) - 1)
1018 if missing <= 0:
1019 return hexstring
1020
1021 return hexstring.replace(".", filler * missing)
1022
1023 # expand byte aligned
1024 elif hexstring.count("..") == 1:
1025 if len(hexstring) % 2:
1026 return hexstring
1027
1028 pos = hexstring.index("..")
1029
1030 if pos % 2:
1031 return hexstring
1032
1033 if pos > 1:
1034 filler = hexstring[pos - 2:pos]
1035 else:
1036 filler = hexstring[pos + 2:pos+4]
1037
1038 missing = length * 2 - (len(hexstring) - 2)
1039 if missing <= 0:
1040 return hexstring
1041
1042 return hexstring.replace("..", filler * (missing // 2))
1043
1044 # no change
1045 return hexstring
1046
1047
Harald Welte5e749a72021-04-10 17:18:17 +02001048class JsonEncoder(json.JSONEncoder):
1049 """Extend the standard library JSONEncoder with support for more types."""
Harald Weltec91085e2022-02-10 18:05:45 +01001050
Harald Welte5e749a72021-04-10 17:18:17 +02001051 def default(self, o):
1052 if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
1053 return b2h(o)
1054 return json.JSONEncoder.default(self, o)
Philipp Maier80ce71f2021-04-19 21:24:23 +02001055
Harald Weltec91085e2022-02-10 18:05:45 +01001056
Philipp Maier80ce71f2021-04-19 21:24:23 +02001057def boxed_heading_str(heading, width=80):
Harald Weltec91085e2022-02-10 18:05:45 +01001058 """Generate a string that contains a boxed heading."""
1059 # Auto-enlarge box if heading exceeds length
1060 if len(heading) > width - 4:
1061 width = len(heading) + 4
Philipp Maier80ce71f2021-04-19 21:24:23 +02001062
Harald Weltec91085e2022-02-10 18:05:45 +01001063 res = "#" * width
1064 fstr = "\n# %-" + str(width - 4) + "s #\n"
1065 res += fstr % (heading)
1066 res += "#" * width
1067 return res
Harald Welte3de6ca22021-05-02 20:05:56 +02001068
1069
1070class DataObject(abc.ABC):
1071 """A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
1072 simply has any number of different TLVs that may occur in any order at any point, ISO 7816
1073 has the habit of specifying TLV data but with very spcific ordering, or specific choices of
1074 tags at specific points in a stream. This class tries to represent this."""
Harald Weltec91085e2022-02-10 18:05:45 +01001075
Harald Welte425038f2022-02-10 19:32:04 +01001076 def __init__(self, name: str, desc: Optional[str] = None, tag: Optional[int] = None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001077 """
1078 Args:
1079 name: A brief, all-lowercase, underscore separated string identifier
1080 desc: A human-readable description of what this DO represents
1081 tag : The tag associated with this DO
1082 """
1083 self.name = name
1084 self.desc = desc
1085 self.tag = tag
1086 self.decoded = None
1087 self.encoded = None
1088
1089 def __str__(self):
1090 return self.name
1091
Harald Welte50368772022-02-10 15:22:22 +01001092 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001093 return '%s(%s)' % (self.__class__, self.name)
1094
Harald Welte50368772022-02-10 15:22:22 +01001095 def __or__(self, other) -> 'DataObjectChoice':
Harald Welte3de6ca22021-05-02 20:05:56 +02001096 """OR-ing DataObjects together renders a DataObjectChoice."""
1097 if isinstance(other, DataObject):
1098 # DataObject | DataObject = DataObjectChoice
1099 return DataObjectChoice(None, members=[self, other])
1100 else:
1101 raise TypeError
1102
Harald Welte50368772022-02-10 15:22:22 +01001103 def __add__(self, other) -> 'DataObjectCollection':
Harald Welte3de6ca22021-05-02 20:05:56 +02001104 """ADD-ing DataObjects together renders a DataObjectCollection."""
1105 if isinstance(other, DataObject):
1106 # DataObject + DataObject = DataObjectCollectin
1107 return DataObjectCollection(None, members=[self, other])
Harald Welte50368772022-02-10 15:22:22 +01001108 else:
1109 raise TypeError
Harald Welte3de6ca22021-05-02 20:05:56 +02001110
Harald Welte50368772022-02-10 15:22:22 +01001111 def _compute_tag(self) -> int:
Harald Welte3de6ca22021-05-02 20:05:56 +02001112 """Compute the tag (sometimes the tag encodes part of the value)."""
1113 return self.tag
1114
Harald Welte50368772022-02-10 15:22:22 +01001115 def to_dict(self) -> dict:
Harald Welte3de6ca22021-05-02 20:05:56 +02001116 """Return a dict in form "name: decoded_value" """
1117 return {self.name: self.decoded}
1118
1119 @abc.abstractmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001120 def from_bytes(self, do: bytes):
Harald Welte3de6ca22021-05-02 20:05:56 +02001121 """Parse the value part of the DO into the internal state of this instance.
1122 Args:
1123 do : binary encoded bytes
1124 """
1125
1126 @abc.abstractmethod
Harald Welte50368772022-02-10 15:22:22 +01001127 def to_bytes(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001128 """Encode the internal state of this instance into the TLV value part.
1129 Returns:
1130 binary bytes encoding the internal state
1131 """
1132
Harald Weltec91085e2022-02-10 18:05:45 +01001133 def from_tlv(self, do: bytes) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001134 """Parse binary TLV representation into internal state. The resulting decoded
1135 representation is _not_ returned, but just internalized in the object instance!
1136 Args:
1137 do : input bytes containing TLV-encoded representation
1138 Returns:
1139 bytes remaining at end of 'do' after parsing one TLV/DO.
1140 """
1141 if do[0] != self.tag:
Harald Weltec91085e2022-02-10 18:05:45 +01001142 raise ValueError('%s: Can only decode tag 0x%02x' %
1143 (self, self.tag))
Harald Welte3de6ca22021-05-02 20:05:56 +02001144 length = do[1]
1145 val = do[2:2+length]
1146 self.from_bytes(val)
1147 # return remaining bytes
1148 return do[2+length:]
1149
Harald Welte50368772022-02-10 15:22:22 +01001150 def to_tlv(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001151 """Encode internal representation to binary TLV.
1152 Returns:
1153 bytes encoded in TLV format.
1154 """
1155 val = self.to_bytes()
Harald Welte785d4842022-04-05 14:24:22 +02001156 return bertlv_encode_tag(self._compute_tag()) + bertlv_encode_len(len(val)) + val
Harald Welte3de6ca22021-05-02 20:05:56 +02001157
1158 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001159 def decode(self, binary: bytes) -> Tuple[dict, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001160 """Decode a single DOs from the input data.
1161 Args:
1162 binary : binary bytes of encoded data
1163 Returns:
1164 tuple of (decoded_result, binary_remainder)
1165 """
1166 tag = binary[0]
1167 if tag != self.tag:
1168 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected 0x%02x' %
1169 (self, tag, binary, self.tag))
1170 remainder = self.from_tlv(binary)
1171 return (self.to_dict(), remainder)
1172
1173 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001174 def encode(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001175 return self.to_tlv()
1176
Harald Weltec91085e2022-02-10 18:05:45 +01001177
Harald Welte3de6ca22021-05-02 20:05:56 +02001178class TL0_DataObject(DataObject):
1179 """Data Object that has Tag, Len=0 and no Value part."""
Harald Weltec91085e2022-02-10 18:05:45 +01001180
1181 def __init__(self, name: str, desc: str, tag: int, val=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001182 super().__init__(name, desc, tag)
1183 self.val = val
1184
Harald Weltec91085e2022-02-10 18:05:45 +01001185 def from_bytes(self, binary: bytes):
Harald Welte3de6ca22021-05-02 20:05:56 +02001186 if len(binary) != 0:
1187 raise ValueError
1188 self.decoded = self.val
1189
Harald Welte50368772022-02-10 15:22:22 +01001190 def to_bytes(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001191 return b''
1192
1193
1194class DataObjectCollection:
1195 """A DataObjectCollection consits of multiple Data Objects identified by their tags.
1196 A given encoded DO may contain any of them in any order, and may contain multiple instances
1197 of each DO."""
Harald Weltec91085e2022-02-10 18:05:45 +01001198
Harald Welte425038f2022-02-10 19:32:04 +01001199 def __init__(self, name: str, desc: Optional[str] = None, members=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001200 self.name = name
1201 self.desc = desc
1202 self.members = members or []
1203 self.members_by_tag = {}
1204 self.members_by_name = {}
Harald Weltec91085e2022-02-10 18:05:45 +01001205 self.members_by_tag = {m.tag: m for m in members}
1206 self.members_by_name = {m.name: m for m in members}
Harald Welte3de6ca22021-05-02 20:05:56 +02001207
Harald Welte50368772022-02-10 15:22:22 +01001208 def __str__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001209 member_strs = [str(x) for x in self.members]
1210 return '%s(%s)' % (self.name, ','.join(member_strs))
1211
Harald Welte50368772022-02-10 15:22:22 +01001212 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001213 member_strs = [repr(x) for x in self.members]
1214 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1215
Harald Welte50368772022-02-10 15:22:22 +01001216 def __add__(self, other) -> 'DataObjectCollection':
Harald Welte3de6ca22021-05-02 20:05:56 +02001217 """Extending DataCollections with other DataCollections or DataObjects."""
1218 if isinstance(other, DataObjectCollection):
1219 # adding one collection to another
1220 members = self.members + other.members
1221 return DataObjectCollection(self.name, self.desc, members)
1222 elif isinstance(other, DataObject):
1223 # adding a member to a collection
1224 return DataObjectCollection(self.name, self.desc, self.members + [other])
1225 else:
1226 raise TypeError
1227
1228 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001229 def decode(self, binary: bytes) -> Tuple[List, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001230 """Decode any number of DOs from the collection until the end of the input data,
1231 or uninitialized memory (0xFF) is found.
1232 Args:
1233 binary : binary bytes of encoded data
1234 Returns:
1235 tuple of (decoded_result, binary_remainder)
1236 """
1237 res = []
1238 remainder = binary
1239 # iterate until no binary trailer is left
1240 while len(remainder):
1241 tag = remainder[0]
Harald Weltec91085e2022-02-10 18:05:45 +01001242 if tag == 0xff: # uninitialized memory at the end?
Harald Welte3de6ca22021-05-02 20:05:56 +02001243 return (res, remainder)
1244 if not tag in self.members_by_tag:
1245 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1246 (self, tag, remainder, self.members_by_tag.keys()))
1247 obj = self.members_by_tag[tag]
1248 # DO from_tlv returns remainder of binary
1249 remainder = obj.from_tlv(remainder)
1250 # collect our results
1251 res.append(obj.to_dict())
1252 return (res, remainder)
1253
1254 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001255 def encode(self, decoded) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001256 res = bytearray()
1257 for i in decoded:
1258 obj = self.members_by_name(i[0])
1259 res.append(obj.to_tlv())
1260 return res
1261
Harald Weltec91085e2022-02-10 18:05:45 +01001262
Harald Welte3de6ca22021-05-02 20:05:56 +02001263class DataObjectChoice(DataObjectCollection):
1264 """One Data Object from within a choice, identified by its tag.
1265 This means that exactly one member of the choice must occur, and which one occurs depends
1266 on the tag."""
Harald Weltec91085e2022-02-10 18:05:45 +01001267
Harald Welte3de6ca22021-05-02 20:05:56 +02001268 def __add__(self, other):
1269 """We overload the add operator here to avoid inheriting it from DataObjecCollection."""
1270 raise TypeError
1271
Harald Welte50368772022-02-10 15:22:22 +01001272 def __or__(self, other) -> 'DataObjectChoice':
Harald Welte3de6ca22021-05-02 20:05:56 +02001273 """OR-ing a Choice to another choice extends the choice, as does OR-ing a DataObject."""
1274 if isinstance(other, DataObjectChoice):
1275 # adding one collection to another
1276 members = self.members + other.members
1277 return DataObjectChoice(self.name, self.desc, members)
1278 elif isinstance(other, DataObject):
1279 # adding a member to a collection
1280 return DataObjectChoice(self.name, self.desc, self.members + [other])
1281 else:
1282 raise TypeError
1283
1284 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001285 def decode(self, binary: bytes) -> Tuple[dict, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001286 """Decode a single DOs from the choice based on the tag.
1287 Args:
1288 binary : binary bytes of encoded data
1289 Returns:
1290 tuple of (decoded_result, binary_remainder)
1291 """
1292 tag = binary[0]
1293 if tag == 0xff:
1294 return (None, binary)
1295 if not tag in self.members_by_tag:
1296 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1297 (self, tag, binary, self.members_by_tag.keys()))
1298 obj = self.members_by_tag[tag]
1299 remainder = obj.from_tlv(binary)
1300 return (obj.to_dict(), remainder)
1301
1302 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001303 def encode(self, decoded) -> bytes:
Harald Welte785d4842022-04-05 14:24:22 +02001304 obj = self.members_by_name[list(decoded)[0]]
1305 obj.decoded = list(decoded.values())[0]
Harald Welte3de6ca22021-05-02 20:05:56 +02001306 return obj.to_tlv()
1307
Harald Weltec91085e2022-02-10 18:05:45 +01001308
Harald Welte3de6ca22021-05-02 20:05:56 +02001309class DataObjectSequence:
1310 """A sequence of DataObjects or DataObjectChoices. This allows us to express a certain
1311 ordered sequence of DOs or choices of DOs that have to appear as per the specification.
1312 By wrapping them into this formal DataObjectSequence, we can offer convenience methods
1313 for encoding or decoding an entire sequence."""
Harald Weltec91085e2022-02-10 18:05:45 +01001314
Harald Welte425038f2022-02-10 19:32:04 +01001315 def __init__(self, name: str, desc: Optional[str] = None, sequence=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001316 self.sequence = sequence or []
1317 self.name = name
1318 self.desc = desc
1319
Harald Welte50368772022-02-10 15:22:22 +01001320 def __str__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001321 member_strs = [str(x) for x in self.sequence]
1322 return '%s(%s)' % (self.name, ','.join(member_strs))
1323
Harald Welte50368772022-02-10 15:22:22 +01001324 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001325 member_strs = [repr(x) for x in self.sequence]
1326 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1327
Harald Welte50368772022-02-10 15:22:22 +01001328 def __add__(self, other) -> 'DataObjectSequence':
Harald Welte3de6ca22021-05-02 20:05:56 +02001329 """Add (append) a DataObject or DataObjectChoice to the sequence."""
1330 if isinstance(other, 'DataObject'):
Harald Weltec91085e2022-02-10 18:05:45 +01001331 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
Harald Welte3de6ca22021-05-02 20:05:56 +02001332 elif isinstance(other, 'DataObjectChoice'):
Harald Weltec91085e2022-02-10 18:05:45 +01001333 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
Harald Welte3de6ca22021-05-02 20:05:56 +02001334 elif isinstance(other, 'DataObjectSequence'):
Harald Weltec91085e2022-02-10 18:05:45 +01001335 return DataObjectSequence(self.name, self.desc, self.sequence + other.sequence)
Harald Welte3de6ca22021-05-02 20:05:56 +02001336
1337 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001338 def decode(self, binary: bytes) -> Tuple[list, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001339 """Decode a sequence by calling the decoder of each element in the sequence.
1340 Args:
1341 binary : binary bytes of encoded data
1342 Returns:
1343 tuple of (decoded_result, binary_remainder)
1344 """
1345 remainder = binary
1346 res = []
1347 for e in self.sequence:
1348 (r, remainder) = e.decode(remainder)
1349 if r:
1350 res.append(r)
1351 return (res, remainder)
1352
1353 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001354 def decode_multi(self, do: bytes) -> Tuple[list, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001355 """Decode multiple occurrences of the sequence from the binary input data.
1356 Args:
1357 do : binary input data to be decoded
1358 Returns:
1359 list of results of the decoder of this sequences
1360 """
1361 remainder = do
1362 res = []
1363 while len(remainder):
1364 (r, remainder2) = self.decode(remainder)
1365 if r:
1366 res.append(r)
1367 if len(remainder2) < len(remainder):
1368 remainder = remainder2
1369 else:
1370 remainder = remainder2
1371 break
1372 return (res, remainder)
1373
1374 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001375 def encode(self, decoded) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001376 """Encode a sequence by calling the encoder of each element in the sequence."""
1377 encoded = bytearray()
1378 i = 0
1379 for e in self.sequence:
1380 encoded += e.encode(decoded[i])
1381 i += 1
1382 return encoded
Harald Welte90441432021-05-02 21:28:12 +02001383
Harald Welte0dcdfbf2022-04-05 14:42:48 +02001384 def encode_multi(self, decoded) -> bytes:
1385 """Encode multiple occurrences of the sequence from the decoded input data.
1386 Args:
1387 decoded : list of json-serializable input data; one sequence per list item
1388 Returns:
1389 binary encoded output data
1390 """
1391 encoded = bytearray()
1392 for d in decoded:
1393 encoded += self.encode(d)
1394 return encoded
1395
Harald Weltec91085e2022-02-10 18:05:45 +01001396
Harald Welte90441432021-05-02 21:28:12 +02001397class CardCommand:
1398 """A single card command / instruction."""
Harald Weltec91085e2022-02-10 18:05:45 +01001399
Harald Welte90441432021-05-02 21:28:12 +02001400 def __init__(self, name, ins, cla_list=None, desc=None):
1401 self.name = name
1402 self.ins = ins
1403 self.cla_list = cla_list or []
1404 self.cla_list = [x.lower() for x in self.cla_list]
1405 self.desc = desc
1406
1407 def __str__(self):
1408 return self.name
1409
1410 def __repr__(self):
1411 return '%s(INS=%02x,CLA=%s)' % (self.name, self.ins, self.cla_list)
1412
1413 def match_cla(self, cla):
1414 """Does the given CLA match the CLA list of the command?."""
1415 if not isinstance(cla, str):
1416 cla = '%02u' % cla
1417 cla = cla.lower()
1418 for cla_match in self.cla_list:
1419 cla_masked = ""
1420 for i in range(0, 2):
1421 if cla_match[i] == 'x':
1422 cla_masked += 'x'
1423 else:
1424 cla_masked += cla[i]
1425 if cla_masked == cla_match:
1426 return True
1427 return False
1428
1429
1430class CardCommandSet:
1431 """A set of card instructions, typically specified within one spec."""
Harald Weltec91085e2022-02-10 18:05:45 +01001432
Harald Welte90441432021-05-02 21:28:12 +02001433 def __init__(self, name, cmds=[]):
1434 self.name = name
Harald Weltec91085e2022-02-10 18:05:45 +01001435 self.cmds = {c.ins: c for c in cmds}
Harald Welte90441432021-05-02 21:28:12 +02001436
1437 def __str__(self):
1438 return self.name
1439
1440 def __getitem__(self, idx):
1441 return self.cmds[idx]
1442
1443 def __add__(self, other):
1444 if isinstance(other, CardCommand):
1445 if other.ins in self.cmds:
1446 raise ValueError('%s: INS 0x%02x already defined: %s' %
1447 (self, other.ins, self.cmds[other.ins]))
1448 self.cmds[other.ins] = other
1449 elif isinstance(other, CardCommandSet):
1450 for c in other.cmds.keys():
1451 self.cmds[c] = other.cmds[c]
1452 else:
Harald Weltec91085e2022-02-10 18:05:45 +01001453 raise ValueError(
1454 '%s: Unsupported type to add operator: %s' % (self, other))
Harald Welte90441432021-05-02 21:28:12 +02001455
1456 def lookup(self, ins, cla=None):
1457 """look-up the command within the CommandSet."""
1458 ins = int(ins)
1459 if not ins in self.cmds:
1460 return None
1461 cmd = self.cmds[ins]
1462 if cla and not cmd.match_cla(cla):
1463 return None
1464 return cmd
Philipp Maiera028c7d2021-11-08 16:12:03 +01001465
Harald Weltec91085e2022-02-10 18:05:45 +01001466
Philipp Maiera028c7d2021-11-08 16:12:03 +01001467def all_subclasses(cls) -> set:
Harald Weltec91085e2022-02-10 18:05:45 +01001468 """Recursively get all subclasses of a specified class"""
1469 return set(cls.__subclasses__()).union([s for c in cls.__subclasses__() for s in all_subclasses(c)])