blob: 6523d98589609e60996072473fda7be38df8875b [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
Harald Welteb3c46132023-12-08 16:08:53 +0100317 if len(binary) < num_len_oct + 1:
318 return (0, b'')
Harald Weltec91085e2022-02-10 18:05:45 +0100319 for i in range(1, 1+num_len_oct):
320 length <<= 8
321 length |= binary[i]
322 return (length, binary[1+num_len_oct:])
Harald Welte917d98c2021-04-21 11:51:25 +0200323
Harald Welte917d98c2021-04-21 11:51:25 +0200324
Harald Weltec91085e2022-02-10 18:05:45 +0100325def bertlv_encode_len(length: int) -> bytes:
326 """Encode a single Length value according to ITU-T X.690 8.1.3;
327 only the definite form is supported here.
328 Args:
329 length : length value to be encoded
330 Returns:
331 binary output data of BER-TLV length field
332 """
333 if length < 0x80:
334 return length.to_bytes(1, 'big')
335 elif length <= 0xff:
336 return b'\x81' + length.to_bytes(1, 'big')
337 elif length <= 0xffff:
338 return b'\x82' + length.to_bytes(2, 'big')
339 elif length <= 0xffffff:
340 return b'\x83' + length.to_bytes(3, 'big')
341 elif length <= 0xffffffff:
342 return b'\x84' + length.to_bytes(4, 'big')
343 else:
344 raise ValueError("Length > 32bits not supported")
345
346
347def bertlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]:
348 """Parse a single TLV IE at the start of the given binary data.
349 Args:
350 binary : binary input data of BER-TLV length field
351 Returns:
352 Tuple of (tag:dict, len:int, remainder:bytes)
353 """
354 (tagdict, remainder) = bertlv_parse_tag(binary)
355 (length, remainder) = bertlv_parse_len(remainder)
356 value = remainder[:length]
357 remainder = remainder[length:]
358 return (tagdict, length, value, remainder)
Harald Welte917d98c2021-04-21 11:51:25 +0200359
360
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200361# IMSI encoded format:
362# For IMSI 0123456789ABCDE:
363#
364# | byte 1 | 2 upper | 2 lower | 3 upper | 3 lower | ... | 9 upper | 9 lower |
365# | length in bytes | 0 | odd/even | 2 | 1 | ... | E | D |
366#
367# If the IMSI is less than 15 characters, it should be padded with 'f' from the end.
368#
369# The length is the total number of bytes used to encoded the IMSI. This includes the odd/even
370# parity bit. E.g. an IMSI of length 14 is 8 bytes long, not 7, as it uses bytes 2 to 9 to
371# encode itself.
372#
373# Because of this, an odd length IMSI fits exactly into len(imsi) + 1 // 2 bytes, whereas an
374# even length IMSI only uses half of the last byte.
375
Harald Weltec91085e2022-02-10 18:05:45 +0100376def enc_imsi(imsi: str):
377 """Converts a string IMSI into the encoded value of the EF"""
378 l = half_round_up(
379 len(imsi) + 1) # Required bytes - include space for odd/even indicator
380 oe = len(imsi) & 1 # Odd (1) / Even (0)
381 ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe << 3) | 1, rpad(imsi, 15)))
382 return ei
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400383
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400384
Harald Weltec91085e2022-02-10 18:05:45 +0100385def dec_imsi(ef: Hexstr) -> Optional[str]:
386 """Converts an EF value to the IMSI string representation"""
387 if len(ef) < 4:
388 return None
389 l = int(ef[0:2], 16) * 2 # Length of the IMSI string
390 l = l - 1 # Encoded length byte includes oe nibble
391 swapped = swap_nibbles(ef[2:]).rstrip('f')
392 if len(swapped) < 1:
393 return None
394 oe = (int(swapped[0]) >> 3) & 1 # Odd (1) / Even (0)
395 if not oe:
396 # if even, only half of last byte was used
397 l = l-1
398 if l != len(swapped) - 1:
399 return None
400 imsi = swapped[1:]
401 return imsi
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400402
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400403
Harald Weltec91085e2022-02-10 18:05:45 +0100404def dec_iccid(ef: Hexstr) -> str:
405 return swap_nibbles(ef).strip('f')
Philipp Maier6c5cd802021-04-23 21:19:36 +0200406
Philipp Maier6c5cd802021-04-23 21:19:36 +0200407
Harald Weltec91085e2022-02-10 18:05:45 +0100408def enc_iccid(iccid: str) -> Hexstr:
409 return swap_nibbles(rpad(iccid, 20))
Philipp Maier6c5cd802021-04-23 21:19:36 +0200410
Philipp Maier6c5cd802021-04-23 21:19:36 +0200411
Harald Weltec91085e2022-02-10 18:05:45 +0100412def enc_plmn(mcc: Hexstr, mnc: Hexstr) -> Hexstr:
413 """Converts integer MCC/MNC into 3 bytes for EF"""
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300414
Harald Weltec91085e2022-02-10 18:05:45 +0100415 # Make sure there are no excess whitespaces in the input
416 # parameters
417 mcc = mcc.strip()
418 mnc = mnc.strip()
419
420 # Make sure that MCC/MNC are correctly padded with leading
421 # zeros or 'F', depending on the length.
422 if len(mnc) == 0:
423 mnc = "FFF"
424 elif len(mnc) == 1:
farhadhfec721f2023-07-19 15:43:13 +0200425 mnc = "0" + mnc + "F"
Harald Weltec91085e2022-02-10 18:05:45 +0100426 elif len(mnc) == 2:
427 mnc += "F"
428
429 if len(mcc) == 0:
430 mcc = "FFF"
431 elif len(mcc) == 1:
432 mcc = "00" + mcc
433 elif len(mcc) == 2:
434 mcc = "0" + mcc
435
436 return (mcc[1] + mcc[0]) + (mnc[2] + mcc[2]) + (mnc[1] + mnc[0])
437
438
439def dec_plmn(threehexbytes: Hexstr) -> dict:
440 res = {'mcc': "0", 'mnc': "0"}
441 dec_mcc_from_plmn_str(threehexbytes)
442 res['mcc'] = dec_mcc_from_plmn_str(threehexbytes)
443 res['mnc'] = dec_mnc_from_plmn_str(threehexbytes)
444 return res
445
Harald Welted7a7e172021-04-07 00:30:10 +0200446
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100447# Accepts hex string representing three bytes
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100448
Philipp Maier6c5cd802021-04-23 21:19:36 +0200449
Harald Weltec91085e2022-02-10 18:05:45 +0100450def dec_mcc_from_plmn(plmn: Hexstr) -> int:
451 ia = h2i(plmn)
452 digit1 = ia[0] & 0x0F # 1st byte, LSB
453 digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB
454 digit3 = ia[1] & 0x0F # 2nd byte, LSB
455 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
456 return 0xFFF # 4095
457 return derive_mcc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100458
Philipp Maier6c5cd802021-04-23 21:19:36 +0200459
Harald Weltec91085e2022-02-10 18:05:45 +0100460def dec_mcc_from_plmn_str(plmn: Hexstr) -> str:
461 digit1 = plmn[1] # 1st byte, LSB
462 digit2 = plmn[0] # 1st byte, MSB
463 digit3 = plmn[3] # 2nd byte, LSB
464 res = digit1 + digit2 + digit3
465 return res.upper().strip("F")
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100466
Harald Weltec91085e2022-02-10 18:05:45 +0100467
468def dec_mnc_from_plmn(plmn: Hexstr) -> int:
469 ia = h2i(plmn)
470 digit1 = ia[2] & 0x0F # 3rd byte, LSB
471 digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB
472 digit3 = (ia[1] & 0xF0) >> 4 # 2nd byte, MSB
473 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
474 return 0xFFF # 4095
475 return derive_mnc(digit1, digit2, digit3)
476
477
478def dec_mnc_from_plmn_str(plmn: Hexstr) -> str:
479 digit1 = plmn[5] # 3rd byte, LSB
480 digit2 = plmn[4] # 3rd byte, MSB
481 digit3 = plmn[2] # 2nd byte, MSB
482 res = digit1 + digit2 + digit3
483 return res.upper().strip("F")
484
485
486def dec_act(twohexbytes: Hexstr) -> List[str]:
487 act_list = [
488 {'bit': 15, 'name': "UTRAN"},
Harald Weltec91085e2022-02-10 18:05:45 +0100489 {'bit': 11, 'name': "NG-RAN"},
Harald Weltec91085e2022-02-10 18:05:45 +0100490 {'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]
Harald Welte542dbf62023-12-21 20:14:48 +0100496 sel = set()
497 # only the simple single-bit ones
Harald Weltec91085e2022-02-10 18:05:45 +0100498 for a in act_list:
499 if u16t & (1 << a['bit']):
Harald Welte542dbf62023-12-21 20:14:48 +0100500 sel.add(a['name'])
501 # TS 31.102 Section 4.2.5 Table 4.2.5.1
502 eutran_bits = u16t & 0x7000
503 if eutran_bits == 0x4000 or eutran_bits == 0x7000:
504 sel.add("E-UTRAN WB-S1")
505 sel.add("E-UTRAN NB-S1")
506 elif eutran_bits == 0x5000:
507 sel.add("E-UTRAN NB-S1")
508 elif eutran_bits == 0x6000:
509 sel.add("E-UTRAN WB-S1")
510 # TS 31.102 Section 4.2.5 Table 4.2.5.2
511 gsm_bits = u16t & 0x008C
512 if gsm_bits == 0x0080 or gsm_bits == 0x008C:
513 sel.add("GSM")
514 sel.add("EC-GSM-IoT")
515 elif u16t & 0x008C == 0x0084:
516 sel.add("GSM")
517 elif u16t & 0x008C == 0x0086:
518 sel.add("EC-GSM-IoT")
519 return sorted(list(sel))
Harald Weltec91085e2022-02-10 18:05:45 +0100520
521
522def dec_xplmn_w_act(fivehexbytes: Hexstr) -> Dict[str, Any]:
523 res = {'mcc': "0", 'mnc': "0", 'act': []}
524 plmn_chars = 6
525 act_chars = 4
526 # first three bytes (six ascii hex chars)
527 plmn_str = fivehexbytes[:plmn_chars]
528 # two bytes after first three bytes
529 act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars]
530 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
531 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
532 res['act'] = dec_act(act_str)
533 return res
534
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100535
Harald Weltec91085e2022-02-10 18:05:45 +0100536def dec_xplmn(threehexbytes: Hexstr) -> dict:
537 res = {'mcc': 0, 'mnc': 0, 'act': []}
538 plmn_chars = 6
539 # first three bytes (six ascii hex chars)
540 plmn_str = threehexbytes[:plmn_chars]
Matan Perelman60951b02023-06-01 17:39:04 +0300541 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
542 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
Harald Weltec91085e2022-02-10 18:05:45 +0100543 return res
Harald Welteca673942020-06-03 15:19:40 +0200544
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900545
Harald Weltec91085e2022-02-10 18:05:45 +0100546def derive_milenage_opc(ki_hex: Hexstr, op_hex: Hexstr) -> Hexstr:
547 """
548 Run the milenage algorithm to calculate OPC from Ki and OP
549 """
Harald Welted75fa3f2023-05-31 20:47:55 +0200550 from Cryptodome.Cipher import AES
Harald Weltec91085e2022-02-10 18:05:45 +0100551 # pylint: disable=no-name-in-module
Harald Welted75fa3f2023-05-31 20:47:55 +0200552 from Cryptodome.Util.strxor import strxor
Harald Weltec91085e2022-02-10 18:05:45 +0100553
554 # We pass in hex string and now need to work on bytes
555 ki_bytes = bytes(h2b(ki_hex))
556 op_bytes = bytes(h2b(op_hex))
557 aes = AES.new(ki_bytes, AES.MODE_ECB)
558 opc_bytes = aes.encrypt(op_bytes)
559 return b2h(strxor(opc_bytes, op_bytes))
560
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900561
Harald Welte52255572021-04-03 09:56:32 +0200562def calculate_luhn(cc) -> int:
Harald Weltec91085e2022-02-10 18:05:45 +0100563 """
564 Calculate Luhn checksum used in e.g. ICCID and IMEI
565 """
566 num = list(map(int, str(cc)))
567 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10))
568 for d in num[::-2]]) % 10
569 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200570
Philipp Maier7592eee2019-09-12 13:03:23 +0200571
Harald Weltec91085e2022-02-10 18:05:45 +0100572def mcc_from_imsi(imsi: str) -> Optional[str]:
573 """
574 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
575 """
576 if imsi == None:
577 return None
Philipp Maier7592eee2019-09-12 13:03:23 +0200578
Harald Weltec91085e2022-02-10 18:05:45 +0100579 if len(imsi) > 3:
580 return imsi[:3]
581 else:
582 return None
Philipp Maier7592eee2019-09-12 13:03:23 +0200583
Supreeth Herled24f1632019-11-30 10:37:09 +0100584
Harald Weltec91085e2022-02-10 18:05:45 +0100585def mnc_from_imsi(imsi: str, long: bool = False) -> Optional[str]:
586 """
587 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
588 """
589 if imsi == None:
590 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100591
Harald Weltec91085e2022-02-10 18:05:45 +0100592 if len(imsi) > 3:
593 if long:
594 return imsi[3:6]
595 else:
596 return imsi[3:5]
597 else:
598 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100599
Supreeth Herled24f1632019-11-30 10:37:09 +0100600
Harald Weltec91085e2022-02-10 18:05:45 +0100601def derive_mcc(digit1: int, digit2: int, digit3: int) -> int:
602 """
603 Derive decimal representation of the MCC (Mobile Country Code)
604 from three given digits.
605 """
Supreeth Herled24f1632019-11-30 10:37:09 +0100606
Harald Weltec91085e2022-02-10 18:05:45 +0100607 mcc = 0
Supreeth Herled24f1632019-11-30 10:37:09 +0100608
Harald Weltec91085e2022-02-10 18:05:45 +0100609 if digit1 != 0x0f:
610 mcc += digit1 * 100
611 if digit2 != 0x0f:
612 mcc += digit2 * 10
613 if digit3 != 0x0f:
614 mcc += digit3
Supreeth Herled24f1632019-11-30 10:37:09 +0100615
Harald Weltec91085e2022-02-10 18:05:45 +0100616 return mcc
Supreeth Herled24f1632019-11-30 10:37:09 +0100617
Supreeth Herled24f1632019-11-30 10:37:09 +0100618
Harald Weltec91085e2022-02-10 18:05:45 +0100619def derive_mnc(digit1: int, digit2: int, digit3: int = 0x0f) -> int:
620 """
621 Derive decimal representation of the MNC (Mobile Network Code)
622 from two or (optionally) three given digits.
623 """
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100624
Harald Weltec91085e2022-02-10 18:05:45 +0100625 mnc = 0
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100626
Harald Weltec91085e2022-02-10 18:05:45 +0100627 # 3-rd digit is optional for the MNC. If present
628 # the algorythm is the same as for the MCC.
629 if digit3 != 0x0f:
630 return derive_mcc(digit1, digit2, digit3)
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100631
Harald Weltec91085e2022-02-10 18:05:45 +0100632 if digit1 != 0x0f:
633 mnc += digit1 * 10
634 if digit2 != 0x0f:
635 mnc += digit2
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100636
Harald Weltec91085e2022-02-10 18:05:45 +0100637 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100638
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100639
Harald Weltec91085e2022-02-10 18:05:45 +0100640def dec_msisdn(ef_msisdn: Hexstr) -> Optional[Tuple[int, int, Optional[str]]]:
641 """
642 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
643 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
644 """
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100645
Harald Weltec91085e2022-02-10 18:05:45 +0100646 # Convert from str to (kind of) 'bytes'
647 ef_msisdn = h2b(ef_msisdn)
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100648
Harald Weltec91085e2022-02-10 18:05:45 +0100649 # Make sure mandatory fields are present
650 if len(ef_msisdn) < 14:
651 raise ValueError("EF.MSISDN is too short")
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100652
Harald Weltec91085e2022-02-10 18:05:45 +0100653 # Skip optional Alpha Identifier
654 xlen = len(ef_msisdn) - 14
655 msisdn_lhv = ef_msisdn[xlen:]
Supreeth Herle5a541012019-12-22 08:59:16 +0100656
Harald Weltec91085e2022-02-10 18:05:45 +0100657 # Parse the length (in bytes) of the BCD encoded number
658 bcd_len = msisdn_lhv[0]
659 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
660 if bcd_len == 0xff:
661 return None
662 elif bcd_len > 11 or bcd_len < 1:
663 raise ValueError(
664 "Length of MSISDN (%d bytes) is out of range" % bcd_len)
Supreeth Herle5a541012019-12-22 08:59:16 +0100665
Harald Weltec91085e2022-02-10 18:05:45 +0100666 # Parse ToN / NPI
667 ton = (msisdn_lhv[1] >> 4) & 0x07
668 npi = msisdn_lhv[1] & 0x0f
669 bcd_len -= 1
Supreeth Herle5a541012019-12-22 08:59:16 +0100670
Harald Weltec91085e2022-02-10 18:05:45 +0100671 # No MSISDN?
672 if not bcd_len:
673 return (npi, ton, None)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200674
Harald Weltec91085e2022-02-10 18:05:45 +0100675 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
676 # International number 10.5.118/3GPP TS 24.008
677 if ton == 0x01:
678 msisdn = '+' + msisdn
Supreeth Herle5a541012019-12-22 08:59:16 +0100679
Harald Weltec91085e2022-02-10 18:05:45 +0100680 return (npi, ton, msisdn)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200681
Supreeth Herle5a541012019-12-22 08:59:16 +0100682
Harald Weltec91085e2022-02-10 18:05:45 +0100683def enc_msisdn(msisdn: str, npi: int = 0x01, ton: int = 0x03) -> Hexstr:
684 """
685 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
686 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
687 will not contain the optional Alpha Identifier at the beginning.)
Supreeth Herle5a541012019-12-22 08:59:16 +0100688
Harald Weltec91085e2022-02-10 18:05:45 +0100689 Default NPI / ToN values:
690 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
691 - ToN: network specific or international number (if starts with '+').
692 """
693
694 # If no MSISDN is supplied then encode the file contents as all "ff"
695 if msisdn == "" or msisdn == "+":
696 return "ff" * 14
697
698 # Leading '+' indicates International Number
699 if msisdn[0] == '+':
700 msisdn = msisdn[1:]
701 ton = 0x01
702
703 # An MSISDN must not exceed 20 digits
704 if len(msisdn) > 20:
705 raise ValueError("msisdn must not be longer than 20 digits")
706
707 # Append 'f' padding if number of digits is odd
708 if len(msisdn) % 2 > 0:
709 msisdn += 'f'
710
711 # BCD length also includes NPI/ToN header
712 bcd_len = len(msisdn) // 2 + 1
713 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
714 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
715
716 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200717
Supreeth Herle441c4a72020-03-24 10:19:15 +0100718
Harald Weltec91085e2022-02-10 18:05:45 +0100719def is_hex(string: str, minlen: int = 2, maxlen: Optional[int] = None) -> bool:
720 """
721 Check if a string is a valid hexstring
722 """
Philipp Maier47236502021-03-09 21:28:25 +0100723
Harald Weltec91085e2022-02-10 18:05:45 +0100724 # Filter obviously bad strings
725 if not string:
726 return False
727 if len(string) < minlen or minlen < 2:
728 return False
729 if len(string) % 2:
730 return False
731 if maxlen and len(string) > maxlen:
732 return False
Philipp Maier47236502021-03-09 21:28:25 +0100733
Harald Weltec91085e2022-02-10 18:05:45 +0100734 # Try actual encoding to be sure
735 try:
736 try_encode = h2b(string)
737 return True
738 except:
739 return False
Philipp Maiere8536c02020-05-11 21:35:01 +0200740
Philipp Maiere8536c02020-05-11 21:35:01 +0200741
Harald Weltec91085e2022-02-10 18:05:45 +0100742def sanitize_pin_adm(pin_adm, pin_adm_hex=None) -> Hexstr:
743 """
744 The ADM pin can be supplied either in its hexadecimal form or as
745 ascii string. This function checks the supplied opts parameter and
746 returns the pin_adm as hex encoded string, regardless in which form
747 it was originally supplied by the user
748 """
Philipp Maiere8536c02020-05-11 21:35:01 +0200749
Harald Weltec91085e2022-02-10 18:05:45 +0100750 if pin_adm is not None:
751 if len(pin_adm) <= 8:
752 pin_adm = ''.join(['%02x' % (ord(x)) for x in pin_adm])
753 pin_adm = rpad(pin_adm, 16)
Philipp Maiere8536c02020-05-11 21:35:01 +0200754
Harald Weltec91085e2022-02-10 18:05:45 +0100755 else:
756 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
757
758 if pin_adm_hex is not None:
759 if len(pin_adm_hex) == 16:
760 pin_adm = pin_adm_hex
761 # Ensure that it's hex-encoded
762 try:
763 try_encode = h2b(pin_adm)
764 except ValueError:
765 raise ValueError(
766 "PIN-ADM needs to be hex encoded using this option")
767 else:
768 raise ValueError(
769 "PIN-ADM needs to be exactly 16 digits (hex encoded)")
770
771 return pin_adm
772
Philipp Maiere8536c02020-05-11 21:35:01 +0200773
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100774def get_addr_type(addr):
Harald Weltec91085e2022-02-10 18:05:45 +0100775 """
776 Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
777 Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given)
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100778
Harald Weltec91085e2022-02-10 18:05:45 +0100779 TODO: Handle IPv6
780 """
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100781
Harald Weltec91085e2022-02-10 18:05:45 +0100782 # Empty address string
783 if not len(addr):
784 return None
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100785
Harald Weltec91085e2022-02-10 18:05:45 +0100786 addr_list = addr.split('.')
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100787
Harald Weltec91085e2022-02-10 18:05:45 +0100788 # Check for IPv4/IPv6
789 try:
790 import ipaddress
791 # Throws ValueError if addr is not correct
792 ipa = ipaddress.ip_address(addr)
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100793
Harald Weltec91085e2022-02-10 18:05:45 +0100794 if ipa.version == 4:
795 return 0x01
796 elif ipa.version == 6:
797 return 0x02
798 except Exception as e:
799 invalid_ipv4 = True
800 for i in addr_list:
801 # Invalid IPv4 may qualify for a valid FQDN, so make check here
802 # e.g. 172.24.15.300
803 import re
804 if not re.match('^[0-9_]+$', i):
805 invalid_ipv4 = False
806 break
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100807
Harald Weltec91085e2022-02-10 18:05:45 +0100808 if invalid_ipv4:
809 return None
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100810
Harald Weltec91085e2022-02-10 18:05:45 +0100811 fqdn_flag = True
812 for i in addr_list:
813 # Only Alpha-numeric characters and hyphen - RFC 1035
814 import re
815 if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
816 fqdn_flag = False
817 break
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100818
Harald Weltec91085e2022-02-10 18:05:45 +0100819 # FQDN
820 if fqdn_flag:
821 return 0x00
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100822
Harald Weltec91085e2022-02-10 18:05:45 +0100823 return None
Harald Welte67d551a2021-01-21 14:50:01 +0100824
Philipp Maier5d3e2592021-02-22 17:22:16 +0100825
Harald Weltec91085e2022-02-10 18:05:45 +0100826def sw_match(sw: str, pattern: str) -> bool:
827 """Match given SW against given pattern."""
828 # Create a masked version of the returned status word
829 sw_lower = sw.lower()
830 sw_masked = ""
831 for i in range(0, 4):
832 if pattern[i] == '?':
833 sw_masked = sw_masked + '?'
834 elif pattern[i] == 'x':
835 sw_masked = sw_masked + 'x'
836 else:
837 sw_masked = sw_masked + sw_lower[i]
838 # Compare the masked version against the pattern
839 return sw_masked == pattern
Harald Welteee3501f2021-04-02 13:00:18 +0200840
Harald Weltec91085e2022-02-10 18:05:45 +0100841
842def tabulate_str_list(str_list, width: int = 79, hspace: int = 2, lspace: int = 1,
843 align_left: bool = True) -> str:
844 """Pretty print a list of strings into a tabulated form.
845
846 Args:
847 width : total width in characters per line
848 space : horizontal space between cells
849 lspace : number of spaces before row
850 align_lef : Align text to the left side
851 Returns:
852 multi-line string containing formatted table
853 """
854 if str_list == None:
855 return ""
856 if len(str_list) <= 0:
857 return ""
858 longest_str = max(str_list, key=len)
859 cellwith = len(longest_str) + hspace
860 cols = width // cellwith
861 rows = (len(str_list) - 1) // cols + 1
862 table = []
863 for i in iter(range(rows)):
864 str_list_row = str_list[i::rows]
865 if (align_left):
866 format_str_cell = '%%-%ds'
867 else:
868 format_str_cell = '%%%ds'
869 format_str_row = (format_str_cell % cellwith) * len(str_list_row)
870 format_str_row = (" " * lspace) + format_str_row
871 table.append(format_str_row % tuple(str_list_row))
872 return '\n'.join(table)
873
Harald Welte5e749a72021-04-10 17:18:17 +0200874
Harald Welte917d98c2021-04-21 11:51:25 +0200875def auto_int(x):
876 """Helper function for argparse to accept hexadecimal integers."""
877 return int(x, 0)
878
Harald Weltec91085e2022-02-10 18:05:45 +0100879
Philipp Maier40ea4a42022-06-02 14:45:41 +0200880def expand_hex(hexstring, length):
881 """Expand a given hexstring to a specified length by replacing "." or ".."
882 with a filler that is derived from the neighboring nibbles respective
883 bytes. Usually this will be the nibble respective byte before "." or
884 "..", execpt when the string begins with "." or "..", then the nibble
885 respective byte after "." or ".." is used.". In case the string cannot
886 be expanded for some reason, the input string is returned unmodified.
887
888 Args:
889 hexstring : hexstring to expand
890 length : desired length of the resulting hexstring.
891 Returns:
892 expanded hexstring
893 """
894
895 # expand digit aligned
896 if hexstring.count(".") == 1:
897 pos = hexstring.index(".")
898 if pos > 0:
899 filler = hexstring[pos - 1]
900 else:
901 filler = hexstring[pos + 1]
902
903 missing = length * 2 - (len(hexstring) - 1)
904 if missing <= 0:
905 return hexstring
906
907 return hexstring.replace(".", filler * missing)
908
909 # expand byte aligned
910 elif hexstring.count("..") == 1:
911 if len(hexstring) % 2:
912 return hexstring
913
914 pos = hexstring.index("..")
915
916 if pos % 2:
917 return hexstring
918
919 if pos > 1:
920 filler = hexstring[pos - 2:pos]
921 else:
922 filler = hexstring[pos + 2:pos+4]
923
924 missing = length * 2 - (len(hexstring) - 2)
925 if missing <= 0:
926 return hexstring
927
928 return hexstring.replace("..", filler * (missing // 2))
929
930 # no change
931 return hexstring
932
933
Harald Welte5e749a72021-04-10 17:18:17 +0200934class JsonEncoder(json.JSONEncoder):
935 """Extend the standard library JSONEncoder with support for more types."""
Harald Weltec91085e2022-02-10 18:05:45 +0100936
Harald Welte5e749a72021-04-10 17:18:17 +0200937 def default(self, o):
938 if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
939 return b2h(o)
940 return json.JSONEncoder.default(self, o)
Philipp Maier80ce71f2021-04-19 21:24:23 +0200941
Harald Weltec91085e2022-02-10 18:05:45 +0100942
Philipp Maier80ce71f2021-04-19 21:24:23 +0200943def boxed_heading_str(heading, width=80):
Harald Weltec91085e2022-02-10 18:05:45 +0100944 """Generate a string that contains a boxed heading."""
945 # Auto-enlarge box if heading exceeds length
946 if len(heading) > width - 4:
947 width = len(heading) + 4
Philipp Maier80ce71f2021-04-19 21:24:23 +0200948
Harald Weltec91085e2022-02-10 18:05:45 +0100949 res = "#" * width
950 fstr = "\n# %-" + str(width - 4) + "s #\n"
951 res += fstr % (heading)
952 res += "#" * width
953 return res
Harald Welte3de6ca22021-05-02 20:05:56 +0200954
955
956class DataObject(abc.ABC):
957 """A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
958 simply has any number of different TLVs that may occur in any order at any point, ISO 7816
959 has the habit of specifying TLV data but with very spcific ordering, or specific choices of
960 tags at specific points in a stream. This class tries to represent this."""
Harald Weltec91085e2022-02-10 18:05:45 +0100961
Harald Welte425038f2022-02-10 19:32:04 +0100962 def __init__(self, name: str, desc: Optional[str] = None, tag: Optional[int] = None):
Harald Welte3de6ca22021-05-02 20:05:56 +0200963 """
964 Args:
965 name: A brief, all-lowercase, underscore separated string identifier
966 desc: A human-readable description of what this DO represents
967 tag : The tag associated with this DO
968 """
969 self.name = name
970 self.desc = desc
971 self.tag = tag
972 self.decoded = None
973 self.encoded = None
974
975 def __str__(self):
976 return self.name
977
Harald Welte50368772022-02-10 15:22:22 +0100978 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +0200979 return '%s(%s)' % (self.__class__, self.name)
980
Harald Welte50368772022-02-10 15:22:22 +0100981 def __or__(self, other) -> 'DataObjectChoice':
Harald Welte3de6ca22021-05-02 20:05:56 +0200982 """OR-ing DataObjects together renders a DataObjectChoice."""
983 if isinstance(other, DataObject):
984 # DataObject | DataObject = DataObjectChoice
985 return DataObjectChoice(None, members=[self, other])
986 else:
987 raise TypeError
988
Harald Welte50368772022-02-10 15:22:22 +0100989 def __add__(self, other) -> 'DataObjectCollection':
Harald Welte3de6ca22021-05-02 20:05:56 +0200990 """ADD-ing DataObjects together renders a DataObjectCollection."""
991 if isinstance(other, DataObject):
992 # DataObject + DataObject = DataObjectCollectin
993 return DataObjectCollection(None, members=[self, other])
Harald Welte50368772022-02-10 15:22:22 +0100994 else:
995 raise TypeError
Harald Welte3de6ca22021-05-02 20:05:56 +0200996
Harald Welte50368772022-02-10 15:22:22 +0100997 def _compute_tag(self) -> int:
Harald Welte3de6ca22021-05-02 20:05:56 +0200998 """Compute the tag (sometimes the tag encodes part of the value)."""
999 return self.tag
1000
Harald Welte50368772022-02-10 15:22:22 +01001001 def to_dict(self) -> dict:
Harald Welte3de6ca22021-05-02 20:05:56 +02001002 """Return a dict in form "name: decoded_value" """
1003 return {self.name: self.decoded}
1004
1005 @abc.abstractmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001006 def from_bytes(self, do: bytes):
Harald Welte3de6ca22021-05-02 20:05:56 +02001007 """Parse the value part of the DO into the internal state of this instance.
1008 Args:
1009 do : binary encoded bytes
1010 """
1011
1012 @abc.abstractmethod
Harald Welte50368772022-02-10 15:22:22 +01001013 def to_bytes(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001014 """Encode the internal state of this instance into the TLV value part.
1015 Returns:
1016 binary bytes encoding the internal state
1017 """
1018
Harald Weltec91085e2022-02-10 18:05:45 +01001019 def from_tlv(self, do: bytes) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001020 """Parse binary TLV representation into internal state. The resulting decoded
1021 representation is _not_ returned, but just internalized in the object instance!
1022 Args:
1023 do : input bytes containing TLV-encoded representation
1024 Returns:
1025 bytes remaining at end of 'do' after parsing one TLV/DO.
1026 """
1027 if do[0] != self.tag:
Harald Weltec91085e2022-02-10 18:05:45 +01001028 raise ValueError('%s: Can only decode tag 0x%02x' %
1029 (self, self.tag))
Harald Welte3de6ca22021-05-02 20:05:56 +02001030 length = do[1]
1031 val = do[2:2+length]
1032 self.from_bytes(val)
1033 # return remaining bytes
1034 return do[2+length:]
1035
Harald Welte50368772022-02-10 15:22:22 +01001036 def to_tlv(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001037 """Encode internal representation to binary TLV.
1038 Returns:
1039 bytes encoded in TLV format.
1040 """
1041 val = self.to_bytes()
Harald Welte785d4842022-04-05 14:24:22 +02001042 return bertlv_encode_tag(self._compute_tag()) + bertlv_encode_len(len(val)) + val
Harald Welte3de6ca22021-05-02 20:05:56 +02001043
1044 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001045 def decode(self, binary: bytes) -> Tuple[dict, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001046 """Decode a single DOs from the input data.
1047 Args:
1048 binary : binary bytes of encoded data
1049 Returns:
1050 tuple of (decoded_result, binary_remainder)
1051 """
1052 tag = binary[0]
1053 if tag != self.tag:
1054 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected 0x%02x' %
1055 (self, tag, binary, self.tag))
1056 remainder = self.from_tlv(binary)
1057 return (self.to_dict(), remainder)
1058
1059 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001060 def encode(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001061 return self.to_tlv()
1062
Harald Weltec91085e2022-02-10 18:05:45 +01001063
Harald Welte3de6ca22021-05-02 20:05:56 +02001064class TL0_DataObject(DataObject):
1065 """Data Object that has Tag, Len=0 and no Value part."""
Harald Weltec91085e2022-02-10 18:05:45 +01001066
1067 def __init__(self, name: str, desc: str, tag: int, val=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001068 super().__init__(name, desc, tag)
1069 self.val = val
1070
Harald Weltec91085e2022-02-10 18:05:45 +01001071 def from_bytes(self, binary: bytes):
Harald Welte3de6ca22021-05-02 20:05:56 +02001072 if len(binary) != 0:
1073 raise ValueError
1074 self.decoded = self.val
1075
Harald Welte50368772022-02-10 15:22:22 +01001076 def to_bytes(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001077 return b''
1078
1079
1080class DataObjectCollection:
1081 """A DataObjectCollection consits of multiple Data Objects identified by their tags.
1082 A given encoded DO may contain any of them in any order, and may contain multiple instances
1083 of each DO."""
Harald Weltec91085e2022-02-10 18:05:45 +01001084
Harald Welte425038f2022-02-10 19:32:04 +01001085 def __init__(self, name: str, desc: Optional[str] = None, members=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001086 self.name = name
1087 self.desc = desc
1088 self.members = members or []
1089 self.members_by_tag = {}
1090 self.members_by_name = {}
Harald Weltec91085e2022-02-10 18:05:45 +01001091 self.members_by_tag = {m.tag: m for m in members}
1092 self.members_by_name = {m.name: m for m in members}
Harald Welte3de6ca22021-05-02 20:05:56 +02001093
Harald Welte50368772022-02-10 15:22:22 +01001094 def __str__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001095 member_strs = [str(x) for x in self.members]
1096 return '%s(%s)' % (self.name, ','.join(member_strs))
1097
Harald Welte50368772022-02-10 15:22:22 +01001098 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001099 member_strs = [repr(x) for x in self.members]
1100 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1101
Harald Welte50368772022-02-10 15:22:22 +01001102 def __add__(self, other) -> 'DataObjectCollection':
Harald Welte3de6ca22021-05-02 20:05:56 +02001103 """Extending DataCollections with other DataCollections or DataObjects."""
1104 if isinstance(other, DataObjectCollection):
1105 # adding one collection to another
1106 members = self.members + other.members
1107 return DataObjectCollection(self.name, self.desc, members)
1108 elif isinstance(other, DataObject):
1109 # adding a member to a collection
1110 return DataObjectCollection(self.name, self.desc, self.members + [other])
1111 else:
1112 raise TypeError
1113
1114 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001115 def decode(self, binary: bytes) -> Tuple[List, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001116 """Decode any number of DOs from the collection until the end of the input data,
1117 or uninitialized memory (0xFF) is found.
1118 Args:
1119 binary : binary bytes of encoded data
1120 Returns:
1121 tuple of (decoded_result, binary_remainder)
1122 """
1123 res = []
1124 remainder = binary
1125 # iterate until no binary trailer is left
1126 while len(remainder):
1127 tag = remainder[0]
Harald Weltec91085e2022-02-10 18:05:45 +01001128 if tag == 0xff: # uninitialized memory at the end?
Harald Welte3de6ca22021-05-02 20:05:56 +02001129 return (res, remainder)
1130 if not tag in self.members_by_tag:
1131 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1132 (self, tag, remainder, self.members_by_tag.keys()))
1133 obj = self.members_by_tag[tag]
1134 # DO from_tlv returns remainder of binary
1135 remainder = obj.from_tlv(remainder)
1136 # collect our results
1137 res.append(obj.to_dict())
1138 return (res, remainder)
1139
1140 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001141 def encode(self, decoded) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001142 res = bytearray()
1143 for i in decoded:
1144 obj = self.members_by_name(i[0])
1145 res.append(obj.to_tlv())
1146 return res
1147
Harald Weltec91085e2022-02-10 18:05:45 +01001148
Harald Welte3de6ca22021-05-02 20:05:56 +02001149class DataObjectChoice(DataObjectCollection):
1150 """One Data Object from within a choice, identified by its tag.
1151 This means that exactly one member of the choice must occur, and which one occurs depends
1152 on the tag."""
Harald Weltec91085e2022-02-10 18:05:45 +01001153
Harald Welte3de6ca22021-05-02 20:05:56 +02001154 def __add__(self, other):
1155 """We overload the add operator here to avoid inheriting it from DataObjecCollection."""
1156 raise TypeError
1157
Harald Welte50368772022-02-10 15:22:22 +01001158 def __or__(self, other) -> 'DataObjectChoice':
Harald Welte3de6ca22021-05-02 20:05:56 +02001159 """OR-ing a Choice to another choice extends the choice, as does OR-ing a DataObject."""
1160 if isinstance(other, DataObjectChoice):
1161 # adding one collection to another
1162 members = self.members + other.members
1163 return DataObjectChoice(self.name, self.desc, members)
1164 elif isinstance(other, DataObject):
1165 # adding a member to a collection
1166 return DataObjectChoice(self.name, self.desc, self.members + [other])
1167 else:
1168 raise TypeError
1169
1170 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001171 def decode(self, binary: bytes) -> Tuple[dict, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001172 """Decode a single DOs from the choice based on the tag.
1173 Args:
1174 binary : binary bytes of encoded data
1175 Returns:
1176 tuple of (decoded_result, binary_remainder)
1177 """
1178 tag = binary[0]
1179 if tag == 0xff:
1180 return (None, binary)
1181 if not tag in self.members_by_tag:
1182 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1183 (self, tag, binary, self.members_by_tag.keys()))
1184 obj = self.members_by_tag[tag]
1185 remainder = obj.from_tlv(binary)
1186 return (obj.to_dict(), remainder)
1187
1188 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001189 def encode(self, decoded) -> bytes:
Harald Welte785d4842022-04-05 14:24:22 +02001190 obj = self.members_by_name[list(decoded)[0]]
1191 obj.decoded = list(decoded.values())[0]
Harald Welte3de6ca22021-05-02 20:05:56 +02001192 return obj.to_tlv()
1193
Harald Weltec91085e2022-02-10 18:05:45 +01001194
Harald Welte3de6ca22021-05-02 20:05:56 +02001195class DataObjectSequence:
1196 """A sequence of DataObjects or DataObjectChoices. This allows us to express a certain
1197 ordered sequence of DOs or choices of DOs that have to appear as per the specification.
1198 By wrapping them into this formal DataObjectSequence, we can offer convenience methods
1199 for encoding or decoding an entire sequence."""
Harald Weltec91085e2022-02-10 18:05:45 +01001200
Harald Welte425038f2022-02-10 19:32:04 +01001201 def __init__(self, name: str, desc: Optional[str] = None, sequence=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001202 self.sequence = sequence or []
1203 self.name = name
1204 self.desc = desc
1205
Harald Welte50368772022-02-10 15:22:22 +01001206 def __str__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001207 member_strs = [str(x) for x in self.sequence]
1208 return '%s(%s)' % (self.name, ','.join(member_strs))
1209
Harald Welte50368772022-02-10 15:22:22 +01001210 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001211 member_strs = [repr(x) for x in self.sequence]
1212 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1213
Harald Welte50368772022-02-10 15:22:22 +01001214 def __add__(self, other) -> 'DataObjectSequence':
Harald Welte3de6ca22021-05-02 20:05:56 +02001215 """Add (append) a DataObject or DataObjectChoice to the sequence."""
1216 if isinstance(other, 'DataObject'):
Harald Weltec91085e2022-02-10 18:05:45 +01001217 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
Harald Welte3de6ca22021-05-02 20:05:56 +02001218 elif isinstance(other, 'DataObjectChoice'):
Harald Weltec91085e2022-02-10 18:05:45 +01001219 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
Harald Welte3de6ca22021-05-02 20:05:56 +02001220 elif isinstance(other, 'DataObjectSequence'):
Harald Weltec91085e2022-02-10 18:05:45 +01001221 return DataObjectSequence(self.name, self.desc, self.sequence + other.sequence)
Harald Welte3de6ca22021-05-02 20:05:56 +02001222
1223 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001224 def decode(self, binary: bytes) -> Tuple[list, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001225 """Decode a sequence by calling the decoder of each element in the sequence.
1226 Args:
1227 binary : binary bytes of encoded data
1228 Returns:
1229 tuple of (decoded_result, binary_remainder)
1230 """
1231 remainder = binary
1232 res = []
1233 for e in self.sequence:
1234 (r, remainder) = e.decode(remainder)
1235 if r:
1236 res.append(r)
1237 return (res, remainder)
1238
1239 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001240 def decode_multi(self, do: bytes) -> Tuple[list, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001241 """Decode multiple occurrences of the sequence from the binary input data.
1242 Args:
1243 do : binary input data to be decoded
1244 Returns:
1245 list of results of the decoder of this sequences
1246 """
1247 remainder = do
1248 res = []
1249 while len(remainder):
1250 (r, remainder2) = self.decode(remainder)
1251 if r:
1252 res.append(r)
1253 if len(remainder2) < len(remainder):
1254 remainder = remainder2
1255 else:
1256 remainder = remainder2
1257 break
1258 return (res, remainder)
1259
1260 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001261 def encode(self, decoded) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001262 """Encode a sequence by calling the encoder of each element in the sequence."""
1263 encoded = bytearray()
1264 i = 0
1265 for e in self.sequence:
1266 encoded += e.encode(decoded[i])
1267 i += 1
1268 return encoded
Harald Welte90441432021-05-02 21:28:12 +02001269
Harald Welte0dcdfbf2022-04-05 14:42:48 +02001270 def encode_multi(self, decoded) -> bytes:
1271 """Encode multiple occurrences of the sequence from the decoded input data.
1272 Args:
1273 decoded : list of json-serializable input data; one sequence per list item
1274 Returns:
1275 binary encoded output data
1276 """
1277 encoded = bytearray()
1278 for d in decoded:
1279 encoded += self.encode(d)
1280 return encoded
1281
Harald Weltec91085e2022-02-10 18:05:45 +01001282
Harald Welte90441432021-05-02 21:28:12 +02001283class CardCommand:
1284 """A single card command / instruction."""
Harald Weltec91085e2022-02-10 18:05:45 +01001285
Harald Welte90441432021-05-02 21:28:12 +02001286 def __init__(self, name, ins, cla_list=None, desc=None):
1287 self.name = name
1288 self.ins = ins
1289 self.cla_list = cla_list or []
1290 self.cla_list = [x.lower() for x in self.cla_list]
1291 self.desc = desc
1292
1293 def __str__(self):
1294 return self.name
1295
1296 def __repr__(self):
1297 return '%s(INS=%02x,CLA=%s)' % (self.name, self.ins, self.cla_list)
1298
1299 def match_cla(self, cla):
1300 """Does the given CLA match the CLA list of the command?."""
1301 if not isinstance(cla, str):
1302 cla = '%02u' % cla
1303 cla = cla.lower()
1304 for cla_match in self.cla_list:
1305 cla_masked = ""
1306 for i in range(0, 2):
1307 if cla_match[i] == 'x':
1308 cla_masked += 'x'
1309 else:
1310 cla_masked += cla[i]
1311 if cla_masked == cla_match:
1312 return True
1313 return False
1314
1315
1316class CardCommandSet:
1317 """A set of card instructions, typically specified within one spec."""
Harald Weltec91085e2022-02-10 18:05:45 +01001318
Harald Welte90441432021-05-02 21:28:12 +02001319 def __init__(self, name, cmds=[]):
1320 self.name = name
Harald Weltec91085e2022-02-10 18:05:45 +01001321 self.cmds = {c.ins: c for c in cmds}
Harald Welte90441432021-05-02 21:28:12 +02001322
1323 def __str__(self):
1324 return self.name
1325
1326 def __getitem__(self, idx):
1327 return self.cmds[idx]
1328
1329 def __add__(self, other):
1330 if isinstance(other, CardCommand):
1331 if other.ins in self.cmds:
1332 raise ValueError('%s: INS 0x%02x already defined: %s' %
1333 (self, other.ins, self.cmds[other.ins]))
1334 self.cmds[other.ins] = other
1335 elif isinstance(other, CardCommandSet):
1336 for c in other.cmds.keys():
1337 self.cmds[c] = other.cmds[c]
1338 else:
Harald Weltec91085e2022-02-10 18:05:45 +01001339 raise ValueError(
1340 '%s: Unsupported type to add operator: %s' % (self, other))
Harald Welte90441432021-05-02 21:28:12 +02001341
1342 def lookup(self, ins, cla=None):
1343 """look-up the command within the CommandSet."""
1344 ins = int(ins)
1345 if not ins in self.cmds:
1346 return None
1347 cmd = self.cmds[ins]
1348 if cla and not cmd.match_cla(cla):
1349 return None
1350 return cmd
Philipp Maiera028c7d2021-11-08 16:12:03 +01001351
Harald Weltec91085e2022-02-10 18:05:45 +01001352
Philipp Maiera028c7d2021-11-08 16:12:03 +01001353def all_subclasses(cls) -> set:
Harald Weltec91085e2022-02-10 18:05:45 +01001354 """Recursively get all subclasses of a specified class"""
1355 return set(cls.__subclasses__()).union([s for c in cls.__subclasses__() for s in all_subclasses(c)])
Harald Weltef9ea63e2023-11-01 23:35:31 +01001356
1357def is_hexstr_or_decimal(instr: str) -> str:
1358 """Method that can be used as 'type' in argparse.add_argument() to validate the value consists of
1359 [hexa]decimal digits only."""
1360 if instr.isdecimal():
1361 return instr
1362 if not all(c in string.hexdigits for c in instr):
1363 raise ValueError('Input must be [hexa]decimal')
1364 if len(instr) & 1:
1365 raise ValueError('Input has un-even number of hex digits')
1366 return instr
Harald Welte4e59d892023-11-01 23:40:07 +01001367
1368def is_hexstr(instr: str) -> str:
1369 """Method that can be used as 'type' in argparse.add_argument() to validate the value consists of
1370 an even sequence of hexadecimal digits only."""
1371 if not all(c in string.hexdigits for c in instr):
1372 raise ValueError('Input must be hexadecimal')
1373 if len(instr) & 1:
1374 raise ValueError('Input has un-even number of hex digits')
1375 return instr
Harald Welte1c849f82023-11-01 23:48:28 +01001376
1377def is_decimal(instr: str) -> str:
1378 """Method that can be used as 'type' in argparse.add_argument() to validate the value consists of
1379 an even sequence of decimal digits only."""
1380 if not instr.isdecimal():
1381 raise ValueError('Input must decimal')
1382 return instr