blob: cf95dab693444e416dae4074ad9099e54e9be1b3 [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 Welte667d5892024-01-17 19:17:24 +01009import datetime
Harald Welte5e749a72021-04-10 17:18:17 +020010from io import BytesIO
Harald Weltef5e26ae2023-07-09 16:11:46 +020011from typing import Optional, List, Dict, Any, Tuple, NewType
Harald Welte52255572021-04-03 09:56:32 +020012
Sylvain Munaut76504e02010-12-07 00:24:32 +010013# Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com>
Harald Welte5e749a72021-04-10 17:18:17 +020014# Copyright (C) 2021 Harald Welte <laforge@osmocom.org>
Sylvain Munaut76504e02010-12-07 00:24:32 +010015#
16# This program is free software: you can redistribute it and/or modify
17# it under the terms of the GNU General Public License as published by
18# the Free Software Foundation, either version 2 of the License, or
19# (at your option) any later version.
20#
21# This program is distributed in the hope that it will be useful,
22# but WITHOUT ANY WARRANTY; without even the implied warranty of
23# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24# GNU General Public License for more details.
25#
26# You should have received a copy of the GNU General Public License
27# along with this program. If not, see <http://www.gnu.org/licenses/>.
28#
29
Harald Welte52255572021-04-03 09:56:32 +020030# just to differentiate strings of hex nibbles from everything else
Harald Weltef5e26ae2023-07-09 16:11:46 +020031Hexstr = NewType('Hexstr', str)
Harald Welteab6897c2023-07-09 16:21:23 +020032SwHexstr = NewType('SwHexstr', str)
33SwMatchstr = NewType('SwMatchstr', str)
Harald Weltefdb187d2023-07-09 17:03:17 +020034ResTuple = Tuple[Hexstr, SwHexstr]
Sylvain Munaut76504e02010-12-07 00:24:32 +010035
Harald Weltec91085e2022-02-10 18:05:45 +010036def h2b(s: Hexstr) -> bytearray:
37 """convert from a string of hex nibbles to a sequence of bytes"""
38 return bytearray.fromhex(s)
Sylvain Munaut76504e02010-12-07 00:24:32 +010039
Sylvain Munaut76504e02010-12-07 00:24:32 +010040
Harald Weltec91085e2022-02-10 18:05:45 +010041def b2h(b: bytearray) -> Hexstr:
42 """convert from a sequence of bytes to a string of hex nibbles"""
43 return ''.join(['%02x' % (x) for x in b])
Sylvain Munaut76504e02010-12-07 00:24:32 +010044
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +030045
Harald Weltec91085e2022-02-10 18:05:45 +010046def h2i(s: Hexstr) -> List[int]:
47 """convert from a string of hex nibbles to a list of integers"""
48 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 +030049
Supreeth Herle7d77d2d2020-05-11 09:07:08 +020050
Harald Weltec91085e2022-02-10 18:05:45 +010051def i2h(s: List[int]) -> Hexstr:
52 """convert from a list of integers to a string of hex nibbles"""
53 return ''.join(['%02x' % (x) for x in s])
Sylvain Munaut76504e02010-12-07 00:24:32 +010054
Sylvain Munaut76504e02010-12-07 00:24:32 +010055
Harald Weltec91085e2022-02-10 18:05:45 +010056def h2s(s: Hexstr) -> str:
57 """convert from a string of hex nibbles to an ASCII string"""
58 return ''.join([chr((int(x, 16) << 4)+int(y, 16)) for x, y in zip(s[0::2], s[1::2])
59 if int(x + y, 16) != 0xff])
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +040060
Ben Fox-Moore0ec14752018-09-24 15:47:02 +020061
Harald Weltec91085e2022-02-10 18:05:45 +010062def s2h(s: str) -> Hexstr:
63 """convert from an ASCII string to a string of hex nibbles"""
64 b = bytearray()
65 b.extend(map(ord, s))
66 return b2h(b)
Philipp Maier796ca3d2021-11-01 17:13:57 +010067
Philipp Maier796ca3d2021-11-01 17:13:57 +010068
Harald Weltec91085e2022-02-10 18:05:45 +010069def i2s(s: List[int]) -> str:
70 """convert from a list of integers to an ASCII string"""
71 return ''.join([chr(x) for x in s])
72
73
74def swap_nibbles(s: Hexstr) -> Hexstr:
75 """swap the nibbles in a hex string"""
76 return ''.join([x+y for x, y in zip(s[1::2], s[0::2])])
77
78
79def rpad(s: str, l: int, c='f') -> str:
80 """pad string on the right side.
81 Args:
82 s : string to pad
83 l : total length to pad to
84 c : padding character
85 Returns:
86 String 's' padded with as many 'c' as needed to reach total length of 'l'
87 """
88 return s + c * (l - len(s))
89
90
91def lpad(s: str, l: int, c='f') -> str:
92 """pad string on the left side.
93 Args:
94 s : string to pad
95 l : total length to pad to
96 c : padding character
97 Returns:
98 String 's' padded with as many 'c' as needed to reach total length of 'l'
99 """
100 return c * (l - len(s)) + s
101
102
103def half_round_up(n: int) -> int:
104 return (n + 1)//2
105
106
107def str_sanitize(s: str) -> str:
108 """replace all non printable chars, line breaks and whitespaces, with ' ', make sure that
109 there are no whitespaces at the end and at the beginning of the string.
110
111 Args:
112 s : string to sanitize
113 Returns:
114 filtered result of string 's'
115 """
116
117 chars_to_keep = string.digits + string.ascii_letters + string.punctuation
118 res = ''.join([c if c in chars_to_keep else ' ' for c in s])
119 return res.strip()
Philipp Maier796ca3d2021-11-01 17:13:57 +0100120
Harald Welte917d98c2021-04-21 11:51:25 +0200121#########################################################################
Harald Welte9f3b44d2021-05-04 18:18:09 +0200122# poor man's COMPREHENSION-TLV decoder.
123#########################################################################
124
Harald Weltec91085e2022-02-10 18:05:45 +0100125
126def comprehensiontlv_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]:
Harald Welte6912b1b2021-05-24 23:16:44 +0200127 """Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
128 if binary[0] in [0x00, 0x80, 0xff]:
Harald Weltec91085e2022-02-10 18:05:45 +0100129 raise ValueError("Found illegal value 0x%02x in %s" %
130 (binary[0], binary))
Harald Welte6912b1b2021-05-24 23:16:44 +0200131 if binary[0] == 0x7f:
132 # three-byte tag
133 tag = binary[0] << 16 | binary[1] << 8 | binary[2]
134 return (tag, binary[3:])
135 elif binary[0] == 0xff:
136 return None, binary
137 else:
138 # single byte tag
139 tag = binary[0]
140 return (tag, binary[1:])
141
Harald Weltec91085e2022-02-10 18:05:45 +0100142
143def comprehensiontlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]:
Harald Welte9f3b44d2021-05-04 18:18:09 +0200144 """Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
145 if binary[0] in [0x00, 0x80, 0xff]:
Harald Weltec91085e2022-02-10 18:05:45 +0100146 raise ValueError("Found illegal value 0x%02x in %s" %
147 (binary[0], binary))
Harald Welte9f3b44d2021-05-04 18:18:09 +0200148 if binary[0] == 0x7f:
149 # three-byte tag
150 tag = (binary[1] & 0x7f) << 8
151 tag |= binary[2]
152 compr = True if binary[1] & 0x80 else False
153 return ({'comprehension': compr, 'tag': tag}, binary[3:])
154 else:
155 # single byte tag
156 tag = binary[0] & 0x7f
157 compr = True if binary[0] & 0x80 else False
158 return ({'comprehension': compr, 'tag': tag}, binary[1:])
159
Harald Weltec91085e2022-02-10 18:05:45 +0100160
Harald Welte9f3b44d2021-05-04 18:18:09 +0200161def comprehensiontlv_encode_tag(tag) -> bytes:
162 """Encode a single Tag according to ETSI TS 101 220 Section 7.1.1"""
163 # permit caller to specify tag also as integer value
164 if isinstance(tag, int):
165 compr = True if tag < 0xff and tag & 0x80 else False
166 tag = {'tag': tag, 'comprehension': compr}
167 compr = tag.get('comprehension', False)
168 if tag['tag'] in [0x00, 0x80, 0xff] or tag['tag'] > 0xff:
169 # 3-byte format
Vadim Yanitskiydbd5ed62021-11-05 16:20:52 +0300170 byte3 = tag['tag'] & 0xff
Harald Welte9f3b44d2021-05-04 18:18:09 +0200171 byte2 = (tag['tag'] >> 8) & 0x7f
172 if compr:
173 byte2 |= 0x80
174 return b'\x7f' + byte2.to_bytes(1, 'big') + byte3.to_bytes(1, 'big')
175 else:
176 # 1-byte format
177 ret = tag['tag']
178 if compr:
179 ret |= 0x80
180 return ret.to_bytes(1, 'big')
181
182# length value coding is equal to BER-TLV
183
Harald Welte6912b1b2021-05-24 23:16:44 +0200184
Harald Weltec91085e2022-02-10 18:05:45 +0100185def comprehensiontlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]:
186 """Parse a single TLV IE at the start of the given binary data.
187 Args:
188 binary : binary input data of BER-TLV length field
189 Returns:
190 Tuple of (tag:dict, len:int, remainder:bytes)
191 """
192 (tagdict, remainder) = comprehensiontlv_parse_tag(binary)
193 (length, remainder) = bertlv_parse_len(remainder)
194 value = remainder[:length]
195 remainder = remainder[length:]
196 return (tagdict, length, value, remainder)
Harald Welte6912b1b2021-05-24 23:16:44 +0200197
Harald Welte9f3b44d2021-05-04 18:18:09 +0200198
199#########################################################################
Harald Welte917d98c2021-04-21 11:51:25 +0200200# poor man's BER-TLV decoder. To be a more sophisticated OO library later
201#########################################################################
202
Harald Weltec91085e2022-02-10 18:05:45 +0100203def bertlv_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]:
204 """Get a single raw Tag from start of input according to ITU-T X.690 8.1.2
205 Args:
206 binary : binary input data of BER-TLV length field
207 Returns:
208 Tuple of (tag:int, remainder:bytes)
209 """
210 # check for FF padding at the end, as customary in SIM card files
211 if binary[0] == 0xff and len(binary) == 1 or binary[0] == 0xff and binary[1] == 0xff:
212 return None, binary
213 tag = binary[0] & 0x1f
214 if tag <= 30:
215 return binary[0], binary[1:]
216 else: # multi-byte tag
217 tag = binary[0]
218 i = 1
219 last = False
220 while not last:
221 last = False if binary[i] & 0x80 else True
222 tag <<= 8
223 tag |= binary[i]
224 i += 1
225 return tag, binary[i:]
Harald Welte6912b1b2021-05-24 23:16:44 +0200226
Harald Weltec91085e2022-02-10 18:05:45 +0100227
228def bertlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]:
229 """Parse a single Tag value according to ITU-T X.690 8.1.2
230 Args:
231 binary : binary input data of BER-TLV length field
232 Returns:
233 Tuple of ({class:int, constructed:bool, tag:int}, remainder:bytes)
234 """
235 cls = binary[0] >> 6
236 constructed = True if binary[0] & 0x20 else False
237 tag = binary[0] & 0x1f
238 if tag <= 30:
239 return ({'class': cls, 'constructed': constructed, 'tag': tag}, binary[1:])
240 else: # multi-byte tag
241 tag = 0
242 i = 1
243 last = False
244 while not last:
245 last = False if binary[i] & 0x80 else True
246 tag <<= 7
247 tag |= binary[i] & 0x7f
248 i += 1
249 return ({'class': cls, 'constructed': constructed, 'tag': tag}, binary[i:])
250
Harald Welte917d98c2021-04-21 11:51:25 +0200251
Harald Weltef0885b12021-05-24 23:18:59 +0200252def bertlv_encode_tag(t) -> bytes:
253 """Encode a single Tag value according to ITU-T X.690 8.1.2
254 """
Harald Weltec91085e2022-02-10 18:05:45 +0100255 def get_top7_bits(inp: int) -> Tuple[int, int]:
Harald Weltef0885b12021-05-24 23:18:59 +0200256 """Get top 7 bits of integer. Returns those 7 bits as integer and the remaining LSBs."""
257 remain_bits = inp.bit_length()
258 if remain_bits >= 7:
259 bitcnt = 7
260 else:
261 bitcnt = remain_bits
262 outp = inp >> (remain_bits - bitcnt)
263 remainder = inp & ~ (inp << (remain_bits - bitcnt))
264 return outp, remainder
265
Harald Welte10669f22023-10-23 01:54:36 +0200266 def count_int_bytes(inp: int) -> int:
267 """count the number of bytes require to represent the given integer."""
268 i = 1
269 inp = inp >> 8
270 while inp:
271 i += 1
272 inp = inp >> 8
273 return i
274
Harald Weltef0885b12021-05-24 23:18:59 +0200275 if isinstance(t, int):
Harald Welte10669f22023-10-23 01:54:36 +0200276 # first convert to a dict representation
277 tag_size = count_int_bytes(t)
278 t, remainder = bertlv_parse_tag(t.to_bytes(tag_size, 'big'))
279 tag = t['tag']
280 constructed = t['constructed']
281 cls = t['class']
Harald Weltef0885b12021-05-24 23:18:59 +0200282 if tag <= 30:
283 t = tag & 0x1f
284 if constructed:
285 t |= 0x20
286 t |= (cls & 3) << 6
287 return bytes([t])
Harald Weltec91085e2022-02-10 18:05:45 +0100288 else: # multi-byte tag
Vadim Yanitskiydbd5ed62021-11-05 16:20:52 +0300289 t = 0x1f
Harald Weltef0885b12021-05-24 23:18:59 +0200290 if constructed:
291 t |= 0x20
292 t |= (cls & 3) << 6
293 tag_bytes = bytes([t])
294 remain = tag
295 while True:
296 t, remain = get_top7_bits(remain)
297 if remain:
298 t |= 0x80
299 tag_bytes += bytes([t])
300 if not remain:
301 break
302 return tag_bytes
303
Harald Welte917d98c2021-04-21 11:51:25 +0200304
Harald Weltec91085e2022-02-10 18:05:45 +0100305def bertlv_parse_len(binary: bytes) -> Tuple[int, bytes]:
306 """Parse a single Length value according to ITU-T X.690 8.1.3;
307 only the definite form is supported here.
308 Args:
309 binary : binary input data of BER-TLV length field
310 Returns:
311 Tuple of (length, remainder)
312 """
313 if binary[0] < 0x80:
314 return (binary[0], binary[1:])
315 else:
316 num_len_oct = binary[0] & 0x7f
317 length = 0
Harald Welteb3c46132023-12-08 16:08:53 +0100318 if len(binary) < num_len_oct + 1:
319 return (0, b'')
Harald Weltec91085e2022-02-10 18:05:45 +0100320 for i in range(1, 1+num_len_oct):
321 length <<= 8
322 length |= binary[i]
323 return (length, binary[1+num_len_oct:])
Harald Welte917d98c2021-04-21 11:51:25 +0200324
Harald Welte917d98c2021-04-21 11:51:25 +0200325
Harald Weltec91085e2022-02-10 18:05:45 +0100326def bertlv_encode_len(length: int) -> bytes:
327 """Encode a single Length value according to ITU-T X.690 8.1.3;
328 only the definite form is supported here.
329 Args:
330 length : length value to be encoded
331 Returns:
332 binary output data of BER-TLV length field
333 """
334 if length < 0x80:
335 return length.to_bytes(1, 'big')
336 elif length <= 0xff:
337 return b'\x81' + length.to_bytes(1, 'big')
338 elif length <= 0xffff:
339 return b'\x82' + length.to_bytes(2, 'big')
340 elif length <= 0xffffff:
341 return b'\x83' + length.to_bytes(3, 'big')
342 elif length <= 0xffffffff:
343 return b'\x84' + length.to_bytes(4, 'big')
344 else:
345 raise ValueError("Length > 32bits not supported")
346
347
348def bertlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]:
349 """Parse a single TLV IE at the start of the given binary data.
350 Args:
351 binary : binary input data of BER-TLV length field
352 Returns:
353 Tuple of (tag:dict, len:int, remainder:bytes)
354 """
355 (tagdict, remainder) = bertlv_parse_tag(binary)
356 (length, remainder) = bertlv_parse_len(remainder)
357 value = remainder[:length]
358 remainder = remainder[length:]
359 return (tagdict, length, value, remainder)
Harald Welte917d98c2021-04-21 11:51:25 +0200360
361
Harald Welte1f7a9bd2024-01-14 10:09:07 +0100362def dgi_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]:
363 # In absence of any clear spec guidance we assume it's always 16 bit
364 return int.from_bytes(binary[:2], 'big'), binary[2:]
365
366def dgi_encode_tag(t: int) -> bytes:
367 return t.to_bytes(2, 'big')
368
369def dgi_encode_len(length: int) -> bytes:
370 """Encode a single Length value according to GlobalPlatform Systems Scripting Language
371 Specification v1.1.0 Annex B.
372 Args:
373 length : length value to be encoded
374 Returns:
375 binary output data of encoded length field
376 """
377 if length < 255:
378 return length.to_bytes(1, 'big')
379 elif length <= 0xffff:
380 return b'\xff' + length.to_bytes(2, 'big')
381 else:
382 raise ValueError("Length > 32bits not supported")
383
384def dgi_parse_len(binary: bytes) -> Tuple[int, bytes]:
385 """Parse a single Length value according to GlobalPlatform Systems Scripting Language
386 Specification v1.1.0 Annex B.
387 Args:
388 binary : binary input data of BER-TLV length field
389 Returns:
390 Tuple of (length, remainder)
391 """
392 if binary[0] == 255:
393 assert len(binary) >= 3
394 return ((binary[1] << 8) | binary[2]), binary[3:]
395 else:
396 return binary[0], binary[1:]
397
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200398# IMSI encoded format:
399# For IMSI 0123456789ABCDE:
400#
401# | byte 1 | 2 upper | 2 lower | 3 upper | 3 lower | ... | 9 upper | 9 lower |
402# | length in bytes | 0 | odd/even | 2 | 1 | ... | E | D |
403#
404# If the IMSI is less than 15 characters, it should be padded with 'f' from the end.
405#
406# The length is the total number of bytes used to encoded the IMSI. This includes the odd/even
407# parity bit. E.g. an IMSI of length 14 is 8 bytes long, not 7, as it uses bytes 2 to 9 to
408# encode itself.
409#
410# Because of this, an odd length IMSI fits exactly into len(imsi) + 1 // 2 bytes, whereas an
411# even length IMSI only uses half of the last byte.
412
Harald Weltec91085e2022-02-10 18:05:45 +0100413def enc_imsi(imsi: str):
414 """Converts a string IMSI into the encoded value of the EF"""
415 l = half_round_up(
416 len(imsi) + 1) # Required bytes - include space for odd/even indicator
417 oe = len(imsi) & 1 # Odd (1) / Even (0)
418 ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe << 3) | 1, rpad(imsi, 15)))
419 return ei
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400420
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400421
Harald Weltec91085e2022-02-10 18:05:45 +0100422def dec_imsi(ef: Hexstr) -> Optional[str]:
423 """Converts an EF value to the IMSI string representation"""
424 if len(ef) < 4:
425 return None
426 l = int(ef[0:2], 16) * 2 # Length of the IMSI string
427 l = l - 1 # Encoded length byte includes oe nibble
428 swapped = swap_nibbles(ef[2:]).rstrip('f')
429 if len(swapped) < 1:
430 return None
431 oe = (int(swapped[0]) >> 3) & 1 # Odd (1) / Even (0)
432 if not oe:
433 # if even, only half of last byte was used
434 l = l-1
435 if l != len(swapped) - 1:
436 return None
437 imsi = swapped[1:]
438 return imsi
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400439
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400440
Harald Weltec91085e2022-02-10 18:05:45 +0100441def dec_iccid(ef: Hexstr) -> str:
442 return swap_nibbles(ef).strip('f')
Philipp Maier6c5cd802021-04-23 21:19:36 +0200443
Philipp Maier6c5cd802021-04-23 21:19:36 +0200444
Harald Weltec91085e2022-02-10 18:05:45 +0100445def enc_iccid(iccid: str) -> Hexstr:
446 return swap_nibbles(rpad(iccid, 20))
Philipp Maier6c5cd802021-04-23 21:19:36 +0200447
Philipp Maier6c5cd802021-04-23 21:19:36 +0200448
Harald Weltec91085e2022-02-10 18:05:45 +0100449def enc_plmn(mcc: Hexstr, mnc: Hexstr) -> Hexstr:
450 """Converts integer MCC/MNC into 3 bytes for EF"""
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300451
Harald Weltec91085e2022-02-10 18:05:45 +0100452 # Make sure there are no excess whitespaces in the input
453 # parameters
454 mcc = mcc.strip()
455 mnc = mnc.strip()
456
457 # Make sure that MCC/MNC are correctly padded with leading
458 # zeros or 'F', depending on the length.
459 if len(mnc) == 0:
460 mnc = "FFF"
461 elif len(mnc) == 1:
farhadhfec721f2023-07-19 15:43:13 +0200462 mnc = "0" + mnc + "F"
Harald Weltec91085e2022-02-10 18:05:45 +0100463 elif len(mnc) == 2:
464 mnc += "F"
465
466 if len(mcc) == 0:
467 mcc = "FFF"
468 elif len(mcc) == 1:
469 mcc = "00" + mcc
470 elif len(mcc) == 2:
471 mcc = "0" + mcc
472
473 return (mcc[1] + mcc[0]) + (mnc[2] + mcc[2]) + (mnc[1] + mnc[0])
474
475
476def dec_plmn(threehexbytes: Hexstr) -> dict:
477 res = {'mcc': "0", 'mnc': "0"}
478 dec_mcc_from_plmn_str(threehexbytes)
479 res['mcc'] = dec_mcc_from_plmn_str(threehexbytes)
480 res['mnc'] = dec_mnc_from_plmn_str(threehexbytes)
481 return res
482
Harald Welted7a7e172021-04-07 00:30:10 +0200483
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100484# Accepts hex string representing three bytes
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100485
Philipp Maier6c5cd802021-04-23 21:19:36 +0200486
Harald Weltec91085e2022-02-10 18:05:45 +0100487def dec_mcc_from_plmn(plmn: Hexstr) -> int:
488 ia = h2i(plmn)
489 digit1 = ia[0] & 0x0F # 1st byte, LSB
490 digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB
491 digit3 = ia[1] & 0x0F # 2nd byte, LSB
492 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
493 return 0xFFF # 4095
494 return derive_mcc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100495
Philipp Maier6c5cd802021-04-23 21:19:36 +0200496
Harald Weltec91085e2022-02-10 18:05:45 +0100497def dec_mcc_from_plmn_str(plmn: Hexstr) -> str:
498 digit1 = plmn[1] # 1st byte, LSB
499 digit2 = plmn[0] # 1st byte, MSB
500 digit3 = plmn[3] # 2nd byte, LSB
501 res = digit1 + digit2 + digit3
502 return res.upper().strip("F")
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100503
Harald Weltec91085e2022-02-10 18:05:45 +0100504
505def dec_mnc_from_plmn(plmn: Hexstr) -> int:
506 ia = h2i(plmn)
507 digit1 = ia[2] & 0x0F # 3rd byte, LSB
508 digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB
509 digit3 = (ia[1] & 0xF0) >> 4 # 2nd byte, MSB
510 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
511 return 0xFFF # 4095
512 return derive_mnc(digit1, digit2, digit3)
513
514
515def dec_mnc_from_plmn_str(plmn: Hexstr) -> str:
516 digit1 = plmn[5] # 3rd byte, LSB
517 digit2 = plmn[4] # 3rd byte, MSB
518 digit3 = plmn[2] # 2nd byte, MSB
519 res = digit1 + digit2 + digit3
520 return res.upper().strip("F")
521
522
523def dec_act(twohexbytes: Hexstr) -> List[str]:
524 act_list = [
525 {'bit': 15, 'name': "UTRAN"},
Harald Weltec91085e2022-02-10 18:05:45 +0100526 {'bit': 11, 'name': "NG-RAN"},
Harald Weltec91085e2022-02-10 18:05:45 +0100527 {'bit': 6, 'name': "GSM COMPACT"},
528 {'bit': 5, 'name': "cdma2000 HRPD"},
529 {'bit': 4, 'name': "cdma2000 1xRTT"},
530 ]
531 ia = h2i(twohexbytes)
532 u16t = (ia[0] << 8) | ia[1]
Harald Welte542dbf62023-12-21 20:14:48 +0100533 sel = set()
534 # only the simple single-bit ones
Harald Weltec91085e2022-02-10 18:05:45 +0100535 for a in act_list:
536 if u16t & (1 << a['bit']):
Harald Welte542dbf62023-12-21 20:14:48 +0100537 sel.add(a['name'])
538 # TS 31.102 Section 4.2.5 Table 4.2.5.1
539 eutran_bits = u16t & 0x7000
540 if eutran_bits == 0x4000 or eutran_bits == 0x7000:
541 sel.add("E-UTRAN WB-S1")
542 sel.add("E-UTRAN NB-S1")
543 elif eutran_bits == 0x5000:
544 sel.add("E-UTRAN NB-S1")
545 elif eutran_bits == 0x6000:
546 sel.add("E-UTRAN WB-S1")
547 # TS 31.102 Section 4.2.5 Table 4.2.5.2
548 gsm_bits = u16t & 0x008C
549 if gsm_bits == 0x0080 or gsm_bits == 0x008C:
550 sel.add("GSM")
551 sel.add("EC-GSM-IoT")
552 elif u16t & 0x008C == 0x0084:
553 sel.add("GSM")
554 elif u16t & 0x008C == 0x0086:
555 sel.add("EC-GSM-IoT")
556 return sorted(list(sel))
Harald Weltec91085e2022-02-10 18:05:45 +0100557
558
559def dec_xplmn_w_act(fivehexbytes: Hexstr) -> Dict[str, Any]:
560 res = {'mcc': "0", 'mnc': "0", 'act': []}
561 plmn_chars = 6
562 act_chars = 4
563 # first three bytes (six ascii hex chars)
564 plmn_str = fivehexbytes[:plmn_chars]
565 # two bytes after first three bytes
566 act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars]
567 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
568 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
569 res['act'] = dec_act(act_str)
570 return res
571
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100572
Harald Weltec91085e2022-02-10 18:05:45 +0100573def dec_xplmn(threehexbytes: Hexstr) -> dict:
574 res = {'mcc': 0, 'mnc': 0, 'act': []}
575 plmn_chars = 6
576 # first three bytes (six ascii hex chars)
577 plmn_str = threehexbytes[:plmn_chars]
Matan Perelman60951b02023-06-01 17:39:04 +0300578 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
579 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
Harald Weltec91085e2022-02-10 18:05:45 +0100580 return res
Harald Welteca673942020-06-03 15:19:40 +0200581
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900582
Harald Weltec91085e2022-02-10 18:05:45 +0100583def derive_milenage_opc(ki_hex: Hexstr, op_hex: Hexstr) -> Hexstr:
584 """
585 Run the milenage algorithm to calculate OPC from Ki and OP
586 """
Harald Welted75fa3f2023-05-31 20:47:55 +0200587 from Cryptodome.Cipher import AES
Harald Weltec91085e2022-02-10 18:05:45 +0100588 # pylint: disable=no-name-in-module
Harald Welted75fa3f2023-05-31 20:47:55 +0200589 from Cryptodome.Util.strxor import strxor
Harald Weltec91085e2022-02-10 18:05:45 +0100590
591 # We pass in hex string and now need to work on bytes
592 ki_bytes = bytes(h2b(ki_hex))
593 op_bytes = bytes(h2b(op_hex))
594 aes = AES.new(ki_bytes, AES.MODE_ECB)
595 opc_bytes = aes.encrypt(op_bytes)
596 return b2h(strxor(opc_bytes, op_bytes))
597
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900598
Harald Welte52255572021-04-03 09:56:32 +0200599def calculate_luhn(cc) -> int:
Harald Weltec91085e2022-02-10 18:05:45 +0100600 """
601 Calculate Luhn checksum used in e.g. ICCID and IMEI
602 """
603 num = list(map(int, str(cc)))
604 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10))
605 for d in num[::-2]]) % 10
606 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200607
Philipp Maier7592eee2019-09-12 13:03:23 +0200608
Harald Weltec91085e2022-02-10 18:05:45 +0100609def mcc_from_imsi(imsi: str) -> Optional[str]:
610 """
611 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
612 """
613 if imsi == None:
614 return None
Philipp Maier7592eee2019-09-12 13:03:23 +0200615
Harald Weltec91085e2022-02-10 18:05:45 +0100616 if len(imsi) > 3:
617 return imsi[:3]
618 else:
619 return None
Philipp Maier7592eee2019-09-12 13:03:23 +0200620
Supreeth Herled24f1632019-11-30 10:37:09 +0100621
Harald Weltec91085e2022-02-10 18:05:45 +0100622def mnc_from_imsi(imsi: str, long: bool = False) -> Optional[str]:
623 """
624 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
625 """
626 if imsi == None:
627 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100628
Harald Weltec91085e2022-02-10 18:05:45 +0100629 if len(imsi) > 3:
630 if long:
631 return imsi[3:6]
632 else:
633 return imsi[3:5]
634 else:
635 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100636
Supreeth Herled24f1632019-11-30 10:37:09 +0100637
Harald Weltec91085e2022-02-10 18:05:45 +0100638def derive_mcc(digit1: int, digit2: int, digit3: int) -> int:
639 """
640 Derive decimal representation of the MCC (Mobile Country Code)
641 from three given digits.
642 """
Supreeth Herled24f1632019-11-30 10:37:09 +0100643
Harald Weltec91085e2022-02-10 18:05:45 +0100644 mcc = 0
Supreeth Herled24f1632019-11-30 10:37:09 +0100645
Harald Weltec91085e2022-02-10 18:05:45 +0100646 if digit1 != 0x0f:
647 mcc += digit1 * 100
648 if digit2 != 0x0f:
649 mcc += digit2 * 10
650 if digit3 != 0x0f:
651 mcc += digit3
Supreeth Herled24f1632019-11-30 10:37:09 +0100652
Harald Weltec91085e2022-02-10 18:05:45 +0100653 return mcc
Supreeth Herled24f1632019-11-30 10:37:09 +0100654
Supreeth Herled24f1632019-11-30 10:37:09 +0100655
Harald Weltec91085e2022-02-10 18:05:45 +0100656def derive_mnc(digit1: int, digit2: int, digit3: int = 0x0f) -> int:
657 """
658 Derive decimal representation of the MNC (Mobile Network Code)
659 from two or (optionally) three given digits.
660 """
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100661
Harald Weltec91085e2022-02-10 18:05:45 +0100662 mnc = 0
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100663
Harald Weltec91085e2022-02-10 18:05:45 +0100664 # 3-rd digit is optional for the MNC. If present
665 # the algorythm is the same as for the MCC.
666 if digit3 != 0x0f:
667 return derive_mcc(digit1, digit2, digit3)
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100668
Harald Weltec91085e2022-02-10 18:05:45 +0100669 if digit1 != 0x0f:
670 mnc += digit1 * 10
671 if digit2 != 0x0f:
672 mnc += digit2
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100673
Harald Weltec91085e2022-02-10 18:05:45 +0100674 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100675
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100676
Harald Weltec91085e2022-02-10 18:05:45 +0100677def dec_msisdn(ef_msisdn: Hexstr) -> Optional[Tuple[int, int, Optional[str]]]:
678 """
679 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
680 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
681 """
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100682
Harald Weltec91085e2022-02-10 18:05:45 +0100683 # Convert from str to (kind of) 'bytes'
684 ef_msisdn = h2b(ef_msisdn)
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100685
Harald Weltec91085e2022-02-10 18:05:45 +0100686 # Make sure mandatory fields are present
687 if len(ef_msisdn) < 14:
688 raise ValueError("EF.MSISDN is too short")
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100689
Harald Weltec91085e2022-02-10 18:05:45 +0100690 # Skip optional Alpha Identifier
691 xlen = len(ef_msisdn) - 14
692 msisdn_lhv = ef_msisdn[xlen:]
Supreeth Herle5a541012019-12-22 08:59:16 +0100693
Harald Weltec91085e2022-02-10 18:05:45 +0100694 # Parse the length (in bytes) of the BCD encoded number
695 bcd_len = msisdn_lhv[0]
696 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
697 if bcd_len == 0xff:
698 return None
699 elif bcd_len > 11 or bcd_len < 1:
700 raise ValueError(
701 "Length of MSISDN (%d bytes) is out of range" % bcd_len)
Supreeth Herle5a541012019-12-22 08:59:16 +0100702
Harald Weltec91085e2022-02-10 18:05:45 +0100703 # Parse ToN / NPI
704 ton = (msisdn_lhv[1] >> 4) & 0x07
705 npi = msisdn_lhv[1] & 0x0f
706 bcd_len -= 1
Supreeth Herle5a541012019-12-22 08:59:16 +0100707
Harald Weltec91085e2022-02-10 18:05:45 +0100708 # No MSISDN?
709 if not bcd_len:
710 return (npi, ton, None)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200711
Harald Weltec91085e2022-02-10 18:05:45 +0100712 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
713 # International number 10.5.118/3GPP TS 24.008
714 if ton == 0x01:
715 msisdn = '+' + msisdn
Supreeth Herle5a541012019-12-22 08:59:16 +0100716
Harald Weltec91085e2022-02-10 18:05:45 +0100717 return (npi, ton, msisdn)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200718
Supreeth Herle5a541012019-12-22 08:59:16 +0100719
Harald Weltec91085e2022-02-10 18:05:45 +0100720def enc_msisdn(msisdn: str, npi: int = 0x01, ton: int = 0x03) -> Hexstr:
721 """
722 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
723 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
724 will not contain the optional Alpha Identifier at the beginning.)
Supreeth Herle5a541012019-12-22 08:59:16 +0100725
Harald Weltec91085e2022-02-10 18:05:45 +0100726 Default NPI / ToN values:
727 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
728 - ToN: network specific or international number (if starts with '+').
729 """
730
731 # If no MSISDN is supplied then encode the file contents as all "ff"
732 if msisdn == "" or msisdn == "+":
733 return "ff" * 14
734
735 # Leading '+' indicates International Number
736 if msisdn[0] == '+':
737 msisdn = msisdn[1:]
738 ton = 0x01
739
740 # An MSISDN must not exceed 20 digits
741 if len(msisdn) > 20:
742 raise ValueError("msisdn must not be longer than 20 digits")
743
744 # Append 'f' padding if number of digits is odd
745 if len(msisdn) % 2 > 0:
746 msisdn += 'f'
747
748 # BCD length also includes NPI/ToN header
749 bcd_len = len(msisdn) // 2 + 1
750 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
751 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
752
753 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200754
Supreeth Herle441c4a72020-03-24 10:19:15 +0100755
Harald Weltec91085e2022-02-10 18:05:45 +0100756def is_hex(string: str, minlen: int = 2, maxlen: Optional[int] = None) -> bool:
757 """
758 Check if a string is a valid hexstring
759 """
Philipp Maier47236502021-03-09 21:28:25 +0100760
Harald Weltec91085e2022-02-10 18:05:45 +0100761 # Filter obviously bad strings
762 if not string:
763 return False
764 if len(string) < minlen or minlen < 2:
765 return False
766 if len(string) % 2:
767 return False
768 if maxlen and len(string) > maxlen:
769 return False
Philipp Maier47236502021-03-09 21:28:25 +0100770
Harald Weltec91085e2022-02-10 18:05:45 +0100771 # Try actual encoding to be sure
772 try:
773 try_encode = h2b(string)
774 return True
775 except:
776 return False
Philipp Maiere8536c02020-05-11 21:35:01 +0200777
Philipp Maiere8536c02020-05-11 21:35:01 +0200778
Harald Weltec91085e2022-02-10 18:05:45 +0100779def sanitize_pin_adm(pin_adm, pin_adm_hex=None) -> Hexstr:
780 """
781 The ADM pin can be supplied either in its hexadecimal form or as
782 ascii string. This function checks the supplied opts parameter and
783 returns the pin_adm as hex encoded string, regardless in which form
784 it was originally supplied by the user
785 """
Philipp Maiere8536c02020-05-11 21:35:01 +0200786
Harald Weltec91085e2022-02-10 18:05:45 +0100787 if pin_adm is not None:
788 if len(pin_adm) <= 8:
789 pin_adm = ''.join(['%02x' % (ord(x)) for x in pin_adm])
790 pin_adm = rpad(pin_adm, 16)
Philipp Maiere8536c02020-05-11 21:35:01 +0200791
Harald Weltec91085e2022-02-10 18:05:45 +0100792 else:
793 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
794
795 if pin_adm_hex is not None:
796 if len(pin_adm_hex) == 16:
797 pin_adm = pin_adm_hex
798 # Ensure that it's hex-encoded
799 try:
800 try_encode = h2b(pin_adm)
801 except ValueError:
802 raise ValueError(
803 "PIN-ADM needs to be hex encoded using this option")
804 else:
805 raise ValueError(
806 "PIN-ADM needs to be exactly 16 digits (hex encoded)")
807
808 return pin_adm
809
Philipp Maiere8536c02020-05-11 21:35:01 +0200810
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100811def get_addr_type(addr):
Harald Weltec91085e2022-02-10 18:05:45 +0100812 """
813 Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
814 Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given)
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100815
Harald Weltec91085e2022-02-10 18:05:45 +0100816 TODO: Handle IPv6
817 """
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100818
Harald Weltec91085e2022-02-10 18:05:45 +0100819 # Empty address string
820 if not len(addr):
821 return None
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100822
Harald Weltec91085e2022-02-10 18:05:45 +0100823 addr_list = addr.split('.')
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100824
Harald Weltec91085e2022-02-10 18:05:45 +0100825 # Check for IPv4/IPv6
826 try:
827 import ipaddress
828 # Throws ValueError if addr is not correct
829 ipa = ipaddress.ip_address(addr)
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100830
Harald Weltec91085e2022-02-10 18:05:45 +0100831 if ipa.version == 4:
832 return 0x01
833 elif ipa.version == 6:
834 return 0x02
835 except Exception as e:
836 invalid_ipv4 = True
837 for i in addr_list:
838 # Invalid IPv4 may qualify for a valid FQDN, so make check here
839 # e.g. 172.24.15.300
840 import re
841 if not re.match('^[0-9_]+$', i):
842 invalid_ipv4 = False
843 break
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100844
Harald Weltec91085e2022-02-10 18:05:45 +0100845 if invalid_ipv4:
846 return None
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100847
Harald Weltec91085e2022-02-10 18:05:45 +0100848 fqdn_flag = True
849 for i in addr_list:
850 # Only Alpha-numeric characters and hyphen - RFC 1035
851 import re
852 if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
853 fqdn_flag = False
854 break
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100855
Harald Weltec91085e2022-02-10 18:05:45 +0100856 # FQDN
857 if fqdn_flag:
858 return 0x00
Supreeth Herle556b0fe2020-03-25 11:26:57 +0100859
Harald Weltec91085e2022-02-10 18:05:45 +0100860 return None
Harald Welte67d551a2021-01-21 14:50:01 +0100861
Philipp Maier5d3e2592021-02-22 17:22:16 +0100862
Harald Weltec91085e2022-02-10 18:05:45 +0100863def sw_match(sw: str, pattern: str) -> bool:
864 """Match given SW against given pattern."""
865 # Create a masked version of the returned status word
866 sw_lower = sw.lower()
867 sw_masked = ""
868 for i in range(0, 4):
869 if pattern[i] == '?':
870 sw_masked = sw_masked + '?'
871 elif pattern[i] == 'x':
872 sw_masked = sw_masked + 'x'
873 else:
874 sw_masked = sw_masked + sw_lower[i]
875 # Compare the masked version against the pattern
876 return sw_masked == pattern
Harald Welteee3501f2021-04-02 13:00:18 +0200877
Harald Weltec91085e2022-02-10 18:05:45 +0100878
879def tabulate_str_list(str_list, width: int = 79, hspace: int = 2, lspace: int = 1,
880 align_left: bool = True) -> str:
881 """Pretty print a list of strings into a tabulated form.
882
883 Args:
884 width : total width in characters per line
885 space : horizontal space between cells
886 lspace : number of spaces before row
887 align_lef : Align text to the left side
888 Returns:
889 multi-line string containing formatted table
890 """
891 if str_list == None:
892 return ""
893 if len(str_list) <= 0:
894 return ""
895 longest_str = max(str_list, key=len)
896 cellwith = len(longest_str) + hspace
897 cols = width // cellwith
898 rows = (len(str_list) - 1) // cols + 1
899 table = []
900 for i in iter(range(rows)):
901 str_list_row = str_list[i::rows]
902 if (align_left):
903 format_str_cell = '%%-%ds'
904 else:
905 format_str_cell = '%%%ds'
906 format_str_row = (format_str_cell % cellwith) * len(str_list_row)
907 format_str_row = (" " * lspace) + format_str_row
908 table.append(format_str_row % tuple(str_list_row))
909 return '\n'.join(table)
910
Harald Welte5e749a72021-04-10 17:18:17 +0200911
Harald Welte917d98c2021-04-21 11:51:25 +0200912def auto_int(x):
913 """Helper function for argparse to accept hexadecimal integers."""
914 return int(x, 0)
915
Harald Weltec91085e2022-02-10 18:05:45 +0100916
Philipp Maier40ea4a42022-06-02 14:45:41 +0200917def expand_hex(hexstring, length):
918 """Expand a given hexstring to a specified length by replacing "." or ".."
919 with a filler that is derived from the neighboring nibbles respective
920 bytes. Usually this will be the nibble respective byte before "." or
921 "..", execpt when the string begins with "." or "..", then the nibble
922 respective byte after "." or ".." is used.". In case the string cannot
923 be expanded for some reason, the input string is returned unmodified.
924
925 Args:
926 hexstring : hexstring to expand
927 length : desired length of the resulting hexstring.
928 Returns:
929 expanded hexstring
930 """
931
932 # expand digit aligned
933 if hexstring.count(".") == 1:
934 pos = hexstring.index(".")
935 if pos > 0:
936 filler = hexstring[pos - 1]
937 else:
938 filler = hexstring[pos + 1]
939
940 missing = length * 2 - (len(hexstring) - 1)
941 if missing <= 0:
942 return hexstring
943
944 return hexstring.replace(".", filler * missing)
945
946 # expand byte aligned
947 elif hexstring.count("..") == 1:
948 if len(hexstring) % 2:
949 return hexstring
950
951 pos = hexstring.index("..")
952
953 if pos % 2:
954 return hexstring
955
956 if pos > 1:
957 filler = hexstring[pos - 2:pos]
958 else:
959 filler = hexstring[pos + 2:pos+4]
960
961 missing = length * 2 - (len(hexstring) - 2)
962 if missing <= 0:
963 return hexstring
964
965 return hexstring.replace("..", filler * (missing // 2))
966
967 # no change
968 return hexstring
969
970
Harald Welte5e749a72021-04-10 17:18:17 +0200971class JsonEncoder(json.JSONEncoder):
972 """Extend the standard library JSONEncoder with support for more types."""
Harald Weltec91085e2022-02-10 18:05:45 +0100973
Harald Welte5e749a72021-04-10 17:18:17 +0200974 def default(self, o):
975 if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
976 return b2h(o)
Harald Welte667d5892024-01-17 19:17:24 +0100977 elif isinstance(o, datetime.datetime):
978 return o.isoformat()
Harald Welte5e749a72021-04-10 17:18:17 +0200979 return json.JSONEncoder.default(self, o)
Philipp Maier80ce71f2021-04-19 21:24:23 +0200980
Harald Weltec91085e2022-02-10 18:05:45 +0100981
Philipp Maier80ce71f2021-04-19 21:24:23 +0200982def boxed_heading_str(heading, width=80):
Harald Weltec91085e2022-02-10 18:05:45 +0100983 """Generate a string that contains a boxed heading."""
984 # Auto-enlarge box if heading exceeds length
985 if len(heading) > width - 4:
986 width = len(heading) + 4
Philipp Maier80ce71f2021-04-19 21:24:23 +0200987
Harald Weltec91085e2022-02-10 18:05:45 +0100988 res = "#" * width
989 fstr = "\n# %-" + str(width - 4) + "s #\n"
990 res += fstr % (heading)
991 res += "#" * width
992 return res
Harald Welte3de6ca22021-05-02 20:05:56 +0200993
994
995class DataObject(abc.ABC):
996 """A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
997 simply has any number of different TLVs that may occur in any order at any point, ISO 7816
998 has the habit of specifying TLV data but with very spcific ordering, or specific choices of
999 tags at specific points in a stream. This class tries to represent this."""
Harald Weltec91085e2022-02-10 18:05:45 +01001000
Harald Welte425038f2022-02-10 19:32:04 +01001001 def __init__(self, name: str, desc: Optional[str] = None, tag: Optional[int] = None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001002 """
1003 Args:
1004 name: A brief, all-lowercase, underscore separated string identifier
1005 desc: A human-readable description of what this DO represents
1006 tag : The tag associated with this DO
1007 """
1008 self.name = name
1009 self.desc = desc
1010 self.tag = tag
1011 self.decoded = None
1012 self.encoded = None
1013
1014 def __str__(self):
1015 return self.name
1016
Harald Welte50368772022-02-10 15:22:22 +01001017 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001018 return '%s(%s)' % (self.__class__, self.name)
1019
Harald Welte50368772022-02-10 15:22:22 +01001020 def __or__(self, other) -> 'DataObjectChoice':
Harald Welte3de6ca22021-05-02 20:05:56 +02001021 """OR-ing DataObjects together renders a DataObjectChoice."""
1022 if isinstance(other, DataObject):
1023 # DataObject | DataObject = DataObjectChoice
1024 return DataObjectChoice(None, members=[self, other])
1025 else:
1026 raise TypeError
1027
Harald Welte50368772022-02-10 15:22:22 +01001028 def __add__(self, other) -> 'DataObjectCollection':
Harald Welte3de6ca22021-05-02 20:05:56 +02001029 """ADD-ing DataObjects together renders a DataObjectCollection."""
1030 if isinstance(other, DataObject):
1031 # DataObject + DataObject = DataObjectCollectin
1032 return DataObjectCollection(None, members=[self, other])
Harald Welte50368772022-02-10 15:22:22 +01001033 else:
1034 raise TypeError
Harald Welte3de6ca22021-05-02 20:05:56 +02001035
Harald Welte50368772022-02-10 15:22:22 +01001036 def _compute_tag(self) -> int:
Harald Welte3de6ca22021-05-02 20:05:56 +02001037 """Compute the tag (sometimes the tag encodes part of the value)."""
1038 return self.tag
1039
Harald Welte50368772022-02-10 15:22:22 +01001040 def to_dict(self) -> dict:
Harald Welte3de6ca22021-05-02 20:05:56 +02001041 """Return a dict in form "name: decoded_value" """
1042 return {self.name: self.decoded}
1043
1044 @abc.abstractmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001045 def from_bytes(self, do: bytes):
Harald Welte3de6ca22021-05-02 20:05:56 +02001046 """Parse the value part of the DO into the internal state of this instance.
1047 Args:
1048 do : binary encoded bytes
1049 """
1050
1051 @abc.abstractmethod
Harald Welte50368772022-02-10 15:22:22 +01001052 def to_bytes(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001053 """Encode the internal state of this instance into the TLV value part.
1054 Returns:
1055 binary bytes encoding the internal state
1056 """
1057
Harald Weltec91085e2022-02-10 18:05:45 +01001058 def from_tlv(self, do: bytes) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001059 """Parse binary TLV representation into internal state. The resulting decoded
1060 representation is _not_ returned, but just internalized in the object instance!
1061 Args:
1062 do : input bytes containing TLV-encoded representation
1063 Returns:
1064 bytes remaining at end of 'do' after parsing one TLV/DO.
1065 """
1066 if do[0] != self.tag:
Harald Weltec91085e2022-02-10 18:05:45 +01001067 raise ValueError('%s: Can only decode tag 0x%02x' %
1068 (self, self.tag))
Harald Welte3de6ca22021-05-02 20:05:56 +02001069 length = do[1]
1070 val = do[2:2+length]
1071 self.from_bytes(val)
1072 # return remaining bytes
1073 return do[2+length:]
1074
Harald Welte50368772022-02-10 15:22:22 +01001075 def to_tlv(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001076 """Encode internal representation to binary TLV.
1077 Returns:
1078 bytes encoded in TLV format.
1079 """
1080 val = self.to_bytes()
Harald Welte785d4842022-04-05 14:24:22 +02001081 return bertlv_encode_tag(self._compute_tag()) + bertlv_encode_len(len(val)) + val
Harald Welte3de6ca22021-05-02 20:05:56 +02001082
1083 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001084 def decode(self, binary: bytes) -> Tuple[dict, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001085 """Decode a single DOs from the input data.
1086 Args:
1087 binary : binary bytes of encoded data
1088 Returns:
1089 tuple of (decoded_result, binary_remainder)
1090 """
1091 tag = binary[0]
1092 if tag != self.tag:
1093 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected 0x%02x' %
1094 (self, tag, binary, self.tag))
1095 remainder = self.from_tlv(binary)
1096 return (self.to_dict(), remainder)
1097
1098 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001099 def encode(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001100 return self.to_tlv()
1101
Harald Weltec91085e2022-02-10 18:05:45 +01001102
Harald Welte3de6ca22021-05-02 20:05:56 +02001103class TL0_DataObject(DataObject):
1104 """Data Object that has Tag, Len=0 and no Value part."""
Harald Weltec91085e2022-02-10 18:05:45 +01001105
1106 def __init__(self, name: str, desc: str, tag: int, val=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001107 super().__init__(name, desc, tag)
1108 self.val = val
1109
Harald Weltec91085e2022-02-10 18:05:45 +01001110 def from_bytes(self, binary: bytes):
Harald Welte3de6ca22021-05-02 20:05:56 +02001111 if len(binary) != 0:
1112 raise ValueError
1113 self.decoded = self.val
1114
Harald Welte50368772022-02-10 15:22:22 +01001115 def to_bytes(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001116 return b''
1117
1118
1119class DataObjectCollection:
1120 """A DataObjectCollection consits of multiple Data Objects identified by their tags.
1121 A given encoded DO may contain any of them in any order, and may contain multiple instances
1122 of each DO."""
Harald Weltec91085e2022-02-10 18:05:45 +01001123
Harald Welte425038f2022-02-10 19:32:04 +01001124 def __init__(self, name: str, desc: Optional[str] = None, members=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001125 self.name = name
1126 self.desc = desc
1127 self.members = members or []
1128 self.members_by_tag = {}
1129 self.members_by_name = {}
Harald Weltec91085e2022-02-10 18:05:45 +01001130 self.members_by_tag = {m.tag: m for m in members}
1131 self.members_by_name = {m.name: m for m in members}
Harald Welte3de6ca22021-05-02 20:05:56 +02001132
Harald Welte50368772022-02-10 15:22:22 +01001133 def __str__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001134 member_strs = [str(x) for x in self.members]
1135 return '%s(%s)' % (self.name, ','.join(member_strs))
1136
Harald Welte50368772022-02-10 15:22:22 +01001137 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001138 member_strs = [repr(x) for x in self.members]
1139 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1140
Harald Welte50368772022-02-10 15:22:22 +01001141 def __add__(self, other) -> 'DataObjectCollection':
Harald Welte3de6ca22021-05-02 20:05:56 +02001142 """Extending DataCollections with other DataCollections or DataObjects."""
1143 if isinstance(other, DataObjectCollection):
1144 # adding one collection to another
1145 members = self.members + other.members
1146 return DataObjectCollection(self.name, self.desc, members)
1147 elif isinstance(other, DataObject):
1148 # adding a member to a collection
1149 return DataObjectCollection(self.name, self.desc, self.members + [other])
1150 else:
1151 raise TypeError
1152
1153 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001154 def decode(self, binary: bytes) -> Tuple[List, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001155 """Decode any number of DOs from the collection until the end of the input data,
1156 or uninitialized memory (0xFF) is found.
1157 Args:
1158 binary : binary bytes of encoded data
1159 Returns:
1160 tuple of (decoded_result, binary_remainder)
1161 """
1162 res = []
1163 remainder = binary
1164 # iterate until no binary trailer is left
1165 while len(remainder):
1166 tag = remainder[0]
Harald Weltec91085e2022-02-10 18:05:45 +01001167 if tag == 0xff: # uninitialized memory at the end?
Harald Welte3de6ca22021-05-02 20:05:56 +02001168 return (res, remainder)
1169 if not tag in self.members_by_tag:
1170 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1171 (self, tag, remainder, self.members_by_tag.keys()))
1172 obj = self.members_by_tag[tag]
1173 # DO from_tlv returns remainder of binary
1174 remainder = obj.from_tlv(remainder)
1175 # collect our results
1176 res.append(obj.to_dict())
1177 return (res, remainder)
1178
1179 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001180 def encode(self, decoded) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001181 res = bytearray()
1182 for i in decoded:
1183 obj = self.members_by_name(i[0])
1184 res.append(obj.to_tlv())
1185 return res
1186
Harald Weltec91085e2022-02-10 18:05:45 +01001187
Harald Welte3de6ca22021-05-02 20:05:56 +02001188class DataObjectChoice(DataObjectCollection):
1189 """One Data Object from within a choice, identified by its tag.
1190 This means that exactly one member of the choice must occur, and which one occurs depends
1191 on the tag."""
Harald Weltec91085e2022-02-10 18:05:45 +01001192
Harald Welte3de6ca22021-05-02 20:05:56 +02001193 def __add__(self, other):
1194 """We overload the add operator here to avoid inheriting it from DataObjecCollection."""
1195 raise TypeError
1196
Harald Welte50368772022-02-10 15:22:22 +01001197 def __or__(self, other) -> 'DataObjectChoice':
Harald Welte3de6ca22021-05-02 20:05:56 +02001198 """OR-ing a Choice to another choice extends the choice, as does OR-ing a DataObject."""
1199 if isinstance(other, DataObjectChoice):
1200 # adding one collection to another
1201 members = self.members + other.members
1202 return DataObjectChoice(self.name, self.desc, members)
1203 elif isinstance(other, DataObject):
1204 # adding a member to a collection
1205 return DataObjectChoice(self.name, self.desc, self.members + [other])
1206 else:
1207 raise TypeError
1208
1209 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001210 def decode(self, binary: bytes) -> Tuple[dict, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001211 """Decode a single DOs from the choice based on the tag.
1212 Args:
1213 binary : binary bytes of encoded data
1214 Returns:
1215 tuple of (decoded_result, binary_remainder)
1216 """
1217 tag = binary[0]
1218 if tag == 0xff:
1219 return (None, binary)
1220 if not tag in self.members_by_tag:
1221 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1222 (self, tag, binary, self.members_by_tag.keys()))
1223 obj = self.members_by_tag[tag]
1224 remainder = obj.from_tlv(binary)
1225 return (obj.to_dict(), remainder)
1226
1227 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001228 def encode(self, decoded) -> bytes:
Harald Welte785d4842022-04-05 14:24:22 +02001229 obj = self.members_by_name[list(decoded)[0]]
1230 obj.decoded = list(decoded.values())[0]
Harald Welte3de6ca22021-05-02 20:05:56 +02001231 return obj.to_tlv()
1232
Harald Weltec91085e2022-02-10 18:05:45 +01001233
Harald Welte3de6ca22021-05-02 20:05:56 +02001234class DataObjectSequence:
1235 """A sequence of DataObjects or DataObjectChoices. This allows us to express a certain
1236 ordered sequence of DOs or choices of DOs that have to appear as per the specification.
1237 By wrapping them into this formal DataObjectSequence, we can offer convenience methods
1238 for encoding or decoding an entire sequence."""
Harald Weltec91085e2022-02-10 18:05:45 +01001239
Harald Welte425038f2022-02-10 19:32:04 +01001240 def __init__(self, name: str, desc: Optional[str] = None, sequence=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001241 self.sequence = sequence or []
1242 self.name = name
1243 self.desc = desc
1244
Harald Welte50368772022-02-10 15:22:22 +01001245 def __str__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001246 member_strs = [str(x) for x in self.sequence]
1247 return '%s(%s)' % (self.name, ','.join(member_strs))
1248
Harald Welte50368772022-02-10 15:22:22 +01001249 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001250 member_strs = [repr(x) for x in self.sequence]
1251 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1252
Harald Welte50368772022-02-10 15:22:22 +01001253 def __add__(self, other) -> 'DataObjectSequence':
Harald Welte3de6ca22021-05-02 20:05:56 +02001254 """Add (append) a DataObject or DataObjectChoice to the sequence."""
1255 if isinstance(other, 'DataObject'):
Harald Weltec91085e2022-02-10 18:05:45 +01001256 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
Harald Welte3de6ca22021-05-02 20:05:56 +02001257 elif isinstance(other, 'DataObjectChoice'):
Harald Weltec91085e2022-02-10 18:05:45 +01001258 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
Harald Welte3de6ca22021-05-02 20:05:56 +02001259 elif isinstance(other, 'DataObjectSequence'):
Harald Weltec91085e2022-02-10 18:05:45 +01001260 return DataObjectSequence(self.name, self.desc, self.sequence + other.sequence)
Harald Welte3de6ca22021-05-02 20:05:56 +02001261
1262 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001263 def decode(self, binary: bytes) -> Tuple[list, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001264 """Decode a sequence by calling the decoder of each element in the sequence.
1265 Args:
1266 binary : binary bytes of encoded data
1267 Returns:
1268 tuple of (decoded_result, binary_remainder)
1269 """
1270 remainder = binary
1271 res = []
1272 for e in self.sequence:
1273 (r, remainder) = e.decode(remainder)
1274 if r:
1275 res.append(r)
1276 return (res, remainder)
1277
1278 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001279 def decode_multi(self, do: bytes) -> Tuple[list, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001280 """Decode multiple occurrences of the sequence from the binary input data.
1281 Args:
1282 do : binary input data to be decoded
1283 Returns:
1284 list of results of the decoder of this sequences
1285 """
1286 remainder = do
1287 res = []
1288 while len(remainder):
1289 (r, remainder2) = self.decode(remainder)
1290 if r:
1291 res.append(r)
1292 if len(remainder2) < len(remainder):
1293 remainder = remainder2
1294 else:
1295 remainder = remainder2
1296 break
1297 return (res, remainder)
1298
1299 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001300 def encode(self, decoded) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001301 """Encode a sequence by calling the encoder of each element in the sequence."""
1302 encoded = bytearray()
1303 i = 0
1304 for e in self.sequence:
1305 encoded += e.encode(decoded[i])
1306 i += 1
1307 return encoded
Harald Welte90441432021-05-02 21:28:12 +02001308
Harald Welte0dcdfbf2022-04-05 14:42:48 +02001309 def encode_multi(self, decoded) -> bytes:
1310 """Encode multiple occurrences of the sequence from the decoded input data.
1311 Args:
1312 decoded : list of json-serializable input data; one sequence per list item
1313 Returns:
1314 binary encoded output data
1315 """
1316 encoded = bytearray()
1317 for d in decoded:
1318 encoded += self.encode(d)
1319 return encoded
1320
Harald Weltec91085e2022-02-10 18:05:45 +01001321
Harald Welte90441432021-05-02 21:28:12 +02001322class CardCommand:
1323 """A single card command / instruction."""
Harald Weltec91085e2022-02-10 18:05:45 +01001324
Harald Welte90441432021-05-02 21:28:12 +02001325 def __init__(self, name, ins, cla_list=None, desc=None):
1326 self.name = name
1327 self.ins = ins
1328 self.cla_list = cla_list or []
1329 self.cla_list = [x.lower() for x in self.cla_list]
1330 self.desc = desc
1331
1332 def __str__(self):
1333 return self.name
1334
1335 def __repr__(self):
1336 return '%s(INS=%02x,CLA=%s)' % (self.name, self.ins, self.cla_list)
1337
1338 def match_cla(self, cla):
1339 """Does the given CLA match the CLA list of the command?."""
1340 if not isinstance(cla, str):
1341 cla = '%02u' % cla
1342 cla = cla.lower()
1343 for cla_match in self.cla_list:
1344 cla_masked = ""
1345 for i in range(0, 2):
1346 if cla_match[i] == 'x':
1347 cla_masked += 'x'
1348 else:
1349 cla_masked += cla[i]
1350 if cla_masked == cla_match:
1351 return True
1352 return False
1353
1354
1355class CardCommandSet:
1356 """A set of card instructions, typically specified within one spec."""
Harald Weltec91085e2022-02-10 18:05:45 +01001357
Harald Welte90441432021-05-02 21:28:12 +02001358 def __init__(self, name, cmds=[]):
1359 self.name = name
Harald Weltec91085e2022-02-10 18:05:45 +01001360 self.cmds = {c.ins: c for c in cmds}
Harald Welte90441432021-05-02 21:28:12 +02001361
1362 def __str__(self):
1363 return self.name
1364
1365 def __getitem__(self, idx):
1366 return self.cmds[idx]
1367
1368 def __add__(self, other):
1369 if isinstance(other, CardCommand):
1370 if other.ins in self.cmds:
1371 raise ValueError('%s: INS 0x%02x already defined: %s' %
1372 (self, other.ins, self.cmds[other.ins]))
1373 self.cmds[other.ins] = other
1374 elif isinstance(other, CardCommandSet):
1375 for c in other.cmds.keys():
1376 self.cmds[c] = other.cmds[c]
1377 else:
Harald Weltec91085e2022-02-10 18:05:45 +01001378 raise ValueError(
1379 '%s: Unsupported type to add operator: %s' % (self, other))
Harald Welte90441432021-05-02 21:28:12 +02001380
1381 def lookup(self, ins, cla=None):
1382 """look-up the command within the CommandSet."""
1383 ins = int(ins)
1384 if not ins in self.cmds:
1385 return None
1386 cmd = self.cmds[ins]
1387 if cla and not cmd.match_cla(cla):
1388 return None
1389 return cmd
Philipp Maiera028c7d2021-11-08 16:12:03 +01001390
Harald Weltec91085e2022-02-10 18:05:45 +01001391
Philipp Maiera028c7d2021-11-08 16:12:03 +01001392def all_subclasses(cls) -> set:
Harald Weltec91085e2022-02-10 18:05:45 +01001393 """Recursively get all subclasses of a specified class"""
1394 return set(cls.__subclasses__()).union([s for c in cls.__subclasses__() for s in all_subclasses(c)])
Harald Weltef9ea63e2023-11-01 23:35:31 +01001395
1396def is_hexstr_or_decimal(instr: str) -> str:
1397 """Method that can be used as 'type' in argparse.add_argument() to validate the value consists of
1398 [hexa]decimal digits only."""
1399 if instr.isdecimal():
1400 return instr
1401 if not all(c in string.hexdigits for c in instr):
1402 raise ValueError('Input must be [hexa]decimal')
1403 if len(instr) & 1:
1404 raise ValueError('Input has un-even number of hex digits')
1405 return instr
Harald Welte4e59d892023-11-01 23:40:07 +01001406
1407def is_hexstr(instr: str) -> str:
1408 """Method that can be used as 'type' in argparse.add_argument() to validate the value consists of
1409 an even sequence of hexadecimal digits only."""
1410 if not all(c in string.hexdigits for c in instr):
1411 raise ValueError('Input must be hexadecimal')
1412 if len(instr) & 1:
1413 raise ValueError('Input has un-even number of hex digits')
1414 return instr
Harald Welte1c849f82023-11-01 23:48:28 +01001415
1416def is_decimal(instr: str) -> str:
1417 """Method that can be used as 'type' in argparse.add_argument() to validate the value consists of
1418 an even sequence of decimal digits only."""
1419 if not instr.isdecimal():
1420 raise ValueError('Input must decimal')
1421 return instr