blob: aac987714dc37dbdeb9c62b6528033c26c0e8065 [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"},
489 {'bit': 14, 'name': "E-UTRAN"},
490 {'bit': 11, 'name': "NG-RAN"},
491 {'bit': 7, 'name': "GSM"},
492 {'bit': 6, 'name': "GSM COMPACT"},
493 {'bit': 5, 'name': "cdma2000 HRPD"},
494 {'bit': 4, 'name': "cdma2000 1xRTT"},
495 ]
496 ia = h2i(twohexbytes)
497 u16t = (ia[0] << 8) | ia[1]
498 sel = []
499 for a in act_list:
500 if u16t & (1 << a['bit']):
501 if a['name'] == "E-UTRAN":
502 # The Access technology identifier of E-UTRAN
503 # allows a more detailed specification:
504 if u16t & (1 << 13) and u16t & (1 << 12):
505 sel.append("E-UTRAN WB-S1")
506 sel.append("E-UTRAN NB-S1")
507 elif u16t & (1 << 13):
508 sel.append("E-UTRAN WB-S1")
509 elif u16t & (1 << 12):
510 sel.append("E-UTRAN NB-S1")
511 else:
512 sel.append("E-UTRAN")
513 else:
514 sel.append(a['name'])
515 return sel
516
517
518def dec_xplmn_w_act(fivehexbytes: Hexstr) -> Dict[str, Any]:
519 res = {'mcc': "0", 'mnc': "0", 'act': []}
520 plmn_chars = 6
521 act_chars = 4
522 # first three bytes (six ascii hex chars)
523 plmn_str = fivehexbytes[:plmn_chars]
524 # two bytes after first three bytes
525 act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars]
526 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
527 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
528 res['act'] = dec_act(act_str)
529 return res
530
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100531
Harald Weltec91085e2022-02-10 18:05:45 +0100532def dec_xplmn(threehexbytes: Hexstr) -> dict:
533 res = {'mcc': 0, 'mnc': 0, 'act': []}
534 plmn_chars = 6
535 # first three bytes (six ascii hex chars)
536 plmn_str = threehexbytes[:plmn_chars]
Matan Perelman60951b02023-06-01 17:39:04 +0300537 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
538 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
Harald Weltec91085e2022-02-10 18:05:45 +0100539 return res
Harald Welteca673942020-06-03 15:19:40 +0200540
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900541
Harald Weltec91085e2022-02-10 18:05:45 +0100542def derive_milenage_opc(ki_hex: Hexstr, op_hex: Hexstr) -> Hexstr:
543 """
544 Run the milenage algorithm to calculate OPC from Ki and OP
545 """
Harald Welted75fa3f2023-05-31 20:47:55 +0200546 from Cryptodome.Cipher import AES
Harald Weltec91085e2022-02-10 18:05:45 +0100547 # pylint: disable=no-name-in-module
Harald Welted75fa3f2023-05-31 20:47:55 +0200548 from Cryptodome.Util.strxor import strxor
Harald Weltec91085e2022-02-10 18:05:45 +0100549
550 # We pass in hex string and now need to work on bytes
551 ki_bytes = bytes(h2b(ki_hex))
552 op_bytes = bytes(h2b(op_hex))
553 aes = AES.new(ki_bytes, AES.MODE_ECB)
554 opc_bytes = aes.encrypt(op_bytes)
555 return b2h(strxor(opc_bytes, op_bytes))
556
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900557
Harald Welte52255572021-04-03 09:56:32 +0200558def calculate_luhn(cc) -> int:
Harald Weltec91085e2022-02-10 18:05:45 +0100559 """
560 Calculate Luhn checksum used in e.g. ICCID and IMEI
561 """
562 num = list(map(int, str(cc)))
563 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10))
564 for d in num[::-2]]) % 10
565 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200566
Philipp Maier7592eee2019-09-12 13:03:23 +0200567
Harald Weltec91085e2022-02-10 18:05:45 +0100568def mcc_from_imsi(imsi: str) -> Optional[str]:
569 """
570 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
571 """
572 if imsi == None:
573 return None
Philipp Maier7592eee2019-09-12 13:03:23 +0200574
Harald Weltec91085e2022-02-10 18:05:45 +0100575 if len(imsi) > 3:
576 return imsi[:3]
577 else:
578 return None
Philipp Maier7592eee2019-09-12 13:03:23 +0200579
Supreeth Herled24f1632019-11-30 10:37:09 +0100580
Harald Weltec91085e2022-02-10 18:05:45 +0100581def mnc_from_imsi(imsi: str, long: bool = False) -> Optional[str]:
582 """
583 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
584 """
585 if imsi == None:
586 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100587
Harald Weltec91085e2022-02-10 18:05:45 +0100588 if len(imsi) > 3:
589 if long:
590 return imsi[3:6]
591 else:
592 return imsi[3:5]
593 else:
594 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100595
Supreeth Herled24f1632019-11-30 10:37:09 +0100596
Harald Weltec91085e2022-02-10 18:05:45 +0100597def derive_mcc(digit1: int, digit2: int, digit3: int) -> int:
598 """
599 Derive decimal representation of the MCC (Mobile Country Code)
600 from three given digits.
601 """
Supreeth Herled24f1632019-11-30 10:37:09 +0100602
Harald Weltec91085e2022-02-10 18:05:45 +0100603 mcc = 0
Supreeth Herled24f1632019-11-30 10:37:09 +0100604
Harald Weltec91085e2022-02-10 18:05:45 +0100605 if digit1 != 0x0f:
606 mcc += digit1 * 100
607 if digit2 != 0x0f:
608 mcc += digit2 * 10
609 if digit3 != 0x0f:
610 mcc += digit3
Supreeth Herled24f1632019-11-30 10:37:09 +0100611
Harald Weltec91085e2022-02-10 18:05:45 +0100612 return mcc
Supreeth Herled24f1632019-11-30 10:37:09 +0100613
Supreeth Herled24f1632019-11-30 10:37:09 +0100614
Harald Weltec91085e2022-02-10 18:05:45 +0100615def derive_mnc(digit1: int, digit2: int, digit3: int = 0x0f) -> int:
616 """
617 Derive decimal representation of the MNC (Mobile Network Code)
618 from two or (optionally) three given digits.
619 """
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100620
Harald Weltec91085e2022-02-10 18:05:45 +0100621 mnc = 0
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100622
Harald Weltec91085e2022-02-10 18:05:45 +0100623 # 3-rd digit is optional for the MNC. If present
624 # the algorythm is the same as for the MCC.
625 if digit3 != 0x0f:
626 return derive_mcc(digit1, digit2, digit3)
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100627
Harald Weltec91085e2022-02-10 18:05:45 +0100628 if digit1 != 0x0f:
629 mnc += digit1 * 10
630 if digit2 != 0x0f:
631 mnc += digit2
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100632
Harald Weltec91085e2022-02-10 18:05:45 +0100633 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100634
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100635
Harald Weltec91085e2022-02-10 18:05:45 +0100636def dec_msisdn(ef_msisdn: Hexstr) -> Optional[Tuple[int, int, Optional[str]]]:
637 """
638 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
639 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
640 """
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100641
Harald Weltec91085e2022-02-10 18:05:45 +0100642 # Convert from str to (kind of) 'bytes'
643 ef_msisdn = h2b(ef_msisdn)
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100644
Harald Weltec91085e2022-02-10 18:05:45 +0100645 # Make sure mandatory fields are present
646 if len(ef_msisdn) < 14:
647 raise ValueError("EF.MSISDN is too short")
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100648
Harald Weltec91085e2022-02-10 18:05:45 +0100649 # Skip optional Alpha Identifier
650 xlen = len(ef_msisdn) - 14
651 msisdn_lhv = ef_msisdn[xlen:]
Supreeth Herle5a541012019-12-22 08:59:16 +0100652
Harald Weltec91085e2022-02-10 18:05:45 +0100653 # Parse the length (in bytes) of the BCD encoded number
654 bcd_len = msisdn_lhv[0]
655 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
656 if bcd_len == 0xff:
657 return None
658 elif bcd_len > 11 or bcd_len < 1:
659 raise ValueError(
660 "Length of MSISDN (%d bytes) is out of range" % bcd_len)
Supreeth Herle5a541012019-12-22 08:59:16 +0100661
Harald Weltec91085e2022-02-10 18:05:45 +0100662 # Parse ToN / NPI
663 ton = (msisdn_lhv[1] >> 4) & 0x07
664 npi = msisdn_lhv[1] & 0x0f
665 bcd_len -= 1
Supreeth Herle5a541012019-12-22 08:59:16 +0100666
Harald Weltec91085e2022-02-10 18:05:45 +0100667 # No MSISDN?
668 if not bcd_len:
669 return (npi, ton, None)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200670
Harald Weltec91085e2022-02-10 18:05:45 +0100671 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
672 # International number 10.5.118/3GPP TS 24.008
673 if ton == 0x01:
674 msisdn = '+' + msisdn
Supreeth Herle5a541012019-12-22 08:59:16 +0100675
Harald Weltec91085e2022-02-10 18:05:45 +0100676 return (npi, ton, msisdn)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200677
Supreeth Herle5a541012019-12-22 08:59:16 +0100678
Harald Weltec91085e2022-02-10 18:05:45 +0100679def enc_msisdn(msisdn: str, npi: int = 0x01, ton: int = 0x03) -> Hexstr:
680 """
681 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
682 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
683 will not contain the optional Alpha Identifier at the beginning.)
Supreeth Herle5a541012019-12-22 08:59:16 +0100684
Harald Weltec91085e2022-02-10 18:05:45 +0100685 Default NPI / ToN values:
686 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
687 - ToN: network specific or international number (if starts with '+').
688 """
689
690 # If no MSISDN is supplied then encode the file contents as all "ff"
691 if msisdn == "" or msisdn == "+":
692 return "ff" * 14
693
694 # Leading '+' indicates International Number
695 if msisdn[0] == '+':
696 msisdn = msisdn[1:]
697 ton = 0x01
698
699 # An MSISDN must not exceed 20 digits
700 if len(msisdn) > 20:
701 raise ValueError("msisdn must not be longer than 20 digits")
702
703 # Append 'f' padding if number of digits is odd
704 if len(msisdn) % 2 > 0:
705 msisdn += 'f'
706
707 # BCD length also includes NPI/ToN header
708 bcd_len = len(msisdn) // 2 + 1
709 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
710 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
711
712 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200713
Supreeth Herle441c4a72020-03-24 10:19:15 +0100714
Harald Weltec91085e2022-02-10 18:05:45 +0100715def is_hex(string: str, minlen: int = 2, maxlen: Optional[int] = None) -> bool:
716 """
717 Check if a string is a valid hexstring
718 """
Philipp Maier47236502021-03-09 21:28:25 +0100719
Harald Weltec91085e2022-02-10 18:05:45 +0100720 # Filter obviously bad strings
721 if not string:
722 return False
723 if len(string) < minlen or minlen < 2:
724 return False
725 if len(string) % 2:
726 return False
727 if maxlen and len(string) > maxlen:
728 return False
Philipp Maier47236502021-03-09 21:28:25 +0100729
Harald Weltec91085e2022-02-10 18:05:45 +0100730 # Try actual encoding to be sure
731 try:
732 try_encode = h2b(string)
733 return True
734 except:
735 return False
Philipp Maiere8536c02020-05-11 21:35:01 +0200736
Philipp Maiere8536c02020-05-11 21:35:01 +0200737
Harald Weltec91085e2022-02-10 18:05:45 +0100738def sanitize_pin_adm(pin_adm, pin_adm_hex=None) -> Hexstr:
739 """
740 The ADM pin can be supplied either in its hexadecimal form or as
741 ascii string. This function checks the supplied opts parameter and
742 returns the pin_adm as hex encoded string, regardless in which form
743 it was originally supplied by the user
744 """
Philipp Maiere8536c02020-05-11 21:35:01 +0200745
Harald Weltec91085e2022-02-10 18:05:45 +0100746 if pin_adm is not None:
747 if len(pin_adm) <= 8:
748 pin_adm = ''.join(['%02x' % (ord(x)) for x in pin_adm])
749 pin_adm = rpad(pin_adm, 16)
Philipp Maiere8536c02020-05-11 21:35:01 +0200750
Harald Weltec91085e2022-02-10 18:05:45 +0100751 else:
752 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
753
754 if pin_adm_hex is not None:
755 if len(pin_adm_hex) == 16:
756 pin_adm = pin_adm_hex
757 # Ensure that it's hex-encoded
758 try:
759 try_encode = h2b(pin_adm)
760 except ValueError:
761 raise ValueError(
762 "PIN-ADM needs to be hex encoded using this option")
763 else:
764 raise ValueError(
765 "PIN-ADM needs to be exactly 16 digits (hex encoded)")
766
767 return pin_adm
768
Philipp Maiere8536c02020-05-11 21:35:01 +0200769
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100770def get_addr_type(addr):
Harald Weltec91085e2022-02-10 18:05:45 +0100771 """
772 Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
773 Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given)
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100774
Harald Weltec91085e2022-02-10 18:05:45 +0100775 TODO: Handle IPv6
776 """
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100777
Harald Weltec91085e2022-02-10 18:05:45 +0100778 # Empty address string
779 if not len(addr):
780 return None
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100781
Harald Weltec91085e2022-02-10 18:05:45 +0100782 addr_list = addr.split('.')
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100783
Harald Weltec91085e2022-02-10 18:05:45 +0100784 # Check for IPv4/IPv6
785 try:
786 import ipaddress
787 # Throws ValueError if addr is not correct
788 ipa = ipaddress.ip_address(addr)
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100789
Harald Weltec91085e2022-02-10 18:05:45 +0100790 if ipa.version == 4:
791 return 0x01
792 elif ipa.version == 6:
793 return 0x02
794 except Exception as e:
795 invalid_ipv4 = True
796 for i in addr_list:
797 # Invalid IPv4 may qualify for a valid FQDN, so make check here
798 # e.g. 172.24.15.300
799 import re
800 if not re.match('^[0-9_]+$', i):
801 invalid_ipv4 = False
802 break
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100803
Harald Weltec91085e2022-02-10 18:05:45 +0100804 if invalid_ipv4:
805 return None
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100806
Harald Weltec91085e2022-02-10 18:05:45 +0100807 fqdn_flag = True
808 for i in addr_list:
809 # Only Alpha-numeric characters and hyphen - RFC 1035
810 import re
811 if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
812 fqdn_flag = False
813 break
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100814
Harald Weltec91085e2022-02-10 18:05:45 +0100815 # FQDN
816 if fqdn_flag:
817 return 0x00
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100818
Harald Weltec91085e2022-02-10 18:05:45 +0100819 return None
Harald Welte67d551a2021-01-21 14:50:01 +0100820
Philipp Maier5d3e2592021-02-22 17:22:16 +0100821
Harald Weltec91085e2022-02-10 18:05:45 +0100822def sw_match(sw: str, pattern: str) -> bool:
823 """Match given SW against given pattern."""
824 # Create a masked version of the returned status word
825 sw_lower = sw.lower()
826 sw_masked = ""
827 for i in range(0, 4):
828 if pattern[i] == '?':
829 sw_masked = sw_masked + '?'
830 elif pattern[i] == 'x':
831 sw_masked = sw_masked + 'x'
832 else:
833 sw_masked = sw_masked + sw_lower[i]
834 # Compare the masked version against the pattern
835 return sw_masked == pattern
Harald Welteee3501f2021-04-02 13:00:18 +0200836
Harald Weltec91085e2022-02-10 18:05:45 +0100837
838def tabulate_str_list(str_list, width: int = 79, hspace: int = 2, lspace: int = 1,
839 align_left: bool = True) -> str:
840 """Pretty print a list of strings into a tabulated form.
841
842 Args:
843 width : total width in characters per line
844 space : horizontal space between cells
845 lspace : number of spaces before row
846 align_lef : Align text to the left side
847 Returns:
848 multi-line string containing formatted table
849 """
850 if str_list == None:
851 return ""
852 if len(str_list) <= 0:
853 return ""
854 longest_str = max(str_list, key=len)
855 cellwith = len(longest_str) + hspace
856 cols = width // cellwith
857 rows = (len(str_list) - 1) // cols + 1
858 table = []
859 for i in iter(range(rows)):
860 str_list_row = str_list[i::rows]
861 if (align_left):
862 format_str_cell = '%%-%ds'
863 else:
864 format_str_cell = '%%%ds'
865 format_str_row = (format_str_cell % cellwith) * len(str_list_row)
866 format_str_row = (" " * lspace) + format_str_row
867 table.append(format_str_row % tuple(str_list_row))
868 return '\n'.join(table)
869
Harald Welte5e749a72021-04-10 17:18:17 +0200870
Harald Welte917d98c2021-04-21 11:51:25 +0200871def auto_int(x):
872 """Helper function for argparse to accept hexadecimal integers."""
873 return int(x, 0)
874
Harald Weltec91085e2022-02-10 18:05:45 +0100875
Philipp Maier40ea4a42022-06-02 14:45:41 +0200876def expand_hex(hexstring, length):
877 """Expand a given hexstring to a specified length by replacing "." or ".."
878 with a filler that is derived from the neighboring nibbles respective
879 bytes. Usually this will be the nibble respective byte before "." or
880 "..", execpt when the string begins with "." or "..", then the nibble
881 respective byte after "." or ".." is used.". In case the string cannot
882 be expanded for some reason, the input string is returned unmodified.
883
884 Args:
885 hexstring : hexstring to expand
886 length : desired length of the resulting hexstring.
887 Returns:
888 expanded hexstring
889 """
890
891 # expand digit aligned
892 if hexstring.count(".") == 1:
893 pos = hexstring.index(".")
894 if pos > 0:
895 filler = hexstring[pos - 1]
896 else:
897 filler = hexstring[pos + 1]
898
899 missing = length * 2 - (len(hexstring) - 1)
900 if missing <= 0:
901 return hexstring
902
903 return hexstring.replace(".", filler * missing)
904
905 # expand byte aligned
906 elif hexstring.count("..") == 1:
907 if len(hexstring) % 2:
908 return hexstring
909
910 pos = hexstring.index("..")
911
912 if pos % 2:
913 return hexstring
914
915 if pos > 1:
916 filler = hexstring[pos - 2:pos]
917 else:
918 filler = hexstring[pos + 2:pos+4]
919
920 missing = length * 2 - (len(hexstring) - 2)
921 if missing <= 0:
922 return hexstring
923
924 return hexstring.replace("..", filler * (missing // 2))
925
926 # no change
927 return hexstring
928
929
Harald Welte5e749a72021-04-10 17:18:17 +0200930class JsonEncoder(json.JSONEncoder):
931 """Extend the standard library JSONEncoder with support for more types."""
Harald Weltec91085e2022-02-10 18:05:45 +0100932
Harald Welte5e749a72021-04-10 17:18:17 +0200933 def default(self, o):
934 if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
935 return b2h(o)
936 return json.JSONEncoder.default(self, o)
Philipp Maier80ce71f2021-04-19 21:24:23 +0200937
Harald Weltec91085e2022-02-10 18:05:45 +0100938
Philipp Maier80ce71f2021-04-19 21:24:23 +0200939def boxed_heading_str(heading, width=80):
Harald Weltec91085e2022-02-10 18:05:45 +0100940 """Generate a string that contains a boxed heading."""
941 # Auto-enlarge box if heading exceeds length
942 if len(heading) > width - 4:
943 width = len(heading) + 4
Philipp Maier80ce71f2021-04-19 21:24:23 +0200944
Harald Weltec91085e2022-02-10 18:05:45 +0100945 res = "#" * width
946 fstr = "\n# %-" + str(width - 4) + "s #\n"
947 res += fstr % (heading)
948 res += "#" * width
949 return res
Harald Welte3de6ca22021-05-02 20:05:56 +0200950
951
952class DataObject(abc.ABC):
953 """A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
954 simply has any number of different TLVs that may occur in any order at any point, ISO 7816
955 has the habit of specifying TLV data but with very spcific ordering, or specific choices of
956 tags at specific points in a stream. This class tries to represent this."""
Harald Weltec91085e2022-02-10 18:05:45 +0100957
Harald Welte425038f2022-02-10 19:32:04 +0100958 def __init__(self, name: str, desc: Optional[str] = None, tag: Optional[int] = None):
Harald Welte3de6ca22021-05-02 20:05:56 +0200959 """
960 Args:
961 name: A brief, all-lowercase, underscore separated string identifier
962 desc: A human-readable description of what this DO represents
963 tag : The tag associated with this DO
964 """
965 self.name = name
966 self.desc = desc
967 self.tag = tag
968 self.decoded = None
969 self.encoded = None
970
971 def __str__(self):
972 return self.name
973
Harald Welte50368772022-02-10 15:22:22 +0100974 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +0200975 return '%s(%s)' % (self.__class__, self.name)
976
Harald Welte50368772022-02-10 15:22:22 +0100977 def __or__(self, other) -> 'DataObjectChoice':
Harald Welte3de6ca22021-05-02 20:05:56 +0200978 """OR-ing DataObjects together renders a DataObjectChoice."""
979 if isinstance(other, DataObject):
980 # DataObject | DataObject = DataObjectChoice
981 return DataObjectChoice(None, members=[self, other])
982 else:
983 raise TypeError
984
Harald Welte50368772022-02-10 15:22:22 +0100985 def __add__(self, other) -> 'DataObjectCollection':
Harald Welte3de6ca22021-05-02 20:05:56 +0200986 """ADD-ing DataObjects together renders a DataObjectCollection."""
987 if isinstance(other, DataObject):
988 # DataObject + DataObject = DataObjectCollectin
989 return DataObjectCollection(None, members=[self, other])
Harald Welte50368772022-02-10 15:22:22 +0100990 else:
991 raise TypeError
Harald Welte3de6ca22021-05-02 20:05:56 +0200992
Harald Welte50368772022-02-10 15:22:22 +0100993 def _compute_tag(self) -> int:
Harald Welte3de6ca22021-05-02 20:05:56 +0200994 """Compute the tag (sometimes the tag encodes part of the value)."""
995 return self.tag
996
Harald Welte50368772022-02-10 15:22:22 +0100997 def to_dict(self) -> dict:
Harald Welte3de6ca22021-05-02 20:05:56 +0200998 """Return a dict in form "name: decoded_value" """
999 return {self.name: self.decoded}
1000
1001 @abc.abstractmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001002 def from_bytes(self, do: bytes):
Harald Welte3de6ca22021-05-02 20:05:56 +02001003 """Parse the value part of the DO into the internal state of this instance.
1004 Args:
1005 do : binary encoded bytes
1006 """
1007
1008 @abc.abstractmethod
Harald Welte50368772022-02-10 15:22:22 +01001009 def to_bytes(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001010 """Encode the internal state of this instance into the TLV value part.
1011 Returns:
1012 binary bytes encoding the internal state
1013 """
1014
Harald Weltec91085e2022-02-10 18:05:45 +01001015 def from_tlv(self, do: bytes) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001016 """Parse binary TLV representation into internal state. The resulting decoded
1017 representation is _not_ returned, but just internalized in the object instance!
1018 Args:
1019 do : input bytes containing TLV-encoded representation
1020 Returns:
1021 bytes remaining at end of 'do' after parsing one TLV/DO.
1022 """
1023 if do[0] != self.tag:
Harald Weltec91085e2022-02-10 18:05:45 +01001024 raise ValueError('%s: Can only decode tag 0x%02x' %
1025 (self, self.tag))
Harald Welte3de6ca22021-05-02 20:05:56 +02001026 length = do[1]
1027 val = do[2:2+length]
1028 self.from_bytes(val)
1029 # return remaining bytes
1030 return do[2+length:]
1031
Harald Welte50368772022-02-10 15:22:22 +01001032 def to_tlv(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001033 """Encode internal representation to binary TLV.
1034 Returns:
1035 bytes encoded in TLV format.
1036 """
1037 val = self.to_bytes()
Harald Welte785d4842022-04-05 14:24:22 +02001038 return bertlv_encode_tag(self._compute_tag()) + bertlv_encode_len(len(val)) + val
Harald Welte3de6ca22021-05-02 20:05:56 +02001039
1040 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001041 def decode(self, binary: bytes) -> Tuple[dict, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001042 """Decode a single DOs from the input data.
1043 Args:
1044 binary : binary bytes of encoded data
1045 Returns:
1046 tuple of (decoded_result, binary_remainder)
1047 """
1048 tag = binary[0]
1049 if tag != self.tag:
1050 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected 0x%02x' %
1051 (self, tag, binary, self.tag))
1052 remainder = self.from_tlv(binary)
1053 return (self.to_dict(), remainder)
1054
1055 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001056 def encode(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001057 return self.to_tlv()
1058
Harald Weltec91085e2022-02-10 18:05:45 +01001059
Harald Welte3de6ca22021-05-02 20:05:56 +02001060class TL0_DataObject(DataObject):
1061 """Data Object that has Tag, Len=0 and no Value part."""
Harald Weltec91085e2022-02-10 18:05:45 +01001062
1063 def __init__(self, name: str, desc: str, tag: int, val=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001064 super().__init__(name, desc, tag)
1065 self.val = val
1066
Harald Weltec91085e2022-02-10 18:05:45 +01001067 def from_bytes(self, binary: bytes):
Harald Welte3de6ca22021-05-02 20:05:56 +02001068 if len(binary) != 0:
1069 raise ValueError
1070 self.decoded = self.val
1071
Harald Welte50368772022-02-10 15:22:22 +01001072 def to_bytes(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001073 return b''
1074
1075
1076class DataObjectCollection:
1077 """A DataObjectCollection consits of multiple Data Objects identified by their tags.
1078 A given encoded DO may contain any of them in any order, and may contain multiple instances
1079 of each DO."""
Harald Weltec91085e2022-02-10 18:05:45 +01001080
Harald Welte425038f2022-02-10 19:32:04 +01001081 def __init__(self, name: str, desc: Optional[str] = None, members=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001082 self.name = name
1083 self.desc = desc
1084 self.members = members or []
1085 self.members_by_tag = {}
1086 self.members_by_name = {}
Harald Weltec91085e2022-02-10 18:05:45 +01001087 self.members_by_tag = {m.tag: m for m in members}
1088 self.members_by_name = {m.name: m for m in members}
Harald Welte3de6ca22021-05-02 20:05:56 +02001089
Harald Welte50368772022-02-10 15:22:22 +01001090 def __str__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001091 member_strs = [str(x) for x in self.members]
1092 return '%s(%s)' % (self.name, ','.join(member_strs))
1093
Harald Welte50368772022-02-10 15:22:22 +01001094 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001095 member_strs = [repr(x) for x in self.members]
1096 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1097
Harald Welte50368772022-02-10 15:22:22 +01001098 def __add__(self, other) -> 'DataObjectCollection':
Harald Welte3de6ca22021-05-02 20:05:56 +02001099 """Extending DataCollections with other DataCollections or DataObjects."""
1100 if isinstance(other, DataObjectCollection):
1101 # adding one collection to another
1102 members = self.members + other.members
1103 return DataObjectCollection(self.name, self.desc, members)
1104 elif isinstance(other, DataObject):
1105 # adding a member to a collection
1106 return DataObjectCollection(self.name, self.desc, self.members + [other])
1107 else:
1108 raise TypeError
1109
1110 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001111 def decode(self, binary: bytes) -> Tuple[List, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001112 """Decode any number of DOs from the collection until the end of the input data,
1113 or uninitialized memory (0xFF) is found.
1114 Args:
1115 binary : binary bytes of encoded data
1116 Returns:
1117 tuple of (decoded_result, binary_remainder)
1118 """
1119 res = []
1120 remainder = binary
1121 # iterate until no binary trailer is left
1122 while len(remainder):
1123 tag = remainder[0]
Harald Weltec91085e2022-02-10 18:05:45 +01001124 if tag == 0xff: # uninitialized memory at the end?
Harald Welte3de6ca22021-05-02 20:05:56 +02001125 return (res, remainder)
1126 if not tag in self.members_by_tag:
1127 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1128 (self, tag, remainder, self.members_by_tag.keys()))
1129 obj = self.members_by_tag[tag]
1130 # DO from_tlv returns remainder of binary
1131 remainder = obj.from_tlv(remainder)
1132 # collect our results
1133 res.append(obj.to_dict())
1134 return (res, remainder)
1135
1136 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001137 def encode(self, decoded) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001138 res = bytearray()
1139 for i in decoded:
1140 obj = self.members_by_name(i[0])
1141 res.append(obj.to_tlv())
1142 return res
1143
Harald Weltec91085e2022-02-10 18:05:45 +01001144
Harald Welte3de6ca22021-05-02 20:05:56 +02001145class DataObjectChoice(DataObjectCollection):
1146 """One Data Object from within a choice, identified by its tag.
1147 This means that exactly one member of the choice must occur, and which one occurs depends
1148 on the tag."""
Harald Weltec91085e2022-02-10 18:05:45 +01001149
Harald Welte3de6ca22021-05-02 20:05:56 +02001150 def __add__(self, other):
1151 """We overload the add operator here to avoid inheriting it from DataObjecCollection."""
1152 raise TypeError
1153
Harald Welte50368772022-02-10 15:22:22 +01001154 def __or__(self, other) -> 'DataObjectChoice':
Harald Welte3de6ca22021-05-02 20:05:56 +02001155 """OR-ing a Choice to another choice extends the choice, as does OR-ing a DataObject."""
1156 if isinstance(other, DataObjectChoice):
1157 # adding one collection to another
1158 members = self.members + other.members
1159 return DataObjectChoice(self.name, self.desc, members)
1160 elif isinstance(other, DataObject):
1161 # adding a member to a collection
1162 return DataObjectChoice(self.name, self.desc, self.members + [other])
1163 else:
1164 raise TypeError
1165
1166 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001167 def decode(self, binary: bytes) -> Tuple[dict, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001168 """Decode a single DOs from the choice based on the tag.
1169 Args:
1170 binary : binary bytes of encoded data
1171 Returns:
1172 tuple of (decoded_result, binary_remainder)
1173 """
1174 tag = binary[0]
1175 if tag == 0xff:
1176 return (None, binary)
1177 if not tag in self.members_by_tag:
1178 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1179 (self, tag, binary, self.members_by_tag.keys()))
1180 obj = self.members_by_tag[tag]
1181 remainder = obj.from_tlv(binary)
1182 return (obj.to_dict(), remainder)
1183
1184 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001185 def encode(self, decoded) -> bytes:
Harald Welte785d4842022-04-05 14:24:22 +02001186 obj = self.members_by_name[list(decoded)[0]]
1187 obj.decoded = list(decoded.values())[0]
Harald Welte3de6ca22021-05-02 20:05:56 +02001188 return obj.to_tlv()
1189
Harald Weltec91085e2022-02-10 18:05:45 +01001190
Harald Welte3de6ca22021-05-02 20:05:56 +02001191class DataObjectSequence:
1192 """A sequence of DataObjects or DataObjectChoices. This allows us to express a certain
1193 ordered sequence of DOs or choices of DOs that have to appear as per the specification.
1194 By wrapping them into this formal DataObjectSequence, we can offer convenience methods
1195 for encoding or decoding an entire sequence."""
Harald Weltec91085e2022-02-10 18:05:45 +01001196
Harald Welte425038f2022-02-10 19:32:04 +01001197 def __init__(self, name: str, desc: Optional[str] = None, sequence=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001198 self.sequence = sequence or []
1199 self.name = name
1200 self.desc = desc
1201
Harald Welte50368772022-02-10 15:22:22 +01001202 def __str__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001203 member_strs = [str(x) for x in self.sequence]
1204 return '%s(%s)' % (self.name, ','.join(member_strs))
1205
Harald Welte50368772022-02-10 15:22:22 +01001206 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001207 member_strs = [repr(x) for x in self.sequence]
1208 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1209
Harald Welte50368772022-02-10 15:22:22 +01001210 def __add__(self, other) -> 'DataObjectSequence':
Harald Welte3de6ca22021-05-02 20:05:56 +02001211 """Add (append) a DataObject or DataObjectChoice to the sequence."""
1212 if isinstance(other, 'DataObject'):
Harald Weltec91085e2022-02-10 18:05:45 +01001213 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
Harald Welte3de6ca22021-05-02 20:05:56 +02001214 elif isinstance(other, 'DataObjectChoice'):
Harald Weltec91085e2022-02-10 18:05:45 +01001215 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
Harald Welte3de6ca22021-05-02 20:05:56 +02001216 elif isinstance(other, 'DataObjectSequence'):
Harald Weltec91085e2022-02-10 18:05:45 +01001217 return DataObjectSequence(self.name, self.desc, self.sequence + other.sequence)
Harald Welte3de6ca22021-05-02 20:05:56 +02001218
1219 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001220 def decode(self, binary: bytes) -> Tuple[list, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001221 """Decode a sequence by calling the decoder of each element in the sequence.
1222 Args:
1223 binary : binary bytes of encoded data
1224 Returns:
1225 tuple of (decoded_result, binary_remainder)
1226 """
1227 remainder = binary
1228 res = []
1229 for e in self.sequence:
1230 (r, remainder) = e.decode(remainder)
1231 if r:
1232 res.append(r)
1233 return (res, remainder)
1234
1235 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001236 def decode_multi(self, do: bytes) -> Tuple[list, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001237 """Decode multiple occurrences of the sequence from the binary input data.
1238 Args:
1239 do : binary input data to be decoded
1240 Returns:
1241 list of results of the decoder of this sequences
1242 """
1243 remainder = do
1244 res = []
1245 while len(remainder):
1246 (r, remainder2) = self.decode(remainder)
1247 if r:
1248 res.append(r)
1249 if len(remainder2) < len(remainder):
1250 remainder = remainder2
1251 else:
1252 remainder = remainder2
1253 break
1254 return (res, remainder)
1255
1256 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001257 def encode(self, decoded) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001258 """Encode a sequence by calling the encoder of each element in the sequence."""
1259 encoded = bytearray()
1260 i = 0
1261 for e in self.sequence:
1262 encoded += e.encode(decoded[i])
1263 i += 1
1264 return encoded
Harald Welte90441432021-05-02 21:28:12 +02001265
Harald Welte0dcdfbf2022-04-05 14:42:48 +02001266 def encode_multi(self, decoded) -> bytes:
1267 """Encode multiple occurrences of the sequence from the decoded input data.
1268 Args:
1269 decoded : list of json-serializable input data; one sequence per list item
1270 Returns:
1271 binary encoded output data
1272 """
1273 encoded = bytearray()
1274 for d in decoded:
1275 encoded += self.encode(d)
1276 return encoded
1277
Harald Weltec91085e2022-02-10 18:05:45 +01001278
Harald Welte90441432021-05-02 21:28:12 +02001279class CardCommand:
1280 """A single card command / instruction."""
Harald Weltec91085e2022-02-10 18:05:45 +01001281
Harald Welte90441432021-05-02 21:28:12 +02001282 def __init__(self, name, ins, cla_list=None, desc=None):
1283 self.name = name
1284 self.ins = ins
1285 self.cla_list = cla_list or []
1286 self.cla_list = [x.lower() for x in self.cla_list]
1287 self.desc = desc
1288
1289 def __str__(self):
1290 return self.name
1291
1292 def __repr__(self):
1293 return '%s(INS=%02x,CLA=%s)' % (self.name, self.ins, self.cla_list)
1294
1295 def match_cla(self, cla):
1296 """Does the given CLA match the CLA list of the command?."""
1297 if not isinstance(cla, str):
1298 cla = '%02u' % cla
1299 cla = cla.lower()
1300 for cla_match in self.cla_list:
1301 cla_masked = ""
1302 for i in range(0, 2):
1303 if cla_match[i] == 'x':
1304 cla_masked += 'x'
1305 else:
1306 cla_masked += cla[i]
1307 if cla_masked == cla_match:
1308 return True
1309 return False
1310
1311
1312class CardCommandSet:
1313 """A set of card instructions, typically specified within one spec."""
Harald Weltec91085e2022-02-10 18:05:45 +01001314
Harald Welte90441432021-05-02 21:28:12 +02001315 def __init__(self, name, cmds=[]):
1316 self.name = name
Harald Weltec91085e2022-02-10 18:05:45 +01001317 self.cmds = {c.ins: c for c in cmds}
Harald Welte90441432021-05-02 21:28:12 +02001318
1319 def __str__(self):
1320 return self.name
1321
1322 def __getitem__(self, idx):
1323 return self.cmds[idx]
1324
1325 def __add__(self, other):
1326 if isinstance(other, CardCommand):
1327 if other.ins in self.cmds:
1328 raise ValueError('%s: INS 0x%02x already defined: %s' %
1329 (self, other.ins, self.cmds[other.ins]))
1330 self.cmds[other.ins] = other
1331 elif isinstance(other, CardCommandSet):
1332 for c in other.cmds.keys():
1333 self.cmds[c] = other.cmds[c]
1334 else:
Harald Weltec91085e2022-02-10 18:05:45 +01001335 raise ValueError(
1336 '%s: Unsupported type to add operator: %s' % (self, other))
Harald Welte90441432021-05-02 21:28:12 +02001337
1338 def lookup(self, ins, cla=None):
1339 """look-up the command within the CommandSet."""
1340 ins = int(ins)
1341 if not ins in self.cmds:
1342 return None
1343 cmd = self.cmds[ins]
1344 if cla and not cmd.match_cla(cla):
1345 return None
1346 return cmd
Philipp Maiera028c7d2021-11-08 16:12:03 +01001347
Harald Weltec91085e2022-02-10 18:05:45 +01001348
Philipp Maiera028c7d2021-11-08 16:12:03 +01001349def all_subclasses(cls) -> set:
Harald Weltec91085e2022-02-10 18:05:45 +01001350 """Recursively get all subclasses of a specified class"""
1351 return set(cls.__subclasses__()).union([s for c in cls.__subclasses__() for s in all_subclasses(c)])
Harald Weltef9ea63e2023-11-01 23:35:31 +01001352
1353def is_hexstr_or_decimal(instr: str) -> str:
1354 """Method that can be used as 'type' in argparse.add_argument() to validate the value consists of
1355 [hexa]decimal digits only."""
1356 if instr.isdecimal():
1357 return instr
1358 if not all(c in string.hexdigits for c in instr):
1359 raise ValueError('Input must be [hexa]decimal')
1360 if len(instr) & 1:
1361 raise ValueError('Input has un-even number of hex digits')
1362 return instr
Harald Welte4e59d892023-11-01 23:40:07 +01001363
1364def is_hexstr(instr: str) -> str:
1365 """Method that can be used as 'type' in argparse.add_argument() to validate the value consists of
1366 an even sequence of hexadecimal digits only."""
1367 if not all(c in string.hexdigits for c in instr):
1368 raise ValueError('Input must be hexadecimal')
1369 if len(instr) & 1:
1370 raise ValueError('Input has un-even number of hex digits')
1371 return instr
Harald Welte1c849f82023-11-01 23:48:28 +01001372
1373def is_decimal(instr: str) -> str:
1374 """Method that can be used as 'type' in argparse.add_argument() to validate the value consists of
1375 an even sequence of decimal digits only."""
1376 if not instr.isdecimal():
1377 raise ValueError('Input must decimal')
1378 return instr